├── .github └── FUNDING.yml ├── .gitignore ├── DEBIAN ├── gcde-common.control ├── gcde-common.install ├── gcde-desktop.control └── gcde-desktp.install ├── LICENSE ├── README.md ├── build.sh ├── etc └── gcde │ ├── controller-mapping.json │ ├── default-tiles.json │ └── defaults-global.json ├── screenshots ├── screenshot_10-26-2020_22-36-49.png ├── screenshot_10-26-2020_22-37-33.png ├── screenshot_10-26-2020_22-37-48.png ├── screenshot_10-26-2020_22-38-04.png └── screenshot_10-26-2020_22-39-06.png └── usr ├── bin └── gcde ├── lib └── python3 │ └── dist-packages │ └── gcde │ ├── __init__.py │ ├── common.py │ └── tile.py └── share ├── gcde ├── controller_support │ ├── __init__.py │ ├── __mapping_applicator__.py │ ├── buttons.py │ ├── conversion.cxx │ ├── conversion.py │ └── gcde-analog-mapper.cxx ├── engine.py ├── handler.py ├── plugins │ ├── README.md │ ├── __init__.py │ ├── _plugin_example.py │ └── sys_info.py └── restart.py └── xsessions └── gcde.desktop /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | liberapay: Batcastle 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | wheels/ 22 | pip-wheel-metadata/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | 53 | # Translations 54 | *.mo 55 | *.pot 56 | 57 | # Django stuff: 58 | *.log 59 | local_settings.py 60 | db.sqlite3 61 | db.sqlite3-journal 62 | 63 | # Flask stuff: 64 | instance/ 65 | .webassets-cache 66 | 67 | # Scrapy stuff: 68 | .scrapy 69 | 70 | # Sphinx documentation 71 | docs/_build/ 72 | 73 | # PyBuilder 74 | target/ 75 | 76 | # Jupyter Notebook 77 | .ipynb_checkpoints 78 | 79 | # IPython 80 | profile_default/ 81 | ipython_config.py 82 | 83 | # pyenv 84 | .python-version 85 | 86 | # pipenv 87 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 88 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 89 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 90 | # install all needed dependencies. 91 | #Pipfile.lock 92 | 93 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 94 | __pypackages__/ 95 | 96 | # Celery stuff 97 | celerybeat-schedule 98 | celerybeat.pid 99 | 100 | # SageMath parsed files 101 | *.sage.py 102 | 103 | # Environments 104 | .env 105 | .venv 106 | env/ 107 | venv/ 108 | ENV/ 109 | env.bak/ 110 | venv.bak/ 111 | 112 | # Spyder project settings 113 | .spyderproject 114 | .spyproject 115 | 116 | # Rope project settings 117 | .ropeproject 118 | 119 | # mkdocs documentation 120 | /site 121 | 122 | # mypy 123 | .mypy_cache/ 124 | .dmypy.json 125 | dmypy.json 126 | 127 | # Pyre type checker 128 | .pyre/ 129 | -------------------------------------------------------------------------------- /DEBIAN/gcde-common.control: -------------------------------------------------------------------------------- 1 | Package: gcde-common 2 | Version: 0.0.4-alpha0 3 | Maintainer: Thomas Castleman 4 | Section: admin 5 | Homepage: https://github.com/drauger-os-development/gcde 6 | Architecture: all 7 | Priority: optional 8 | License: GPL-2+ 9 | Depends: gir1.2-gtk-3.0 (>=3.24.5), python3 10 | Description: GCDE Desktop - Common Files 11 | GCDE is a desktop that gives Linux a game console look and feel. It's easy to use, 12 | lightweight, and hackable. 13 | . 14 | This package contains common files, such as important libaries and assets. 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /DEBIAN/gcde-common.install: -------------------------------------------------------------------------------- 1 | usr/lib 2 | -------------------------------------------------------------------------------- /DEBIAN/gcde-desktop.control: -------------------------------------------------------------------------------- 1 | Package: gcde-desktop 2 | Version: 0.0.9-alpha0 3 | Maintainer: Thomas Castleman 4 | Section: admin 5 | Homepage: https://github.com/drauger-os-development/gcde 6 | Architecture: amd64 7 | Priority: optional 8 | License: GPL-2+ 9 | Depends: gir1.2-gtk-3.0 (>=3.24.5), python3, python3-psutil (>=5.5.1), gcde-common (>=0.0.3-alpha0), xfwm4 | openbox, xdg-utils, compton, wmctrl, xfdesktop4 (>=4.14) 10 | Description: GCDE Desktop 11 | GCDE is a desktop that gives Linux a game console look and feel. It's easy to use, 12 | lightweight, and hackable. 13 | -------------------------------------------------------------------------------- /DEBIAN/gcde-desktp.install: -------------------------------------------------------------------------------- 1 | etc 2 | usr/bin 3 | usr/share 4 | -------------------------------------------------------------------------------- /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 | 294 | Copyright (C) 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 | , 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. 340 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gcde 2 | GTK+ Console Desktop Environment (or, alternatively, Game Console Desktop Environment), is a desktop environment to give Linux a game-console look and feel. 3 | 4 | 5 | ## Goals 6 | 7 | GCDE has a few different goals: 8 | 9 | - Make an easy-to-use, lightweight desktop environment 10 | - This DE must not only be lightweight and performant, but look aestheticly pleasing as well 11 | - Furthermore, this DE must _NATIVELY_ support game console controllers 12 | - Finally, this DE must be usable on as many Linux, and ideally BSD, distros as possible 13 | 14 | ## Features 15 | 16 | Currently working features: 17 | 18 | - Lightweight 19 | - Controller-optimized 20 | - Auto-scaling to the user's display resolution 21 | - this has been tested and is known working from 400x300, all the way up to 3840x1080 22 | - icons are the _ONLY_ thing not scaling right now, due to issues with GTK handling them 23 | - Testing this functionality in various resolutions above 1080p is needed. 24 | - Only _rectangular_ monitor geometries are supported due to limitations of the way GCDE is written 25 | 26 | Still under development: 27 | 28 | - Built-in Controller support (this will be implemented as a Background Plugin, so it can be disabled if desired. (see Plugin Support below)) 29 | - Aesthetically Pleasing (this won't get to a state that we like it in until GTK 4 comes out) 30 | - Plugin support (for info on how this is supposed to work, check out [this file](https://github.com/drauger-os-development/gcde/blob/master/usr/share/gcde/plugins/README.md)) 31 | 32 | ## Building and Installing 33 | 34 | Currently, GCDE is written entirely in Python. So, the only building necessary is to make the package for your distro! This is what the `BASH` scripting you see is used for. 35 | 36 | ### Debian, Ubuntu, Drauger OS, other Debian-based distros 37 | 38 | 1. Clone this repository:

`git clone https://github.com/drauger-os-development/gcde`

39 | 40 | 2. `cd` into the GCDE directory:

`cd gcde`

41 | 42 | 3. Build it!:

`./build.sh`

This should take less than a second, and generate two (2) *.deb files in the parent directory.

43 | 44 | 4. From here, you can install the two *.deb files with your favorite package installer, or do it from the command line:

`cd ..; sudo apt install ./gcde-*.deb` 45 | 46 | ### Arch Linux, openSUSE, Fedora 47 | 48 | The necessary files to generate a *.rpm file for Arch Linux have not been made yet. Feel free to contribute to get this working! 49 | 50 | Ideally, the build for these distros would be equally as easy as it is for Debian-based distros 51 | 52 | ## Uninstalling 53 | 54 | If you followed the instructions above for building and installing, then all you have to do is uninstall GCDE like you would any other application: 55 | 56 | ### Debian-based 57 | `sudo apt uninstall gcde-desktop gcde-common` 58 | 59 | ### openSUSE 60 | `sudo zypper remove gcde-desktop gcde-common` 61 | 62 | ### Fedora 63 | `sudo dnf remove gcde-desktop gcde-common` 64 | 65 | ### Arch Linux 66 | `sudo pacman -Rsc gcde-desktop gcde-common` 67 | 68 | ## Screenshots!!!! 69 | 70 | 71 | Of course I gave y'all screenshots. I'm not gonna leave ya hanging like that. 72 | 73 | GCDE at 1080p 74 |
75 |
76 | GCDE Application Menu 77 |
78 |
79 | GCDE Settings Menu 80 |
81 |
82 | GCDE Idle Memory and CPU usage -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eE 3 | cp DEBIAN/gcde-desktop.control DEBIAN/control 4 | VERSION=$(cat DEBIAN/control | grep 'Version: ' | sed 's/Version: //g') 5 | PAK=$(cat DEBIAN/control | grep 'Package: ' | sed 's/Package: //g') 6 | ARCH=$(cat DEBIAN/control | grep 'Architecture: '| sed 's/Architecture: //g') 7 | FOLDER="$PAK\_$VERSION\_$ARCH" 8 | FOLDER=$(echo "$FOLDER" | sed 's/\\//g') 9 | if [ "$ARCH" == "amd64" ]; then 10 | COMPILER="g++ -m64" 11 | ARGS="-Wall" 12 | elif [ "$ARCH" == "arm64" ]; then 13 | COMPILER="aarch64-linux-gnu-g++" 14 | ARGS="" 15 | fi 16 | mkdir ../"$FOLDER" 17 | ############################################################## 18 | # # 19 | # # 20 | # COMPILE ANYTHING NECSSARY HERE # 21 | # # 22 | # # 23 | ############################################################## 24 | cd usr/share/gcde/controller_support 25 | $COMPILER $ARGS gcde-analog-mapper.cxx -o gcde-analog-mapper 26 | cd ../../../.. 27 | ############################################################## 28 | # # 29 | # # 30 | # REMEMBER TO DELETE SOURCE FILES FROM TMP # 31 | # FOLDER BEFORE BUILD # 32 | # # 33 | # # 34 | ############################################################## 35 | if [ -d bin ]; then 36 | cp -R bin ../"$FOLDER"/bin 37 | fi 38 | if [ -d etc ]; then 39 | cp -R etc ../"$FOLDER"/etc 40 | fi 41 | if [ -d usr ]; then 42 | cp -R usr ../"$FOLDER"/usr 43 | fi 44 | if [ -d lib ]; then 45 | cp -R lib ../"$FOLDER"/lib 46 | fi 47 | if [ -d lib32 ]; then 48 | cp -R lib32 ../"$FOLDER"/lib32 49 | fi 50 | if [ -d lib64 ]; then 51 | cp -R lib64 ../"$FOLDER"/lib64 52 | fi 53 | if [ -d libx32 ]; then 54 | cp -R libx32 ../"$FOLDER"/libx32 55 | fi 56 | if [ -d sbin ]; then 57 | cp -R sbin ../"$FOLDER"/sbin 58 | fi 59 | if [ -d var ]; then 60 | cp -R var ../"$FOLDER"/var 61 | fi 62 | if [ -d opt ]; then 63 | cp -R opt ../"$FOLDER"/opt 64 | fi 65 | if [ -d srv ]; then 66 | cp -R srv ../"$FOLDER"/srv 67 | fi 68 | cp -R DEBIAN ../"$FOLDER"/DEBIAN 69 | cd .. 70 | #DELETE STUFF HERE 71 | rm "$FOLDER"/usr/share/gcde/controller_support/gcde-analog-mapper.cxx 72 | rm gcde/usr/share/gcde/controller_support/gcde-analog-mapper 73 | rm -rf "$FOLDER"/usr/lib 74 | #build the shit 75 | rm "$FOLDER"/DEBIAN/gcde-common.control "$FOLDER"/DEBIAN/gcde-common.install "$FOLDER"/DEBIAN/gcde-desktop.control 76 | dpkg-deb --build "$FOLDER" 77 | cd gcde 78 | cp DEBIAN/gcde-common.control DEBIAN/control 79 | VERSION=$(cat DEBIAN/control | grep 'Version: ' | sed 's/Version: //g') 80 | PAK=$(cat DEBIAN/control | grep 'Package: ' | sed 's/Package: //g') 81 | ARCH=$(cat DEBIAN/control | grep 'Architecture: '| sed 's/Architecture: //g') 82 | NEW_FOLDER="$PAK\_$VERSION\_$ARCH" 83 | NEW_FOLDER=$(echo "$NEW_FOLDER" | sed 's/\\//g') 84 | mv ../"$FOLDER" ../"$NEW_FOLDER" 85 | mv DEBIAN/control ../"$NEW_FOLDER"/DEBIAN/control 86 | cp DEBIAN/gcde-common.install ../"$NEW_FOLDER"/DEBIAN/gcde-common.install 87 | cd .. 88 | rm -rf "$NEW_FOLDER"/usr/share 89 | rm -rf "$NEW_FOLDER"/usr/bin 90 | rm -rf "$NEW_FOLDER"/etc 91 | mkdir "$NEW_FOLDER"/usr/lib 92 | cp -R gcde/usr/lib "$NEW_FOLDER"/usr 93 | dpkg-deb --build "$NEW_FOLDER" 94 | rm -rf "$NEW_FOLDER" 95 | -------------------------------------------------------------------------------- /etc/gcde/controller-mapping.json: -------------------------------------------------------------------------------- 1 | { 2 | "xbox":{ 3 | "left_dpad":"left", 4 | "right_dpad":"right", 5 | "up_dpad":"up", 6 | "down_dpad":"down", 7 | "A":"enter", 8 | "Y":"tab", 9 | "B":"" 10 | }, 11 | "ps4":{ 12 | "left_dpad":"left", 13 | "right_dpad":"right", 14 | "up_dpad":"up", 15 | "down_dpad":"down", 16 | "X":"enter", 17 | "tri":"tab", 18 | "circ":"" 19 | }, 20 | "ps3":{ 21 | "left_dpad":"left", 22 | "right_dpad":"right", 23 | "up_dpad":"up", 24 | "down_dpad":"down", 25 | "X":"enter", 26 | "tri":"tab", 27 | "circ":"" 28 | 29 | }, 30 | "default":"ps3" 31 | } 32 | -------------------------------------------------------------------------------- /etc/gcde/default-tiles.json: -------------------------------------------------------------------------------- 1 | { 2 | "menu":{"exec":["menu"], 3 | "icon":"view-grid", 4 | "name":"Menu", 5 | "X":2, 6 | "Y":2, 7 | "width":1, 8 | "height":1}, 9 | "Tile1":{"exec":["xdg-open", "https://draugeros.org/go"], 10 | "icon":"internet-web-browser", 11 | "name":"Browser", 12 | "X":0, 13 | "Y":0, 14 | "width":1, 15 | "height":1}, 16 | "Tile2":{"exec":["steam"], 17 | "icon":"steam", 18 | "name":"Steam", 19 | "X":3, 20 | "Y":3, 21 | "width":1, 22 | "height":1}, 23 | "settings":{"exec":["settings"], 24 | "icon":"settings", 25 | "name":"Settings", 26 | "X":4, 27 | "Y":4, 28 | "width":1, 29 | "height":1}, 30 | "Tile3":{"exec":["exo-open", "--launch", "FileManager"], 31 | "icon":"folder", 32 | "name":"File Manager", 33 | "X":3, 34 | "Y":0, 35 | "width":1, 36 | "height":1}, 37 | "Tile4":{"exec":["lutris"], 38 | "icon":"lutris", 39 | "name":"Lutris", 40 | "X":2, 41 | "Y":0, 42 | "width":1, 43 | "height":1}, 44 | "Tile5":{"exec":["gamehub"], 45 | "icon":"com.github.tkashkin.gamehub", 46 | "name":"GameHub", 47 | "X":0, 48 | "Y":4, 49 | "width":1, 50 | "height":1}, 51 | "Tile6":{"exec":["exo-open", "--launch", "TerminalEmulator"], 52 | "icon":"utilities-x-terminal", 53 | "name":"Terminal Emulator", 54 | "X":4, 55 | "Y":0, 56 | "width":1, 57 | "height":1}, 58 | "session_manager":{"exec":["session_manager"], 59 | "icon":"system-reboot", 60 | "name":"Log Out / Shutdown", 61 | "X":3, 62 | "Y":4, 63 | "width":1, 64 | "height":1}, 65 | "Tile7":{"exec":["playonlinux"], 66 | "icon":"playonlinux", 67 | "name":"PlayOnLinux", 68 | "X":0, 69 | "Y":2, 70 | "width":1, 71 | "height":1}, 72 | "Tile8":{"exec":["audacious"], 73 | "icon":"audacious", 74 | "name":"Audacious", 75 | "X":3, 76 | "Y":2, 77 | "width":1, 78 | "height":1}, 79 | "Tile9":{"exec":["gnome-system-monitor"], 80 | "icon":"applications-utilities", 81 | "name":"System Monitor", 82 | "X":2, 83 | "Y":3, 84 | "width":1, 85 | "height":1}, 86 | "Tile10":{"exec":["gnome-software"], 87 | "icon":"software", 88 | "name":"App Store", 89 | "X":0, 90 | "Y":3, 91 | "width":1, 92 | "height":1}, 93 | "Tile11":{"exec":["update-manager"], 94 | "icon":"system-software-update", 95 | "name":"Update System", 96 | "X":2, 97 | "Y":4, 98 | "width":1, 99 | "height":1}, 100 | "sys_info":{"X":4, 101 | "Y":2, 102 | "width":1, 103 | "height":2} 104 | } 105 | -------------------------------------------------------------------------------- /etc/gcde/defaults-global.json: -------------------------------------------------------------------------------- 1 | { 2 | "blur":0.75, 3 | "names":true, 4 | "icon size":64, 5 | "menu":{ 6 | "width":1, 7 | "height":1 8 | }, 9 | "window manager":"xfwm4" 10 | } 11 | -------------------------------------------------------------------------------- /screenshots/screenshot_10-26-2020_22-36-49.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drauger-os-development/gcde/082995ee7566623860cd78c48abb00ab7123b6b0/screenshots/screenshot_10-26-2020_22-36-49.png -------------------------------------------------------------------------------- /screenshots/screenshot_10-26-2020_22-37-33.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drauger-os-development/gcde/082995ee7566623860cd78c48abb00ab7123b6b0/screenshots/screenshot_10-26-2020_22-37-33.png -------------------------------------------------------------------------------- /screenshots/screenshot_10-26-2020_22-37-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drauger-os-development/gcde/082995ee7566623860cd78c48abb00ab7123b6b0/screenshots/screenshot_10-26-2020_22-37-48.png -------------------------------------------------------------------------------- /screenshots/screenshot_10-26-2020_22-38-04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drauger-os-development/gcde/082995ee7566623860cd78c48abb00ab7123b6b0/screenshots/screenshot_10-26-2020_22-38-04.png -------------------------------------------------------------------------------- /screenshots/screenshot_10-26-2020_22-39-06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drauger-os-development/gcde/082995ee7566623860cd78c48abb00ab7123b6b0/screenshots/screenshot_10-26-2020_22-39-06.png -------------------------------------------------------------------------------- /usr/bin/gcde: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # gcde 5 | # 6 | # Copyright 2020 Thomas Castleman 7 | # 8 | # This program is free software; you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation; either version 2 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program; if not, write to the Free Software 20 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 21 | # MA 02110-1301, USA. 22 | # 23 | # 24 | """GCDE Launch File""" 25 | import sys 26 | import os 27 | import subprocess 28 | import time 29 | import gcde 30 | VERSION = "0.0.9-alpha0" 31 | HELP = """GCDE, Version %s 32 | 33 | \t-h, --help\t\tPrint this help dialog and exit. 34 | \t-s, --start\t\tStart GCDE 35 | \t\t-d, --debug\t\t\tStart GCDE in debugging mode (USED FOR DEVELOPMENT AND TESTING) 36 | \t\t-r, --resolution\t\t\tOver-ride resolution GCDE uses. 37 | \t-v, --version\t\tPrint version and exit 38 | """ % (VERSION) 39 | ARGC = len(sys.argv) 40 | 41 | # Define configuration location 42 | home = os.getenv("HOME") 43 | if home[-1] != "/": 44 | home = home + "/" 45 | local_settings = home + ".config/gcde/global_settings.json" 46 | 47 | if len(sys.argv) > 1: 48 | for each in sys.argv: 49 | if each in ("--debug", "-d", "--testing", "-t"): 50 | debug = True 51 | break 52 | debug = False 53 | 54 | def cmd_exist(command): 55 | """Check if command exists""" 56 | path = os.getenv("PATH").split(":") 57 | for each in path: 58 | if os.path.exists(each + "/" + command): 59 | return each + "/" + command 60 | return "" 61 | 62 | 63 | def main(args=[]): 64 | """main runner for GCDE""" 65 | home = os.getenv("HOME") 66 | command = [] 67 | # Set up several environment variables 68 | os.putenv("XDG_CURRENT_DESKTOP", "GCDE") 69 | os.putenv("DESKTOP_SESSION", "GCDE") 70 | os.putenv("XDG_CONFIG_DIRS", "/etc/xdg:/etc/xdg") 71 | if os.getenv("XDG_CONFIG_HOME") == "x": 72 | os.putenv("XDG_CONFIG_HOME", home + "/.config") 73 | if os.getenv("XDG_CACHE_HOME") == "x": 74 | os.putenv("XDG_CACHE_HOME", home + "/.cache") 75 | if cmd_exist("xdg-user-dirs-update"): 76 | subprocess.Popen("xdg-user-dirs-update") 77 | os.putenv("XDG_DATA_DIRS", home + "/.local/share/flatpak/exports/share:/var/lib/flatpak/exports/share:/usr/local/share:/usr/share:/var/lib/snapd/desktop:/usr/share") 78 | wm = gcde.common.get_settings(local_settings) 79 | if "window manager" in wm: 80 | wm = wm["window manager"] 81 | else: 82 | wm = "xfwm4" 83 | path = cmd_exist(wm) 84 | if not path: 85 | path = cmd_exist("xfwm4") 86 | if not path: 87 | path = cmd_exist("openbox") 88 | if not debug: 89 | command = [path, "--replace"] 90 | if "xfwm4" in path: 91 | command = command + ["--compositor=off"] 92 | subprocess.Popen(command) 93 | subprocess.Popen(["/usr/bin/compton", "-b"]) 94 | command = ["/usr/bin/xfdesktop", "--display", os.getenv("DISPLAY")] 95 | subprocess.Popen(command) 96 | subprocess.Popen(["/usr/bin/xset", "s", "off"]) 97 | subprocess.Popen(["dbus-update-activation-environment", "--all"]) 98 | time.sleep(2) 99 | subprocess.Popen(["/usr/bin/wmctrl", "-n", "1"]) 100 | del path, home, command, wm 101 | print(subprocess.check_output(["/usr/share/gcde/engine.py"] + args)) 102 | 103 | 104 | if ARGC > 1: 105 | if sys.argv[1] in ("-h", "--help"): 106 | print(HELP) 107 | elif sys.argv[1] in ("-v", "--version"): 108 | print(VERSION) 109 | elif sys.argv[1] in ("-s", "--start"): 110 | if ARGC > 2: 111 | main(args=sys.argv[2:]) 112 | else: 113 | main() 114 | else: 115 | gcde.common.eprint("ERROR: `%s': flag not recognized" % (sys.argv[1])) 116 | else: 117 | print(HELP) 118 | -------------------------------------------------------------------------------- /usr/lib/python3/dist-packages/gcde/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # __init__.py 5 | # 6 | # Copyright 2020 Thomas Castleman 7 | # 8 | # This program is free software; you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation; either version 2 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program; if not, write to the Free Software 20 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 21 | # MA 02110-1301, USA. 22 | # 23 | # 24 | """__init__ for GCDE Lib""" 25 | from gcde import tile as tile 26 | from gcde import common as common 27 | -------------------------------------------------------------------------------- /usr/lib/python3/dist-packages/gcde/common.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # common.py 5 | # 6 | # Copyright 2020 Thomas Castleman 7 | # 8 | # This program is free software; you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation; either version 2 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program; if not, write to the Free Software 20 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 21 | # MA 02110-1301, USA. 22 | # 23 | # 24 | """Common functions""" 25 | import sys 26 | import os 27 | import json 28 | from ctypes import cdll, byref, create_string_buffer 29 | 30 | 31 | def scale(scale: float, res: int): 32 | """Get integer that is 'scale' of 'res' 33 | 34 | Useful for scaling images and objects based on the resolution of a screen""" 35 | return int(scale * res) 36 | 37 | 38 | def set_procname(newname): 39 | """set procname for current process""" 40 | libc = cdll.LoadLibrary('libc.so.6') #Loading a 3rd party library C 41 | buff = create_string_buffer(10) #Note: One larger than the name (man prctl says that) 42 | buff.value = bytes(newname, 'utf-8') #Null terminated string as it should be 43 | libc.prctl(15, byref(buff), 0, 0, 0) #Refer to "#define" of "/usr/include/linux/prctl.h" for the misterious value 16 & arg[3..5] are zero as the man page says. 44 | 45 | 46 | def eprint(*args, **kwargs): 47 | """Make it easier for us to print to stderr""" 48 | print(*args, file=sys.stderr, **kwargs) 49 | 50 | 51 | def get_settings(local_settings, global_settings="../../../etc/gcde/defaults-global.json"): 52 | """Get settings, global or local""" 53 | if os.path.exists(local_settings): 54 | with open(local_settings, "r") as file: 55 | return json.load(file) 56 | with open(global_settings, "r") as file: 57 | # shutil.copy(global_settings, local_settings) 58 | return json.load(file) 59 | -------------------------------------------------------------------------------- /usr/lib/python3/dist-packages/gcde/tile.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # tile.py 5 | # 6 | # Copyright 2020 Thomas Castleman 7 | # 8 | # This program is free software; you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation; either version 2 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program; if not, write to the Free Software 20 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 21 | # MA 02110-1301, USA. 22 | # 23 | # 24 | """Tile Object library to create tiles with different functions on GCDE""" 25 | import gi 26 | gi.require_version('Gtk', '3.0') 27 | from gi.repository import Gtk, Gdk, GdkPixbuf 28 | from subprocess import Popen 29 | import gcde.common as common 30 | 31 | 32 | class Tile(): 33 | """Tile object for GCDE""" 34 | def __init__(self): 35 | """Intialize the Tile""" 36 | self.settings = {"exec":[], 37 | "icon":None, 38 | "name":"Name", 39 | "X":0, 40 | "Y":0, 41 | "width":0.1, 42 | "height":0.1} 43 | self.obj = Gtk.Button.new() 44 | self.obj.set_always_show_image(True) 45 | 46 | def __set_settings__(self, new_settings: dict): 47 | """Set inital settings for a Tile object""" 48 | for each in new_settings: 49 | self.settings[each] = new_settings[each] 50 | print("Set settings for : %s " % (new_settings["name"])) 51 | 52 | def change_setting(self, setting_key: str, setting_value): 53 | """Change an individual setting for an indivdual tile""" 54 | self.settings[setting_key] = setting_value 55 | 56 | def get_settings(self): 57 | """Get settings for a specific tile 58 | 59 | This is useful for debugging, and for re-initializing Tiles.""" 60 | return self.settings 61 | 62 | def make(self, global_settings, width, height): 63 | """Define Tile drawing properties""" 64 | if global_settings["names"] is True: 65 | self.obj.set_label(self.settings["name"]) 66 | icon_theme = Gtk.IconTheme.get_default() 67 | icon_info = icon_theme.lookup_icon(self.settings["icon"], 48, 0) 68 | image = GdkPixbuf.Pixbuf() 69 | try: 70 | image = image.new_from_file_at_scale(icon_info.get_filename(), 71 | global_settings["icon size"], 72 | global_settings["icon size"], 73 | False) 74 | except AttributeError: 75 | icon_theme = Gtk.IconTheme.get_default() 76 | icon_info = icon_theme.lookup_icon("unknown", 48, 0) 77 | image = GdkPixbuf.Pixbuf() 78 | image = image.new_from_file_at_scale(icon_info.get_filename(), 79 | global_settings["icon size"], 80 | global_settings["icon size"], 81 | False) 82 | image = Gtk.Image.new_from_pixbuf(image) 83 | self.obj.set_image(image) 84 | self.obj.set_image_position(Gtk.PositionType.TOP) 85 | self.obj.set_margin_top(common.scale(0.0073, height)) 86 | self.obj.set_margin_bottom(common.scale(0.0073, height)) 87 | self.obj.set_margin_left(common.scale(0.006, width)) 88 | self.obj.set_margin_right(common.scale(0.006, width)) 89 | # This line below has been commented out because it just makes Tiles 90 | # transparent. It doesn't blur the background. We must wait until GTK 4 91 | # for that capability. 92 | # self.obj.set_opacity(global_settings["blur"]) 93 | 94 | def __get_internal_obj__(self): 95 | """Get internal GTK Object for Tile 96 | 97 | FOR INTERNAL GCDE USE ONLY""" 98 | return (self.obj, self.settings["X"], self.settings["Y"], 99 | self.settings["width"], self.settings["height"]) 100 | 101 | def run(self, widget): 102 | """Execute Click action""" 103 | Popen(self.settings["exec"]) 104 | 105 | 106 | def new(settings: dict): 107 | """Make a new tile with the settings in the `settings` list. 108 | 109 | This is meant to normally be used by the GCDE engine to initalize and 110 | configure a tile 111 | """ 112 | obj = Tile() 113 | obj.__set_settings__(new_settings=settings) 114 | return obj 115 | 116 | 117 | def reset_tile(tile): 118 | """Reset a tile by destroying it and making a new one.""" 119 | settings = tile.get_settings() 120 | tile = new(settings) 121 | return tile 122 | -------------------------------------------------------------------------------- /usr/share/gcde/controller_support/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # __init__.py 5 | # 6 | # Copyright 2020 Thomas Castleman 7 | # 8 | # This program is free software; you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation; either version 2 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program; if not, write to the Free Software 20 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 21 | # MA 02110-1301, USA. 22 | # 23 | # 24 | """Controller Support Module for GCDE""" 25 | import controller_support.buttons as buttons 26 | import controller_support.analog_in as analog 27 | import controller_support.conversion as converter 28 | -------------------------------------------------------------------------------- /usr/share/gcde/controller_support/__mapping_applicator__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # __mapping_applicator__.py 5 | # 6 | # Copyright 2020 Thomas Castleman 7 | # 8 | # This program is free software; you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation; either version 2 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program; if not, write to the Free Software 20 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 21 | # MA 02110-1301, USA. 22 | # 23 | # 24 | """Convert Analog Mappings to Keyboard events""" 25 | import sys 26 | import select 27 | import gcde 28 | from conversion import type_out 29 | 30 | 31 | def apply_mapping(): 32 | """Take Direction from Analog Mappings and Make Keyboard Events""" 33 | while True: 34 | while sys.stdin in select.select([sys.stdin], [], [], 0)[0]: 35 | line = sys.stdin.readline().lower() 36 | if line == "pausing!": 37 | continue 38 | else: 39 | type_out(line) 40 | 41 | if __name__ == "__main__": 42 | gcde.common.set_procname("gcde-map-applyer") 43 | apply_mapping() 44 | -------------------------------------------------------------------------------- /usr/share/gcde/controller_support/buttons.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # buttons.py 5 | # 6 | # Copyright 2020 Thomas Castleman 7 | # 8 | # This program is free software; you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation; either version 2 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program; if not, write to the Free Software 20 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 21 | # MA 02110-1301, USA. 22 | # 23 | # 24 | """Button Handler""" 25 | import pygame 26 | import sys 27 | import subprocess 28 | from conversion import type_out 29 | 30 | 31 | def __get_button_count__(joystick): 32 | """Get Button Count""" 33 | if not joystick.get_init(): 34 | joystick.init() 35 | return joystick.get_numbuttons() 36 | 37 | 38 | def button_handler(data, config): 39 | """Determine which function to use, then use it""" 40 | if data[0] == "xbox": 41 | xbox_handler(data[1], config) 42 | elif data[0] == "ps3": 43 | ps3_handler(data[1], config) 44 | elif data[0] in ("ps4", "switch"): 45 | switch_ps4_handler(data[1], config) 46 | 47 | 48 | def xbox_handler(j, config): 49 | """Map Xbox Controllers""" 50 | while True: 51 | events = pygame.event.get() 52 | for event in events: 53 | if event.type == pygame.JOYBUTTONDOWN: 54 | button = 0 55 | while button <= 12: 56 | try: 57 | j.get_button(button) 58 | except: 59 | continue 60 | if j.get_button(button): 61 | if button == 0: 62 | print(button) 63 | #button = "a" 64 | # type_out("space") 65 | break 66 | elif button == 1: 67 | print(button) 68 | #button = "b" 69 | break 70 | elif button == 2: 71 | #button = "x" 72 | print(button) 73 | # type_out("enter") 74 | break 75 | elif button == 3: 76 | print(button) 77 | #button = "y" 78 | # type_out("tab") 79 | break 80 | elif button == 4: 81 | print(button) 82 | #button = "left_bumper" 83 | # type_out("left") 84 | break 85 | elif button == 5: 86 | print(button) 87 | #button = "right_bumper" 88 | # type_out("right") 89 | break 90 | elif button == 6: 91 | print(button) 92 | #button = "back" 93 | break 94 | elif button == 7: 95 | print(button) 96 | #button = "start" 97 | break 98 | elif button == 8: 99 | print(button) 100 | #button = "menu"" 101 | break 102 | elif button == 9: 103 | print(button) 104 | #left analog button 105 | #type_out("backspace") 106 | break 107 | elif button == 10: 108 | #right analog button 109 | print(button) 110 | break 111 | else: 112 | print(button) 113 | break 114 | else: 115 | button += 1 116 | 117 | def switch_ps4_handler(j, config): 118 | """Map PS4/Switch Controllers""" 119 | while True: 120 | events = pygame.event.get() 121 | for event in events: 122 | if event.type == pygame.JOYBUTTONDOWN: 123 | button = 0 124 | while button <= 12: 125 | try: 126 | j.get_button(button) 127 | except: 128 | continue 129 | if j.get_button(button): 130 | if button == 0: 131 | print(button) 132 | #SWITCH: Y 133 | type_out("tab") 134 | break 135 | elif button == 1: 136 | print(button) 137 | #SWITCH: B 138 | break 139 | elif button == 2: 140 | #SWITCH: A 141 | print(button) 142 | # type_out("space") 143 | break 144 | elif button == 3: 145 | print(button) 146 | #SWITCH: X 147 | # type_out("enter") 148 | break 149 | elif button == 4: 150 | print(button) 151 | #SWITCH: LEFT BUMPER 152 | # type_out("left") 153 | break 154 | elif button == 5: 155 | print(button) 156 | #SWITCH: RIGHT BUMPER 157 | # type_out("right") 158 | break 159 | elif button == 6: 160 | print(button) 161 | #SWITCH: LEFT TRIGGER 162 | # type_out("backspace") 163 | break 164 | elif button == 7: 165 | print(button) 166 | #SWITCH: RIGHT TRIGGER 167 | break 168 | elif button == 8: 169 | print(button) 170 | #SWITCH: - 171 | break 172 | elif button == 9: 173 | print(button) 174 | #SWITCH: + 175 | break 176 | else: 177 | print(button) 178 | break 179 | else: 180 | button = button+1 181 | 182 | def ps3_handler(): 183 | """Map PS3 Controllers""" 184 | while True: 185 | events = pygame.event.get() 186 | for event in events: 187 | if event.type == pygame.JOYBUTTONDOWN: 188 | button = 0 189 | while button <= 12: 190 | try: 191 | j.get_button(button) 192 | except: 193 | continue 194 | if j.get_button(button): 195 | if button == 0: 196 | print(button) 197 | #PS3: Triangle 198 | # type_out("enter") 199 | break 200 | elif button == 1: 201 | print(button) 202 | #PS3: Circle 203 | # type_out("space") 204 | exit(0) 205 | break 206 | elif button == 2: 207 | #PS3: X 208 | print(button) 209 | # type_out("space") 210 | break 211 | elif button == 3: 212 | print(button) 213 | #SWITCH: X 214 | break 215 | elif button == 4: 216 | print(button) 217 | #PS3 LEFT BUMPER 218 | # type_out("left") 219 | break 220 | elif button == 5: 221 | print(button) 222 | #PS3: RIGHT BUMPER 223 | # type_out("right") 224 | break 225 | elif button == 6: 226 | print(button) 227 | #PS3: LEFT TRIGGER 228 | # type_out("backspace") 229 | break 230 | elif button == 7: 231 | print(button) 232 | #PS3: RIGHT TRIGGER 233 | break 234 | elif button == 8: 235 | print(button) 236 | #PS3: Select 237 | break 238 | elif button == 9: 239 | print(button) 240 | #PS3: Start 241 | break 242 | else: 243 | print(button) 244 | break 245 | else: 246 | button = button+1 247 | -------------------------------------------------------------------------------- /usr/share/gcde/controller_support/conversion.cxx: -------------------------------------------------------------------------------- 1 | /* 2 | * conversion.cxx 3 | * 4 | * Copyright 2020 Thomas Castleman 5 | * 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation; either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 19 | * MA 02110-1301, USA. 20 | * 21 | * 22 | */ 23 | 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | using namespace std; 37 | 38 | bool pressKeys( void ) 39 | { 40 | static int keyboardFd = -1; 41 | int rd,n; 42 | bool ret = false; 43 | 44 | DIR *dirp; 45 | struct dirent *dp; 46 | regex_t kbd; 47 | 48 | char fullPath[1024]; 49 | static char *dirName = "/dev/input/by-id"; 50 | 51 | int result; 52 | struct input_event forcedKey; 53 | 54 | 55 | // Send ls 56 | uint16_t keys[] = {KEY_LEFT,KEY_RIGHT,KEY_UP,KEY_DOWN}; 57 | int index; 58 | 59 | /* Find the device with a name ending in "event-kbd" */ 60 | 61 | if(regcomp(&kbd,"event-kbd",0)!=0) 62 | { 63 | printf("regcomp for kbd failed\n"); 64 | return false; 65 | 66 | } 67 | if ((dirp = opendir(dirName)) == NULL) { 68 | perror("couldn't open '/dev/input/by-id'"); 69 | return false; 70 | } 71 | 72 | 73 | do { 74 | errno = 0; 75 | if ((dp = readdir(dirp)) != NULL) 76 | { 77 | printf("readdir (%s)\n",dp->d_name); 78 | if(regexec (&kbd, dp->d_name, 0, NULL, 0) == 0) 79 | { 80 | printf("match for the kbd = %s\n",dp->d_name); 81 | sprintf(fullPath,"%s/%s",dirName,dp->d_name); 82 | keyboardFd = open(fullPath,O_WRONLY | O_NONBLOCK); 83 | printf("%s Fd = %d\n",fullPath,keyboardFd); 84 | printf("Getting exclusive access: "); 85 | result = ioctl(keyboardFd, EVIOCGRAB, 1); 86 | printf("%s\n", (result == 0) ? "SUCCESS" : "FAILURE"); 87 | 88 | } 89 | 90 | 91 | } 92 | } while (dp != NULL); 93 | 94 | closedir(dirp); 95 | 96 | 97 | regfree(&kbd); 98 | 99 | 100 | /* Now write some key press and key release events to the device */ 101 | 102 | 103 | index = 0; 104 | while(keys[index] != 0) 105 | { 106 | 107 | forcedKey.type = EV_KEY; 108 | forcedKey.value = 1; // Press 109 | forcedKey.code = keys[index]; 110 | gettimeofday(&forcedKey.time,NULL); 111 | 112 | n = write(keyboardFd,&forcedKey,sizeof(struct input_event)); 113 | printf("n=%d\n",n); 114 | 115 | forcedKey.type = EV_KEY; 116 | forcedKey.value = 0 ; // Release 117 | forcedKey.code = keys[index]; 118 | gettimeofday(&forcedKey.time,NULL); 119 | 120 | n = write(keyboardFd,&forcedKey,sizeof(struct input_event)); 121 | printf("n=%d\n",n); 122 | 123 | index += 1; 124 | } 125 | 126 | close(keyboardFd); 127 | 128 | return(true); 129 | 130 | } 131 | 132 | int main(int argc, char **argv) 133 | { 134 | pressKeys(); 135 | return 0; 136 | } 137 | -------------------------------------------------------------------------------- /usr/share/gcde/controller_support/conversion.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # conversion.py 5 | # 6 | # Copyright 2020 Thomas Castleman 7 | # 8 | # This program is free software; you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation; either version 2 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program; if not, write to the Free Software 20 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 21 | # MA 02110-1301, USA. 22 | # 23 | # 24 | # TODO: Fix error from "backspace" and "caps_lock": throws some sort of error from pynput 25 | """Convert Human-Readable Input to Machine-Readable Output""" 26 | from pynput.keyboard import Key, Controller 27 | 28 | def type_out(output_key): 29 | keyboard=Controller() 30 | #output_key=sys.argv[1] 31 | output_key=str(output_key) 32 | output_key=output_key.lower() 33 | if output_key == "backspace": 34 | keyboard.press(Key.backspace) 35 | keyboard.release(Key.backspace) 36 | elif output_key == "caps_lock" or output_key == "shift": 37 | keyboard.press(Key.caps_lock) 38 | keyboard.release(Key.caps_lock) 39 | elif output_key == "space": 40 | keyboard.press(Key.space) 41 | keyboard.release(Key.space) 42 | elif output_key == "left": 43 | keyboard.press(Key.left) 44 | keyboard.release(Key.left) 45 | elif output_key == "right": 46 | keyboard.press(Key.right) 47 | keyboard.release(Key.right) 48 | elif output_key == "up": 49 | keyboard.press(Key.up) 50 | keyboard.release(Key.up) 51 | elif output_key == "down": 52 | keyboard.press(Key.down) 53 | keyboard.release(Key.down) 54 | elif output_key == "enter": 55 | keyboard.press(Key.enter) 56 | keyboard.release(Key.enter) 57 | elif output_key == "tab": 58 | keyboard.press(Key.tab) 59 | keyboard.release(Key.tab) 60 | else: 61 | keyboard.press(output_key) 62 | keyboard.release(output_key) 63 | keyboard.release(Key.caps_lock) 64 | -------------------------------------------------------------------------------- /usr/share/gcde/controller_support/gcde-analog-mapper.cxx: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: Jason White 3 | * 4 | * Description: 5 | * Reads joystick/gamepad events and displays them. 6 | * 7 | * Compile: 8 | * gcc joystick.c -o joystick 9 | * 10 | * Run: 11 | * ./joystick [/dev/input/jsX] 12 | * 13 | * See also: 14 | * https://www.kernel.org/doc/Documentation/input/joystick-api.txt 15 | * 16 | * Modified by: Thomas Castleman 17 | * 18 | * 19 | * Modifications: 20 | * * Removed button support 21 | * * Added directional understanding for left joystick 22 | */ 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | using namespace std; 32 | 33 | #define elif else if 34 | 35 | bool DoesPathExist(const string &s) 36 | { 37 | struct stat buffer; 38 | return (stat (s.c_str(), &buffer) == 0); 39 | } 40 | /** 41 | * Reads a joystick event from the joystick device. 42 | * 43 | * Returns 0 on success. Otherwise -1 is returned. 44 | */ 45 | int read_event(int fd, struct js_event *event) 46 | { 47 | ssize_t bytes; 48 | 49 | bytes = read(fd, event, sizeof(*event)); 50 | 51 | if (bytes == sizeof(*event)) 52 | return 0; 53 | 54 | /* Error, could not read full event. */ 55 | return -1; 56 | } 57 | 58 | /** 59 | * Returns the number of axes on the controller or 0 if an error occurs. 60 | */ 61 | size_t get_axis_count(int fd) 62 | { 63 | __u8 axes; 64 | 65 | if (ioctl(fd, JSIOCGAXES, &axes) == -1) 66 | return 0; 67 | 68 | return axes; 69 | } 70 | 71 | /** 72 | * Returns the number of buttons on the controller or 0 if an error occurs. 73 | */ 74 | size_t get_button_count(int fd) 75 | { 76 | __u8 buttons; 77 | if (ioctl(fd, JSIOCGBUTTONS, &buttons) == -1) 78 | return 0; 79 | 80 | return buttons; 81 | } 82 | 83 | /** 84 | * Current state of an axis. 85 | */ 86 | struct axis_state { 87 | short x, y; 88 | }; 89 | 90 | /** 91 | * Keeps track of the current axis state. 92 | * 93 | * NOTE: This function assumes that axes are numbered starting from 0, and that 94 | * the X axis is an even number, and the Y axis is an odd number. However, this 95 | * is usually a safe assumption. 96 | * 97 | * Returns the axis that the event indicated. 98 | */ 99 | size_t get_axis_state(struct js_event *event, struct axis_state axes[3]) 100 | { 101 | size_t axis = event->number / 2; 102 | 103 | if (axis < 3) 104 | { 105 | if (event->number % 2 == 0) 106 | axes[axis].x = event->value; 107 | else 108 | axes[axis].y = event->value; 109 | } 110 | 111 | return axis; 112 | } 113 | 114 | // Get the angle at which the joystick is pointing, 115 | // And how far 116 | string get_direction(struct axis_state axis[0]) 117 | { 118 | //Get hypotenuse, this will determine how far we are from center 119 | //THAT tells us whether we are in the dead-zone or not 120 | double x = axis->x; 121 | double y = axis->y * -1; 122 | //double z = pow((pow(x, 2) + pow(y, 2)), 0.5); 123 | if (pow((pow(x, 2) + pow(y, 2)), 0.5) > 3000) 124 | { 125 | // If Y > X, we go left or up 126 | if (y > x) 127 | { 128 | if (y > -x) 129 | { 130 | return "UP"; 131 | } 132 | else 133 | { 134 | return "LEFT"; 135 | } 136 | } 137 | // If X > Y, we go right or down 138 | elif (x > y) 139 | { 140 | if (x > -y) 141 | { 142 | return "RIGHT"; 143 | } 144 | else 145 | { 146 | return "DOWN"; 147 | } 148 | // return "RIGHT or DOWN!"; 149 | } 150 | else 151 | { 152 | return "None"; 153 | } 154 | } 155 | else 156 | { 157 | return "None"; 158 | } 159 | } 160 | 161 | 162 | int main(int argc, char *argv[]) 163 | { 164 | const char *device; 165 | int js; 166 | struct js_event event; 167 | struct axis_state axes[3] = {0}; 168 | size_t axis; 169 | string direction; 170 | 171 | if (argc > 1) 172 | device = argv[1]; 173 | else 174 | device = "/dev/input/js0"; 175 | 176 | js = open(device, O_RDONLY); 177 | 178 | if (js == -1) 179 | perror("Could not open joystick"); 180 | 181 | /* This loop will exit if the controller is unplugged. */ 182 | while (read_event(js, &event) == 0) 183 | { 184 | if (DoesPathExist("/etc/gcde/enable-mapping.flag")) 185 | { 186 | cout << "Pausing!" << endl; 187 | usleep(850); 188 | continue; 189 | } 190 | switch (event.type) 191 | { 192 | case JS_EVENT_AXIS: 193 | // cout << "Casing ..." << endl; 194 | axis = get_axis_state(&event, axes); 195 | if (axis == 0) 196 | { 197 | direction = get_direction(axes); 198 | if (direction != "") 199 | { 200 | cout << direction << endl; 201 | } 202 | } 203 | break; 204 | default: 205 | /* Ignore init events. */ 206 | break; 207 | } 208 | 209 | } 210 | 211 | close(js); 212 | return 0; 213 | } 214 | -------------------------------------------------------------------------------- /usr/share/gcde/engine.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # engine.py 5 | # 6 | # Copyright 2020 Thomas Castleman 7 | # 8 | # This program is free software; you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation; either version 2 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program; if not, write to the Free Software 20 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 21 | # MA 02110-1301, USA. 22 | # 23 | # 24 | """Engine for GCDE""" 25 | GTK_VERSION = "3.0" 26 | import os 27 | import json 28 | import copy 29 | import shlex 30 | import multiprocessing 31 | import sys 32 | from subprocess import check_output, Popen 33 | import gi 34 | gi.require_version('Gtk', GTK_VERSION) 35 | from gi.repository import Gtk, Pango 36 | import cairo 37 | import gcde 38 | import plugins 39 | 40 | gcde.common.set_procname("gcde") 41 | 42 | # This block makes it MUCH easier to test functionality without having to log 43 | # out then log into GCDE 44 | if len(sys.argv) > 1: 45 | for each in sys.argv: 46 | if each in ("--debug", "-d", "--testing", "-t"): 47 | debug = True 48 | break 49 | debug = False 50 | for each in enumerate(sys.argv): 51 | if sys.argv[each[0]] in ("--resolution", "-r"): 52 | res_override = True 53 | width = int(sys.argv[each[0] + 1].split("x")[0]) 54 | height = int(sys.argv[each[0] + 1].split("x")[1]) 55 | break 56 | res_override = False 57 | else: 58 | debug = False 59 | res_override = False 60 | 61 | 62 | # Define configuration location 63 | home = os.getenv("HOME") 64 | if home[-1] != "/": 65 | home = home + "/" 66 | local_settings = home + ".config/gcde/global_settings.json" 67 | local_tiles = home + ".config/gcde/tiles.json" 68 | global_settings = "/etc/gcde/defaults-global.json" 69 | global_tiles = "/etc/gcde/default-tiles.json" 70 | themes_file = home + ".config/gtk-3.0/settings.ini" 71 | 72 | # Get screen resolution for proper scaling 73 | if not res_override: 74 | results = check_output(['xrandr']).decode().split("current")[1].split(",")[0] 75 | width = results.split("x")[0].strip() 76 | height = results.split("x")[1].strip() 77 | width = int(width) 78 | height = int(height) 79 | 80 | # Make config dir 81 | try: 82 | os.mkdir(home + ".config/gcde") 83 | except FileExistsError: 84 | pass 85 | 86 | 87 | def get_tiles(): 88 | """Get tile configurations, global or local""" 89 | if os.path.exists(local_tiles): 90 | with open(local_tiles, "r") as file: 91 | return json.load(file) 92 | with open(global_tiles, "r") as file: 93 | # shutil.copy(global_tiles, local_tiles) 94 | return json.load(file) 95 | 96 | 97 | def launch_autostarters(): 98 | """Launch Autostart apps""" 99 | prefix = home + ".config/autostart/" 100 | file_list = os.listdir(prefix) 101 | for each in file_list: 102 | skip = False 103 | with open(prefix + each, "r") as file: 104 | data = file.read().split("\n") 105 | for each1 in data: 106 | if each1 == "X-GNOME-Autostart-enabled=false": 107 | skip = True 108 | break 109 | if skip: 110 | continue 111 | for each1 in data: 112 | if each1[:5] == "Exec=": 113 | execute = shlex.split(each1[5:]) 114 | break 115 | subprocess.Popen(execute) 116 | for each in file_list: 117 | skip = False 118 | with open(prefix + each, "r") as file: 119 | data = file.read() 120 | if "X-GNOME-Autostart-enabled=" not in data: 121 | data = data.split("\n") 122 | data.append("X-GNOME-Autostart-enabled=false") 123 | data = "\n".join(data) 124 | os.remove(prefix + each) 125 | with open(prefix + each, "w") as file: 126 | file.write(data) 127 | 128 | 129 | def autostart_enabled(path): 130 | """Get whether autostart is enabled for a file""" 131 | with open(path, "r") as file: 132 | data = file.read().split("\n") 133 | for each in enumerate(data): 134 | if "X-GNOME-Autostart-enabled" in data[each[0]]: 135 | data[each[0]] = data[each[0]].split("=") 136 | if data[each[0]][1] == "false": 137 | return False 138 | return True 139 | data.append("X-GNOME-Autostart-enabled=false") 140 | data = "\n".join(data) 141 | os.remove(path) 142 | with open(path, "w") as file: 143 | file.write(data) 144 | return False 145 | 146 | 147 | 148 | def toggle_autostart(path): 149 | """Toggle Autostart setting""" 150 | with open(path, "r") as file: 151 | data = file.read().split("\n") 152 | for each in enumerate(data): 153 | if "X-GNOME-Autostart-enabled" in data[each[0]]: 154 | data[each[0]] = data[each[0]].split("=") 155 | if data[each[0]][1] == "false": 156 | data[each[0]][1] = "true" 157 | else: 158 | data[each[0]][1] = "false" 159 | data[each[0]] = "=".join(data[each[0]]) 160 | data = "\n".join(data) 161 | os.remove(path) 162 | with open(path, "w") as file: 163 | file.write(data) 164 | 165 | 166 | def desktop_to_json(path, x, y, w, h, extra_key="Hidden="): 167 | """Convert Desktop file to GCDE Tile Json""" 168 | hidden = (extra_key, len(extra_key)) 169 | with open(path, "r") as file: 170 | data = file.read().split("\n") 171 | for each in range(len(data) - 1, -1, -1): 172 | if data[each] == "": 173 | del data[each] 174 | if len(data) < 4: 175 | return {} 176 | tile_settings = {} 177 | for each in data: 178 | if each[:5] == "Name=": 179 | tile_settings["name"] = each[5:] 180 | elif each[:5] == "Icon=": 181 | tile_settings["icon"] = each[5:] 182 | elif each[:hidden[1]] == hidden[0]: 183 | if each[hidden[1]:] == "true": 184 | tile_settings["hidden"] = True 185 | else: 186 | tile_settings["hidden"] = False 187 | elif each[:5] == "Exec=": 188 | tile_settings["execute"] = shlex.split(each[5:]) 189 | if "hidden" not in tile_settings: 190 | tile_settings["hidden"] = False 191 | tile_settings["X"] = x 192 | tile_settings["Y"] = y 193 | tile_settings["width"] = w 194 | tile_settings["height"] = h 195 | return tile_settings 196 | 197 | 198 | class Matrix(Gtk.Window): 199 | """Matrix in which Tiles live""" 200 | def __init__(self): 201 | """Make the Matrix""" 202 | Gtk.Window.__init__(self, title="GTK+ Console Desktop Environment") 203 | self.grid = Gtk.Grid(orientation=Gtk.Orientation.VERTICAL) 204 | self.grid.set_column_homogeneous(True) 205 | self.grid.set_row_homogeneous(True) 206 | self.grid.set_column_spacing(1) 207 | self.grid.set_row_spacing(1) 208 | self.add(self.grid) 209 | self.settings = gcde.common.get_settings(local_settings) 210 | self.tiles = get_tiles() 211 | self.scrolling = False 212 | self.background_launched = False 213 | 214 | self.reboot = {"exec":["reboot"], 215 | "icon":"system-reboot", "name":"Reboot", "X":0, "Y":2, 216 | "width":int(width / 4), "height":1} 217 | self.log_out = {"exec":["logout"], 218 | "icon":"system-log-out", "name":"Log Out", 219 | "X":int(width / 4), "Y":2, "width":int(width / 4), 220 | "height":1} 221 | self.shutdown = {"exec":["poweroff"], 222 | "icon":"gnome-shutdown", 223 | "name":"Shutdown", 224 | "X":int(width / 4) * 2, 225 | "Y":2, "width":int(width / 4), 226 | "height":1} 227 | self.back = {"exec":["main"], 228 | "icon":"application-exit", 229 | "name":"Back", 230 | "X":int(width / 4) * 3, 231 | "Y":2, 232 | "width":int(width / 4), 233 | "height":1} 234 | self.back = gcde.tile.new(self.back) 235 | self.reboot = gcde.tile.new(self.reboot) 236 | self.poweroff = gcde.tile.new(self.shutdown) 237 | self.log_out = gcde.tile.new(self.log_out) 238 | 239 | if not debug: 240 | Popen(["/usr/bin/wmctrl", "-n", "1"]) 241 | proc = multiprocessing.Process(target=launch_autostarters) 242 | proc.start() 243 | 244 | self.main("clicked") 245 | 246 | def make_scrolling(self, widget): 247 | """Make current window scroll""" 248 | self.scrolling = True 249 | 250 | self.remove(self.grid) 251 | 252 | self.scrolled_window = Gtk.ScrolledWindow() 253 | self.scrolled_window.set_border_width(10) 254 | # there is always the scrollbar (otherwise: AUTOMATIC - 255 | # only if needed 256 | # - or NEVER) 257 | self.scrolled_window.set_policy(Gtk.PolicyType.AUTOMATIC, 258 | Gtk.PolicyType.AUTOMATIC) 259 | self.add(self.scrolled_window) 260 | # self.scrolled_window.add_with_viewport(self.grid) 261 | self.scrolled_window.add(self.grid) 262 | 263 | def main(self, widget): 264 | self.clear_window() 265 | 266 | self.connect('destroy', Gtk.main_quit) 267 | self.connect('draw', self.make) 268 | 269 | screen = self.get_screen() 270 | visual = screen.get_rgba_visual() 271 | if visual and screen.is_composited(): 272 | self.set_visual(visual) 273 | 274 | self.set_app_paintable(True) 275 | 276 | self.show_all() 277 | 278 | def session_manager(self, widget): 279 | """Basic Session Manager""" 280 | self.clear_window() 281 | 282 | title = Gtk.Label() 283 | title.set_markup("\n\tAre you sure?\t\n") 284 | title.override_font(Pango.FontDescription("Open Sans %s" % (gcde.common.scale(0.05, 285 | height)))) 286 | self.grid.attach(title, 0, 0, width, 2) 287 | 288 | self.__place_tile__(self.log_out, scale=False) 289 | self.__place_tile__(self.reboot, scale=False) 290 | self.__place_tile__(self.poweroff, scale=False) 291 | self.__place_tile__(self.back, scale=False) 292 | 293 | self.show_all() 294 | 295 | def make(self, widget, context): 296 | """Draw window background""" 297 | context.set_source_rgba(0, 0, 0, 0) 298 | context.set_operator(cairo.OPERATOR_SOURCE) 299 | context.paint() 300 | context.set_operator(cairo.OPERATOR_OVER) 301 | 302 | 303 | def tile(self, widget): 304 | """Get Tiles to place into Matrix, then place them""" 305 | self.clear_window() 306 | 307 | plugin_list = dir(plugins) 308 | for each in range(len(plugin_list) - 1, -1, -1): 309 | if plugin_list[each][0] == "_": 310 | del plugin_list[each] 311 | elif plugin_list[each] == "example": 312 | del plugin_list[each] 313 | plug_objs = [] 314 | for each in plugin_list: 315 | plug_new = getattr(plugins, each) 316 | try: 317 | if plug_new.PLUGIN_TYPE == 0: 318 | plug_objs.append(plug_new.plugin_setup(copy.deepcopy(self.tiles[each]))) 319 | elif plug_new.PLUGIN_TYPE == 1: 320 | plug_objs.append(plug_new.plugin_setup(copy.deepcopy(self.settings))) 321 | elif plug_new.PLUGIN_TYPE >= 2: 322 | plug_objs.append(plug_new.plugin_setup({"loc":copy.deepcopy(self.tiles[each]), 323 | "global":copy.deepcopy(self.settings)})) 324 | except KeyError: 325 | continue 326 | 327 | 328 | for each in self.tiles: 329 | if ("tile" in each.lower()) or (each in ("session_manager", "menu", "settings")): 330 | self.__place_tile__(gcde.tile.new(self.tiles[each]), scale=False) 331 | for each in plug_objs: 332 | self.__place_tile__(each, scale=False) 333 | 334 | del plug_objs, plugin_list, each 335 | 336 | self.show_all() 337 | 338 | def __place_tile__(self, tile, scale=True): 339 | """Place tile in matrix""" 340 | tile.make(copy.deepcopy(self.settings), width, height) 341 | tile_obj = tile.__get_internal_obj__() 342 | tile_settings = tile.get_settings() 343 | try: 344 | if tile_settings["exec"][0].lower() == "settings": 345 | tile_obj[0].connect("clicked", self.settings_window) 346 | elif tile_settings["exec"][0].lower() == "logout": 347 | tile_obj[0].connect("clicked", self.logout) 348 | elif tile_settings["exec"][0].lower() == "menu": 349 | tile_obj[0].connect("clicked", self.menu) 350 | elif tile_settings["exec"][0].lower() == "main": 351 | tile_obj[0].connect("clicked", self.tile) 352 | elif tile_settings["exec"][0].lower() == "session_manager": 353 | tile_obj[0].connect("clicked", self.session_manager) 354 | elif tile_settings["exec"][0].lower() == "restart": 355 | tile_obj[0].connect("clicked", self.restart) 356 | elif tile_settings["exec"][0].lower() == "autostart-settings": 357 | tile_obj[0].connect("clicked", self.autostart_settings) 358 | else: 359 | tile_obj[0].connect("clicked", tile.run) 360 | except TypeError: 361 | pass 362 | if scale: 363 | self.grid.attach(tile_obj[0], 364 | gcde.common.scale(tile_obj[1], width), 365 | gcde.common.scale(tile_obj[2], height), 366 | gcde.common.scale(tile_obj[3], width), 367 | gcde.common.scale(tile_obj[4], height)) 368 | else: 369 | self.grid.attach(tile_obj[0], tile_obj[1], tile_obj[2], tile_obj[3], 370 | tile_obj[4]) 371 | 372 | def autostart_settings(self, widget): 373 | """Window to define which files should be autostart and which shouldn't""" 374 | self.clear_window() 375 | 376 | self.make_scrolling("clicked") 377 | prefix = home + ".config/autostart/" 378 | w = 1 379 | h = 1 380 | x = 0 381 | y = 2 382 | file_list = os.listdir(prefix) 383 | 384 | title = Gtk.Label() 385 | title.set_markup("\n\tAutostart Applications\t\n") 386 | title.override_font(Pango.FontDescription("Open Sans %s" % (gcde.common.scale(0.03, 387 | height)))) 388 | self.grid.attach(title, 0, 0, 1, 2) 389 | 390 | for each in file_list: 391 | data = desktop_to_json(prefix + each, x, y, w, h, 392 | extra_key="X-GNOME-Autostart-enabled=") 393 | if len(data) == 0: 394 | continue 395 | check_box = Gtk.CheckButton.new_with_label(data["name"]) 396 | check_box.set_active(data["hidden"]) 397 | check_box.override_font(Pango.FontDescription("Open Sans %s" % (gcde.common.scale(0.02, 398 | height)))) 399 | self.grid.attach(check_box, data["X"], data["Y"], data["width"], 400 | data["height"]) 401 | y += 1 402 | 403 | back_button = {"exec":["settings"], "icon":"application-exit", 404 | "name":"Back", "X":0, "Y":y, "width":1, "height":1} 405 | back_button = gcde.tile.new(back_button) 406 | self.__place_tile__(back_button, scale=False) 407 | 408 | del w, h, x, y, file_list, prefix, check_box, each, data, title, back_button 409 | self.show_all() 410 | 411 | def settings_window(self, widget): 412 | """Settings Window""" 413 | self.clear_window() 414 | 415 | self.make_scrolling("clicked") 416 | 417 | sub_heading = 0.025 418 | label = 0.015 419 | 420 | title = Gtk.Label() 421 | title.set_markup("\n\tSettings\t\n") 422 | title.override_font(Pango.FontDescription("Open Sans %s" % (gcde.common.scale(0.05, 423 | height)))) 424 | self.grid.attach(title, 0, 0, width, 2) 425 | 426 | icon_title = Gtk.Label() 427 | icon_title.set_markup("\n\tIcon Size\t\n") 428 | icon_title.override_font(Pango.FontDescription("Open Sans %s" % (gcde.common.scale(sub_heading, 429 | height)))) 430 | self.grid.attach(icon_title, 0, 1, width, 2) 431 | 432 | self.icon_scaler = Gtk.Scale.new_with_range(Gtk.Orientation.HORIZONTAL, 4, 433 | 200, 2) 434 | self.icon_scaler.set_draw_value(True) 435 | self.icon_scaler.set_value(self.settings["icon size"]) 436 | self.icon_scaler.override_font(Pango.FontDescription("Open Sans %s" % (gcde.common.scale(sub_heading, 437 | height)))) 438 | self.grid.attach(self.icon_scaler, 0, 2, width, 2) 439 | 440 | menu_title = Gtk.Label() 441 | menu_title.set_markup("\n\tApplication Menu Tile Size\t\n") 442 | menu_title.override_font(Pango.FontDescription("Open Sans %s" % (gcde.common.scale(sub_heading, 443 | height)))) 444 | self.grid.attach(menu_title, 0, 3, width, 2) 445 | 446 | X_title = Gtk.Label() 447 | X_title.set_markup("\n\tWidth\t\n") 448 | X_title.override_font(Pango.FontDescription("Open Sans %s" % (gcde.common.scale(label, 449 | height)))) 450 | self.grid.attach(X_title, 0, 4, width, 2) 451 | 452 | Y_title = Gtk.Label() 453 | Y_title.set_markup("\n\tHeight\t\n") 454 | Y_title.override_font(Pango.FontDescription("Open Sans %s" % (gcde.common.scale(label, 455 | height)))) 456 | self.grid.attach(Y_title, 0, 6, width, 2) 457 | 458 | self.X_scaler = Gtk.Scale.new_with_range(Gtk.Orientation.HORIZONTAL, 1, 459 | 10, 1) 460 | self.X_scaler.set_draw_value(True) 461 | self.X_scaler.set_value(self.settings["menu"]["width"]) 462 | self.X_scaler.override_font(Pango.FontDescription("Open Sans %s" % (gcde.common.scale(sub_heading, 463 | height)))) 464 | self.grid.attach(self.X_scaler, 0, 5, width, 2) 465 | 466 | self.Y_scaler = Gtk.Scale.new_with_range(Gtk.Orientation.HORIZONTAL, 1, 467 | 10, 1) 468 | self.Y_scaler.set_draw_value(True) 469 | self.Y_scaler.set_value(self.settings["menu"]["height"]) 470 | self.Y_scaler.override_font(Pango.FontDescription("Open Sans %s" % (gcde.common.scale(sub_heading, 471 | height)))) 472 | self.grid.attach(self.Y_scaler, 0, 7, width, 2) 473 | 474 | theming_defaults = get_theming_defaults() 475 | gtk_themes = list_gtk_themes() 476 | icon_themes = list_icon_themes() 477 | 478 | theming_title = Gtk.Label() 479 | theming_title.set_markup("\n\tTheming\t\n") 480 | theming_title.override_font(Pango.FontDescription("Open Sans %s" % (gcde.common.scale(sub_heading, 481 | height)))) 482 | self.grid.attach(theming_title, 0, 9, width, 2) 483 | 484 | gtk_theming_title = Gtk.Label() 485 | gtk_theming_title.set_markup("\n\tGtk Theme\t\n") 486 | gtk_theming_title.override_font(Pango.FontDescription("Open Sans %s" % (gcde.common.scale(label, 487 | height)))) 488 | self.grid.attach(gtk_theming_title, 0, 11, width, 2) 489 | 490 | self.gtk_theme_chooser = Gtk.ComboBoxText.new() 491 | for each in gtk_themes: 492 | self.gtk_theme_chooser.append(each, each) 493 | self.gtk_theme_chooser.set_active_id(theming_defaults["gtk-theme-name"]) 494 | self.gtk_theme_chooser.override_font(Pango.FontDescription("Open Sans %s" % (gcde.common.scale(label, 495 | height)))) 496 | 497 | self.grid.attach(self.gtk_theme_chooser, 0, 13, width, 2) 498 | 499 | icon_theming_title = Gtk.Label() 500 | icon_theming_title.set_markup("\n\tIcon Theme\t\n") 501 | icon_theming_title.override_font(Pango.FontDescription("Open Sans %s" % (gcde.common.scale(label, 502 | height)))) 503 | self.grid.attach(icon_theming_title, 0, 15, width, 2) 504 | 505 | self.icon_theme_chooser = Gtk.ComboBoxText.new() 506 | for each in icon_themes: 507 | self.icon_theme_chooser.append(each.lower(), each) 508 | self.icon_theme_chooser.set_active_id(theming_defaults["gtk-icon-theme-name"]) 509 | self.icon_theme_chooser.override_font(Pango.FontDescription("Open Sans %s" % (gcde.common.scale(label, 510 | height)))) 511 | 512 | self.grid.attach(self.icon_theme_chooser, 0, 17, width, 2) 513 | 514 | sars = {"exec":["restart"], 515 | "icon":"system-reboot", 516 | "name":"Save and Restart GCDE", 517 | "X":0, 518 | "Y":19, 519 | "width":int(width / 3), 520 | "height":1} 521 | autolaunch = {"exec":["autostart-settings"], 522 | "icon":"xfce4-session", 523 | "name":"Autostart Applications", 524 | "X":int(width / 3), 525 | "Y":19, 526 | "width":int(width / 3), 527 | "height":1} 528 | quit = {"exec":["main"], 529 | "icon":"application-exit", 530 | "name":"Exit", 531 | "X":int(width / 3) * 2, 532 | "Y":19, 533 | "width":int(width / 3), 534 | "height":1} 535 | sars = gcde.tile.new(sars) 536 | quit = gcde.tile.new(quit) 537 | autolaunch = gcde.tile.new(autolaunch) 538 | self.__place_tile__(sars, scale=False) 539 | self.__place_tile__(autolaunch, scale=False) 540 | self.__place_tile__(quit, scale=False) 541 | 542 | del theming_defaults, gtk_themes, icon_themes, sub_heading, label 543 | 544 | self.show_all() 545 | 546 | def restart(self, widget): 547 | """Restart GCDE""" 548 | self.save_settings("clicked") 549 | try: 550 | Popen(["/usr/share/gcde/restart.py"]) 551 | except FileNotFoundError: 552 | Popen(["./restart.py"]) 553 | 554 | def logout(self, widget): 555 | """Logout of GCDE""" 556 | Gtk.main_quit("delete-event") 557 | self.destroy() 558 | # Probably the wrong way to do this, but it gets us back to the login screen 559 | exit() 560 | 561 | def save_settings(self, widget): 562 | """Save settings to file""" 563 | try: 564 | os.remove(local_settings) 565 | except FileNotFoundError: 566 | pass 567 | self.settings["icon size"] = self.icon_scaler.get_value() 568 | self.settings["menu"]["width"] = self.X_scaler.get_value() 569 | self.settings["menu"]["height"] = self.Y_scaler.get_value() 570 | with open(local_settings, "w") as file: 571 | json.dump(self.settings, file, indent=1) 572 | with open(themes_file, "r") as file: 573 | data = file.read().split("\n") 574 | for each in range(len(data) - 1, -1, -1): 575 | data[each] = data[each].split("=") 576 | for each in data: 577 | if each[0] == "gtk-theme-name": 578 | each[1] = self.gtk_theme_chooser.get_active_id() 579 | elif each[0] == "gtk-icon-theme-name": 580 | each[1] = self.icon_theme_chooser.get_active_id() 581 | for each in enumerate(data): 582 | data[each[0]] = "=".join(data[each[0]]) 583 | data = "\n".join(data) 584 | os.remove(themes_file) 585 | with open(themes_file, "w") as file: 586 | file.write(data) 587 | 588 | def menu(self, widget): 589 | """Application Menu""" 590 | self.clear_window() 591 | 592 | self.make_scrolling("clicked") 593 | 594 | prefix = "/usr/share/applications/" 595 | file_list = sorted(os.listdir(prefix)) 596 | for each in range(len(file_list) - 1, -1, -1): 597 | if os.path.isdir(prefix + file_list[each]): 598 | del file_list[each] 599 | print(len(file_list)) 600 | w = self.settings["menu"]["width"] 601 | h = self.settings["menu"]["height"] 602 | x = 0 603 | y = 0 604 | width_max = 7 605 | tiles = [] 606 | back = {"exec":["main"], 607 | "icon":"application-exit", 608 | "name":"Back to Matrix", 609 | "X":x, 610 | "Y":y, 611 | "width":w, 612 | "height":h} 613 | tiles.append(gcde.tile.new(back)) 614 | x += 1 615 | for each in file_list: 616 | skip = False 617 | with open(prefix + each, "r") as file: 618 | data = file.read().split("\n") 619 | for each1 in data: 620 | if "NoDisplay" in each1: 621 | if each1[-4:].lower() == "true": 622 | skip = True 623 | break 624 | elif "OnlyShowIn" in each1: 625 | if "gcde" not in each1.lower(): 626 | skip = True 627 | break 628 | elif "Terminal" in each1: 629 | if each1[-4:].lower() == "true": 630 | skip = True 631 | break 632 | elif each1 == "": 633 | break 634 | if skip: 635 | continue 636 | for each1 in data: 637 | if each1[:5] == "Name=": 638 | name = each1[5:] 639 | elif each1[:5] == "Icon=": 640 | icon = each1[5:] 641 | elif each1[:5] == "Exec=": 642 | execute = shlex.split(each1[5:]) 643 | tile_settings = {"exec":execute, 644 | "icon":icon, "name":name, 645 | "X":x, "Y":y, "width":w, "height":h} 646 | tiles.append(gcde.tile.new(tile_settings)) 647 | if x >= width_max: 648 | x = 0 649 | y += 1 650 | else: 651 | x += 1 652 | for each in tiles: 653 | self.__place_tile__(each, scale=False) 654 | # Try to free some memeory 655 | del tiles, file_list, each, each1, tile_settings, back, x, y, width_max 656 | del w, h, prefix, execute, icon, name, data 657 | 658 | self.show_all() 659 | 660 | def clear_window(self): 661 | """Clear Window""" 662 | children = self.grid.get_children() 663 | for each in children: 664 | self.grid.remove(each) 665 | if self.scrolling: 666 | self.scrolled_window.remove(self.grid) 667 | self.remove(self.scrolled_window) 668 | self.add(self.grid) 669 | self.scrolling = False 670 | 671 | 672 | def list_icon_themes(): 673 | """List Icon Themes""" 674 | themes = os.listdir("/usr/share/icons") 675 | for each in range(len(themes) - 1, -1, -1): 676 | if "default" in themes[each].lower(): 677 | del themes[each] 678 | return themes 679 | 680 | 681 | def list_gtk_themes(version=GTK_VERSION): 682 | """List GTK themes for a specific version""" 683 | themes_dir = "/usr/share/themes/" 684 | themes = os.listdir(themes_dir) 685 | for each in range(len(themes) - 1, -1, -1): 686 | sub = os.listdir(themes_dir + themes[each]) 687 | if ("gtk-" + version) not in sub: 688 | del themes[each] 689 | elif "default" in themes[each].lower(): 690 | del themes[each] 691 | return themes 692 | 693 | 694 | def get_theming_defaults(): 695 | """Get theming defaults""" 696 | with open(themes_file, "r") as file: 697 | defaults = file.read() 698 | defaults = defaults.split("\n")[1:] 699 | output = {} 700 | for each in range(len(defaults) - 1, -1, -1): 701 | defaults[each] = defaults[each].split("=") 702 | try: 703 | if defaults[each][0] == "gtk-application-prefer-dark-theme": 704 | del defaults[each] 705 | continue 706 | output[defaults[each][0]] = defaults[each][1] 707 | except IndexError: 708 | pass 709 | return output 710 | 711 | 712 | def main(): 713 | """Set up the Matrix desktop wide, make it transparent""" 714 | matrix = Matrix() 715 | matrix.tile("clicked") 716 | matrix.set_decorated(False) 717 | matrix.set_resizable(False) 718 | matrix.move(0, 0) 719 | matrix.set_size_request(width, height) 720 | matrix.set_keep_below(True) 721 | matrix.show_all() 722 | Gtk.main() 723 | 724 | 725 | if __name__ == '__main__': 726 | main() 727 | -------------------------------------------------------------------------------- /usr/share/gcde/handler.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # handler.py 5 | # 6 | # Copyright 2020 Thomas Castleman 7 | # 8 | # This program is free software; you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation; either version 2 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program; if not, write to the Free Software 20 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 21 | # MA 02110-1301, USA. 22 | # 23 | # 24 | """Get Controller Support going""" 25 | import time 26 | import json 27 | import sys 28 | import subprocess 29 | import multiprocessing 30 | import pygame 31 | import controller_support 32 | 33 | 34 | class NoJoysticksError(Exception): 35 | """Error For No Joysticks. Made so special handling may be done""" 36 | pass 37 | 38 | 39 | def initialize(): 40 | """Setup for Controller Support""" 41 | pygame.init() 42 | joysticks = pygame.joystick.get_count() 43 | if joysticks < 1: 44 | raise NoJoysticksError("No Joysticks Found") 45 | # Search terms to determine which controller we are dealing with 46 | xbox = ["xbox", "x-box"] 47 | ps3 = ["ps3", "playstation 3"] 48 | ps4 = ["ps4", "playstation 4"] 49 | switch = ["switch"] 50 | j = pygame.joystick.Joystick(0) 51 | name = j.get_name().lower() 52 | for each in xbox: 53 | if each in name: 54 | return ["xbox", j] 55 | for each in ps3: 56 | if each in name: 57 | return ["ps3", j] 58 | for each in ps4: 59 | if each in name: 60 | return ["ps4", j] 61 | for each in switch: 62 | if each in name: 63 | return ["switch", j] 64 | return ["default", j] 65 | 66 | 67 | def worker(): 68 | """Worker Thread""" 69 | while True: 70 | data = None 71 | try: 72 | # Get appropriate config and object 73 | data = initialize() 74 | except NoJoysticksError: 75 | time.sleep(1) 76 | subprocess.Popen(sys.argv[0]) 77 | exit() 78 | # Get appropriate config 79 | with open("/etc/gcde/controller-mapping.json", "r") as file: 80 | config = json.load(file) 81 | if data[0] == "default": 82 | config = config[config["default"]] 83 | elif ((data[0] == "switch") and ("switch" not in config)): 84 | config = config["ps4"] 85 | data[0] = "ps4" 86 | else: 87 | config = config[data[0]] 88 | button_process = multiprocessing.Process(target=controller_support.buttons.button_handler, 89 | args=[data, config]) 90 | button_process.start() 91 | analog_process = multiprocessing.Process(target=controller_support.analog.run, args=[]) 92 | analog_process.start() 93 | 94 | 95 | if __name__ == "__main__": 96 | worker() 97 | -------------------------------------------------------------------------------- /usr/share/gcde/plugins/README.md: -------------------------------------------------------------------------------- 1 | # Plugins 2 | Plugin support is in-progress. Expect bugs and inconsistent behavior. 3 | 4 | ## Basics 5 | 6 | There are 3 types of plugins: 7 | 8 | * Foreground Plugins 9 | * Background Plugins 10 | * Multi-threaded Plugins 11 | 12 | Foreground Plugins are basic Tiles that provide basic functionality and run in the main thread. 13 | These plugins are easiest to make if you overwrite the Tile.run() function. 14 | 15 | Background Plugins do not need to use the Tile library. 16 | They allow more advanced functionality, but have no GUI component. 17 | These plugins CAN be multithreaded, but if no GUI component is present, 18 | it is still considered a Background Plugin. 19 | 20 | Multi-threaded Plugins have both a background and foreground component. 21 | They will have a GUI Tile that provides much more advanced functionality, such as 22 | system, memory, or network usage; app indicators; messaging clients built into Tiles; 23 | and more! 24 | 25 | 26 | ## Recognition 27 | In order for your plugin to be recognized, it must be registered in`__init__.py`. Otherwise, it will not be imported and used. It may be registered under whatever name you desire, except `example`. This name is reserved for the example plugin and is ignored in most circumstances. 28 | 29 | ## Expected Objects 30 | 31 | ### `plugin_type` 32 | `plugin_type` defines what type of Plugin this is for GCDE. Every plugin MUST have this variable be publicly available. 33 | 34 | * 0 = Foreground Plugin 35 | * 1 = Background Plugin 36 | * \>= 2 = Multi-Threaded Plugin 37 | 38 | This also lets GCDE know how many threads to make. GCDE will pass the thread 39 | number to `run()` so that you can determine which thread does what. 40 | 41 | 42 | ### `run()` 43 | **Foreground Plugins do not need a `run()` function, as GCDE does not anticipate there being one.** 44 | 45 | This `run()` function is used as an entry point for forking off Background and Multi-Threaded Plugins. 46 | 47 | #### args 48 | `run()` receives a single integer as its argument. This integer indicates the thread number `run()` is running as. It is _NOT A PID_. 49 | 50 | 51 | 52 | ### `plugin_setup()` 53 | **ALL** plugins **MUST** have this function. If no setup is needed for your plugin, you can do the following definition: 54 | 55 | ```python 56 | 57 | def plugin_setup(settings): 58 | pass 59 | ``` 60 | 61 | #### args 62 | `plugin_setup()` receives a dictionary as it's argument, `settings`. 63 | The `settings` variable contains either location settings (if a Foreground Plugin), global settings (Background Plugin), 64 | or both (Multi-Threaded Plugin). If both, location settings will be under `settings["loc"]` 65 | global settings will be under `settings["global"]` 66 | 67 | Settings are passed to `plugin_setup()` by value, not by reference. So, attempting 68 | to edit global settings from within a plugin will not affect a running GCDE 69 | instance. 70 | -------------------------------------------------------------------------------- /usr/share/gcde/plugins/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # __init__.py 5 | # 6 | # Copyright 2020 Thomas Castleman 7 | # 8 | # This program is free software; you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation; either version 2 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program; if not, write to the Free Software 20 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 21 | # MA 02110-1301, USA. 22 | # 23 | # 24 | """__init__""" 25 | import plugins._plugin_example as example 26 | import plugins.sys_info as sys_info 27 | -------------------------------------------------------------------------------- /usr/share/gcde/plugins/_plugin_example.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # _plugin_example.py 5 | # 6 | # Copyright 2020 Thomas Castleman 7 | # 8 | # This program is free software; you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation; either version 2 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program; if not, write to the Free Software 20 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 21 | # MA 02110-1301, USA. 22 | # 23 | # 24 | """Plugin Example Code""" 25 | # There are 3 types of plugins: 26 | # * Foreground Plugins 27 | # * Background Plugins 28 | # * Multi-threaded Plugins 29 | # 30 | # Foreground Plugins are basic Tiles that provide basic functionality and run in the main thread. 31 | # These plugins are easiest to make if you overwrite the Tile.run() function. 32 | # 33 | # Background Plugins do not need to use the Tile library. 34 | # They allow more advanced functionality, but have no GUI component. 35 | # These plugins CAN be multithreaded, but if no GUI component is present, 36 | # it is still considered a Background Plugin. 37 | # 38 | # Multi-threaded Plugins have both a background and foreground component. 39 | # They will have a GUI Tile that provides much more advanced functionality, such as 40 | # system, memory, or network usage; app indicators; messaging clients built into Tiles; 41 | # and more! 42 | 43 | # This plugin will be a Foreground Plugin, which allows a user to update their 44 | # system from a single click 45 | 46 | # We need this to update a user's system correctly 47 | import subprocess 48 | # All Foreground Plugins NEED to import the gcde.Tile class 49 | import gcde 50 | 51 | # plugin_type defines what type of Plugin this is for GCDE. 52 | # 0 = Foreground Plugin 53 | # 1 = Background Plugin 54 | # >= 2 = Multi-Threaded Plugin 55 | # This also lets GCDE know how many threads to make. GCDE will pass the thread 56 | # number to run() so that you can determine which thread does what. 57 | PLUGIN_TYPE = 0 58 | 59 | # This could be named run(), but just in case we avoided naming it that 60 | # GCDE will know this is a Foreground Plugin, and thus will expect to get a modified 61 | # Tile back, that's it 62 | def _replacement_run(widget): 63 | """Function to overwrite Tile.run() with""" 64 | subprocess.Popen(["notify-send", "Updating apt cache . . ."]) 65 | subprocess.check_output(["pkexec", "apt", "update"]) 66 | subprocess.Popen(["notify-send", "Upgrading System . . ."]) 67 | subprocess.check_output(["pkexec", "apt", "-y", "upgrade"]) 68 | 69 | 70 | def run(thread_number): 71 | """Demo run() function for Background and Multi-threaded Plugins""" 72 | print("I am Thread %s!" % (thread_number)) 73 | 74 | 75 | def plugin_setup(settings): 76 | """Setup plugin for GCDE""" 77 | # You can start by simply defining a Tile 78 | # Do NOT hard-code your plugin's location in the Matrix 79 | # This could overwrite a pre-existing Tile 80 | # The `settings` variable contains either location settings, or global settings, 81 | # or both. If both, location settings will be under settings["loc"] 82 | # global settings will be under settings["global"] 83 | # 84 | # Settings are passed to plugin_setup() by value, not by reference. So, attempting 85 | # to edit global settings from within a plugin will not affect a running GCDE 86 | # instance. 87 | # 88 | # If you plan to overwrite Tile.run(), leave "exec" blank, but it still must be defined. 89 | updater_settings = {"exec":[""], 90 | "icon":"steam", 91 | "name":"Update System", 92 | "X":settings["X"], 93 | "Y":settings["Y"], 94 | "width":settings["width"], 95 | "height":settings["height"]} 96 | # Make our tile 97 | updater = gcde.tile.new(updater_settings) 98 | # Redefine run() 99 | updater.run = _replacement_run 100 | # return our modified Tile 101 | return updater 102 | -------------------------------------------------------------------------------- /usr/share/gcde/plugins/sys_info.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # sys_info.py 5 | # 6 | # Copyright 2020 Thomas Castleman 7 | # 8 | # This program is free software; you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation; either version 2 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program; if not, write to the Free Software 20 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 21 | # MA 02110-1301, USA. 22 | # 23 | # 24 | """System Info Display""" 25 | import gcde 26 | import psutil 27 | import subprocess 28 | import copy 29 | import gi 30 | gi.require_version('Gtk', '3.0') 31 | from gi.repository import Gtk, Pango, Gdk 32 | 33 | PLUGIN_TYPE=0 34 | 35 | class sys_info_display(gcde.tile.Tile): 36 | """Placeholder class""" 37 | def __init__(self): 38 | """replacement init""" 39 | print("INIT!") 40 | self.settings = {"exec":[], 41 | "icon":None, 42 | "name":"Name", 43 | "X":0, 44 | "Y":0, 45 | "width":0.1, 46 | "height":0.1} 47 | self.obj = Gtk.Label() 48 | 49 | def make(self, global_settings, width, height): 50 | """Make Sys Info Panel""" 51 | mem = list(str(psutil.virtual_memory().total / (1.074 * 10 ** 9))) 52 | points = 0 53 | for each in enumerate(mem): 54 | if mem[each[0] - points] == ".": 55 | points += 1 56 | if points == 3: 57 | mem = "".join(mem)[:each[0] + 1] 58 | break 59 | with open("/proc/cpuinfo", "r") as file: 60 | cpu = file.read().split("\n") 61 | for each in cpu: 62 | if "model name" in each: 63 | cpu = each.split(":")[1] 64 | break 65 | os = subprocess.check_output(["lsb_release", "-sd"]).decode()[:-1] 66 | cpu = "".join(cpu.split("(R)")) 67 | cpu = "".join(cpu.split("(TM)")) 68 | cpu = "".join(cpu.split("CPU ")) 69 | # version = subprocess.check_output(["lsb_release", "-sr"]).decode()[:-1] 70 | user = subprocess.check_output(["whoami"]).decode()[:-1] 71 | disk = subprocess.check_output("df -h -x aufs -x tmpfs -x overlay -x drvfs --total 2>/dev/null | tail -1", 72 | shell=True).decode()[:-3].split(" ") 73 | disk_output = "" 74 | count = 0 75 | for each in disk: 76 | if each != "": 77 | count += 1 78 | if count == 2: 79 | disk_output = each 80 | elif count == 3: 81 | disk_output = each + " / " + disk_output 82 | break 83 | 84 | info = """Welcome, %s! 85 | OS: %s 86 | CPU: %s 87 | RAM: %s GiB 88 | DISK USAGE: %s""" % (user, os, cpu, mem, disk_output) 89 | 90 | self.obj.set_label(info) 91 | self.obj.override_font(Pango.FontDescription("Open Sans %s" % (gcde.common.scale(0.015, 92 | height)))) 93 | self.obj.set_line_wrap(True) 94 | self.obj.set_margin_top(gcde.common.scale(0.0073, height)) 95 | self.obj.set_margin_bottom(gcde.common.scale(0.0073, height)) 96 | self.obj.set_margin_left(gcde.common.scale(0.006, width)) 97 | self.obj.set_margin_right(gcde.common.scale(0.006, width)) 98 | 99 | color = gcde.tile.Tile() 100 | color = color.__get_internal_obj__()[0].props.style.background[0].get_rgba() 101 | color = Gdk.Color.from_floats(color[0], color[1], color[2]) 102 | color = Gdk.RGBA.from_color(color) 103 | self.obj.override_background_color(Gtk.StateFlags.NORMAL, color) 104 | 105 | 106 | 107 | # Remember to clean up after yourselves boys and girls! 108 | del cpu,os,user,mem,disk,disk_output,points,count,each,info,color 109 | 110 | 111 | def run(self, widget): 112 | """run whatever command needed""" 113 | pass 114 | 115 | 116 | def plugin_setup(settings): 117 | """Setup Plugin""" 118 | # Minimal set up. Must be generated on demand 119 | settings["exec"] = [""] 120 | settings["icon"] = "" 121 | settings["name"] = "" 122 | mod = sys_info_display() 123 | mod.__set_settings__(new_settings=settings) 124 | return mod 125 | -------------------------------------------------------------------------------- /usr/share/gcde/restart.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # restart.py 5 | # 6 | # Copyright 2020 Thomas Castleman 7 | # 8 | # This program is free software; you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation; either version 2 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program; if not, write to the Free Software 20 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 21 | # MA 02110-1301, USA. 22 | # 23 | # 24 | """Restart GCDE""" 25 | import gcde 26 | import os 27 | import psutil 28 | import signal 29 | import subprocess 30 | 31 | gcde.common.set_procname("gcde-re") 32 | 33 | pid = None 34 | process_name = "gcde" 35 | 36 | for proc in psutil.process_iter(): 37 | if process_name == proc.name(): 38 | pid = proc.pid 39 | 40 | os.kill(pid, signal.SIGTERM) 41 | try: 42 | subprocess.Popen(["/usr/share/gcde/engine.py"]) 43 | except FileNotFoundError: 44 | subprocess.Popen(["./engine.py"]) 45 | -------------------------------------------------------------------------------- /usr/share/xsessions/gcde.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=GCDE 3 | Comment=GTK+ Console Desktop Environment 4 | Exec=/usr/bin/gcde --start 5 | Icon= 6 | Type=Application 7 | --------------------------------------------------------------------------------