22 |
23 | > This is a rewrite of the original [MCPIL](https://github.com/MCPI-Devs/MCPIL-Legacy) project meant to be less fragile and more compatible.
24 |
25 | ## ⚠️ Deprecation notice
26 | This version of the Launcher is deprecated, and won't work with MCPI-Reborn 2.0. Please use [gMCPIL](https://github.com/MCPI-Revival/gMCPIL) or [jMCPIL](https://github.com/MCPI-Revival/jMCPIL) instead.
27 |
28 | ## Getting started
29 |
30 | ### Prerequisites
31 | - Python >= 3.7.x
32 | - [MCPI-Reborn](https://gitea.thebrokenrail.com/TheBrokenRail/minecraft-pi-reborn)
33 |
34 | ### System Requirements
35 | Debian/Raspbian Buster/Ubuntu 20.04 Or Higher
36 |
37 | ### Installation
38 | [](https://github.com/Botspot/pi-apps)
39 |
40 | ## Features
41 | + Switch between Minecraft Pi and MCPE GUI/Touch GUI
42 | + Multiplayer and multiplayer username changing
43 | + Render distance toggle
44 | + Coming soon: More stuff
45 |
46 | ## Changelog
47 | [View Changelog](CHANGELOG.md)
48 |
49 | ## Debian Packages
50 | [GitHub Releases](https://github.com/MCPI-Revival/MCPIL/releases/latest)
51 |
52 | ## Compiling/Packaging
53 | ```sh
54 | git clone --recurse-submodules https://github.com/MCPI-Revival/MCPIL.git
55 | cd MCPIL
56 | ./scripts/package.sh
57 | ```
58 | Then install the resulting .deb package, eg:
59 | ```sudo apt install ./out/mcpil_*_.deb'```
60 | NOTE: You may need to run `pip3 install ttkthemes` if you are getting a missing module error upon run.
61 |
--------------------------------------------------------------------------------
/.github/workflows/codeql-analysis.yml:
--------------------------------------------------------------------------------
1 | # For most projects, this workflow file will not need changing; you simply need
2 | # to commit it to your repository.
3 | #
4 | # You may wish to alter this file to override the set of languages analyzed,
5 | # or to provide custom queries or build logic.
6 | name: "CodeQL"
7 |
8 | on:
9 | push:
10 | pull_request:
11 | schedule:
12 | - cron: '0 19 * * 3'
13 |
14 | jobs:
15 | analyze:
16 | name: Analyze
17 | runs-on: ubuntu-latest
18 |
19 | strategy:
20 | fail-fast: false
21 | matrix:
22 | # Override automatic language detection by changing the below list
23 | # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
24 | language: ['python']
25 | # Learn more...
26 | # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
27 |
28 | steps:
29 | - name: Checkout repository
30 | uses: actions/checkout@v2
31 | with:
32 | # We must fetch at least the immediate parents so that if this is
33 | # a pull request then we can checkout the head.
34 | fetch-depth: 2
35 |
36 | # If this run was triggered by a pull request event, then checkout
37 | # the head of the pull request instead of the merge commit.
38 | - run: git checkout HEAD^2
39 | if: ${{ github.event_name == 'pull_request' }}
40 |
41 | # Initializes the CodeQL tools for scanning.
42 | - name: Initialize CodeQL
43 | uses: github/codeql-action/init@v1
44 | with:
45 | languages: ${{ matrix.language }}
46 | # If you wish to specify custom queries, you can do so here or in a config file.
47 | # By default, queries listed here will override any specified in a config file.
48 | # Prefix the list here with "+" to use these queries and those in the config file.
49 | # queries: ./path/to/local/query, your-org/your-repo/queries@main
50 |
51 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
52 | # If this step fails, then you should remove it and run the build manually (see below)
53 | - name: Autobuild
54 | uses: github/codeql-action/autobuild@v1
55 |
56 | # ℹ️ Command-line programs to run using the OS shell.
57 | # 📚 https://git.io/JvXDl
58 |
59 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
60 | # and modify them (or add more) to build your code if your project
61 | # uses a compiled language
62 |
63 | #- run: |
64 | # make bootstrap
65 | # make release
66 |
67 | - name: Perform CodeQL Analysis
68 | uses: github/codeql-action/analyze@v1
69 |
--------------------------------------------------------------------------------
/src/launcher.py:
--------------------------------------------------------------------------------
1 | # Licensed Uniquely Under MIT Because This File Might Be Useful For Other Projects
2 | #
3 | # MIT License
4 | #
5 | # Copyright (c) 2020 TheBrokenRail
6 | #
7 | # Permission is hereby granted, free of charge, to any person obtaining a copy
8 | # of this software and associated documentation files (the "Software"), to deal
9 | # in the Software without restriction, including without limitation the rights
10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | # copies of the Software, and to permit persons to whom the Software is
12 | # furnished to do so, subject to the following conditions:
13 | #
14 | # The above copyright notice and this permission notice shall be included in all
15 | # copies or substantial portions of the Software.
16 |
17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 | # SOFTWARE.
24 |
25 | import subprocess
26 | import os
27 | from typing import Dict
28 |
29 | # Feature Parse Failure
30 | def _parse_fail():
31 | raise Exception('Unable To Parse Features')
32 |
33 | # Get All Available MCPI-Docker Features
34 | def _get_features() -> Dict[str, bool]:
35 | out: Dict[str, bool] = {}
36 |
37 | # Block X11 If Using Older MCPi-Docker
38 | env = os.environ.copy()
39 | if 'DISPLAY' in env:
40 | del env['DISPLAY']
41 |
42 | result: subprocess.CompletedProcess = subprocess.run(['/usr/bin/minecraft-pi', '--print-features'], capture_output=True, text=True, env=env)
43 | result.check_returncode()
44 |
45 | stage = 0
46 | skip = 0
47 | escaped = False
48 | current_default = False
49 | current_name = ''
50 | for part in str(result.stdout):
51 | if skip > 0:
52 | skip -= 1
53 | continue
54 | if stage == 0:
55 | if part == 'T':
56 | current_default = True
57 | skip = 3
58 | stage += 1
59 | elif part == 'F':
60 | current_default = False
61 | skip = 4
62 | stage += 1
63 | elif part != ' ' and part != '\n':
64 | _parse_fail()
65 | elif stage == 1:
66 | if part == '\'':
67 | stage += 1
68 | elif stage == 2:
69 | is_escaped = False
70 | if part == '\\':
71 | escaped = True
72 | elif escaped:
73 | is_escaped = True
74 | escaped = False
75 | if part == 'n':
76 | # Hide Newline
77 | part = ''
78 | elif part == 't':
79 | # Add Tab
80 | part = '\t'
81 | if part == '\'' and not is_escaped:
82 | out[current_name] = current_default
83 | current_name = ''
84 | current_default = False
85 | stage = 0
86 | else:
87 | current_name += part
88 | else:
89 | _parse_fail()
90 |
91 | return out
92 |
93 | # All Available MCPI-Docker Features
94 | AVAILABLE_FEATURES = _get_features()
95 | print('Loaded Available Features: ' + str(AVAILABLE_FEATURES))
96 |
97 | # Run MCPI-Docker
98 | def run(features: list, render_distance: str, username: str) -> subprocess.Popen:
99 | env = os.environ.copy()
100 | env['MCPI_FEATURES'] = '|'.join(features)
101 | env['MCPI_RENDER_DISTANCE'] = render_distance
102 | env['MCPI_USERNAME'] = username
103 | return subprocess.Popen(['/usr/bin/minecraft-pi'], env=env, preexec_fn=os.setsid)
104 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/mcpil.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 | #
4 | # mcpil.py
5 | #
6 | # Copyright 2020-2021 Alvarito050506
7 | # Copyright 2020-2021 StealthHydrac/StealthHydra179/a1ma
8 | # Copyright 2020-2021 JumpeR6790
9 | # Copyright 2021 Boba
10 | # Copyright 2021 LEHAtupointow
11 | #
12 | # This program is free software; you can redistribute it and/or modify
13 | # it under the terms of the GNU General Public License as published by
14 | # the Free Software Foundation; version 2 of the License.
15 | #
16 | # This program is distributed in the hope that it will be useful,
17 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 | # GNU General Public License for more details.
20 | #
21 | # You should have received a copy of the GNU General Public License
22 | # along with this program; if not, write to the Free Software
23 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
24 | # MA 02110-1301, USA.
25 | #
26 |
27 | import signal
28 |
29 | from typing import Dict
30 |
31 | from proxy.proxy import Proxy
32 |
33 | import launcher
34 | import config
35 |
36 | from splashes import SPLASHES
37 | import random
38 |
39 | from os import kill, killpg, getpid, getpgid
40 | import platform
41 | import threading
42 |
43 | from subprocess import Popen
44 |
45 | from tkinter import *
46 | from tkinter import ttk
47 | from tkinter.messagebox import showerror
48 |
49 | from ttkthemes import ThemedTk
50 |
51 | import webbrowser
52 |
53 | import random
54 | from datetime import date
55 |
56 | '''
57 | Global variables.
58 | '''
59 |
60 | # Root Window
61 | window: Tk
62 |
63 | # Constants
64 | DESCRIPTIONS = [
65 | 'Classic Minecraft Pi Edition.\n(Not Recommended)\nAll optional features disabled.',
66 | 'Modded Minecraft Pi Edition.\nDefault MCPI-Reborn optional features without Touch GUI.',
67 | 'Minecraft Pocket Edition.\n(Recommended)\nDefault MCPI-Reborn optional features.',
68 | 'Optimized Minecraft Pocket Edition.\nDefault MCPI-Reborn optional features with lower quality graphics.',
69 | 'Custom Profile.\nModify its settings in the Features tab.'
70 | ]
71 | RENDER_DISTANCES = [
72 | 'Far',
73 | 'Normal',
74 | 'Short',
75 | 'Tiny',
76 | ]
77 |
78 | # Current Profile
79 | current_profile_selection = 2
80 | # Current Profile Description Text
81 | description_text: StringVar
82 |
83 | # Launch Button
84 | launch_button: ttk.Button
85 |
86 | # Settings
87 | current_render_distance: StringVar
88 | current_username: StringVar
89 | current_hide_launcher: IntVar
90 |
91 | # Proxy Settings
92 | current_ip: StringVar
93 | current_port: StringVar
94 |
95 | # Custom Profile Features
96 | current_features = []
97 | feature_widgets: Dict[str, ttk.Checkbutton] = {}
98 |
99 | # Current Process
100 | current_process: Popen = None
101 |
102 | # Current Config
103 | current_config = {}
104 |
105 | # Proxy
106 | proxy_lock = threading.Lock()
107 | proxy_thread: threading.Thread = None
108 | proxy = Proxy()
109 |
110 | '''
111 | Helper classes.
112 | '''
113 |
114 | # Hyper-Link
115 | class HyperLink(ttk.Label):
116 | def __init__(self, parent, url, text=None, cursor=None, *args, **kwargs):
117 | self.url = url
118 | super().__init__(parent, text=(text or url), cursor=(cursor or 'hand2'), *args, **kwargs)
119 | self.bind('', self.web_open)
120 |
121 | def web_open(self, event):
122 | return webbrowser.open(self.url)
123 |
124 | # Frame With Scrollbar
125 | class ScrollableFrame(ttk.Frame):
126 | def __init__(self, root):
127 | super().__init__(root)
128 |
129 | self.grid_columnconfigure(0, weight=1)
130 | self.grid_rowconfigure(0, weight=1)
131 |
132 | self.canvas = Canvas(self)
133 | scrollbar = ttk.Scrollbar(self, orient='vertical', command=self.canvas.yview)
134 | self.canvas.configure(yscrollcommand=scrollbar.set)
135 |
136 | scrollbar.grid(row=0, column=1, sticky='NSE')
137 | self.canvas.grid(row=0, column=0, sticky='NSEW')
138 |
139 | self.scrollable_frame = ttk.Frame(self.canvas)
140 | scrollable_frame_id = self.canvas.create_window(0, 0, window=self.scrollable_frame, anchor='nw')
141 |
142 | def configure_scrollable_frame(event):
143 | self.canvas.configure(scrollregion=self.canvas.bbox("all"))
144 |
145 | self.scrollable_frame.bind('', configure_scrollable_frame)
146 |
147 | def configure_canvas(event):
148 | self.canvas.itemconfig(scrollable_frame_id, width=event.width, height=(self.scrollable_frame.winfo_height() if self.scrollable_frame.winfo_height() > event.height else event.height))
149 |
150 | self.canvas.bind('', configure_canvas)
151 |
152 |
153 |
154 | class ToolTip(object):
155 | def __init__(self, widget, text=None):
156 | self.widget = widget
157 | self.text = text
158 | widget.bind("", self.mouse_enter)
159 | widget.bind("", self.mouse_leave)
160 |
161 |
162 | def mouse_enter(self, _event):
163 | self.show_tooltip()
164 |
165 | def mouse_leave(self, _event_):
166 | self.hide_tooltip()
167 |
168 | def show_tooltip(self):
169 | x_left = self.widget.winfo_rooty()
170 | y_top = self.widget.winfo_rootx() - 18
171 | self.tip_window = Toplevel(self.widget)
172 | self.tip_window.overrideredirect(True)
173 | self.tip_window.geometry("+%d+%d" % (x_left, y_top))
174 | label = Label(self.tip_window, text=self.text, justify=LEFT, background="#ffffe0", relief=SOLID, borderwidth=1, font=("tahoma", 8, "normal"))
175 | label.pack(ipadx=1)
176 |
177 | def hide_tooltip(self):
178 | if self.tip_window:
179 | self.tip_window.destroy()
180 |
181 | '''
182 | Helper functions and back-end.
183 | '''
184 |
185 | # Get Path Base-Name
186 | def basename(path):
187 | return path.split('/')[-1]
188 |
189 | # Convert Dict Of Features To List Of Enabled Features
190 | def features_dict_to_list(features: Dict[str, bool]):
191 | out = []
192 | for key in features:
193 | if features[key]:
194 | out.append(key)
195 | return out
196 | # Get Features From Selected Profile
197 | def get_features() -> list:
198 | global current_profile_selection, current_features
199 | if current_profile_selection == 0:
200 | # No Features
201 | return []
202 | if current_profile_selection == 1:
203 | # Default Features Minus Touch GUI
204 | mods = launcher.AVAILABLE_FEATURES.copy()
205 | mods['Touch GUI'] = False
206 | return features_dict_to_list(mods)
207 | if current_profile_selection == 2:
208 | # Default Features
209 | return features_dict_to_list(launcher.AVAILABLE_FEATURES.copy())
210 | if current_profile_selection == 3:
211 | # Default Features With Lower Quality Graphics
212 | mods = launcher.AVAILABLE_FEATURES.copy()
213 | mods['Fancy Graphics'] = False
214 | mods['Smooth Lighting'] = False
215 | mods['Animated Water'] = False
216 | mods['Disable gui_blocks Atlas'] = False
217 | return features_dict_to_list(mods)
218 | if current_profile_selection == 4:
219 | # Custom Features (Use Features Tab)
220 | return current_features
221 | # Impossible
222 | raise ValueError
223 | # Update Features From Widgets
224 | def update_features():
225 | global current_features, feature_widgets
226 | current_features = []
227 | for key in feature_widgets:
228 | if feature_widgets[key].instate(['selected']):
229 | current_features.append(key)
230 |
231 | # Launch Minecraft
232 | def launch():
233 | global current_render_distance, current_username, current_process
234 | launch_button.state(['disabled'])
235 | if current_process is None or current_process.poll() is not None:
236 | current_process = launcher.run(get_features(), current_render_distance.get(), current_username.get())
237 | return 0
238 |
239 | # Hide/Show Window
240 | window_shown = True
241 | def hide_window():
242 | global window, window_shown
243 | if window_shown:
244 | window.withdraw()
245 | window_shown = False
246 |
247 | def show_window():
248 | global window, window_shown
249 | if not window_shown:
250 | window.update()
251 | window.deiconify()
252 | window_shown = True
253 |
254 | # Update Launch Button
255 | def update_launch_button():
256 | global launch_button, current_hide_launcher
257 |
258 | game_closed = current_process is None or current_process.poll() is not None
259 |
260 | if (not game_closed) and current_hide_launcher.get():
261 | hide_window()
262 | else:
263 | show_window()
264 |
265 | if game_closed and launch_button.instate(['disabled']):
266 | launch_button.state(['!disabled'])
267 |
268 | launch_button.after(10, update_launch_button)
269 |
270 | # Close MCPIL
271 | def quit():
272 | global current_process
273 | if current_process is not None and current_process.poll() is None:
274 | killpg(getpgid(current_process.pid), signal.SIGTERM)
275 |
276 | window.destroy()
277 | kill(getpid(), signal.SIGTERM)
278 | return 0
279 |
280 | # Start/Stop Proxy
281 | def update_proxy():
282 | global proxy, proxy_thread, proxy_lock, current_ip, current_port
283 | proxy_lock.acquire()
284 | if proxy_thread is not None:
285 | proxy.stop()
286 | proxy_thread.join()
287 | try:
288 | proxy.set_option("src_addr", current_ip.get())
289 | proxy.set_option("src_port", int(current_port.get()))
290 | proxy_thread = threading.Thread(target=lambda *args: proxy.run())
291 | proxy_thread.start()
292 | except ValueError:
293 | # Invalid Port
294 | pass
295 | proxy_lock.release()
296 |
297 | # Save/Load Config
298 | def load():
299 | global current_config, current_render_distance, current_username, current_features, feature_widgets, current_hide_launcher
300 | current_config = config.load()
301 | current_render_distance.set(current_config['general']['render-distance'])
302 | current_username.set(current_config['general']['username'])
303 | current_features = current_config['general']['custom-features'].copy()
304 | for key in feature_widgets:
305 | feature_widgets[key].state(['!alternate'])
306 | if key in current_features:
307 | feature_widgets[key].state(['selected'])
308 | else:
309 | feature_widgets[key].state(['!selected'])
310 | current_hide_launcher.set(int(current_config['general']['hide-launcher']))
311 | current_ip.set(current_config['server']['ip'])
312 | current_port.set(current_config['server']['port'])
313 | update_proxy()
314 | def save():
315 | global current_config, current_render_distance, current_username, current_features, current_hide_launcher
316 | current_config['general']['render-distance'] = current_render_distance.get()
317 | current_config['general']['username'] = current_username.get()
318 | current_config['general']['custom-features'] = current_features.copy()
319 | current_config['general']['hide-launcher'] = bool(current_hide_launcher.get())
320 | current_config['server']['ip'] = current_ip.get()
321 | current_config['server']['port'] = current_port.get()
322 | config.save(current_config)
323 |
324 | '''
325 | Event handlers.
326 | '''
327 |
328 | def select_version(version: int):
329 | global current_profile_selection
330 | try:
331 | current_profile_selection = int(version)
332 | description_text.set(DESCRIPTIONS[current_profile_selection])
333 | except IndexError:
334 | pass
335 | except Exception as err:
336 | return 'Critical error {}'.format(err)
337 | def on_select_versions(event):
338 | select_version(event.widget.selection()[0])
339 | return 0
340 |
341 | '''
342 | Tabs.
343 | '''
344 |
345 | # Play Tab
346 | def play_tab(parent):
347 | global description_text, launch_button
348 |
349 | tab = ttk.Frame(parent)
350 |
351 | today = date.today()
352 | randomnumber = random.randint(1,100)
353 | if today.month == 4 and today.day == 1:
354 | title = ttk.Label(tab, text='Banana Launcher')
355 | else:
356 | if randomnumber == 1:
357 | title = ttk.Label(tab, text='Minceraft Pi Launcher')
358 | else:
359 | title = ttk.Label(tab, text='Minecraft Pi Launcher')
360 |
361 |
362 | title.config(font=('', 24))
363 | title.grid(row=0)
364 |
365 | splash = random.choice(SPLASHES)
366 | if today.month == 4 and today.day == 1:
367 | splash = "Happy B-Day Alvarito050506"
368 | elif today.month == 8 and today.day == 24:
369 | splash = "Happy Birthday LEHAtupointow"
370 | elif today == 2 and today.day == 20:
371 | splash = "Happy Birthday Boba"
372 | elif today == 7 and today.day == 15:
373 | splash = "Happy Birthday RPiNews!"
374 | elif today.month == 5 and today.day == 5:
375 | splash = random.randint(["I shifted them a bit", "We're moving to gMCPIL or jMCPIL", "Should have come back", "YOU DON'T LIKE POTATOES?"])
376 |
377 | splash_text = ttk.Label(tab, text=splash, foreground='yellow')
378 | splash_text.grid(row=1, pady=4)
379 |
380 | choose_text = ttk.Label(tab, text='Choose a Minecraft version to launch.')
381 | choose_text.grid(row=2, pady=(0, 16))
382 |
383 | versions_frame = ttk.Frame(tab)
384 |
385 | tab.columnconfigure(0, weight=1)
386 | versions_frame.columnconfigure(0, weight=1)
387 | tab.rowconfigure(2, weight=1)
388 | versions_frame.rowconfigure(0, weight=1)
389 |
390 | description_text = StringVar(versions_frame)
391 | description_text_label = ttk.Label(versions_frame, textvariable=description_text, wraplength=256, anchor='center', justify='center')
392 | description_text_label.tooltip = ToolTip(description_text_label, text="The description of the profile shows up here.")
393 |
394 | versions = ttk.Treeview(versions_frame, selectmode='browse', show='tree')
395 | versions.insert('', 'end', text='Classic MCPI', iid=0)
396 | versions.insert('', 'end', text='Modded MCPI', iid=1)
397 | versions.insert('', 'end', text='Modded MCPE', iid=2)
398 | versions.insert('', 'end', text='Optimized MCPE', iid=3)
399 | versions.insert('', 'end', text='Custom Profile', iid=4)
400 | versions.bind('<>', on_select_versions)
401 | versions.grid(row=0, column=0, sticky='NSEW')
402 | versions.selection_set(2)
403 | select_version(versions.selection()[0])
404 | versions.tooltip = ToolTip(versions, text="Select a profile from this menu.")
405 |
406 | description_text_label.grid(row=0, column=1, pady=48, padx=48, sticky='NSE')
407 |
408 | versions_frame.grid(row=3, sticky='NSEW')
409 |
410 | launch_frame = ttk.Frame(tab)
411 | launch_button = ttk.Button(launch_frame, text='Launch', command=launch, cursor="shuttle")
412 | launch_button.tooltip = ToolTip(launch_button, text="Click this to launch MCPI-Reborn")
413 | launch_button.pack(side=RIGHT, anchor=S)
414 | launch_frame.grid(row=4, sticky='SE')
415 |
416 | launch_button.after(0, update_launch_button)
417 |
418 | return tab
419 |
420 | def settings_tab(parent):
421 | global current_render_distance, current_username, current_hide_launcher
422 |
423 | tab = ttk.Frame(parent)
424 |
425 | tab.rowconfigure(0, weight=1)
426 | tab.columnconfigure(0, weight=1)
427 |
428 | main_frame = ttk.Frame(tab)
429 |
430 | main_frame.columnconfigure(1, weight=1)
431 |
432 | render_distance_label = ttk.Label(main_frame, text='Render Distance:')
433 | render_distance_label.grid(row=0, column=0, padx=6, pady=6, sticky='W')
434 | current_render_distance = StringVar(main_frame)
435 | render_distance = ttk.Combobox(main_frame, textvariable=current_render_distance, values=RENDER_DISTANCES, width=24)
436 | render_distance.state(['readonly'])
437 | render_distance.tooltip = ToolTip(render_distance, text="Select the chunk render distance here")
438 | render_distance.grid(row=0, column=1, padx=6, pady=6, sticky='EW')
439 |
440 | username_label = ttk.Label(main_frame, text='Username:')
441 | username_label.grid(row=1, column=0, padx=6, pady=6, sticky='W')
442 | current_username = StringVar(main_frame)
443 | username = ttk.Entry(main_frame, width=24, textvariable=current_username)
444 | username.tooltip = ToolTip(username, text="Username for servers")
445 |
446 | username.grid(row=1, column=1, padx=6, pady=6, sticky='EW')
447 |
448 | hide_launcher_label = ttk.Label(main_frame, text='Hide Launcher While Game Is Open:')
449 | hide_launcher_label.grid(row=2, column=0, padx=6, pady=6, sticky='W')
450 | current_hide_launcher = IntVar(main_frame)
451 | hide_launcher = ttk.Checkbutton(main_frame, variable=current_hide_launcher)
452 | hide_launcher.tooltip = ToolTip(hide_launcher, text="Check this if you want the launcher to dissapear when you start the game.")
453 | hide_launcher.grid(row=2, column=1, padx=6, pady=6, sticky='EW')
454 |
455 | main_frame.grid(row=0, sticky='NEW')
456 |
457 | save_frame = ttk.Frame(tab)
458 | save_button = ttk.Button(save_frame, text='Save', command=save)
459 | save_button.pack(side=RIGHT, anchor=S)
460 | save_button.tooltip = ToolTip(save_button, text="Save your options before launching.")
461 | save_frame.grid(row=1, sticky='SE')
462 |
463 | return tab
464 |
465 | def features_tab(parent):
466 | global feature_widgets
467 |
468 | tab = ttk.Frame(parent)
469 |
470 | tab.rowconfigure(0, weight=1)
471 | tab.columnconfigure(0, weight=1)
472 |
473 | main_frame = ScrollableFrame(tab)
474 |
475 | main_frame.scrollable_frame.columnconfigure(1, weight=1)
476 |
477 | row = 0
478 | for key in launcher.AVAILABLE_FEATURES:
479 | check = ttk.Checkbutton(main_frame.scrollable_frame, command=update_features, text=key)
480 | check.tooltip = ToolTip(check, text=f"Check this if you want {key} to be enabled.")
481 | check.pack(padx=6, pady=6, anchor='w')
482 | feature_widgets[key] = check
483 |
484 | row += 1
485 |
486 | main_frame.grid(row=0, sticky='NSEW')
487 |
488 | save_frame = ttk.Frame(tab)
489 | save_button = ttk.Button(save_frame, text='Save', command=save)
490 | save_button.tooltip = ToolTip(save_button, text="Save your options before launching.")
491 | save_button.pack(side=RIGHT, anchor=S)
492 | save_frame.grid(row=1, sticky='SE')
493 |
494 | return tab
495 |
496 | def multiplayer_tab(parent):
497 | global current_ip, current_port
498 |
499 | tab = ttk.Frame(parent)
500 |
501 | tab.rowconfigure(0, weight=1)
502 | tab.columnconfigure(0, weight=1)
503 |
504 | main_frame = ttk.Frame(tab)
505 |
506 | main_frame.columnconfigure(1, weight=1)
507 |
508 | ip_label = ttk.Label(main_frame, text='IP:')
509 | ip_label.grid(row=0, column=0, padx=6, pady=6, sticky='W')
510 | current_ip = StringVar(main_frame)
511 | current_ip.trace('w', lambda *args: update_proxy())
512 | ip = ttk.Entry(main_frame, width=24, textvariable=current_ip)
513 | ip.tooltip = ToolTip(ip, text="This is the IP of a multiplayer server.")
514 | ip.grid(row=0, column=1, padx=6, pady=6, sticky='EW')
515 |
516 | port_label = ttk.Label(main_frame, text='Port:')
517 | port_label.grid(row=1, column=0, padx=6, pady=6, sticky='W')
518 | current_port = StringVar(main_frame)
519 | current_port.trace('w', lambda *args: update_proxy())
520 | port = ttk.Entry(main_frame, width=24, textvariable=current_port)
521 | port.tooltip = ToolTip(port, text="Set the port.")
522 | port.grid(row=1, column=1, padx=6, pady=6, sticky='EW')
523 |
524 | main_frame.grid(row=0, sticky='NEW')
525 |
526 | save_frame = ttk.Frame(tab)
527 | save_button = ttk.Button(save_frame, text='Save', command=save)
528 | save_button.tooltip = ToolTip(save_button, text="Save your options before launching.")
529 | save_button.pack(side=RIGHT, anchor=S)
530 | save_frame.grid(row=1, sticky='SE')
531 |
532 | return tab
533 |
534 | # Get Version
535 | def get_version() -> str:
536 | try:
537 | with open('/opt/mcpil/VERSION', 'r') as file:
538 | return 'v' + file.readline().strip()
539 | except OSError:
540 | # File Does Not Exists Or Is Inaccessible
541 | pass
542 | return 'Unknown Version'
543 |
544 | def about_tab(parent):
545 | tab = ttk.Frame(parent)
546 |
547 | main_frame = ttk.Frame(tab)
548 |
549 | main_frame.columnconfigure(0, weight=1)
550 |
551 | title = ttk.Label(main_frame, text='Minecraft Pi Launcher', anchor='center')
552 | title.config(font=('', 24))
553 | title.grid(row=0, sticky='NSEW')
554 |
555 | version = ttk.Label(main_frame, text=get_version(), anchor='center')
556 | version.config(font=('', 10))
557 | version.grid(row=1, sticky='NSEW')
558 |
559 | authors = HyperLink(main_frame, 'https://github.com/MCPI-Revival/MCPIL/graphs/contributors', text='by all its contributors', anchor='center')
560 | authors.config(font=('', 10))
561 | authors.grid(row=2, sticky='NSEW')
562 |
563 | url = HyperLink(main_frame, 'https://github.com/MCPI-Revival/MCPIL', anchor='center', foreground='blue')
564 | url.config(font=('', 10))
565 | url.grid(row=3, sticky='NSEW')
566 |
567 | main_frame.pack(expand=True)
568 |
569 | return tab
570 |
571 | def main():
572 | if platform.system() != 'Linux':
573 | showerror('Error', 'Linux Is Required')
574 | return 1
575 |
576 | global window
577 |
578 | window = ThemedTk(theme='equilux', className='mcpil')
579 | window.title('MCPIL')
580 | window.geometry('512x400')
581 | window.resizable(True, True)
582 |
583 | # Set icon in taskbar
584 | window.iconphoto(True, PhotoImage(file="/usr/share/pixmaps/mcpil.png"))
585 |
586 | tabs = ttk.Notebook(window)
587 | tabs.add(play_tab(tabs), text='Play')
588 | tabs.add(features_tab(tabs), text='Features')
589 | tabs.add(multiplayer_tab(tabs), text='Multiplayer')
590 | tabs.add(settings_tab(tabs), text='Settings')
591 | tabs.add(about_tab(tabs), text='About')
592 | tabs.pack(fill=BOTH, expand=True)
593 |
594 | load()
595 | save()
596 |
597 | window.wm_protocol('WM_DELETE_WINDOW', quit)
598 | signal.signal(signal.SIGINT, lambda *args: quit())
599 |
600 | try:
601 | window.mainloop()
602 | except KeyboardInterrupt:
603 | quit()
604 |
605 | return 0
606 |
607 | if __name__ == '__main__':
608 | sys.exit(main())
609 |
--------------------------------------------------------------------------------