├── LICENSE ├── Makefile ├── README.md ├── debian ├── changelog ├── compat ├── control ├── copyright ├── kdesk-dbg.install ├── kdesk.install ├── libkdesk-dev.install └── rules ├── doc └── config │ ├── .kdeskrc │ ├── install-snake.lnk │ ├── install-updater.lnk │ └── terminal.lnk ├── src ├── Makefile ├── background.cpp ├── background.h ├── configuration.cpp ├── configuration.h ├── desktop.cpp ├── desktop.h ├── grid.cpp ├── grid.h ├── icon.cpp ├── icon.h ├── kdesk-blur │ ├── Makefile │ ├── kdesk-blur.cpp │ └── kdesk-blur.h ├── kdesk-eglsaver │ ├── Makefile │ ├── README.md │ ├── bitmap_homefolder.raw │ ├── bitmap_minecraft.raw │ ├── bitmap_pong.raw │ ├── cube_texture_and_coords.h │ ├── hid.cpp │ ├── hid.h │ └── kdesk-eglsaver.c ├── kfbsaver │ ├── Makefile │ ├── README │ └── kfbsaver.cpp ├── libkdesk-hourglass │ ├── Makefile │ ├── kdesk-hourglass-app.cpp │ ├── kdesk-hourglass.cpp │ ├── kdesk-hourglass.h │ ├── python │ │ ├── __init__.py │ │ └── hourglass.py │ ├── sn_callbacks.cpp │ ├── testdynlib.cpp │ ├── testlib.py │ ├── testmodule.py │ └── testshared.cpp ├── logging.h ├── main.cpp ├── main.h ├── sound.cpp ├── sound.h ├── ssaver.cpp ├── ssaver.h └── version.h └── tests ├── configurations └── kdeskrc_test └── test_configuration.py /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Makefile - builds kdesk 3 | # 4 | # Please read README.md file for differences on release and debug builds 5 | # 6 | 7 | all: kdesk 8 | 9 | kdesk: 10 | cd src/libkdesk-hourglass && make all 11 | 12 | cd src && make -B 13 | cd src && make debug -B 14 | 15 | cd src/kdesk-eglsaver && make all 16 | cd src/kdesk-blur && make all 17 | 18 | debug: 19 | cd src/libkdesk-hourglass && make all 20 | cd src && make debug -B 21 | cd src/kdesk-eglsaver && make debug 22 | cd src/kdesk-blur && make debug 23 | 24 | kano-debber: 25 | mkdir -p /home/user/.kdesktop 26 | cp doc/config/.kdeskrc /home/user/ 27 | cp doc/config/*.lnk /home/user/.kdesktop/ 28 | 29 | sed -i.bak 's/@pcmanfm --desktop --profile LXDE/@kdesk/' /etc/xdg/lxsession/LXDE/autostart 30 | 31 | chown -R user:user /home/user/.kdesk* 32 | cd src && make all 33 | cd src/kdesk-blur && make all 34 | cd src/libkdesk-hourglass && make all 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Kano Desktop manager - kdesk 2 | 3 | 4 | Kano Desktop Manager, aka *kdesk*, is a lightweight desktop manager optimized for Kano OS and other Debian-based systems, including the desktop background image, icons, and egl screensaver. 5 | 6 | We’ve created the utility from scratch, with inspiration taken from other desktop managers, especially [idesk](https://github.com/neagix/idesk) and [vdesk](http://xvnkb.sourceforge.net/?menu=vdesk&lang=en). 7 | 8 | Features: 9 | * optimized connection with the notify library for Openbox window manager and others, so the hourglass start-up feature works seamlessly 10 | * 100% customizable background, icons, screensaver 11 | * efficient screensaver with low CPU usage 12 | 13 | Check all the detailed information in the [wiki](https://github.com/KanoComputing/kdesk/wiki) 14 | 15 | 16 | [Introduction](https://github.com/KanoComputing/kdesk/wiki/Introduction) 17 | [Development](https://github.com/KanoComputing/kdesk/wiki/Development) 18 | [Collaboration](https://github.com/KanoComputing/kdesk/wiki/Collaboration) 19 | 20 | 21 | ### Localization 22 | 23 | kdesk supports localization of two icon file types: `Icon` and `IconHover`. 24 | 25 | In your `lnk` icon files, simply set a path to the default international version of the asset, 26 | and kdesk will try to find the localized version of it on the fly, the lnk file will not be modified. 27 | For example, provided your LANG is set to `es_AR.UTF-8`: 28 | 29 | ``` 30 | Icon: /usr/share/my-app/icons/app.png 31 | IconHover: /usr/share/my-app/hovers/app-hover.png 32 | ``` 33 | 34 | Would be translated by kdesk to: 35 | 36 | ``` 37 | Icon: /usr/share/my-app/icons/i18n/es_AR/app.png 38 | IconHover: /usr/share/my-app/hovers/i18n/es_AR/app-hover.png 39 | ``` 40 | 41 | If the localized icon file cannot be found, kdesk will fallback to load the original one. 42 | The `es_AR` will be obtained by querying the `LANG` environment variable, you can easily force it to test new locales. 43 | Testing the configuration on the debug version will tell you which icons are localized or missing: 44 | 45 | ``` 46 | LANG=zh_TW.Big5 ./kdesk-dbg -t | grep i18n 47 | ``` 48 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | kdesk (4.0.1-0) unstable; urgency=low 2 | 3 | * Fixed a memory leak that stopped screen saver from working 4 | 5 | -- Team Kano Tue, 29 May 2018 15:27:00 +0100 6 | 7 | kdesk (4.0.0-0) unstable; urgency=low 8 | 9 | * Build for Debian Stretch 10 | * Added flag -m to disable MIT-SHM XServer extension 11 | 12 | -- Team Kano Tue, 22 May 2018 14:46:00 +0100 13 | 14 | kdesk (1.6-3) unstable; urgency=low 15 | 16 | * Added support for localizing desktop icons 17 | 18 | -- Team Kano Mon, 20 Feb 2017 15:33:00 +0100 19 | 20 | kdesk (1.5-3) unstable; urgency=low 21 | 22 | * Added screen saver run mode with hooks, and no icons loaded 23 | 24 | -- Team Kano Thu, 26 May 2016 17:58:00 +0100 25 | 26 | kdesk (1.4-3) unstable; urgency=low 27 | 28 | * Added automatic conversion and caching of SVG icons 29 | 30 | -- Team Kano Mon, 18 Jan 2016 14:17:00 +0100 31 | 32 | kdesk (1.3-3) unstable; urgency=low 33 | 34 | * Added command to find icon positions on the desktop (-j) 35 | 36 | -- Team Kano Fri, 8 Jan 2016 12:35:00 +0000 37 | 38 | kdesk (1.3-2) unstable; urgency=low 39 | 40 | * Roll back to the grid to accomodate 7 icons per row 41 | 42 | -- Team Kano Fri, 13 Feb 2015 10:29:00 +0000 43 | 44 | kdesk (1.3-1) unstable; urgency=low 45 | 46 | * The grid can now accomodate 8 icons per row 47 | 48 | -- Team Kano Tue, 9 Dec 2014 10:29:00 +0000 49 | 50 | kdesk (1.1-9) unstable; urgency=low 51 | 52 | * kdesk-hourglass provides a new API based on the app command line only 53 | 54 | -- Team Kano Wed, 12 Nov 2014 15:20:00 +0000 55 | 56 | kdesk (1.1-8) unstable; urgency=low 57 | 58 | * kdesk now maintains a file with the number of icons on the desktop 59 | 60 | -- Team Kano Tue, 15 Jul 2014 18:29:15 +0001 61 | 62 | kdesk (1.1-07) unstable; urgency=low 63 | 64 | * Making the kdesk package replace idesk 65 | 66 | -- Team Kano Mon, 17 Mar 2014 11:05:10 +0000 67 | 68 | kdesk (1.1-06) unstable; urgency=low 69 | 70 | * Package rebuilt, updated to revision 2a6ea088. 71 | 72 | -- Team Kano Mon, 10 Mar 2014 17:36:51 +0000 73 | 74 | kdesk (1.1-05) unstable; urgency=low 75 | 76 | * Package rebuilt, updated to revision 2a6ea088. 77 | 78 | -- Team Kano Mon, 10 Mar 2014 17:27:29 +0000 79 | 80 | kdesk (1.1-04) unstable; urgency=low 81 | 82 | * Package rebuilt, updated to revision 3c2e8089. 83 | 84 | -- Team Kano Mon, 10 Mar 2014 17:14:47 +0000 85 | 86 | kdesk (1.1-03) unstable; urgency=low 87 | 88 | * Package rebuilt, updated to revision 3c2e8089. 89 | 90 | -- Team Kano Mon, 10 Mar 2014 17:01:37 +0000 91 | 92 | kdesk (1.1-02) unstable; urgency=low 93 | 94 | * Package rebuilt, updated to revision d5e09a42. 95 | 96 | -- Team Kano Fri, 07 Mar 2014 21:06:14 +0000 97 | 98 | kdesk (1.1-01) unstable; urgency=low 99 | 100 | * Package rebuilt, updated to revision a9a7cfc0. 101 | 102 | -- Team Kano Fri, 07 Mar 2014 14:56:58 +0000 103 | 104 | kdesk (1.1-00) unstable; urgency=low 105 | 106 | * Initial release. 107 | 108 | -- Team Kano Wed, 13 Sep 2013 15:10:03 +0000 109 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 9 2 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: kdesk 2 | Maintainer: Team Kano 3 | Section: x11 4 | Priority: optional 5 | Standards-Version: 1.1.0 6 | Build-Depends: libx11-dev, libxft-dev, libimlib2-dev, libstartup-notification0-dev, libxss-dev, libraspberrypi-dev, debhelper (>=9.0.0), g++-4.7 7 | 8 | Package: kdesk 9 | Architecture: any 10 | Depends: libkdesk-dev (= ${binary:Version}), ${misc:Depends}, ${shlibs:Depends}, libstartup-notification0, libxss1, librsvg2-bin 11 | Provides: idesk 12 | Conflicts: idesk 13 | Replaces: idesk 14 | Description: Kanux desktop icon manager 15 | This package provides the Kanux graphical desktop facade. 16 | It presents the Kanux background along with all Kano-Make icons organized 17 | so you can follow all learning experience apps: Make Pong, Minecraft, Video 18 | and surf the rest of system applications directly from the desktop 19 | 20 | Package: libkdesk-dev 21 | Architecture: any 22 | Depends: ${misc:Depends}, ${shlibs:Depends}, libstartup-notification0, python 23 | Description: Kanux desktop hourglass developer library 24 | This package contains the library, include file and sample source code 25 | to provide your applications with an hourglass for desktop lengthy tasks. 26 | 27 | Package: kdesk-dbg 28 | Architecture: any 29 | Depends: libkdesk-dev (= ${binary:Version}), ${misc:Depends}, ${shlibs:Depends}, libstartup-notification0, libxss1 30 | Description: Kanux desktop icon manager (Debug version) 31 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Files: * 2 | Copyright: 2014-2016 Kano Computing Ltd. 3 | License: GPL-2+ 4 | -------------------------------------------------------------------------------- /debian/kdesk-dbg.install: -------------------------------------------------------------------------------- 1 | src/kdesk-dbg /usr/bin 2 | -------------------------------------------------------------------------------- /debian/kdesk.install: -------------------------------------------------------------------------------- 1 | src/kdesk /usr/bin 2 | src/kdesk-blur/kdesk-blur /usr/bin 3 | src/kdesk-eglsaver/kdesk-eglsaver /usr/bin 4 | -------------------------------------------------------------------------------- /debian/libkdesk-dev.install: -------------------------------------------------------------------------------- 1 | src/libkdesk-hourglass/libkdesk-hourglass.so /usr/lib 2 | src/libkdesk-hourglass/kdesk-hourglass.h /usr/include/ 3 | src/libkdesk-hourglass/testlib.py /usr/share/doc/kdesk 4 | src/libkdesk-hourglass/testmodule.py /usr/share/doc/kdesk 5 | src/libkdesk-hourglass/testdynlib.cpp /usr/share/doc/kdesk 6 | src/libkdesk-hourglass/testshared.cpp /usr/share/doc/kdesk 7 | src/libkdesk-hourglass/python/* /usr/lib/python2.7/dist-packages/kdesk 8 | src/libkdesk-hourglass/kdesk-hourglass-app /usr/bin 9 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | %: 4 | dh $@ 5 | 6 | override_dh_auto_build: 7 | # FIXME: The chroot environment should handle these links 8 | ln -sfv -r /opt/vc/lib/libbrcmEGL.so /opt/vc/lib/libEGL.so 9 | ln -sfv -r /opt/vc/lib/libbrcmGLESv2.so /opt/vc/lib/libGLESv2.so 10 | make kdesk 11 | 12 | override_dh_strip: 13 | # don't strip symbols off libkdesk-hourglass.so 14 | # FIXME: find a better way to resolve this issue 15 | 16 | override_dh_shlibdeps: 17 | # same rules apply as for override_dh_strip, so we provide exported library symbols 18 | -------------------------------------------------------------------------------- /doc/config/.kdeskrc: -------------------------------------------------------------------------------- 1 | table Config 2 | 3 | FontName: Bariol 4 | FontSize: 10 5 | FontColor: #FFFFFF 6 | ToolTip.FontSize: 10 7 | ToolTip.FontName: Bariol 8 | ToolTip.ForeColor: #0000FF 9 | ToolTip.BackColor: #FFFFFF 10 | ToolTip.CaptionOnHover: false 11 | ToolTip.CaptionPlacement: Right 12 | Locked: true 13 | 14 | EnableSound: false 15 | #SoundWelcome: /usr/share/kano-media/sounds/kano_boot_up.wav 16 | #SoundLaunchApp: /usr/share/kano-media/sounds/kano_open_app.wav 17 | #SoundDisabledIcon: /usr/share/kano-media/sounds/kano_error.wav 18 | 19 | ScreenSaverTimeout: 0 20 | #ScreenSaverProgram: /usr/bin/kdesk-eglsaver 21 | 22 | OneClick: true 23 | 24 | Transparency: 255 25 | Shadow: true 26 | ShadowColor: #000000 27 | ShadowX: 1 28 | ShadowY: 1 29 | Bold: true 30 | ClickDelay: 300 31 | IconStartDelay: 5000 32 | IconSnap: true 33 | IconTitleGap: 20 34 | SnapWidth: 10 35 | SnapHeight: 10 36 | SnapOrigin: BottomRight 37 | SnapShadow: false 38 | SnapShadowTrans: 200 39 | CaptionOnHover: false 40 | CaptionPlacement: bottom 41 | FillStyle: None 42 | Background.Delay: 0 43 | Background.Source: / 44 | 45 | ScreenMedResWidth: 1900 46 | Background.File-medium: /usr/share/lxde/wallpapers/kanux-background-1024.png 47 | Background.File-4-3: /usr/share/lxde/wallpapers/kanux-background-4-3.jpg 48 | Background.File-16-9: /usr/share/lxde/wallpapers/kanux-background-16-9.jpg 49 | Background.Mode: scale 50 | Background.Color: #C2CCFF 51 | end 52 | 53 | table Actions 54 | # Lock: control right doubleClk 55 | # Reload: middle doubleClk 56 | # Drag: left hold 57 | # EndDrag: left singleClk 58 | 59 | Execute[0]: left doubleClk 60 | 61 | # Execute[1]: right doubleClk 62 | end 63 | -------------------------------------------------------------------------------- /doc/config/install-snake.lnk: -------------------------------------------------------------------------------- 1 | table Icon 2 | Caption: Snake 3 | Command: lxterminal -e "sudo /home/user/install-snake.sh" 4 | Singleton: true 5 | Icon: /usr/share/icons/kano/snake.png 6 | HoverXOffset: 0 7 | Width: 64 8 | Height: 64 9 | Relative-To: top-left 10 | X: 30 11 | Y: 30 12 | end 13 | -------------------------------------------------------------------------------- /doc/config/install-updater.lnk: -------------------------------------------------------------------------------- 1 | table Icon 2 | Caption: Updater 3 | Command: lxterminal -e "sudo /home/user/install-updater.sh" 4 | Singleton: true 5 | Icon: /usr/share/icons/kano/updater.png 6 | HoverXOffset: 0 7 | Width: 65 8 | Height: 65 9 | Relative-To: top-left 10 | X: 120 11 | Y: 30 12 | end 13 | -------------------------------------------------------------------------------- /doc/config/terminal.lnk: -------------------------------------------------------------------------------- 1 | table Icon 2 | Caption: Shell 3 | Command: lxterminal 4 | AppID: lxtermina[l] 5 | Singleton: true 6 | Icon: /usr/share/icons/gnome/48x48/apps/terminal.png 7 | HoverXOffset: 0 8 | Width: 50 9 | Height: 50 10 | Relative-To: top-left 11 | X: 220 12 | Y: 30 13 | end 14 | -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Build kano-desktop 3 | # 4 | # $ make - Builds the release version 5 | # $ make debug - Debug version (kdesk-dbg binary) 6 | # 7 | # 8 | 9 | DEBUGGING:= 10 | 11 | LIBS:=-lXft -lImlib2 -lstdc++ -lpthread -lX11 -lXss -L`pwd`/libkdesk-hourglass -lkdesk-hourglass 12 | XFTINC:=-I/usr/include/freetype2 13 | HOURGLASSINCS= -I`pwd`/libkdesk-hourglass 14 | 15 | CFLAGS=-std=c++11 16 | 17 | TARGET=kdesk 18 | 19 | .PHONY: clean 20 | 21 | all: $(TARGET) 22 | 23 | debug: 24 | make all DEBUGGING="-ggdb -DDEBUG" TARGET=kdesk-dbg 25 | 26 | # the linkage 27 | $(TARGET): main.o icon.o grid.o background.o configuration.o desktop.o sound.o ssaver.o 28 | $(CXX) $(LIBS) $^ -o $(TARGET) 29 | 30 | # the compilation 31 | icon.o: icon.cpp icon.h logging.h configuration.h grid.h 32 | $(CXX) -c $(CFLAGS) $(DEBUGGING) $(XFTINC) icon.cpp 33 | 34 | grid.o: grid.cpp grid.h 35 | $(CXX) -c $(CFLAGS) $(DEBUGGING) $(XFTINC) grid.cpp 36 | 37 | main.o: main.cpp main.h configuration.h logging.h version.h ssaver.h 38 | $(CXX) -c $(CFLAGS) $(DEBUGGING) $(XFTINC) main.cpp 39 | 40 | background.o: background.cpp logging.h sound.h 41 | $(CXX) -c $(CFLAGS) $(DEBUGGING) background.cpp 42 | 43 | configuration.o: configuration.cpp configuration.h logging.h main.h 44 | $(CXX) -c $(CFLAGS) $(DEBUGGING) configuration.cpp 45 | 46 | desktop.o: desktop.cpp desktop.h logging.h configuration.h sound.h grid.h 47 | $(CXX) -c $(CFLAGS) $(DEBUGGING) $(XFTINC) $(HOURGLASSINCS) desktop.cpp 48 | 49 | sound.o: sound.cpp sound.h 50 | $(CXX) -c $(CFLAGS) $(DEBUGGING) sound.cpp 51 | 52 | ssaver.o: ssaver.cpp ssaver.h 53 | $(CXX) -c $(CFLAGS) $(DEBUGGING) ssaver.cpp 54 | 55 | clean: 56 | -rm *o kdesk kdesk-dbg 57 | -------------------------------------------------------------------------------- /src/background.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // background.cpp - Class to draw an icon on the desktop 3 | // 4 | // Copyright (C) 2013-2014 Kano Computing Ltd. 5 | // License: http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2 6 | // 7 | // An app to show and bring life to Kano-Make Desktop Icons. 8 | // 9 | 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | #include "configuration.h" 18 | #include "background.h" 19 | #include "logging.h" 20 | 21 | Background::Background (Configuration *loaded_conf) 22 | { 23 | running = false; 24 | pconf = loaded_conf; 25 | } 26 | 27 | Background::~Background (void) 28 | { 29 | } 30 | 31 | bool Background::setup(Display *display) 32 | { 33 | int i, screen; 34 | 35 | // Setup X resources 36 | screen = DefaultScreen (display); 37 | vis = DefaultVisual (display, screen); 38 | cm = DefaultColormap (display, screen); 39 | root = RootWindow (display, screen); 40 | XSelectInput (display, root, StructureNotifyMask); 41 | 42 | deskw = DisplayWidth(display, screen); 43 | deskh = DisplayHeight(display, screen); 44 | 45 | // Bind resources to Imlib2 46 | imlib_context_set_display(display); 47 | imlib_context_set_visual(vis); 48 | imlib_context_set_colormap(cm); 49 | 50 | pmap = XCreatePixmap (display, root, deskw, deskh, DefaultDepth (display, DefaultScreen (display))); 51 | if (!pmap) { 52 | log ("error creating pixmap for desktop background"); 53 | return false; 54 | } 55 | 56 | return true; 57 | } 58 | 59 | bool Background::load (Display *display) 60 | { 61 | int i, w, h, nx, ny, nh, nw, tmp; 62 | float ratio, dist43, dist169; 63 | double factor; 64 | string background_file; 65 | Imlib_Image tmpimg, buffer; 66 | bool bsuccess=false; 67 | 68 | buffer = imlib_create_image (deskw, deskh); 69 | if (!buffer) 70 | { 71 | log ("error creating an image surface for the background"); 72 | } 73 | else 74 | { 75 | ratio = (deskw *1.0) / (deskh); 76 | dist43 = std::abs (ratio - 4.0/3.0); 77 | dist169 = std::abs (ratio - 16.0/9); 78 | 79 | // Decide which image to load depending on screen resolution and aspect/ratio 80 | unsigned int midreswidth=pconf->get_config_int ("screenmedreswidth"); 81 | if (midreswidth > 0 && deskw < midreswidth) { 82 | // If a minimal medium resolution is specified, and 83 | // the screen resolution falls below this setting, then 84 | // Take the middle resolution wallpaper image 85 | background_file = pconf->get_config_string ("background.file-medium"); 86 | log1 ("loading medium resolution wallpaper image", background_file); 87 | } 88 | else { 89 | // 90 | // Otherwise we are on a high resolution screen, 91 | // display the appropiate wallpaper based on the screen aspect/ratio. 92 | // 93 | if (dist43 < dist169) { 94 | background_file = pconf->get_config_string ("background.file-4-3"); 95 | log1 ("loading 4:3 wallpaper image", background_file); 96 | } 97 | else { 98 | background_file = pconf->get_config_string ("background.file-16-9"); 99 | log1 ("loading 16:9 wallpaper image", background_file); 100 | } 101 | } 102 | 103 | image = imlib_load_image_without_cache(background_file.c_str()); 104 | if (!image) { 105 | log1 ("error loading background", background_file); 106 | } 107 | else { 108 | // Prepare imlib2 drawing spaces 109 | imlib_context_set_image(buffer); 110 | imlib_context_set_color(0,0,0,0); 111 | imlib_image_fill_rectangle(0, 0, deskw, deskh); 112 | imlib_context_set_blend(1); 113 | 114 | imlib_context_set_image(image); 115 | w = imlib_image_get_width(); 116 | h = imlib_image_get_height(); 117 | if (!(tmpimg = imlib_clone_image())) { 118 | log ("error cloning the desktop background image"); 119 | } 120 | else { 121 | imlib_context_set_image(tmpimg); 122 | imlib_context_set_image(buffer); 123 | 124 | int screen_num = DefaultScreen(display); 125 | int nw = DisplayWidth(display, screen_num); 126 | int nh = DisplayHeight(display, screen_num); 127 | 128 | imlib_blend_image_onto_image(tmpimg, 0, 0, 0, w, h, 0, 0, nw, nh); 129 | imlib_context_set_image(tmpimg); 130 | imlib_free_image(); 131 | 132 | imlib_context_set_blend(0); 133 | imlib_context_set_image(buffer); 134 | imlib_context_set_drawable(root); 135 | imlib_render_image_on_drawable(0, 0); 136 | imlib_context_set_drawable(pmap); 137 | imlib_render_image_on_drawable(0, 0); 138 | XSetWindowBackgroundPixmap(display, root, pmap); 139 | 140 | // Apply the background to the root window 141 | // so it stays permanently even if kdesk quits (-w parameter) 142 | XClearWindow (display, root); 143 | XFlush (display); 144 | 145 | // Free imlib and Xlib image resources 146 | imlib_context_set_image(buffer); 147 | imlib_free_image(); 148 | imlib_context_set_image(image); 149 | imlib_free_image(); 150 | 151 | XFreePixmap(display, pmap); 152 | 153 | bsuccess = true; 154 | log1 ("desktop background created successfully", image); 155 | } 156 | } 157 | } 158 | 159 | return bsuccess; 160 | } 161 | 162 | int Background::refresh_background(Display *display) 163 | { 164 | Window root_return, parent_return, *children_return; 165 | unsigned int nchildren_return=0; 166 | 167 | // Enumerate all top level windows and request a repaint 168 | // to refresh transparent areas with the new background. 169 | if (XQueryTree(display, root, &root_return, &parent_return, &children_return, &nchildren_return)) 170 | { 171 | for (int i=0; i < nchildren_return; i++) 172 | { 173 | // clear the window area 174 | XClearWindow (display, children_return[i]); 175 | 176 | // repaint 177 | XEvent exppp; 178 | memset(&exppp, 0x00, sizeof(exppp)); 179 | exppp.type = Expose; 180 | exppp.xexpose.window = children_return[i]; 181 | XSendEvent (display, children_return[i], False, ExposureMask, &exppp); 182 | XFlush(display); 183 | } 184 | } 185 | 186 | return nchildren_return; 187 | } 188 | -------------------------------------------------------------------------------- /src/background.h: -------------------------------------------------------------------------------- 1 | // 2 | // background.cpp - Class to draw an icon on the desktop 3 | // 4 | // Copyright (C) 2013-2014 Kano Computing Ltd. 5 | // License: http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2 6 | // 7 | // An app to show and bring life to Kano-Make Desktop Icons. 8 | // 9 | 10 | class Background 11 | { 12 | private: 13 | Configuration *pconf; 14 | Window root; 15 | Visual *vis; 16 | Colormap cm; 17 | Pixmap pmap; 18 | Imlib_Image image; 19 | unsigned int deskw, deskh; 20 | 21 | public: 22 | bool running; 23 | 24 | public: 25 | Background (Configuration *loaded_conf); 26 | virtual ~Background (void); 27 | 28 | bool setup (Display *display); 29 | bool load (Display *display); 30 | bool draw (Display *display); 31 | int refresh_background(Display *display); 32 | 33 | }; 34 | -------------------------------------------------------------------------------- /src/configuration.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // config.cpp 3 | // 4 | // Copyright (C) 2013-2014 Kano Computing Ltd. 5 | // License: http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2 6 | // 7 | // An app to show and bring life to Kano-Make Desktop Icons. 8 | // 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include "main.h" 19 | #include "configuration.h" 20 | #include "logging.h" 21 | 22 | // Name of the reserved icon filename which will always 23 | // be positioned at the last cell of the grid 24 | char *kdesk_plus_icon_filename=NULL; 25 | 26 | 27 | Configuration::Configuration() 28 | { 29 | numicons=0; 30 | } 31 | 32 | Configuration::~Configuration() 33 | { 34 | } 35 | 36 | bool Configuration::load_conf(const char *filename) 37 | { 38 | string token, value; 39 | struct stat file_status; 40 | 41 | // Check that configuration is a regular file 42 | stat (filename, &file_status); 43 | if (!S_ISREG(file_status.st_mode)) { 44 | return false; 45 | } 46 | 47 | // Open it for reading 48 | ifile.open (filename, std::ifstream::in); 49 | if (ifile.fail()) { 50 | return false; 51 | } 52 | 53 | // separate parameter - value tokens 54 | // in the form "Parameter: Value", one per line 55 | while (ifile >> token) 56 | { 57 | if (token == "FontName:") { 58 | ifile >> value; 59 | configuration["fontname"] = value; 60 | } 61 | 62 | if (token == "FontColor:") { 63 | ifile >> value; 64 | configuration["fontcolor"] = value; 65 | } 66 | 67 | if (token == "FontSize:") { 68 | ifile >> value; 69 | configuration["fontsize"] = value; 70 | } 71 | 72 | if (token == "SubtitleFontSize:") { 73 | ifile >> value; 74 | configuration["subtitlefontsize"] = value; 75 | } 76 | 77 | if (token == "Bold:") { 78 | ifile >> value; 79 | configuration["bold"] = value; 80 | } 81 | 82 | if (token == "Shadow:") { 83 | ifile >> value; 84 | configuration["shadow"] = value; 85 | } 86 | 87 | if (token == "ShadowColor:") { 88 | ifile >> value; 89 | configuration["shadowcolor"] = value; 90 | } 91 | 92 | if (token == "ShadowX:") { 93 | ifile >> value; 94 | configuration["shadowx"] = value; 95 | } 96 | 97 | if (token == "ShadowY:") { 98 | ifile >> value; 99 | configuration["shadowy"] = value; 100 | } 101 | 102 | if (token == "Background.File-4-3:") { 103 | value = get_spaced_value(); 104 | configuration["background.file-4-3"] = value; 105 | } 106 | 107 | if (token == "Background.File-16-9:") { 108 | value = get_spaced_value(); 109 | configuration["background.file-16-9"] = value; 110 | } 111 | 112 | if (token == "ClickDelay:") { 113 | ifile >> value; 114 | configuration["clickdelay"] = value; 115 | } 116 | 117 | if (token == "DefaultDesktopIcon:") { 118 | // Points to a png filename to be used if any lnk icons are not found. 119 | ifile >> value; 120 | configuration["defaultdesktopicon"] = value; 121 | } 122 | 123 | if (token == "IconStartDelay:") { 124 | ifile >> value; 125 | configuration["iconstartdelay"] = value; 126 | } 127 | 128 | if (token == "IconTitleGap:") { 129 | ifile >> value; 130 | configuration["icontitlegap"] = value; 131 | } 132 | 133 | if (token == "IconGapHorz:") { 134 | ifile >> value; 135 | configuration["icongaphorz"] = value; 136 | } 137 | 138 | if (token == "IconGapVert:") { 139 | ifile >> value; 140 | configuration["icongapvert"] = value; 141 | } 142 | 143 | if (token == "Transparency:") { 144 | ifile >> value; 145 | configuration["transparency"] = value; 146 | } 147 | 148 | if (token == "EnableSound:") { 149 | ifile >> value; 150 | configuration["enablesound"] = value; 151 | } 152 | 153 | if (token == "SoundWelcome:") { 154 | ifile >> value; 155 | configuration["soundwelcome"] = value; 156 | } 157 | 158 | if (token == "SoundLaunchApp:") { 159 | ifile >> value; 160 | configuration["soundlaunchapp"] = value; 161 | } 162 | 163 | if (token == "SoundDisabledIcon:") { 164 | ifile >> value; 165 | configuration["sounddisabledicon"] = value; 166 | } 167 | 168 | if (token == "Background.Delay:") { 169 | ifile >> value; 170 | configuration["background.delay"] = value; 171 | } 172 | 173 | if (token == "ScreenSaverTimeout:") { 174 | ifile >> value; 175 | configuration["screensavertimeout"] = value; 176 | log1("found ScreenSaverTimeout:", value) 177 | } 178 | 179 | if (token == "ScreenSaverProgram:") { 180 | ifile >> value; 181 | configuration["screensaverprogram"] = value; 182 | log1("found ScreenSaverProgram:", value) 183 | } 184 | 185 | if (token == "OneClick:") { 186 | ifile >> value; 187 | configuration["oneclick"] = value; 188 | } 189 | 190 | if (token == "ScreenMedResWidth:") { 191 | ifile >> value; 192 | configuration["screenmedreswidth"] = value; 193 | } 194 | 195 | if (token == "Background.File-medium:") { 196 | value = get_spaced_value(); 197 | configuration["background.file-medium"] = value; 198 | } 199 | 200 | if (token == "MouseHoverIcon:") { 201 | ifile >> value; 202 | configuration["mousehovericon"] = value; 203 | } 204 | 205 | if (token == "IconHook:") { 206 | ifile >> value; 207 | configuration["iconhook"] = value; 208 | } 209 | 210 | if (token == "GridWidth:") { 211 | ifile >> value; 212 | configuration["gridwidth"] = value; 213 | } 214 | 215 | if (token == "GridHeight:") { 216 | ifile >> value; 217 | configuration["gridheight"] = value; 218 | } 219 | 220 | if (token == "GridIconWidth:") { 221 | ifile >> value; 222 | configuration["gridiconwidth"] = value; 223 | } 224 | 225 | if (token == "GridIconHeight:") { 226 | ifile >> value; 227 | configuration["gridiconheight"] = value; 228 | } 229 | 230 | if (token == "MaximizeSingleton:") { 231 | ifile >> value; 232 | configuration["maximizesingleton"] = value; 233 | } 234 | 235 | if (token == "ImageCacheSize:") { 236 | ifile >> value; 237 | configuration["imagecachesize"] = value; 238 | } 239 | 240 | if (token == "LastGridIcon:") { 241 | ifile >> value; 242 | configuration["lastgridicon"] = value; 243 | 244 | // This icon filename will be used to make sure 245 | // it is the last icon to position in the grid 246 | kdesk_plus_icon_filename = (char *) configuration["lastgridicon"].c_str(); 247 | } 248 | 249 | } 250 | 251 | ifile.close(); 252 | return true; 253 | } 254 | 255 | /* 256 | * If icon_filename is a svg file, it is rasterized into a png thanks to the svrg-convert tool, 257 | * saved in the user's home cache directory, and replaced automatically. The path to the cached 258 | * png file is returned. If the icon is already cached, the complete filename will be returned. 259 | * 260 | * For regular raster icons, if it cannot be found, a default one will be provided (DefaulDesktopIcon 261 | * in the .kdeskrc file). The same rule will apply if an error ocurrs converting svg files. 262 | * 263 | * If no conversion / default icon is needed, an empty string will be provided. 264 | * 265 | * This function assumes that the icon filenames are suffixed with correct extensions in lowercase (.svg, .png) 266 | * 267 | */ 268 | string Configuration::convert_svg(string icon_filename) 269 | { 270 | string svg_extension=".svg"; 271 | string converted; 272 | 273 | if (svg_extension.size() > icon_filename.size()) 274 | return converted; 275 | 276 | // if this is a SVG file, proceed with cache a converted version 277 | if (std::equal(svg_extension.rbegin(), svg_extension.rend(), icon_filename.rbegin())) { 278 | 279 | log1("converting svg icon", icon_filename); 280 | 281 | // Locate the png file in the cache directory 282 | converted=string(getenv("HOME")); 283 | converted += "/"; 284 | converted += CACHE_DIRECTORY_ICONS; 285 | converted += "/"; 286 | 287 | // Extract the filename without the path 288 | const char *pathless=NULL; 289 | if (strchr(icon_filename.c_str(), '/')) { 290 | size_t i = icon_filename.find_last_of(string("/")); 291 | pathless = &icon_filename.c_str()[i+1]; 292 | } 293 | else { 294 | // Icon files without a path should never happen, but just in case 295 | pathless=icon_filename.c_str(); 296 | } 297 | converted += pathless; 298 | 299 | // replace filename extension svg to png 300 | size_t i = converted.find_last_of(string(".svg")); 301 | if (i != std::string::npos) { 302 | converted.replace(i - 3, 4, ".png"); 303 | 304 | // is the png already cached, and is it up-to-date? 305 | struct stat info_original, info_cached; 306 | int rc_original = stat(icon_filename.c_str(), &info_original); 307 | int not_cached = stat(converted.c_str(), &info_cached); 308 | 309 | if (not_cached || ! S_ISREG(info_cached.st_mode) || ! info_cached.st_size || 310 | // Timestamp comparision between original and cached 311 | (!rc_original && !not_cached && info_original.st_mtime > info_cached.st_mtime)) 312 | { 313 | // ok, let's convert and cache it now 314 | string cmdline = SVG_PNG_CONVERTER; 315 | cmdline = SVG_PNG_CONVERTER; 316 | cmdline += " "; 317 | cmdline += icon_filename; 318 | cmdline += " --format png --output "; 319 | cmdline += converted; 320 | log1("svg conversion command", cmdline); 321 | int rc=system(cmdline.c_str()); 322 | 323 | // make sure it has actually been converted succesfully 324 | memset(&info_cached, 0, sizeof(info_cached)); 325 | not_cached = stat(converted.c_str(), &info_cached); 326 | if (not_cached || ! S_ISREG(info_cached.st_mode) || ! info_cached.st_size) { 327 | converted=configuration["defaultdesktopicon"]; 328 | log2("error converting svg, providing a default icon - rc,icon", 329 | WEXITSTATUS(rc), converted); 330 | } 331 | else { 332 | // enforce the cached file access times 333 | struct timeval access_time; 334 | memset(&access_time, 0, sizeof(access_time)); 335 | utimes(converted.c_str(), &access_time); 336 | } 337 | } 338 | else { 339 | log1("svg icon is already cached", converted); 340 | } 341 | } 342 | else { 343 | converted=configuration["defaultdesktopicon"]; 344 | log1("could not find .svg extension, providing a default icon", converted); 345 | } 346 | } 347 | else { 348 | // For regular icons, provide a default icon if it cannot be found 349 | struct stat info; 350 | int icon_not_found = stat(icon_filename.c_str(), &info); 351 | if (icon_not_found || ! S_ISREG(info.st_mode) || ! info.st_size) { 352 | converted=configuration["defaultdesktopicon"]; 353 | log2("icon not found, providing a default one (original, default)", icon_filename, converted); 354 | } 355 | } 356 | 357 | return converted; 358 | } 359 | 360 | /* 361 | * localize_icon() 362 | * 363 | * Try to find a localized version of the icon filename. Otherwise return the default one. 364 | * It works by querying the LANG environment variable, so it's easy to test. 365 | * 366 | * Example, given LANG is set to "es_AR.UTF-8": 367 | * 368 | * /usr/share/kano-desktop/icons/myicon.png ... would become: 369 | * /usr/share/kano-desktop/icons/i18n/es_AR/myicon.png 370 | * 371 | */ 372 | string Configuration::localize_icon(string icon_filename) 373 | { 374 | // Obtain current locale, return original file if unavailable 375 | char *current_locale = getenv("LANG"); 376 | if (!current_locale || !strlen(current_locale)) 377 | return icon_filename; 378 | else { 379 | char *dot = strchr(current_locale, '.'); 380 | if (dot) 381 | *dot = 0x00; 382 | } 383 | 384 | // Construct the path to new localized asset file 385 | std::size_t path_ext = icon_filename.find_last_of("/\\"); 386 | std::string localized = icon_filename.substr(0, path_ext); 387 | std::string filename = icon_filename.substr(path_ext + 1); 388 | 389 | localized += "/i18n/"; 390 | localized += current_locale; 391 | localized += "/"; 392 | localized += filename; 393 | 394 | // If new localized icon cannot be found, return original one 395 | struct stat file_status; 396 | memset(&file_status, 0x00, sizeof(file_status)); 397 | int rc=stat (localized.c_str(), &file_status); 398 | if (rc != 0 || !S_ISREG(file_status.st_mode)) { 399 | log1("i18n localized icon not found", localized); 400 | return icon_filename; 401 | } 402 | 403 | log1("i18n setting localized icon", localized); 404 | return localized; 405 | } 406 | 407 | bool Configuration::parse_icon (const char *directory, string fname, int iconid) 408 | { 409 | bool bsuccess=false; 410 | string lnk_extension = ".lnk"; 411 | string fpath; 412 | 413 | // Only read kano-desktop LNK files. 414 | if (! (std::equal(lnk_extension.rbegin(), lnk_extension.rend(), fname.rbegin()))) { 415 | return false; 416 | } 417 | 418 | // open the icon file and parse arguments 419 | fpath = directory; 420 | fpath += "/"; 421 | fpath += fname.c_str(); 422 | ifile.open (fpath.c_str(), std::ifstream::in); 423 | if (!ifile.is_open()) { 424 | log1 ("could not open icon file", fpath); 425 | } 426 | else { 427 | log1 ("parsing icon file", fpath); 428 | 429 | // Process line-by-line tokens 430 | // In the form "Parameter: some values" 431 | icons[iconid]["filename"] = fname; 432 | std::string line; 433 | while (std::getline(ifile, line)) 434 | { 435 | std::istringstream iss(line); 436 | std::string temp, value, token; 437 | 438 | // Collect the key name aka "token" 439 | iss >> token; 440 | 441 | // Then collect the token's value, up to EOL 442 | while (!iss.eof()) { 443 | iss >> temp; 444 | value += temp; 445 | if (!iss.eof()) value += " "; 446 | } 447 | 448 | if (token == "AppID:") { 449 | icons[iconid]["appid"] = value; 450 | } 451 | 452 | if (token == "Command:") { 453 | icons[iconid]["command"] = value; 454 | } 455 | 456 | if (token == "Icon:") { 457 | 458 | // find a localized icon asset 459 | string localized=localize_icon(value); 460 | 461 | // SVG icons are converted to png and cached. 462 | string converted=convert_svg(localized); 463 | if (converted.length()) { 464 | icons[iconid]["icon"] = converted; 465 | } 466 | else { 467 | icons[iconid]["icon"] = localized; 468 | } 469 | } 470 | 471 | if (token == "IconHover:") { 472 | 473 | // find a localized icon asset 474 | string localized=localize_icon(value); 475 | 476 | // SVG icons are converted to png and cached. 477 | string converted=convert_svg(localized); 478 | if (converted.length()) { 479 | icons[iconid]["iconhover"] = converted; 480 | } 481 | else { 482 | icons[iconid]["iconhover"] = localized; 483 | } 484 | } 485 | 486 | if (token == "IconStamp:") { 487 | icons[iconid]["iconstamp"] = value; 488 | 489 | // SVG icons are converted to png and cached. 490 | string converted=convert_svg(value); 491 | if (converted.length()) { 492 | icons[iconid]["iconstamp"] = converted; 493 | } 494 | else { 495 | icons[iconid]["iconstamp"] = value; 496 | } 497 | } 498 | 499 | if (token == "IconStatus:") { 500 | 501 | // SVG icons are converted to png and cached. 502 | string converted=convert_svg(value); 503 | if (converted.length()) { 504 | icons[iconid]["iconstatus"] = converted; 505 | } 506 | else { 507 | icons[iconid]["iconstatus"] = value; 508 | } 509 | } 510 | 511 | if (token == "HoverTransparent:") { 512 | icons[iconid]["hovertransparent"] = value; 513 | } 514 | 515 | if (token == "HoverXOffset:") { 516 | icons[iconid]["hoverxoffset"] = value; 517 | } 518 | 519 | if (token == "HoverYOffset:") { 520 | icons[iconid]["hoveryoffset"] = value; 521 | } 522 | 523 | if (token == "Caption:") { 524 | 525 | // caption supports environment variable expansion 526 | if (value[0] == '$') { 527 | value = getenv (&value[1]); 528 | } 529 | 530 | icons[iconid]["caption"] = value; 531 | } 532 | 533 | if (token == "Message:") { 534 | icons[iconid]["message"] = value; 535 | } 536 | 537 | if (token == "HAlign:") { 538 | icons[iconid]["halign"] = value; 539 | } 540 | 541 | if (token == "X:") { 542 | icons[iconid]["x"] = value; 543 | } 544 | 545 | if (token == "Y:") { 546 | icons[iconid]["y"] = value; 547 | } 548 | 549 | if (token == "Width:") { 550 | icons[iconid]["width"] = value; 551 | } 552 | 553 | if (token == "Height:") { 554 | icons[iconid]["height"] = value; 555 | } 556 | 557 | if (token == "Singleton:") { 558 | icons[iconid]["singleton"] = value; 559 | } 560 | 561 | if (token == "Relative-To:") { 562 | icons[iconid]["relative-to"] = value; 563 | } 564 | 565 | if (token == "Transparency:") { 566 | icons[iconid]["transparency"] = value; 567 | } 568 | } 569 | 570 | ifile.close(); 571 | bsuccess = true; 572 | } 573 | 574 | return bsuccess; 575 | } 576 | 577 | bool Configuration::load_icons(const char *directory) 578 | { 579 | struct dirent **files; 580 | int numfiles, count; 581 | string last_grid_icon_file; 582 | char last_grid_icon_dir[256]={0}; 583 | 584 | // Make sure we have a cache directory, where we keep svg converted icons 585 | struct stat info; 586 | string cache_directory=string(getenv("HOME")); 587 | cache_directory += "/"; 588 | cache_directory += CACHE_DIRECTORY_ICONS; 589 | 590 | int dirstat = stat(cache_directory.c_str(), &info); 591 | if (dirstat || ! S_ISDIR(info.st_mode)) { 592 | log1("Creating kdesk cache directory", cache_directory); 593 | string cmd_create_cache=string("mkdir -p "); 594 | cmd_create_cache += cache_directory; 595 | system(cmd_create_cache.c_str()); 596 | } 597 | 598 | // Read kano-desktop distributed icons first 599 | log1 ("Loading icons from directory", directory); 600 | numfiles = scandir (directory, &files, 0, 0); 601 | for (count=0; count < numfiles && numicons <= MAX_ICONS; count++) 602 | { 603 | string f = files[count]->d_name; 604 | if (kdesk_plus_icon_filename && !strcmp(f.c_str(), kdesk_plus_icon_filename)) { 605 | last_grid_icon_file=f; 606 | strcpy(last_grid_icon_dir, directory); 607 | continue; 608 | } 609 | 610 | if (parse_icon (directory, f, numicons) == true) { 611 | numicons++; 612 | } 613 | } 614 | 615 | // Read icons located at the user's home directory 616 | char *env_display = getenv ("HOME"); 617 | string kdesk_homedir = env_display + string("/"); 618 | kdesk_homedir += DIR_KDESKTOP_USER; 619 | log1 ("Loading icons from homedir", kdesk_homedir); 620 | numfiles = scandir (kdesk_homedir.c_str(), &files, 0, 0); 621 | for (count=0; count < numfiles && numicons <= MAX_ICONS; count++) 622 | { 623 | string f = files[count]->d_name; 624 | if (kdesk_plus_icon_filename && !strcmp(f.c_str(), kdesk_plus_icon_filename)) { 625 | last_grid_icon_file=f; 626 | strcpy(last_grid_icon_dir, kdesk_homedir.c_str()); 627 | continue; 628 | } 629 | 630 | if (parse_icon (kdesk_homedir.c_str(), f, numicons) == true) { 631 | 632 | // Put a mark that this is a user-defined icon 633 | icons[numicons]["usericon"] = "true"; 634 | 635 | numicons++; 636 | } 637 | } 638 | 639 | if (last_grid_icon_file.length()) { 640 | log1 ("Adding a last_grid_icon: ", last_grid_icon_file); 641 | 642 | // add the last grid icon at the end of the list 643 | if (parse_icon (last_grid_icon_dir, last_grid_icon_file, numicons) == true) { 644 | numicons++; 645 | } 646 | } 647 | 648 | log2 ("Number of icons loaded, max permitted", numicons, MAX_ICONS); 649 | return (bool) (count > 0); 650 | } 651 | 652 | string Configuration::get_config_string(string item) 653 | { 654 | string value = configuration[item]; 655 | return value; 656 | } 657 | 658 | unsigned int Configuration::get_config_int(string item) 659 | { 660 | string value = configuration[item]; 661 | return atoi(value.c_str()); 662 | } 663 | 664 | std::string Configuration::get_icon_string(int iconid, std::string key) 665 | { 666 | string value = icons[iconid][key]; 667 | return value; 668 | } 669 | 670 | int Configuration::get_icon_int(int iconid, std::string key) 671 | { 672 | string value = icons[iconid][key]; 673 | return atoi(value.c_str()); 674 | } 675 | 676 | int Configuration::get_numicons(void) 677 | { 678 | return numicons; 679 | } 680 | 681 | std::string Configuration::get_spaced_value(void) 682 | { 683 | std::string value; 684 | 685 | // get the value until end of line 686 | std::getline (ifile, value); 687 | 688 | // trim leading and trailing spaces 689 | value.erase (0, value.find_first_not_of (' ')); 690 | value.erase (value.find_last_not_of (' ') + 1); 691 | 692 | return value; 693 | } 694 | 695 | void Configuration::dump() 696 | { 697 | log ("dumping MAP configuration values"); 698 | std::map::iterator it; 699 | for (it=configuration.begin(); it != configuration.end(); ++it) 700 | { 701 | log2 ("configuration item", it->first, it->second); 702 | } 703 | 704 | log1 ("dumping all loaded icons:", numicons); 705 | for (int c=0; c < numicons; c++) { 706 | log1 ("dumping icon file:", icons[c]["filename"]); 707 | for (it=icons[c].begin(); it != icons[c].end(); ++it) 708 | { 709 | log2 ("icon key", it->first, it->second); 710 | } 711 | } 712 | } 713 | 714 | void Configuration::reset(void) 715 | { 716 | // Erase each key->value pair for the global configuration map 717 | configuration.erase(configuration.begin(), configuration.end()); 718 | 719 | // Then clear the map list 720 | configuration.clear(); 721 | reset_icons(); 722 | } 723 | 724 | void Configuration::reset_icons(void) 725 | { 726 | for (int c=0; c < numicons; c++) { 727 | // Erase each key->value entry for all the icons 728 | icons[c].erase(icons[c].begin(), icons[c].end()); 729 | } 730 | 731 | icons.clear(); 732 | numicons = 0; 733 | } 734 | 735 | -------------------------------------------------------------------------------- /src/configuration.h: -------------------------------------------------------------------------------- 1 | // 2 | // config.h 3 | // 4 | // Copyright (C) 2013-2014 Kano Computing Ltd. 5 | // License: http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2 6 | // 7 | // An app to show and bring life to Kano-Make Desktop Icons. 8 | // 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #define MAX_ICONS 64 16 | #define CACHE_DIRECTORY_ICONS ".cache/kdesk/icons" 17 | #define SVG_PNG_CONVERTER "rsvg-convert" 18 | 19 | class Configuration 20 | { 21 | protected: 22 | std::ifstream ifile; 23 | std::map configuration; 24 | std::map > icons; 25 | int numicons; 26 | Configuration *pconf; 27 | 28 | public: 29 | Configuration (); 30 | virtual ~Configuration (void); 31 | bool load_conf (const char *filename); 32 | bool load_icons (const char *directory); 33 | bool parse_icon (const char *directory, std::string fname, int iconid); 34 | std::string convert_svg(std::string icon_filename); 35 | std::string localize_icon(std::string icon_filename); 36 | void dump (void); 37 | void reset(void); 38 | void reset_icons(void); 39 | std::string get_spaced_value(void); 40 | 41 | std::string get_config_string(std::string item); 42 | unsigned int get_config_int(std::string item); 43 | std::string get_icon_string(int iconid, std::string key); 44 | int get_icon_int(int iconid, std::string key); 45 | int get_numicons(void); 46 | }; 47 | -------------------------------------------------------------------------------- /src/desktop.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // desktop.cpp 3 | // 4 | // Copyright (C) 2013-2014 Kano Computing Ltd. 5 | // License: http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2 6 | // 7 | // This module is the backbone of the desktop icons presentation. 8 | // It dispatches all user interaction events to the icons, is responsible for the reload, 9 | // redraws the icons when necessary, and also sends user defined signals. 10 | // 11 | 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | #include 19 | #include 20 | #include 21 | 22 | #include 23 | 24 | #include 25 | 26 | #include "main.h" 27 | 28 | #include 29 | #include 30 | 31 | #include "icon.h" 32 | #include "sound.h" 33 | #include "background.h" 34 | #include "desktop.h" 35 | #include "logging.h" 36 | #include "grid.h" 37 | 38 | Desktop::Desktop(void) 39 | { 40 | atom_finish = atom_reload = atom_reload_icons = atom_icon_alert = 0L; 41 | wcontrol = 0L; 42 | numicons = 0; 43 | initialized = false; 44 | icon_grid = NULL; 45 | cache_size = 0; 46 | } 47 | 48 | void Desktop::initialize(Background *p) 49 | { 50 | pbground = p; 51 | initialized = true; 52 | } 53 | 54 | Desktop::~Desktop(void) 55 | { 56 | // Free all allocated icon handlers in the map 57 | std::map ::iterator it; 58 | for (it=iconHandlers.begin(); it != iconHandlers.end(); ++it) 59 | { 60 | delete it->second; 61 | } 62 | 63 | // FIXME: Atoms are not meant to be freed, correct me if I'm wrong. 64 | 65 | if (icon_grid) { 66 | delete icon_grid; 67 | } 68 | } 69 | 70 | bool Desktop::create_icons (Display *display) 71 | { 72 | int nicon=0; 73 | 74 | icon_grid = new IconGrid(display, pconf); 75 | 76 | // By default we do not use imlib2 image cache. 77 | cache_size = pconf->get_config_int("imagecachesize"); 78 | if (cache_size > 0) { 79 | log1 ("Setting image cache-size via config ImageCacheSize to bytes", cache_size); 80 | } 81 | else { 82 | // Do not use imlib2 cache by default. It occassionally messes up icon images. 83 | // Force a cache of 1 if no cache specified. This is to account for the free_image 84 | // calls not being called as it causes memory leaks - see icon.cpp#destroy() 85 | cache_size = 1; 86 | log ("Not using Imlib2 image cache"); 87 | } 88 | 89 | // Sets the cache size. The size is in bytes. Setting the cache size to 0 effectively flushes the cache 90 | // and keeps the cache size at 0 until set to another value. 91 | // Whenever you set the cache size Imlib2 will flush as many old images and pixmap 92 | // from the cache as needed until the current cache usage is less than or equal to the cache size. 93 | if (cache_size > 0) { 94 | imlib_set_cache_size(cache_size); 95 | } 96 | 97 | // Create and draw all icons, save a mapping of their window IDs -> handlers 98 | // so that we can dispatch events to each one in turn later on. 99 | // 100 | // The icons are created in two passes. The first one creates only grid 101 | // icons with hints, so they get a priority over the other ones. 102 | for (int pass = 0; pass < 2; pass++) 103 | { 104 | for (nicon=0; nicon < pconf->get_numicons(); nicon++) 105 | { 106 | string pos, x, y; 107 | pos = pconf->get_icon_string(nicon, "relative-to"); 108 | x = pconf->get_icon_string(nicon, "x"); 109 | y = pconf->get_icon_string(nicon, "y"); 110 | 111 | /* This condition is a special exception for outdated LNK files */ 112 | /* which defined the X and Y coordinates as "auto" in the grid, where it should be "0" */ 113 | if (pconf->get_icon_string(nicon, "usericon") != "true") { 114 | 115 | if (pos == "grid" && x != "auto" && y != "auto") { 116 | /* Has hints, and it's not a user defined icon, skip in the second pass */ 117 | if (pass == 1) 118 | continue; 119 | } 120 | else { 121 | /* No hints, skip in the first pass */ 122 | if (pass == 0) 123 | continue; 124 | } 125 | } 126 | else { 127 | if (pass == 1) 128 | continue; 129 | } 130 | 131 | Icon *pico = new Icon(pconf, nicon); 132 | Window wicon = pico->create(display, icon_grid); 133 | if (wicon) { 134 | XEvent emptyev; 135 | iconHandlers[wicon] = pico; 136 | pico->draw(display, emptyev, false); 137 | 138 | // Invoke the icon hook so it is refreshed immediately 139 | // right after Kdesk startup and refresh signals 140 | string hookscript = pconf->get_config_string("iconhook"); 141 | if (hookscript.length() > 0) { 142 | call_icon_hook (display, emptyev, pconf->get_config_string("iconhook"), pico); 143 | } 144 | 145 | numicons++; 146 | } 147 | else { 148 | log1 ("Warning: error creating icon", pconf->get_icon_string(nicon, "filename")); 149 | delete pico; 150 | } 151 | } 152 | } 153 | 154 | // tell the outside world how the icon creation has completed 155 | dump_metrics(display); 156 | 157 | // returns true if at least one icon is available on the desktop 158 | log1 ("Desktop icon classes have been allocated", nicon); 159 | return (bool) (nicon > 0); 160 | } 161 | 162 | Icon *Desktop::find_icon_name (char *icon_name) 163 | { 164 | // Search through the icon dispatcher table for the icon filename 165 | std::map ::iterator it; 166 | for (it=iconHandlers.begin(); it != iconHandlers.end(); ++it) 167 | { 168 | if (it->second != NULL) { 169 | if (!strcasecmp (icon_name, it->second->get_icon_name().c_str())) { 170 | log2 ("Icon name found (name, instance)", icon_name, it->second); 171 | return it->second; 172 | } 173 | } 174 | } 175 | 176 | return NULL; 177 | } 178 | 179 | bool Desktop::redraw_icons (Display *display, bool forceClear) 180 | { 181 | // Search through the icon dispatcher table for the icon filename 182 | XEvent ev; 183 | std::map ::iterator it; 184 | int redraws=0; 185 | 186 | memset (&ev, 0x00, sizeof (ev)); 187 | 188 | for (it=iconHandlers.begin(); it != iconHandlers.end(); ++it) 189 | { 190 | if (it->second != NULL) { 191 | it->second->draw (display, ev, forceClear); 192 | redraws++; 193 | } 194 | } 195 | 196 | return (redraws > 0); 197 | } 198 | 199 | bool Desktop::destroy_icons (Display *display) 200 | { 201 | std::map ::iterator it; 202 | 203 | // Ask every icon to close, deallocate, and disappear from the desktop 204 | for (it=iconHandlers.begin(); it != iconHandlers.end(); ++it) 205 | { 206 | // The reason we check for icon handler validity here is because 207 | // during close time, the previous icons can still get notification 208 | // events which we are not dealing with anymore, they are defunct. 209 | // This is the case with hover effects when the mouse is over them during refresh. 210 | if (it->second) { 211 | it->second->destroy(display); 212 | delete it->second; 213 | } 214 | } 215 | 216 | // Then erase each window->handler mapping entry, and empty the map list 217 | iconHandlers.erase(iconHandlers.begin(), iconHandlers.end()); 218 | iconHandlers.clear(); 219 | numicons = 0; 220 | 221 | // Reset imlib2 image cache memory, only if a cache is specified. 222 | // This will effectively remove them from memory to be reloaded from disk. 223 | // Imlib2 cache system is based on file pathnames. specially important during kdesk -refresh 224 | if (cache_size > 0) { 225 | imlib_set_cache_size(0); 226 | } 227 | 228 | } 229 | 230 | bool Desktop::reload_icons (Display *display) 231 | { 232 | pconf->reset_icons(); 233 | pconf->load_icons(DIR_KDESKTOP); 234 | 235 | log ("Reloading desktop icons only"); 236 | 237 | // delete desktop icons for which lnk files have been removed 238 | std::map ::iterator it; 239 | for (it=iconHandlers.begin(); it != iconHandlers.end(); ++it) 240 | { 241 | if (it->second != NULL) 242 | { 243 | bool found = false; 244 | string iconname = it->second->get_icon_filename().c_str(); 245 | for (int nicon=0; nicon < pconf->get_numicons(); nicon++) 246 | { 247 | string icon_filename = pconf->get_icon_string(nicon, "filename"); 248 | if (!strcasecmp (icon_filename.c_str(), it->second->get_icon_filename().c_str())) { 249 | found=true; 250 | } 251 | } 252 | 253 | if (!found) { 254 | // the desktop icon is missing the lnk file, remove it from the desktop 255 | log1 ("Icon has been removed from desktop (lnk is gone)", it->second->get_icon_filename().c_str()); 256 | it->second->destroy(display); 257 | delete it->second; 258 | it->second = NULL; 259 | numicons--; 260 | } 261 | } 262 | } 263 | 264 | // search for newly added lnk files and create their desktop icons. 265 | for (int nicon=0; nicon < pconf->get_numicons(); nicon++) 266 | { 267 | string icon_filename = pconf->get_icon_string(nicon, "filename"); 268 | bool found=false; 269 | for (it=iconHandlers.begin(); it != iconHandlers.end(); ++it) 270 | { 271 | if (it->second != NULL) { 272 | if (!strcasecmp (icon_filename.c_str(), it->second->get_icon_filename().c_str())) { 273 | found=true; 274 | 275 | // update the iconid as reported from the configurator, 276 | // as they have most probably been rearranged in the preceding "remove" loop 277 | it->second->set_iconid(nicon); 278 | } 279 | } 280 | } 281 | 282 | if (!found) { 283 | // create a new icon handler and add it to the desktop 284 | log2 ("adding new icon to desktop (name, id)", icon_filename, nicon); 285 | 286 | Icon *pico = new Icon(pconf, nicon); 287 | Window wicon = pico->create(display, icon_grid); 288 | if (wicon) { 289 | XEvent emptyev; 290 | iconHandlers[wicon] = pico; 291 | pico->draw(display, emptyev, false); 292 | } 293 | numicons++; 294 | log1 ("Nem icon has been added to desktop", icon_filename); 295 | } 296 | } 297 | 298 | // tell the outside world how the icon creation has completed 299 | dump_metrics(display); 300 | log1 ("Finished reloading desktop icons only (num icons)", pconf->get_numicons()); 301 | return true; 302 | } 303 | 304 | bool Desktop::process_and_dispatch(Display *display) 305 | { 306 | // Process X11 events and dispatch them to each icon handler for processing 307 | static unsigned long last_click=0, last_dblclick=0; 308 | bool doubleclicked, oneclick; 309 | static bool bstarted = false; 310 | int iGrace = 500; 311 | XEvent ev; 312 | Window wtarget = 0; 313 | 314 | do 315 | { 316 | // This is the main X11 event processing loop 317 | XNextEvent(display, &ev); 318 | wtarget = ev.xany.window; 319 | 320 | // If the event is sent to Kdesk's object control window, 321 | // this means it's a special signal sent from external processes via kill SIG or an XSendEvent. 322 | // It will allow us to give UIX visual feedback on-the-fly, reload configuration, or other useful async use cases. 323 | if (wtarget == wcontrol) 324 | { 325 | if (ev.type == ClientMessage) { 326 | log2 ("Kdesk client message arriving to control window with atom", ev.type, ev.xclient.data.l[0]); 327 | if ((Atom) ev.xclient.data.l[0] == atom_reload) { 328 | log ("Kdesk object control window receives a RELOAD event"); 329 | return true; // true means do whatever you need to, and come back to process_and_dispatch, we are ready. 330 | } 331 | if ((Atom) ev.xclient.data.l[0] == atom_reload_icons) { 332 | log ("Kdesk object control window receives a ICON RELOAD event"); 333 | reload_icons (display); 334 | return false; // false means do not reload kdesk settings 335 | } 336 | else if ((Atom) ev.xclient.data.l[0] == atom_finish) { 337 | log ("Kdesk object control window receives a FINISH event"); 338 | return false; // false means do not reload kdesk settings 339 | } 340 | else if ((Atom) ev.xclient.data.l[0] == atom_icon_alert) { 341 | 342 | // Kdesk Icon Hooks alert is managed here. 343 | // This event comes with a message string (the icon name) 344 | // The icon name can be 16 bytes maximum. This comes from 20 bytes for X11 CientMessage data buffer minus 4 bytes 345 | // the actual Atom message ID. 346 | // An Icon hook is fired so the icon look&feel can be dynamically changed. 347 | 348 | // Is there a Kdesk Icon hook defined? 349 | string iconhook_script = pconf->get_config_string("iconhook"); 350 | if (iconhook_script.length() == 0) { 351 | log ("No kdesk icon hook defined - ignoring alert"); 352 | } 353 | else { 354 | // Collect the icon name from the X11 event to which the hook alert needs to be sent 355 | char alert_iconname[17]; 356 | memset (alert_iconname, 0x00, sizeof (alert_iconname)); 357 | memcpy (alert_iconname, &ev.xclient.data.l[1], 16); 358 | alert_iconname[16] = 0x00; // Truncate it - this is not a nullified string 359 | log1 ("Icon Hook signal received for icon", alert_iconname); 360 | 361 | // Is the icon name on the desktop? Can we send him a signal? 362 | Icon *pico_hook = find_icon_name (alert_iconname); 363 | if (!pico_hook) { 364 | log ("Could not find this icon on the desktop, ignoring"); 365 | } 366 | else { 367 | log2 ("Calling icon hook script (script, icon name)", iconhook_script, alert_iconname); 368 | call_icon_hook (display, ev, iconhook_script, pico_hook); 369 | } 370 | } 371 | } 372 | } 373 | 374 | XFlush (display); 375 | continue; 376 | } 377 | 378 | // During Kdesk configuration refresh we might get events for now defunct icon windows 379 | if (iconHandlers[wtarget] == NULL) { 380 | XFlush (display); 381 | continue; 382 | } 383 | 384 | // All events directed to the desktop icons we have created are processed here. 385 | switch (ev.type) 386 | { 387 | case ButtonPress: 388 | 389 | // Stop processing a.s.a.p. if the mouse button being pressed is not left or right. 390 | // Button1=Left, Button2=Middle, Button3=Right, higher than those are mapped to mouse scroll wheel 391 | // 392 | // http://tronche.com/gui/x/xlib/events/keyboard-pointer/keyboard-pointer.html 393 | // 394 | if (ev.xbutton.button != Button1 && ev.xbutton.button != Button3) { 395 | log1 ("ButtonPress for unsupported button number - ignoring", ev.xbutton.button); 396 | break; 397 | } 398 | 399 | doubleclicked=((ev.xbutton.time - last_click) < pconf->get_config_int("clickdelay")) ? true : false; 400 | oneclick=(pconf->get_config_string("oneclick") == "true") ? true : false; 401 | 402 | // A double click event is defined by the time elapsed between 2 clicks: "clickdelay" 403 | // And a grace time period to protect nested double clicks: "clickgrace" 404 | // Note we are listening for both left and right mouse click events. 405 | log3 ("ButtonPress event: window, time, last click", wtarget, ev.xbutton.time, last_click); 406 | if ((doubleclicked == true && oneclick == false) || (doubleclicked == false and oneclick == true)) 407 | { 408 | // Protect the UI experience by disallowing a new app startup if one is in progress 409 | if (bstarted == true && (ev.xbutton.time - last_dblclick < pconf->get_config_int("iconstartdelay"))) { 410 | log1 ("icon start request too fast (iconstartdelay)", pconf->get_config_int("iconstartdelay")); 411 | psound->play_sound("sounddisabledicon"); 412 | } 413 | else { 414 | log ("Processing mouse click startup event"); 415 | last_dblclick = ev.xbutton.time; 416 | bstarted = false; 417 | 418 | // Save to request an app startup: tell the icon a mouse double click needs processing 419 | Window winapp = iconHandlers[wtarget]->find_icon_window (display, iconHandlers[wtarget]->get_appid()); 420 | if (!winapp) { 421 | // Notify system we are about to load a new app (hourglass) 422 | psound->play_sound("soundlaunchapp"); 423 | 424 | // Show the hourglass mouse 425 | string command_line=iconHandlers[wtarget]->get_commandline(); 426 | kdesk_hourglass_start_appcmd((char *) command_line.c_str()); 427 | 428 | bstarted = iconHandlers[wtarget]->double_click (display, ev); 429 | if (!bstarted) { 430 | // Remove the hourglass, we could not start the app 431 | kdesk_hourglass_end(); 432 | } 433 | } 434 | else { 435 | // The app is already running, icon is disabled, unless MaximizeSingleton flag is set 436 | if (pconf->get_config_string("maximizesingleton") == "true") { 437 | log1 ("Maximizing AppID which is a running singleton", iconHandlers[wtarget]->get_appid()); 438 | iconHandlers[wtarget]->maximize (display, winapp); 439 | } 440 | else { 441 | log1 ("AppID running, disabling icon with alert sound", iconHandlers[wtarget]->get_appid()); 442 | psound->play_sound("sounddisabledicon"); 443 | } 444 | } 445 | } 446 | } 447 | break; 448 | 449 | case ButtonRelease: 450 | if (ev.xbutton.button != Button1 && ev.xbutton.button != Button3) { 451 | log1 ("ButtonRelease for unsupported button number - ignoring", ev.xbutton.button); 452 | } 453 | else { 454 | log2 ("ButtonRelease event to window and time ms", wtarget, ev.xbutton.time); 455 | last_click = ev.xbutton.time; 456 | } 457 | break; 458 | 459 | case MotionNotify: 460 | iconHandlers[wtarget]->motion(display, ev); 461 | break; 462 | 463 | case EnterNotify: 464 | log1 ("EnterNotify event to window", wtarget); 465 | iconHandlers[wtarget]->blink_icon(display, ev); 466 | break; 467 | 468 | case LeaveNotify: 469 | log1 ("LeaveNotify event to window", wtarget); 470 | iconHandlers[wtarget]->unblink_icon(display, ev); 471 | break; 472 | 473 | case Expose: 474 | iconHandlers[wtarget]->draw(display, ev, false); 475 | break; 476 | 477 | default: 478 | break; 479 | } 480 | 481 | } while (!finish); 482 | 483 | return true; 484 | } 485 | 486 | bool Desktop::initialize(Display *display, Configuration *loaded_conf, Sound *ksound) 487 | { 488 | pconf = loaded_conf; 489 | psound = ksound; 490 | finish = false; 491 | 492 | #ifdef DEBUG 493 | int x=10,y=10,cw=100,ch=100,width=5; 494 | #else 495 | int x=0,y=0,cw=1,ch=1,width=0; 496 | #endif 497 | 498 | // Allocate Atoms used for signaling Kdesk's object window 499 | atom_finish = XInternAtom(display, KDESK_SIGNAL_FINISH, False); 500 | atom_reload = XInternAtom(display, KDESK_SIGNAL_RELOAD, False); 501 | atom_reload_icons = XInternAtom(display, KDESK_SIGNAL_RELOAD_ICONS, False); 502 | atom_icon_alert = XInternAtom(display, KDESK_SIGNAL_ICON_ALERT, False); 503 | 504 | // Create a hidden Object Control window which will receive Kdesk external events 505 | XSetWindowAttributes attr; 506 | attr.background_pixmap = ParentRelative; 507 | attr.backing_store = Always; 508 | attr.event_mask = ExposureMask | EnterWindowMask | LeaveWindowMask; 509 | attr.override_redirect = True; 510 | wcontrol = XCreateWindow (display, DefaultRootWindow(display), x, y, cw, ch, width, 511 | CopyFromParent, CopyFromParent, CopyFromParent, 512 | CWEventMask, 513 | &attr); 514 | 515 | XClearWindow(display, wcontrol); 516 | 517 | // We'll give this window a meaningful name 518 | XStoreName(display, wcontrol, KDESK_CONTROL_WINDOW_NAME); 519 | 520 | #ifdef DEBUG 521 | // Show kdesk control window on the bottom right corner of the screen 522 | XMapWindow (display, wcontrol); 523 | int deskw = DisplayWidth(display, DefaultScreen (display)); 524 | int deskh = DisplayHeight(display, DefaultScreen (display)); 525 | XMoveWindow (display, wcontrol, deskw - cw - width, deskh - ch - width); 526 | #endif 527 | 528 | log1 ("Creating Kdesk control window, handle", wcontrol); 529 | return (wcontrol ? true : false); 530 | } 531 | 532 | bool Desktop::finalize(void) 533 | { 534 | finish = true; 535 | } 536 | 537 | Window Desktop::find_kdesk_control_window (Display *display) 538 | { 539 | Window kdesk_control_window=0L; 540 | Window returnedroot, returnedparent, root = DefaultRootWindow(display); 541 | char *windowname=NULL; 542 | Window *children=NULL, *subchildren=NULL; 543 | unsigned int numchildren, numsubchildren; 544 | 545 | // Enumarate top-level windows in search for Kdesk control window 546 | XQueryTree (display, root, &returnedroot, &returnedparent, &children, &numchildren); 547 | XWindowAttributes w; 548 | int i; 549 | for (int i=numchildren-1; i>=0 && !kdesk_control_window; i--) { 550 | 551 | if (XFetchName (display, children[i], &windowname)) { 552 | if (!strncmp (windowname, KDESK_CONTROL_WINDOW_NAME, strlen (KDESK_CONTROL_WINDOW_NAME))) { 553 | kdesk_control_window = children[i]; 554 | log1 ("Kdesk control window found", kdesk_control_window); 555 | XFree (windowname); 556 | break; 557 | } 558 | } 559 | 560 | // Kdesk might sit a little deeper in the tree hierarchy, let's step in 561 | XQueryTree (display, children[i], &returnedroot, &returnedparent, &subchildren, &numsubchildren); 562 | 563 | for (int k=numsubchildren-1; k>=0; k--) { 564 | if (XFetchName (display, subchildren[k], &windowname)) { 565 | if (!strncmp (windowname, KDESK_CONTROL_WINDOW_NAME, strlen (KDESK_CONTROL_WINDOW_NAME))) { 566 | kdesk_control_window = subchildren[k]; 567 | log1 ("Kdesk control window found", kdesk_control_window); 568 | XFree (windowname); 569 | break; 570 | } 571 | } 572 | } 573 | } 574 | 575 | if(children) { 576 | XFree(children); 577 | } 578 | 579 | if(subchildren) { 580 | XFree(subchildren); 581 | } 582 | 583 | return kdesk_control_window; 584 | } 585 | 586 | bool Desktop::send_signal (Display *display, const char *signalName, char *message) 587 | { 588 | // Don't trigger anything if we have inherited the environment variable 589 | // set on calls to icon hook script 590 | const char *recurse_flag=getenv("KDESK_NO_RECURSE"); 591 | if(recurse_flag){ 592 | log("Error! Attempt to use kdesk from a process run via iconhook script"); 593 | return false; 594 | } 595 | 596 | Window wsig = wcontrol; // Kdesk control window, either found by instantiation or enumarated. 597 | 598 | // The signal name has been allocated by kdesk, if it's not there, kdesk is not running. 599 | Atom atom_signal = XInternAtom(display, signalName, True); 600 | if (!atom_signal) { 601 | log1 ("Atom signal name not defined. Perhaps kdesk is not running?", signalName); 602 | return false; 603 | } 604 | 605 | if (!wsig && !(wsig=find_kdesk_control_window(display))) { 606 | // Kdesk control window cannot be found, most likely Kdesk is not running. 607 | return false; 608 | } 609 | 610 | // Finally send the event to the control window to unfold the job 611 | XEvent xev; 612 | memset (&xev, 0x00, sizeof(xev)); 613 | xev.type = ClientMessage; 614 | xev.xclient.window = wsig; 615 | xev.xclient.format = 32; // 32 means data is interpreted as consecutive set of unordered LONGs 616 | 617 | // A Kdesk message data is encoded in the following way: 618 | // 4-byte atom ID name + an optional 16 byte char icon name - the case for KDESK_SIGNAL_ICON_ALERT. 619 | xev.xclient.data.l[0] = atom_signal; 620 | 621 | if (message != NULL) { 622 | // Append a message string to the event - This is a custom fixed lenght literal. 623 | memcpy (&xev.xclient.data.l[1], message, (strlen(message) > 15 ? 16 : strlen(message))); 624 | } 625 | 626 | int rc = XSendEvent (display, wsig, 1, NoEventMask, (XEvent *) &xev); 627 | XFlush (display); 628 | log2 ("Sending a client message event to kdesk control window (win, rc)", wsig, rc); 629 | return (rc == Success ? true : false); 630 | } 631 | 632 | bool Desktop::call_icon_hook (Display *display, XEvent ev, string hookscript, Icon *pico_hook) 633 | { 634 | FILE *fp_iconhooks=NULL; 635 | char chcmdline[1024]; 636 | char chline[1024], key[64], value[900], word[256]; 637 | int updates=0; 638 | 639 | // Given a hook script name and an icon instance, call the script 640 | // to update its attributes, then ask the icon instance to refresh them 641 | if (!pico_hook) { 642 | log ("Icon handler is empty"); 643 | return false; 644 | } 645 | // Execute the Icon Hook, parse the stdout, and communicate with the icon to refresh attributes 646 | 647 | // Set environment variable so other programs called from script 648 | // cannot accidentally create an infinite loop. 649 | const char *norec = "export KDESK_NO_RECURSE=1"; 650 | 651 | sprintf (chcmdline, "/bin/bash -c \"%s; %s %s\"", 652 | norec, 653 | hookscript.c_str(), 654 | pico_hook->get_icon_name().c_str()); 655 | log1 ("Executing hook script:", chcmdline); 656 | fp_iconhooks = popen (chcmdline, "r"); 657 | while (fgets (chline, sizeof (chline), fp_iconhooks) != NULL) 658 | { 659 | char *toks=chline; 660 | memset (key, 0x00, sizeof(key)); 661 | memset (value, 0x00, sizeof(value)); 662 | int n=0; 663 | while (sscanf (toks, "%s%n", word, &n) == 1 ) { 664 | if (word[strlen(word)-1] == ':') { 665 | strcpy (key, word); 666 | } 667 | else { 668 | strcat (value, word); 669 | strcat (value, " "); 670 | } 671 | toks += n; 672 | } 673 | 674 | // Parse the keys (attributes) that can be applied to the icon, pass them to the icon instance 675 | value[strlen(value)-1]=0x00; 676 | if (!strcmp (key, "Message:")) { 677 | pico_hook->set_message (value); 678 | updates++; 679 | } 680 | else if (!strcmp (key, "Caption:")) { 681 | pico_hook->set_caption (value); 682 | updates++; 683 | } 684 | else if (!strcmp (key, "Icon:")) { 685 | pico_hook->set_icon (value); 686 | updates++; 687 | } 688 | else if (!strcmp (key, "IconStamp:")) { 689 | pico_hook->set_icon_stamp (value); 690 | updates++; 691 | } 692 | else if (!strcmp (key, "IconStatus:")) { 693 | pico_hook->set_icon_status (value); 694 | updates++; 695 | } 696 | } 697 | 698 | // Redraw the icon if attributes have been modified 699 | pclose (fp_iconhooks); 700 | if (updates) { 701 | log1 ("Populating hook updates to icon (#updates)", updates); 702 | pico_hook->clear(display, ev); 703 | pico_hook->draw(display, ev, false); 704 | } 705 | return true; 706 | } 707 | 708 | bool Desktop::get_metrics_filename(Display *display, char *chfilename, int size) 709 | { 710 | char *chdisplayname = XDisplayString(display); 711 | if (!chdisplayname) { 712 | return false; 713 | } 714 | else { 715 | snprintf (chfilename, (size_t) size, "/tmp/kdesk-metrics%s.dump", XDisplayString(display)); 716 | return true; 717 | } 718 | } 719 | 720 | bool Desktop::dump_metrics (Display *display) 721 | { 722 | // 723 | // Save Kdesk counters in a json-like file prepended with current display number. 724 | // 725 | 726 | char chmetrics_filename[80]; 727 | if (!get_metrics_filename(display, chmetrics_filename, sizeof(chmetrics_filename))) { 728 | return false; 729 | } 730 | 731 | unlink (chmetrics_filename); 732 | FILE *fp = fopen (chmetrics_filename, "w"); 733 | if (fp) { 734 | fprintf (fp, "{\n \"icons-found\": %d,\n", pconf->get_numicons()); 735 | fprintf (fp, " \"icons-rendered\": %d,\n", numicons); 736 | fprintf (fp, " \"grid-full\": %s\n}\n", icon_grid->grid_full == true ? "true" : "false"); 737 | log1 ("Metrics file saved", chmetrics_filename); 738 | fclose (fp); 739 | 740 | // broaden permissions so we can remove it next time we switch user contexts 741 | chmod (chmetrics_filename, 0666); 742 | return true; 743 | } 744 | else { 745 | log2 ("Error creating metrics file (errno, file)", errno, chmetrics_filename); 746 | return false; 747 | } 748 | } 749 | -------------------------------------------------------------------------------- /src/desktop.h: -------------------------------------------------------------------------------- 1 | // 2 | // desktop.h 3 | // 4 | // Copyright (C) 2013-2014 Kano Computing Ltd. 5 | // License: http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2 6 | // 7 | // An app to show and bring life to Kano-Make Desktop Icons. 8 | // 9 | 10 | // 11 | // TODO: Always keep an eye on libsn upgrades. 12 | // This API library is not officially conformed across different architectures 13 | // See the file: /usr/include/startup-notification-1.0/libsn/sn-common.h for details 14 | // 15 | 16 | #define KDESK_CONTROL_WINDOW_NAME "KdeskControlWindow" 17 | #define KDESK_SIGNAL_FINISH "KSIG_FINISH" 18 | #define KDESK_SIGNAL_RELOAD "KSIG_RELOAD" 19 | #define KDESK_SIGNAL_RELOAD_ICONS "KSIG_RELOAD_ICONS" 20 | #define KDESK_SIGNAL_ICON_ALERT "KSIG_ICON_ALERT" 21 | 22 | class IconGrid; 23 | 24 | class Desktop 25 | { 26 | private: 27 | Window wcontrol; 28 | Background *pbground; 29 | bool initialized; 30 | std::map iconHandlers; 31 | IconGrid *icon_grid; 32 | Configuration *pconf; 33 | Sound *psound; 34 | bool finish; 35 | static int error_trap_depth; 36 | int numicons; 37 | int cache_size; 38 | Atom atom_finish, atom_reload, atom_reload_icons, atom_icon_alert; 39 | 40 | public: 41 | Desktop(void); 42 | virtual ~Desktop(void); 43 | 44 | void initialize(Background *p); 45 | bool create_icons (Display *display); 46 | Icon *find_icon_name (char *icon_name); 47 | bool redraw_icons (Display *display, bool forceClear); 48 | bool destroy_icons (Display *display); 49 | bool reload_icons (Display *display); 50 | 51 | bool notify_startup_load (Display *display, int iconid, Time time); 52 | bool notify_startup_ready (Display *display); 53 | void notify_startup_event (Display *display, XEvent *pev); 54 | 55 | bool initialize(Display *display, Configuration *loaded_conf, Sound *psound); 56 | bool is_kdesk_running (Display *display); 57 | Window find_kdesk_control_window (Display *display); 58 | bool process_and_dispatch(Display *display); 59 | bool send_signal (Display *display, const char *signalName, char *message); 60 | bool call_icon_hook (Display *display, XEvent ev, std::string hookscript, Icon *pico_hook); 61 | bool finalize(void); 62 | bool get_metrics_filename(Display *display, char *chfilename, int size); 63 | bool dump_metrics (Display *display); 64 | 65 | }; 66 | -------------------------------------------------------------------------------- /src/grid.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // grid.cpp 3 | // 4 | // Copyright (C) 2013-2014 Kano Computing Ltd. 5 | // License: http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2 6 | // 7 | 8 | #include "configuration.h" 9 | #include "grid.h" 10 | #include "logging.h" 11 | 12 | 13 | // Static non-const member variable need be defined at file scope 14 | int IconGrid::ICON_W; 15 | int IconGrid::ICON_H; 16 | 17 | /* IconGrid */ 18 | IconGrid::IconGrid(Display *display, Configuration *pconf) 19 | { 20 | int screen_num = DefaultScreen(display); 21 | int w = DisplayWidth(display, screen_num); 22 | int h = DisplayHeight(display, screen_num); 23 | 24 | grid_full = false; 25 | 26 | // Get the grid dimensions from kdeskrc file 27 | if (pconf) { 28 | 29 | // this is the blank, unsensitive empty space beween icons, horizontally and vertically. 30 | int horz_gap=pconf->get_config_int("icongaphorz"); 31 | if (horz_gap) { 32 | HORZ_SPC=horz_gap; 33 | } 34 | else { 35 | HORZ_SPC=DEFAULT_ICON_HORZ_SPACE; 36 | } 37 | 38 | int vert_gap=pconf->get_config_int("icongapvert"); 39 | if (vert_gap) { 40 | VERT_SPC=vert_gap; 41 | } 42 | else { 43 | VERT_SPC=DEFAULT_ICON_VERT_SPACE; 44 | } 45 | 46 | ICON_W = pconf->get_config_int("gridwidth"); 47 | ICON_H = pconf->get_config_int("gridheight"); 48 | } 49 | 50 | // Or set default values if they're not defined 51 | if (!ICON_W) { 52 | ICON_W = DEFAULT_GRID_WIDTH; 53 | } 54 | 55 | if (!ICON_H) { 56 | ICON_H = DEFAULT_GRID_HEIGHT; 57 | } 58 | 59 | log2 ("Icon grid width and height", ICON_W, ICON_H); 60 | 61 | // Take into account the space needed for the icon and the empty margin 62 | width = w / (ICON_W + HORZ_SPC); 63 | if (width > MAX_FIELDS_X) 64 | width = MAX_FIELDS_X; 65 | 66 | height = (h - MARGIN_TOP - MARGIN_BOTTOM) / ICON_H; 67 | 68 | // FIXME: + ((HORZ_SPC/2) - 2, this is needed to avoid overall displacement to the left 69 | // I believe the gap applies to all the icons except one, but needs clarification. 70 | start_x = w/2 - ((width * (ICON_W + HORZ_SPC)) / 2) + ((HORZ_SPC/2) - 2); 71 | 72 | start_y = h - MARGIN_BOTTOM; 73 | } 74 | 75 | IconGrid::~IconGrid() 76 | { 77 | /* Nothing here yet */ 78 | } 79 | 80 | 81 | bool IconGrid::is_place_used(int x, int y) 82 | { 83 | for (coord_list_t::iterator it = used_fields.begin(); 84 | it != used_fields.end(); it++) { 85 | if (it->first == x && it->second == y) { 86 | return true; 87 | } 88 | } 89 | return false; 90 | } 91 | 92 | bool IconGrid::free_space_used(int x, int y) 93 | { 94 | for (coord_list_t::iterator it = used_fields.begin(); 95 | it != used_fields.end(); it++) { 96 | 97 | if (it->first == x && it->second == y) { 98 | used_fields.erase(it); 99 | return true; 100 | } 101 | } 102 | return false; 103 | } 104 | 105 | bool IconGrid::get_real_position(int field_x, int field_y, 106 | int *real_x, int *real_y, int *gridx, int *gridy) 107 | { 108 | *real_x = start_x + field_x * (ICON_W + HORZ_SPC); 109 | *real_y = start_y - (1 + field_y) * (ICON_H + VERT_SPC); 110 | 111 | // Because the grid grows from bottom to top (0,0 being top left corner of screen) 112 | // if the real icon position represented on the screen falls beyond limits, reject the icon 113 | if (*real_y <= MARGIN_TOP) { 114 | grid_full = true; 115 | return false; 116 | } 117 | else { 118 | if (gridx) *gridx = field_x; 119 | if (gridy) *gridy = field_y; 120 | used_fields.push_back(std::pair(field_x, field_y)); 121 | return true; 122 | } 123 | } 124 | 125 | bool IconGrid::request_position(int field_hint_x, int field_hint_y, 126 | int *x, int *y, int *gridx, int *gridy) 127 | { 128 | if (field_hint_x >= 0 && field_hint_x < width && 129 | field_hint_y >= 0 && field_hint_y < height) { 130 | if (!is_place_used(field_hint_x, field_hint_y)) { 131 | return (get_real_position(field_hint_x, field_hint_y, x, y, gridx, gridy)); 132 | } 133 | } 134 | 135 | /* find a free spot */ 136 | for (int iy = 0; iy < height; iy++) { 137 | for (int ix = 0; ix < width; ix++) { 138 | if (!is_place_used(ix, iy)) { 139 | return (get_real_position(ix, iy, x, y, gridx, gridy)); 140 | } 141 | } 142 | } 143 | 144 | grid_full = true; 145 | return false; 146 | } 147 | -------------------------------------------------------------------------------- /src/grid.h: -------------------------------------------------------------------------------- 1 | // 2 | // grid.h 3 | // 4 | // Copyright (C) 2013-2014-2015 Kano Computing Ltd. 5 | // License: http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2 6 | // 7 | // 8 | 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | 15 | #define DEFAULT_GRID_WIDTH 128 16 | #define DEFAULT_GRID_HEIGHT 128 17 | 18 | #define DEFAULT_ICON_HORZ_SPACE 50 19 | #define DEFAULT_ICON_VERT_SPACE 25 20 | 21 | class IconGrid 22 | { 23 | private: 24 | int VERT_SPC; 25 | int HORZ_SPC; 26 | 27 | static const int MARGIN_BOTTOM = 84; 28 | static const int MARGIN_TOP = 50; 29 | 30 | static const int MAX_FIELDS_X = 7; 31 | 32 | typedef std::vector > coord_list_t; 33 | 34 | int width; 35 | int height; 36 | coord_list_t used_fields; 37 | 38 | int start_x; 39 | int start_y; 40 | 41 | bool is_place_used(int x, int y); 42 | bool get_real_position(int field_x, int field_y, int *real_x, int *real_y, int *gridx, int *gridy); 43 | 44 | public: 45 | IconGrid(Display *display, Configuration *pconf); 46 | ~IconGrid(void); 47 | 48 | static int ICON_W; 49 | static int ICON_H; 50 | 51 | bool grid_full; 52 | 53 | bool request_position(int field_hint_x, int field_hint_y, int *x, int *y, int *gridx, int *gridy); 54 | bool free_space_used(int x, int y); 55 | }; 56 | -------------------------------------------------------------------------------- /src/icon.h: -------------------------------------------------------------------------------- 1 | // 2 | // icon.h 3 | // 4 | // Copyright (C) 2013-2014 Kano Computing Ltd. 5 | // License: http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2 6 | // 7 | // An app to show and bring life to Kano-Make Desktop Icons. 8 | // 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | #include "configuration.h" 18 | 19 | // Default icon cursor when mouse moves over the icon 20 | // 21 | #define DEFAULT_ICON_CURSOR XC_hand1 22 | 23 | // Number of points to decrease the font size for subtitle text in icons 24 | #define DEFAULT_SUBTITLE_FONT_POINT_DECREASE 6 25 | 26 | class IconGrid; 27 | 28 | class Icon 29 | { 30 | private: 31 | Configuration *configuration; 32 | Display *icon_display; 33 | Window win; 34 | IconGrid *pgrid; 35 | int iconx, icony, iconw, iconh; 36 | int shadowx, shadowy; 37 | int stamp_x, stamp_y; 38 | int status_x, status_y; 39 | int message_x, message_y; 40 | int icontitlegap; 41 | int transparency_value; 42 | bool is_grid; 43 | int gridx, gridy; 44 | Cursor cursor; 45 | int cursor_id; 46 | Imlib_Image image, image_stamp, image_status; 47 | Imlib_Image backsafe; 48 | Visual *vis; 49 | Colormap cmap; 50 | XftFont *font; 51 | XftFont *fontsmaller; 52 | XGlyphInfo fontInfoCaption, fontInfoMessage; 53 | XftDraw *xftdraw1; 54 | XftColor xftcolor, xftcolor_shadow; 55 | unsigned char *iconMapNone, *iconMapGlow, *iconMapTransparency; 56 | std::string filename; 57 | std::string ficon; 58 | std::string ficon_hover; 59 | std::string ficon_stamp; 60 | std::string ficon_status; 61 | std::string caption; 62 | std::string message_line1; 63 | std::string message_line2; 64 | 65 | public: 66 | int iconid; 67 | 68 | Icon (Configuration *loaded_conf, int iconidx); 69 | virtual ~Icon (void); 70 | 71 | int get_iconid(void); 72 | int set_iconid(int iconidx); 73 | std::string get_appid(void); 74 | std::string get_icon_filename(void); 75 | std::string get_icon_name(void); 76 | std::string get_commandline(void); 77 | std::string get_font_name(void); 78 | int get_icon_horizontal_placement (int image_width); 79 | bool is_singleton_running (Display *display); 80 | 81 | Window create(Display *display, IconGrid *icon_grid); 82 | void destroy(Display *display); 83 | 84 | void draw(Display *display, XEvent ev, bool fClear); 85 | void clear(Display *display, XEvent ev); 86 | bool blink_icon(Display *display, XEvent ev); 87 | bool unblink_icon(Display *display, XEvent ev); 88 | bool double_click(Display *display, XEvent ev); 89 | bool motion(Display *display, XEvent ev); 90 | bool maximize(Display *display); 91 | bool maximize(Display *display, Window win); 92 | Window find_icon_window (Display *display, std::string appid); 93 | 94 | static int IgnoreBadWindowExceptions(Display *display, XErrorEvent *error); 95 | 96 | void set_caption (char *new_caption); 97 | void set_message (char *new_message); 98 | void set_icon (char *new_icon); 99 | void set_icon_stamp (char *new_icon); 100 | void set_icon_status (char *new_icon); 101 | 102 | }; 103 | -------------------------------------------------------------------------------- /src/kdesk-blur/Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # kdesk-blur Makefile 3 | # 4 | 5 | INCS:=-I../ 6 | LIBS:=-lXft -lImlib2 -lstdc++ -lpthread -lXss -lX11 7 | TARGET=kdesk-blur 8 | 9 | all: $(TARGET) 10 | 11 | debug: 12 | make all DEBUGGING="-ggdb -O3 -DDEBUG" 13 | 14 | # the linkage 15 | $(TARGET): $(TARGET).o 16 | g++ $(LIBS) $^ $(DEBUGGING) -o $(TARGET) 17 | 18 | # the compilation 19 | kdesk-blur.o: $(TARGET).cpp $(TARGET).h 20 | g++ -c $(INCS) $(DEBUGGING) $(TARGET).cpp 21 | -------------------------------------------------------------------------------- /src/kdesk-blur/kdesk-blur.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // kdesk-blur.cpp - Blur the desktop and start an application on top 3 | // 4 | // Copyright (C) 2013-2014 Kano Computing Ltd. 5 | // License: http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2 6 | // 7 | // 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #include "logging.h" 20 | #include "kdesk-blur.h" 21 | 22 | void *BlurDesktop (void *pvnothing); 23 | int main (int argc, char *argv[]); 24 | 25 | 26 | // A printf macro sensitive to the -v (verbose) flag 27 | // Use kprintf for regular stdout messages instead of printf or cout 28 | bool verbose=false; // Mute by default, no stdout messages unless in Debug build 29 | #define kprintf(fmt, ...) ( (verbose==false ? : printf(fmt, ##__VA_ARGS__) )) 30 | 31 | 32 | // 33 | // This function to be spawned in the background (p_thread) 34 | // Will create a top-level window with a blurred snapshot of the desktop 35 | // 36 | void *BlurDesktop(void *pvnothing) 37 | { 38 | Display *display=XOpenDisplay(NULL); 39 | if (!display) { 40 | log ("Could not connect to the XServer"); 41 | return NULL; 42 | } 43 | 44 | Window winblur=0L; 45 | bool success = false; 46 | Pixmap pmap_blur = 0L; 47 | Imlib_Image img_blurred; 48 | Imlib_Color_Modifier colorTrans=NULL; 49 | 50 | int screen = DefaultScreen (display); 51 | Window root_window = RootWindow (display, screen); 52 | int deskw = DisplayWidth(display, screen); 53 | int deskh = DisplayHeight(display, screen); 54 | 55 | // Creating a desktop blur effect. 56 | pmap_blur = XCreatePixmap (display, root_window, deskw, deskh, DefaultDepth (display, DefaultScreen (display))); 57 | img_blurred = imlib_create_image(deskw, deskh); 58 | if (!pmap_blur || !img_blurred) { 59 | log ("no resources to create a blurred pixmap or imlib image buffer"); 60 | success = false; 61 | } 62 | else { 63 | // Take a screenshot from the desktop and save it in a Imblib2 object 64 | // So we can use it's API to blur the image 65 | imlib_context_set_image(img_blurred); 66 | imlib_context_set_display(display); 67 | imlib_context_set_visual(DefaultVisual(display, 0)); 68 | imlib_context_set_drawable(root_window); 69 | imlib_copy_drawable_to_image(0, 0, 0, deskw, deskh, 0, 0, 1); 70 | 71 | // Create RGB tables to blur the colors 72 | unsigned char iconR[256], iconG[256], iconB[256], iconTR[256]; 73 | colorTrans = imlib_create_color_modifier(); 74 | imlib_context_set_color_modifier(colorTrans); 75 | imlib_get_color_modifier_tables(iconR, iconG, iconB, iconTR); 76 | imlib_reset_color_modifier(); 77 | 78 | // Blur the RGB channels to a third of their intensities 79 | for (int n=0; n < 256; n++) { 80 | iconR[n] = iconR[n] / 3; 81 | iconG[n] = iconG[n] / 3; 82 | iconB[n] = iconB[n] / 3; 83 | } 84 | 85 | // At this point img_blurred contains a blurred copy of the desktop image 86 | imlib_set_color_modifier_tables (iconR, iconG, iconB, iconR); 87 | 88 | // create a top level window which will draw the blurred desktop on top 89 | XSetWindowAttributes attr; 90 | memset (&attr, 0x00, sizeof (attr)); 91 | attr.background_pixmap = ParentRelative; 92 | 93 | // this will ensure the blurred image is repainted 94 | // should the top level unblurred window move along the desktop. 95 | attr.backing_store = Always; 96 | 97 | attr.event_mask = 0; // no relevant events we want to care about, XServer will do it for us 98 | attr.override_redirect = False; 99 | winblur = XCreateWindow (display, root_window, 0, 0, 100 | // 101 | // FIXME: 102 | // int 41 number needs to be fit with the amount of over-space used by the decorations. 103 | // 104 | deskw, deskh - 41, 0, 105 | CopyFromParent, CopyFromParent, CopyFromParent, 106 | CWEventMask, &attr); 107 | if (!winblur) { 108 | // Out of resources or problems with Xlib parameters 109 | log ("Could not create blur window"); 110 | success = false; 111 | } 112 | else { 113 | // The "Hints" code below is needed to remove the window decorations 114 | // We don't want a title or border frames. 115 | // TODO: Remove the taskbar icon associated to the window. 116 | typedef struct Hints 117 | { 118 | unsigned long flags; 119 | unsigned long functions; 120 | unsigned long decorations; 121 | long inputMode; 122 | unsigned long status; 123 | } Hints; 124 | 125 | Hints hints; 126 | Atom property_hints; 127 | hints.flags = 2; 128 | hints.decorations = 0; 129 | hints.inputMode = False; 130 | 131 | property_hints = XInternAtom(display, "_MOTIF_WM_HINTS", false); 132 | XChangeProperty (display, winblur, property_hints, property_hints, 32, PropModeReplace, (unsigned char *) &hints, 5); 133 | 134 | // _NET_WM_STATE_SKIP_TASKBAR Tells the window manager to not use an icon on the taskbar 135 | Atom net_wm_state = XInternAtom (display, "_NET_WM_STATE", false); 136 | Atom net_skip_taskbar = XInternAtom(display, "_NET_WM_STATE_SKIP_TASKBAR", false); 137 | XChangeProperty (display, winblur, net_wm_state, 138 | XA_ATOM, 32, PropModeAppend, 139 | (unsigned char *) &net_skip_taskbar, 1); 140 | 141 | // Give the blurred window a meaningful name. 142 | XStoreName (display, winblur, KDESK_BLUR_NAME); 143 | 144 | // Draw the blurred image into the pixmap, then apply the pixmap to the window 145 | // We do it this way so the XServer can have a copy for the backing store to repaint on Exposure events 146 | imlib_context_set_drawable(pmap_blur); 147 | imlib_render_image_on_drawable (0, 0); 148 | XSetWindowBackgroundPixmap(display, winblur, pmap_blur); 149 | 150 | // Map the window after setting the pixmap so it is displayed before the app that will run on top 151 | XMapWindow(display, winblur); 152 | XFlush(display); 153 | 154 | imlib_free_image(); 155 | 156 | log1 ("Blur window created successfully (winid)", winblur); 157 | success = true; 158 | } 159 | } 160 | 161 | return NULL; // we are launched in the background, use IsDesktopBlurred to find blur result 162 | } 163 | 164 | bool IsDesktopBlurred (void) 165 | { 166 | Display *display=XOpenDisplay(NULL); 167 | if (!display) { 168 | log ("Could not connect to the XServer"); 169 | return false; 170 | } 171 | 172 | int screen = DefaultScreen (display); 173 | Window root = RootWindow (display, screen); 174 | Window root_return, parent_return, *children_return=NULL, *subchildren_return=NULL; 175 | unsigned int nchildren_return=0, nsubchildren_return=0; 176 | bool found = false; 177 | 178 | // Enumerate all top level windows in search for the Kdesk's blurred window 179 | if (XQueryTree(display, root, &root_return, &parent_return, &children_return, &nchildren_return)) 180 | { 181 | char *windowname=NULL; 182 | for (int i=0; i < nchildren_return; i++) 183 | { 184 | if (XFetchName (display, children_return[i], &windowname)) { 185 | if (!strncmp (windowname, KDESK_BLUR_NAME, strlen (KDESK_BLUR_NAME))) { 186 | log1 ("Blurred window was found level1 (winid)", children_return[i]); 187 | found = true; 188 | XFree (windowname); 189 | break; 190 | } 191 | } 192 | 193 | XQueryTree (display, children_return[i], &root_return, &parent_return, &subchildren_return, &nsubchildren_return); 194 | 195 | for (int k=nsubchildren_return-1; k>=0; k--) { 196 | if (XFetchName (display, subchildren_return[k], &windowname)) { 197 | if (!strncmp (windowname, KDESK_BLUR_NAME, strlen (KDESK_BLUR_NAME))) { 198 | log1 ("Blurred window was found level2 (winid)", subchildren_return[i]); 199 | found=true; 200 | XFree (windowname); 201 | break; 202 | } 203 | } 204 | } 205 | 206 | } 207 | } 208 | 209 | if (children_return) { 210 | XFree(children_return); 211 | } 212 | 213 | if (subchildren_return) { 214 | XFree(subchildren_return); 215 | } 216 | 217 | return found; 218 | } 219 | 220 | int main (int argc, char *argv[]) 221 | { 222 | bool blurred = false; 223 | char *cmdline=NULL; 224 | int timeout = 5; // seconds to wait for blur to become visible 225 | pthread_t t; 226 | 227 | // collect top application command-line 228 | if (argc < 2) { 229 | printf ("Syntax: kdesk-blur [-v]\n"); 230 | printf (" Use double quotation marks and escaping for multiple arguments:\n"); 231 | printf (" $ kdesk-blur 'lxterminal --command=\"/bin/bash -c \\\"ls -l ; sleep 5\\\"\"'\n"); 232 | printf (" -v will emit messages during the process\n"); 233 | printf (" Error level will be set to -1 if blur error, otherwise the app's rc will be set\n"); 234 | exit (-1); 235 | } 236 | else { 237 | cmdline = strdup (argv[1]); 238 | if (argc > 2 && !strcasecmp (argv[2], "-v")) { 239 | verbose = true; 240 | } 241 | } 242 | 243 | // if desktop is not blurred yet, create the blur window 244 | if (!IsDesktopBlurred()) { 245 | kprintf ("Blurring the desktop\n"); 246 | log ("Blurring the desktop"); 247 | pthread_create (&t, NULL, BlurDesktop, NULL); 248 | while (!(blurred=IsDesktopBlurred()) && timeout-- > 0) 249 | { 250 | sleep(1); 251 | } 252 | 253 | if (!blurred) { 254 | // There was a problem blurring the desktop 255 | kprintf ("Error blurring the desktop\n"); 256 | log ("Error blurring the desktop"); 257 | exit (-1); 258 | } 259 | } 260 | else { 261 | log ("Desktop is already blurred"); 262 | } 263 | 264 | // Now we can execute the application on top 265 | kprintf ("Starting app on top of desktop: %s\n", cmdline); 266 | log1 ("Starting app on top of the desktop (cmdline)", cmdline); 267 | int rc = system (cmdline); 268 | log1 ("App has terminated (rc)", rc); 269 | kprintf ("App has terminated with rc=%d\n", rc); 270 | free (cmdline); 271 | exit (rc); 272 | } 273 | -------------------------------------------------------------------------------- /src/kdesk-blur/kdesk-blur.h: -------------------------------------------------------------------------------- 1 | // 2 | // kdesk-blur.h 3 | // 4 | 5 | #define KDESK_BLUR_NAME "KdeskBlurApp" 6 | -------------------------------------------------------------------------------- /src/kdesk-eglsaver/Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Makefile - To build kdesk-eglsaver 3 | # 4 | # Change OPTDIR to point to your RaspberryPI /opt firmware root directory - for include and lib files 5 | # 6 | 7 | OPTDIR=/opt/vc 8 | 9 | INCLUDES=-I$(OPTDIR)/include -I$(OPTDIR)/include/interface/vcos/pthreads -I$(OPTDIR)/include/interface/vmcs_host/linux 10 | 11 | CFLAGS=-DSTANDALONE -D__STDC_CONSTANT_MACROS -D__STDC_LIMIT_MACROS -DTARGET_POSIX -D_LINUX -fPIC -DPIC -D_REENTRANT -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 -U_FORTIFY_SOURCE -Wall -g -DHAVE_LIBOPENMAX=2 -DOMX -DOMX_SKIP64BIT -ftree-vectorize -pipe -DUSE_EXTERNAL_OMX -DHAVE_LIBBCM_HOST -DUSE_EXTERNAL_LIBBCM_HOST -DUSE_VCHIQ_ARM -Wno-psabi -Wno-unused-function 12 | 13 | LDFLAGS+=-L$(OPTDIR)/lib/ -lGLESv2 -lEGL -lbcm_host 14 | 15 | APP=kdesk-eglsaver 16 | 17 | # macro to transform a raw bitmap into a C array structure 18 | cstyle_from_raw=$(shell echo "// $1 Array Definition\nconst unsigned char $1[]={\n" > $1.h; cat $1.raw | hexdump -v -e '16/1 "0x%02x, "' -e '"\n"' >> $1.h; echo "};" >> $1.h) 19 | 20 | all: $(APP) 21 | debug: all 22 | 23 | # dynamically create C bitmap arrays from RAW bitmap files 24 | bitmap_minecraft.h: bitmap_minecraft.raw 25 | $(call cstyle_from_raw, "bitmap_minecraft") 26 | 27 | bitmap_pong.h: bitmap_pong.raw 28 | $(call cstyle_from_raw, "bitmap_pong") 29 | 30 | bitmap_homefolder.h: bitmap_homefolder.raw 31 | $(call cstyle_from_raw, "bitmap_homefolder") 32 | 33 | hid.o: hid.cpp hid.h 34 | g++ -c -o $@ hid.cpp 35 | 36 | $(APP): $(APP).o hid.o 37 | gcc -o $@ -Wl,--whole-archive $(APP).o hid.o $(LDFLAGS) -Wl,--no-whole-archive -rdynamic 38 | 39 | $(APP).o: $(APP).c bitmap_minecraft.h bitmap_pong.h bitmap_homefolder.h hid.h 40 | gcc $(CFLAGS) $(INCLUDES) -g -c $< -o $@ -Wno-deprecated-declarations 41 | -------------------------------------------------------------------------------- /src/kdesk-eglsaver/README.md: -------------------------------------------------------------------------------- 1 | == Kdesk - OpenGL screen saver 2 | 3 | This is kdesk openGL screen saver. To build it just execute: 4 | 5 | $ make 6 | 7 | You will need to change the variable OPTDIR in the Makefile to point to your Raspberry /opt 8 | include files and libraries. Usually this is provided by the Raspbian package "raspberrypi-firmware". 9 | 10 | === Icon RAW format 11 | 12 | Icons are in raw byte format, no encoding, to make for faster load times. 13 | 14 | In order to convert the icons for the cube surfaces, they must be 15 | 128x128, 3bpp and no alpha channel. To obtain these raw formats 16 | use imagemagick and follow these simple steps 17 | 18 | $ convert source.png -resize 128x128! -alpha off clean.png 19 | $ convert clean.png -size 128x128 -endian LSB -flip rgb:source.raw 20 | 21 | This gives each bitmap a small size footprint of 45K Bytes. 22 | 23 | The default build process will provide 3 sample Kano bitmaps embedded as C array structures, 24 | so the binary does not rely on external files and at the same time loads a bit faster. 25 | 26 | === Building your own screen saver 27 | 28 | You can provide your own screen saver program which performs whatever action needed. In such case please keep in mind the following 29 | rules to integrate nicely into kdesk: 30 | 31 | * Provide an initial wait delay of approximately 1 second for seamless user experience. 32 | * Periodically listen for user input events from keyboard/mouse (/dev/input is a good source) 33 | * Upon reception of user input, just terminate the program and kdesk will take over control 34 | * If you'd like kdesk to refresh the graphical desktop upon termination, set your return code to 0, any other value will not refresh the X screen. 35 | 36 | -------------------------------------------------------------------------------- /src/kdesk-eglsaver/bitmap_homefolder.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KanoComputing/kdesk/57b8e2a6001129f209f9b2526cf86d9f9b73b597/src/kdesk-eglsaver/bitmap_homefolder.raw -------------------------------------------------------------------------------- /src/kdesk-eglsaver/bitmap_minecraft.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KanoComputing/kdesk/57b8e2a6001129f209f9b2526cf86d9f9b73b597/src/kdesk-eglsaver/bitmap_minecraft.raw -------------------------------------------------------------------------------- /src/kdesk-eglsaver/bitmap_pong.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KanoComputing/kdesk/57b8e2a6001129f209f9b2526cf86d9f9b73b597/src/kdesk-eglsaver/bitmap_pong.raw -------------------------------------------------------------------------------- /src/kdesk-eglsaver/cube_texture_and_coords.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012, Broadcom Europe Ltd 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * Neither the name of the copyright holder nor the 13 | names of its contributors may be used to endorse or promote products 14 | derived from this software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | // Spatial coordinates for the cube 29 | 30 | static const GLbyte quadx[6*4*3] = { 31 | /* FRONT */ 32 | -10, -10, 10, 33 | 10, -10, 10, 34 | -10, 10, 10, 35 | 10, 10, 10, 36 | 37 | /* BACK */ 38 | -10, -10, -10, 39 | -10, 10, -10, 40 | 10, -10, -10, 41 | 10, 10, -10, 42 | 43 | /* LEFT */ 44 | -10, -10, 10, 45 | -10, 10, 10, 46 | -10, -10, -10, 47 | -10, 10, -10, 48 | 49 | /* RIGHT */ 50 | 10, -10, -10, 51 | 10, 10, -10, 52 | 10, -10, 10, 53 | 10, 10, 10, 54 | 55 | /* TOP */ 56 | -10, 10, 10, 57 | 10, 10, 10, 58 | -10, 10, -10, 59 | 10, 10, -10, 60 | 61 | /* BOTTOM */ 62 | -10, -10, 10, 63 | -10, -10, -10, 64 | 10, -10, 10, 65 | 10, -10, -10, 66 | }; 67 | 68 | /** Texture coordinates for the quad. */ 69 | static const GLfloat texCoords[6 * 4 * 2] = { 70 | 0.f, 0.f, 71 | 0.f, 1.f, 72 | 1.f, 0.f, 73 | 1.f, 1.f, 74 | 75 | 0.f, 0.f, 76 | 0.f, 1.f, 77 | 1.f, 0.f, 78 | 1.f, 1.f, 79 | 80 | 0.f, 0.f, 81 | 0.f, 1.f, 82 | 1.f, 0.f, 83 | 1.f, 1.f, 84 | 85 | 0.f, 0.f, 86 | 0.f, 1.f, 87 | 1.f, 0.f, 88 | 1.f, 1.f, 89 | 90 | 0.f, 0.f, 91 | 0.f, 1.f, 92 | 1.f, 0.f, 93 | 1.f, 1.f, 94 | 95 | 0.f, 0.f, 96 | 0.f, 1.f, 97 | 1.f, 0.f, 98 | 1.f, 1.f 99 | }; 100 | 101 | -------------------------------------------------------------------------------- /src/kdesk-eglsaver/hid.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // hid.cpp - Human Input Device 3 | // 4 | // This module encapsulates functions to deal with user keyboard and mouse activity 5 | // needed to decide when the screen saver needs to stop. 6 | // 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | 18 | #include "hid.h" 19 | 20 | HID_HANDLE hid_init(int flags) 21 | { 22 | int n=0; 23 | char buf[BUFF_SIZE]; 24 | 25 | // Allocate a structure to hold a list of HID devices 26 | HID_HANDLE hid=(HID_HANDLE) calloc (sizeof (HID_STRUCT), 1); 27 | if (!hid) { 28 | return NULL; 29 | } 30 | 31 | // Give a gratious time for the input event streams to flush type-ahead events 32 | usleep (GRACE_START_TIME * 1000); 33 | 34 | // Open all possible input devices (keyboard / mouse), 35 | // save their fd like handles 36 | // Emptying any possible type-ahead events. 37 | 38 | hid->fdkbd0 = open(chkbd0, O_RDWR | O_NOCTTY | O_NDELAY); 39 | if (hid->fdkbd0 != -1) { 40 | fcntl(hid->fdkbd0, F_SETFL, O_NONBLOCK); 41 | n=read(hid->fdkbd0, (void*)buf, sizeof(buf)); 42 | } 43 | 44 | hid->fdkbd1 = open(chkbd1, O_RDWR | O_NOCTTY | O_NDELAY); 45 | if (hid->fdkbd1 != -1) { 46 | fcntl(hid->fdkbd1, F_SETFL, O_NONBLOCK); 47 | n=read(hid->fdkbd1, (void*)buf, sizeof(buf)); 48 | } 49 | 50 | hid->fdkbd2 = open(chkbd2, O_RDWR | O_NOCTTY | O_NDELAY); 51 | if (hid->fdkbd2 != -1) { 52 | fcntl(hid->fdkbd2, F_SETFL, O_NONBLOCK); 53 | n=read(hid->fdkbd2, (void*)buf, sizeof(buf)); 54 | } 55 | 56 | hid->fdmouse0 = open(chmouse0, O_RDWR | O_NOCTTY | O_NDELAY); 57 | if (hid->fdmouse0 != -1) { 58 | fcntl(hid->fdmouse0, F_SETFL, O_NONBLOCK); 59 | n=read(hid->fdmouse0, (void*)buf, sizeof(buf)); 60 | } 61 | 62 | hid->fdmouse1 = open(chmouse1, O_RDWR | O_NOCTTY | O_NDELAY); 63 | if (hid->fdmouse1 != -1) { 64 | fcntl(hid->fdmouse1, F_SETFL, O_NONBLOCK); 65 | n=read(hid->fdmouse1, (void*)buf, sizeof(buf)); 66 | } 67 | 68 | hid->fdmice = open(chmice, O_RDWR | O_NOCTTY | O_NDELAY); 69 | if (hid->fdmice != -1) { 70 | fcntl(hid->fdmice, F_SETFL, O_NONBLOCK); 71 | n=read(hid->fdmice, (void*)buf, sizeof(buf)); 72 | } 73 | 74 | return hid; 75 | } 76 | 77 | bool hid_is_user_idle (HID_HANDLE hid, int timeout) 78 | { 79 | int rc=0; 80 | fd_set hid_devices; 81 | struct timeval tv; 82 | 83 | if (!hid) { 84 | return false; 85 | } 86 | 87 | // setting timeout member values to zero means return immediately 88 | // the passed timeout parameter is expressed in seconds. 89 | tv.tv_sec = timeout; 90 | tv.tv_usec = 0; 91 | 92 | FD_ZERO(&hid_devices); 93 | FD_SET(hid->fdkbd0, &hid_devices); 94 | FD_SET(hid->fdkbd1, &hid_devices); 95 | FD_SET(hid->fdkbd2, &hid_devices); 96 | FD_SET(hid->fdmouse0, &hid_devices); 97 | FD_SET(hid->fdmouse1, &hid_devices); 98 | FD_SET(hid->fdmice, &hid_devices); 99 | 100 | // return ASAP with indication on wether there is a HID input event or not 101 | rc = select (6, &hid_devices, NULL, NULL, &tv); 102 | return ((rc ? true : false)); 103 | } 104 | 105 | void hid_terminate(HID_HANDLE hid) 106 | { 107 | // Free HID devices and deallocate wrapped structure 108 | if (hid != NULL) { 109 | if (hid->fdkbd0 != -1) close (hid->fdkbd0); 110 | if (hid->fdkbd1 != -1) close (hid->fdkbd1); 111 | if (hid->fdkbd2 != -1) close (hid->fdkbd2); 112 | if (hid->fdmouse0 != -1) close(hid->fdmouse0); 113 | if (hid->fdmouse1 != -1) close (hid->fdmouse1); 114 | if (hid->fdmice != -1) close (hid->fdmice); 115 | 116 | free (hid); 117 | } 118 | 119 | return; 120 | } 121 | -------------------------------------------------------------------------------- /src/kdesk-eglsaver/hid.h: -------------------------------------------------------------------------------- 1 | // 2 | // hid.h - definitions for Human Input Device 3 | // 4 | 5 | #include 6 | 7 | // Defaut generous buffer size to read from devices 8 | #define BUFF_SIZE 128 9 | #define GRACE_START_TIME 300 // milliseconds to wait for type-ahead input events 10 | 11 | // Devices to which we are listening for user events 12 | #define chkbd0 "/dev/input/event0" 13 | #define chkbd1 "/dev/input/event1" 14 | #define chkbd2 "/dev/input/event2" 15 | #define chmouse0 "/dev/input/mouse0" 16 | #define chmouse1 "/dev/input/mouse1" 17 | #define chmice "/dev/input/mice" 18 | 19 | // Structure to hold the devices to listen to 20 | typedef struct _HID_STRUCT 21 | { 22 | // File-like handles to user input devices 23 | int fdkbd0, fdkbd1, fdkbd2, fdmouse0, fdmouse1, fdmice; 24 | 25 | } HID_STRUCT; 26 | 27 | typedef HID_STRUCT *HID_HANDLE; 28 | 29 | // APIs to init, terminate, and get events from HID devices 30 | 31 | 32 | #ifdef __cplusplus 33 | extern "C" 34 | #endif 35 | HID_HANDLE hid_init(int flags); 36 | 37 | #ifdef __cplusplus 38 | extern "C" 39 | #endif 40 | bool hid_is_user_idle (HID_HANDLE hhid, int timeout); 41 | 42 | #ifdef __cplusplus 43 | extern "C" 44 | #endif 45 | void hid_terminate(HID_HANDLE hid); 46 | -------------------------------------------------------------------------------- /src/kdesk-eglsaver/kdesk-eglsaver.c: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012, Broadcom Europe Ltd 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * Neither the name of the copyright holder nor the 13 | names of its contributors may be used to endorse or promote products 14 | derived from this software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | // A rotating cube rendered with OpenGL|ES. Three images used as textures on the cube faces. 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | #include "bcm_host.h" 38 | 39 | #include "GLES/gl.h" 40 | #include "EGL/egl.h" 41 | #include "EGL/eglext.h" 42 | 43 | #include "cube_texture_and_coords.h" 44 | 45 | // Bitmaps to be drawn on surfaces are C embedded data structures from RAW files 46 | #include "bitmap_minecraft.h" 47 | #include "bitmap_pong.h" 48 | #include "bitmap_homefolder.h" 49 | 50 | #include "hid.h" 51 | 52 | #define PATH "./" 53 | 54 | #define IMAGE_SIZE 512 55 | 56 | #ifndef M_PI 57 | #define M_PI 3.141592654 58 | #endif 59 | 60 | 61 | typedef struct 62 | { 63 | uint32_t screen_width; 64 | uint32_t screen_height; 65 | // OpenGL|ES objects 66 | EGLDisplay display; 67 | EGLSurface surface; 68 | EGLContext context; 69 | GLuint tex[6]; 70 | // model rotation vector and direction 71 | GLfloat rot_angle_x_inc; 72 | GLfloat rot_angle_y_inc; 73 | GLfloat rot_angle_z_inc; 74 | // current model rotation angles 75 | GLfloat rot_angle_x; 76 | GLfloat rot_angle_y; 77 | GLfloat rot_angle_z; 78 | // current distance from camera 79 | GLfloat distance; 80 | GLfloat distance_inc; 81 | // pointers to texture buffers 82 | char *tex_buf1; 83 | char *tex_buf2; 84 | char *tex_buf3; 85 | } CUBE_STATE_T; 86 | 87 | static void init_ogl(CUBE_STATE_T *state); 88 | static void init_model_proj(CUBE_STATE_T *state); 89 | static void reset_model(CUBE_STATE_T *state); 90 | static GLfloat inc_and_wrap_angle(GLfloat angle, GLfloat angle_inc); 91 | static GLfloat inc_and_clip_distance(GLfloat distance, GLfloat distance_inc); 92 | static void redraw_scene(CUBE_STATE_T *state); 93 | static void update_model(CUBE_STATE_T *state); 94 | static void init_textures(CUBE_STATE_T *state); 95 | static void load_tex_buffers(CUBE_STATE_T *state); 96 | static void load_tex_images(CUBE_STATE_T *state); 97 | static void exit_func(void); 98 | static volatile int terminate; 99 | static CUBE_STATE_T _state, *state=&_state; 100 | 101 | 102 | /*********************************************************** 103 | * Name: init_ogl 104 | * 105 | * Arguments: 106 | * CUBE_STATE_T *state - holds OGLES model info 107 | * 108 | * Description: Sets the display, OpenGL|ES context and screen stuff 109 | * 110 | * Returns: void 111 | * 112 | ***********************************************************/ 113 | static void init_ogl(CUBE_STATE_T *state) 114 | { 115 | int32_t success = 0; 116 | EGLBoolean result; 117 | EGLint num_config; 118 | 119 | static EGL_DISPMANX_WINDOW_T nativewindow; 120 | 121 | DISPMANX_ELEMENT_HANDLE_T dispman_element; 122 | DISPMANX_DISPLAY_HANDLE_T dispman_display; 123 | DISPMANX_UPDATE_HANDLE_T dispman_update; 124 | VC_RECT_T dst_rect; 125 | VC_RECT_T src_rect; 126 | 127 | static const EGLint attribute_list[] = 128 | { 129 | EGL_RED_SIZE, 8, 130 | EGL_GREEN_SIZE, 8, 131 | EGL_BLUE_SIZE, 8, 132 | EGL_ALPHA_SIZE, 8, 133 | EGL_SURFACE_TYPE, EGL_WINDOW_BIT, 134 | EGL_NONE 135 | }; 136 | 137 | EGLConfig config; 138 | 139 | // get an EGL display connection 140 | state->display = eglGetDisplay(EGL_DEFAULT_DISPLAY); 141 | assert(state->display!=EGL_NO_DISPLAY); 142 | 143 | // initialize the EGL display connection 144 | result = eglInitialize(state->display, NULL, NULL); 145 | assert(EGL_FALSE != result); 146 | 147 | // get an appropriate EGL frame buffer configuration 148 | result = eglChooseConfig(state->display, attribute_list, &config, 1, &num_config); 149 | assert(EGL_FALSE != result); 150 | 151 | // create an EGL rendering context 152 | state->context = eglCreateContext(state->display, config, EGL_NO_CONTEXT, NULL); 153 | assert(state->context!=EGL_NO_CONTEXT); 154 | 155 | // create an EGL window surface 156 | success = graphics_get_display_size(0 /* LCD */, &state->screen_width, &state->screen_height); 157 | assert( success >= 0 ); 158 | 159 | dst_rect.x = 0; 160 | dst_rect.y = 0; 161 | dst_rect.width = state->screen_width; 162 | dst_rect.height = state->screen_height; 163 | 164 | src_rect.x = 0; 165 | src_rect.y = 0; 166 | src_rect.width = state->screen_width << 16; 167 | src_rect.height = state->screen_height << 16; 168 | 169 | dispman_display = vc_dispmanx_display_open( 0 /* LCD */); 170 | dispman_update = vc_dispmanx_update_start( 0 ); 171 | 172 | // Put the screen saver on a specified Dispman layer if needed. 173 | // Eventually needed to cooperate with other EGL based apps and their Z-order. 174 | int layer=0; 175 | char *pchlayer=getenv("KDESK_EGLSAVER_LAYER"); 176 | if (pchlayer) { 177 | layer=atoi(pchlayer); 178 | } 179 | 180 | dispman_element = vc_dispmanx_element_add ( dispman_update, dispman_display, 181 | layer /*layer*/, &dst_rect, 0 /*src*/, 182 | &src_rect, DISPMANX_PROTECTION_NONE, 183 | 0 /*alpha*/, 0 /*clamp*/, 0 /*transform*/); 184 | 185 | nativewindow.element = dispman_element; 186 | nativewindow.width = state->screen_width; 187 | nativewindow.height = state->screen_height; 188 | vc_dispmanx_update_submit_sync( dispman_update ); 189 | 190 | state->surface = eglCreateWindowSurface( state->display, config, &nativewindow, NULL ); 191 | assert(state->surface != EGL_NO_SURFACE); 192 | 193 | // connect the context to the surface 194 | result = eglMakeCurrent(state->display, state->surface, state->surface, state->context); 195 | assert(EGL_FALSE != result); 196 | 197 | // Set background color and clear buffers 198 | // glClearColor(0.15f, 0.25f, 0.35f, 1.0f); 199 | 200 | // This mode will set desktop a black desktop background 201 | // causing no corners around the box 202 | glClearColor (0.0f, 0.0f, 0.0f, 1.0f); 203 | 204 | // Enable back face culling. 205 | glEnable(GL_CULL_FACE); 206 | 207 | glMatrixMode(GL_MODELVIEW); 208 | } 209 | 210 | /*********************************************************** 211 | * Name: init_model_proj 212 | * 213 | * Arguments: 214 | * CUBE_STATE_T *state - holds OGLES model info 215 | * 216 | * Description: Sets the OpenGL|ES model to default values 217 | * 218 | * Returns: void 219 | * 220 | ***********************************************************/ 221 | static void init_model_proj(CUBE_STATE_T *state) 222 | { 223 | float nearp = 1.0f; 224 | float farp = 500.0f; 225 | float hht; 226 | float hwd; 227 | 228 | glHint( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST ); 229 | 230 | glViewport(0, 0, (GLsizei)state->screen_width, (GLsizei)state->screen_height); 231 | 232 | glMatrixMode(GL_PROJECTION); 233 | glLoadIdentity(); 234 | 235 | hht = nearp * (float)tan(45.0 / 2.0 / 180.0 * M_PI); 236 | hwd = hht * (float)state->screen_width / (float)state->screen_height; 237 | 238 | glFrustumf(-hwd, hwd, -hht, hht, nearp, farp); 239 | 240 | glEnableClientState( GL_VERTEX_ARRAY ); 241 | glVertexPointer( 3, GL_BYTE, 0, quadx ); 242 | 243 | reset_model(state); 244 | } 245 | 246 | /*********************************************************** 247 | * Name: reset_model 248 | * 249 | * Arguments: 250 | * CUBE_STATE_T *state - holds OGLES model info 251 | * 252 | * Description: Resets the Model projection and rotation direction 253 | * 254 | * Returns: void 255 | * 256 | ***********************************************************/ 257 | static void reset_model(CUBE_STATE_T *state) 258 | { 259 | // reset model position 260 | glMatrixMode(GL_MODELVIEW); 261 | glLoadIdentity(); 262 | glTranslatef(0.f, 0.f, -50.f); 263 | 264 | // reset model rotation 265 | state->rot_angle_x = 45.f; state->rot_angle_y = 30.f; state->rot_angle_z = 0.f; 266 | state->rot_angle_x_inc = 0.5f; state->rot_angle_y_inc = 0.5f; state->rot_angle_z_inc = 0.f; 267 | state->distance = 40.f; 268 | } 269 | 270 | /*********************************************************** 271 | * Name: update_model 272 | * 273 | * Arguments: 274 | * CUBE_STATE_T *state - holds OGLES model info 275 | * 276 | * Description: Updates model projection to current position/rotation 277 | * 278 | * Returns: void 279 | * 280 | ***********************************************************/ 281 | static void update_model(CUBE_STATE_T *state) 282 | { 283 | // update position 284 | state->rot_angle_x = inc_and_wrap_angle(state->rot_angle_x, state->rot_angle_x_inc); 285 | state->rot_angle_y = inc_and_wrap_angle(state->rot_angle_y, state->rot_angle_y_inc); 286 | state->rot_angle_z = inc_and_wrap_angle(state->rot_angle_z, state->rot_angle_z_inc); 287 | state->distance = inc_and_clip_distance(state->distance, state->distance_inc); 288 | 289 | glLoadIdentity(); 290 | // move camera back to see the cube 291 | glTranslatef(0.f, 0.f, -state->distance); 292 | 293 | // Rotate model to new position 294 | glRotatef(state->rot_angle_x, 1.f, 0.f, 0.f); 295 | glRotatef(state->rot_angle_y, 0.f, 1.f, 0.f); 296 | glRotatef(state->rot_angle_z, 0.f, 0.f, 1.f); 297 | } 298 | 299 | /*********************************************************** 300 | * Name: inc_and_wrap_angle 301 | * 302 | * Arguments: 303 | * GLfloat angle current angle 304 | * GLfloat angle_inc angle increment 305 | * 306 | * Description: Increments or decrements angle by angle_inc degrees 307 | * Wraps to 0 at 360 deg. 308 | * 309 | * Returns: new value of angle 310 | * 311 | ***********************************************************/ 312 | static GLfloat inc_and_wrap_angle(GLfloat angle, GLfloat angle_inc) 313 | { 314 | angle += angle_inc; 315 | 316 | if (angle >= 360.0) 317 | angle -= 360.f; 318 | else if (angle <=0) 319 | angle += 360.f; 320 | 321 | return angle; 322 | } 323 | 324 | /*********************************************************** 325 | * Name: inc_and_clip_distance 326 | * 327 | * Arguments: 328 | * GLfloat distance current distance 329 | * GLfloat distance_inc distance increment 330 | * 331 | * Description: Increments or decrements distance by distance_inc units 332 | * Clips to range 333 | * 334 | * Returns: new value of angle 335 | * 336 | ***********************************************************/ 337 | static GLfloat inc_and_clip_distance(GLfloat distance, GLfloat distance_inc) 338 | { 339 | distance += distance_inc; 340 | 341 | if (distance >= 120.0f) 342 | distance = 120.f; 343 | else if (distance <= 40.0f) 344 | distance = 40.0f; 345 | 346 | return distance; 347 | } 348 | 349 | /*********************************************************** 350 | * Name: redraw_scene 351 | * 352 | * Arguments: 353 | * CUBE_STATE_T *state - holds OGLES model info 354 | * 355 | * Description: Draws the model and calls eglSwapBuffers 356 | * to render to screen 357 | * 358 | * Returns: void 359 | * 360 | ***********************************************************/ 361 | static void redraw_scene(CUBE_STATE_T *state) 362 | { 363 | // Start with a clear screen 364 | glClear( GL_COLOR_BUFFER_BIT ); 365 | 366 | // Draw first (front) face: 367 | // Bind texture surface to current vertices 368 | glBindTexture(GL_TEXTURE_2D, state->tex[0]); 369 | 370 | // Need to rotate textures - do this by rotating each cube face 371 | glRotatef(270.f, 0.f, 0.f, 1.f ); // front face normal along z axis 372 | 373 | // draw first 4 vertices 374 | glDrawArrays( GL_TRIANGLE_STRIP, 0, 4); 375 | 376 | // same pattern for other 5 faces - rotation chosen to make image orientation 'nice' 377 | glBindTexture(GL_TEXTURE_2D, state->tex[1]); 378 | glRotatef(90.f, 0.f, 0.f, 1.f ); // back face normal along z axis 379 | glDrawArrays( GL_TRIANGLE_STRIP, 4, 4); 380 | 381 | glBindTexture(GL_TEXTURE_2D, state->tex[2]); 382 | glRotatef(90.f, 1.f, 0.f, 0.f ); // left face normal along x axis 383 | glDrawArrays( GL_TRIANGLE_STRIP, 8, 4); 384 | 385 | glBindTexture(GL_TEXTURE_2D, state->tex[3]); 386 | glRotatef(90.f, 1.f, 0.f, 0.f ); // right face normal along x axis 387 | glDrawArrays( GL_TRIANGLE_STRIP, 12, 4); 388 | 389 | glBindTexture(GL_TEXTURE_2D, state->tex[4]); 390 | glRotatef(270.f, 0.f, 1.f, 0.f ); // top face normal along y axis 391 | glDrawArrays( GL_TRIANGLE_STRIP, 16, 4); 392 | 393 | glBindTexture(GL_TEXTURE_2D, state->tex[5]); 394 | glRotatef(90.f, 0.f, 1.f, 0.f ); // bottom face normal along y axis 395 | glDrawArrays( GL_TRIANGLE_STRIP, 20, 4); 396 | 397 | eglSwapBuffers(state->display, state->surface); 398 | } 399 | 400 | /*********************************************************** 401 | * Name: init_textures 402 | * 403 | * Arguments: 404 | * CUBE_STATE_T *state - holds OGLES model info 405 | * 406 | * Description: Initialise OGL|ES texture surfaces to use image 407 | * buffers 408 | * 409 | * Returns: void 410 | * 411 | ***********************************************************/ 412 | static void init_textures(CUBE_STATE_T *state) 413 | { 414 | 415 | // TODO: use load_tex_images when bitmap files are provided on the command line 416 | // load three texture buffers from files but use them on six OGL|ES texture surfaces 417 | // load_tex_images; 418 | 419 | // load_tex_buffers() use in-memory images compiled into the binary as raw data. 420 | load_tex_buffers(state); 421 | 422 | glGenTextures(6, &state->tex[0]); 423 | 424 | // setup first texture 425 | glBindTexture(GL_TEXTURE_2D, state->tex[0]); 426 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, IMAGE_SIZE, IMAGE_SIZE, 0, 427 | GL_RGB, GL_UNSIGNED_BYTE, state->tex_buf1); 428 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, (GLfloat)GL_NEAREST); 429 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, (GLfloat)GL_NEAREST); 430 | 431 | // setup second texture - reuse first image 432 | glBindTexture(GL_TEXTURE_2D, state->tex[1]); 433 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, IMAGE_SIZE, IMAGE_SIZE, 0, 434 | GL_RGB, GL_UNSIGNED_BYTE, state->tex_buf1); 435 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, (GLfloat)GL_NEAREST); 436 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, (GLfloat)GL_NEAREST); 437 | 438 | // third texture 439 | glBindTexture(GL_TEXTURE_2D, state->tex[2]); 440 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, IMAGE_SIZE, IMAGE_SIZE, 0, 441 | GL_RGB, GL_UNSIGNED_BYTE, state->tex_buf2); 442 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, (GLfloat)GL_NEAREST); 443 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, (GLfloat)GL_NEAREST); 444 | 445 | // fourth texture - reuse second image 446 | glBindTexture(GL_TEXTURE_2D, state->tex[3]); 447 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, IMAGE_SIZE, IMAGE_SIZE, 0, 448 | GL_RGB, GL_UNSIGNED_BYTE, state->tex_buf2); 449 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, (GLfloat)GL_NEAREST); 450 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, (GLfloat)GL_NEAREST); 451 | 452 | //fifth texture 453 | glBindTexture(GL_TEXTURE_2D, state->tex[4]); 454 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, IMAGE_SIZE, IMAGE_SIZE, 0, 455 | GL_RGB, GL_UNSIGNED_BYTE, state->tex_buf3); 456 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, (GLfloat)GL_NEAREST); 457 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, (GLfloat)GL_NEAREST); 458 | 459 | // sixth texture - reuse third image 460 | glBindTexture(GL_TEXTURE_2D, state->tex[5]); 461 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, IMAGE_SIZE, IMAGE_SIZE, 0, 462 | GL_RGB, GL_UNSIGNED_BYTE, state->tex_buf3); 463 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, (GLfloat)GL_NEAREST); 464 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, (GLfloat)GL_NEAREST); 465 | 466 | // setup overall texture environment 467 | glTexCoordPointer(2, GL_FLOAT, 0, texCoords); 468 | glEnableClientState(GL_TEXTURE_COORD_ARRAY); 469 | 470 | glEnable(GL_TEXTURE_2D); 471 | } 472 | 473 | static void load_tex_buffers(CUBE_STATE_T *state) 474 | { 475 | state->tex_buf1 = (char *) bitmap_homefolder; 476 | state->tex_buf2 = (char *) bitmap_pong; 477 | state->tex_buf3 = (char *) bitmap_minecraft; 478 | } 479 | 480 | 481 | /*********************************************************** 482 | * Name: load_tex_images 483 | * 484 | * Arguments: 485 | * void 486 | * 487 | * Description: Loads three raw images to use as textures on faces 488 | * 489 | * Returns: void 490 | * 491 | ***********************************************************/ 492 | static void load_tex_images(CUBE_STATE_T *state) 493 | { 494 | FILE *tex_file1 = NULL, *tex_file2=NULL, *tex_file3 = NULL; 495 | int bytes_read=0, image_sz = IMAGE_SIZE*IMAGE_SIZE*3; 496 | 497 | state->tex_buf1 = malloc(image_sz); 498 | state->tex_buf2 = malloc(image_sz); 499 | state->tex_buf3 = malloc(image_sz); 500 | 501 | // FIXME: Resolve image file sizes once perfect resolution has been matched 502 | // TODO: Extract paths to these images into kdesk configuration file 503 | tex_file1 = fopen(PATH "make-minecraft.raw", "rb"); 504 | if (tex_file1 && state->tex_buf1) 505 | { 506 | bytes_read=fread(state->tex_buf1, 1, image_sz, tex_file1); 507 | assert(bytes_read == image_sz); // some problem with file? 508 | fclose(tex_file1); 509 | } 510 | 511 | tex_file2 = fopen(PATH "kano-homefolder.raw", "rb"); 512 | if (tex_file2 && state->tex_buf2) 513 | { 514 | bytes_read=fread(state->tex_buf2, 1, image_sz, tex_file2); 515 | assert(bytes_read == image_sz); // some problem with file? 516 | fclose(tex_file2); 517 | } 518 | 519 | tex_file3 = fopen(PATH "pong.raw", "rb"); 520 | if (tex_file3 && state->tex_buf3) 521 | { 522 | bytes_read=fread(state->tex_buf3, 1, image_sz, tex_file3); 523 | assert(bytes_read == image_sz); // some problem with file? 524 | fclose(tex_file3); 525 | } 526 | } 527 | 528 | //------------------------------------------------------------------------------ 529 | 530 | static void exit_func(void) 531 | // Function to be passed to atexit(). 532 | { 533 | // clear screen 534 | glClear( GL_COLOR_BUFFER_BIT ); 535 | eglSwapBuffers(state->display, state->surface); 536 | 537 | // Release OpenGL resources 538 | eglMakeCurrent( state->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT ); 539 | eglDestroySurface( state->display, state->surface ); 540 | eglDestroyContext( state->display, state->context ); 541 | eglTerminate( state->display ); 542 | 543 | // TODO: Free buffers only if bitmaps were loaded from RAW image files 544 | // release texture buffers 545 | //free(state->tex_buf1); 546 | //free(state->tex_buf2); 547 | //free(state->tex_buf3); 548 | 549 | } // exit_func() 550 | 551 | //============================================================================== 552 | 553 | int main () 554 | { 555 | HID_HANDLE hid=hid_init(0); 556 | 557 | bcm_host_init(); 558 | 559 | // Clear application state 560 | memset (state, 0, sizeof(*state)); 561 | 562 | // Start OGLES 563 | init_ogl(state); 564 | 565 | // Setup the model world 566 | init_model_proj(state); 567 | 568 | // initialise the OGLES texture(s) 569 | init_textures(state); 570 | 571 | // Initial startup delay to settle relax XServer events 572 | usleep (1000 * 1000); 573 | 574 | while (!terminate) { 575 | 576 | update_model(state); 577 | redraw_scene(state); 578 | 579 | // If there is an input event from keyboard or mouse, stop now 580 | if (hid_is_user_idle(hid, 0) == true) { 581 | terminate=true; 582 | } 583 | } 584 | 585 | hid_terminate(hid); 586 | 587 | exit_func(); 588 | return 0; 589 | } 590 | -------------------------------------------------------------------------------- /src/kfbsaver/Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Build kfbsaver - Kdesk Framebuffer screen saver 3 | # 4 | 5 | all: kfbsaver 6 | 7 | kfbsaver: kfbsaver.o 8 | gcc kfbsaver.o -o kfbsaver 9 | 10 | kfbsaver.o: kfbsaver.cpp 11 | gcc -c -O1 kfbsaver.cpp -------------------------------------------------------------------------------- /src/kfbsaver/README: -------------------------------------------------------------------------------- 1 | README - KFBSaver 2 | A very simple frame buffer screen saver for Kdesk 3 | ------------------------------------------------- 4 | 5 | Implementing a screen saver in Kdesk is a bit tricky, because 6 | we depend on Minecraft, and it seems to draw directly to the framebuffer. 7 | 8 | A bit of background on the RPi framebuffer: 9 | 10 | http://elinux.org/RPi_Framebuffer 11 | 12 | This means we need to have a screen saver that draws directly 13 | to this buffer on the topmost layer to overlap on top of Minecraft. 14 | This screen saver attempts to do this but *still* at this point fails for Minecraft. 15 | 16 | Nevertheless it succeeds on the rest of the system apps. 17 | The code is based on the recipes found on these set of articles: 18 | 19 | http://raspberrycompote.blogspot.ie/2012/12/low-level-graphics-on-raspberry-pi-part_9509.html 20 | 21 | Additionally it listens for events from the keyboard and mouse, 22 | so any activity the user sends to the system will effectively kill the screen saver 23 | returning the user to the Kano Desktop. 24 | 25 | Keys on kdeskrc that control the screen saver are: 26 | 27 | ScreenSaverTimeout: x (expressed in seconds) 28 | ScreenSaverProgra: y (binary program to draw on the screen: kfbsaver provides this) 29 | 30 | Kano Computing, March 2014 31 | -------------------------------------------------------------------------------- /src/kfbsaver/kfbsaver.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // kbfsaver.cpp - Implement a simple screen saver drawing directly to the framebuffer 3 | // With a bit more time investing on this it could also use OpenGL using a top z-order layer 4 | // 5 | // This code is highly based on this great tutorial: 6 | // 7 | // * http://raspberrycompote.blogspot.com.es/2013/01/low-level-graphics-on-raspberry-pi-part.html 8 | // 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | int draw_something (char *screen_surface, long int screen_size) 20 | { 21 | // just fill upper half of the screen with something 22 | memset (screen_surface, 0x12, screen_size/2); 23 | 24 | // and lower half with something else 25 | memset (screen_surface + screen_size/2, 0x10, screen_size/2); 26 | } 27 | 28 | int main(int argc, char* argv[]) 29 | { 30 | unsigned long refresh_rate = 1; 31 | struct fb_var_screeninfo vinfo; 32 | struct fb_fix_screeninfo finfo; 33 | long int screen_size = 0; 34 | char *fbp = 0; 35 | int idling = 1; 36 | int fbfd = 0; 37 | int n; 38 | 39 | // Open the file for reading and writing 40 | fbfd = open("/dev/fb0", O_RDWR); 41 | if (!fbfd) { 42 | printf("Error: cannot open framebuffer device.\n"); 43 | return(1); 44 | } 45 | printf("The framebuffer device was opened successfully.\n"); 46 | 47 | // Get fixed screen information 48 | if (ioctl(fbfd, FBIOGET_FSCREENINFO, &finfo)) { 49 | printf("Error reading fixed information.\n"); 50 | } 51 | 52 | // Get variable screen information 53 | if (ioctl(fbfd, FBIOGET_VSCREENINFO, &vinfo)) { 54 | printf("Error reading variable information.\n"); 55 | } 56 | printf("%dx%d, %d bpp\n", vinfo.xres, vinfo.yres, 57 | vinfo.bits_per_pixel ); 58 | 59 | // map framebuffer to user memory 60 | screen_size = finfo.smem_len; 61 | fbp = (char*)mmap(0, 62 | screen_size, 63 | PROT_READ | PROT_WRITE, 64 | MAP_SHARED, 65 | fbfd, 0); 66 | 67 | if ((int)fbp == -1) { 68 | printf("Failed to mmap.\n"); 69 | } 70 | else { 71 | 72 | // Open access to input devices (keyboard / mouse) 73 | int fdkbd, fdmouse; 74 | char buf[128]; 75 | 76 | // Initial startup delay to settle relax XServer events 77 | usleep (1000 * 1000); 78 | 79 | fdkbd = open("/dev/input/event1", O_RDWR | O_NOCTTY | O_NDELAY); 80 | if (fdkbd == -1) { 81 | printf("open_port: Unable to open /dev/input/event1"); 82 | idling = 0; 83 | } 84 | else { 85 | // Turn off blocking for reads, use (fd, F_SETFL, FNDELAY) if you want that 86 | fcntl(fdkbd, F_SETFL, O_NONBLOCK); 87 | } 88 | 89 | fdmouse = open("/dev/input/event0", O_RDWR | O_NOCTTY | O_NDELAY); 90 | if (fdmouse == -1) { 91 | printf("open_port: Unable to open /dev/input/event2"); 92 | idling = 0; 93 | } 94 | else { 95 | // Turn off blocking for reads, use (fd, F_SETFL, FNDELAY) if you want that 96 | fcntl(fdmouse, F_SETFL, O_NONBLOCK); 97 | } 98 | 99 | // Let's draw something on the screen, repeatedly, 100 | // until we receive input from either the keyboard or mouse 101 | while (idling) 102 | { 103 | draw_something (fbp, screen_size); 104 | 105 | // If there is an input event from keyboard or mouse, stop now 106 | n = read(fdkbd, (void*)buf, 255); 107 | if (n > 0) { 108 | idling = 0; 109 | } 110 | 111 | n = read(fdmouse, (void*)buf, 255); 112 | if (n > 0) { 113 | idling = 0; 114 | } 115 | 116 | // wait half a second 117 | usleep (1000 * refresh_rate); 118 | } 119 | } 120 | 121 | printf ("cleanup and exit"); 122 | munmap(fbp, screen_size); 123 | close(fbfd); 124 | 125 | // Refresh the - most possibly - running XServer desktop 126 | system ("xrefresh"); 127 | return 0; 128 | } 129 | -------------------------------------------------------------------------------- /src/libkdesk-hourglass/Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # libkdesk-hourglass - Makefile 3 | # 4 | # A library API to provide for a loading progress mouse cursor accross your apps 5 | # 6 | 7 | libstartup:=libstartup-notification-1.0 8 | 9 | INCS:=-I../ `pkg-config --cflags-only-I $(libstartup)` 10 | LIBS:=`pkg-config --libs $(libstartup)` 11 | TARGET:=kdesk-hourglass 12 | 13 | CPPTESTDYNLIB:=testdynlib 14 | CPPTESTSHARED:=testshared 15 | CPPTESTSHARED:=testshared 16 | 17 | KDESK_HOURGLASS_APP:=kdesk-hourglass-app 18 | 19 | all: $(TARGET) $(CPPTESTDYNLIB) $(CPPTESTSHARED) $(KDESK_HOURGLASS_APP) 20 | 21 | debug: 22 | make all DEBUGGING="-ggdb -O3 -DDEBUG" 23 | 24 | clean: 25 | -rm *o 26 | -rm lib$(TARGET).so 27 | -rm $(CPPTESTDYNLIB) $(CPPTESTSHARED) $(KDESK_HOURGLASS_APP) 28 | 29 | # the linkage 30 | $(TARGET): $(TARGET).o 31 | $(CXX) -shared -rdynamic -Wl,-soname,lib$(TARGET).so -o lib$(TARGET).so $(LIBS) $^ $(DEBUGGING) 32 | 33 | # the compilation 34 | $(TARGET).o: $(TARGET).cpp $(TARGET).h 35 | $(CXX) -c -fPIC $(INCS) $(DEBUGGING) $(TARGET).cpp 36 | 37 | # C++ sample on how to use the kdesk hourglass library dynamically 38 | $(CPPTESTDYNLIB): $(CPPTESTDYNLIB).o 39 | $(CXX) $(CPPTESTDYNLIB).o -ldl -o $(CPPTESTDYNLIB) 40 | 41 | $(CPPTESTDYNLIB).o: $(CPPTESTDYNLIB).cpp 42 | $(CXX) -c $(CPPTESTDYNLIB).cpp 43 | 44 | # C++ sample on how to use the kdesk hourglass library at build time 45 | $(CPPTESTSHARED): $(CPPTESTSHARED).o 46 | $(CXX) $(CPPTESTSHARED).o -L. -l$(TARGET) -o $(CPPTESTSHARED) 47 | 48 | $(CPPTESTSHARED).o: $(CPPTESTSHARED).cpp 49 | $(CXX) -c -g $(CPPTESTSHARED).cpp 50 | 51 | # A command line tool to bring up the hourglass to a loading app 52 | $(KDESK_HOURGLASS_APP): $(KDESK_HOURGLASS_APP).o 53 | $(CXX) $(KDESK_HOURGLASS_APP).o -L. -l$(TARGET) -o $(KDESK_HOURGLASS_APP) 54 | 55 | $(KDESK_HOURGLASS_APP).o: $(KDESK_HOURGLASS_APP).cpp 56 | $(CXX) -c -g $(KDESK_HOURGLASS_APP).cpp 57 | 58 | -------------------------------------------------------------------------------- /src/libkdesk-hourglass/kdesk-hourglass-app.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // kdesk-hourglass-app.cpp 3 | // 4 | // Copyright (C) 2013-2014 Kano Computing Ltd. 5 | // License: http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2 6 | // 7 | // C Client tool to bring up the hourglass for a specified app name 8 | // 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #include "kdesk-hourglass.h" 15 | 16 | char *defapp=(char *)"/usr/bin/xcalc"; 17 | char *defappname=(char *)"xcalc"; 18 | 19 | int main(int argc, char *argv[]) { 20 | 21 | char *app=defapp, *appname=defappname; 22 | 23 | if (argc < 2) { 24 | printf ("Syntax: kdesk-hourglass-app <[application name] [-r]>\n"); 25 | printf (" The -r parameter will cancel the hourglass\n"); 26 | printf (" Any other value will bring up the hourglass until the application shows up\n"); 27 | exit(0); 28 | } 29 | else { 30 | // Remove the hourglass 31 | if (!strcmp(argv[1], "-r")) { 32 | kdesk_hourglass_end(); 33 | exit(0); 34 | } 35 | else { 36 | // Start the hourglass for the app 37 | appname=strdup(argv[1]); 38 | kdesk_hourglass_start(appname); 39 | exit(0); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/libkdesk-hourglass/kdesk-hourglass.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // kdesk-hourglass.cpp - Provide a system wide library for a mouse hourglass 3 | // 4 | // Copyright (C) 2013-2014 Kano Computing Ltd. 5 | // License: http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2 6 | // 7 | // A shared library to provide apps with a desktop app loading mouse hourglass. 8 | // 9 | 10 | #define SN_API_NOT_YET_FROZEN 11 | #include 12 | 13 | #include "sn_callbacks.cpp" 14 | 15 | #include 16 | #include 17 | #include "kdesk-hourglass.h" 18 | 19 | // Global variables 20 | SnDisplay *sn_display=NULL; 21 | SnLauncherContext *sn_context=NULL; 22 | Display *display=NULL; 23 | bool initialized=false; 24 | 25 | // local private prototypes 26 | void __attribute__ ((constructor)) initialize(void); 27 | void __attribute__ ((destructor)) finalize(void); 28 | void _start_launcher_(char *appname, char *cmdline); 29 | 30 | 31 | void kdesk_hourglass_start(char *appname) 32 | { 33 | // Use this API when you know the exact name of the app 34 | // in the X11 namespace (execute xwinfinfo and find the WINDOW_NAME tuple attributes) 35 | _start_launcher_(appname, NULL); 36 | } 37 | 38 | void kdesk_hourglass_start_appcmd(char *cmdline) 39 | { 40 | // Use this API when you only have the command line of your app 41 | 42 | // We will strip off the cmdline full path, 43 | // beacause sn_notify wants to match against the program binary name only. 44 | char *jump_slash=strstr(cmdline, "/"); 45 | while (jump_slash != NULL) { 46 | cmdline=jump_slash + sizeof(char); 47 | jump_slash=strstr(cmdline, "/"); 48 | } 49 | 50 | _start_launcher_(NULL, cmdline); 51 | } 52 | 53 | void kdesk_hourglass_end() 54 | { 55 | finalize(); 56 | return; 57 | } 58 | 59 | void _start_launcher_(char *appname, char *cmdline) 60 | { 61 | Time xlib_time=0L; 62 | 63 | // bind to the startup notify library 64 | initialize(); 65 | if (!display) { 66 | // could not bind to the XServer 67 | return; 68 | } 69 | 70 | // sn_notify cannot cope with appname being NULL, but he likes it as an empty string. 71 | if (!appname) { 72 | appname=(char*) ""; 73 | } 74 | 75 | sn_launcher_context_set_name (sn_context, appname); 76 | sn_launcher_context_set_description (sn_context, appname); 77 | sn_launcher_context_set_binary_name (sn_context, cmdline ? cmdline : appname); 78 | sn_launcher_context_set_icon_name(sn_context, appname); 79 | 80 | // We are not interested on the launcher-launchee relationship at this point 81 | sn_launcher_context_initiate (sn_context, "launcher", "launchee", xlib_time); 82 | sn_launcher_context_setup_child_process (sn_context); 83 | 84 | // use a new context if this app requests a new hourglass 85 | initialized=false; 86 | } 87 | 88 | // Library constructor 89 | void __attribute__ ((constructor)) initialize(void) 90 | { 91 | if (initialized) { 92 | return; 93 | } 94 | 95 | if (!display) { 96 | display=XOpenDisplay(NULL); 97 | if (!display) { 98 | return; 99 | } 100 | } 101 | 102 | if (!sn_display) { 103 | sn_display = sn_display_new (display, error_trap_push, error_trap_pop); 104 | } 105 | 106 | // renew the startup notify context for apps that chain hourglass requests 107 | // i.e. multiple hourglass requests without terminating the process. 108 | if (sn_context) { 109 | sn_launcher_context_complete(sn_context); 110 | sn_launcher_context_unref(sn_context); 111 | sn_context=NULL; 112 | } 113 | 114 | if (!sn_context) { 115 | sn_context = sn_launcher_context_new (sn_display, DefaultScreen (display)); 116 | } 117 | 118 | initialized=true; 119 | } 120 | 121 | // Library destructor 122 | void __attribute__ ((destructor)) finalize(void) 123 | { 124 | if (!initialized) { 125 | return; 126 | } 127 | 128 | if (display) { 129 | XCloseDisplay(display); 130 | display=0L; 131 | sn_display=0L; 132 | } 133 | 134 | initialized=false; 135 | } 136 | -------------------------------------------------------------------------------- /src/libkdesk-hourglass/kdesk-hourglass.h: -------------------------------------------------------------------------------- 1 | // 2 | // kdesk-hourglass.h 3 | // 4 | // Copyright (C) 2013-2014 Kano Computing Ltd. 5 | // License: http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2 6 | // 7 | // A shared library to provide apps with a desktop app loading mouse hourglass. 8 | // 9 | 10 | #ifdef __cplusplus 11 | extern "C" 12 | #endif 13 | 14 | void kdesk_hourglass_start(char *appname); 15 | 16 | #ifdef __cplusplus 17 | extern "C" 18 | #endif 19 | 20 | void kdesk_hourglass_start_appcmd(char *cmdline); 21 | 22 | #ifdef __cplusplus 23 | extern "C" 24 | #endif 25 | 26 | void kdesk_hourglass_end(void); 27 | -------------------------------------------------------------------------------- /src/libkdesk-hourglass/python/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # __init__.py 4 | # 5 | # Copyright (C) 2014 Kano Computing Ltd. 6 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2 7 | # 8 | 9 | __author__ = 'Kano Computing Ltd.' 10 | __email__ = 'dev@kano.me' 11 | -------------------------------------------------------------------------------- /src/libkdesk-hourglass/python/hourglass.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # hourglass.py 4 | # 5 | # Copyright (C) 2014 Kano Computing Ltd. 6 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2 7 | # 8 | # Provides a Python interface to kdesk hourglass library. 9 | # 10 | 11 | import ctypes 12 | 13 | libname='libkdesk-hourglass.so' 14 | kdesk_hourglass_lib=ctypes.CDLL(libname) 15 | 16 | def hourglass_start(appname): 17 | c_appname=ctypes.c_char_p(appname) 18 | kdesk_hourglass_lib.kdesk_hourglass_start(c_appname) 19 | 20 | def hourglass_start_appcmd(cmdline): 21 | c_cmdline=ctypes.c_char_p(cmdline) 22 | kdesk_hourglass_lib.kdesk_hourglass_start_appcmd(c_cmdline) 23 | 24 | def hourglass_end(): 25 | kdesk_hourglass_lib.kdesk_hourglass_end() 26 | -------------------------------------------------------------------------------- /src/libkdesk-hourglass/sn_callbacks.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // sn_callbacks.cpp 3 | // 4 | // Copyright (C) 2013-2014 Kano Computing Ltd. 5 | // License: http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2 6 | // 7 | // An app to show and bring life to Kano-Make Desktop Icons. 8 | // 9 | 10 | // 11 | // The following module provides for sn_notify framework callback functions 12 | // to handle event errors. 13 | // 14 | // TODO: Always keep an eye on libsn upgrades. 15 | // This API library is not officially conformed across different architectures 16 | // See the file: /usr/include/startup-notification-1.0/libsn/sn-common.h for details 17 | // 18 | 19 | #define SN_API_NOT_YET_FROZEN 20 | #include 21 | 22 | #include "logging.h" 23 | 24 | unsigned int error_trap_depth; 25 | 26 | void error_trap_push (SnDisplay *display, Display *xdisplay); 27 | void error_trap_pop (SnDisplay *display, Display *xdisplay); 28 | 29 | void error_trap_push (SnDisplay *display, Display *xdisplay) 30 | { 31 | log("error_trap_push incrementing"); 32 | ++error_trap_depth; 33 | } 34 | 35 | void error_trap_pop (SnDisplay *display, Display *xdisplay) 36 | { 37 | log ("error_trap_pop call"); 38 | if (error_trap_depth == 0) 39 | { 40 | log("fatal error trap underflow - the x11 event chain is hurt"); 41 | } 42 | 43 | XSync (xdisplay, False); /* get all errors out of the queue */ 44 | --error_trap_depth; 45 | } 46 | -------------------------------------------------------------------------------- /src/libkdesk-hourglass/testdynlib.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // testlib.cpp - C++ sample to test the hourglass library 3 | // 4 | // Copyright (C) 2013-2014 Kano Computing Ltd. 5 | // License: http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2 6 | // 7 | // A shared library to provide apps with a desktop app loading mouse hourglass. 8 | // 9 | // Compilation: gcc testdynlib.cpp -ldl -o testdynlib 10 | // 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | void *lib_handle; 18 | void (*fn_start)(char *); 19 | void (*fn_end)(void); 20 | char *error=NULL; 21 | 22 | char *app=(char *)"/usr/bin/xcalc &"; 23 | char *appname=(char *)"xcalc"; 24 | 25 | int main(void) 26 | { 27 | 28 | printf ("loading the hourglass dynamic library\n"); 29 | lib_handle = (void*) dlopen("libkdesk-hourglass.so", RTLD_LAZY); 30 | if (!lib_handle) 31 | { 32 | fprintf(stderr, "%s\n", dlerror()); 33 | exit(1); 34 | } 35 | 36 | // load the library function entry points 37 | fn_start = ( void (*)(char *) ) dlsym(lib_handle, "kdesk_hourglass_start"); 38 | if ((error = dlerror()) != NULL) { 39 | fprintf(stderr, "%s\n", error); 40 | exit(1); 41 | } 42 | else { 43 | 44 | fn_end = ( void (*)() ) dlsym(lib_handle, "kdesk_hourglass_end"); 45 | if ((error = dlerror()) != NULL) { 46 | fprintf(stderr, "%s\n", error); 47 | exit(1); 48 | } 49 | else { 50 | printf ("showing the hourglass before loading the app\n"); 51 | (*fn_start)(appname); 52 | } 53 | } 54 | 55 | printf ("do the lengthy loading job here (hourglass is visible)\n"); 56 | int rc = system (app); 57 | if (rc) { 58 | printf ("something went wrong, removing the hourglass\n"); 59 | (*fn_end)(); 60 | } 61 | else { 62 | printf ("app started, hourglass should disappear once it is responsive\n"); 63 | } 64 | 65 | printf ("seeya!\n"); 66 | dlclose(lib_handle); 67 | return 0; 68 | } 69 | 70 | -------------------------------------------------------------------------------- /src/libkdesk-hourglass/testlib.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # testlib.py 4 | # 5 | # Copyright (C) 2013-2014 Kano Computing Ltd. 6 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2 7 | # 8 | # A python test app to load and call the kdesk-hourglass dynamic library 9 | # 10 | 11 | import ctypes 12 | import sys 13 | import subprocess 14 | import time 15 | import os 16 | 17 | app='/usr/bin/xcalc' 18 | appname='xcalc' 19 | 20 | print 'loading the hourglass dynamic library' 21 | libname='libkdesk-hourglass.so' 22 | kdesk_hourglass_lib=ctypes.CDLL(libname) 23 | 24 | print 'showing the hourglass before loading the app' 25 | c_appname=ctypes.c_char_p(appname) 26 | kdesk_hourglass_lib.kdesk_hourglass_start(c_appname) 27 | 28 | try: 29 | # as soon as the app is responsive, the hourglass will self-disappear 30 | cmdline=app.split(' ') 31 | p = subprocess.Popen(cmdline) 32 | print 'our app is ready and responsive, removing the hourglass' 33 | p.communicate() 34 | except: 35 | # something went wrong... stop the hourglass 36 | print 'could not start app.. cancelling hourglass' 37 | kdesk_hourglass_lib.kdesk_hourglass_start(appname) 38 | 39 | print 'seeya!' 40 | sys.exit(0) 41 | -------------------------------------------------------------------------------- /src/libkdesk-hourglass/testmodule.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # testmodule.py 4 | # 5 | # Copyright (C) 2013-2014 Kano Computing Ltd. 6 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2 7 | # 8 | # A python test app to load and call the kdesk-hourglass dynamic library 9 | # 10 | 11 | from kdesk.hourglass import hourglass_start, hourglass_start_appcmd, hourglass_end 12 | import os 13 | import sys 14 | import time 15 | 16 | # Application to test for startup hourglass 17 | app='/usr/bin/xcalc' 18 | appname='xcalc' 19 | 20 | # testing the appname API 21 | hourglass_start(appname) 22 | rc = os.system(app) 23 | if (rc!=0): 24 | hourglass_end() 25 | sys.exit(1) 26 | 27 | time.sleep(1) 28 | 29 | # testing the cmdline API 30 | hourglass_start_appcmd(app) 31 | rc = os.system(app) 32 | if (rc!=0): 33 | hourglass_end() 34 | sys.exit(1) 35 | 36 | print 'byebye!' 37 | sys.exit(rc) 38 | -------------------------------------------------------------------------------- /src/libkdesk-hourglass/testshared.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // testsharedlib.cpp - C++ sample te test the shared hourglass library 3 | // 4 | // Copyright (C) 2013-2014 Kano Computing Ltd. 5 | // License: http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2 6 | // 7 | // A shared library to provide apps with a desktop app loading mouse hourglass. 8 | // 9 | // Compilation: gcc testshared.cpp -lkdesk-hourglass -o testshared 10 | // 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | #include "kdesk-hourglass.h" 17 | 18 | char *defapp=(char *)"/usr/bin/xcalc"; 19 | char *defappname=(char *)"xcalc"; 20 | 21 | int main(int argc, char *argv[]) { 22 | 23 | char *app=defapp, *appname=defappname; 24 | 25 | if (argc == 3) { 26 | appname=strdup(argv[1]); 27 | app=strdup(argv[2]); 28 | } 29 | 30 | printf ("Starting appname: %s (%s)\n", appname, app); 31 | kdesk_hourglass_start(appname); 32 | int rc = system (app); 33 | if (rc) { 34 | printf ("something went wrong, removing the hourglass\n"); 35 | kdesk_hourglass_end(); 36 | } 37 | 38 | printf ("app started - byebye!\n"); 39 | exit(0); 40 | } 41 | -------------------------------------------------------------------------------- /src/logging.h: -------------------------------------------------------------------------------- 1 | // 2 | // Macro to log in debug mode. When built for release, the code just disappears. 3 | // 4 | // Copyright (C) 2013-2014 Kano Computing Ltd. 5 | // License: http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2 6 | // 7 | // An app to show and bring life to Kano-Make Desktop Icons. 8 | // 9 | 10 | #include 11 | 12 | using namespace std; 13 | 14 | #ifdef DEBUG 15 | #define log(s) { cout << __FILE__ << ":" << __FUNCTION__ << "()@" << __LINE__ << " => " << s << endl ;} 16 | #define log1(s,f) { cout << __FILE__ << ":" << __FUNCTION__ << "()@" << __LINE__ << " => " << s << " " << f \ 17 | << endl ;} 18 | #define log2(s,f,j) { cout << __FILE__ << ":" << __FUNCTION__ << "()@" << __LINE__ << " => " << s << " " << f \ 19 | << " " << j << endl ;} 20 | 21 | #define log3(s,f,j,k) { cout << __FILE__ << ":" << __FUNCTION__ << "()@" << __LINE__ << " => " << s << " " << f \ 22 | << " " << j << " " << k << endl ;} 23 | 24 | #define log4(s,f,j,k,l) { cout << __FILE__ << ":" << __FUNCTION__ << "()@" << __LINE__ << " => " << s << " " \ 25 | << f << " " << j << " " << k << " " << l << endl ;} 26 | 27 | #define log5(s,f,j,k,l,m) { cout << __FILE__ << ":" << __FUNCTION__ << "()@" << __LINE__ << " => " << s << " " \ 28 | << f << " " << j << " " << k << " " << l << " " << m << endl ;} 29 | #else 30 | #define log(s) {} 31 | #define log1(s,f) {} 32 | #define log2(s,f,j) {} 33 | #define log3(s,f,j,k) {} 34 | #define log4(s,f,j,k,l) {} 35 | #define log5(s,f,j,k,l,m) {} 36 | #endif 37 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // main.cpp 3 | // 4 | // Copyright (C) 2013-2014 Kano Computing Ltd. 5 | // License: http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2 6 | // 7 | // An app to show and bring life to Kano-Make Desktop Icons. 8 | // 9 | 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | #include 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | #include "X11/extensions/scrnsaver.h" 25 | 26 | #include 27 | 28 | #include "version.h" 29 | #include "main.h" 30 | #include "icon.h" 31 | #include "background.h" 32 | #include "sound.h" 33 | #include "desktop.h" 34 | #include "logging.h" 35 | #include "ssaver.h" 36 | 37 | 38 | // A printf macro sensitive to the -v (verbose) flag 39 | // Use kprintf for regular stdout messages instead of printf or cout 40 | bool verbose=false; // Kdesk is mute by default, no stdout messages unless in Debug build 41 | #define kprintf(fmt, ...) ( (verbose==false ? : printf(fmt, ##__VA_ARGS__) )) 42 | 43 | // The desktop object will be dynamically accessed to refresh the configuration 44 | static Desktop dsk; 45 | 46 | // Flag to enable imlib2 to use the MIT-SHM X11 extension, see command line options. 47 | // Disabled by default to fix black areas on Debian Stretch 48 | bool enable_shm=false; 49 | 50 | // local function prototypes 51 | void signal_callback_handler(int signum); 52 | void reload_configuration (Display *display); 53 | void reload_icons (Display *display); 54 | void trigger_icon_hook (Display *display, char *message); 55 | void finish_kdesk (Display *display); 56 | 57 | Display *get_current_display(void); 58 | void close_display(Display *display); 59 | 60 | 61 | 62 | // Signal handler reached via a kill -USR 63 | void signal_callback_handler(int signum) 64 | { 65 | Display *display=get_current_display(); 66 | if (!display) { 67 | return; 68 | } 69 | 70 | log1 ("Received signal", signum); 71 | if (signum == SIGHUP) { 72 | log ("Received signal to reload configuration"); 73 | reload_configuration(display); 74 | } 75 | else if (signum == SIGUSR1) { 76 | log ("Received graceful kdesk quit signal"); 77 | finish_kdesk(display); 78 | } 79 | 80 | close_display(display); 81 | return; 82 | } 83 | 84 | void reload_configuration (Display *display) 85 | { 86 | log ("Reloading kdesk configuration"); 87 | dsk.send_signal (display, KDESK_SIGNAL_RELOAD, NULL); 88 | return; 89 | } 90 | 91 | void reload_icons (Display *display) 92 | { 93 | log ("Reloading kdesk configuration"); 94 | dsk.send_signal (display, KDESK_SIGNAL_RELOAD_ICONS, NULL); 95 | return; 96 | } 97 | 98 | // Prints a json string with icon coordinate details. returns false if not found 99 | bool print_json_icon_placement(Display *display, char *icon_name) 100 | { 101 | bool found=false; 102 | Window returnedroot=0L, returnedparent=0L, *children=0L, root=DefaultRootWindow(display); 103 | char *windowname=NULL; 104 | unsigned int numchildren=0; 105 | 106 | if (!icon_name) { 107 | return false; 108 | } 109 | 110 | XQueryTree (display, root, &returnedroot, &returnedparent, &children, &numchildren); 111 | for (int i=numchildren-1; i>=0; i--) 112 | { 113 | windowname = NULL; 114 | if (XFetchName (display, children[i], &windowname)) { 115 | 116 | // kdesk icon names have a prefix so they are unique 117 | string search_name=string("kdesk-"); 118 | search_name += icon_name; 119 | if (!strcmp (windowname, search_name.c_str())) { 120 | XWindowAttributes xwa; 121 | memset(&xwa, 0x00, sizeof(xwa)); 122 | if (XGetWindowAttributes(display, children[i], &xwa)) { 123 | printf ("{ \"icon_name\": \"%s\", \"x\": %d, \"y\": %d, " 124 | "\"width\": %d, \"height\": %d }\n", 125 | icon_name, xwa.x, xwa.y, xwa.width, xwa.height); 126 | XFree(windowname); 127 | found=true; 128 | break; // stop searching 129 | } 130 | } 131 | 132 | XFree(windowname); 133 | } 134 | } 135 | 136 | if(numchildren) { 137 | XFree(children); 138 | } 139 | 140 | return found; 141 | } 142 | 143 | void trigger_icon_hook (Display *display, char *message) 144 | { 145 | log ("Trigger an icon hook signal"); 146 | dsk.send_signal (display, KDESK_SIGNAL_ICON_ALERT, message); 147 | return; 148 | } 149 | 150 | void finish_kdesk (Display *display) 151 | { 152 | log ("Finishing Kdesk"); 153 | dsk.send_signal (display, KDESK_SIGNAL_FINISH, NULL); 154 | return; 155 | } 156 | 157 | Display *get_current_display(void) 158 | { 159 | // Get a handle to the display bound to the current user session 160 | return XOpenDisplay(NULL); 161 | } 162 | 163 | void close_display(Display *display) 164 | { 165 | XCloseDisplay(display); 166 | } 167 | 168 | 169 | /* 170 | * XShmQueryExtension 171 | * 172 | * This is an instrumented function that replaces the official API 173 | * from the libXext library - X11 extensions. It allows to disable this extension for kdesk. 174 | * 175 | * On Debian Stretch, using the MIT-SHM extension from imlib2 produces unfortunate 176 | * graphics artifacts that break the rendering with black areas. 177 | * 178 | */ 179 | int XShmQueryExtension(Display *d) 180 | { 181 | return (enable_shm); 182 | } 183 | 184 | 185 | int main(int argc, char *argv[]) 186 | { 187 | Status rc; 188 | Display *display=get_current_display(); 189 | Configuration conf; 190 | KSAVER_DATA ksaver_data; 191 | string strKdeskRC, strHomeKdeskRC, strKdeskDir, strKdeskUser; 192 | string configuration_file; 193 | bool test_mode = false, wallpaper_mode = false, screen_saver_mode = false; 194 | bool reload = false, running=true; 195 | int c; 196 | 197 | 198 | // Collect command-line parameters 199 | while ((c = getopt(argc, argv, "?htwsc:ria:vqmj:")) != EOF) 200 | { 201 | switch (c) 202 | { 203 | case '?': 204 | case 'h': 205 | cout << "kano-desktop [ -h | -t | -w | -r | -a | -q ]" << endl; 206 | cout << " -h help, or -? this screen" << endl; 207 | cout << " -v verbose mode with minimal progress messages" << endl; 208 | cout << " -t test mode, read configuration files and exit"<< endl; 209 | cout << " -w set desktop wallpaper and exit" << endl; 210 | cout << " -s screen saver mode - sets wallpaper, no icons, screen saver hooks" << endl; 211 | cout << " -c use a custom configuration file"<< endl; 212 | cout << " -r refresh configuration and exit" << endl; 213 | cout << " -i refresh desktop icons only and exit" << endl; 214 | cout << " -q query if kdesk is running on the current desktop (rc 0 running, nonzero otherwise)" << endl; 215 | cout << " -a send an icon hook alert" << endl; 216 | cout << " -m enable the use of MIT-SHM XServer extension (default=" << enable_shm << ")" << endl; 217 | cout << " -j get a json dump of the icon position on the desktop" << endl << endl; 218 | exit (1); 219 | 220 | case 't': 221 | kprintf ("testing configuration\n"); 222 | test_mode = true; 223 | break; 224 | 225 | case 'v': 226 | verbose = true; 227 | break; 228 | 229 | case 'w': 230 | wallpaper_mode = true; 231 | break; 232 | 233 | case 's': 234 | screen_saver_mode = true; 235 | break; 236 | 237 | case 'c': 238 | configuration_file = optarg; 239 | if (access(configuration_file.c_str(), R_OK)) { 240 | kprintf("Could not find configuration file: %s\n", configuration_file.c_str()); 241 | exit(1); 242 | } 243 | break; 244 | 245 | case 'r': 246 | if (!display) { 247 | kprintf ("Could not connect to the XServer\n"); 248 | kprintf ("Is the DISPLAY variable correctly set?\n\n"); 249 | exit (1); 250 | } 251 | 252 | kprintf ("Sending a refresh signal to Kdesk\n"); 253 | reload_configuration(display); 254 | close_display(display); 255 | exit (0); 256 | 257 | case 'i': 258 | if (!display) { 259 | kprintf ("Could not connect to the XServer\n"); 260 | kprintf ("Is the DISPLAY variable correctly set?\n\n"); 261 | exit (1); 262 | } 263 | 264 | kprintf ("Sending an icon refresh signal to Kdesk\n"); 265 | reload_icons(display); 266 | close_display(display); 267 | exit (0); 268 | 269 | case 'a': 270 | if (!display) { 271 | kprintf ("Could not connect to the XServer\n"); 272 | kprintf ("Is the DISPLAY variable correctly set?\n\n"); 273 | exit (1); 274 | } 275 | 276 | kprintf ("Triggering icon hook with message: %s\n\n", optarg); 277 | trigger_icon_hook(display, optarg); 278 | close_display(display); 279 | exit (0); 280 | 281 | case 'j': 282 | if (!display) { 283 | kprintf ("Could not connect to the XServer\n"); 284 | kprintf ("Is the DISPLAY variable correctly set?\n\n"); 285 | exit (1); 286 | } 287 | 288 | kprintf ("Querying information for icon name: %s\n\n", optarg); 289 | if(print_json_icon_placement(display, optarg) == true) { 290 | exit(0); 291 | } 292 | exit(1); 293 | 294 | case 'm': 295 | kprintf ("Enabling use of the MIT-SHM Xserver extension\n"); 296 | enable_shm=true; 297 | break; 298 | 299 | case 'q': 300 | if (!display) { 301 | kprintf ("Could not connect to the XServer\n"); 302 | kprintf ("Is the DISPLAY variable correctly set?\n\n"); 303 | exit (1); 304 | } 305 | else { 306 | //dsk.initialize (&bg); 307 | if (dsk.find_kdesk_control_window (display)) { 308 | kprintf ("Kdesk is running on this Display\n"); 309 | close_display(display); 310 | exit (0); 311 | } 312 | else { 313 | kprintf ("Kdesk is not running on this Display\n"); 314 | close_display(display); 315 | exit (-1); 316 | } 317 | } 318 | } 319 | } 320 | 321 | kprintf ("Kano-Desktop - A desktop Icon Manager\n"); 322 | kprintf ("Version v%s\n", VERSION); 323 | 324 | // We don't allow kdesk to run as the superuser 325 | uid_t userid = getuid(); 326 | if (userid == 0) { 327 | kprintf ("kdesk cannot run as root, please use sudo or login as a regular user\n"); 328 | exit(1); 329 | } 330 | 331 | // Load configuration settings from user's home directory 332 | kprintf ("initializing...\n"); 333 | struct passwd *pw = getpwuid(getuid()); 334 | const char *homedir = pw->pw_dir; 335 | bool bconf, buser = false; 336 | strKdeskRC = FILE_KDESKRC; 337 | strHomeKdeskRC = homedir + string("/") + string(FILE_HOME_KDESKRC); 338 | strKdeskDir = DIR_KDESKTOP; 339 | strKdeskUser = homedir + string(DIR_KDESKTOP_USER); 340 | 341 | // Load a custom configuration 342 | if (configuration_file.length()) { 343 | kprintf ("loading custom configuration file: %s\n", configuration_file.c_str()); 344 | bconf = conf.load_conf(configuration_file.c_str()); 345 | if (!bconf) { 346 | kprintf ("could not read custom configuration settings\n"); 347 | exit(1); 348 | } 349 | } 350 | else { 351 | // Load system wide configuration file from /usr/share 352 | // And override any settings provided by the user's home dir configuration 353 | kprintf ("loading generic configuration file: %s\n", strKdeskRC.c_str()); 354 | bconf = conf.load_conf(strKdeskRC.c_str()); 355 | } 356 | 357 | // combine loaded configuration with custom settings located in the users HOME dir 358 | kprintf ("overriding settings with home configuration file: %s\n", strHomeKdeskRC.c_str()); 359 | buser = conf.load_conf(strHomeKdeskRC.c_str()); 360 | if (bconf == false && buser == false) { 361 | kprintf ("could not read generic or user configuration settings\n"); 362 | exit(1); 363 | } 364 | 365 | log1 ("loading icons from directory", strKdeskDir.c_str()); 366 | conf.load_icons(strKdeskDir.c_str()); 367 | if (test_mode == true) { 368 | kprintf ("configuration test mode - exiting\n"); 369 | exit(0); 370 | } 371 | 372 | if (conf.get_numicons() == 0) { 373 | log ("Warning: no icons have been loaded"); 374 | } 375 | 376 | // Kdesk is a multithreaded X app 377 | rc = XInitThreads(); 378 | log1 ("XInitThreads rc", rc); 379 | 380 | // Connect to the X Server 381 | if (!display) { 382 | char *env_display = getenv ("DISPLAY"); 383 | kprintf ("could not connect to X display\n"); 384 | kprintf ("DISPLAY=%s\n", (env_display ? env_display : "null")); 385 | exit (1); 386 | } 387 | else { 388 | kprintf ("Connected to display %s\n", DisplayString(display)); 389 | } 390 | 391 | // Create and draw the desktop background 392 | Background bg(&conf); 393 | bg.setup(display); 394 | bg.load(display); 395 | 396 | // If wallpaper mode requested, exit now. 397 | if (wallpaper_mode == true) { 398 | kprintf ("refreshing background and exiting\n"); 399 | bg.refresh_background(display); 400 | exit (0); 401 | } 402 | 403 | // Play sound once the background is displayed 404 | // Only if we are running on the first available display, 405 | // Otherwise disable sound - VNC remote sessions 406 | Sound ksound(&conf); 407 | if (!(strncmp (DisplayString(display), DEFAULT_DISPLAY, strlen (DEFAULT_DISPLAY)))) { 408 | ksound.init(); 409 | ksound.play_sound("soundwelcome"); 410 | } 411 | else { 412 | kprintf ("This allocated display is not primary, disabling sound (%s)\n", DEFAULT_DISPLAY); 413 | } 414 | 415 | // Initialize the desktop management class, 416 | // stop here if there is already a Kdesk running on this display 417 | dsk.initialize (&bg); 418 | if (dsk.find_kdesk_control_window (display)) { 419 | kprintf ("Kdesk is already running on this Desktop - exiting\n"); 420 | exit (1); 421 | } 422 | else { 423 | dsk.initialize(display, &conf, &ksound); 424 | } 425 | 426 | // Register signal handlers to provide for external wake-ups 427 | signal (SIGUSR1, signal_callback_handler); 428 | signal (SIGUSR2, signal_callback_handler); 429 | 430 | // Delay desktop startup if requested 431 | unsigned long startup_delay=0L; 432 | startup_delay = conf.get_config_int ("background.delay"); 433 | if (startup_delay > 0) { 434 | log1 ("Delaying desktop startup for milliseconds", startup_delay); 435 | unsigned long ms=1000 * startup_delay; 436 | usleep(ms); // 1000 microseconds in a millisecond. 437 | } 438 | 439 | // Starting screen saver thread 440 | if (conf.get_config_int("screensavertimeout") > 0) { 441 | 442 | int rc=0, event_base=0, error_base=0; 443 | 444 | rc = XScreenSaverQueryExtension (display, &event_base, &error_base); 445 | if (rc == 0) { 446 | kprintf ("This XServer does not provide Screen Saver extensions - disabling\n"); 447 | } 448 | else { 449 | memset(&ksaver_data, 0, sizeof(KSAVER_DATA)); 450 | 451 | ksaver_data.display_name = NULL; // NULL means the currently attached display 452 | ksaver_data.idle_timeout = conf.get_config_int("screensavertimeout"); 453 | ksaver_data.saver_program = strdup(conf.get_config_string("screensaverprogram").c_str()); 454 | ksaver_data.saver_hooks = strdup(conf.get_config_string("iconhook").c_str()); 455 | setup_ssaver (&ksaver_data); 456 | } 457 | 458 | // in screen saver mode, we only process the thread events, no icons loaded. 459 | if (screen_saver_mode == true) { 460 | kprintf ("starting kdesk in screen saver mode\n"); 461 | do { 462 | reload = dsk.process_and_dispatch(display); 463 | // TODO: Reload configuration settings if ever needed 464 | 465 | } while (reload == true); 466 | 467 | // terminate gracefully via user signal SIGUSR1 468 | kprintf ("terminating screensaver mode gracefully\n"); 469 | free(ksaver_data.saver_program); 470 | free(ksaver_data.saver_hooks); 471 | exit(0); 472 | } 473 | } 474 | 475 | // Create and draw desktop icons, then attend user interaction 476 | bool bicons = dsk.create_icons(display); 477 | log1 ("desktop icons created", (bicons == true ? "successfully" : "errors found")); 478 | 479 | kprintf ("processing X11 events...\n"); 480 | do { 481 | reload = dsk.process_and_dispatch(display); 482 | if (reload == true) { 483 | // Discard configuration and reload everything again 484 | conf.reset(); 485 | 486 | kprintf ("loading generic configuration file: %s\n", strKdeskRC.c_str()); 487 | conf.load_conf(strKdeskRC.c_str()); 488 | kprintf ("overriding home configuration file: %s\n", strHomeKdeskRC.c_str()); 489 | conf.load_conf(strHomeKdeskRC.c_str()); 490 | 491 | log1 ("loading icons from directory", strKdeskDir.c_str()); 492 | conf.load_icons(strKdeskDir.c_str()); 493 | conf.dump(); 494 | 495 | // Destroy all icons and recreate them from the new reloaded configuration 496 | dsk.destroy_icons(display); 497 | 498 | // Reload the desktop wallpaper 499 | bg.setup(display); 500 | bg.load(display); 501 | 502 | // Regenerate new icons 503 | bool bicons = dsk.create_icons(display); 504 | } 505 | else { 506 | // This means we don't want to refresh settings 507 | // TODO: We need to change this semantics the day we want a graceful kdesk stop 508 | running = true; 509 | } 510 | 511 | } while (running == true); 512 | 513 | // Free strings used to start screen saver 514 | if (ksaver_data.saver_program) 515 | free(ksaver_data.saver_program); 516 | if (ksaver_data.saver_hooks) 517 | free(ksaver_data.saver_hooks); 518 | 519 | kprintf ("kdesk is finishing...\n"); 520 | exit (0); 521 | } 522 | -------------------------------------------------------------------------------- /src/main.h: -------------------------------------------------------------------------------- 1 | // 2 | // main.h 3 | // 4 | 5 | #define FILE_HOME_KDESKRC ".kdeskrc" 6 | #define FILE_KDESKRC "/usr/share/kano-desktop/kdesk/.kdeskrc" 7 | #define DIR_KDESKTOP "/usr/share/kano-desktop/kdesk/kdesktop" 8 | #define DIR_KDESKTOP_USER ".kdesktop" 9 | 10 | // Primary physical display, used to discern when to play sounds 11 | // :0 alone means the first display on the local system. 12 | #define DEFAULT_DISPLAY ":0" 13 | -------------------------------------------------------------------------------- /src/sound.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // sound.cpp 3 | // 4 | // Copyright (C) 2013-2014 Kano Computing Ltd. 5 | // License: http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2 6 | // 7 | // An app to show and bring life to Kano-Make Desktop Icons. 8 | // 9 | 10 | #include 11 | #include 12 | 13 | #include "configuration.h" 14 | #include "logging.h" 15 | #include "sound.h" 16 | 17 | Sound::Sound (Configuration *loaded_conf) 18 | { 19 | configuration = loaded_conf; 20 | playing = false; 21 | } 22 | 23 | Sound::~Sound (void) 24 | { 25 | if (playing) { 26 | pthread_join(t, NULL); 27 | } 28 | } 29 | 30 | bool Sound::init(void) 31 | { 32 | return true; 33 | } 34 | 35 | bool Sound::terminate(void) 36 | { 37 | return true; 38 | } 39 | 40 | bool Sound::play(void) 41 | { 42 | bool success=false; 43 | int rc; 44 | 45 | if (!tune || ! (tune->size() > 0)) { 46 | log2 ("no tune file set, or a play is in progress (tune, playing)", tune, playing); 47 | } 48 | else { 49 | // Call external tool with the sound file 50 | playing = true; 51 | 52 | string sound_cmdline; 53 | sound_cmdline = "/usr/bin/aplay "; 54 | sound_cmdline += tune->c_str(); 55 | sound_cmdline += " &"; 56 | log1 ("Playing sound cmdline:", sound_cmdline); 57 | 58 | // protect against eventual race condition 59 | tune_tmp=tune; 60 | tune = NULL; 61 | delete (tune_tmp); 62 | 63 | rc = system (sound_cmdline.c_str()); 64 | log1 ("Sound played (return code)", rc); 65 | success = (rc == 0 ? true : false); 66 | playing = false; 67 | } 68 | 69 | return success; 70 | } 71 | 72 | void Sound::play_sound(string sound_name) 73 | { 74 | int rc; 75 | string sound_cmdline; 76 | 77 | // Do not play anything if sound is disabled in kdeskrc 78 | if (!(configuration->get_config_string ("enablesound") == "true")) { 79 | return; 80 | } 81 | 82 | if (playing == true) { 83 | log ("A sound is currently being played"); 84 | } 85 | else { 86 | // sound name is the key name specified in the configuration file 87 | tune = new std::string (configuration->get_config_string (sound_name)); 88 | pthread_create (&t, NULL, InternalThreadEntryFunc, this); 89 | } 90 | 91 | return; 92 | } 93 | -------------------------------------------------------------------------------- /src/sound.h: -------------------------------------------------------------------------------- 1 | // 2 | // sound.h 3 | // 4 | // Copyright (C) 2013-2014 Kano Computing Ltd. 5 | // License: http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2 6 | // 7 | // An app to show and bring life to Kano-Make Desktop Icons. 8 | // 9 | 10 | #include 11 | 12 | class Sound 13 | { 14 | private: 15 | Configuration *configuration; 16 | std::string * volatile tune; 17 | std::string *tune_tmp; 18 | pthread_t t; 19 | bool playing; 20 | 21 | public: 22 | Sound (Configuration *loaded_conf); 23 | virtual ~Sound (void); 24 | 25 | bool init(void); 26 | 27 | static void * InternalThreadEntryFunc(void * This) 28 | { 29 | ((Sound *)This)->play(); return NULL; 30 | } 31 | 32 | bool play(void); 33 | void play_sound(std::string sound_name); 34 | bool terminate(void); 35 | }; 36 | -------------------------------------------------------------------------------- /src/ssaver.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // ssaver.cpp - Class to detect user idle time and fire a screen saver. 3 | // 4 | // Copyright (C) 2013-2014 Kano Computing Ltd. 5 | // License: http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2 6 | // 7 | // An app to show and bring life to Kano-Make Desktop Icons. 8 | // 9 | 10 | #include "X11/extensions/scrnsaver.h" 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | #include "logging.h" 17 | #include "ssaver.h" 18 | 19 | #include 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | // Returns the currently active tty on the local system 27 | // If 0 is returned this info could not be obtained 28 | int get_current_console (void) 29 | { 30 | int current_tty = 0; 31 | struct vt_stat vtstat = {0,0,0}; 32 | 33 | int fd = open (TTY_QUERY, O_RDONLY); 34 | int rc = ioctl (fd, VT_GETSTATE, &vtstat); 35 | close(fd); 36 | if (rc >= 0) { 37 | current_tty = vtstat.v_active; 38 | } 39 | return current_tty; 40 | } 41 | 42 | int execute_hook(const char *hook_script, const char *params) 43 | { 44 | int rc=-1; 45 | size_t cmdline_bytes=1024 * sizeof(char); 46 | char *chcmdline = (char *) calloc (1, cmdline_bytes); 47 | 48 | // Set environment variable so other programs called from script 49 | // cannot accidentally create an infinite loop. 50 | const char *norec = "export KDESK_NO_RECURSE=1"; 51 | 52 | if (chcmdline != NULL && hook_script != NULL) { 53 | snprintf (chcmdline, cmdline_bytes, "/bin/bash -c \"%s; %s %s\"", 54 | norec, 55 | hook_script, 56 | (params ? params : "")); 57 | log1 ("Executing screen saver hook:", chcmdline); 58 | 59 | signal(SIGCHLD, SIG_DFL); 60 | rc = system (chcmdline); 61 | signal(SIGCHLD, SIG_IGN); 62 | 63 | log2 ("Screen saver hook returns with RC, WEXITSTATUS", rc, WEXITSTATUS(rc)); 64 | if (rc != -1) { 65 | rc = WEXITSTATUS(rc); 66 | } 67 | } 68 | 69 | if (chcmdline) { 70 | free (chcmdline); 71 | } 72 | 73 | return rc; 74 | } 75 | 76 | int hook_ssaver_start(const char *hook_script) 77 | { 78 | int rc = execute_hook (hook_script, SSAVER_HOOK_START); 79 | if (rc == -1) { 80 | // If the hook script cannot be executed, assume success, go forward 81 | log ("Warning: Screen saver start hook returns -1, assuming 0=success"); 82 | rc = 0; 83 | } 84 | 85 | return rc; 86 | } 87 | 88 | int hook_ssaver_finish(const char *hook_script, time_t time_ssaver_run) 89 | { 90 | char hook_params[256]; 91 | 92 | sprintf (hook_params, "%s %ld", SSAVER_HOOK_FINISH, time_ssaver_run); 93 | int rc = execute_hook (hook_script, hook_params); 94 | return 0; 95 | } 96 | 97 | bool setup_ssaver (KSAVER_DATA *kdata) 98 | { 99 | pthread_t *tssaver = new pthread_t(); 100 | pthread_create (tssaver, NULL, &idle_time, (void *) kdata); 101 | log1 ("SSaver thread started tid", tssaver); 102 | return true; 103 | } 104 | 105 | void fake_user_input (Display *display) 106 | { 107 | // Send a fake mouse movement so that the XServer restarts the inactivity timer 108 | Window root = DefaultRootWindow(display); 109 | 110 | // From the docs: If dest_w is None, XWarpPointer() moves the pointer by the offsets 111 | // (dest_x, dest_y) relative to the current position of the pointer 112 | int rc=XWarpPointer(display, root, 0, // Display, source window, destination window 113 | 0, 0, 0, 0, // source x,y and width, height 114 | 1, 1); // destination x,y 115 | 116 | log1("xwarppointer fake user input returns with rc", rc) 117 | XFlush(display); 118 | } 119 | 120 | void *idle_time (void *p) 121 | { 122 | bool running=true; 123 | Status rc=0, rchook=0; 124 | unsigned long ms=1000 * POLL_INTERVAL; 125 | unsigned long ultimeout=0L; 126 | PKSAVER_DATA pdata=(PKSAVER_DATA) p; 127 | 128 | // Initial X11 connection delay 129 | usleep(ms); 130 | 131 | Display *display = XOpenDisplay(pdata->display_name); 132 | if (!display) { 133 | log ("Ssaver cannot connect to X Display! No screen saver available"); 134 | return NULL; 135 | } 136 | else { 137 | log2 ("Setting screen saver - T/O (secs) and program", pdata->idle_timeout, pdata->saver_program); 138 | } 139 | 140 | XScreenSaverInfo *info = XScreenSaverAllocInfo(); 141 | if (!info) { 142 | log ("Error! Could not allocate screen saver information structure, screen saver disabled"); 143 | return NULL; 144 | } 145 | 146 | while (running) 147 | { 148 | rc = XScreenSaverQueryInfo(display, DefaultRootWindow(display), info); 149 | log3 ("asking for system idle time - rcsuccess, T/O, and idle time in secs", rc, info->idle / 1000, pdata->idle_timeout); 150 | log2 ("current tty, default X tty", get_current_console(), GUI_TTY_DEVICE); 151 | if (rc) 152 | { 153 | // If idle timeout expires, then launch the screen saver 154 | // Note that this would trigger the screen saver whilst on a tty console as well 155 | if (info->idle > (pdata->idle_timeout * 1000)) { 156 | rchook = hook_ssaver_start(pdata->saver_hooks); 157 | if (rchook == 0) { 158 | log2 ("Starting the Screen Saver (idle, timeout in secs)", info->idle / 1000, pdata->idle_timeout); 159 | 160 | time_t ssaver_time_start = time (NULL); 161 | signal(SIGCHLD, SIG_DFL); 162 | rc = system (pdata->saver_program); 163 | signal(SIGCHLD, SIG_IGN); 164 | time_t ssaver_time_end = time (NULL); 165 | 166 | log1 ("Screen saver finished with rc", rc); 167 | if (rc == 0) { 168 | log1 ("Calling xrefresh: ", XREFRESH); 169 | rc = system (XREFRESH); 170 | 171 | // Tell kdesk hooks that the screen saver has finished 172 | rchook = hook_ssaver_finish(pdata->saver_hooks, ssaver_time_end - ssaver_time_start); 173 | 174 | // some bluetooth keyboard devices need an explicit activity event, 175 | // otherwise the inactivity timer stops working (returning 0 which means activity is being received). 176 | fake_user_input(display); 177 | } 178 | } 179 | else { 180 | log1 ("Screen saver start hook not returning 0, cancelling the screen saver, rc=", rchook); 181 | fake_user_input(display); 182 | } 183 | } 184 | } 185 | else { 186 | log1 ("XScreenSaverQueryInfo failed with rc", rc); 187 | } 188 | 189 | usleep(ms); 190 | } 191 | 192 | XFree(info); 193 | return NULL; 194 | } 195 | -------------------------------------------------------------------------------- /src/ssaver.h: -------------------------------------------------------------------------------- 1 | // 2 | // ssaver.h - Class to detect user idle time and fire a screen saver. 3 | // 4 | // Copyright (C) 2013-2014 Kano Computing Ltd. 5 | // License: http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2 6 | // 7 | // An app to show and bring life to Kano-Make Desktop Icons. 8 | // 9 | 10 | #define POLL_INTERVAL 15000 // milliseconds between each system idle query 11 | #define XREFRESH "xrefresh" // called after the screen saver to redraw the desktop 12 | #define TTY_QUERY "/dev/tty1" // name of the tty device to use as a trampoline to know who has the focus 13 | #define SSAVER_HOOK_START "ScreenSaverStart" // First parameter name sent to hooks when the screen saver is about to start 14 | #define SSAVER_HOOK_FINISH "ScreenSaverFinish" // First parameter name sent to hooks when screen saver terminates 15 | 16 | #define GUI_TTY_DEVICE 7 // tty device number where the GUI XServer is running, normally tty7 17 | 18 | typedef struct _ksaver_data { 19 | 20 | char *display_name; // Usually this can be set to NULL to attach to first available display 21 | unsigned long idle_timeout; // seconds to idle before starting the screen saver 22 | char *saver_program; // path to binary program that paints the screen saver 23 | char *saver_hooks; // path to a hook script that will be executed to alert on screen saver transitions (start, finish) 24 | 25 | } KSAVER_DATA; 26 | 27 | typedef KSAVER_DATA* PKSAVER_DATA; 28 | 29 | bool setup_ssaver (KSAVER_DATA *kdata); 30 | void *idle_time (void *p); 31 | int hook_ssaver_start(const char *hook_script); 32 | int hook_ssaver_finish(const char *hook_script); 33 | void fake_user_input (void); 34 | -------------------------------------------------------------------------------- /src/version.h: -------------------------------------------------------------------------------- 1 | // 2 | // This is a quick and simple source controlled version file 3 | // 4 | // Copyright (C) 2013-2014 Kano Computing Ltd. 5 | // License: http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2 6 | // 7 | // An app to show and bring life to Kano-Make Desktop Icons. 8 | // 9 | 10 | #define VERSION "1.04" 11 | -------------------------------------------------------------------------------- /tests/configurations/kdeskrc_test: -------------------------------------------------------------------------------- 1 | # .kdeskrc 2 | # 3 | # Copyright (C) 2014,2015 Kano Computing Ltd. 4 | # License: http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2 5 | # 6 | # 7 | 8 | 9 | table Config 10 | FontName: Bariol 11 | FontSize: 14 12 | SubtitleFontSize: 11 13 | FontColor: #323232 14 | 15 | ToolTip.FontSize: 10 16 | ToolTip.FontName: Bariol 17 | ToolTip.ForeColor: #0000FF 18 | ToolTip.BackColor: #FFFFFF 19 | ToolTip.CaptionOnHover: false 20 | ToolTip.CaptionPlacement: Right 21 | Locked: true 22 | 23 | EnableSound: true 24 | SoundWelcome: /usr/share/kano-media/sounds/kano_boot_up.wav 25 | SoundLaunchApp: /usr/share/kano-media/sounds/kano_open_app.wav 26 | SoundDisabledIcon: /usr/share/kano-media/sounds/kano_error.wav 27 | 28 | ScreenSaverTimeout: 30 29 | ScreenSaverProgram: /usr/bin/qmlmatrix 30 | 31 | OneClick: true 32 | MaximizeSingleton: true 33 | 34 | IconHook: /usr/share/kano-desktop/kdesk/icon-hooks.sh 35 | 36 | DefaultDesktopIcon: /usr/share/kano-desktop/icons/default-icon.png 37 | 38 | LastGridIcon: PlusIcon.lnk 39 | 40 | IconGapHorz: 54 41 | IconGapVert: 10 42 | 43 | GridWidth: 90 44 | GridHeight: 125 45 | 46 | GridIconWidth: 88 47 | GridIconHeight: 88 48 | 49 | Transparency: 0 50 | Shadow: true 51 | ShadowColor: #6e6e6e 52 | ShadowX: 1 53 | ShadowY: 1 54 | Bold: true 55 | ClickDelay: 300 56 | IconStartDelay: 5000 57 | IconSnap: true 58 | IconTitleGap: 0 59 | SnapWidth: 10 60 | SnapHeight: 10 61 | SnapOrigin: BottomRight 62 | SnapShadow: false 63 | SnapShadowTrans: 200 64 | CaptionOnHover: false 65 | CaptionPlacement: bottom 66 | FillStyle: None 67 | Background.Delay: 0 68 | Background.Source: / 69 | 70 | ScreenMedResWidth: 1025 71 | Background.File-medium: /usr/share/kano-desktop/wallpapers/kanux-default-1024.png 72 | Background.File-4-3: /usr/share/kano-desktop/wallpapers/kanux-default-4-3.png 73 | Background.File-16-9: /usr/share/kano-desktop/wallpapers/kanux-default-16-9.png 74 | Background.Mode: scale 75 | Background.Color: #C2CCFF 76 | end 77 | 78 | table Actions 79 | # Lock: control right doubleClk 80 | # Reload: middle doubleClk 81 | # Drag: left hold 82 | # EndDrag: left singleClk 83 | 84 | Execute[0]: left doubleClk 85 | 86 | # Execute[1]: right doubleClk 87 | end 88 | -------------------------------------------------------------------------------- /tests/test_configuration.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # 3 | # Tests that kdesk uses the correct configuration settings 4 | # by merging HOME settings into the main configuration file. 5 | # 6 | 7 | import os 8 | 9 | def run_kdesk_configuration(configuration): 10 | 11 | # allow the tests to run either isolated on this repo, or on a kano os image / real RPI 12 | os.environ["PATH"] = '../src:' + os.environ['PATH'] 13 | 14 | # we run kdesk in test mode, possibly with custom configuration file 15 | command='kdesk-dbg -v -t' 16 | if configuration: 17 | command += ' -c {}'.format(configuration) 18 | return os.popen(command).read() 19 | 20 | def test_config_custom(): 21 | config_file='configurations/kdeskrc_test' 22 | out=run_kdesk_configuration(config_file) 23 | assert(out.find('loading custom configuration file: {}'.format(config_file)) != -1) 24 | 25 | def test_config_custom_ssaver(): 26 | config_file='configurations/kdeskrc_test' 27 | out=run_kdesk_configuration(config_file) 28 | assert(out.find('found ScreenSaverTimeout: 30') != -1) 29 | assert(out.find('found ScreenSaverProgram: /usr/bin/qmlmatrix') != -1) 30 | 31 | def test_config_combined_with_default(): 32 | with open('{}/.kdeskrc'.format(os.getenv("HOME")), 'w') as f: 33 | f.write('ScreenSaverTimeout: 10') 34 | 35 | out=run_kdesk_configuration(None) 36 | assert(out.find('found ScreenSaverTimeout: 10') != -1) 37 | 38 | def test_config_combined_with_custom(): 39 | config_file='configurations/kdeskrc_test' 40 | with open('{}/.kdeskrc'.format(os.getenv("HOME")), 'w') as f: 41 | f.write('ScreenSaverTimeout: 10') 42 | 43 | out=run_kdesk_configuration(config_file) 44 | assert(out.find('found ScreenSaverTimeout: 10') != -1) 45 | 46 | def test_config_combined_with_absent_home(): 47 | config_file='configurations/kdeskrc_test' 48 | homeconf='{}/.kdeskrc'.format(os.getenv("HOME")) 49 | if os.path.isfile(homeconf): 50 | os.remove(homeconf) 51 | 52 | out=run_kdesk_configuration(config_file) 53 | assert(out.find('found ScreenSaverTimeout: 30') != -1) 54 | assert(out.find('could not read generic or user configuration settings') == -1) 55 | 56 | def test_config_custom_missing(): 57 | config_file='configurations/nonexistent' 58 | homeconf='{}/.kdeskrc'.format(os.getenv("HOME")) 59 | if os.path.isfile(homeconf): 60 | os.remove(homeconf) 61 | 62 | out=run_kdesk_configuration(config_file) 63 | assert(out.find('Could not find configuration file: {}'.format(config_file)) != -1) 64 | --------------------------------------------------------------------------------