Initial commit
19
.gitignore
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
/Makefile
|
||||
/qrc_resources.cpp
|
||||
/.qmake.stash
|
||||
/.project
|
||||
/.cproject
|
||||
/.settings
|
||||
/parallel-launcher
|
||||
/parallel-launcher-lsjs
|
||||
/parallel-launcher.exe
|
||||
/parallel-launcher-lsjs.exe
|
||||
/build
|
||||
/debug
|
||||
/release
|
||||
/win32
|
||||
/win64
|
||||
/System Volume Information
|
||||
/parallel-launcher_setup_win32.exe
|
||||
/parallel-launcher_setup_win64.exe
|
||||
/doc
|
299
LICENSE
Executable file
@@ -0,0 +1,299 @@
|
||||
Parallel Launcher
|
||||
Copyright (C) 2020 Matt Pharoah
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/>
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
87
README.md
Normal file
@@ -0,0 +1,87 @@
|
||||
# Parallel Launcher
|
||||
|
||||
An open source launcher for the ParallelN64 emulator.
|
||||
|
||||
TODO
|
||||
|
||||
## Building Parallel Launcher from Source
|
||||
|
||||
### Building on Linux
|
||||
|
||||
- Install the dev dependencies. For Debian-based systems this can be done with:
|
||||
`sudo apt install build-essential qt5-qmake qtdeclarative5-dev libsdl2-dev`
|
||||
- Build the makefile with `./qmake-release.sh` (or `./qmake-debug.sh` for the debug build)
|
||||
- Run `make` to build
|
||||
|
||||
If you are editing the UI, you will also want to install either Qt Designer or Qt Creator
|
||||
|
||||
### Building on Windows
|
||||
|
||||
- Firstly, you're going to need to install and setup msys2
|
||||
- Download and install from https://www.msys2.org/
|
||||
- Follow the 7 steps listed on the linked website to setup the package manager
|
||||
- Now, we need to install mingw-w64 and Qt
|
||||
- Open MSYS2
|
||||
- Install gcc, make, and qmake
|
||||
- 64-bit: `pacman -S mingw-w64-x86_64-gcc mingw-w64-x86_64-make mingw-w64-x86_64-qt5`
|
||||
- 32-bit: `pacman -S mingw-w64-i686-gcc mingw-w64-i686-make mingw-w64-i686-qt5`
|
||||
- [Optional] Install gdb:
|
||||
- 64-bit: `pacman -S mingw-w64-x86_64-gdb`
|
||||
- 32-bit: `pacman -S mingw-w64-i686-gdb`
|
||||
- Next, we need to add the required dlls to the directory where we will build
|
||||
- Open MSYS2 MinGW 64-bit (or MSYS2 MinGW 32-bit if you are building 32-bit)
|
||||
- Navigate to the source directory
|
||||
- Create a win64 (or win32 for 32-bit) directory: `mkdir win64`
|
||||
- Copy the following dll files from `C:\msys64\mingw64\bin` (or `C:\msys64\mingw32\bin` for 32-bit builds) into your newly created win64/win32 directory:
|
||||
- libbrotildec.dll
|
||||
- libbrotilicommon.dll
|
||||
- libbz2-1.dll
|
||||
- libdouble-conversion.dll
|
||||
- libfreetype-6.dll
|
||||
- libgcc_s_seh-1.dll
|
||||
- libglib-2.0-0.dll (or libgcc_s_dw2-1.dll for 32-bit)
|
||||
- libgraphite2.dll
|
||||
- libharfbuzz-0.dll
|
||||
- libiconv-2.dll
|
||||
- libicudt67.dll
|
||||
- libicuin67.dll
|
||||
- libicuuc67.dll
|
||||
- libintl-8.dll
|
||||
- libpcre-1.dll
|
||||
- libpcre2-16-0.dll
|
||||
- libpng16-16.dll
|
||||
- libstdc++-6.dll
|
||||
- libwinpthread-1.dll
|
||||
- libzstd.dll
|
||||
- zlib1.dll
|
||||
- Manually install the SDL2 development library
|
||||
- Download the SDL2 development libraries for Windows MinGW release from https://www.libsdl.org/download-2.0.php
|
||||
- Extract the tarball and go to the `x86_64-w64-mingw32` folder (or the `i686-w64-mingw32` folder for 32-bit)
|
||||
- Copy `bin/SDL2.dll` to your win64/win32 directory with the rest of the dlls you added in the previous steps
|
||||
- Merge the `bin`, `include`, `lib`, and `share` folders into your `C:\msys64\mingw64` directory (or `C:\msys64\mingw32` for 32-bit)
|
||||
- Now, we will build the app for the first time.
|
||||
- Run `./qmake-windows.sh` (or `./qmake-windows-debug.sh` for a debug build) to create a makefile
|
||||
- Run `mingw32-make` to build. Compilation takes much longer on Windows than on Linux, so be patient
|
||||
- [Optional] Build the theme plugins
|
||||
- Clone the repository hosted at https://github.com/qt/qtstyleplugins into some temporary directory
|
||||
- Build the plugins using qmake and make
|
||||
- Add a `style` folder inside your `win32`/`win64` folder and place the plugin dlls inside
|
||||
- Finally, we need to add the last few things Qt requires on Windows
|
||||
- Move the built application into the win64/win32 folder: `mv parallel-launcher.exe win64`
|
||||
- Enter the win64/win32 directory: `cd win64`
|
||||
- Run `windeployqt parallel-launcher.exe`
|
||||
- Now that the application has been built, we can build the installer
|
||||
- Download and install Inno Setup (https://jrsoftware.org/isinfo.php)
|
||||
- Open `win64-installer.iss` (or `win32-installer.iss` for a 32-bit build) in the source directory
|
||||
- Click the green play button to build the installer
|
||||
|
||||
If you are editing the UI, you will also want to install either Qt Designer or Qt Creator
|
||||
|
||||
Note that you will only get output on the command line when running Parallel
|
||||
Launcher through MSYS2 MinGW. You will not see any command line output if you
|
||||
use Command Prompt or Powershell to open it. Additionally, sometimes even with
|
||||
MSYS2 you still don't get output when you should.
|
||||
|
||||
You can debug on Windows by using gdb in MSYS2, though it is EXTREMELY slow on
|
||||
Windows. If the error you are debugging is not specific to Windows, I strongly
|
||||
recommend debugging on Linux for your own sanity.
|
66
app.pro
Normal file
@@ -0,0 +1,66 @@
|
||||
QT += core gui widgets
|
||||
TEMPLATE = app
|
||||
|
||||
TARGET = "parallel-launcher"
|
||||
|
||||
CONFIG += qt object_parallel_to_source
|
||||
CONFIG(debug, debug|release){
|
||||
OBJECTS_DIR = "build/debug/obj"
|
||||
MOC_DIR = "build/debug/moc"
|
||||
UI_DIR = "build/debug/ui"
|
||||
DEFINES += DEBUG
|
||||
!win32 {
|
||||
QMAKE_CXXFLAGS += -rdynamic
|
||||
}
|
||||
}
|
||||
CONFIG(release, debug|release){
|
||||
OBJECTS_DIR = "build/release/obj"
|
||||
MOC_DIR = "build/release/moc"
|
||||
UI_DIR = "build/release/ui"
|
||||
}
|
||||
|
||||
QMAKE_CXXFLAGS += -std=c++17 -Werror
|
||||
DEFINES += QT_DEPRECATED_WARNINGS
|
||||
|
||||
SOURCES += src/main.cpp src/core/*.cpp src/ui/*.cpp src/polyfill/*.cpp src/input/*.cpp
|
||||
HEADERS += src/types.hpp src/core/*.hpp src/ui/*.hpp src/polyfill/*.hpp src/input/*.hpp
|
||||
FORMS += src/ui/designer/*.ui
|
||||
RESOURCES = data/resources.qrc
|
||||
LIBS += -lstdc++fs -lSDL2 -lSDL2main -lpthread
|
||||
|
||||
win32 {
|
||||
CONFIG += embed_manifest_exe
|
||||
VERSION = 1.0.0
|
||||
RC_ICONS = "data\\appicon.ico"
|
||||
RC_LANG = "EN-CA"
|
||||
QMAKE_TARGET_COMPANY = "Matt Pharoah"
|
||||
QMAKE_TARGET_COPYRIGHT = "GNU GENERAL PUBLIC LICENSE Version 2"
|
||||
QMAKE_TARGET_PRODUCT = "Parallel Launcher"
|
||||
|
||||
INCLUDEPATH += .
|
||||
INCLUDEPATH += $MOC_DIR
|
||||
|
||||
LIBS += -lole32 -luuid
|
||||
}
|
||||
|
||||
!win32 {
|
||||
QMAKE_LFLAGS += -no-pie -fno-pie
|
||||
|
||||
program.path = "/usr/local/bin"
|
||||
program.files = "parallel-launcher"
|
||||
|
||||
lsjs.path = "/usr/local/bin"
|
||||
lsjs.files = "parallel-launcher-lsjs"
|
||||
|
||||
icon.path = "/usr/local/share/parallel-launcher"
|
||||
icon.files = "data/appicon.svg"
|
||||
|
||||
launcher.path = "/usr/local/share/applications"
|
||||
launcher.files = "parallel-launcher.desktop"
|
||||
|
||||
launcher_post.path = "."
|
||||
launcher_post.files = ""
|
||||
launcher_post.extra = "update-desktop-database /usr/local/share/applications"
|
||||
|
||||
INSTALLS += program lsjs icon launcher launcher_post
|
||||
}
|
BIN
data/appicon.ico
Normal file
After Width: | Height: | Size: 112 KiB |
80
data/appicon.svg
Normal file
@@ -0,0 +1,80 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
id="svg4675"
|
||||
viewBox="0 0 389.61821 389.61821"
|
||||
height="256"
|
||||
width="256"
|
||||
version="1.1">
|
||||
<metadata
|
||||
id="metadata4681">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs4679" />
|
||||
<g
|
||||
id="g4673"
|
||||
transform="matrix(2.4919039,0,0,2.4919039,0,-73.645788)">
|
||||
<g
|
||||
transform="translate(-16.773187,-13.369163)"
|
||||
id="g5369">
|
||||
<g
|
||||
id="g5350">
|
||||
<path
|
||||
id="path4641"
|
||||
d="m 72.5,84.9 22.6,-8.6 22.6,8.6 v 0 l -22.6,8.6 z"
|
||||
style="fill:#ffc001;stroke:none" />
|
||||
<path
|
||||
id="path5228"
|
||||
d="M 72.499999,84.900002 72.399999,176.4 95,185.9 95.1,93.500001 Z"
|
||||
style="fill:#069330;fill-opacity:1;stroke:none;stroke-width:0.61075634px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
<path
|
||||
id="path5230"
|
||||
d="M 117.6,176.4 95,185.9 95.1,93.500001 117.7,84.900002 Z"
|
||||
style="fill:#011da9;fill-opacity:1;stroke:none;stroke-width:0.61075634px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
</g>
|
||||
<g
|
||||
id="g5345">
|
||||
<path
|
||||
id="path4645"
|
||||
d="m 41.1,56.3 21.3,8.2 -21.3,8.2 -21.3,-8.2 v 0 z"
|
||||
style="fill:#ffc001;stroke:none" />
|
||||
<path
|
||||
id="path5226"
|
||||
d="m 41.1,163.2 -21.4,-9 0.1,-89.7 21.3,8.200003 z"
|
||||
style="fill:#069330;fill-opacity:1;stroke:none;stroke-width:0.61075634px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
<path
|
||||
style="fill:#011da9;fill-opacity:1;stroke:none;stroke-width:0.61075634px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M 41.1,163.2 V 72.700005 L 62.4,64.500002 V 154.2 Z"
|
||||
id="path5234" />
|
||||
</g>
|
||||
<g
|
||||
id="g5355">
|
||||
<path
|
||||
id="path4643"
|
||||
d="m 148.9,56.3 21.3,8.2 -21.3,8.2 -21.3,-8.2 v 0 z"
|
||||
style="fill:#ffc001;stroke:none" />
|
||||
<path
|
||||
id="path5232"
|
||||
d="M 148.9,163.2 V 72.700003 L 170.2,64.5 v 89.7 z"
|
||||
style="fill:#011da9;fill-opacity:1;stroke:none;stroke-width:0.61075634px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
<path
|
||||
style="fill:#069330;fill-opacity:1;stroke:none;stroke-width:0.61075634px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 148.9,163.2 -21.4,-9 0.1,-89.700002 21.3,8.200003 z"
|
||||
id="path5236" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.9 KiB |
183
data/fallback-icons/LICENSE
Normal file
@@ -0,0 +1,183 @@
|
||||
|
||||
Copyright (C) 2014 Uri Herrera <uri_herrera@nitrux.in> and others
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 3 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
|
||||
This version of the GNU Lesser General Public License incorporates
|
||||
the terms and conditions of version 3 of the GNU General Public
|
||||
License, supplemented by the additional permissions listed below.
|
||||
|
||||
0. Additional Definitions.
|
||||
|
||||
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||
General Public License.
|
||||
|
||||
"The Library" refers to a covered work governed by this License,
|
||||
other than an Application or a Combined Work as defined below.
|
||||
|
||||
An "Application" is any work that makes use of an interface provided
|
||||
by the Library, but which is not otherwise based on the Library.
|
||||
Defining a subclass of a class defined by the Library is deemed a mode
|
||||
of using an interface provided by the Library.
|
||||
|
||||
A "Combined Work" is a work produced by combining or linking an
|
||||
Application with the Library. The particular version of the Library
|
||||
with which the Combined Work was made is also called the "Linked
|
||||
Version".
|
||||
|
||||
The "Minimal Corresponding Source" for a Combined Work means the
|
||||
Corresponding Source for the Combined Work, excluding any source code
|
||||
for portions of the Combined Work that, considered in isolation, are
|
||||
based on the Application, and not on the Linked Version.
|
||||
|
||||
The "Corresponding Application Code" for a Combined Work means the
|
||||
object code and/or source code for the Application, including any data
|
||||
and utility programs needed for reproducing the Combined Work from the
|
||||
Application, but excluding the System Libraries of the Combined Work.
|
||||
|
||||
1. Exception to Section 3 of the GNU GPL.
|
||||
|
||||
You may convey a covered work under sections 3 and 4 of this License
|
||||
without being bound by section 3 of the GNU GPL.
|
||||
|
||||
2. Conveying Modified Versions.
|
||||
|
||||
If you modify a copy of the Library, and, in your modifications, a
|
||||
facility refers to a function or data to be supplied by an Application
|
||||
that uses the facility (other than as an argument passed when the
|
||||
facility is invoked), then you may convey a copy of the modified
|
||||
version:
|
||||
|
||||
a) under this License, provided that you make a good faith effort to
|
||||
ensure that, in the event an Application does not supply the
|
||||
function or data, the facility still operates, and performs
|
||||
whatever part of its purpose remains meaningful, or
|
||||
|
||||
b) under the GNU GPL, with none of the additional permissions of
|
||||
this License applicable to that copy.
|
||||
|
||||
3. Object Code Incorporating Material from Library Header Files.
|
||||
|
||||
The object code form of an Application may incorporate material from
|
||||
a header file that is part of the Library. You may convey such object
|
||||
code under terms of your choice, provided that, if the incorporated
|
||||
material is not limited to numerical parameters, data structure
|
||||
layouts and accessors, or small macros, inline functions and templates
|
||||
(ten or fewer lines in length), you do both of the following:
|
||||
|
||||
a) Give prominent notice with each copy of the object code that the
|
||||
Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
4. Combined Works.
|
||||
|
||||
You may convey a Combined Work under terms of your choice that,
|
||||
taken together, effectively do not restrict modification of the
|
||||
portions of the Library contained in the Combined Work and reverse
|
||||
engineering for debugging such modifications, if you also do each of
|
||||
the following:
|
||||
|
||||
a) Give prominent notice with each copy of the Combined Work that
|
||||
the Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
c) For a Combined Work that displays copyright notices during
|
||||
execution, include the copyright notice for the Library among
|
||||
these notices, as well as a reference directing the user to the
|
||||
copies of the GNU GPL and this license document.
|
||||
|
||||
d) Do one of the following:
|
||||
|
||||
0) Convey the Minimal Corresponding Source under the terms of this
|
||||
License, and the Corresponding Application Code in a form
|
||||
suitable for, and under terms that permit, the user to
|
||||
recombine or relink the Application with a modified version of
|
||||
the Linked Version to produce a modified Combined Work, in the
|
||||
manner specified by section 6 of the GNU GPL for conveying
|
||||
Corresponding Source.
|
||||
|
||||
1) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (a) uses at run time
|
||||
a copy of the Library already present on the user's computer
|
||||
system, and (b) will operate properly with a modified version
|
||||
of the Library that is interface-compatible with the Linked
|
||||
Version.
|
||||
|
||||
e) Provide Installation Information, but only if you would otherwise
|
||||
be required to provide such information under section 6 of the
|
||||
GNU GPL, and only to the extent that such information is
|
||||
necessary to install and execute a modified version of the
|
||||
Combined Work produced by recombining or relinking the
|
||||
Application with a modified version of the Linked Version. (If
|
||||
you use option 4d0, the Installation Information must accompany
|
||||
the Minimal Corresponding Source and Corresponding Application
|
||||
Code. If you use option 4d1, you must provide the Installation
|
||||
Information in the manner specified by section 6 of the GNU GPL
|
||||
for conveying Corresponding Source.)
|
||||
|
||||
5. Combined Libraries.
|
||||
|
||||
You may place library facilities that are a work based on the
|
||||
Library side by side in a single library together with other library
|
||||
facilities that are not Applications and are not covered by this
|
||||
License, and convey such a combined library under terms of your
|
||||
choice, if you do both of the following:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work based
|
||||
on the Library, uncombined with any other library facilities,
|
||||
conveyed under the terms of this License.
|
||||
|
||||
b) Give prominent notice with the combined library that part of it
|
||||
is a work based on the Library, and explaining where to find the
|
||||
accompanying uncombined form of the same work.
|
||||
|
||||
6. Revised Versions of the GNU Lesser General Public License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions
|
||||
of the GNU Lesser General Public License from time to time. Such new
|
||||
versions will be similar in spirit to the present version, but may
|
||||
differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Library as you received it specifies that a certain numbered version
|
||||
of the GNU Lesser General Public License "or any later version"
|
||||
applies to it, you have the option of following the terms and
|
||||
conditions either of that published version or of any later version
|
||||
published by the Free Software Foundation. If the Library as you
|
||||
received it does not specify a version number of the GNU Lesser
|
||||
General Public License, you may choose any version of the GNU Lesser
|
||||
General Public License ever published by the Free Software Foundation.
|
||||
|
||||
If the Library as you received it specifies that a proxy can decide
|
||||
whether future versions of the GNU Lesser General Public License shall
|
||||
apply, that proxy's public statement of acceptance of any version is
|
||||
permanent authorization for you to choose that version for the
|
||||
Library.
|
13
data/fallback-icons/application-menu.svg
Normal file
@@ -0,0 +1,13 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#232629;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="M 2 3 L 2 5 L 14 5 L 14 3 L 2 3 z M 2 7 L 2 9 L 14 9 L 14 7 L 2 7 z M 2 11 L 2 13 L 14 13 L 14 11 L 2 11 z "
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 443 B |
14
data/fallback-icons/delete.svg
Normal file
@@ -0,0 +1,14 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-NegativeText {
|
||||
color:#da4453;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path
|
||||
style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
class="ColorScheme-NegativeText"
|
||||
d="m5 2v2h1v-1h4v1h1v-2h-5zm-3 3v1h2v8h8v-8h2v-1zm3 1h6v7h-6z"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 417 B |
13
data/fallback-icons/dialog-cancel.svg
Normal file
@@ -0,0 +1,13 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#4d4d4d;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="M 8 2 C 4.6862928 2 2 4.6862997 2 8 C 2 11.3137 4.6862928 14 8 14 C 11.313707 14 14 11.3137 14 8 C 14 4.6862997 11.313707 2 8 2 z M 8 3 C 10.761424 3 13 5.2385763 13 8 C 13 9.199635 12.548037 10.263384 11.84375 11.125 L 4.875 4.15625 C 5.7366165 3.4519627 6.8003651 3 8 3 z M 4.15625 4.875 L 11.125 11.84375 C 10.263384 12.548037 9.199635 13 8 13 C 5.2385763 13 3 10.761424 3 8 C 3 6.8003651 3.4519627 5.7366165 4.15625 4.875 z "
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 766 B |
17
data/fallback-icons/dialog-close.svg
Normal file
@@ -0,0 +1,17 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#232629;
|
||||
}
|
||||
.ColorScheme-NegativeText {
|
||||
color:#da4453;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path
|
||||
style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
class="ColorScheme-NegativeText"
|
||||
d="M 8,2 A 6,6 0 0 0 2,8 6,6 0 0 0 8,14 6,6 0 0 0 14,8 6,6 0 0 0 8,2 Z M 5.70703,5 8,7.29297 10.29297,5 11,5.70703 8.70703,8 11,10.29297 10.29297,11 8,8.70703 5.70703,11 5,10.29297 7.29297,8 5,5.70703 5.70703,5 Z"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 626 B |
29
data/fallback-icons/dialog-error.svg
Normal file
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
<svg width="64" version="1.1" xmlns="http://www.w3.org/2000/svg" height="64" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape">
|
||||
<defs id="defs3811">
|
||||
<linearGradient inkscape:collect="always" id="linearGradient4344">
|
||||
<stop style="stop-color:#ed868d" id="stop4346"/>
|
||||
<stop offset="1" style="stop-color:#fbe6e8" id="stop4348"/>
|
||||
</linearGradient>
|
||||
<linearGradient inkscape:collect="always" id="linearGradient4435">
|
||||
<stop style="stop-color:#c61423" id="stop4437"/>
|
||||
<stop offset="1" style="stop-color:#dc2b41" id="stop4439"/>
|
||||
</linearGradient>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient4400" id="linearGradient4394" y1="19.999998" x1="19.999998" y2="44" x2="43.999996" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1 0 0 1 0 -7)"/>
|
||||
<linearGradient inkscape:collect="always" id="linearGradient4400">
|
||||
<stop style="stop-color:#020303" id="stop4402"/>
|
||||
<stop offset="1" style="stop-color:#424649;stop-opacity:0" id="stop4404"/>
|
||||
</linearGradient>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient4344" id="linearGradient4179" y1="201.93361" y2="177.93361" x2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1 0 0 1 0 -7)"/>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient4435" id="linearGradient4416" y1="43.999989" y2="6.999989" x2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-1.4054053 0 0 1.4054053 804.69502 154.09579)"/>
|
||||
</defs>
|
||||
<metadata id="metadata3814"/>
|
||||
<g inkscape:label="Layer 1" inkscape:groupmode="layer" id="layer1" transform="matrix(1 0 0 1 -736.85718 -157.93361)">
|
||||
<path inkscape:connector-curvature="0" style="fill:url(#linearGradient4416);fill-rule:evenodd" id="path4445" d="m 794.85718,163.93361 0,37.94594 -25.29727,0 -14.05412,14.05406 0,-14.05406 -12.64861,0 0,-37.94594 z"/>
|
||||
<path style="fill:url(#linearGradient4394);opacity:0.2;fill-rule:evenodd" id="path4389" d="M 44 14.414062 L 21.53125 37.03125 L 30.574219 46.074219 L 32.703125 43.945312 L 49.703125 43.945312 A 26 26 0 0 0 57.783203 28.197266 L 44 14.414062 z " transform="matrix(1 0 0 1 736.85718 157.93361)"/>
|
||||
<path inkscape:connector-curvature="0" style="fill:url(#linearGradient4179)" id="rect4168" d="m 779.44312,170.93361 1.41406,1.41406 -10.58594,10.58594 10.58594,10.58594 -1.41406,1.41406 -10.58594,-10.58594 -10.58594,10.58594 -1.41406,-1.41406 10.58594,-10.58594 -10.58594,-10.58594 1.41406,-1.41406 10.58594,10.58594 10.58594,-10.58594 z"/>
|
||||
<path style="fill:#aa111e;fill-rule:evenodd" id="path4256" d="M 6 42.945312 L 6 43.945312 L 18.648438 43.945312 L 18.648438 42.945312 L 6 42.945312 z M 32.703125 42.945312 L 18.648438 57 L 18.648438 58 L 32.703125 43.945312 L 58 43.945312 L 58 42.945312 L 32.703125 42.945312 z " transform="matrix(1 0 0 1 736.85718 157.93361)"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.9 KiB |
29
data/fallback-icons/dialog-information.svg
Normal file
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
<svg width="64" version="1.1" xmlns="http://www.w3.org/2000/svg" height="64" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape">
|
||||
<defs id="defs3811">
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient4143" id="linearGradient4416" y1="43.999989" y2="6.999989" x2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-1.4054053 0 0 1.4054053 804.69502 154.09579)"/>
|
||||
<linearGradient inkscape:collect="always" id="linearGradient4143">
|
||||
<stop style="stop-color:#197cf1" id="stop4145"/>
|
||||
<stop offset="1" style="stop-color:#20bcfa" id="stop4147"/>
|
||||
</linearGradient>
|
||||
<linearGradient inkscape:collect="always" id="linearGradient4290">
|
||||
<stop style="stop-color:#7cbaf8" id="stop4292"/>
|
||||
<stop offset="1" style="stop-color:#f4fcff" id="stop4294"/>
|
||||
</linearGradient>
|
||||
<linearGradient inkscape:collect="always" id="linearGradient4144" xlink:href="#linearGradient4290" y1="29.999973" y2="2" gradientUnits="userSpaceOnUse" x2="0" gradientTransform="matrix(2 0 0 2 735.85719 152.93361)"/>
|
||||
<linearGradient inkscape:collect="always" id="linearGradient4227">
|
||||
<stop style="stop-color:#292c2f" id="stop4229"/>
|
||||
<stop offset="1" style="stop-opacity:0" id="stop4231"/>
|
||||
</linearGradient>
|
||||
<linearGradient inkscape:collect="always" id="linearGradient4191" xlink:href="#linearGradient4227" y1="9" y2="23" x1="9.00001" gradientUnits="userSpaceOnUse" x2="23.00004" gradientTransform="matrix(2 0 0 2 -0.99999 -5)"/>
|
||||
</defs>
|
||||
<metadata id="metadata3814"/>
|
||||
<g inkscape:label="Layer 1" inkscape:groupmode="layer" id="layer1" transform="matrix(1 0 0 1 -736.85718 -157.93361)">
|
||||
<path inkscape:connector-curvature="0" style="fill:url(#linearGradient4416);fill-rule:evenodd" id="path4445" d="m 794.85718,163.93361 0,37.94594 -25.29727,0 -14.05412,14.05406 0,-14.05406 -12.64861,0 0,-37.94594 z"/>
|
||||
<path style="fill:url(#linearGradient4191);opacity:0.2;fill-rule:evenodd" id="path4184" d="M 33 13 L 29 17 L 33 21 L 29 37 L 35.945312 43.945312 L 58 43.945312 L 58 38 L 33 13 z " transform="matrix(1 0 0 1 736.85718 157.93361)"/>
|
||||
<path inkscape:connector-curvature="0" style="fill:url(#linearGradient4144)" id="rect4133" d="m 765.85719,170.93361 0,4 4,0 0,-4 z m 0,8 0,16 4,0 0,-16 z"/>
|
||||
<path style="fill:#0c69cf;fill-rule:evenodd" id="path4256" d="M 6 42.945312 L 6 43.945312 L 18.648438 43.945312 L 18.648438 42.945312 L 6 42.945312 z M 32.703125 42.945312 L 18.648438 57 L 18.648438 58 L 32.703125 43.945312 L 58 43.945312 L 58 42.945312 L 32.703125 42.945312 z " transform="matrix(1 0 0 1 736.85718 157.93361)"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.7 KiB |
13
data/fallback-icons/dialog-ok.svg
Normal file
@@ -0,0 +1,13 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#4d4d4d;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="M 13.273438 3.5 L 5.6367188 11.060547 L 2.7265625 8.1796875 L 2 8.9003906 L 4.9082031 11.779297 L 5.6367188 12.5 L 6.7265625 11.419922 L 14 4.2207031 L 13.273438 3.5 z "
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 504 B |
29
data/fallback-icons/dialog-warning.svg
Normal file
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
<svg width="64" version="1.1" xmlns="http://www.w3.org/2000/svg" height="64" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape">
|
||||
<defs id="defs3811">
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient4143-6" id="linearGradient4416" y1="43.999989" y2="6.999989" x2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-1.4054053 0 0 1.4054053 804.69502 154.09579)"/>
|
||||
<linearGradient inkscape:collect="always" id="linearGradient4144" xlink:href="#linearGradient4155" y1="29.999973" y2="2" gradientUnits="userSpaceOnUse" x2="0" gradientTransform="matrix(2 0 0 -2 0 53)"/>
|
||||
<linearGradient inkscape:collect="always" id="linearGradient4155">
|
||||
<stop style="stop-color:#fcd994" id="stop4157"/>
|
||||
<stop offset="1" style="stop-color:#fff6e1" id="stop4159"/>
|
||||
</linearGradient>
|
||||
<linearGradient inkscape:collect="always" id="linearGradient4143-6">
|
||||
<stop style="stop-color:#faae2a" id="stop4145-8"/>
|
||||
<stop offset="1" style="stop-color:#ffc35a" id="stop4147-0"/>
|
||||
</linearGradient>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient4227" id="linearGradient4191" y1="12.999999" x1="17" y2="41" x2="45" gradientUnits="userSpaceOnUse"/>
|
||||
<linearGradient inkscape:collect="always" id="linearGradient4227">
|
||||
<stop style="stop-color:#292c2f" id="stop4229"/>
|
||||
<stop offset="1" style="stop-opacity:0" id="stop4231"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<metadata id="metadata3814"/>
|
||||
<g inkscape:label="Layer 1" inkscape:groupmode="layer" id="layer1" transform="matrix(1 0 0 1 -736.85718 -157.93361)">
|
||||
<path inkscape:connector-curvature="0" style="fill:url(#linearGradient4416);fill-rule:evenodd" id="path4445" d="m 794.85718,163.93361 0,37.94594 -25.29727,0 -14.05412,14.05406 0,-14.05406 -12.64861,0 0,-37.94594 z"/>
|
||||
<path style="fill:#e59305;fill-rule:evenodd" id="path4256" d="M 6 42.945312 L 6 43.945312 L 18.648438 43.945312 L 18.648438 42.945312 L 6 42.945312 z M 32.703125 42.945312 L 18.648438 57 L 18.648438 58 L 32.703125 43.945312 L 58 43.945312 L 58 42.945312 L 32.703125 42.945312 z " transform="matrix(1 0 0 1 736.85718 157.93361)"/>
|
||||
<path style="fill:url(#linearGradient4191);opacity:0.2;fill-rule:evenodd" id="path4186" d="M 32 13 L 32 15 L 44 35.785156 L 44.701172 37 L 19 37 L 29.324219 47.324219 L 32.703125 43.945312 L 58 43.945312 L 58 39 L 32 13 z " transform="matrix(1 0 0 1 736.85718 157.93361)"/>
|
||||
<path inkscape:connector-curvature="0" style="fill:url(#linearGradient4144)" id="rect4133" d="M 32,13 18.853516,37 19.992188,37 20,36.986328 20,37 l 24,0 0,-0.01367 0.0078,0.01367 1.138672,0 z M 32,15.076172 43.460938,36 20.539062,36 Z M 31,20 l 0,10 2,0 0,-10 z m 0,12 0,2 2,0 0,-2 z" transform="matrix(1 0 0 1 736.85718 157.93361)"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.8 KiB |
13
data/fallback-icons/document-edit.svg
Normal file
@@ -0,0 +1,13 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#4d4d4d;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="M 10.398438 2 L 5.2871094 7.1113281 L 2 10.398438 L 2 14 L 5.6015625 14 L 14 5.6015625 L 10.398438 2 z M 8.3496094 5.4902344 L 10.509766 7.6503906 L 7.3359375 10.826172 L 7.3359375 10.150391 L 6.3222656 10.171875 L 5.2871094 10.171875 L 5.2871094 9.1367188 L 5.2871094 8.5507812 L 6.7285156 7.1113281 L 8.3496094 5.4902344 z M 4.2734375 9.5644531 L 4.2734375 11.185547 L 5.3085938 11.185547 L 6.3007812 11.185547 L 6.3222656 11.837891 L 5.2421875 12.919922 L 3.8007812 12.919922 L 3.0800781 12.199219 L 3.0800781 10.757812 L 4.2734375 9.5644531 z "
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 883 B |
13
data/fallback-icons/document-open.svg
Normal file
@@ -0,0 +1,13 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#4d4d4d;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="M 2 2 L 2 3 L 2 6 L 2 7 L 2 13 L 2 14 L 14 14 L 14 13 L 14 6 L 14 5 L 14 4 L 9.0078125 4 L 7.0078125 2 L 7 2.0078125 L 7 2 L 3 2 L 2 2 z M 3 3 L 6.5917969 3 L 7.59375 4 L 7 4 L 7 4.0078125 L 6.9921875 4 L 4.9921875 6 L 3 6 L 3 3 z M 3 7 L 13 7 L 13 13 L 3 13 L 3 7 z "
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 603 B |
13
data/fallback-icons/document-save-as.svg
Normal file
@@ -0,0 +1,13 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#232629;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="M 2 2 L 2 14 L 3 14 L 4 14 L 7 14 L 7 13 L 6 13 L 5 13 L 5 10 L 6 10 L 7 10 L 8 10 L 8 9 L 7 9 L 5 9 L 3.96875 9 L 3.96875 13 L 3 13 L 3 3 L 4 3 L 5 3 L 5 6 L 5 7 L 7 7 L 11 7 L 11 6 L 11 3 L 11.28125 3 L 13 4.71875 L 13 5 L 13 7 L 14 7 L 14 4.28125 L 11.71875 2 L 11.6875 2 L 11 2 L 4 2 L 3 2 L 2 2 z M 6 3 L 7.90625 3 L 7.90625 6 L 6 6 L 6 3 z M 12 8 L 11 9 L 10 10 L 8 12 L 8 13 L 8 14 L 10 14 L 11 13 L 12 12 L 13 11 L 14 10 L 12 8 z M 11.689453 9.6894531 L 12.28125 10.28125 L 10.59375 11.96875 L 10.59375 11.984375 L 9.3125 13.28125 L 8.71875 12.6875 L 11.689453 9.6894531 z "
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 917 B |
13
data/fallback-icons/document-save.svg
Normal file
@@ -0,0 +1,13 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#4d4d4d;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="M 2 2 L 2 14 L 3 14 L 4 14 L 10 14 L 11 14 L 12 14 L 14 14 L 14 4.28125 L 11.71875 2 L 11.6875 2 L 11 2 L 4 2 L 3 2 L 2 2 z M 3 3 L 4 3 L 5 3 L 5 6 L 5 7 L 11 7 L 11 6 L 11 3 L 11.28125 3 L 13 4.71875 L 13 5 L 13 13 L 12 13 L 12 9 L 11 9 L 5 9 L 3.96875 9 L 3.96875 13 L 3 13 L 3 3 z M 6 3 L 7.90625 3 L 7.90625 6 L 6 6 L 6 3 z M 5 10 L 6 10 L 10 10 L 11 10 L 11 13 L 10 13 L 6 13 L 5 13 L 5 10 z "
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 733 B |
13
data/fallback-icons/go-next-skip.svg
Normal file
@@ -0,0 +1,13 @@
|
||||
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
||||
<style
|
||||
type="text/css"
|
||||
id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#232629;
|
||||
}
|
||||
</style>
|
||||
<g class="ColorScheme-Text" fill="currentColor">
|
||||
<path d="M9.707 8l-6 6L3 13.293 8.293 8 3 2.707 3.707 2z"/>
|
||||
<path d="M13.707 8l-6 6L7 13.293 12.293 8 7 2.707 7.707 2z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 416 B |
13
data/fallback-icons/group.svg
Normal file
@@ -0,0 +1,13 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#232629;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="M 11.5 2 A 2.5 2.5 0 0 0 9 4.5 A 2.5 2.5 0 0 0 11.5 7 A 2.5 2.5 0 0 0 14 4.5 A 2.5 2.5 0 0 0 11.5 2 z M 5.5 4 A 2.5 2.5 0 0 0 3 6.5 A 2.5 2.5 0 0 0 5.5 9 A 2.5 2.5 0 0 0 8 6.5 A 2.5 2.5 0 0 0 5.5 4 z M 11.646484 8 A 3.4999979 4 0 0 0 8.4921875 10.273438 A 4.5 5 0 0 1 9.6171875 12 L 14.146484 12 L 15.146484 12 A 3.4999979 4 0 0 0 11.646484 8 z M 11.646484 9 A 2.5 3 0 0 1 14 11 L 9.2929688 11 A 2.5 3 0 0 1 11.646484 9 z M 5.5 10 A 3.4999979 4 0 0 0 2 14 L 3 14 L 8 14 L 9 14 A 3.4999979 4 0 0 0 5.5 10 z M 5.5 11 A 2.5 3 0 0 1 7.8535156 13 L 3.1464844 13 A 2.5 3 0 0 1 5.5 11 z "
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 916 B |
11
data/fallback-icons/input-gamepad.svg
Normal file
@@ -0,0 +1,11 @@
|
||||
<svg height="16" width="16" xmlns="http://www.w3.org/2000/svg">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#232629;
|
||||
}
|
||||
</style>
|
||||
<g fill="currentColor" class="ColorScheme-Text">
|
||||
<path d="m5 6v1h-1v1h1v1h1v-1h1v-1h-1v-1zm5.5-.1a.6.6 0 0 0 -.6.6.6.6 0 0 0 .6.6.6.6 0 0 0 .6-.6.6.6 0 0 0 -.6-.6zm-1 1a.6.6 0 0 0 -.6.6.6.6 0 0 0 .6.6.6.6 0 0 0 .6-.6.6.6 0 0 0 -.6-.6zm2 0a.6.6 0 0 0 -.6.6.6.6 0 0 0 .6.6.6.6 0 0 0 .6-.6.6.6 0 0 0 -.6-.6zm-1 1a.6.6 0 0 0 -.6.6.6.6 0 0 0 .6.6.6.6 0 0 0 .6-.6.6.6 0 0 0 -.6-.6z"/>
|
||||
<path d="m4 4a2 4 0 0 0 -2 4v2.5a1.5 1.5 0 0 0 2.56 1.06l.944-.943a1.5 1.5 0 0 0 .996.383 1.5 1.5 0 0 0 1.5-1.5 1.5 1.5 0 0 0 2.498 1.12l.941.94a1.5 1.5 0 0 0 2.561-1.06v-2.5a2 4 0 0 0 -2-4zm0 1h8a1 3 0 0 1 1 3v2.5a.5.5 0 0 1 -.854.354l-1.853-1.854h-.293v.5a.5.5 0 0 1 -1 0v-.5h-2v.5a.5.5 0 0 1 -1 0v-.5h-.293l-1.853 1.854a.5.5 0 0 1 -.854-.354v-2.5a1 3 0 0 1 1-3z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 967 B |
13
data/fallback-icons/list-add.svg
Normal file
@@ -0,0 +1,13 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#4d4d4d;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="M 7 3.0058594 L 7 8 L 2 8 L 2 8.9980469 L 7 8.9980469 L 7 14.007812 L 8 14.007812 L 8 8.9980469 L 13 8.9980469 L 13 8 L 8 8 L 8 3.0058594 L 7 3.0058594 z "
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 490 B |
8
data/fallback-icons/media-playback-start.svg
Normal file
@@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#232629;
|
||||
}
|
||||
</style>
|
||||
<path d="m2 2v12l12-6z" class="ColorScheme-Text" fill="currentColor"/>
|
||||
</svg>
|
After Width: | Height: | Size: 275 B |
8
data/fallback-icons/media-seek-forward.svg
Normal file
@@ -0,0 +1,8 @@
|
||||
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#232629;
|
||||
}
|
||||
</style>
|
||||
<path d="m8 2v12l7-6zm-7 0v12l7-6z" class="ColorScheme-Text" fill="currentColor"/>
|
||||
</svg>
|
After Width: | Height: | Size: 287 B |
17
data/fallback-icons/process-stop.svg
Normal file
@@ -0,0 +1,17 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#232629;
|
||||
}
|
||||
.ColorScheme-NegativeText {
|
||||
color:#da4453;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path
|
||||
style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
class="ColorScheme-NegativeText"
|
||||
d="M 8 2 A 6 5.9999852 0 0 0 4.5273438 3.1132812 L 5.2460938 3.8320312 A 5 5 0 0 1 8 3 A 5 5 0 0 1 13 8 A 5 5 0 0 1 12.167969 10.753906 L 12.884766 11.470703 A 6 5.9999852 0 0 0 14 8 A 6 5.9999852 0 0 0 8 2 z M 3.1152344 4.5292969 A 6 5.9999852 0 0 0 2 8 A 6 5.9999852 0 0 0 8 14 A 6 5.9999852 0 0 0 11.472656 12.886719 L 10.753906 12.167969 A 5 5 0 0 1 8 13 A 5 5 0 0 1 3 8 A 5 5 0 0 1 3.8320312 5.2460938 L 3.1152344 4.5292969 z M 5 7 L 5 9 L 11 9 L 11 7 L 5 7 z "
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 881 B |
8
data/fallback-icons/view-refresh.svg
Normal file
@@ -0,0 +1,8 @@
|
||||
<svg version="1.1" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<style type="text/css" id="current-color-scheme">.ColorScheme-Text {
|
||||
color:#232629;
|
||||
}</style>
|
||||
</defs>
|
||||
<path class="ColorScheme-Text" fill="currentColor" d="m14 8c0 1.1088173-0.319333 2.140071-0.84375 3.03125l-2.6875-2.6875 0.71875-0.71875 1.625 1.625c0.05109-0.19534 0.097716-0.3898623 0.125-0.59375 0.007789-0.0524063 0.025184-0.1032787 0.03125-0.15625 0.01707-0.1680854 0.03125-0.327411 0.03125-0.5 0-2.761424-2.238576-5-5-5-0.243024 0-0.4855082 0.02845-0.71875 0.0625-0.1362493 0.019955-0.2738032 0.031786-0.40625 0.0625-0.2865815 0.06695-0.547624 0.166647-0.8125 0.28125-0.030675 0.013272-0.063399 0.017376-0.09375 0.03125-0.09166 0.040961-0.163333 0.108255-0.25 0.15625-8e-3 0.00445-0.02305-0.00433-0.03125 0l-0.71875-0.75c0.891179-0.524417 1.922432-0.84375 3.03125-0.84375 3.313709 0 6 2.686293 6 6zm-2.96875 5.15625c-0.891179 0.524417-1.922432 0.84375-3.03125 0.84375-3.313709 0-6-2.686293-6-6 0-1.1088173 0.319333-2.140071 0.84375-3.03125l0.75 0.75 1.90625 1.9375-0.6875 0.6875-1.625-1.59375c-0.05109 0.19534-0.09772 0.3898623-0.125 0.59375-0.0078 0.052406-0.02518 0.1032787-0.03125 0.15625-0.01707 0.1680854-0.03125 0.327411-0.03125 0.5s0.01418 0.3319146 0.03125 0.5c0.01707 0.1680853 0.029198 0.337256 0.0625 0.5 0.466231 2.278415 2.490004 4 4.90625 4 0.2482626 0 0.4801862-0.02756 0.71875-0.0625 0.1362493-0.01995 0.2738032-0.03179 0.40625-0.0625 0.2865815-0.06695 0.547624-0.166647 0.8125-0.28125 0.030718-0.01299 0.063349-0.01766 0.09375-0.03125 0.08886-0.04062 0.164735-0.108612 0.25-0.15625h0.03125z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
33
data/resources.qrc
Normal file
@@ -0,0 +1,33 @@
|
||||
<!DOCTYPE RCC>
|
||||
<RCC version="1.0">
|
||||
<qresource>
|
||||
<file>appicon.svg</file>
|
||||
|
||||
<file>fallback-icons/application-menu.svg</file>
|
||||
<file>fallback-icons/delete.svg</file>
|
||||
<file>fallback-icons/dialog-cancel.svg</file>
|
||||
<file>fallback-icons/dialog-close.svg</file>
|
||||
<file>fallback-icons/dialog-error.svg</file>
|
||||
<file>fallback-icons/dialog-information.svg</file>
|
||||
<file>fallback-icons/dialog-ok.svg</file>
|
||||
<file>fallback-icons/dialog-warning.svg</file>
|
||||
<file>fallback-icons/document-edit.svg</file>
|
||||
<file>fallback-icons/document-open.svg</file>
|
||||
<file>fallback-icons/document-save-as.svg</file>
|
||||
<file>fallback-icons/document-save.svg</file>
|
||||
<file>fallback-icons/go-next-skip.svg</file>
|
||||
<file>fallback-icons/group.svg</file>
|
||||
<file>fallback-icons/input-gamepad.svg</file>
|
||||
<file>fallback-icons/list-add.svg</file>
|
||||
<file>fallback-icons/media-playback-start.svg</file>
|
||||
<file>fallback-icons/media-seek-forward.svg</file>
|
||||
<file>fallback-icons/process-stop.svg</file>
|
||||
<file>fallback-icons/view-refresh.svg</file>
|
||||
|
||||
<file>sm64/cannon-active.svg</file>
|
||||
<file>sm64/cannon-inactive.svg</file>
|
||||
<file>sm64/coin.svg</file>
|
||||
<file>sm64/star-collected.png</file>
|
||||
<file>sm64/star-missing.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
101
data/sm64/cannon-active.svg
Normal file
@@ -0,0 +1,101 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="48"
|
||||
height="48"
|
||||
viewBox="0 0 12.7 12.7"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"
|
||||
sodipodi:docname="cannon-active.svg">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="5.6568542"
|
||||
inkscape:cx="15.039792"
|
||||
inkscape:cy="27.961619"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
units="px"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1015"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-284.29999)">
|
||||
<g
|
||||
id="g859"
|
||||
transform="matrix(1.9999977,0,0,1.9999977,-2.6458231,-294.75036)">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
style="opacity:1;fill:#dbd800;fill-opacity:1;stroke:none;stroke-width:21.96969223;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
d="m 5.6885423,294.08958 a 1.0583332,1.0583332 0 0 1 1.0583333,1.05833 1.0583332,1.0583332 0 0 1 -0.00982,0.13229 H 5.6885408 5.164025 a 0.52916579,1.0583332 0 0 1 -0.00465,-0.13229 0.52916579,1.0583332 0 0 1 0.5291673,-1.05833 z"
|
||||
id="path847" />
|
||||
<path
|
||||
transform="matrix(0.26458333,0,0,0.26458333,0,288.53332)"
|
||||
id="path832"
|
||||
d="m 12.5,21 a 3.9999996,3.9999996 0 0 0 -4,4 3.9999996,3.9999996 0 0 0 0.037109,0.5 H 12.5 14.482422 A 1.9999967,3.9999995 0 0 0 14.5,25 a 1.9999967,3.9999995 0 0 0 -2,-4 z"
|
||||
style="opacity:1;fill:#dbd800;fill-opacity:1;stroke:none;stroke-width:83.03505707;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
inkscape:connector-curvature="0" />
|
||||
<ellipse
|
||||
ry="2.2324219"
|
||||
rx="2.279669"
|
||||
cy="292.67688"
|
||||
cx="4.4979167"
|
||||
id="path826"
|
||||
style="opacity:1;fill:#e53f67;fill-opacity:1;stroke:none;stroke-width:21.16666603;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
|
||||
<ellipse
|
||||
ry="0.66145831"
|
||||
rx="0.39687499"
|
||||
cy="292.63437"
|
||||
cx="3.8364584"
|
||||
id="path849"
|
||||
style="opacity:1;fill:#ffffff;fill-opacity:0.93939394;stroke:none;stroke-width:20.30591393;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
|
||||
<ellipse
|
||||
style="opacity:1;fill:#ffffff;fill-opacity:0.93939394;stroke:none;stroke-width:20.30591393;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="ellipse851"
|
||||
cx="5.1593752"
|
||||
cy="292.63437"
|
||||
rx="0.39687499"
|
||||
ry="0.66145831" />
|
||||
<rect
|
||||
y="290.12082"
|
||||
x="3.4395833"
|
||||
height="0.59531248"
|
||||
width="2.1166666"
|
||||
id="rect828"
|
||||
style="opacity:1;fill:#7e7e7e;fill-opacity:1;stroke:none;stroke-width:17.89359283;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 4.0 KiB |
35
data/sm64/cannon-inactive.svg
Normal file
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
id="svg8"
|
||||
version="1.1"
|
||||
viewBox="0 0 12.7 12.7"
|
||||
height="48"
|
||||
width="48">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
transform="translate(0,-284.29999)"
|
||||
id="layer1">
|
||||
<path
|
||||
style="opacity:1;fill:#818181;fill-opacity:1;stroke:none;stroke-width:43.93933868;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
d="m 4.2333335,285.49062 v 1.16065 a 4.5593329,4.4648388 0 0 0 -2.442745,3.95169 4.5593329,4.4648388 0 0 0 1.23455,3.04995 2.116664,2.116664 0 0 0 -1.1730551,1.89187 2.116664,2.116664 0 0 0 0.019637,0.26459 h 2.0970297 1.0490316 a 1.0583304,2.116664 0 0 0 0.0093,-0.26459 1.0583304,2.116664 0 0 0 -0.058911,-0.68885 4.5593329,4.4648388 0 0 0 1.3818278,0.21188 4.5593329,4.4648388 0 0 0 1.3823445,-0.21601 1.0583304,2.116664 0 0 0 -0.059428,0.69298 1.0583304,2.116664 0 0 0 0.0093,0.26459 H 8.7312503 10.82828 a 2.116664,2.116664 0 0 0 0.01964,-0.26459 2.116664,2.116664 0 0 0 -1.175122,-1.89291 4.5593329,4.4648388 0 0 0 1.236617,-3.04891 4.5593329,4.4648388 0 0 0 -2.4427451,-3.95118 v -1.16116 z"
|
||||
id="path847" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.7 KiB |
80
data/sm64/coin.svg
Normal file
@@ -0,0 +1,80 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="32"
|
||||
height="32"
|
||||
viewBox="0 0 8.4666665 8.4666669"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
sodipodi:docname="coin.svg"
|
||||
inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="11.2"
|
||||
inkscape:cx="12.713924"
|
||||
inkscape:cy="11.786397"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="true"
|
||||
units="px"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1015"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid849"
|
||||
spacingx="4.2333334"
|
||||
spacingy="4.2333334" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-288.53332)">
|
||||
<circle
|
||||
style="opacity:1;fill:#e6e30d;fill-opacity:1;stroke:none;stroke-width:0.5291667;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="path845"
|
||||
cx="4.2333331"
|
||||
cy="292.76666"
|
||||
r="3.96875" />
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#73700a;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.5291667;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="M 4.2324219,288.5332 C 1.8975503,288.5332 0,290.43271 0,292.76758 0,295.10245 1.8975503,297 4.2324219,297 c 2.3348715,0 4.234375,-1.89755 4.234375,-4.23242 0,-2.33487 -1.8995035,-4.23438 -4.234375,-4.23438 z m 0,0.5293 c 2.0488886,0 3.7050781,1.65619 3.7050781,3.70508 0,2.04889 -1.6561895,3.70312 -3.7050781,3.70312 -2.0488887,0 -3.70312502,-1.65423 -3.70312502,-3.70312 -10e-9,-2.04889 1.65423632,-3.70508 3.70312502,-3.70508 z"
|
||||
id="circle853"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#675b00;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.5291667;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 2.2871094,295.44531 0.3710937,-2.16797 -1.5742187,-1.5332 2.1757812,-0.31641 0.9726563,-1.97265 0.2382812,0.48047 0.7363281,1.49218 2.1757813,0.31641 -1.5742187,1.5332 0.3710937,2.16797 -1.9472656,-1.02343 z m 3.1914062,-0.96484 -0.2382812,-1.38672 1.0058594,-0.98047 -1.390625,-0.20312 -0.6210938,-1.25977 -0.6230469,1.25977 -1.390625,0.20312 1.0058594,0.98047 -0.2382813,1.38477 1.2441407,-0.6543 z"
|
||||
id="path847"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 5.4 KiB |
BIN
data/sm64/star-collected.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
317
data/sm64/star-collected.svg
Normal file
@@ -0,0 +1,317 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="128"
|
||||
height="128"
|
||||
viewBox="0 0 33.866666 33.866668"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"
|
||||
sodipodi:docname="star-collected.svg">
|
||||
<defs
|
||||
id="defs2">
|
||||
<linearGradient
|
||||
id="linearGradient880"
|
||||
inkscape:collect="always">
|
||||
<stop
|
||||
id="stop876"
|
||||
offset="0"
|
||||
style="stop-color:#ffffff;stop-opacity:1" />
|
||||
<stop
|
||||
id="stop878"
|
||||
offset="1"
|
||||
style="stop-color:#ffffff;stop-opacity:0" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient872">
|
||||
<stop
|
||||
style="stop-color:#ffff19;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop868" />
|
||||
<stop
|
||||
style="stop-color:#ffff19;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop870" />
|
||||
</linearGradient>
|
||||
<radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient880"
|
||||
id="radialGradient903"
|
||||
cx="14.948958"
|
||||
cy="277.02396"
|
||||
fx="14.948958"
|
||||
fy="277.02396"
|
||||
r="0.92604166"
|
||||
gradientTransform="matrix(1,0,0,1.5714285,0,-158.2994)"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient880"
|
||||
id="radialGradient907"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1,0,0,1.5714285,3.9687499,-158.2994)"
|
||||
cx="14.948958"
|
||||
cy="277.02396"
|
||||
fx="14.948958"
|
||||
fy="277.02396"
|
||||
r="0.92604166" />
|
||||
<meshgradient
|
||||
inkscape:collect="always"
|
||||
id="meshgradient40076"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x="32.043655"
|
||||
y="275.63101"
|
||||
type="bicubic">
|
||||
<meshrow
|
||||
id="meshrow62002">
|
||||
<meshpatch
|
||||
id="meshpatch62004">
|
||||
<stop
|
||||
path="l -6.51471,7.517"
|
||||
style="stop-color:#ffff19;stop-opacity:1"
|
||||
id="stop62006" />
|
||||
<stop
|
||||
path="l -8.58601,-3.80858"
|
||||
style="stop-color:#dec600;stop-opacity:1"
|
||||
id="stop62008" />
|
||||
<stop
|
||||
path="l 0,0"
|
||||
style="stop-color:#ffff19;stop-opacity:1"
|
||||
id="stop62010" />
|
||||
<stop
|
||||
path="l 15.1007,-3.70842"
|
||||
style="stop-color:#dec600;stop-opacity:1"
|
||||
id="stop62012" />
|
||||
</meshpatch>
|
||||
<meshpatch
|
||||
id="meshpatch62014">
|
||||
<stop
|
||||
path="l 2.43884,9.12057"
|
||||
id="stop62016" />
|
||||
<stop
|
||||
path="l -11.0248,-12.9292"
|
||||
style="stop-color:#ffff19;stop-opacity:1"
|
||||
id="stop62018" />
|
||||
<stop
|
||||
path="l -5e-05,5e-05"
|
||||
style="stop-color:#dec600;stop-opacity:1"
|
||||
id="stop62020" />
|
||||
</meshpatch>
|
||||
<meshpatch
|
||||
id="meshpatch62022">
|
||||
<stop
|
||||
path="l -10.8244,-4.17609"
|
||||
id="stop62024" />
|
||||
<stop
|
||||
path="l -0.200558,-8.75309"
|
||||
style="stop-color:#dec600;stop-opacity:1"
|
||||
id="stop62026" />
|
||||
<stop
|
||||
path="l 0.000158,-2e-05"
|
||||
style="stop-color:#ffff19;stop-opacity:1"
|
||||
id="stop62028" />
|
||||
</meshpatch>
|
||||
<meshpatch
|
||||
id="meshpatch62030">
|
||||
<stop
|
||||
path="l -11.2922,4.37654"
|
||||
id="stop62032" />
|
||||
<stop
|
||||
path="l 11.0919,-13.1297"
|
||||
style="stop-color:#ffff19;stop-opacity:1"
|
||||
id="stop62034" />
|
||||
<stop
|
||||
path="l -0.000258,7e-05"
|
||||
style="stop-color:#dec600;stop-opacity:1"
|
||||
id="stop62036" />
|
||||
</meshpatch>
|
||||
<meshpatch
|
||||
id="meshpatch62038">
|
||||
<stop
|
||||
path="l 2.57252,-9.52147"
|
||||
id="stop62040" />
|
||||
<stop
|
||||
path="l 8.51957,-3.60823"
|
||||
style="stop-color:#dec600;stop-opacity:1"
|
||||
id="stop62042" />
|
||||
<stop
|
||||
path="l -0.00019,0"
|
||||
style="stop-color:#ffff19;stop-opacity:1"
|
||||
id="stop62044" />
|
||||
</meshpatch>
|
||||
<meshpatch
|
||||
id="meshpatch62046">
|
||||
<stop
|
||||
path="l -6.58152,-7.2831"
|
||||
id="stop62048" />
|
||||
<stop
|
||||
path="l 15.1009,3.67467"
|
||||
style="stop-color:#ffff19;stop-opacity:1"
|
||||
id="stop62050" />
|
||||
<stop
|
||||
path="l 0.00019,0.0002"
|
||||
style="stop-color:#dec600;stop-opacity:1"
|
||||
id="stop62052" />
|
||||
</meshpatch>
|
||||
<meshpatch
|
||||
id="meshpatch62054">
|
||||
<stop
|
||||
path="l 9.55489,-0.835216"
|
||||
id="stop62056" />
|
||||
<stop
|
||||
path="l 5.54601,4.50981"
|
||||
style="stop-color:#dec600;stop-opacity:1"
|
||||
id="stop62058" />
|
||||
<stop
|
||||
path="l 0,7.6e-05"
|
||||
style="stop-color:#ffff19;stop-opacity:1"
|
||||
id="stop62060" />
|
||||
</meshpatch>
|
||||
<meshpatch
|
||||
id="meshpatch62062">
|
||||
<stop
|
||||
path="l 5.57925,-8.61944"
|
||||
id="stop62064" />
|
||||
<stop
|
||||
path="l -0.0332374,13.1292"
|
||||
style="stop-color:#ffff19;stop-opacity:1"
|
||||
id="stop62066" />
|
||||
<stop
|
||||
path="l -2.6e-06,5e-05"
|
||||
style="stop-color:#dec600;stop-opacity:1"
|
||||
id="stop62068" />
|
||||
</meshpatch>
|
||||
<meshpatch
|
||||
id="meshpatch62070">
|
||||
<stop
|
||||
path="l 5.57925,8.51922"
|
||||
id="stop62072" />
|
||||
<stop
|
||||
path="l -5.61245,4.61008"
|
||||
style="stop-color:#dec600;stop-opacity:1"
|
||||
id="stop62074" />
|
||||
<stop
|
||||
path="l -3.74e-05,-0.0001"
|
||||
style="stop-color:#ffff19;stop-opacity:1"
|
||||
id="stop62076" />
|
||||
</meshpatch>
|
||||
<meshpatch
|
||||
id="meshpatch62078">
|
||||
<stop
|
||||
path="l 9.48808,0.901984"
|
||||
id="stop62080" />
|
||||
<stop
|
||||
path="l -15.1006,3.7081"
|
||||
style="stop-color:#ffff19;stop-opacity:1"
|
||||
id="stop62082" />
|
||||
<stop
|
||||
path="l 7e-05,-4e-06"
|
||||
style="stop-color:#dec600;stop-opacity:1"
|
||||
id="stop62084" />
|
||||
</meshpatch>
|
||||
</meshrow>
|
||||
</meshgradient>
|
||||
<radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient872"
|
||||
id="radialGradient874"
|
||||
cx="16.938753"
|
||||
cy="279.32703"
|
||||
fx="16.938753"
|
||||
fy="279.32703"
|
||||
r="4.2333331"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1.75,0,0,1.75,-12.729066,-209.47027)" />
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="8"
|
||||
inkscape:cx="55.288709"
|
||||
inkscape:cy="67.059813"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="g62100"
|
||||
showgrid="true"
|
||||
showguides="false"
|
||||
units="px"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1015"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-263.13329)">
|
||||
<g
|
||||
id="g62100"
|
||||
transform="translate(-0.00542042,0.73959707)">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path828"
|
||||
d="m 5.851241,292.46899 11.292136,-4.37654 10.824417,4.17609 -2.438834,-9.12057 6.514695,-7.51696 -9.488069,-0.90203 -5.579251,-8.51922 -5.579252,8.61944 -9.554885,0.83522 6.581513,7.2831 z"
|
||||
style="fill:url(#meshgradient40076);fill-opacity:1;stroke:none;stroke-width:0.52916667;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<circle
|
||||
style="opacity:1;fill:url(#radialGradient874);fill-opacity:1;stroke:none;stroke-width:1.04118919;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="path866"
|
||||
cx="16.913754"
|
||||
cy="279.35202"
|
||||
r="7.4083333" />
|
||||
<ellipse
|
||||
ry="2.9104166"
|
||||
rx="1.3890625"
|
||||
cy="277.95001"
|
||||
cx="14.750521"
|
||||
id="path848"
|
||||
style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:24.95042229;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
|
||||
<ellipse
|
||||
style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:24.95042229;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="ellipse850"
|
||||
cx="18.719271"
|
||||
cy="277.95001"
|
||||
rx="1.3890625"
|
||||
ry="2.9104166" />
|
||||
<ellipse
|
||||
ry="1.4552083"
|
||||
rx="0.92604166"
|
||||
cy="277.02396"
|
||||
cx="14.948958"
|
||||
id="path852"
|
||||
style="opacity:1;fill:url(#radialGradient903);fill-opacity:1.0;stroke:none;stroke-width:21.08862495;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
|
||||
<ellipse
|
||||
style="opacity:1;fill:url(#radialGradient907);fill-opacity:1.0;stroke:none;stroke-width:21.08862495;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="ellipse905"
|
||||
cx="18.917707"
|
||||
cy="277.02396"
|
||||
rx="0.92604166"
|
||||
ry="1.4552083" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 10 KiB |
BIN
data/sm64/star-missing.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
267
data/sm64/star-missing.svg
Normal file
@@ -0,0 +1,267 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="128"
|
||||
height="128"
|
||||
viewBox="0 0 33.866666 33.866668"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"
|
||||
sodipodi:docname="star-missing.svg">
|
||||
<defs
|
||||
id="defs2">
|
||||
<linearGradient
|
||||
id="linearGradient902"
|
||||
inkscape:collect="always">
|
||||
<stop
|
||||
id="stop906"
|
||||
offset="0"
|
||||
style="stop-color:#9197d3;stop-opacity:1" />
|
||||
<stop
|
||||
id="stop908"
|
||||
offset="1"
|
||||
style="stop-color:#9197d3;stop-opacity:0" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient902-3">
|
||||
<stop
|
||||
style="stop-color:#9197d3;stop-opacity:1"
|
||||
offset="0"
|
||||
id="stop898" />
|
||||
<stop
|
||||
style="stop-color:#9197d3;stop-opacity:0"
|
||||
offset="1"
|
||||
id="stop900" />
|
||||
</linearGradient>
|
||||
<meshgradient
|
||||
inkscape:collect="always"
|
||||
id="meshgradient40076"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x="32.043655"
|
||||
y="275.63101"
|
||||
type="bicubic">
|
||||
<meshrow
|
||||
id="meshrow62002">
|
||||
<meshpatch
|
||||
id="meshpatch62004">
|
||||
<stop
|
||||
path="l -6.51471,7.517"
|
||||
style="stop-color:#888a93;stop-opacity:1"
|
||||
id="stop62006" />
|
||||
<stop
|
||||
path="l -8.58601,-3.80858"
|
||||
style="stop-color:#9197d3;stop-opacity:1"
|
||||
id="stop62008" />
|
||||
<stop
|
||||
path="l 0,0"
|
||||
style="stop-color:#888a93;stop-opacity:1"
|
||||
id="stop62010" />
|
||||
<stop
|
||||
path="l 15.1007,-3.70842"
|
||||
style="stop-color:#9197d3;stop-opacity:1"
|
||||
id="stop62012" />
|
||||
</meshpatch>
|
||||
<meshpatch
|
||||
id="meshpatch62014">
|
||||
<stop
|
||||
path="l 2.43884,9.12057"
|
||||
id="stop62016" />
|
||||
<stop
|
||||
path="l -11.0248,-12.9292"
|
||||
style="stop-color:#888a93;stop-opacity:1"
|
||||
id="stop62018" />
|
||||
<stop
|
||||
path="l -5e-05,5e-05"
|
||||
style="stop-color:#9197d3;stop-opacity:1"
|
||||
id="stop62020" />
|
||||
</meshpatch>
|
||||
<meshpatch
|
||||
id="meshpatch62022">
|
||||
<stop
|
||||
path="l -10.8244,-4.17609"
|
||||
id="stop62024" />
|
||||
<stop
|
||||
path="l -0.200558,-8.75309"
|
||||
style="stop-color:#9197d3;stop-opacity:1"
|
||||
id="stop62026" />
|
||||
<stop
|
||||
path="l 0.000158,-2e-05"
|
||||
style="stop-color:#888a93;stop-opacity:1"
|
||||
id="stop62028" />
|
||||
</meshpatch>
|
||||
<meshpatch
|
||||
id="meshpatch62030">
|
||||
<stop
|
||||
path="l -11.2922,4.37654"
|
||||
id="stop62032" />
|
||||
<stop
|
||||
path="l 11.0919,-13.1297"
|
||||
style="stop-color:#888a93;stop-opacity:1"
|
||||
id="stop62034" />
|
||||
<stop
|
||||
path="l -0.000258,7e-05"
|
||||
style="stop-color:#9197d3;stop-opacity:1"
|
||||
id="stop62036" />
|
||||
</meshpatch>
|
||||
<meshpatch
|
||||
id="meshpatch62038">
|
||||
<stop
|
||||
path="l 2.57252,-9.52147"
|
||||
id="stop62040" />
|
||||
<stop
|
||||
path="l 8.51957,-3.60823"
|
||||
style="stop-color:#9197d3;stop-opacity:1"
|
||||
id="stop62042" />
|
||||
<stop
|
||||
path="l -0.00019,0"
|
||||
style="stop-color:#888a93;stop-opacity:1"
|
||||
id="stop62044" />
|
||||
</meshpatch>
|
||||
<meshpatch
|
||||
id="meshpatch62046">
|
||||
<stop
|
||||
path="l -6.58152,-7.2831"
|
||||
id="stop62048" />
|
||||
<stop
|
||||
path="l 15.1009,3.67467"
|
||||
style="stop-color:#888a93;stop-opacity:1"
|
||||
id="stop62050" />
|
||||
<stop
|
||||
path="l 0.00019,0.0002"
|
||||
style="stop-color:#9197d3;stop-opacity:1"
|
||||
id="stop62052" />
|
||||
</meshpatch>
|
||||
<meshpatch
|
||||
id="meshpatch62054">
|
||||
<stop
|
||||
path="l 9.55489,-0.835216"
|
||||
id="stop62056" />
|
||||
<stop
|
||||
path="l 5.54601,4.50981"
|
||||
style="stop-color:#9197d3;stop-opacity:1"
|
||||
id="stop62058" />
|
||||
<stop
|
||||
path="l 0,7.6e-05"
|
||||
style="stop-color:#888a93;stop-opacity:1"
|
||||
id="stop62060" />
|
||||
</meshpatch>
|
||||
<meshpatch
|
||||
id="meshpatch62062">
|
||||
<stop
|
||||
path="l 5.57925,-8.61944"
|
||||
id="stop62064" />
|
||||
<stop
|
||||
path="l -0.0332374,13.1292"
|
||||
style="stop-color:#888a93;stop-opacity:1"
|
||||
id="stop62066" />
|
||||
<stop
|
||||
path="l -2.6e-06,5e-05"
|
||||
style="stop-color:#9197d3;stop-opacity:1"
|
||||
id="stop62068" />
|
||||
</meshpatch>
|
||||
<meshpatch
|
||||
id="meshpatch62070">
|
||||
<stop
|
||||
path="l 5.57925,8.51922"
|
||||
id="stop62072" />
|
||||
<stop
|
||||
path="l -5.61245,4.61008"
|
||||
style="stop-color:#9197d3;stop-opacity:1"
|
||||
id="stop62074" />
|
||||
<stop
|
||||
path="l -3.74e-05,-0.0001"
|
||||
style="stop-color:#888a93;stop-opacity:1"
|
||||
id="stop62076" />
|
||||
</meshpatch>
|
||||
<meshpatch
|
||||
id="meshpatch62078">
|
||||
<stop
|
||||
path="l 9.48808,0.901984"
|
||||
id="stop62080" />
|
||||
<stop
|
||||
path="l -15.1006,3.7081"
|
||||
style="stop-color:#888a93;stop-opacity:1"
|
||||
id="stop62082" />
|
||||
<stop
|
||||
path="l 7e-05,-4e-06"
|
||||
style="stop-color:#9197d3;stop-opacity:1"
|
||||
id="stop62084" />
|
||||
</meshpatch>
|
||||
</meshrow>
|
||||
</meshgradient>
|
||||
<radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient902-3"
|
||||
id="radialGradient904"
|
||||
cx="16.983717"
|
||||
cy="279.8114"
|
||||
fx="16.983717"
|
||||
fy="279.8114"
|
||||
r="15.100728"
|
||||
gradientTransform="matrix(0.59003676,-4.4834525e-6,-9.199071e-8,0.54022096,6.9627242,128.58989)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
spreadMethod="reflect" />
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="4"
|
||||
inkscape:cx="51.642871"
|
||||
inkscape:cy="68.271587"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="g62100"
|
||||
showgrid="true"
|
||||
showguides="false"
|
||||
units="px"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1015"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-263.13329)">
|
||||
<g
|
||||
id="g62100"
|
||||
transform="translate(-0.00542042,0.73959707)">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path828"
|
||||
d="m 5.851241,292.46899 11.292136,-4.37654 10.824417,4.17609 -2.438834,-9.12057 6.514695,-7.51696 -9.488069,-0.90203 -5.579251,-8.51922 -5.579252,8.61944 -9.554885,0.83522 6.581513,7.2831 z"
|
||||
style="fill:url(#meshgradient40076);fill-opacity:1;stroke:none;stroke-width:0.52916667;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
style="fill:url(#radialGradient904);fill-opacity:1;stroke:none;stroke-width:0.52916667;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 5.851241,292.46899 11.292136,-4.37654 10.824417,4.17609 -2.438834,-9.12057 6.514695,-7.51696 -9.488069,-0.90203 -5.579251,-8.51922 -5.579252,8.61944 -9.554885,0.83522 6.581513,7.2831 z"
|
||||
id="path862"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 8.4 KiB |
37
lsjs.c
Normal file
@@ -0,0 +1,37 @@
|
||||
#define SDL_MAIN_HANDLED 1
|
||||
#include <SDL2/SDL.h>
|
||||
#include <stdio.h>
|
||||
|
||||
typedef unsigned char ubyte;
|
||||
|
||||
static void writeHex( char *out, const ubyte *data, size_t numBytes ) {
|
||||
for( size_t i = 0; i < numBytes; i++ ) {
|
||||
const ubyte upperNibble = data[i] >> 4;
|
||||
const ubyte lowerNibble = data[i] & 0xF;
|
||||
out[i*2] = upperNibble + (upperNibble > 9 ? ('a' - (char)10) : '0');
|
||||
out[i*2+1] = lowerNibble + (lowerNibble > 9 ? ('a' - (char)10) : '0');
|
||||
}
|
||||
}
|
||||
|
||||
static inline void writeUuid( const ubyte *data, char *template ) {
|
||||
writeHex( template, data, 4 );
|
||||
writeHex( &template[9], &data[4], 2 );
|
||||
writeHex( &template[14], &data[6], 2 );
|
||||
writeHex( &template[19], &data[8], 2 );
|
||||
writeHex( &template[24], &data[10], 6 );
|
||||
}
|
||||
|
||||
int main( int argc, char *argv[] ) {
|
||||
SDL_Init( SDL_INIT_JOYSTICK );
|
||||
SDL_JoystickUpdate();
|
||||
|
||||
char uuid[37] = "00000000-0000-0000-0000-000000000000";
|
||||
const int numConnected = SDL_NumJoysticks();
|
||||
for( int i = 0; i < numConnected; i++ ) {
|
||||
writeUuid( SDL_JoystickGetDeviceGUID( i ).data, uuid );
|
||||
printf( "%s\n", uuid );
|
||||
}
|
||||
|
||||
SDL_Quit();
|
||||
return 0;
|
||||
}
|
11
parallel-launcher.desktop
Normal file
@@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env xdg-open
|
||||
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Version=1.0.0
|
||||
Name=Parallel Launcher
|
||||
Exec=/usr/local/bin/parallel-launcher "%f"
|
||||
Icon=/usr/local/share/parallel-launcher/appicon.svg
|
||||
Terminal=false
|
||||
MimeType=application/x-n64-rom
|
||||
Categories=Game
|
37
parallel-launcher_resource.rc
Normal file
@@ -0,0 +1,37 @@
|
||||
#include <windows.h>
|
||||
|
||||
IDI_ICON1 ICON DISCARDABLE "P:/data/appicon.ico"
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 1,0,0,0
|
||||
PRODUCTVERSION 1,0,0,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS VS_FF_DEBUG
|
||||
#else
|
||||
FILEFLAGS 0x0L
|
||||
#endif
|
||||
FILEOS VOS__WINDOWS32
|
||||
FILETYPE VFT_DLL
|
||||
FILESUBTYPE 0x0L
|
||||
BEGIN
|
||||
BLOCK "StringFileInfo"
|
||||
BEGIN
|
||||
BLOCK "040904b0"
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Matt Pharoah\0"
|
||||
VALUE "FileDescription", "\0"
|
||||
VALUE "FileVersion", "1.0.0.0\0"
|
||||
VALUE "LegalCopyright", "GNU GENERAL PUBLIC LICENSE Version 2\0"
|
||||
VALUE "OriginalFilename", "parallel-launcher.exe\0"
|
||||
VALUE "ProductName", "Parallel Launcher\0"
|
||||
VALUE "ProductVersion", "1.0.0.0\0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
BEGIN
|
||||
VALUE "Translation", 0x0409, 1200
|
||||
END
|
||||
END
|
||||
/* End of Version info */
|
||||
|
7
qmake-debug.sh
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/bin/sh
|
||||
gcc -std=c99 -O3 lsjs.c -o parallel-launcher-lsjs -lSDL2
|
||||
if [ -n "$(which qtchooser)" ]; then
|
||||
qmake -qt=qt5 app.pro -spec linux-g++ CONFIG+=debug CONFIG+=qml_debug
|
||||
else
|
||||
qmake app.pro -spec linux-g++ CONFIG+=debug CONFIG+=qml_debug
|
||||
fi
|
7
qmake-release.sh
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/bin/sh
|
||||
gcc -std=c99 -O3 lsjs.c -o parallel-launcher-lsjs -lSDL2
|
||||
if [ -n "$(which qtchooser)" ]; then
|
||||
qmake -qt=qt5 app.pro -spec linux-g++
|
||||
else
|
||||
qmake app.pro -spec linux-g++
|
||||
fi
|
6
qmake-windows-debug.sh
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/bin/sh
|
||||
gcc -std=c99 -O3 lsjs.c -o parallel-launcher-lsjs.exe -lSDL2
|
||||
cp parallel-launcher-lsjs.exe Debug/parallel-launcher-lsjs.exe
|
||||
rm qrc_resources.cpp 2> /dev/null
|
||||
qmake app.pro -spec win32-g++ CONFIG+=debug CONFIG+=qml_debug
|
||||
sed -i 's/\.\.\///g' Makefile
|
6
qmake-windows-release.sh
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/bin/sh
|
||||
gcc -std=c99 -O3 lsjs.c -o parallel-launcher-lsjs.exe -lSDL2
|
||||
cp parallel-launcher-lsjs.exe Release/parallel-launcher-lsjs.exe
|
||||
rm qrc_resources.cpp 2> /dev/null
|
||||
qmake app.pro -spec win32-g++ CONFIG+=release CONFIG+=qml_release
|
||||
sed -i 's/\.\.\///g' Makefile
|
47
src/core/async.hpp
Normal file
@@ -0,0 +1,47 @@
|
||||
#ifndef SRC_CORE_ASYNC_HPP_
|
||||
#define SRC_CORE_ASYNC_HPP_
|
||||
|
||||
#include <memory>
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
|
||||
class CancellationToken {
|
||||
|
||||
private:
|
||||
std::shared_ptr<volatile bool> m_value;
|
||||
|
||||
public:
|
||||
CancellationToken() {
|
||||
m_value = std::shared_ptr<volatile bool>( new volatile bool( false ) );
|
||||
}
|
||||
|
||||
CancellationToken( const CancellationToken &other ) noexcept {
|
||||
m_value = other.m_value;
|
||||
}
|
||||
|
||||
CancellationToken( CancellationToken &&other ) noexcept {
|
||||
m_value = std::move( other.m_value );
|
||||
}
|
||||
|
||||
inline CancellationToken &operator=( const CancellationToken &other ) noexcept {
|
||||
m_value = other.m_value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline CancellationToken &operator=( CancellationToken &&other ) noexcept {
|
||||
m_value = std::move( other.m_value );
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
inline void cancel() noexcept {
|
||||
*m_value = true;
|
||||
}
|
||||
|
||||
inline bool isCancelled() const noexcept {
|
||||
return *m_value;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
#endif /* SRC_CORE_ASYNC_HPP_ */
|
75
src/core/controller.cpp
Normal file
@@ -0,0 +1,75 @@
|
||||
#include "src/core/controller.hpp"
|
||||
|
||||
template<> void JsonSerializer::serialize<Binding>(
|
||||
JsonWriter &jw,
|
||||
const Binding &obj
|
||||
) {
|
||||
switch( obj.type ) {
|
||||
case BindingType::None:
|
||||
jw.writeNull();
|
||||
break;
|
||||
case BindingType::Button:
|
||||
jw.writeString( std::to_string( obj.buttonOrAxis ) );
|
||||
break;
|
||||
case BindingType::AxisNegative:
|
||||
jw.writeString( "-"s + std::to_string( obj.buttonOrAxis ) );
|
||||
break;
|
||||
case BindingType::AxisPositive:
|
||||
jw.writeString( "+"s + std::to_string( obj.buttonOrAxis ) );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
template<> Binding JsonSerializer::parse<Binding>(
|
||||
const Json &json
|
||||
) {
|
||||
if( json.isNull() ) return { BindingType::None, 0 };
|
||||
const string inputString = json.get<string>();
|
||||
|
||||
if( inputString.at( 0 ) == '+' ) {
|
||||
return { BindingType::AxisPositive, (ushort)std::stoi( inputString ) };
|
||||
} else if( inputString.at( 0 ) == '-' ) {
|
||||
return { BindingType::AxisNegative, (ushort)-std::stoi( inputString ) };
|
||||
} else {
|
||||
return { BindingType::Button, (ushort)std::stoi( inputString ) };
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr char P_NAME[] = "name";
|
||||
static constexpr char P_BINDINGS[] = "bindings";
|
||||
static constexpr char P_SENSITIVITY[] = "sensitivity";
|
||||
static constexpr char P_DEADZONE[] = "deadzone";
|
||||
static constexpr char P_RUMBLE[] = "rumble";
|
||||
|
||||
template<> void JsonSerializer::serialize<ControllerProfile>(
|
||||
JsonWriter &jw,
|
||||
const ControllerProfile &obj
|
||||
) {
|
||||
jw.writeObjectStart();
|
||||
jw.writeProperty( P_NAME, obj.name );
|
||||
jw.writePropertyName( P_BINDINGS );
|
||||
jw.writeArrayStart();
|
||||
for( int i = 0; i < (int)ControllerAction::NUM_ACTIONS; i++ ) {
|
||||
JsonSerializer::serialize<Binding>( jw, obj.bindings[i] );
|
||||
}
|
||||
jw.writeArrayEnd();
|
||||
jw.writeProperty( P_SENSITIVITY, obj.sensitivity );
|
||||
jw.writeProperty( P_DEADZONE, obj.deadzone );
|
||||
jw.writeProperty( P_RUMBLE, obj.rumble );
|
||||
jw.writeObjectEnd();
|
||||
}
|
||||
|
||||
template<> ControllerProfile JsonSerializer::parse<ControllerProfile>(
|
||||
const Json &json
|
||||
) {
|
||||
ControllerProfile profile;
|
||||
profile.name = json[P_NAME].get<string>();
|
||||
const JArray bindingsJson = json[P_BINDINGS].array();
|
||||
for( int i = 0; i < (int)ControllerAction::NUM_ACTIONS; i++ ) {
|
||||
profile.bindings[i] = JsonSerializer::parse<Binding>( bindingsJson[i] );
|
||||
}
|
||||
profile.sensitivity = json[P_SENSITIVITY].get<double>();
|
||||
profile.deadzone = json[P_DEADZONE].get<double>();
|
||||
profile.rumble = json[P_RUMBLE].get<bool>();
|
||||
return profile;
|
||||
}
|
102
src/core/controller.hpp
Normal file
@@ -0,0 +1,102 @@
|
||||
#ifndef SRC_CORE_CONTROLLER_HPP_
|
||||
#define SRC_CORE_CONTROLLER_HPP_
|
||||
|
||||
#include <functional>
|
||||
#include "src/types.hpp"
|
||||
#include "src/core/uuid.hpp"
|
||||
#include "src/core/json.hpp"
|
||||
|
||||
enum class ControllerAction : ubyte {
|
||||
AnalogUp,
|
||||
AnalogDown,
|
||||
AnalogLeft,
|
||||
AnalogRight,
|
||||
CUp,
|
||||
CDown,
|
||||
CLeft,
|
||||
CRight,
|
||||
DPadUp,
|
||||
DPadDown,
|
||||
DPadLeft,
|
||||
DPadRight,
|
||||
ButtonA,
|
||||
ButtonB,
|
||||
TriggerL,
|
||||
TriggerZ,
|
||||
TriggerR,
|
||||
Start,
|
||||
|
||||
SaveState,
|
||||
LoadState,
|
||||
|
||||
NUM_ACTIONS
|
||||
};
|
||||
|
||||
enum class BindingType : ubyte {
|
||||
None = 0,
|
||||
Button = 2,
|
||||
AxisPositive = 3,
|
||||
AxisNegative = 4
|
||||
};
|
||||
|
||||
struct Binding {
|
||||
BindingType type;
|
||||
ushort buttonOrAxis;
|
||||
};
|
||||
|
||||
struct ControllerId {
|
||||
ushort vendorId;
|
||||
ushort productId;
|
||||
|
||||
inline bool operator==( const ControllerId &other ) const noexcept {
|
||||
return productId == other.productId && vendorId == other.vendorId;
|
||||
}
|
||||
|
||||
inline bool operator!=( const ControllerId &other ) const noexcept {
|
||||
return productId != other.productId || vendorId != other.vendorId;
|
||||
}
|
||||
};
|
||||
|
||||
template<> struct std::hash<ControllerId> {
|
||||
inline size_t operator()(const ControllerId &id) const noexcept {
|
||||
return ((size_t)id.vendorId << 16) | (size_t)id.productId;
|
||||
}
|
||||
};
|
||||
|
||||
template<> struct std::hash<Binding> {
|
||||
inline size_t operator()(const Binding &binding) const noexcept {
|
||||
return ((size_t)binding.type << 16) | (size_t)binding.buttonOrAxis;
|
||||
}
|
||||
};
|
||||
|
||||
struct ControllerInfo {
|
||||
string name;
|
||||
ControllerId controllerId;
|
||||
Uuid uuid;
|
||||
|
||||
ushort numButtons;
|
||||
ushort numAxes;
|
||||
};
|
||||
|
||||
struct ControllerProfile {
|
||||
string name;
|
||||
Binding bindings[(ubyte)ControllerAction::NUM_ACTIONS];
|
||||
double sensitivity;
|
||||
double deadzone;
|
||||
bool rumble;
|
||||
};
|
||||
|
||||
struct PlayerController {
|
||||
ControllerProfile profile;
|
||||
Uuid deviceUuid;
|
||||
};
|
||||
|
||||
namespace JsonSerializer {
|
||||
template<> void serialize<Binding>( JsonWriter &jw, const Binding &obj );
|
||||
template<> Binding parse<Binding>( const Json &json );
|
||||
|
||||
template<> void serialize<ControllerProfile>( JsonWriter &jw, const ControllerProfile &obj );
|
||||
template<> ControllerProfile parse<ControllerProfile>( const Json &json );
|
||||
}
|
||||
|
||||
#endif /* SRC_CORE_CONTROLLER_HPP_ */
|
295
src/core/file-controller.cpp
Normal file
@@ -0,0 +1,295 @@
|
||||
#include "src/core/file-controller.hpp"
|
||||
|
||||
#include "src/polyfill/base-directory.hpp"
|
||||
#include "src/core/preset-controllers.hpp"
|
||||
#include "src/ui/error-notifier.hpp"
|
||||
#include <fstream>
|
||||
#include <ios>
|
||||
|
||||
template<class T> class FileCache {
|
||||
|
||||
private:
|
||||
T m_value;
|
||||
const fs::path m_filePath;
|
||||
void (*const m_setter)(JsonWriter&, const T &);
|
||||
|
||||
void commit() const {
|
||||
try {
|
||||
std::ofstream file( m_filePath.string(), std::ios_base::out | std::ios_base::trunc );
|
||||
JsonWriter writer( &file, true );
|
||||
m_setter( writer, m_value );
|
||||
file << std::flush;
|
||||
} catch( const std::exception &exception ) {
|
||||
string msg = "Failed to save data to '"s + m_filePath.string() + "'.\n";
|
||||
if( dynamic_cast<const JsonWriterException*>( &exception ) ) {
|
||||
msg += "Failed to serialize data to JSON. Please report this bug!\nDetails: ";
|
||||
} else if( dynamic_cast<const fs::filesystem_error*>( &exception ) ) {
|
||||
msg += "Filesystem Error: ";
|
||||
} else if( dynamic_cast<const std::ios_base::failure*>( &exception ) ) {
|
||||
msg += "I/O Error: ";
|
||||
}
|
||||
msg += exception.what();
|
||||
ErrorNotifier::alert( msg );
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
FileCache(
|
||||
const fs::path &filePath,
|
||||
const T &defaultValue,
|
||||
T (*getter)(const Json&),
|
||||
void (*setter)(JsonWriter&, const T &)
|
||||
) : m_filePath( filePath ), m_setter( setter ) {
|
||||
if( !fs::is_regular_file( filePath ) ) {
|
||||
m_value = defaultValue;
|
||||
} else try {
|
||||
std::ifstream file( filePath.string() );
|
||||
m_value = getter( Json::parse( file ) );
|
||||
} catch( const std::exception &exception ) {
|
||||
string msg = "Failed to load data from '"s + m_filePath.string() + "'.\n";
|
||||
if( dynamic_cast<const JsonReaderException*>( &exception ) ) {
|
||||
msg += "Failed to parse JSON data. Please report this bug!\nDetails: ";
|
||||
} else if( dynamic_cast<const fs::filesystem_error*>( &exception ) ) {
|
||||
msg += "Filesystem Error: ";
|
||||
} else if( dynamic_cast<const std::ios_base::failure*>( &exception ) ) {
|
||||
msg += "I/O Error: ";
|
||||
}
|
||||
msg += exception.what();
|
||||
ErrorNotifier::alert( msg );
|
||||
m_value = defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
~FileCache() {}
|
||||
|
||||
inline const T &get() const {
|
||||
return m_value;
|
||||
}
|
||||
|
||||
inline void set( const T &value ) {
|
||||
m_value = value;
|
||||
commit();
|
||||
}
|
||||
|
||||
inline void set( T &&value ) {
|
||||
m_value = std::move( value );
|
||||
commit();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
template<class T> static std::vector<T> parseJsonArray( const Json &json ) {
|
||||
const JArray &jsonArray = json.array();
|
||||
|
||||
std::vector<T> parsedArray;
|
||||
parsedArray.reserve( jsonArray.size() );
|
||||
for( const Json &item : jsonArray ) {
|
||||
parsedArray.push_back( JsonSerializer::parse<T>( item ) );
|
||||
}
|
||||
return parsedArray;
|
||||
}
|
||||
|
||||
template<class T> static void serializeToJsonArray( JsonWriter &writer, const std::vector<T> &array ) {
|
||||
writer.writeArrayStart();
|
||||
for( const T &item : array ) {
|
||||
JsonSerializer::serialize<T>( writer, item );
|
||||
}
|
||||
writer.writeArrayEnd();
|
||||
}
|
||||
|
||||
static std::set<string> parseTags( const Json &json ) {
|
||||
const JArray &jsonArray = json.array();
|
||||
|
||||
std::set<string> tags;
|
||||
for( const Json &tagJson : jsonArray ) {
|
||||
tags.insert( tagJson.get<string>() );
|
||||
}
|
||||
return tags;
|
||||
}
|
||||
|
||||
static void serializeTags( JsonWriter &writer, const std::set<string> &tags ) {
|
||||
writer.writeArrayStart();
|
||||
for( const string &tag : tags ) {
|
||||
writer.writeString( tag );
|
||||
}
|
||||
writer.writeArrayEnd();
|
||||
}
|
||||
|
||||
static std::map<string, ControllerProfile> parseProfiles( const Json &json ) {
|
||||
const JArray &jsonArray = json.array();
|
||||
|
||||
std::map<string, ControllerProfile> profiles;
|
||||
for( const Json &profileJson : jsonArray ) {
|
||||
ControllerProfile profile = JsonSerializer::parse<ControllerProfile>( profileJson );
|
||||
profiles[profile.name] = profile;
|
||||
}
|
||||
profiles[DefaultProfile::Gamecube.name] = DefaultProfile::Gamecube;
|
||||
profiles[DefaultProfile::XBox360.name] = DefaultProfile::XBox360;
|
||||
return profiles;
|
||||
}
|
||||
|
||||
static void serializeProfiles( JsonWriter &writer, const std::map<string, ControllerProfile> &profiles ) {
|
||||
writer.writeArrayStart();
|
||||
for( const auto &i : profiles ) {
|
||||
if( !DefaultProfile::exists( i.first ) ) {
|
||||
JsonSerializer::serialize<ControllerProfile>( writer, i.second );
|
||||
}
|
||||
}
|
||||
writer.writeArrayEnd();
|
||||
}
|
||||
|
||||
static HashMap<Uuid,string> parseMappings( const Json &json ) {
|
||||
const JArray &jsonArray = json.array();
|
||||
|
||||
HashMap<Uuid,string> mappings;
|
||||
for( const Json &mappingJson : jsonArray ) {
|
||||
const Uuid deviceId = Uuid::parse( mappingJson["device"].get<string>() );
|
||||
mappings[deviceId] = mappingJson["profile"].get<string>();
|
||||
}
|
||||
return mappings;
|
||||
}
|
||||
|
||||
static void serializeMappings( JsonWriter &writer, const HashMap<Uuid,string> &mappings ) {
|
||||
writer.writeArrayStart();
|
||||
for( const auto &i : mappings ) {
|
||||
writer.writeObjectStart();
|
||||
writer.writeProperty( "device", i.first.toString() );
|
||||
writer.writeProperty( "profile", i.second );
|
||||
writer.writeObjectEnd();
|
||||
}
|
||||
writer.writeArrayEnd();
|
||||
}
|
||||
|
||||
|
||||
|
||||
static FileCache<AppSettings> &appSettingsCache() {
|
||||
static FileCache<AppSettings> s_cache = FileCache<AppSettings>(
|
||||
BaseDir::config() / "settings.cfg",
|
||||
AppSettings::Default,
|
||||
JsonSerializer::parse<AppSettings>,
|
||||
JsonSerializer::serialize<AppSettings>
|
||||
);
|
||||
return s_cache;
|
||||
}
|
||||
|
||||
static FileCache<std::vector<ROM>> &romListCache() {
|
||||
static FileCache<std::vector<ROM>> s_cache = FileCache<std::vector<ROM>>(
|
||||
BaseDir::data() / "roms.json",
|
||||
std::vector<ROM>(),
|
||||
parseJsonArray<ROM>,
|
||||
serializeToJsonArray<ROM>
|
||||
);
|
||||
return s_cache;
|
||||
}
|
||||
|
||||
static FileCache<std::vector<RomSource>> &romSourcesCache() {
|
||||
static FileCache<std::vector<RomSource>> s_cache = FileCache<std::vector<RomSource>>(
|
||||
BaseDir::data() / "sources.json",
|
||||
std::vector<RomSource>(),
|
||||
parseJsonArray<RomSource>,
|
||||
serializeToJsonArray<RomSource>
|
||||
);
|
||||
return s_cache;
|
||||
}
|
||||
|
||||
static FileCache<std::set<string>> &tagsCache() {
|
||||
static FileCache<std::set<string>> s_cache = FileCache<std::set<string>>(
|
||||
BaseDir::data() / "groups.json",
|
||||
std::set<string>({ "Favourites" }),
|
||||
parseTags,
|
||||
serializeTags
|
||||
);
|
||||
return s_cache;
|
||||
}
|
||||
|
||||
static FileCache<std::map<string, ControllerProfile>> &profilesCache() {
|
||||
static FileCache<std::map<string, ControllerProfile>> s_cache = FileCache<std::map<string, ControllerProfile>>(
|
||||
BaseDir::data() / "profiles.json",
|
||||
{
|
||||
{ DefaultProfile::Gamecube.name, DefaultProfile::Gamecube },
|
||||
{ DefaultProfile::XBox360.name, DefaultProfile::XBox360 }
|
||||
},
|
||||
parseProfiles,
|
||||
serializeProfiles
|
||||
);
|
||||
return s_cache;
|
||||
}
|
||||
|
||||
static FileCache<HashMap<Uuid,string>> &mappingsCache() {
|
||||
static FileCache<HashMap<Uuid,string>> s_cache = FileCache<HashMap<Uuid,string>>(
|
||||
BaseDir::data() / "devices.json",
|
||||
HashMap<Uuid,string>(),
|
||||
parseMappings,
|
||||
serializeMappings
|
||||
);
|
||||
return s_cache;
|
||||
}
|
||||
|
||||
|
||||
static FileCache<UiState> &uiStateCache() {
|
||||
static FileCache<UiState> s_cache = FileCache<UiState>(
|
||||
BaseDir::cache() / "ui-state.json",
|
||||
UiState::Default,
|
||||
JsonSerializer::parse<UiState>,
|
||||
JsonSerializer::serialize<UiState>
|
||||
);
|
||||
return s_cache;
|
||||
}
|
||||
|
||||
|
||||
|
||||
const AppSettings &FileController::loadAppSettings() {
|
||||
return appSettingsCache().get();
|
||||
}
|
||||
|
||||
void FileController::saveAppSettings( const AppSettings &settings ) {
|
||||
appSettingsCache().set( settings );
|
||||
}
|
||||
|
||||
const std::vector<ROM> &FileController::loadRomList() {
|
||||
return romListCache().get();
|
||||
}
|
||||
|
||||
void FileController::saveRomList( const std::vector<ROM> &roms ) {
|
||||
romListCache().set( roms );
|
||||
}
|
||||
|
||||
const std::vector<RomSource> &FileController::loadRomSources() {
|
||||
return romSourcesCache().get();
|
||||
}
|
||||
|
||||
void FileController::saveRomSources( const std::vector<RomSource> &sources ) {
|
||||
romSourcesCache().set( sources );
|
||||
}
|
||||
|
||||
const std::set<string> &FileController::loadTags() {
|
||||
return tagsCache().get();
|
||||
}
|
||||
|
||||
void FileController::saveTags( const std::set<string> &tags ) {
|
||||
tagsCache().set( tags );
|
||||
}
|
||||
|
||||
const std::map<string, ControllerProfile> &FileController::loadControllerProfiles() {
|
||||
return profilesCache().get();
|
||||
}
|
||||
|
||||
void FileController::saveControllerProfiles( const std::map<string, ControllerProfile> &profiles ) {
|
||||
profilesCache().set( profiles );
|
||||
}
|
||||
|
||||
const HashMap<Uuid,string> &FileController::loadControllerMappings() {
|
||||
return mappingsCache().get();
|
||||
}
|
||||
|
||||
void FileController::saveControllerMappings( const HashMap<Uuid,string> &mappings ) {
|
||||
mappingsCache().set( mappings );
|
||||
}
|
||||
|
||||
const UiState &FileController::loadUiState() {
|
||||
return uiStateCache().get();
|
||||
}
|
||||
|
||||
void FileController::saveUiState( const UiState &state ) {
|
||||
uiStateCache().set( state );
|
||||
}
|
40
src/core/file-controller.hpp
Normal file
@@ -0,0 +1,40 @@
|
||||
#ifndef SRC_CORE_FILE_CONTROLLER_HPP_
|
||||
#define SRC_CORE_FILE_CONTROLLER_HPP_
|
||||
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#include <map>
|
||||
#include "src/core/controller.hpp"
|
||||
#include "src/core/settings.hpp"
|
||||
#include "src/core/rom.hpp"
|
||||
#include "src/ui/ui-state.hpp"
|
||||
|
||||
namespace FileController {
|
||||
|
||||
const AppSettings &loadAppSettings();
|
||||
void saveAppSettings( const AppSettings &settings );
|
||||
|
||||
const std::vector<ROM> &loadRomList();
|
||||
void saveRomList( const std::vector<ROM> &roms );
|
||||
|
||||
const std::vector<RomSource> &loadRomSources();
|
||||
void saveRomSources( const std::vector<RomSource> &sources );
|
||||
|
||||
const std::set<string> &loadTags();
|
||||
void saveTags( const std::set<string> &tags );
|
||||
|
||||
const std::map<string, ControllerProfile> &loadControllerProfiles();
|
||||
void saveControllerProfiles( const std::map<string, ControllerProfile> &profiles );
|
||||
|
||||
const HashMap<Uuid,string> &loadControllerMappings();
|
||||
void saveControllerMappings( const HashMap<Uuid,string> &mappings );
|
||||
|
||||
const UiState &loadUiState();
|
||||
void saveUiState( const UiState &state );
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
#endif /* SRC_CORE_FILE_CONTROLLER_HPP_ */
|
15
src/core/filesystem.hpp
Normal file
@@ -0,0 +1,15 @@
|
||||
#ifndef SRC_CORE_FILESYSTEM_HPP_
|
||||
#define SRC_CORE_FILESYSTEM_HPP_
|
||||
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
template<> struct std::hash<fs::path> {
|
||||
inline size_t operator()(const fs::path &path) const noexcept {
|
||||
return hash_value( path );
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* SRC_CORE_FILESYSTEM_HPP_ */
|
31
src/core/flags.hpp
Normal file
@@ -0,0 +1,31 @@
|
||||
#ifndef DEFINE_FLAG_OPERATIONS
|
||||
|
||||
namespace Flags {
|
||||
template<typename E> inline bool has( E e, E x ) {
|
||||
return (e & x) != (E)0;
|
||||
};
|
||||
}
|
||||
|
||||
#define DEFINE_FLAG_OPERATIONS( EnumType, BackingType ) \
|
||||
inline EnumType operator|( EnumType f1, EnumType f2 ) { \
|
||||
return static_cast<EnumType>( static_cast<BackingType>( f1 ) | static_cast<BackingType>( f2 ) ); \
|
||||
} \
|
||||
inline EnumType operator&( EnumType f1, EnumType f2 ) { \
|
||||
return static_cast<EnumType>( static_cast<BackingType>( f1 ) & static_cast<BackingType>( f2 ) ); \
|
||||
} \
|
||||
inline EnumType operator^( EnumType f1, EnumType f2 ) { \
|
||||
return static_cast<EnumType>( static_cast<BackingType>( f1 ) ^ static_cast<BackingType>( f2 ) ); \
|
||||
} \
|
||||
inline EnumType operator~( EnumType f ) { \
|
||||
return static_cast<EnumType>( ~static_cast<BackingType>( f ) ); \
|
||||
} \
|
||||
inline EnumType &operator|=( EnumType &f1, EnumType f2 ) { \
|
||||
return f1 = f1 | f2; \
|
||||
} \
|
||||
inline EnumType &operator&=( EnumType &f1, EnumType f2 ) { \
|
||||
return f1 = f1 & f2; \
|
||||
} \
|
||||
inline EnumType &operator^=( EnumType &f1, EnumType f2 ) { \
|
||||
return f1 = f1 ^ f2; \
|
||||
}
|
||||
#endif
|
445
src/core/json.cpp
Normal file
@@ -0,0 +1,445 @@
|
||||
#include "src/core/json.hpp"
|
||||
|
||||
#include <cctype>
|
||||
#include <cstdlib>
|
||||
#include <cstdio>
|
||||
|
||||
using namespace JsonInternals;
|
||||
using namespace std;
|
||||
|
||||
const Json *const Json::UNDEFINED = new Json( JUndefined() );
|
||||
|
||||
inline static void notEof( istream &jsonStream ) {
|
||||
if( jsonStream.eof() ) {
|
||||
throw JsonReaderException( "Unexpected end of stream." );
|
||||
}
|
||||
}
|
||||
|
||||
inline static void require( istream &jsonStream, char character ) {
|
||||
notEof( jsonStream );
|
||||
const char c = jsonStream.get();
|
||||
if( c != character ) {
|
||||
throw JsonReaderException( "Unexpected character '"s + c + "' (expected '" + character + "')." );
|
||||
}
|
||||
}
|
||||
|
||||
inline static void skipWhitespace( istream &jsonStream ) {
|
||||
while( !jsonStream.eof() && isspace( jsonStream.peek() ) ) {
|
||||
jsonStream.get();
|
||||
}
|
||||
}
|
||||
|
||||
inline static char parseHexCode( istream &jsonStream ) {
|
||||
char code[5];
|
||||
bool isNull = true;
|
||||
for( int i = 0; i < 4; i++ ) {
|
||||
notEof( jsonStream );
|
||||
const char c = jsonStream.get();
|
||||
if( !( c >= '0' && c <= '9' ) &&
|
||||
!( c >= 'a' && c <= 'f' ) &&
|
||||
!( c >= 'A' && c <= 'F' )
|
||||
) throw JsonReaderException( "Unexpected character '"s + c + "' (expected hex digit)." );
|
||||
code[i] = c;
|
||||
isNull &= ( c == '0' );
|
||||
}
|
||||
|
||||
if( isNull ) {
|
||||
throw JsonReaderException( "Cannot have nul character in string." );
|
||||
}
|
||||
code[4] = '\0';
|
||||
|
||||
return (char)strtol( code, nullptr, 16 );
|
||||
}
|
||||
|
||||
static string parseString( istream &jsonStream ) {
|
||||
require( jsonStream, '"' );
|
||||
string text;
|
||||
while( !jsonStream.eof() ) {
|
||||
const char c = jsonStream.get();
|
||||
if( c == '"' ) {
|
||||
return text;
|
||||
} else if( c == '\\' ) {
|
||||
notEof( jsonStream );
|
||||
switch( char escapedChar = jsonStream.get() ) {
|
||||
case 'b': text += '\b'; break;
|
||||
case 'f': text += '\f'; break;
|
||||
case 'n': text += '\n'; break;
|
||||
case 'r': text += '\r'; break;
|
||||
case 't': text += '\t'; break;
|
||||
case 'u': text += parseHexCode( jsonStream ); break;
|
||||
default: text += escapedChar; break;
|
||||
}
|
||||
} else {
|
||||
text += c;
|
||||
}
|
||||
}
|
||||
throw JsonReaderException( "Unterminated string." );
|
||||
}
|
||||
|
||||
static JObject parseObject( istream &jsonStream ) {
|
||||
require( jsonStream, '{' );
|
||||
JObject object;
|
||||
|
||||
skipWhitespace( jsonStream );
|
||||
notEof( jsonStream );
|
||||
if( jsonStream.peek() == '}' ) {
|
||||
jsonStream.get();
|
||||
return object;
|
||||
}
|
||||
|
||||
while( true ) {
|
||||
string propertyName = parseString( jsonStream );
|
||||
skipWhitespace( jsonStream );
|
||||
notEof( jsonStream );
|
||||
char colon = jsonStream.get();
|
||||
if( colon != ':' ) {
|
||||
throw JsonReaderException( "Unexpected character '"s + colon + "' (expected colon)." );
|
||||
}
|
||||
object[propertyName] = Json::parse( jsonStream );
|
||||
skipWhitespace( jsonStream );
|
||||
notEof( jsonStream );
|
||||
const char delim = jsonStream.get();
|
||||
if( delim == '}' ) {
|
||||
break;
|
||||
} else if( delim != ',' ) {
|
||||
throw JsonReaderException( "Unexpected character '"s + colon + "' (expected comma or close brace)." );
|
||||
}
|
||||
skipWhitespace( jsonStream );
|
||||
}
|
||||
return object;
|
||||
}
|
||||
|
||||
static JArray parseArray( istream &jsonStream ) {
|
||||
require( jsonStream, '[' );
|
||||
JArray jArray;
|
||||
|
||||
skipWhitespace( jsonStream );
|
||||
notEof( jsonStream );
|
||||
if( jsonStream.peek() == ']' ) {
|
||||
jsonStream.get();
|
||||
return jArray;
|
||||
}
|
||||
|
||||
while( true ) {
|
||||
skipWhitespace( jsonStream );
|
||||
jArray.push_back( Json::parse( jsonStream ) );
|
||||
skipWhitespace( jsonStream );
|
||||
notEof( jsonStream );
|
||||
const char delimiter = jsonStream.get();
|
||||
if( delimiter == ']' ) {
|
||||
break;
|
||||
} else if( delimiter != ',' ) {
|
||||
throw JsonReaderException( "Unexpected character '"s + delimiter + "' (expected comma or close bracket)." );
|
||||
}
|
||||
}
|
||||
return jArray;
|
||||
}
|
||||
|
||||
static constexpr bool isDigit( char c ) {
|
||||
return c >= '0' && c <= '9';
|
||||
}
|
||||
|
||||
enum class NumberParseState {
|
||||
Sign,
|
||||
FirstDigit,
|
||||
Integer,
|
||||
Delimiter,
|
||||
FirstFractionDigit,
|
||||
Fraction,
|
||||
ExponentSign,
|
||||
FirstExponentDigit,
|
||||
Exponent
|
||||
};
|
||||
|
||||
inline static Json parseNumber( istream &jsonStream ) {
|
||||
notEof( jsonStream );
|
||||
|
||||
string numberString;
|
||||
bool isNegative = false;
|
||||
NumberParseState state = NumberParseState::Sign;
|
||||
while( true ) {
|
||||
const char c = jsonStream.get();
|
||||
numberString += c;
|
||||
|
||||
switch( state ) {
|
||||
case NumberParseState::Sign:
|
||||
if( c == '-' ) {
|
||||
state = NumberParseState::FirstDigit;
|
||||
isNegative = true;
|
||||
break;
|
||||
}
|
||||
[[fallthrough]];
|
||||
case NumberParseState::FirstDigit:
|
||||
state = (c == '0') ? NumberParseState::Delimiter : NumberParseState::Integer;
|
||||
break;
|
||||
case NumberParseState::Integer:
|
||||
if( c == '.' ) {
|
||||
state = NumberParseState::FirstFractionDigit;
|
||||
} else if( c == 'e' || c == 'E' ) {
|
||||
state = NumberParseState::ExponentSign;
|
||||
} else if( !isDigit( c ) ) {
|
||||
throw JsonReaderException( "Invalid number format." );
|
||||
}
|
||||
break;
|
||||
case NumberParseState::Delimiter:
|
||||
if( c == '.' ) {
|
||||
state = NumberParseState::FirstFractionDigit;
|
||||
} else if( c == 'e' || c == 'E' ) {
|
||||
state = NumberParseState::ExponentSign;
|
||||
} else throw JsonReaderException( "Invalid number format." );
|
||||
break;
|
||||
case NumberParseState::FirstFractionDigit:
|
||||
if( !isDigit( c ) ) {
|
||||
throw JsonReaderException( "Invalid number format." );
|
||||
}
|
||||
state = NumberParseState::Fraction;
|
||||
break;
|
||||
case NumberParseState::Fraction:
|
||||
if( c == 'e' || c == 'E' ) {
|
||||
state = NumberParseState::ExponentSign;
|
||||
}
|
||||
break;
|
||||
case NumberParseState::ExponentSign:
|
||||
if( c == '+' || c == '-' ) {
|
||||
state = NumberParseState::FirstExponentDigit;
|
||||
break;
|
||||
} else if( isDigit( c ) ) {
|
||||
state = NumberParseState::Exponent;
|
||||
break;
|
||||
} else throw JsonReaderException( "Invalid number format." );
|
||||
case NumberParseState::FirstExponentDigit:
|
||||
if( !isDigit( c ) ) {
|
||||
throw JsonReaderException( "Invalid number format." );
|
||||
}
|
||||
state = NumberParseState::Exponent;
|
||||
break;
|
||||
case NumberParseState::Exponent:
|
||||
if( !isDigit( c ) ) {
|
||||
throw JsonReaderException( "Invalid number format." );
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw JsonReaderException( "Internal Error." );
|
||||
}
|
||||
|
||||
if( jsonStream.eof() ) {
|
||||
break;
|
||||
} else {
|
||||
const char nextChar = jsonStream.peek();
|
||||
if( isspace( nextChar ) || nextChar == ']' || nextChar == '}' || nextChar == ',' ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( state == NumberParseState::Fraction || state == NumberParseState::Exponent ) {
|
||||
return Json( stod( numberString ) );
|
||||
} else if( state == NumberParseState::Integer || state == NumberParseState::Delimiter ) {
|
||||
return Json( isNegative ? (int64)stol( numberString ) : (int64)stoul( numberString ) );
|
||||
} else throw JsonReaderException( "Invalid number format." );
|
||||
}
|
||||
|
||||
inline static bool parseBoolean( istream &jsonStream ) {
|
||||
const char firstChar = jsonStream.get();
|
||||
if( firstChar == 't' ) {
|
||||
require( jsonStream, 'r' );
|
||||
require( jsonStream, 'u' );
|
||||
require( jsonStream, 'e' );
|
||||
return true;
|
||||
} else if( firstChar == 'f' ) {
|
||||
require( jsonStream, 'a' );
|
||||
require( jsonStream, 'l' );
|
||||
require( jsonStream, 's' );
|
||||
require( jsonStream, 'e' );
|
||||
return false;
|
||||
} else throw JsonReaderException( "Unexpected character '"s + firstChar + "' (expected boolean)." );
|
||||
}
|
||||
|
||||
inline static JNull parseNull( istream &jsonStream ) {
|
||||
require( jsonStream, 'n' );
|
||||
require( jsonStream, 'u' );
|
||||
require( jsonStream, 'l' );
|
||||
require( jsonStream, 'l' );
|
||||
return JNull();
|
||||
}
|
||||
|
||||
Json Json::parse( istream &jsonStream ) {
|
||||
skipWhitespace( jsonStream );
|
||||
notEof( jsonStream );
|
||||
|
||||
switch( char token = jsonStream.peek() ) {
|
||||
case '{':
|
||||
return Json( parseObject( jsonStream ) );
|
||||
case '[':
|
||||
return Json( parseArray( jsonStream ) );
|
||||
case '"':
|
||||
return Json( parseString( jsonStream ) );
|
||||
case 't':
|
||||
case 'f':
|
||||
return Json( parseBoolean( jsonStream ) );
|
||||
case 'n':
|
||||
return Json( parseNull( jsonStream ) );
|
||||
default:
|
||||
if( token == '-' || ( token >= '0' && token <= '9' ) ) {
|
||||
return parseNumber( jsonStream );
|
||||
}
|
||||
throw JsonReaderException( "Unexpected character '"s + token + "' (expected value)." );
|
||||
}
|
||||
}
|
||||
|
||||
inline static void indent( ostream *stream, int tabs ) {
|
||||
for( int i = 0; i < tabs; i++ ) {
|
||||
stream->put( '\t' );
|
||||
}
|
||||
}
|
||||
|
||||
string JsonWriter::quoteAndEscape( const string &value ) {
|
||||
string qstr = "\"";
|
||||
qstr.reserve( value.size() + 3 );
|
||||
for( char c : value ) {
|
||||
switch( c ) {
|
||||
case '"':
|
||||
qstr += "\\\"";
|
||||
break;
|
||||
case '\\':
|
||||
qstr += "\\\\";
|
||||
break;
|
||||
[[unlikely]]
|
||||
case '\b':
|
||||
qstr += "\\b";
|
||||
break;
|
||||
[[unlikely]]
|
||||
case '\f':
|
||||
qstr += "\\f";
|
||||
break;
|
||||
case '\n':
|
||||
qstr += "\\n";
|
||||
break;
|
||||
case '\r':
|
||||
qstr += "\\r";
|
||||
break;
|
||||
case '\t':
|
||||
qstr += "\\t";
|
||||
break;
|
||||
[[likely]]
|
||||
default:
|
||||
if( isprint( c ) ) {
|
||||
qstr += c;
|
||||
} else {
|
||||
qstr += "\\u";
|
||||
char hexCode[5];
|
||||
snprintf( hexCode, 5, "%04X", c );
|
||||
qstr.append( hexCode, 4 );
|
||||
}
|
||||
}
|
||||
}
|
||||
qstr += '"';
|
||||
return qstr;
|
||||
}
|
||||
|
||||
void JsonWriter::writeRawValue( const string &value ) {
|
||||
if( m_state.top().expectPropertyName ) {
|
||||
throw JsonWriterException();
|
||||
}
|
||||
|
||||
JsonWriterState &state = m_state.top();
|
||||
switch( state.context ) {
|
||||
case ContainerType::None:
|
||||
if( !state.empty ) {
|
||||
throw JsonWriterException();
|
||||
}
|
||||
state.empty = false;
|
||||
m_out->write( value.c_str(), value.size() );
|
||||
break;
|
||||
case ContainerType::Object:
|
||||
if( state.expectPropertyName ) {
|
||||
throw JsonWriterException();
|
||||
}
|
||||
m_out->write( value.c_str(), value.size() );
|
||||
state.expectPropertyName = true;
|
||||
break;
|
||||
case ContainerType::Array:
|
||||
if( !state.empty ) {
|
||||
m_out->put( ',' );
|
||||
}
|
||||
state.empty = false;
|
||||
if( m_pretty ) {
|
||||
m_out->put( '\n' );
|
||||
indent( m_out, m_state.size() - 1 );
|
||||
}
|
||||
m_out->write( value.c_str(), value.size() );
|
||||
break;
|
||||
default:
|
||||
throw JsonWriterException( "Internal Error" );
|
||||
}
|
||||
}
|
||||
|
||||
void JsonWriter::writePropertyName( const string &name ) {
|
||||
JsonWriterState &objectState = m_state.top();
|
||||
if( objectState.context != ContainerType::Object || !objectState.expectPropertyName ) {
|
||||
throw JsonWriterException();
|
||||
}
|
||||
|
||||
if( !objectState.empty ) {
|
||||
m_out->put( ',' );
|
||||
}
|
||||
objectState.expectPropertyName = false;
|
||||
objectState.empty = false;
|
||||
if( m_pretty ) {
|
||||
m_out->put( '\n' );
|
||||
indent( m_out, m_state.size() - 1 );
|
||||
}
|
||||
|
||||
const string &propertyName = quoteAndEscape( name );
|
||||
m_out->write( propertyName.c_str(), propertyName.size() );
|
||||
m_out->put( ':' );
|
||||
if( m_pretty ) {
|
||||
m_out->put( ' ' );
|
||||
}
|
||||
}
|
||||
|
||||
void JsonWriter::writeObjectStart() {
|
||||
writeRawValue( "{" );
|
||||
m_state.push({ true, true, ContainerType::Object });
|
||||
}
|
||||
|
||||
void JsonWriter::writeObjectEnd() {
|
||||
const JsonWriterState &objectState = m_state.top();
|
||||
if( objectState.context != ContainerType::Object || !objectState.expectPropertyName ) {
|
||||
throw JsonWriterException();
|
||||
}
|
||||
|
||||
if( !m_pretty || objectState.empty ) {
|
||||
m_out->put( '}' );
|
||||
m_state.pop();
|
||||
return;
|
||||
}
|
||||
|
||||
m_state.pop();
|
||||
m_out->put( '\n' );
|
||||
indent( m_out, m_state.size() - 1 );
|
||||
m_out->put( '}' );
|
||||
}
|
||||
|
||||
void JsonWriter::writeArrayStart() {
|
||||
writeRawValue( "[" );
|
||||
m_state.push({ true, false, ContainerType::Array });
|
||||
}
|
||||
|
||||
void JsonWriter::writeArrayEnd() {
|
||||
const JsonWriterState &arrayState = m_state.top();
|
||||
if( arrayState.context != ContainerType::Array ) {
|
||||
throw JsonWriterException();
|
||||
}
|
||||
|
||||
if( !m_pretty || arrayState.empty ) {
|
||||
m_out->put( ']' );
|
||||
m_state.pop();
|
||||
return;
|
||||
}
|
||||
|
||||
m_state.pop();
|
||||
m_out->put( '\n' );
|
||||
indent( m_out, m_state.size() - 1 );
|
||||
m_out->put( ']' );
|
||||
}
|
323
src/core/json.hpp
Normal file
@@ -0,0 +1,323 @@
|
||||
#ifndef CORE_JSON_HPP_
|
||||
#define CORE_JSON_HPP_
|
||||
|
||||
#include <vector>
|
||||
#include <variant>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <optional>
|
||||
#include <iostream>
|
||||
#include <type_traits>
|
||||
#include <limits>
|
||||
#include <cmath>
|
||||
#include <stack>
|
||||
#include <stdexcept>
|
||||
#include "src/types.hpp"
|
||||
#include "src/core/traceable-exception.hpp"
|
||||
|
||||
class Json;
|
||||
|
||||
typedef std::vector<Json> JArray;
|
||||
|
||||
class JsonReaderException : public std::runtime_error, TraceableException {
|
||||
|
||||
public:
|
||||
JsonReaderException( const string &reason ) :
|
||||
std::runtime_error( "Failed to parse JSON: " + reason ),
|
||||
TraceableException() {}
|
||||
|
||||
};
|
||||
|
||||
class JsonWriterException : public std::runtime_error, TraceableException {
|
||||
|
||||
public:
|
||||
JsonWriterException() :
|
||||
std::runtime_error( "Failed to serialize to JSON." ),
|
||||
TraceableException() {}
|
||||
|
||||
JsonWriterException( const string &reason ) :
|
||||
std::runtime_error( "Failed to serialize to JSON: " + reason ),
|
||||
TraceableException() {}
|
||||
|
||||
};
|
||||
|
||||
namespace JsonInternals {
|
||||
using std::string;
|
||||
|
||||
struct JNull final {};
|
||||
struct JUndefined final {};
|
||||
typedef std::map<string,Json> JObject;
|
||||
typedef std::variant<int64,double,string,bool,JArray,JObject,JNull,JUndefined> JsonType;
|
||||
|
||||
template<typename TT, typename TS> constexpr bool fits( TS value ) {
|
||||
if constexpr( std::is_floating_point_v<TT> || std::is_floating_point_v<TS> ) {
|
||||
return(
|
||||
value >= std::numeric_limits<TT>::min() &&
|
||||
value <= std::numeric_limits<TT>::max()
|
||||
);
|
||||
} else if constexpr( std::is_signed_v<TT> ) {
|
||||
if constexpr( std::is_signed_v<TS> ) {
|
||||
return(
|
||||
value >= std::numeric_limits<TT>::min() &&
|
||||
value <= std::numeric_limits<TT>::max()
|
||||
);
|
||||
} else {
|
||||
return value <= (std::make_unsigned_t<TT>)std::numeric_limits<TT>::max();
|
||||
}
|
||||
} else {
|
||||
return value >= 0 && (std::make_unsigned_t<TS>)value <= std::numeric_limits<TT>::max();
|
||||
}
|
||||
}
|
||||
|
||||
enum class ContainerType {
|
||||
None,
|
||||
Object,
|
||||
Array
|
||||
};
|
||||
|
||||
struct JsonWriterState {
|
||||
bool empty;
|
||||
bool expectPropertyName;
|
||||
ContainerType context;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
class Json final {
|
||||
private:
|
||||
const JsonInternals::JsonType m_value;
|
||||
|
||||
static const Json *const UNDEFINED;
|
||||
|
||||
public:
|
||||
template<typename T> T get() const;
|
||||
template<typename T> std::optional<T> tryGet() const;
|
||||
template<typename T> inline T getOrDefault( const T &defaultValue ) const {
|
||||
return tryGet<T>().value_or( defaultValue );
|
||||
}
|
||||
|
||||
Json() : m_value( JsonInternals::JUndefined() ) {};
|
||||
Json( const JsonInternals::JsonType &value ) : m_value( value ) {};
|
||||
Json( const Json &other ) : m_value( other.m_value ) {};
|
||||
|
||||
Json &operator=( Json &&other ) {
|
||||
new(this) Json( other );
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline const Json &operator[]( const std::string &property ) const {
|
||||
const JsonInternals::JObject *maybeObject = std::get_if<JsonInternals::JObject>( &m_value );
|
||||
if( maybeObject == nullptr ) {
|
||||
return *UNDEFINED;
|
||||
}
|
||||
auto propertyValue = maybeObject->find( property );
|
||||
if( propertyValue == maybeObject->end() ) {
|
||||
return *UNDEFINED;
|
||||
}
|
||||
return propertyValue->second;
|
||||
}
|
||||
|
||||
inline const Json &operator[]( int i ) const {
|
||||
const JArray *maybeArray = std::get_if<JArray>( &m_value );
|
||||
if( maybeArray == nullptr || i >= (int)maybeArray->size() ) {
|
||||
return *UNDEFINED;
|
||||
}
|
||||
return maybeArray->at( i );
|
||||
}
|
||||
|
||||
inline const JArray &array() const {
|
||||
return std::get<JArray>( m_value );
|
||||
}
|
||||
|
||||
inline bool exists() const {
|
||||
return !std::holds_alternative<JsonInternals::JUndefined>( m_value );
|
||||
}
|
||||
|
||||
inline bool isNull() const {
|
||||
return std::holds_alternative<JsonInternals::JNull>( m_value );
|
||||
}
|
||||
|
||||
inline bool isArray() const {
|
||||
return std::holds_alternative<JArray>( m_value );
|
||||
}
|
||||
|
||||
inline bool isObject() const {
|
||||
return std::holds_alternative<JsonInternals::JObject>( m_value );
|
||||
}
|
||||
|
||||
inline bool hasValue() const {
|
||||
return exists() && !isNull();
|
||||
}
|
||||
|
||||
static Json parse( std::istream &jsonStream );
|
||||
|
||||
};
|
||||
|
||||
template<typename T> T Json::get() const {
|
||||
if( std::holds_alternative<JsonInternals::JUndefined>( m_value ) ) {
|
||||
throw JsonReaderException( "Value does not exist in JSON." );
|
||||
}
|
||||
|
||||
if constexpr( std::is_enum_v<T> ) {
|
||||
return (T)get<int64>();
|
||||
} else if constexpr( std::is_same_v<T,bool> ) {
|
||||
return std::get<bool>( m_value );
|
||||
} else if constexpr( std::is_integral_v<T> ) {
|
||||
const int64 *maybeLong = std::get_if<int64>( &m_value );
|
||||
if( maybeLong != nullptr ) {
|
||||
if( JsonInternals::fits<T>( *maybeLong ) ) {
|
||||
return (T)*maybeLong;
|
||||
} else throw JsonReaderException( "Number out of range of the requested type." );
|
||||
} else {
|
||||
const double value = std::get<double>( m_value );
|
||||
if( value != std::trunc( value ) ) {
|
||||
throw JsonReaderException( "Number is not an integer." );
|
||||
} if( JsonInternals::fits<T>( value ) ) {
|
||||
return (T)value;
|
||||
} else throw JsonReaderException( "Number out of range of the requested type." );
|
||||
}
|
||||
} else if constexpr( std::is_floating_point_v<T> ) {
|
||||
const double *maybeDouble = std::get_if<double>( &m_value );
|
||||
if( maybeDouble != nullptr ) {
|
||||
return (T)*maybeDouble;
|
||||
} else {
|
||||
return (T)std::get<int64>( m_value );
|
||||
}
|
||||
} else if constexpr( std::is_same_v<T,string> ) {
|
||||
return std::get<string>( m_value );
|
||||
} else {
|
||||
throw JsonReaderException( "Invalid Type" );
|
||||
//static_assert( false, "Invalid Type" );
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T> std::optional<T> Json::tryGet() const {
|
||||
std::optional<T> value;
|
||||
if( std::holds_alternative<JsonInternals::JUndefined>( m_value ) ) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if constexpr( std::is_enum_v<T> ) {
|
||||
const std::optional<int64> longVal = tryGet<int64>();
|
||||
return longVal.has_value() ? std::optional<T>( (T)longVal.value() ) : std::optional<T>();
|
||||
} else if constexpr( std::is_same_v<T,bool> ) {
|
||||
const bool *maybeBool = std::get_if<bool>( &m_value );
|
||||
if( maybeBool != nullptr ) {
|
||||
value = *maybeBool;
|
||||
}
|
||||
} else if constexpr( std::is_integral_v<T> ) {
|
||||
const int64 *maybeLong = std::get_if<int64>( &m_value );
|
||||
if( maybeLong != nullptr && JsonInternals::fits<T>( *maybeLong ) ) {
|
||||
value = (T)*maybeLong;
|
||||
} else {
|
||||
const double *maybeDouble = std::get_if<double>( &m_value );
|
||||
if( maybeDouble != nullptr && *maybeDouble == std::trunc( *maybeDouble ) && JsonInternals::fits<T>( *maybeDouble ) ) {
|
||||
value = (T)*maybeDouble;
|
||||
}
|
||||
}
|
||||
} else if constexpr( std::is_floating_point_v<T> ) {
|
||||
const double *maybeValue = std::get_if<double>( &m_value );
|
||||
if( maybeValue != nullptr ) {
|
||||
value = (T)*maybeValue;
|
||||
} else {
|
||||
const int64 *maybeLong = std::get_if<int64>( &m_value );
|
||||
if( maybeLong != nullptr ) {
|
||||
return (T)*maybeLong;
|
||||
}
|
||||
}
|
||||
} else if( std::is_same_v<T,std::string> ) {
|
||||
const std::string *maybeString = std::get_if<std::string>( &m_value );
|
||||
if( maybeString != nullptr ) {
|
||||
value = *maybeString;
|
||||
}
|
||||
} else {
|
||||
throw JsonReaderException( "Invalid Type" );
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
class JsonWriter final {
|
||||
private:
|
||||
std::ostream *const m_out;
|
||||
const bool m_pretty;
|
||||
std::stack<JsonInternals::JsonWriterState> m_state;
|
||||
|
||||
void writeRawValue( const string &value );
|
||||
string quoteAndEscape( const string &value );
|
||||
|
||||
public:
|
||||
JsonWriter( std::ostream *stream, bool humanReadable ) :
|
||||
m_out( stream ),
|
||||
m_pretty( humanReadable )
|
||||
{
|
||||
m_state.push({ true, false, JsonInternals::ContainerType::None });
|
||||
}
|
||||
|
||||
inline void writeNull() {
|
||||
writeRawValue( "null" );
|
||||
}
|
||||
|
||||
inline void writeBool( bool value ) {
|
||||
writeRawValue( value ? "true" : "false" );
|
||||
}
|
||||
|
||||
inline void writeNumber( long value ) {
|
||||
writeRawValue( std::to_string( value ) );
|
||||
}
|
||||
|
||||
inline void writeNumber( ulong value ) {
|
||||
writeRawValue( std::to_string( value ) );
|
||||
}
|
||||
|
||||
inline void writeNumber( double value ) {
|
||||
writeRawValue( std::to_string( value ) );
|
||||
}
|
||||
|
||||
inline void writeString( const string &value ) {
|
||||
writeRawValue( quoteAndEscape( value ) );
|
||||
}
|
||||
|
||||
void writeObjectStart();
|
||||
void writeObjectEnd();
|
||||
void writeArrayStart();
|
||||
void writeArrayEnd();
|
||||
|
||||
void writePropertyName( const string &name );
|
||||
|
||||
template<typename T> inline void writeProperty( const string &name, const T &value ) {
|
||||
writePropertyName( name );
|
||||
if constexpr( std::is_same_v<T,std::nullptr_t> ) {
|
||||
writeNull();
|
||||
} else if constexpr( std::is_same_v<T,bool> ) {
|
||||
writeBool( value );
|
||||
} else if constexpr( std::is_same_v<T,string> || std::is_same_v<T,char*> || std::is_same_v<T,const char*> ) {
|
||||
writeString( value );
|
||||
} else if constexpr( std::is_integral_v<T> && std::is_signed_v<T> ) {
|
||||
writeNumber( (long)value );
|
||||
} else if constexpr( std::is_integral_v<T> && std::is_unsigned_v<T> ) {
|
||||
writeNumber( (ulong)value );
|
||||
} else if constexpr( std::is_floating_point_v<T> ) {
|
||||
writeNumber( (double)value );
|
||||
} else if constexpr( std::is_enum_v<T> ) {
|
||||
if( std::is_signed_v<std::underlying_type_t<T>> ) {
|
||||
writeNumber( (long)value );
|
||||
} else {
|
||||
writeNumber( (ulong)value );
|
||||
}
|
||||
} else {
|
||||
throw JsonReaderException( "Invalid Type" );
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
namespace JsonSerializer {
|
||||
template<typename T> void serialize( JsonWriter &jw, const T &obj ) {
|
||||
throw JsonWriterException( "No JSON serializer has been registered for this type" );
|
||||
}
|
||||
template<typename T> T parse( const Json &json ) {
|
||||
throw JsonReaderException( "No JSON deserializer has been registered for this type" );
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* CORE_JSON_HPP_ */
|
104
src/core/preset-controllers.cpp
Normal file
@@ -0,0 +1,104 @@
|
||||
#include "preset-controllers.hpp"
|
||||
|
||||
#include <unordered_set>
|
||||
|
||||
static const HashSet<ControllerId> s_gamecube = {
|
||||
{ 0x057E, 0x0337 }, // Official
|
||||
{ 0x0079, 0x1844 } // Mayflash
|
||||
};
|
||||
|
||||
static const HashSet<ControllerId> s_xbox360 = {
|
||||
{ 0x045E, 0x028E }, // Official, wired
|
||||
{ 0x045E, 0x028F }, // Official, wireless
|
||||
{ 0x0E6F, 0x0213 }, // Afterglow
|
||||
{ 0x0E6F, 0x0413 }, // Afterglow AX.1
|
||||
{ 0x12AB, 0x0301 }, // Afterglow (Hong Kong)
|
||||
{ 0x046D, 0x0242 }, // Logitech
|
||||
{ 0x0738, 0x4716 }, // Mad Catz, wired
|
||||
{ 0x0738, 0x4726 }, // Mad Cats, wireless
|
||||
{ 0x0E6F, 0x011F }, // Rock Candy, wired
|
||||
{ 0x0E6F, 0x021F }, // Rock Candy, wireless
|
||||
{ 0x0F0D, 0x000C }, // Hori
|
||||
{ 0x146B, 0x0601 }, // BigBen
|
||||
{ 0x15E4, 0x3F0A }, // Numark Airflo
|
||||
{ 0x24C6, 0x5300 }, // ThrustMaster (Mini ProEX)
|
||||
{ 0x24C6, 0x5303 }, // ThrustMaster (Airflo)
|
||||
{ 0x24C6, 0x530A }, // ThrustMaster (ProEX)
|
||||
{ 0x24C6, 0xFAFD }, // ThrustMaster (Afterglow)
|
||||
{ 0x24C6, 0xFAFE } // ThrustMaster (Rock Candy)
|
||||
};
|
||||
|
||||
ControllerType getControllerType( const ControllerId &controllerId ) {
|
||||
if( s_gamecube.count( controllerId ) > 0 ) {
|
||||
return ControllerType::Gamecube;
|
||||
}
|
||||
|
||||
if( s_xbox360.count( controllerId ) > 0 ) {
|
||||
return ControllerType::XBox360;
|
||||
}
|
||||
|
||||
return ControllerType::Other;
|
||||
}
|
||||
|
||||
const ControllerProfile DefaultProfile::Gamecube = {
|
||||
/* Name */ "Default Gamecube Profile",
|
||||
/* Bindings */ {
|
||||
/* AnalogUp */ { BindingType::AxisNegative, 1 },
|
||||
/* AnalogDown */ { BindingType::AxisPositive, 1 },
|
||||
/* AnalogLeft */ { BindingType::AxisNegative, 0 },
|
||||
/* AnalogRight */ { BindingType::AxisPositive, 0 },
|
||||
/* CUp */ { BindingType::AxisNegative, 4 },
|
||||
/* CDown */ { BindingType::AxisPositive, 4 },
|
||||
/* CLeft */ { BindingType::AxisNegative, 3 },
|
||||
/* CRight */ { BindingType::AxisPositive, 3 },
|
||||
/* DPadUp */ { BindingType::Button, 8 },
|
||||
/* DPadDown */ { BindingType::Button, 9 },
|
||||
/* DPadLeft */ { BindingType::Button, 10 },
|
||||
/* DPadRight */ { BindingType::Button, 11 },
|
||||
/* ButtonA */ { BindingType::Button, 0 },
|
||||
/* ButtonB */ { BindingType::Button, 3 },
|
||||
/* TriggerL */ { BindingType::Button, 6 },
|
||||
/* TriggerZ */ { BindingType::AxisPositive, 2 },
|
||||
/* TriggerR */ { BindingType::AxisPositive, 5 },
|
||||
/* Start */ { BindingType::Button, 7 },
|
||||
/* SaveState */ { BindingType::None, 0 },
|
||||
/* LoadState */ { BindingType::None, 0 }
|
||||
},
|
||||
/* Sensitivity */ 1.15,
|
||||
/* Deadzone */ 0.15,
|
||||
/* Rumble */ false
|
||||
};
|
||||
|
||||
const ControllerProfile DefaultProfile::XBox360 = {
|
||||
/* Name */ "Default XBox360 Profile",
|
||||
/* Bindings */ {
|
||||
/* AnalogUp */ { BindingType::AxisNegative, 1 },
|
||||
/* AnalogDown */ { BindingType::AxisPositive, 1 },
|
||||
/* AnalogLeft */ { BindingType::AxisNegative, 0 },
|
||||
/* AnalogRight */ { BindingType::AxisPositive, 0 },
|
||||
/* CUp */ { BindingType::AxisNegative, 3 },
|
||||
/* CDown */ { BindingType::AxisPositive, 3 },
|
||||
/* CLeft */ { BindingType::AxisNegative, 2 },
|
||||
/* CRight */ { BindingType::AxisPositive, 2 },
|
||||
/* DPadUp */ { BindingType::Button, 11 },
|
||||
/* DPadDown */ { BindingType::Button, 12 },
|
||||
/* DPadLeft */ { BindingType::Button, 13 },
|
||||
/* DPadRight */ { BindingType::Button, 14 },
|
||||
/* ButtonA */ { BindingType::Button, 0 },
|
||||
/* ButtonB */ { BindingType::Button, 2 },
|
||||
/* TriggerL */ { BindingType::Button, 10 },
|
||||
/* TriggerZ */ { BindingType::AxisPositive, 4 },
|
||||
/* TriggerR */ { BindingType::AxisPositive, 5 },
|
||||
/* Start */ { BindingType::Button, 6 },
|
||||
/* SaveState */ { BindingType::None, 0 },
|
||||
/* LoadState */ { BindingType::None, 0 }
|
||||
},
|
||||
/* Sensitivity */ 1.0,
|
||||
/* Deadzone */ 0.15,
|
||||
/* Rumble */ false
|
||||
};
|
||||
|
||||
bool DefaultProfile::exists( const string &name ) {
|
||||
return name == DefaultProfile::Gamecube.name || name == DefaultProfile::XBox360.name;
|
||||
}
|
||||
|
22
src/core/preset-controllers.hpp
Normal file
@@ -0,0 +1,22 @@
|
||||
#ifndef SRC_CORE_PRESET_CONTROLLERS_HPP_
|
||||
#define SRC_CORE_PRESET_CONTROLLERS_HPP_
|
||||
|
||||
#include "src/core/controller.hpp"
|
||||
|
||||
enum class ControllerType {
|
||||
Gamecube,
|
||||
XBox360,
|
||||
Other
|
||||
};
|
||||
|
||||
ControllerType getControllerType( const ControllerId &controllerId );
|
||||
|
||||
namespace DefaultProfile {
|
||||
extern const ControllerProfile Gamecube;
|
||||
extern const ControllerProfile XBox360;
|
||||
|
||||
extern bool exists( const string &name );
|
||||
}
|
||||
|
||||
|
||||
#endif /* SRC_CORE_PRESET_CONTROLLERS_HPP_ */
|
568
src/core/retroarch.cpp
Normal file
@@ -0,0 +1,568 @@
|
||||
#include "src/core/retroarch.hpp"
|
||||
|
||||
#include "src/polyfill/base-directory.hpp"
|
||||
#include "src/core/file-controller.hpp"
|
||||
#include "src/input/gamepad-controller.hpp"
|
||||
#include <map>
|
||||
#include <queue>
|
||||
#include <unordered_map>
|
||||
#include <cstdio>
|
||||
#include <fstream>
|
||||
#include <regex>
|
||||
#include <cstring>
|
||||
|
||||
#ifndef _WIN32
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
bool RetroArch::hasFlatpakInstall() {
|
||||
return(
|
||||
std::system( "which flatpak > /dev/null" ) == 0 &&
|
||||
std::system( "flatpak info org.libretro.RetroArch > /dev/null" ) == 0
|
||||
);
|
||||
}
|
||||
|
||||
fs::path RetroArch::configPath( bool useFlatpakInstall ) {
|
||||
if( useFlatpakInstall ) {
|
||||
return BaseDir::home() / ".var/app/org.libretro.RetroArch/config/retroarch/retroarch.cfg";
|
||||
} else {
|
||||
return BaseDir::config().parent_path() / "retroarch/retroarch.cfg";
|
||||
}
|
||||
}
|
||||
|
||||
static inline string escapePath( const fs::path &path ) {
|
||||
string escapedPath = "";
|
||||
for( const char c : path.string() ) {
|
||||
if( c == '\"' || c == '\\' ) {
|
||||
escapedPath += '\\';
|
||||
}
|
||||
escapedPath += c;
|
||||
}
|
||||
return escapedPath;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static fs::path expand_path_helper( const fs::path &path ) {
|
||||
const string pathStr = path.string();
|
||||
if( pathStr == "~" ) {
|
||||
return BaseDir::home();
|
||||
} else if( pathStr.length() >= 2 && pathStr[0] == '~' && (pathStr[1] == '/' || pathStr[1] == '\\' ) ) {
|
||||
return BaseDir::home() / fs::path( &path.c_str()[2] );
|
||||
} else return path;
|
||||
}
|
||||
|
||||
static inline fs::path getBaseConfigDirectory() {
|
||||
const AppSettings &settings = FileController::loadAppSettings();
|
||||
#ifdef _WIN32
|
||||
return expand_path_helper( settings.retroInstallDir );
|
||||
#else
|
||||
return RetroArch::configPath( settings.usingFlatpak ).parent_path();
|
||||
#endif
|
||||
}
|
||||
|
||||
static fs::path expand_path( const fs::path &path ) {
|
||||
const string pathStr = path.string();
|
||||
if( pathStr == ":" ) {
|
||||
return getBaseConfigDirectory();
|
||||
} else if( pathStr.length() >= 2 && pathStr[0] == ':' && (pathStr[1] == '/' || pathStr[1] == '\\' ) ) {
|
||||
return getBaseConfigDirectory() / fs::path( &path.c_str()[2] );
|
||||
} else return expand_path_helper( path );
|
||||
}
|
||||
|
||||
static inline bool stringEndsWith( const string &str, const string &suffix ) {
|
||||
if( str.length() < suffix.length() ) return false;
|
||||
return std::strcmp( &str.c_str()[str.length() - suffix.length()], suffix.c_str() ) == 0;
|
||||
}
|
||||
|
||||
static const char* s_plugins[] = {
|
||||
"auto",
|
||||
"parallel",
|
||||
"glide64",
|
||||
"angrylion",
|
||||
"gln64",
|
||||
"rice"
|
||||
};
|
||||
|
||||
ubyte RetroArch::resolveUpscaling(
|
||||
ParallelUpscaling requestedUpscaling,
|
||||
ubyte windowScale
|
||||
) noexcept {
|
||||
if( requestedUpscaling != ParallelUpscaling::Auto ) return (ubyte)requestedUpscaling;
|
||||
if( windowScale <= 1 ) return 1;
|
||||
if( windowScale == 2 ) return 2;
|
||||
if( windowScale < 8 ) return 4;
|
||||
return 8;
|
||||
}
|
||||
|
||||
static const std::regex s_cfgRegex(
|
||||
"^([^=]+)\\s*=\\s*\"([^\"]*)\"",
|
||||
std::regex_constants::ECMAScript | std::regex_constants::optimize
|
||||
);
|
||||
|
||||
static void loadConfigHelper( const fs::path &configPath, std::map<string,string> &configs ) {
|
||||
if( !fs::exists( configPath ) ) return;
|
||||
std::ifstream configFile( configPath.string() );
|
||||
while( configFile.good() && !configFile.eof() ) {
|
||||
string line;
|
||||
std::getline( configFile, line );
|
||||
|
||||
std::smatch matches;
|
||||
if( std::regex_search( line, matches, s_cfgRegex ) ) {
|
||||
string key = matches[1];
|
||||
while( !key.empty() && key.back() == ' ') key.pop_back();
|
||||
if( !key.empty() ) {
|
||||
configs[key] = matches[2];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void loadBaseConfig(
|
||||
std::map<string,string> &configs,
|
||||
bool includeCoreOptions
|
||||
) {
|
||||
const fs::path baseConfigPath = getBaseConfigDirectory() / "retroarch.cfg";
|
||||
loadConfigHelper( baseConfigPath, configs );
|
||||
if( includeCoreOptions ) {
|
||||
loadConfigHelper( baseConfigPath.parent_path() / "retroarch-core-options.cfg", configs );
|
||||
}
|
||||
}
|
||||
|
||||
static void loadConfig(
|
||||
const AppSettings &settings,
|
||||
std::map<string, string> &configs
|
||||
) {
|
||||
if( settings.configBehaviour == ConfigBehaviour::Inherited ) {
|
||||
loadBaseConfig( configs, true );
|
||||
} else if( settings.configBehaviour == ConfigBehaviour::Persistent ) {
|
||||
loadConfigHelper( BaseDir::data() / "retroarch.cfg", configs );
|
||||
loadConfigHelper( BaseDir::data() / "retroarch-core-options.cfg", configs );
|
||||
}
|
||||
|
||||
if( settings.configBehaviour != ConfigBehaviour::Inherited ) {
|
||||
std::map<string,string> baseConfig;
|
||||
loadBaseConfig( baseConfig, false );
|
||||
|
||||
for( const auto &i : baseConfig ) {
|
||||
if( configs.count( i.first ) == 0 && (
|
||||
stringEndsWith( i.first, "_dir" ) ||
|
||||
stringEndsWith( i.first, "_path" ) ||
|
||||
stringEndsWith( i.first, "_directory" )
|
||||
)) {
|
||||
configs[i.first] = i.second;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void saveConfig( const std::map<string,string> &configs ) {
|
||||
std::ofstream mainConfigFile( (BaseDir::data() / "retroarch.cfg").string(), std::ios_base::out | std::ios_base::trunc );
|
||||
std::ofstream coreConfigFile( (BaseDir::data() / "retroarch-core-options.cfg").string(), std::ios_base::out | std::ios_base::trunc );
|
||||
|
||||
for( const auto &i : configs ) {
|
||||
const string &key = i.first;
|
||||
const string &value = i.second;
|
||||
|
||||
if( std::strncmp( key.c_str(), "parallel-n64-", sizeof( "parallel-n64-" ) - 1 ) == 0 ) {
|
||||
coreConfigFile << key << " = \"" << value << '"' << std::endl;
|
||||
} else {
|
||||
mainConfigFile << key << " = \"" << value << '"' << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
mainConfigFile << std::endl << std::flush;
|
||||
coreConfigFile << std::endl << std::flush;
|
||||
}
|
||||
|
||||
static const char *s_inputNames[] = {
|
||||
"l_y_minus", // AnalogUp
|
||||
"l_y_plus", // AnalogDown
|
||||
"l_x_minus", // AnalogLeft
|
||||
"l_x_plus", // AnalogRight
|
||||
"r_y_minus", // CUp
|
||||
"r_y_plus", // CDown
|
||||
"r_x_minus", // CLeft
|
||||
"r_x_plus", // CRight
|
||||
"up", // DPadUp
|
||||
"down", // DPadDown
|
||||
"left", // DPadLeft
|
||||
"right", // DPadRight
|
||||
"b", // ButtonA
|
||||
"y", // ButtonB
|
||||
"l", // TriggerL
|
||||
"l2", // TriggerZ
|
||||
"r", // TriggerR
|
||||
"start" // Start
|
||||
};
|
||||
|
||||
static void bindInput( std::map<string,string> &configs, const string &key, const Binding &binding ) {
|
||||
const string buttonKey = key + "_btn";
|
||||
const string axisKey = key + "_axis";
|
||||
|
||||
// 99 and +99 are used instead of nul to prevent default bindings from messing things up
|
||||
switch( binding.type ) {
|
||||
case BindingType::None:
|
||||
configs[buttonKey] = "99";
|
||||
configs[axisKey] = "+99";
|
||||
break;
|
||||
case BindingType::Button:
|
||||
configs[buttonKey] = std::to_string( binding.buttonOrAxis );
|
||||
configs[axisKey] = "+99";
|
||||
break;
|
||||
case BindingType::AxisNegative:
|
||||
configs[buttonKey] = "99";
|
||||
configs[axisKey] = "-"s + std::to_string( binding.buttonOrAxis );
|
||||
break;
|
||||
case BindingType::AxisPositive:
|
||||
configs[buttonKey] = "99";
|
||||
configs[axisKey] = "+"s + std::to_string( binding.buttonOrAxis );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static inline HashMap<Uuid,std::queue<int>> getDevicePorts( [[maybe_unused]] const AppSettings &settings, int &numMapped ) {
|
||||
HashMap<Uuid,std::queue<int>> devicePorts;
|
||||
numMapped = 0;
|
||||
|
||||
bool lsjsFailed = false;
|
||||
try {
|
||||
#ifdef _WIN32
|
||||
const string lsjsCmd = (BaseDir::program() / "parallel-launcher-lsjs.exe").string();
|
||||
#else
|
||||
const fs::path lsjsPath = BaseDir::program() / "parallel-launcher-lsjs";
|
||||
const string lsjsCmd = settings.usingFlatpak ?
|
||||
("flatpak run --command=\""s + escapePath( lsjsPath ) + "\" org.libretro.RetroArch") :
|
||||
escapePath( lsjsPath );
|
||||
#endif
|
||||
|
||||
string uuidList;
|
||||
lsjsFailed = !Process::tryGetOutput( lsjsCmd, uuidList );
|
||||
if( !lsjsFailed ) {
|
||||
|
||||
size_t lineStart = 0;
|
||||
for( int i = 0; lineStart < uuidList.length(); i++ ) {
|
||||
size_t lineEnd = uuidList.find( '\n', lineStart );
|
||||
if( lineEnd == string::npos ) {
|
||||
lineEnd = uuidList.length();
|
||||
}
|
||||
|
||||
Uuid uuid;
|
||||
if( Uuid::tryParse( uuidList.substr( lineStart, lineEnd - lineStart ), uuid ) ) {
|
||||
devicePorts[uuid].push( i );
|
||||
numMapped++;
|
||||
}
|
||||
|
||||
lineStart = lineEnd + 1;
|
||||
}
|
||||
}
|
||||
} catch( ... ) {
|
||||
lsjsFailed = true;
|
||||
}
|
||||
|
||||
if( lsjsFailed ) {
|
||||
std::cerr << "Failed to run parallel-launcher-lsjs. Falling back to default port ordering. Controllers may not be bound correctly." << std::endl << std::flush;
|
||||
|
||||
const std::vector<ConnectedGamepad> fallbackPortOrder = GamepadController::instance().getConnected();
|
||||
devicePorts.clear();
|
||||
devicePorts.reserve( fallbackPortOrder.size() );
|
||||
|
||||
numMapped = (int)fallbackPortOrder.size();
|
||||
for( size_t i = 0; i < fallbackPortOrder.size(); i++ ) {
|
||||
devicePorts[fallbackPortOrder[i].info.uuid].push( i );
|
||||
}
|
||||
}
|
||||
|
||||
return devicePorts;
|
||||
}
|
||||
|
||||
AsyncProcess RetroArch::launchRom(
|
||||
fs::path romPath,
|
||||
const AppSettings &settings,
|
||||
const std::vector<PlayerController> &players,
|
||||
GfxPlugin plugin,
|
||||
bool optimizePerformance,
|
||||
bool bindSavestate
|
||||
) {
|
||||
std::map<string,string> configs;
|
||||
loadConfig( settings, configs );
|
||||
|
||||
const string width = std::to_string( 320 * (int)settings.windowScale );
|
||||
const string height = std::to_string( 240 * (int)settings.windowScale );
|
||||
const GfxPlugin gfxPlugin = (plugin == GfxPlugin::UseDefault) ? settings.defaultPlugin : plugin;
|
||||
|
||||
if( configs.count( "video_fullscreen" ) == 0 ) {
|
||||
configs["video_fullscreen"] = "false";
|
||||
}
|
||||
|
||||
if( optimizePerformance ) {
|
||||
configs["video_frame_delay"] = "0";
|
||||
}
|
||||
|
||||
configs["suspend_screensaver_enable"] = "true";
|
||||
configs["video_scale_integer"] = "true";
|
||||
configs["video_force_aspect"] = "true";
|
||||
configs["video_window_save_positions"] = "true";
|
||||
configs["parallel-n64-cpucore"] = "dynamic_recompiler";
|
||||
configs["parallel-n64-gfxplugin-accuracy"] = "veryhigh";
|
||||
configs["parallel-n64-parallel-rdp-native-texture-lod"] = "true";
|
||||
configs["global_core_options"] = "true";
|
||||
configs["config_save_on_exit"] = "true";
|
||||
configs["save_file_compression"] = "false";
|
||||
|
||||
configs["video_vsync"] = settings.vsync ? "true" : "false";
|
||||
configs["video_windowed_position_width"] = configs["custom_viewport_width"] = width;
|
||||
configs["video_windowed_position_height"] = configs["custom_viewport_height"] = height;
|
||||
configs["parallel-n64-screensize"] = width + "x" + height;
|
||||
configs["parallel-n64-framerate"] = optimizePerformance ? "fullspeed" : "original";
|
||||
configs["parallel-n64-virefresh"] = optimizePerformance ? "2200" : "auto";
|
||||
configs["parallel-n64-parallel-rdp-upscaling"] = std::to_string( (int)resolveUpscaling( settings.upscaling, settings.windowScale ) ) + "x";
|
||||
configs["parallel-n64-gfxplugin"] = s_plugins[(int)gfxPlugin];
|
||||
configs["parallel-n64-filtering"] = "automatic";
|
||||
|
||||
configs["parallel-n64-astick-deadzone"] = "0";
|
||||
configs["parallel-n64-astick-sensitivity"] = "100";
|
||||
|
||||
char floatStr[10];
|
||||
std::snprintf( floatStr, sizeof( floatStr ), "%0.6f", players.at( 0 ).profile.deadzone );
|
||||
configs["input_analog_deadzone"] = string( floatStr );
|
||||
std::snprintf( floatStr, sizeof( floatStr ), "%0.6f", players.at( 0 ).profile.sensitivity );
|
||||
configs["input_analog_sensitivity"] = string( floatStr );
|
||||
|
||||
configs["input_remap_binds_enable"] = "false";
|
||||
configs["input_joypad_driver"] = "sdl2";
|
||||
configs["input_max_users"] = std::to_string( (int)players.size() );
|
||||
|
||||
int fallbackPort;
|
||||
HashMap<Uuid,std::queue<int>> devicePorts = getDevicePorts( settings, fallbackPort );
|
||||
bool enableRumble = false;
|
||||
for( int i = 0; i < (int)players.size(); i++ ) {
|
||||
const char pc = '1' + (char)i;
|
||||
|
||||
const string indexKey = "input_player"s + pc + "_joypad_index";
|
||||
if( !devicePorts[players[i].deviceUuid].empty() ) {
|
||||
configs[indexKey] = std::to_string( devicePorts.at( players[i].deviceUuid ).front() );
|
||||
devicePorts.at( players[i].deviceUuid ).pop();
|
||||
} else {
|
||||
configs[indexKey] = std::to_string( fallbackPort++ );
|
||||
}
|
||||
|
||||
const string rumbleKey = "parallel-n64-pak"s + pc;
|
||||
configs[rumbleKey] = players[i].profile.rumble ? "rumble" : "none";
|
||||
enableRumble |= players[i].profile.rumble;
|
||||
|
||||
for( int j = 0; j <= (int)ControllerAction::Start; j++ ) {
|
||||
const string key = "input_player"s + pc + '_' + s_inputNames[j];
|
||||
bindInput( configs, key, players[i].profile.bindings[j] );
|
||||
}
|
||||
}
|
||||
|
||||
if( bindSavestate ) {
|
||||
bind( configs, "input_load_state", players.at( 0 ).profile.bindings[(int)ControllerAction::LoadState] );
|
||||
bind( configs, "input_save_state", players.at( 0 ).profile.bindings[(int)ControllerAction::SaveState] );
|
||||
} else {
|
||||
configs["input_load_state_btn"] = "nul";
|
||||
configs["input_load_state_axis"] = "nul";
|
||||
configs["input_save_state_btn"] = "nul";
|
||||
configs["input_save_state_axis"] = "nul";
|
||||
}
|
||||
|
||||
configs["enable_device_vibration"] = enableRumble ? "true" : "false";
|
||||
|
||||
saveConfig( configs );
|
||||
|
||||
fs::path corePath;
|
||||
if( configs.count( "libretro_directory" ) > 0 ) {
|
||||
corePath = expand_path( configs["libretro_directory"] ) / "parallel_n64_libretro";
|
||||
} else {
|
||||
#ifdef _WIN32
|
||||
corePath = expand_path_helper( settings.retroInstallDir ) / "cores" / "parallel_n64_libretro";
|
||||
#else
|
||||
corePath = RetroArch::configPath( settings.usingFlatpak ).parent_path() / "cores" / "parallel_n64_libretro";
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
corePath += ".dll";
|
||||
#else
|
||||
corePath += ".so";
|
||||
#endif
|
||||
|
||||
std::vector<string> args;
|
||||
args.reserve( 7 );
|
||||
#ifndef _WIN32
|
||||
if( settings.usingFlatpak ) {
|
||||
args.insert( args.end(), { "run"s, "org.libretro.RetroArch"s });
|
||||
}
|
||||
#endif
|
||||
args.insert( args.end(), { "-L"s, corePath.string(), "--config"s, (BaseDir::data() / "retroarch.cfg").string(), romPath.string() } );
|
||||
|
||||
return AsyncProcess(
|
||||
#ifdef _WIN32
|
||||
(expand_path_helper( settings.retroInstallDir ) / "retroarch.exe").string(),
|
||||
#else
|
||||
settings.usingFlatpak ? "flatpak" : "retroarch",
|
||||
#endif
|
||||
args
|
||||
);
|
||||
}
|
||||
|
||||
static const std::regex s_falseRegex(
|
||||
"^\\s*false\\s*$",
|
||||
std::regex_constants::ECMAScript | std::regex_constants::optimize | std::regex_constants::icase
|
||||
);
|
||||
|
||||
static fs::path s_cachedSaveDir;
|
||||
static fs::file_time_type s_baseConfigLastModified;
|
||||
static fs::file_time_type s_mainConfigLastModified;
|
||||
static ConfigBehaviour s_lastConfigBehaviour = (ConfigBehaviour)0xFF;
|
||||
#ifdef _WIN32
|
||||
static fs::path s_lastRetroInstallDir;
|
||||
#else
|
||||
static bool s_lastUsingFlatpak;
|
||||
#endif
|
||||
|
||||
static inline fs::path getSaveDir( const AppSettings &settings ) {
|
||||
#ifdef _WIN32
|
||||
const fs::path baseConfigPath = expand_path_helper( settings.retroInstallDir ) / "retroarch.cfg";
|
||||
#else
|
||||
const fs::path baseConfigPath = RetroArch::configPath( settings.usingFlatpak );
|
||||
#endif
|
||||
const fs::path mainConfigPath = BaseDir::data() / "retroarch.cfg";
|
||||
|
||||
std::error_code err;
|
||||
const fs::file_time_type baseConfigLastModified = fs::last_write_time( baseConfigPath, err );
|
||||
const fs::file_time_type mainConfigLastModified = fs::last_write_time( mainConfigPath, err );
|
||||
if(
|
||||
settings.configBehaviour == s_lastConfigBehaviour &&
|
||||
baseConfigLastModified == s_baseConfigLastModified &&
|
||||
(settings.configBehaviour != ConfigBehaviour::Persistent || mainConfigLastModified == s_baseConfigLastModified) &&
|
||||
#ifdef _WIN32
|
||||
settings.retroInstallDir == s_lastRetroInstallDir &&
|
||||
#else
|
||||
settings.usingFlatpak == s_lastUsingFlatpak &&
|
||||
#endif
|
||||
!err
|
||||
) {
|
||||
return s_cachedSaveDir;
|
||||
}
|
||||
|
||||
std::map<string,string> configs;
|
||||
loadConfig( settings, configs );
|
||||
|
||||
fs::path saveDir;
|
||||
if( std::regex_match( configs["savefiles_in_content_dir"], s_falseRegex ) ) {
|
||||
saveDir = configs["savefile_directory"];
|
||||
}
|
||||
|
||||
s_cachedSaveDir = saveDir;
|
||||
s_baseConfigLastModified = baseConfigLastModified;
|
||||
s_mainConfigLastModified = mainConfigLastModified;
|
||||
s_lastConfigBehaviour = settings.configBehaviour;
|
||||
#ifdef _WIN32
|
||||
s_lastRetroInstallDir = settings.retroInstallDir;
|
||||
#else
|
||||
s_lastUsingFlatpak = settings.usingFlatpak;
|
||||
#endif
|
||||
return saveDir;
|
||||
}
|
||||
|
||||
fs::path RetroArch::getSaveFilePath(
|
||||
const AppSettings &settings,
|
||||
const fs::path &romPath
|
||||
) {
|
||||
const fs::path saveDir = getSaveDir( settings );
|
||||
if( saveDir.empty() ) {
|
||||
fs::path saveFilePath = expand_path( romPath );
|
||||
saveFilePath.replace_extension( ".srm" );
|
||||
return saveFilePath;
|
||||
} else {
|
||||
fs::path saveFilePath = expand_path( saveDir / romPath.filename() );
|
||||
saveFilePath.replace_extension( ".srm" );
|
||||
return saveFilePath;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool RetroArch::isRetroArchInstalled() {
|
||||
AppSettings settings = FileController::loadAppSettings();
|
||||
#ifdef _WIN32
|
||||
return fs::is_regular_file( expand_path_helper( settings.retroInstallDir ) / "RetroArch.exe" );
|
||||
#else
|
||||
if( settings.usingFlatpak ) {
|
||||
if( hasFlatpakInstall() ) {
|
||||
return true;
|
||||
} else if( std::system( "which retroarch > /dev/null" ) == 0 ) {
|
||||
settings.usingFlatpak = false;
|
||||
FileController::saveAppSettings( settings );
|
||||
return true;
|
||||
}
|
||||
} else if( std::system( "which retroarch > /dev/null" ) == 0 ) {
|
||||
return true;
|
||||
} else if( hasFlatpakInstall() ) {
|
||||
settings.usingFlatpak = true;
|
||||
FileController::saveAppSettings( settings );
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool RetroArch::isParallelN64Installed() {
|
||||
std::map<string,string> configs;
|
||||
loadBaseConfig( configs, false );
|
||||
|
||||
fs::path corePath = expand_path( fs::path( configs["libretro_directory"] ) );
|
||||
if( corePath.empty() ) return false;
|
||||
|
||||
corePath = corePath / "parallel_n64_libretro";
|
||||
#ifdef _WIN32
|
||||
corePath += ".dll";
|
||||
#else
|
||||
corePath += ".so";
|
||||
#endif
|
||||
|
||||
return fs::is_regular_file( corePath );
|
||||
}
|
||||
|
||||
static const std::regex s_versionRegex(
|
||||
"^RetroArch.*v(\\d+)\\.(\\d+)(?:\\.\\d+)*(?:\\s|$)",
|
||||
std::regex_constants::ECMAScript | std::regex_constants::optimize | std::regex_constants::icase
|
||||
);
|
||||
|
||||
bool RetroArch::tryGetVersion( ushort &versionMajor, ushort &versionMinor ) {
|
||||
AppSettings settings = FileController::loadAppSettings();
|
||||
|
||||
string versionOutput;
|
||||
versionOutput.reserve( 128 );
|
||||
|
||||
if( !Process::tryGetErrorOutput(
|
||||
#ifdef _WIN32
|
||||
(expand_path_helper( settings.retroInstallDir ) / "retroarch.exe").string() + " --version",
|
||||
#else
|
||||
settings.usingFlatpak ? "flatpak run org.libretro.RetroArch --version" : "retroarch --version",
|
||||
#endif
|
||||
versionOutput
|
||||
)) return false;
|
||||
|
||||
size_t lineStart = 0;
|
||||
while( lineStart < versionOutput.length() ) {
|
||||
size_t lineEnd = versionOutput.find( '\n', lineStart );
|
||||
if( lineEnd == string::npos ) {
|
||||
lineEnd = versionOutput.length();
|
||||
}
|
||||
|
||||
std::smatch match;
|
||||
const string line = versionOutput.substr( lineStart, lineEnd - lineStart );
|
||||
if( std::regex_search( line, match, s_versionRegex ) ) {
|
||||
versionMajor = (ushort)std::stoi( match[1] );
|
||||
versionMinor = (ushort)std::stoi( match[2] );
|
||||
return true;
|
||||
}
|
||||
|
||||
lineStart = lineEnd + 1;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
41
src/core/retroarch.hpp
Normal file
@@ -0,0 +1,41 @@
|
||||
#ifndef SRC_CORE_RETROARCH_HPP_
|
||||
#define SRC_CORE_RETROARCH_HPP_
|
||||
|
||||
#include "src/types.hpp"
|
||||
#include "src/core/controller.hpp"
|
||||
#include "src/core/settings.hpp"
|
||||
#include "src/polyfill/process.hpp"
|
||||
|
||||
namespace RetroArch {
|
||||
|
||||
#ifndef _WIN32
|
||||
extern bool hasFlatpakInstall();
|
||||
extern fs::path configPath( bool useFlatpakInstall );
|
||||
#endif
|
||||
|
||||
extern ubyte resolveUpscaling(
|
||||
ParallelUpscaling requestedUpscaling,
|
||||
ubyte windowScale
|
||||
) noexcept;
|
||||
|
||||
extern AsyncProcess launchRom(
|
||||
fs::path romPath,
|
||||
const AppSettings &settings,
|
||||
const std::vector<PlayerController> &players,
|
||||
GfxPlugin plugin,
|
||||
bool optimizePerformance,
|
||||
bool bindSavestate
|
||||
);
|
||||
|
||||
extern fs::path getSaveFilePath(
|
||||
const AppSettings &settings,
|
||||
const fs::path &romPath
|
||||
);
|
||||
|
||||
extern bool isRetroArchInstalled();
|
||||
extern bool isParallelN64Installed();
|
||||
extern bool tryGetVersion( ushort &versionMajor, ushort &versionMinor );
|
||||
|
||||
}
|
||||
|
||||
#endif /* SRC_CORE_RETROARCH_HPP_ */
|
257
src/core/rom.cpp
Normal file
@@ -0,0 +1,257 @@
|
||||
#include "src/core/rom.hpp"
|
||||
|
||||
#include <thread>
|
||||
#include <regex>
|
||||
#include <unordered_map>
|
||||
|
||||
static constexpr char P_NAME[] = "name";
|
||||
static constexpr char P_PATH[] = "file_path";
|
||||
static constexpr char P_PLUGIN[] = "gfx_plugin";
|
||||
static constexpr char P_LAST_PLAYED[] = "last_played";
|
||||
static constexpr char P_PLAY_TIME[] = "play_time";
|
||||
static constexpr char P_TAGS[] = "tags";
|
||||
static constexpr char P_NOTES[] = "notes";
|
||||
static constexpr char P_OPTIMIZE[] = "optimize";
|
||||
static constexpr char P_FOLDER[] = "folder_path";
|
||||
static constexpr char P_RECURSIVE[] = "recursive";
|
||||
static constexpr char P_IGNORE_HIDDEN[] = "ignore_hidden";
|
||||
static constexpr char P_FOLLOW_SYMLINKS[] = "follow_symlinks";
|
||||
static constexpr char P_MAX_DEPTH[] = "max_depth";
|
||||
|
||||
template<> void JsonSerializer::serialize<ROM>( JsonWriter &jw, const ROM &obj ) {
|
||||
jw.writeObjectStart();
|
||||
jw.writeProperty( P_NAME, obj.name );
|
||||
jw.writeProperty( P_PATH, obj.path.string() );
|
||||
jw.writeProperty( P_PLUGIN, obj.plugin );
|
||||
jw.writeProperty( P_LAST_PLAYED, obj.lastPlayed );
|
||||
jw.writeProperty( P_PLAY_TIME, obj.playTime );
|
||||
jw.writePropertyName( P_TAGS );
|
||||
jw.writeArrayStart();
|
||||
for( const string &tag : obj.tags ) {
|
||||
jw.writeString( tag );
|
||||
}
|
||||
jw.writeArrayEnd();
|
||||
jw.writeProperty( P_NOTES, obj.notes );
|
||||
jw.writeProperty( P_OPTIMIZE, obj.optimize );
|
||||
jw.writeObjectEnd();
|
||||
}
|
||||
|
||||
template<> ROM JsonSerializer::parse<ROM>( const Json &json ) {
|
||||
std::set<string> tags;
|
||||
for( const Json &tag : json[P_TAGS].array() ) {
|
||||
tags.insert( tag.get<string>() );
|
||||
}
|
||||
|
||||
return ROM{
|
||||
json[P_NAME].get<string>(),
|
||||
std::filesystem::path( json[P_PATH].get<string>() ),
|
||||
json[P_PLUGIN].get<GfxPlugin>(),
|
||||
json[P_LAST_PLAYED].get<int64>(),
|
||||
json[P_PLAY_TIME].get<int64>(),
|
||||
std::move( tags ),
|
||||
json[P_NOTES].get<string>(),
|
||||
json[P_OPTIMIZE].get<bool>()
|
||||
};
|
||||
}
|
||||
|
||||
template<> void JsonSerializer::serialize<RomSource>( JsonWriter &jw, const RomSource &obj ) {
|
||||
jw.writeObjectStart();
|
||||
jw.writeProperty( P_FOLDER, obj.folder.string() );
|
||||
jw.writeProperty( P_RECURSIVE, obj.recursive );
|
||||
jw.writeProperty( P_IGNORE_HIDDEN, obj.ignoreHidden && obj.recursive );
|
||||
jw.writeProperty( P_FOLLOW_SYMLINKS, obj.followSymlinks && obj.recursive );
|
||||
jw.writeProperty( P_MAX_DEPTH, obj.recursive ? obj.maxDepth : 5 );
|
||||
jw.writePropertyName( P_TAGS );
|
||||
jw.writeArrayStart();
|
||||
for( const string &tag : obj.autoTags ) {
|
||||
jw.writeString( tag );
|
||||
}
|
||||
jw.writeArrayEnd();
|
||||
jw.writeObjectEnd();
|
||||
}
|
||||
|
||||
template<> RomSource JsonSerializer::parse<RomSource>( const Json &json ) {
|
||||
std::set<string> tags;
|
||||
for( const Json &tag : json[P_TAGS].array() ) {
|
||||
tags.insert( tag.get<string>() );
|
||||
}
|
||||
|
||||
return RomSource{
|
||||
std::filesystem::path( json[P_FOLDER].get<string>() ),
|
||||
json[P_RECURSIVE].get<bool>(),
|
||||
json[P_IGNORE_HIDDEN].get<bool>(),
|
||||
json[P_FOLLOW_SYMLINKS].get<bool>(),
|
||||
json[P_MAX_DEPTH].get<ubyte>(),
|
||||
std::move( tags )
|
||||
};
|
||||
}
|
||||
|
||||
static const std::regex s_extensionRegex(
|
||||
"^\\.[nvzNVZ]64$",
|
||||
std::regex_constants::optimize | std::regex_constants::ECMAScript
|
||||
);
|
||||
|
||||
static inline bool isRom( const fs::path &filePath, bool allowSymlinks ) {
|
||||
const string extension = filePath.extension().string();
|
||||
if( !std::regex_match( extension, s_extensionRegex ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if( fs::is_regular_file( filePath ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if( !allowSymlinks || !fs::is_symlink( filePath ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
fs::path resolvedPath = fs::read_symlink( filePath );
|
||||
while( fs::is_symlink( resolvedPath ) ) {
|
||||
resolvedPath = fs::read_symlink( resolvedPath );
|
||||
}
|
||||
|
||||
return fs::is_regular_file( resolvedPath );
|
||||
}
|
||||
|
||||
static void tryAddRom(
|
||||
const RomSource &source,
|
||||
const HashMap<fs::path,ROM> &previousRoms,
|
||||
HashMap<fs::path,ROM> &newRoms,
|
||||
std::mutex &mapLock,
|
||||
const fs::path &romPath
|
||||
) {
|
||||
std::unique_lock lock( mapLock );
|
||||
|
||||
if( newRoms.count( romPath ) > 0 || !isRom( romPath, source.followSymlinks ) ) return;
|
||||
auto oldRom = previousRoms.find( romPath );
|
||||
|
||||
if( oldRom == previousRoms.end() ) {
|
||||
newRoms[romPath] = ROM{
|
||||
/* name */ romPath.stem().string(),
|
||||
/* folder */ romPath,
|
||||
/* plugin */ GfxPlugin::UseDefault,
|
||||
/* lastPlayed */ 0,
|
||||
/* playTime */ 0,
|
||||
/* tags */ source.autoTags,
|
||||
/* notes */ "",
|
||||
/* optimize */ true
|
||||
};
|
||||
} else {
|
||||
newRoms[romPath] = std::move( oldRom->second );
|
||||
}
|
||||
}
|
||||
|
||||
static inline void searchAsyncNonrecursiveWorker(
|
||||
const RomSource &source,
|
||||
const HashMap<fs::path,ROM> &previousRoms,
|
||||
HashMap<fs::path,ROM> &newRoms,
|
||||
std::mutex &mapLock,
|
||||
CancellationToken &cancellationToken
|
||||
) {
|
||||
for( auto &i : fs::directory_iterator( source.folder ) ) {
|
||||
if( cancellationToken.isCancelled() ) break;
|
||||
if( i.is_regular_file() ) {
|
||||
tryAddRom( source, previousRoms, newRoms, mapLock, i.path() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline void searchAsyncRecursiveWorker(
|
||||
const RomSource &source,
|
||||
const HashMap<fs::path,ROM> &previousRoms,
|
||||
HashMap<fs::path,ROM> &newRoms,
|
||||
std::mutex &mapLock,
|
||||
CancellationToken &cancellationToken
|
||||
) {
|
||||
auto searchOptions = fs::directory_options::skip_permission_denied;
|
||||
if( source.followSymlinks ) {
|
||||
searchOptions |= fs::directory_options::follow_directory_symlink;
|
||||
}
|
||||
|
||||
fs::recursive_directory_iterator iterator( source.folder, searchOptions );
|
||||
for( auto &i : iterator ) {
|
||||
if( cancellationToken.isCancelled() ) break;
|
||||
|
||||
if( i.is_directory() && (
|
||||
iterator.depth() >= source.maxDepth || (
|
||||
source.ignoreHidden &&
|
||||
i.path().stem().c_str()[0] == '.'
|
||||
))) {
|
||||
iterator.disable_recursion_pending();
|
||||
}
|
||||
|
||||
if( i.is_regular_file() ) {
|
||||
tryAddRom( source, previousRoms, newRoms, mapLock, i.path() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void searchAsyncWorker(
|
||||
const RomSource &source,
|
||||
const HashMap<fs::path,ROM> &previousRoms,
|
||||
HashMap<fs::path,ROM> &newRoms,
|
||||
std::mutex &mapLock,
|
||||
CancellationToken &cancellationToken
|
||||
) {
|
||||
if( !fs::is_directory( fs::status( source.folder ) ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if( source.recursive ) {
|
||||
searchAsyncRecursiveWorker( source, previousRoms, newRoms, mapLock, cancellationToken );
|
||||
} else {
|
||||
searchAsyncNonrecursiveWorker( source, previousRoms, newRoms, mapLock, cancellationToken );
|
||||
}
|
||||
}
|
||||
|
||||
static inline std::vector<ROM> searchAsyncDispatcher(
|
||||
const std::vector<RomSource> &romSources,
|
||||
const HashMap<fs::path,ROM> &previousRoms,
|
||||
CancellationToken &cancellationToken
|
||||
) {
|
||||
HashMap<fs::path,ROM> foundRoms;
|
||||
std::vector<std::thread> workers;
|
||||
std::mutex mapLock;
|
||||
|
||||
for( const RomSource &source : romSources ) {
|
||||
workers.push_back( std::thread(
|
||||
searchAsyncWorker,
|
||||
std::cref( source ),
|
||||
std::cref( previousRoms ),
|
||||
std::ref( foundRoms ),
|
||||
std::ref( mapLock ),
|
||||
std::ref( cancellationToken )
|
||||
));
|
||||
}
|
||||
|
||||
for( std::thread &worker : workers ) {
|
||||
worker.join();
|
||||
}
|
||||
|
||||
if( cancellationToken.isCancelled() ) {
|
||||
return std::vector<ROM>();
|
||||
}
|
||||
|
||||
std::vector<ROM> result;
|
||||
result.reserve( foundRoms.size() );
|
||||
for( auto &i : foundRoms ) {
|
||||
result.push_back( std::move( i.second ) );
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<ROM> RomFinder::scan(
|
||||
const std::vector<ROM> ¤tRomList,
|
||||
const std::vector<RomSource> &sources,
|
||||
CancellationToken &cancellationToken
|
||||
) {
|
||||
HashMap<fs::path,ROM> previousRoms;
|
||||
previousRoms.reserve( currentRomList.size() );
|
||||
for( const ROM &rom : currentRomList ) {
|
||||
previousRoms[rom.path] = rom;
|
||||
}
|
||||
|
||||
return searchAsyncDispatcher( sources, previousRoms, cancellationToken );
|
||||
}
|
||||
|
49
src/core/rom.hpp
Normal file
@@ -0,0 +1,49 @@
|
||||
#ifndef SRC_CORE_ROM_HPP_
|
||||
#define SRC_CORE_ROM_HPP_
|
||||
|
||||
#include <chrono>
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#include <unordered_map>
|
||||
#include "src/core/filesystem.hpp"
|
||||
#include "src/core/retroarch.hpp"
|
||||
#include "src/core/json.hpp"
|
||||
#include "src/core/async.hpp"
|
||||
|
||||
struct ROM {
|
||||
string name;
|
||||
fs::path path;
|
||||
GfxPlugin plugin;
|
||||
int64 lastPlayed;
|
||||
int64 playTime;
|
||||
std::set<string> tags;
|
||||
string notes;
|
||||
bool optimize;
|
||||
};
|
||||
|
||||
struct RomSource {
|
||||
fs::path folder;
|
||||
bool recursive;
|
||||
bool ignoreHidden;
|
||||
bool followSymlinks;
|
||||
ubyte maxDepth;
|
||||
std::set<string> autoTags;
|
||||
};
|
||||
|
||||
namespace JsonSerializer {
|
||||
template<> void serialize<ROM>( JsonWriter &jw, const ROM &obj );
|
||||
template<> ROM parse<ROM>( const Json &json );
|
||||
|
||||
template<> void serialize<RomSource>( JsonWriter &jw, const RomSource &obj );
|
||||
template<> RomSource parse<RomSource>( const Json &json );
|
||||
}
|
||||
|
||||
namespace RomFinder {
|
||||
std::vector<ROM> scan(
|
||||
const std::vector<ROM> ¤tRomList,
|
||||
const std::vector<RomSource> &sources,
|
||||
CancellationToken &cancellationToken
|
||||
);
|
||||
}
|
||||
|
||||
#endif /* SRC_CORE_ROM_HPP_ */
|
84
src/core/settings.cpp
Normal file
@@ -0,0 +1,84 @@
|
||||
#include "src/core/settings.hpp"
|
||||
|
||||
const AppSettings AppSettings::Default = {
|
||||
/* visibleColumns */ RomInfoColumn::LastPlayed | RomInfoColumn::PlayTime,
|
||||
/* defaultPlugin */ GfxPlugin::Glide64,
|
||||
/* upscaling */ ParallelUpscaling::Auto,
|
||||
/* configBehaviour */ ConfigBehaviour::Persistent,
|
||||
/* windowScale */ 4,
|
||||
/* vsync */ false,
|
||||
/* hideWhenPlaying */ false,
|
||||
#ifdef _WIN32
|
||||
/* retroInstallDir */ fs::path( "~\\AppData\\Roaming\\RetroArch" ),
|
||||
#else
|
||||
/* usingFlatpak */ false,
|
||||
#endif
|
||||
/* preferredController */ std::nullopt,
|
||||
/* showNoControllerWarning */ true,
|
||||
#ifdef _WIN32
|
||||
/* windowsTheme */ "Fusion"
|
||||
#endif
|
||||
};
|
||||
|
||||
static constexpr char P_RETRO_INSTALL_PATH[] = "retroarch_install_path";
|
||||
static constexpr char P_VISIBLE_COLUMNS[] = "visible_columns";
|
||||
static constexpr char P_DEFAULT_PLUGIN[] = "default_gfx_plugin";
|
||||
static constexpr char P_UPSCALING[] = "parallel_upscaling";
|
||||
static constexpr char P_CONFIG_BEHAVIOUR[] = "config_behaviour";
|
||||
static constexpr char P_WINDOW_SCALE[] = "window_scale";
|
||||
static constexpr char P_VSYNC[] = "enable_vsync";
|
||||
static constexpr char P_HIDE_WHILE_PLAYING[] = "hide_while_playing";
|
||||
static constexpr char P_USING_FLATPAK[] = "using_flatpak_install";
|
||||
static constexpr char P_PREFERRED_CONTROLLER[] = "preferred_controller";
|
||||
static constexpr char P_SHOW_NO_CONTROLLER_WARNING[] = "show_no_controller_warning";
|
||||
static constexpr char P_WINDOWS_THEME[] = "windows_theme";
|
||||
|
||||
template<> void JsonSerializer::serialize<AppSettings>( JsonWriter &jw, const AppSettings &obj ) {
|
||||
jw.writeObjectStart();
|
||||
jw.writeProperty( P_VISIBLE_COLUMNS, obj.visibleColumns );
|
||||
jw.writeProperty( P_DEFAULT_PLUGIN, obj.defaultPlugin );
|
||||
jw.writeProperty( P_UPSCALING, obj.upscaling );
|
||||
jw.writeProperty( P_CONFIG_BEHAVIOUR, obj.configBehaviour );
|
||||
jw.writeProperty( P_WINDOW_SCALE, obj.windowScale );
|
||||
jw.writeProperty( P_VSYNC, obj.vsync );
|
||||
jw.writeProperty( P_HIDE_WHILE_PLAYING, obj.hideWhenPlaying );
|
||||
#ifdef _WIN32
|
||||
jw.writeProperty( P_RETRO_INSTALL_PATH, obj.retroInstallDir.string() );
|
||||
#else
|
||||
jw.writeProperty( P_USING_FLATPAK, obj.usingFlatpak );
|
||||
#endif
|
||||
jw.writePropertyName( P_PREFERRED_CONTROLLER );
|
||||
if( obj.preferredController.has_value() ) {
|
||||
jw.writeString( obj.preferredController.value().toString() );
|
||||
} else {
|
||||
jw.writeNull();
|
||||
}
|
||||
jw.writeProperty( P_SHOW_NO_CONTROLLER_WARNING, obj.showNoControllerWarning );
|
||||
#ifdef _WIN32
|
||||
jw.writeProperty( P_WINDOWS_THEME, obj.windowsTheme );
|
||||
#endif
|
||||
jw.writeObjectEnd();
|
||||
}
|
||||
|
||||
template<> AppSettings JsonSerializer::parse<AppSettings>( const Json &json ) {
|
||||
return AppSettings{
|
||||
json[P_VISIBLE_COLUMNS].getOrDefault<RomInfoColumn>( AppSettings::Default.visibleColumns ),
|
||||
json[P_DEFAULT_PLUGIN].getOrDefault<GfxPlugin>( AppSettings::Default.defaultPlugin ),
|
||||
json[P_UPSCALING].getOrDefault<ParallelUpscaling>( AppSettings::Default.upscaling ),
|
||||
json[P_CONFIG_BEHAVIOUR].getOrDefault<ConfigBehaviour>( AppSettings::Default.configBehaviour ),
|
||||
json[P_WINDOW_SCALE].getOrDefault<ubyte>( AppSettings::Default.windowScale ),
|
||||
json[P_VSYNC].getOrDefault<bool>( AppSettings::Default.vsync ),
|
||||
json[P_HIDE_WHILE_PLAYING].getOrDefault<bool>( AppSettings::Default.hideWhenPlaying ),
|
||||
#ifdef _WIN32
|
||||
std::filesystem::path( json[P_RETRO_INSTALL_PATH].getOrDefault<string>( AppSettings::Default.retroInstallDir.string() ) ),
|
||||
#else
|
||||
json[P_USING_FLATPAK].getOrDefault<bool>( AppSettings::Default.usingFlatpak ),
|
||||
#endif
|
||||
(json[P_PREFERRED_CONTROLLER].exists() && !json[P_PREFERRED_CONTROLLER].isNull()) ?
|
||||
Uuid::parse( json[P_PREFERRED_CONTROLLER].get<string>() ) : std::optional<Uuid>(),
|
||||
json[P_SHOW_NO_CONTROLLER_WARNING].getOrDefault<bool>( true ),
|
||||
#ifdef _WIN32
|
||||
json[P_WINDOWS_THEME].getOrDefault<string>( AppSettings::Default.windowsTheme )
|
||||
#endif
|
||||
};
|
||||
}
|
69
src/core/settings.hpp
Normal file
@@ -0,0 +1,69 @@
|
||||
#ifndef SRC_CORE_SETTINGS_HPP_
|
||||
#define SRC_CORE_SETTINGS_HPP_
|
||||
|
||||
#include <optional>
|
||||
#include "src/core/filesystem.hpp"
|
||||
#include "src/core/flags.hpp"
|
||||
#include "src/core/json.hpp"
|
||||
#include "src/core/uuid.hpp"
|
||||
|
||||
enum class GfxPlugin : ubyte {
|
||||
UseDefault = 0,
|
||||
ParaLLEl = 1,
|
||||
Glide64 = 2,
|
||||
Angrylion = 3,
|
||||
GlideN64 = 4,
|
||||
Rice = 5
|
||||
};
|
||||
|
||||
enum class ParallelUpscaling : ubyte {
|
||||
Auto = 0,
|
||||
None = 1,
|
||||
x2 = 2,
|
||||
x4 = 4,
|
||||
x8 = 8
|
||||
};
|
||||
|
||||
enum class ConfigBehaviour : ubyte {
|
||||
Transient = 0,
|
||||
Persistent = 1,
|
||||
Inherited = 2
|
||||
};
|
||||
|
||||
enum class RomInfoColumn : ubyte {
|
||||
Path = 0x1,
|
||||
LastPlayed = 0x2,
|
||||
PlayTime = 0x4,
|
||||
Notes = 0x8
|
||||
};
|
||||
|
||||
DEFINE_FLAG_OPERATIONS( RomInfoColumn, ubyte )
|
||||
|
||||
struct AppSettings {
|
||||
RomInfoColumn visibleColumns;
|
||||
GfxPlugin defaultPlugin;
|
||||
ParallelUpscaling upscaling;
|
||||
ConfigBehaviour configBehaviour;
|
||||
ubyte windowScale;
|
||||
bool vsync;
|
||||
bool hideWhenPlaying;
|
||||
#ifdef _WIN32
|
||||
fs::path retroInstallDir;
|
||||
#else
|
||||
bool usingFlatpak;
|
||||
#endif
|
||||
std::optional<Uuid> preferredController;
|
||||
bool showNoControllerWarning;
|
||||
#ifdef _WIN32
|
||||
string windowsTheme;
|
||||
#endif
|
||||
|
||||
static const AppSettings Default;
|
||||
};
|
||||
|
||||
namespace JsonSerializer {
|
||||
template<> void serialize<AppSettings>( JsonWriter &jw, const AppSettings &obj );
|
||||
template<> AppSettings parse<AppSettings>( const Json &json );
|
||||
}
|
||||
|
||||
#endif /* SRC_CORE_SETTINGS_HPP_ */
|
226
src/core/sm64.cpp
Normal file
@@ -0,0 +1,226 @@
|
||||
#include "src/core/sm64.hpp"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
using namespace SM64;
|
||||
|
||||
const char *const SM64::CourseNames[25] = {
|
||||
"Bob-Omb Battlefield",
|
||||
"Whomp's Fortress",
|
||||
"Jolly Rodger Bay",
|
||||
"Cool Cool Mountain",
|
||||
"Big Boo's Haunt",
|
||||
"Hazy Maze Cave",
|
||||
"Lethal Lava Land",
|
||||
"Shifting Sand Land",
|
||||
"Dire Dire Docks",
|
||||
"Snowman's Land",
|
||||
"Wet Dry World",
|
||||
"Tall, Tall Mountain",
|
||||
"Tiny-Huge Island",
|
||||
"Tick Tock Clock",
|
||||
"Rainbow Ride",
|
||||
|
||||
"Bowser in the Dark World",
|
||||
"Bowser in the Fire Sea",
|
||||
"Bowser in the Sky",
|
||||
"Peach's Secret Slide",
|
||||
"Cavern of the Metal Cap",
|
||||
"Tower of the Wing Cap",
|
||||
"Vanish Cap Under the Moat",
|
||||
"Winged Mario over the Rainbow",
|
||||
"Secret Aquarium",
|
||||
|
||||
"End Screen"
|
||||
};
|
||||
|
||||
const char *const SM64::LevelNames[37] = {
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
"Big Boo's Haunt",
|
||||
"Cool Cool Mountain",
|
||||
"Castle Interior",
|
||||
"Hazy Maze Cave",
|
||||
"Shifting Sand Land",
|
||||
"Bob-Omb Battlefield",
|
||||
"Snowman's Land",
|
||||
"Wet Dry World",
|
||||
"Jolly Rodger Bay",
|
||||
"Tiny-Huge Island",
|
||||
"Tick Tock Clock",
|
||||
"Rainbow Ride",
|
||||
"Castle Grounds",
|
||||
"Bowser in the Dark World",
|
||||
"Vanish Cap Under the Moat",
|
||||
"Bowser in the Fire Sea",
|
||||
"Secret Aquarium",
|
||||
"Bowser in the Sky",
|
||||
"Lethal Lava Land",
|
||||
"Dire Dire Docks",
|
||||
"Whomp's Fortress",
|
||||
"End Screen",
|
||||
"Castle Courtyard",
|
||||
"Peach's Secret Slide",
|
||||
"Cavern of the Metal Cap",
|
||||
"Tower of the Wing Cap",
|
||||
"Bowser 1",
|
||||
"Winged Mario over the Rainbow",
|
||||
nullptr,
|
||||
"Bowser 2",
|
||||
"Bowser 3",
|
||||
nullptr,
|
||||
"Tall, Tall Mountain"
|
||||
};
|
||||
|
||||
const std::array<LevelId,31> SM64::LevelIds = {
|
||||
LevelId::CastleGrounds,
|
||||
LevelId::CastleInterior,
|
||||
LevelId::CastleCourtyard,
|
||||
LevelId::BobOmbBattlefield,
|
||||
LevelId::WhompsFortress,
|
||||
LevelId::JollyRodgerBay,
|
||||
LevelId::CoolCoolMountain,
|
||||
LevelId::BigBoosHaunt,
|
||||
LevelId::HazyMazeCave,
|
||||
LevelId::LethalLavaLand,
|
||||
LevelId::ShiftingSandLand,
|
||||
LevelId::DireDireDocks,
|
||||
LevelId::SnowmansLands,
|
||||
LevelId::WetDryWorld,
|
||||
LevelId::TallTallMountain,
|
||||
LevelId::TinyHugeIsland,
|
||||
LevelId::TickTockClock,
|
||||
LevelId::RainbowRide,
|
||||
LevelId::BowserInTheDarkWorld,
|
||||
LevelId::BowserInTheFireSea,
|
||||
LevelId::BowserInTheSky,
|
||||
LevelId::Bowser1,
|
||||
LevelId::Bowser2,
|
||||
LevelId::Bowser3,
|
||||
LevelId::TowerOfTheWingCap,
|
||||
LevelId::CavernOfTheMetalCap,
|
||||
LevelId::VanishCapUnderTheMoat,
|
||||
LevelId::PeachsSecretSlide,
|
||||
LevelId::SecretAquarium,
|
||||
LevelId::WingedMarioOverTheRainbow,
|
||||
LevelId::EndScreen
|
||||
};
|
||||
|
||||
struct Checksum {
|
||||
ubyte upper;
|
||||
ubyte lower;
|
||||
};
|
||||
|
||||
static Checksum getChecksum( const ubyte *data ) noexcept {
|
||||
ushort checksum = 0;
|
||||
for( sbyte i = 0; i < 54; i++ ) {
|
||||
checksum += data[i];
|
||||
}
|
||||
return Checksum{
|
||||
(ubyte)(checksum >> 8),
|
||||
(ubyte)(checksum & 0xFF)
|
||||
};
|
||||
}
|
||||
|
||||
static inline void writeSaveSlot( std::ostream &saveFile, const SaveSlot &slot ) {
|
||||
ubyte data[56];
|
||||
|
||||
data[0] = (ubyte)slot.capLevel;
|
||||
data[1] = slot.capArea & 0x7;
|
||||
|
||||
std::memset( &data[2], 0, 6 );
|
||||
|
||||
data[8] = slot.castleStars & 0x7F;
|
||||
data[9] = (ubyte)(((uint)slot.flags >> 16) & 0x1F);
|
||||
data[10] = (ubyte)(((uint)slot.flags >> 8) & 0xFF);
|
||||
data[11] = (ubyte)((uint)slot.flags & 0xFF);
|
||||
|
||||
// Due to a programming error, the bit controlling whether or not the cannon
|
||||
// is open is actually stored in the NEXT course's byte.
|
||||
data[12] = slot.starFlags[0] & 0x7F;
|
||||
for( sbyte i = 1; i < 25; i++ ) {
|
||||
data[i + 12] = (slot.starFlags[i] & 0x7F) | (slot.starFlags[i-1] & 0x80);
|
||||
}
|
||||
|
||||
std::memcpy( &data[37], slot.coinScores, 15 );
|
||||
|
||||
data[52] = 0x44;
|
||||
data[53] = 0x41;
|
||||
|
||||
const Checksum checksum = getChecksum( data );
|
||||
data[54] = checksum.upper;
|
||||
data[55] = checksum.lower;
|
||||
|
||||
saveFile.write( (const char*)data, 56 );
|
||||
saveFile.write( (const char*)data, 56 );
|
||||
}
|
||||
|
||||
static inline void readSaveSlot( std::istream &saveFile, SaveSlot &slot ) {
|
||||
ubyte data[56];
|
||||
Checksum checksum;
|
||||
|
||||
saveFile.read( (char*)data, 56 );
|
||||
checksum = getChecksum( data );
|
||||
|
||||
if( checksum.upper == data[54] && checksum.lower == data[55] ) {
|
||||
saveFile.seekg( 56, std::ios_base::cur );
|
||||
} else {
|
||||
saveFile.read( (char*)data, 56 );
|
||||
checksum = getChecksum( data );
|
||||
|
||||
if( checksum.upper != data[54] || checksum.lower != data[55] ) {
|
||||
std::memset( data, 0, 56 );
|
||||
}
|
||||
}
|
||||
|
||||
slot.capLevel = (LevelId)data[0];
|
||||
slot.capArea = data[1] & 0x7;
|
||||
|
||||
slot.castleStars = data[8] & 0x7F;
|
||||
slot.flags = (SaveFileFlag)(
|
||||
((uint)(data[9] & 0x1F) << 16) |
|
||||
((uint)data[10] << 8 ) |
|
||||
(uint)data[11]
|
||||
);
|
||||
|
||||
for( sbyte i = 0; i < 25; i++ ) {
|
||||
// off-by-one error in reading the cannon state is intentional
|
||||
slot.starFlags[i] = (data[12 + i] & 0x7F) | (data[13 + i] & 0x80);
|
||||
}
|
||||
|
||||
std::memcpy( slot.coinScores, &data[37], 15 );
|
||||
}
|
||||
|
||||
void SaveFile::write( std::ostream &saveFile ) const {
|
||||
saveFile.seekp( 0 );
|
||||
for( sbyte i = 0; i < 4; i++ ) {
|
||||
writeSaveSlot( saveFile, m_slots[i] );
|
||||
}
|
||||
}
|
||||
|
||||
SaveFile SaveFile::read( std::istream &saveFile ) {
|
||||
saveFile.seekg( 0 );
|
||||
|
||||
SaveFile saveData;
|
||||
for( sbyte i = 0; i < 4; i++ ) {
|
||||
readSaveSlot( saveFile, saveData.m_slots[i] );
|
||||
}
|
||||
|
||||
return saveData;
|
||||
}
|
||||
|
||||
int SaveSlot::countStars() const {
|
||||
int starCount = 0;
|
||||
uint *wStarFlags = (uint*)starFlags;
|
||||
|
||||
for( int i = 0; i < 6; i++ ) {
|
||||
starCount += __builtin_popcount( wStarFlags[i] & 0x7F7F7F7Fu );
|
||||
}
|
||||
|
||||
starCount += __builtin_popcount( (uint)starFlags[24] & 0x7Fu );
|
||||
starCount += __builtin_popcount( (uint)castleStars & 0x7Fu );
|
||||
|
||||
return starCount;
|
||||
}
|
125
src/core/sm64.hpp
Normal file
@@ -0,0 +1,125 @@
|
||||
#ifndef SRC_CORE_SM64_HPP_
|
||||
#define SRC_CORE_SM64_HPP_
|
||||
|
||||
#include <iostream>
|
||||
#include <array>
|
||||
#include "src/types.hpp"
|
||||
#include "src/core/flags.hpp"
|
||||
|
||||
namespace SM64 {
|
||||
|
||||
extern const char *const CourseNames[25];
|
||||
extern const char *const LevelNames[37];
|
||||
|
||||
enum class LevelId : ubyte {
|
||||
Null = 0,
|
||||
|
||||
CastleGrounds = 16,
|
||||
CastleInterior = 6,
|
||||
CastleCourtyard = 26,
|
||||
|
||||
BobOmbBattlefield = 9,
|
||||
WhompsFortress = 24,
|
||||
JollyRodgerBay = 12,
|
||||
CoolCoolMountain = 5,
|
||||
BigBoosHaunt = 4,
|
||||
HazyMazeCave = 7,
|
||||
LethalLavaLand = 22,
|
||||
ShiftingSandLand = 8,
|
||||
DireDireDocks = 23,
|
||||
SnowmansLands = 10,
|
||||
WetDryWorld = 11,
|
||||
TallTallMountain = 36,
|
||||
TinyHugeIsland = 13,
|
||||
TickTockClock = 14,
|
||||
RainbowRide = 15,
|
||||
|
||||
BowserInTheDarkWorld = 17,
|
||||
BowserInTheFireSea = 19,
|
||||
BowserInTheSky = 21,
|
||||
Bowser1 = 30,
|
||||
Bowser2 = 33,
|
||||
Bowser3 = 34,
|
||||
|
||||
TowerOfTheWingCap = 29,
|
||||
CavernOfTheMetalCap = 28,
|
||||
VanishCapUnderTheMoat = 18,
|
||||
|
||||
PeachsSecretSlide = 27,
|
||||
SecretAquarium = 20,
|
||||
WingedMarioOverTheRainbow = 31,
|
||||
|
||||
EndScreen = 25
|
||||
};
|
||||
|
||||
extern const std::array<LevelId,31> LevelIds;
|
||||
|
||||
enum class SaveFileFlag : uint {
|
||||
Exists = 0x000001,
|
||||
HasWingCap = 0x000002,
|
||||
HasMetalCap = 0x000004,
|
||||
HasVanishCap = 0x000008,
|
||||
HasBasementKey = 0x000010,
|
||||
HasUpstairsKey = 0x000020,
|
||||
UnlockedBasement = 0x000040,
|
||||
UnlockedUpstairs = 0x000080,
|
||||
BowserSubGone = 0x000100,
|
||||
MoatDrained = 0x000200,
|
||||
UnlockedSlideDoor = 0x000400,
|
||||
UnlockedWhompsDoor = 0x000800,
|
||||
UnlockedCCMDoor = 0x001000,
|
||||
UnlockedJRBDoor = 0x002000,
|
||||
UnlockedDarkWorldDoor = 0x004000,
|
||||
UnlockedFireSeaDoor = 0x008000,
|
||||
CapOnGround = 0x010000,
|
||||
CapOnKlepto = 0x020000,
|
||||
CapOnUkiki = 0x040000,
|
||||
CapOnSnowman = 0x080000,
|
||||
UnlockedTippyDoor = 0x100000
|
||||
};
|
||||
|
||||
DEFINE_FLAG_OPERATIONS( SaveFileFlag, uint )
|
||||
|
||||
struct SaveSlot {
|
||||
LevelId capLevel;
|
||||
ubyte capArea;
|
||||
|
||||
SaveFileFlag flags;
|
||||
ubyte castleStars;
|
||||
|
||||
ubyte starFlags[25];
|
||||
ubyte coinScores[15];
|
||||
|
||||
int countStars() const;
|
||||
};
|
||||
|
||||
class SaveFile {
|
||||
|
||||
private:
|
||||
SaveSlot m_slots[4];
|
||||
|
||||
public:
|
||||
void write( std::ostream &saveFile ) const;
|
||||
static SaveFile read( std::istream &saveFile );
|
||||
|
||||
inline const SaveSlot &slot( int i ) const { return m_slots[i]; }
|
||||
inline SaveSlot &slot( int i ) { return m_slots[i]; }
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
Castle Stars:
|
||||
0 - Toad 1
|
||||
1 - Toad 2
|
||||
2 - Toad 3
|
||||
3 - MIPS 1
|
||||
4 - MIPS 2
|
||||
5 - unused
|
||||
6 - unused
|
||||
7 - unused
|
||||
*/
|
||||
|
||||
|
||||
#endif /* SRC_CORE_SM64_HPP_ */
|
13
src/core/traceable-exception.cpp
Normal file
@@ -0,0 +1,13 @@
|
||||
#include "src/core/traceable-exception.hpp"
|
||||
|
||||
#if defined(DEBUG) && !defined(_WIN32)
|
||||
#include <execinfo.h>
|
||||
|
||||
TraceableException::TraceableException() {
|
||||
m_size = backtrace( m_trace, 32 );
|
||||
}
|
||||
|
||||
void TraceableException::printBacktrace( int fd ) const {
|
||||
backtrace_symbols_fd( m_trace, m_size, fd );
|
||||
}
|
||||
#endif
|
28
src/core/traceable-exception.hpp
Normal file
@@ -0,0 +1,28 @@
|
||||
#ifndef SRC_CORE_TRACEABLE_EXCEPTION_HPP_
|
||||
#define SRC_CORE_TRACEABLE_EXCEPTION_HPP_
|
||||
|
||||
#if defined(DEBUG) && !defined(_WIN32)
|
||||
class TraceableException {
|
||||
|
||||
private:
|
||||
void *m_trace[32];
|
||||
int m_size;
|
||||
|
||||
protected:
|
||||
TraceableException();
|
||||
|
||||
public:
|
||||
virtual ~TraceableException() {};
|
||||
|
||||
void printBacktrace( int fd = 2 ) const;
|
||||
|
||||
};
|
||||
#else
|
||||
class TraceableException {
|
||||
inline void printBacktrace( [[maybe_unused]] int fd = 2 ) const {}
|
||||
};
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
#endif /* SRC_CORE_TRACEABLE_EXCEPTION_HPP_ */
|
92
src/core/uuid.cpp
Normal file
@@ -0,0 +1,92 @@
|
||||
#include "src/core/uuid.hpp"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
Uuid::Uuid() noexcept {
|
||||
std::memset( m_data, 0, 16 );
|
||||
}
|
||||
|
||||
Uuid::Uuid( const Uuid &other ) noexcept {
|
||||
std::memcpy( m_data, other.m_data, 16 );
|
||||
}
|
||||
|
||||
Uuid::Uuid( const ubyte *data ) noexcept {
|
||||
std::memcpy( m_data, data, 16 );
|
||||
}
|
||||
|
||||
Uuid &Uuid::operator=( const Uuid &other ) noexcept {
|
||||
std::memcpy( m_data, other.m_data, 16 );
|
||||
return *this;
|
||||
}
|
||||
|
||||
static constexpr char NUMERAL_OFFSET = '0';
|
||||
static constexpr char LOWER_ALPHA_OFFSET = 'a' - (char)10;
|
||||
static constexpr char UPPER_ALPHA_OFFSET = 'A' - (char)10;
|
||||
|
||||
static void writeHex( char *out, const ubyte *data, size_t numBytes ) noexcept {
|
||||
for( size_t i = 0; i < numBytes; i++ ) {
|
||||
const ubyte upperNibble = data[i] >> 4;
|
||||
const ubyte lowerNibble = data[i] & 0xF;
|
||||
out[i*2] = upperNibble + (upperNibble > 9 ? LOWER_ALPHA_OFFSET : NUMERAL_OFFSET);
|
||||
out[i*2+1] = lowerNibble + (lowerNibble > 9 ? LOWER_ALPHA_OFFSET : NUMERAL_OFFSET);
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool tryReadNibble( char c, ubyte &out ) {
|
||||
if( c < '0' ) {
|
||||
return false;
|
||||
} else if( c <= '9' ) {
|
||||
out = c - NUMERAL_OFFSET;
|
||||
return true;
|
||||
} else if( c < 'A' ) {
|
||||
return false;
|
||||
} else if( c <= 'Z' ) {
|
||||
out = c - UPPER_ALPHA_OFFSET;
|
||||
return true;
|
||||
} else if( c < 'a' ) {
|
||||
return false;
|
||||
} else if( c <= 'z' ) {
|
||||
out = c - LOWER_ALPHA_OFFSET;
|
||||
return true;
|
||||
} else return false;
|
||||
}
|
||||
|
||||
static bool tryReadHex( ubyte *out, const char *str, size_t numBytes ) noexcept {
|
||||
for( size_t i = 0; i < numBytes; i++ ) {
|
||||
ubyte upperNibble, lowerNibble;
|
||||
if( tryReadNibble( str[i*2], upperNibble ) && tryReadNibble( str[i*2+1], lowerNibble ) ) {
|
||||
out[i] = (upperNibble << 4) | lowerNibble;
|
||||
} else return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
string Uuid::toString() const noexcept {
|
||||
char str[37] = "00000000-0000-0000-0000-000000000000";
|
||||
writeHex( str, m_data, 4 );
|
||||
writeHex( &str[9], &m_data[4], 2 );
|
||||
writeHex( &str[14], &m_data[6], 2 );
|
||||
writeHex( &str[19], &m_data[8], 2 );
|
||||
writeHex( &str[24], &m_data[10], 6 );
|
||||
return string( str );
|
||||
}
|
||||
|
||||
bool Uuid::tryParse( const string &str, Uuid &out ) noexcept {
|
||||
const char *const cstr = str.c_str();
|
||||
return (
|
||||
str.length() == 36 &&
|
||||
cstr[8] == '-' &&
|
||||
cstr[13] == '-' &&
|
||||
cstr[18] == '-' &&
|
||||
cstr[23] == '-' &&
|
||||
tryReadHex( out.m_data, cstr, 4 ) &&
|
||||
tryReadHex( &out.m_data[4], &cstr[9], 2 ) &&
|
||||
tryReadHex( &out.m_data[6], &cstr[14], 2 ) &&
|
||||
tryReadHex( &out.m_data[8], &cstr[19], 2 ) &&
|
||||
tryReadHex( &out.m_data[10], &cstr[24], 6 )
|
||||
);
|
||||
}
|
||||
|
||||
int Uuid::compare( const Uuid &lhs, const Uuid &rhs ) noexcept {
|
||||
return std::memcmp( lhs.m_data, rhs.m_data, 16 );
|
||||
}
|
92
src/core/uuid.hpp
Normal file
@@ -0,0 +1,92 @@
|
||||
#ifndef SRC_CORE_UUID_HPP_
|
||||
#define SRC_CORE_UUID_HPP_
|
||||
|
||||
#include <functional>
|
||||
#include <stdexcept>
|
||||
#include "src/types.hpp"
|
||||
#include "src/core/traceable-exception.hpp"
|
||||
|
||||
class UuidParseException : public std::runtime_error, TraceableException {
|
||||
|
||||
private:
|
||||
const string m_badUuid;
|
||||
|
||||
public:
|
||||
UuidParseException( const string &badUuid ) :
|
||||
std::runtime_error( "Failed to parse string as UUID: " + badUuid ),
|
||||
TraceableException(),
|
||||
m_badUuid( badUuid ) {}
|
||||
|
||||
inline const string &badUuid() const noexcept {
|
||||
return m_badUuid;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
class Uuid final {
|
||||
|
||||
private:
|
||||
ubyte m_data[16];
|
||||
|
||||
public:
|
||||
Uuid() noexcept;
|
||||
Uuid( const Uuid &other ) noexcept;
|
||||
Uuid( const ubyte *data ) noexcept;
|
||||
|
||||
Uuid &operator=( const Uuid &other ) noexcept;
|
||||
|
||||
inline const ubyte *data() const noexcept {
|
||||
return m_data;
|
||||
}
|
||||
|
||||
string toString() const noexcept;
|
||||
|
||||
static bool tryParse( const string &str, Uuid &out ) noexcept;
|
||||
inline static Uuid parse( const string &str ) {
|
||||
Uuid uuid;
|
||||
if( !tryParse( str, uuid ) ) {
|
||||
throw UuidParseException( str );
|
||||
}
|
||||
return uuid;
|
||||
}
|
||||
|
||||
static int compare( const Uuid &lhs, const Uuid &rhs ) noexcept;
|
||||
inline static const Uuid &empty() {
|
||||
static const Uuid s_zero;
|
||||
return s_zero;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
template<> struct std::hash<Uuid> {
|
||||
inline size_t operator()( const Uuid & x ) const {
|
||||
static_assert( sizeof( size_t ) == 8 || sizeof( size_t ) == 4 );
|
||||
const size_t *data = (const size_t*)&x;
|
||||
if constexpr( sizeof( size_t ) == 8 ) {
|
||||
return data[0] ^ data[1];
|
||||
} else {
|
||||
return (data[0] ^ data[1]) ^ (data[2] ^ data[3]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
inline bool operator==( const Uuid &lhs, const Uuid &rhs ) noexcept {
|
||||
return Uuid::compare( lhs, rhs ) == 0;
|
||||
}
|
||||
inline bool operator!=( const Uuid &lhs, const Uuid &rhs ) noexcept {
|
||||
return Uuid::compare( lhs, rhs ) != 0;
|
||||
}
|
||||
inline bool operator<( const Uuid &lhs, const Uuid &rhs ) noexcept {
|
||||
return Uuid::compare( lhs, rhs ) < 0;
|
||||
}
|
||||
inline bool operator<=( const Uuid &lhs, const Uuid &rhs ) noexcept {
|
||||
return Uuid::compare( lhs, rhs ) <= 0;
|
||||
}
|
||||
inline bool operator>( const Uuid &lhs, const Uuid &rhs ) noexcept {
|
||||
return Uuid::compare( lhs, rhs ) > 0;
|
||||
}
|
||||
inline bool operator>=( const Uuid &lhs, const Uuid &rhs ) noexcept {
|
||||
return Uuid::compare( lhs, rhs ) >= 0;
|
||||
}
|
||||
|
||||
#endif /* SRC_CORE_UUID_HPP_ */
|
230
src/input/gamepad-controller.cpp
Normal file
@@ -0,0 +1,230 @@
|
||||
#include "gamepad-controller.hpp"
|
||||
|
||||
#define SDL_MAIN_HANDLED 1
|
||||
#include <SDL2/SDL.h>
|
||||
#include <unordered_map>
|
||||
#include <cstring>
|
||||
#include "src/core/preset-controllers.hpp"
|
||||
|
||||
const HashMap<ControllerType, const char*> s_knownNames = {
|
||||
{ ControllerType::Gamecube, "Gamecube Controller" },
|
||||
{ ControllerType::XBox360, "XBox 360 Controller" }
|
||||
};
|
||||
|
||||
static inline double normalize( short axisPosn ) {
|
||||
return (double)axisPosn / (axisPosn >= 0 ? (double)0x7FFF : (double)0x8000 );
|
||||
}
|
||||
|
||||
static GamepadId openGamepad( int index, ControllerInfo &info ) {
|
||||
info.controllerId.vendorId = SDL_JoystickGetDeviceVendor( index );
|
||||
info.controllerId.productId = SDL_JoystickGetDeviceProduct( index );
|
||||
info.uuid = Uuid( SDL_JoystickGetDeviceGUID( index ).data );
|
||||
|
||||
SDL_Joystick *joypad = nullptr;
|
||||
const char *name;
|
||||
if( SDL_IsGameController( index ) ) {
|
||||
SDL_GameController *controller = SDL_GameControllerOpen( index );
|
||||
if( controller == nullptr ) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
name = SDL_GameControllerName( controller );
|
||||
joypad = SDL_GameControllerGetJoystick( controller );
|
||||
info.numButtons = 15;
|
||||
info.numAxes = 6;
|
||||
} else {
|
||||
joypad = SDL_JoystickOpen( index );
|
||||
if( joypad == nullptr ) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
name = SDL_JoystickNameForIndex( index );
|
||||
info.numButtons = (ushort)SDL_JoystickNumButtons( joypad );
|
||||
info.numAxes = (ushort)SDL_JoystickNumAxes( joypad );
|
||||
}
|
||||
|
||||
if( name == nullptr ) {
|
||||
auto i = s_knownNames.find( getControllerType( info.controllerId ) );
|
||||
name = (i != s_knownNames.end()) ? i->second : "";
|
||||
}
|
||||
|
||||
if(
|
||||
info.controllerId.vendorId == 0 &&
|
||||
info.controllerId.productId == 0 &&
|
||||
std::strncmp( name, "Wii U GameCube Adapter", 22 ) == 0
|
||||
) {
|
||||
info.controllerId.vendorId = 0x057E;
|
||||
info.controllerId.productId = 0x0337;
|
||||
}
|
||||
|
||||
info.name = name;
|
||||
return SDL_JoystickInstanceID( joypad );
|
||||
}
|
||||
|
||||
void GamepadController::processEvents() {
|
||||
SDL_Event event;
|
||||
if( !m_timer.isActive() ) return;
|
||||
while( SDL_PollEvent( &event ) ) {
|
||||
switch( event.type ) {
|
||||
case SDL_JOYDEVICEADDED: {
|
||||
ControllerInfo info;
|
||||
SDL_LockJoysticks();
|
||||
const GamepadId joypadId = openGamepad( event.jdevice.which, info );
|
||||
SDL_UnlockJoysticks();
|
||||
if( joypadId >= 0 ) {
|
||||
emit gamepadConnected({
|
||||
joypadId,
|
||||
info
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SDL_JOYDEVICEREMOVED:
|
||||
emit gamepadDisconnected({
|
||||
event.jdevice.which
|
||||
});
|
||||
break;
|
||||
case SDL_CONTROLLERBUTTONDOWN:
|
||||
case SDL_CONTROLLERBUTTONUP:
|
||||
emit gamepadButtonPressed({
|
||||
event.cbutton.which,
|
||||
event.cbutton.button,
|
||||
event.cbutton.state == SDL_PRESSED
|
||||
});
|
||||
break;
|
||||
case SDL_JOYBUTTONDOWN:
|
||||
case SDL_JOYBUTTONUP:
|
||||
if( SDL_GameControllerFromInstanceID( event.jbutton.which ) != nullptr ) break;
|
||||
emit gamepadButtonPressed({
|
||||
event.jbutton.which,
|
||||
event.jbutton.button,
|
||||
event.jbutton.state == SDL_PRESSED
|
||||
});
|
||||
break;
|
||||
case SDL_CONTROLLERAXISMOTION:
|
||||
emit gamepadAxisMoved({
|
||||
event.caxis.which,
|
||||
event.caxis.axis,
|
||||
normalize( event.caxis.value )
|
||||
});
|
||||
break;
|
||||
case SDL_JOYAXISMOTION:
|
||||
if( SDL_GameControllerFromInstanceID( event.jbutton.which ) != nullptr ) break;
|
||||
emit gamepadAxisMoved({
|
||||
event.jaxis.which,
|
||||
event.jaxis.axis,
|
||||
normalize( event.jaxis.value )
|
||||
});
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GamepadController::GamepadController() : QObject() {
|
||||
SDL_Init( SDL_INIT_GAMECONTROLLER | SDL_INIT_JOYSTICK | SDL_INIT_EVENTS );
|
||||
connect( &m_timer, SIGNAL(timeout()), this, SLOT(processEvents()) );
|
||||
}
|
||||
|
||||
GamepadController::~GamepadController() {
|
||||
m_timer.stop();
|
||||
SDL_Quit();
|
||||
}
|
||||
|
||||
void GamepadController::start() {
|
||||
SDL_JoystickEventState( SDL_ENABLE );
|
||||
SDL_GameControllerEventState( SDL_ENABLE );
|
||||
|
||||
SDL_LockJoysticks();
|
||||
const int numConnected = SDL_NumJoysticks();
|
||||
for( int i = 0; i < numConnected; i++ ) {
|
||||
ControllerInfo info;
|
||||
const GamepadId id = openGamepad( i, info );
|
||||
if( id < 0 ) continue;
|
||||
emit gamepadConnected({ id, info });
|
||||
}
|
||||
SDL_UnlockJoysticks();
|
||||
|
||||
processEvents();
|
||||
m_timer.setInterval( 10 );
|
||||
m_timer.start();
|
||||
}
|
||||
|
||||
void GamepadController::stop() {
|
||||
SDL_JoystickEventState( SDL_DISABLE );
|
||||
SDL_GameControllerEventState( SDL_DISABLE );
|
||||
processEvents();
|
||||
m_timer.stop();
|
||||
}
|
||||
|
||||
GamepadController &GamepadController::instance() {
|
||||
static GamepadController s_instance;
|
||||
return s_instance;
|
||||
}
|
||||
|
||||
static inline GamepadState getState_SDLController( SDL_GameController *controller ) {
|
||||
GamepadState state;
|
||||
|
||||
state.buttons.reserve( 15 );
|
||||
state.axes.reserve( 6 );
|
||||
|
||||
for( int i = 0; i < 15; i++ ) {
|
||||
state.buttons.push_back( SDL_GameControllerGetButton( controller, (SDL_GameControllerButton)i ) > 0 );
|
||||
}
|
||||
|
||||
for( int i = 0; i < 6; i++ ) {
|
||||
state.axes.push_back( normalize( SDL_GameControllerGetAxis( controller, (SDL_GameControllerAxis)i ) ) );
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
static inline GamepadState getState_SDLJoystick( SDL_Joystick *joystick ) {
|
||||
GamepadState state;
|
||||
|
||||
int numButtons = SDL_JoystickNumButtons( joystick );
|
||||
int numAxes = SDL_JoystickNumAxes( joystick );
|
||||
|
||||
state.buttons.reserve( numButtons > 0 ? numButtons : 0 );
|
||||
state.axes.reserve( numAxes > 0 ? numAxes : 0 );
|
||||
|
||||
for( int i = 0; i < numButtons; i++ ) {
|
||||
state.buttons.push_back( SDL_JoystickGetButton( joystick, i ) > 0 );
|
||||
}
|
||||
|
||||
for( int i = 0; i < numAxes; i++ ) {
|
||||
state.axes.push_back( normalize( SDL_JoystickGetAxis( joystick, i ) ) );
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
GamepadState GamepadController::getState( GamepadId id ) const {
|
||||
SDL_GameController *controller = SDL_GameControllerFromInstanceID( id );
|
||||
if( controller != nullptr ) {
|
||||
return getState_SDLController( controller );
|
||||
}
|
||||
|
||||
SDL_Joystick *joystick = SDL_JoystickFromInstanceID( id );
|
||||
if( controller != nullptr ) {
|
||||
return getState_SDLJoystick( joystick );
|
||||
}
|
||||
|
||||
return GamepadState();
|
||||
}
|
||||
|
||||
std::vector<ConnectedGamepad> GamepadController::getConnected() const {
|
||||
std::vector<ConnectedGamepad> gamepads;
|
||||
|
||||
SDL_JoystickUpdate();
|
||||
const int numConnected = SDL_NumJoysticks();
|
||||
for( int i = 0; i < numConnected; i++ ) {
|
||||
ConnectedGamepad gamepad;
|
||||
gamepad.id = openGamepad( i, gamepad.info );
|
||||
if( gamepad.id >= 0 ) {
|
||||
gamepads.push_back( std::move( gamepad ) );
|
||||
}
|
||||
}
|
||||
|
||||
return gamepads;
|
||||
}
|
71
src/input/gamepad-controller.hpp
Normal file
@@ -0,0 +1,71 @@
|
||||
#ifndef SRC_INPUT_JOYPAD_CONTROLLER_HPP_
|
||||
#define SRC_INPUT_JOYPAD_CONTROLLER_HPP_
|
||||
|
||||
#include <QTimer>
|
||||
#include <vector>
|
||||
#include "src/core/controller.hpp"
|
||||
|
||||
typedef int GamepadId;
|
||||
|
||||
struct GamepadConnectedEvent {
|
||||
GamepadId id;
|
||||
ControllerInfo info;
|
||||
};
|
||||
|
||||
struct GamepadDisconnectedEvent {
|
||||
GamepadId id;
|
||||
};
|
||||
|
||||
struct GamepadButtonEvent {
|
||||
GamepadId id;
|
||||
ubyte button;
|
||||
bool isPressed;
|
||||
};
|
||||
|
||||
struct GamepadAxisEvent {
|
||||
GamepadId id;
|
||||
ubyte axis;
|
||||
double position;
|
||||
};
|
||||
|
||||
struct GamepadState {
|
||||
std::vector<bool> buttons;
|
||||
std::vector<double> axes;
|
||||
};
|
||||
|
||||
struct ConnectedGamepad {
|
||||
GamepadId id;
|
||||
ControllerInfo info;
|
||||
};
|
||||
|
||||
class GamepadController : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
QTimer m_timer;
|
||||
|
||||
GamepadController();
|
||||
|
||||
public:
|
||||
virtual ~GamepadController();
|
||||
|
||||
void start();
|
||||
void stop();
|
||||
|
||||
std::vector<ConnectedGamepad> getConnected() const;
|
||||
GamepadState getState( GamepadId id ) const;
|
||||
|
||||
static GamepadController &instance();
|
||||
|
||||
private slots:
|
||||
void processEvents();
|
||||
|
||||
signals:
|
||||
void gamepadConnected( GamepadConnectedEvent );
|
||||
void gamepadDisconnected( GamepadDisconnectedEvent );
|
||||
void gamepadButtonPressed( GamepadButtonEvent );
|
||||
void gamepadAxisMoved( GamepadAxisEvent );
|
||||
|
||||
};
|
||||
|
||||
#endif /* SRC_INPUT_JOYPAD_CONTROLLER_HPP_ */
|
120
src/main.cpp
Normal file
@@ -0,0 +1,120 @@
|
||||
#include "src/types.hpp"
|
||||
|
||||
#if defined(DEBUG) && !defined(_WIN32)
|
||||
#include "src/core/traceable-exception.hpp"
|
||||
#include <execinfo.h>
|
||||
#include <exception>
|
||||
#include <iostream>
|
||||
#include <csignal>
|
||||
#include <cstdlib>
|
||||
#endif
|
||||
|
||||
#include <QGuiApplication>
|
||||
#include <QApplication>
|
||||
#include <QMessageBox>
|
||||
#include "src/core/retroarch.hpp"
|
||||
#include "src/ui/main-window.hpp"
|
||||
#include "src/ui/ui-fixes.hpp"
|
||||
#ifdef _WIN32
|
||||
#include <QInputDialog>
|
||||
#include "src/core/file-controller.hpp"
|
||||
|
||||
static const char *const m_msgInstallRetroArch = ""
|
||||
"RetroArch does not appear to be installed. You can install it by going to "
|
||||
"https://www.retroarch.com/ and clicking 'Get RetroArch' then 'Download Stable'.";
|
||||
#else
|
||||
static const char *const m_msgInstallRetroArch = ""
|
||||
"RetroArch does not appear to be installed. You can install it by going to "
|
||||
"https://www.retroarch.com/ and clicking 'Get RetroArch' then 'Download Stable'. "
|
||||
"If you are using a custom build of RetroArch, ensure that the folder containing "
|
||||
"your RetroArch binary is in your $PATH variable.";
|
||||
#endif
|
||||
|
||||
static const char *const s_msgMissingCore = ""
|
||||
"RetroArch is installed, but you have not installed the ParallelN64 core. "
|
||||
"Open up RetroArch, then go to Main Menu → Load Core → Download a Core, "
|
||||
"then scroll down to find Nintendo - Nintendo 64 (ParaLLEl N64) and select it. "
|
||||
"Once it is installed, close out of RetroArch and relaunch this application.";
|
||||
|
||||
static int run( QApplication &app ) {
|
||||
QGuiApplication::setQuitOnLastWindowClosed( false );
|
||||
|
||||
MainWindow mainWindow;
|
||||
mainWindow.show();
|
||||
|
||||
return app.exec();
|
||||
}
|
||||
|
||||
#if defined(DEBUG) && !defined(_WIN32)
|
||||
extern "C" void onSegfault( [[maybe_unused]] int signal ) {
|
||||
void *trace[32];
|
||||
int frames = backtrace( trace, 32 );
|
||||
backtrace_symbols_fd( trace, frames, 2 );
|
||||
std::cerr << "Segmentation Fault" << std::endl << std::flush;
|
||||
std::_Exit( EXIT_FAILURE );
|
||||
}
|
||||
|
||||
static void onTerminate() {
|
||||
const std::exception_ptr ep = std::current_exception();
|
||||
if( !ep ) return;
|
||||
|
||||
try {
|
||||
std::rethrow_exception( ep );
|
||||
} catch( const std::exception &exception ) {
|
||||
std::cerr << "Uncaught Exception: " << std::endl << exception.what() << std::endl;
|
||||
const TraceableException *traceableException = dynamic_cast<const TraceableException*>( &exception );
|
||||
if( traceableException != nullptr ) {
|
||||
traceableException->printBacktrace();
|
||||
}
|
||||
std::cerr << std::flush;
|
||||
} catch( ... ) {
|
||||
std::cerr << "Uncaught Exception: (non-std::exception)" << std::endl << std::flush;
|
||||
}
|
||||
|
||||
std::abort();
|
||||
}
|
||||
#endif
|
||||
|
||||
int main( int argc, char *argv[] ) {
|
||||
#if defined(DEBUG) && !defined(_WIN32)
|
||||
std::signal( SIGSEGV, onSegfault );
|
||||
std::set_terminate( onTerminate );
|
||||
#endif
|
||||
|
||||
QApplication app( argc, argv );
|
||||
applyUiFixes( app );
|
||||
|
||||
#ifdef _WIN32
|
||||
AppSettings settings = FileController::loadAppSettings();
|
||||
while( !RetroArch::isRetroArchInstalled() ) {
|
||||
const QString installDir = QInputDialog::getText( nullptr, "RetroArch Not Installed", m_msgInstallRetroArch, QLineEdit::Normal, settings.retroInstallDir.string().c_str() );
|
||||
if( installDir.isNull() ) {
|
||||
return 0;
|
||||
} else if( !installDir.isEmpty() ) {
|
||||
settings.retroInstallDir = fs::path( installDir.toStdString() );
|
||||
FileController::saveAppSettings( settings );
|
||||
}
|
||||
}
|
||||
#else
|
||||
if( !RetroArch::isRetroArchInstalled() ) {
|
||||
QMessageBox::critical( nullptr, "RetroArch Not Installed", m_msgInstallRetroArch );
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
ushort major, minor;
|
||||
if( RetroArch::tryGetVersion( major, minor ) ) {
|
||||
if( major < 1 || (major == 1 && minor < 9) ) {
|
||||
QMessageBox::warning( nullptr, "Old RetroArch Version", "You are using an old version of RetroArch. Please upgrade to version 1.9.0 or later." );
|
||||
}
|
||||
} else {
|
||||
QMessageBox::warning( nullptr, "Unknown RetroArch Version", "Unable to determine RetroArch version. If you are using a version of RetroArch older than 1.9.0, some features may not work correctly." );
|
||||
}
|
||||
|
||||
if( !RetroArch::isParallelN64Installed() ) {
|
||||
QMessageBox::critical( nullptr, "Missing Emulator Core", s_msgMissingCore );
|
||||
return 0;
|
||||
}
|
||||
|
||||
return run( app );
|
||||
}
|
147
src/polyfill/base-directory.cpp
Normal file
@@ -0,0 +1,147 @@
|
||||
#include "src/polyfill/base-directory.hpp"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#include <knownfolders.h>
|
||||
#include <shlobj.h>
|
||||
#include <libloaderapi.h>
|
||||
#include <exception>
|
||||
#include <string>
|
||||
#include <locale>
|
||||
#include <codecvt>
|
||||
#else
|
||||
#include <cstdlib>
|
||||
#endif
|
||||
|
||||
struct Locations {
|
||||
fs::path home;
|
||||
fs::path config;
|
||||
fs::path data;
|
||||
fs::path cache;
|
||||
fs::path temp;
|
||||
fs::path program;
|
||||
};
|
||||
|
||||
static constexpr char APP_NAME[] = "parallel-launcher";
|
||||
|
||||
#ifdef _WIN32
|
||||
static inline std::string toUtf8( const wchar_t *wtsr ) {
|
||||
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>,wchar_t> converter;
|
||||
return converter.to_bytes( std::wstring( wtsr ) );
|
||||
}
|
||||
|
||||
Locations getLocations() {
|
||||
wchar_t *buffer;
|
||||
|
||||
if( SHGetKnownFolderPath(
|
||||
FOLDERID_Profile,
|
||||
KF_FLAG_NO_ALIAS | KF_FLAG_DEFAULT_PATH | KF_FLAG_NOT_PARENT_RELATIVE,
|
||||
nullptr,
|
||||
&buffer
|
||||
) != S_OK ) {
|
||||
if( buffer != nullptr ) {
|
||||
CoTaskMemFree( buffer );
|
||||
}
|
||||
throw std::runtime_error( "Failed to get home directory location from Windows." );
|
||||
}
|
||||
|
||||
if( buffer != nullptr ) {
|
||||
CoTaskMemFree( buffer );
|
||||
}
|
||||
|
||||
const fs::path home = fs::path( toUtf8( buffer ) );
|
||||
fs::path appData;
|
||||
|
||||
buffer = nullptr;
|
||||
if( SHGetKnownFolderPath(
|
||||
FOLDERID_LocalAppData,
|
||||
KF_FLAG_CREATE | KF_FLAG_NO_ALIAS | KF_FLAG_DONT_VERIFY | KF_FLAG_DEFAULT_PATH | KF_FLAG_NOT_PARENT_RELATIVE,
|
||||
nullptr,
|
||||
&buffer
|
||||
) == S_OK ) {
|
||||
appData = fs::path( toUtf8( buffer ) );
|
||||
} else {
|
||||
appData = home / "AppData" / "Local";
|
||||
}
|
||||
|
||||
appData = appData / APP_NAME;
|
||||
if( buffer != nullptr ) {
|
||||
CoTaskMemFree( buffer );
|
||||
}
|
||||
|
||||
char currentExePath[261];
|
||||
if( GetModuleFileNameA( nullptr, currentExePath, 261 ) == 0 ) {
|
||||
throw std::runtime_error( "Failed to get current executable path from Windows." );
|
||||
}
|
||||
|
||||
return Locations{
|
||||
home,
|
||||
appData / "config",
|
||||
appData / "data",
|
||||
appData / "cache",
|
||||
#ifdef _WIN64
|
||||
fs::temp_directory_path() / APP_NAME,
|
||||
#else
|
||||
appData / "Temp",
|
||||
#endif
|
||||
fs::path( currentExePath ).parent_path()
|
||||
};
|
||||
}
|
||||
#else
|
||||
static fs::path xdg( const char *varName, const fs::path &homePath, const char *defaultRelativePath ) {
|
||||
char *xdgVal = getenv( varName );
|
||||
if( xdgVal != nullptr && xdgVal[0] != '\0' ) {
|
||||
return fs::path( xdgVal );
|
||||
}
|
||||
return homePath / defaultRelativePath;
|
||||
}
|
||||
|
||||
Locations getLocations() {
|
||||
const fs::path home = fs::path( getenv( "HOME" ) );
|
||||
return Locations{
|
||||
home,
|
||||
xdg( "XDG_CONFIG_HOME", home, ".config" ) / APP_NAME,
|
||||
xdg( "XDG_DATA_HOME", home, ".local/share" ) / APP_NAME,
|
||||
xdg( "XDG_CACHE_HOME", home, ".cache" ) / APP_NAME,
|
||||
fs::temp_directory_path() / APP_NAME,
|
||||
fs::read_symlink( "/proc/self/exe" ).parent_path()
|
||||
};
|
||||
}
|
||||
#endif
|
||||
|
||||
static inline Locations fetchOrCreateLocations() {
|
||||
const Locations locations = getLocations();
|
||||
fs::create_directories( locations.config );
|
||||
fs::create_directories( locations.data );
|
||||
fs::create_directories( locations.cache );
|
||||
return locations;
|
||||
}
|
||||
|
||||
static inline const Locations &lazyGetLocations() {
|
||||
static const Locations s_locations = fetchOrCreateLocations();
|
||||
return s_locations;
|
||||
}
|
||||
|
||||
const fs::path &BaseDir::home() {
|
||||
return lazyGetLocations().home;
|
||||
}
|
||||
|
||||
const fs::path &BaseDir::config() {
|
||||
return lazyGetLocations().config;
|
||||
}
|
||||
|
||||
const fs::path &BaseDir::data() {
|
||||
return lazyGetLocations().data;
|
||||
}
|
||||
|
||||
const fs::path &BaseDir::cache() {
|
||||
return lazyGetLocations().cache;
|
||||
}
|
||||
|
||||
const fs::path &BaseDir::temp() {
|
||||
return lazyGetLocations().temp;
|
||||
}
|
||||
|
||||
const fs::path &BaseDir::program() {
|
||||
return lazyGetLocations().program;
|
||||
}
|
18
src/polyfill/base-directory.hpp
Normal file
@@ -0,0 +1,18 @@
|
||||
#ifndef SRC_POLYFILL_BASE_DIRECTORY_HPP_
|
||||
#define SRC_POLYFILL_BASE_DIRECTORY_HPP_
|
||||
|
||||
#include "src/core/filesystem.hpp"
|
||||
#include "src/types.hpp"
|
||||
|
||||
namespace BaseDir {
|
||||
|
||||
const fs::path &home();
|
||||
const fs::path &config();
|
||||
const fs::path &data();
|
||||
const fs::path &cache();
|
||||
const fs::path &temp();
|
||||
const fs::path &program();
|
||||
|
||||
}
|
||||
|
||||
#endif /* SRC_POLYFILL_BASE_DIRECTORY_HPP_ */
|
330
src/polyfill/process.cpp
Normal file
@@ -0,0 +1,330 @@
|
||||
#include "src/polyfill/process.hpp"
|
||||
|
||||
#include <thread>
|
||||
#include <cassert>
|
||||
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#include <shlobj.h>
|
||||
#include <winnt.h>
|
||||
|
||||
#define m_status static_cast<WindowsProcessData*>( m_implData )
|
||||
|
||||
struct WindowsProcessData {
|
||||
PROCESS_INFORMATION info;
|
||||
_STARTUPINFOA si;
|
||||
std::thread thread;
|
||||
int64 exitCode;
|
||||
};
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#include <spawn.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
#define m_status static_cast<PosixProcessData*>( m_implData )
|
||||
|
||||
struct PosixProcessData {
|
||||
pid_t pid;
|
||||
std::thread thread;
|
||||
int exitCode;
|
||||
};
|
||||
#endif
|
||||
|
||||
AsyncProcess::AsyncProcess( AsyncProcess &&other ) noexcept {
|
||||
m_implData = other.m_implData;
|
||||
other.m_implData = nullptr;
|
||||
}
|
||||
|
||||
AsyncProcess &AsyncProcess::operator=( AsyncProcess &&other ) noexcept {
|
||||
if( m_implData != nullptr ) {
|
||||
assert( !m_status->thread.joinable() );
|
||||
delete m_status;
|
||||
}
|
||||
m_implData = other.m_implData;
|
||||
other.m_implData = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
AsyncProcess::~AsyncProcess() {
|
||||
if( m_implData != nullptr ) {
|
||||
assert( !m_status->thread.joinable() );
|
||||
delete m_status;
|
||||
}
|
||||
}
|
||||
|
||||
bool AsyncProcess::running() const noexcept {
|
||||
return (m_implData != nullptr) && m_status->thread.joinable();
|
||||
}
|
||||
|
||||
int64 AsyncProcess::join() {
|
||||
if( m_implData == nullptr ) return -1;
|
||||
if( m_status->thread.joinable() ) {
|
||||
m_status->thread.join();
|
||||
}
|
||||
return m_status->exitCode;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
static void WaitForProcess( WindowsProcessData *info ) {
|
||||
WaitForSingleObject( info->info.hProcess, INFINITE );
|
||||
|
||||
DWORD exitCode;
|
||||
if( GetExitCodeProcess( info->info.hProcess, &exitCode ) && exitCode != 0xFFFFFFFFu ) {
|
||||
info->exitCode = (int64)exitCode;
|
||||
} else {
|
||||
info->exitCode = -1;
|
||||
}
|
||||
|
||||
CloseHandle( info->info.hProcess );
|
||||
CloseHandle( info->info.hThread );
|
||||
}
|
||||
|
||||
static string quoteAndEscape( const string &str ) {
|
||||
// Windows command line parsing is hilariously stupid.
|
||||
// Escape characters only work if they are followed by
|
||||
// a double quote
|
||||
int numEscapes = 0;
|
||||
string result = "\"";
|
||||
for( char c : str ) {
|
||||
switch( c ) {
|
||||
case '\\':
|
||||
numEscapes++;
|
||||
break;
|
||||
case '"':
|
||||
while( numEscapes-- >= 0 ) result += '\\';
|
||||
[[fallthrough]];
|
||||
default:
|
||||
numEscapes = 0;
|
||||
break;
|
||||
}
|
||||
result += c;
|
||||
}
|
||||
while( numEscapes-- > 0 ) result += '\\';
|
||||
result += "\"";
|
||||
return result;
|
||||
}
|
||||
|
||||
AsyncProcess::AsyncProcess( const string &process, const std::vector<string> &args ) {
|
||||
m_implData = new WindowsProcessData();
|
||||
|
||||
ZeroMemory( &m_status->si, sizeof(_STARTUPINFOA) );
|
||||
ZeroMemory( &m_status->info, sizeof(PROCESS_INFORMATION) );
|
||||
m_status->si.cb = sizeof( _STARTUPINFOA );
|
||||
|
||||
string cmd = quoteAndEscape( process );
|
||||
for( const string &arg : args ) {
|
||||
cmd += ' ';
|
||||
cmd += quoteAndEscape( arg );
|
||||
}
|
||||
|
||||
char lpCommandLine[cmd.length() + 1];
|
||||
cmd.copy( lpCommandLine, cmd.length() );
|
||||
lpCommandLine[cmd.length()] = '\0';
|
||||
|
||||
if( !CreateProcessA(
|
||||
nullptr,
|
||||
lpCommandLine,
|
||||
nullptr,
|
||||
nullptr,
|
||||
false,
|
||||
CREATE_NO_WINDOW | CREATE_NEW_PROCESS_GROUP,
|
||||
nullptr,
|
||||
nullptr,
|
||||
&m_status->si,
|
||||
&m_status->info
|
||||
)) {
|
||||
delete m_status;
|
||||
m_implData = nullptr;
|
||||
throw ProcessLaunchException( process, args, (int)GetLastError() );
|
||||
}
|
||||
|
||||
m_status->exitCode = -1;
|
||||
m_status->thread = std::thread( WaitForProcess, m_status );
|
||||
}
|
||||
|
||||
int64 AsyncProcess::pid() const noexcept {
|
||||
if( m_implData == nullptr ) return -1;
|
||||
return (int64)m_status->info.dwProcessId;
|
||||
}
|
||||
|
||||
bool AsyncProcess::kill() {
|
||||
if( m_implData == nullptr ) return false;
|
||||
if( m_status->thread.joinable() ) {
|
||||
const bool result = TerminateProcess( m_status->info.hProcess, 0xFFFFFFFFu );
|
||||
return result;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool tryGetOutputHelper(
|
||||
const string &command,
|
||||
string &output,
|
||||
bool useErrorOutput
|
||||
) {
|
||||
output.clear();
|
||||
|
||||
_STARTUPINFOA si;
|
||||
PROCESS_INFORMATION pi;
|
||||
|
||||
HANDLE stdOutRead = nullptr;
|
||||
HANDLE stdOutWrite = nullptr;
|
||||
|
||||
SECURITY_ATTRIBUTES security;
|
||||
security.nLength = sizeof( SECURITY_ATTRIBUTES );
|
||||
security.bInheritHandle = true;
|
||||
security.lpSecurityDescriptor = nullptr;
|
||||
|
||||
if( !CreatePipe( &stdOutRead, &stdOutWrite, &security, 1024 ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if( !SetHandleInformation( stdOutRead, HANDLE_FLAG_INHERIT, 0 ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ZeroMemory( &si, sizeof(si) );
|
||||
ZeroMemory( &pi, sizeof(pi) );
|
||||
si.cb = sizeof(si);
|
||||
if( useErrorOutput ) {
|
||||
si.hStdError = stdOutWrite;
|
||||
} else {
|
||||
si.hStdOutput = stdOutWrite;
|
||||
}
|
||||
si.dwFlags |= STARTF_USESTDHANDLES;
|
||||
|
||||
char lpCommandLine[command.length() + 1];
|
||||
command.copy( lpCommandLine, command.length() );
|
||||
lpCommandLine[command.length()] = '\0';
|
||||
|
||||
if( !CreateProcessA(
|
||||
nullptr,
|
||||
lpCommandLine,
|
||||
nullptr,
|
||||
nullptr,
|
||||
true,
|
||||
CREATE_NO_WINDOW,
|
||||
nullptr,
|
||||
nullptr,
|
||||
&si,
|
||||
&pi
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if( !CloseHandle( stdOutWrite ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
DWORD bytesRead = 0;
|
||||
char buffer[1024];
|
||||
|
||||
while( ReadFile( stdOutRead, buffer, 1023, &bytesRead, nullptr ) && bytesRead > 0 ) {
|
||||
buffer[bytesRead] = '\0';
|
||||
output += buffer;
|
||||
}
|
||||
|
||||
if( !CloseHandle( stdOutRead ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
DWORD exitCode;
|
||||
if( !GetExitCodeProcess( pi.hProcess, &exitCode ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if( !CloseHandle( pi.hProcess ) || !CloseHandle( pi.hThread ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return exitCode == 0;
|
||||
}
|
||||
|
||||
bool Process::tryGetOutput(
|
||||
const string &command,
|
||||
string &output
|
||||
) {
|
||||
return tryGetOutputHelper( command, output, false );
|
||||
}
|
||||
|
||||
bool Process::tryGetErrorOutput(
|
||||
const string &command,
|
||||
string &output
|
||||
) {
|
||||
return tryGetOutputHelper( command, output, true );
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
AsyncProcess::AsyncProcess( const string &process, const std::vector<string> &args ) {
|
||||
const char *argv[args.size() + 2];
|
||||
argv[0] = process.c_str();
|
||||
for( size_t i = 0; i < args.size(); i++ ) {
|
||||
argv[i+1] = args[i].c_str();
|
||||
}
|
||||
argv[args.size() + 1] = nullptr;
|
||||
|
||||
posix_spawnattr_t spawnAttr;
|
||||
posix_spawnattr_init( &spawnAttr );
|
||||
posix_spawnattr_setflags( &spawnAttr, POSIX_SPAWN_SETSID );
|
||||
|
||||
m_implData = new PosixProcessData();
|
||||
int errorCode = posix_spawnp(
|
||||
&m_status->pid,
|
||||
process.c_str(),
|
||||
nullptr,
|
||||
&spawnAttr,
|
||||
(char *const*)argv,
|
||||
environ
|
||||
);
|
||||
|
||||
if( errorCode != 0 || m_status->pid < 0 ) {
|
||||
delete m_status;
|
||||
m_implData = nullptr;
|
||||
throw ProcessLaunchException( process, args, errorCode );
|
||||
}
|
||||
|
||||
m_status->exitCode = -1;
|
||||
m_status->thread = std::thread( waitpid, m_status->pid, &m_status->exitCode, 0 );
|
||||
}
|
||||
|
||||
int64 AsyncProcess::pid() const noexcept {
|
||||
if( m_implData == nullptr ) return -1;
|
||||
return (int64)m_status->pid;
|
||||
}
|
||||
|
||||
bool AsyncProcess::kill() {
|
||||
if( m_implData == nullptr || !m_status->thread.joinable() ) return false;
|
||||
assert( m_status->pid > 1 );
|
||||
const pid_t sid = getsid( m_status->pid );
|
||||
return sid > 0 && std::system( ("pkill -s "s + std::to_string( sid )).c_str() ) == 0;
|
||||
}
|
||||
|
||||
bool Process::tryGetOutput(
|
||||
const string &command,
|
||||
string &output
|
||||
) {
|
||||
output.clear();
|
||||
|
||||
FILE *proc = popen( command.c_str(), "r" );
|
||||
if( proc == nullptr ) return false;
|
||||
|
||||
char buffer[1024];
|
||||
while( fgets( buffer, 1024, proc ) != nullptr ) {
|
||||
output += buffer;
|
||||
}
|
||||
|
||||
return pclose( proc ) == 0;
|
||||
}
|
||||
|
||||
bool Process::tryGetErrorOutput(
|
||||
const string &command,
|
||||
string &output
|
||||
) {
|
||||
return Process::tryGetOutput( command + " 2>&1 >/dev/null", output );
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#undef m_status
|
77
src/polyfill/process.hpp
Normal file
@@ -0,0 +1,77 @@
|
||||
#ifndef SRC_POLYFILL_PROCESS_HPP_
|
||||
#define SRC_POLYFILL_PROCESS_HPP_
|
||||
|
||||
#include "src/core/traceable-exception.hpp"
|
||||
#include "src/types.hpp"
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
class ProcessLaunchException : public std::runtime_error, TraceableException {
|
||||
|
||||
private:
|
||||
const string m_process;
|
||||
const std::vector<string> m_args;
|
||||
const int m_errorCode;
|
||||
|
||||
public:
|
||||
ProcessLaunchException(
|
||||
const string &process,
|
||||
const std::vector<string> &args,
|
||||
int errorCode
|
||||
) :
|
||||
std::runtime_error( "Failed to launch child process" ),
|
||||
TraceableException(),
|
||||
m_process( process ),
|
||||
m_args( args ),
|
||||
m_errorCode( errorCode )
|
||||
{}
|
||||
|
||||
inline const string &process() const noexcept { return m_process; }
|
||||
inline const std::vector<string> &processArgs() const noexcept { return m_args; }
|
||||
inline int errorCode() const noexcept { return m_errorCode; }
|
||||
|
||||
};
|
||||
|
||||
class AsyncProcess {
|
||||
|
||||
private:
|
||||
void *m_implData;
|
||||
|
||||
public:
|
||||
AsyncProcess() noexcept : m_implData( nullptr ) {}
|
||||
|
||||
AsyncProcess( const string &process, const std::vector<string> &args );
|
||||
|
||||
AsyncProcess( const AsyncProcess &other ) = delete;
|
||||
AsyncProcess( AsyncProcess &&other ) noexcept;
|
||||
|
||||
~AsyncProcess();
|
||||
|
||||
AsyncProcess &operator=( const AsyncProcess &other ) = delete;
|
||||
AsyncProcess &operator=( AsyncProcess &&other ) noexcept;
|
||||
|
||||
int64 pid() const noexcept;
|
||||
bool running() const noexcept;
|
||||
|
||||
int64 join();
|
||||
bool kill();
|
||||
|
||||
};
|
||||
|
||||
namespace Process {
|
||||
|
||||
extern bool tryGetOutput(
|
||||
const string &command,
|
||||
string &output
|
||||
);
|
||||
|
||||
extern bool tryGetErrorOutput(
|
||||
const string &command,
|
||||
string &output
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
#endif /* SRC_POLYFILL_PROCESS_HPP_ */
|
25
src/types.hpp
Normal file
@@ -0,0 +1,25 @@
|
||||
#ifndef SRC_TYPES_HPP_
|
||||
#define SRC_TYPES_HPP_
|
||||
|
||||
#include <string>
|
||||
|
||||
using std::string;
|
||||
using std::size_t;
|
||||
using namespace std::string_literals;
|
||||
|
||||
#define HashMap std::unordered_map
|
||||
#define HashSet std::unordered_set
|
||||
|
||||
typedef signed char sbyte;
|
||||
typedef unsigned char ubyte;
|
||||
|
||||
#ifdef _WIN32
|
||||
typedef unsigned short ushort;
|
||||
typedef unsigned uint;
|
||||
typedef unsigned long ulong;
|
||||
#endif
|
||||
|
||||
typedef long long int64;
|
||||
typedef unsigned long long uint64;
|
||||
|
||||
#endif /* SRC_TYPES_HPP_ */
|
98
src/ui/bind-input-dialog.cpp
Normal file
@@ -0,0 +1,98 @@
|
||||
#include "src/ui/bind-input-dialog.hpp"
|
||||
#include "ui_bind-input-dialog.h"
|
||||
|
||||
#include <cmath>
|
||||
#include "src/ui/icons.hpp"
|
||||
|
||||
static constexpr double AXIS_MOVE_THRESHOLD = 0.2;
|
||||
|
||||
BindInputDialog::BindInputDialog( const QString &input, GamepadId gamepadId, bool showSkipButton ) :
|
||||
QDialog( nullptr ),
|
||||
m_ui( new Ui::BindInputDialog ),
|
||||
m_gamepadId( gamepadId ),
|
||||
m_binding({ BindingType::None, 0 })
|
||||
{
|
||||
m_ui->setupUi( this );
|
||||
|
||||
m_ui->skipButton->setVisible( showSkipButton );
|
||||
m_ui->inputName->setText( input );
|
||||
setWindowTitle( input );
|
||||
|
||||
setWindowIcon( Icon::appIcon() );
|
||||
m_ui->skipButton->setIcon( Icon::skip() );
|
||||
m_ui->cancelButton->setIcon( Icon::cancel() );
|
||||
|
||||
connect( &GamepadController::instance(), SIGNAL( gamepadButtonPressed(GamepadButtonEvent) ), this, SLOT( buttonPressed(GamepadButtonEvent) ) );
|
||||
connect( &GamepadController::instance(), SIGNAL( gamepadAxisMoved(GamepadAxisEvent) ), this, SLOT( axisMoved(GamepadAxisEvent) ) );
|
||||
m_initialState = GamepadController::instance().getState( gamepadId );
|
||||
}
|
||||
|
||||
BindInputDialog::~BindInputDialog() {
|
||||
cleanup();
|
||||
delete m_ui;
|
||||
}
|
||||
|
||||
void BindInputDialog::cleanup() {
|
||||
for( auto &i : m_axisTimers ) {
|
||||
i.second->stop();
|
||||
i.second->deleteLater();
|
||||
}
|
||||
m_axisTimers.clear();
|
||||
}
|
||||
|
||||
void BindInputDialog::buttonPressed( GamepadButtonEvent event ) {
|
||||
if( event.id != m_gamepadId || !event.isPressed ) return;
|
||||
|
||||
m_binding = {
|
||||
BindingType::Button,
|
||||
(ushort)event.button
|
||||
};
|
||||
cleanup();
|
||||
accept();
|
||||
}
|
||||
|
||||
void BindInputDialog::axisMoved( GamepadAxisEvent event ) {
|
||||
if( event.id != m_gamepadId ) return;
|
||||
|
||||
if(
|
||||
std::abs( event.position ) > AXIS_MOVE_THRESHOLD &&
|
||||
std::abs( event.position - m_initialState.axes.at( event.axis ) ) > AXIS_MOVE_THRESHOLD
|
||||
) {
|
||||
if( m_axisTimers.count( event.axis ) == 0 ) {
|
||||
QTimer *timer = new QTimer( this );
|
||||
connect( timer, SIGNAL(timeout()), this, SLOT(axisHeld()) );
|
||||
timer->setSingleShot( true );
|
||||
timer->start( 500 );
|
||||
m_axisTimers[event.axis] = timer;
|
||||
}
|
||||
} else {
|
||||
if( m_axisTimers.count( event.axis ) > 0 ) {
|
||||
m_axisTimers[event.axis]->stop();
|
||||
m_axisTimers[event.axis]->deleteLater();
|
||||
m_axisTimers.erase( event.axis );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BindInputDialog::axisHeld() {
|
||||
for( const auto &i : m_axisTimers ) {
|
||||
if( i.second->remainingTime() <= 0 ) {
|
||||
const GamepadState currentState = GamepadController::instance().getState( m_gamepadId );
|
||||
if( i.first >= currentState.axes.size() ) continue;
|
||||
|
||||
m_binding = {
|
||||
currentState.axes.at( i.first ) > 0 ? BindingType::AxisPositive : BindingType::AxisNegative,
|
||||
(ushort)i.first
|
||||
};
|
||||
|
||||
cleanup();
|
||||
accept();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Binding BindInputDialog::getBinding( bool &cancelled ) {
|
||||
cancelled = (exec() == QDialog::Rejected);
|
||||
return m_binding;
|
||||
}
|
42
src/ui/bind-input-dialog.hpp
Normal file
@@ -0,0 +1,42 @@
|
||||
#ifndef SRC_UI_BIND_INPUT_DIALOG_HPP_
|
||||
#define SRC_UI_BIND_INPUT_DIALOG_HPP_
|
||||
|
||||
#include <QDialog>
|
||||
#include <map>
|
||||
#include "src/input/gamepad-controller.hpp"
|
||||
|
||||
namespace Ui {
|
||||
class BindInputDialog;
|
||||
}
|
||||
|
||||
class BindInputDialog : protected QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
Ui::BindInputDialog *m_ui;
|
||||
GamepadId m_gamepadId;
|
||||
Binding m_binding;
|
||||
GamepadState m_initialState;
|
||||
std::map<ubyte,QTimer*> m_axisTimers;
|
||||
|
||||
void cleanup();
|
||||
|
||||
public:
|
||||
BindInputDialog( const QString &input, GamepadId gamepadId, bool showSkipButton );
|
||||
virtual ~BindInputDialog();
|
||||
|
||||
Binding getBinding( bool &cancelled );
|
||||
inline Binding getBinding() {
|
||||
bool scratch;
|
||||
return getBinding( scratch );
|
||||
}
|
||||
|
||||
private slots:
|
||||
void buttonPressed( GamepadButtonEvent event );
|
||||
void axisMoved( GamepadAxisEvent event );
|
||||
void axisHeld();
|
||||
|
||||
};
|
||||
|
||||
|
||||
#endif /* SRC_UI_BIND_INPUT_DIALOG_HPP_ */
|
166
src/ui/controller-config-dialog.cpp
Normal file
@@ -0,0 +1,166 @@
|
||||
#include "src/ui/controller-config-dialog.hpp"
|
||||
#include "ui_controller-config-dialog.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include <QInputDialog>
|
||||
#include <QMessageBox>
|
||||
#include "src/input/gamepad-controller.hpp"
|
||||
#include "src/core/preset-controllers.hpp"
|
||||
#include "src/core/file-controller.hpp"
|
||||
#include "src/ui/bind-input-dialog.hpp"
|
||||
#include "src/ui/neutral-input-dialog.hpp"
|
||||
#include "src/ui/icons.hpp"
|
||||
|
||||
static const char *s_actionNames[] = {
|
||||
"Analog Up",
|
||||
"Analog Down",
|
||||
"Analog Left",
|
||||
"Analog Right",
|
||||
"C Up",
|
||||
"C Down",
|
||||
"C Left",
|
||||
"C Right",
|
||||
"D-Pad Up",
|
||||
"D-Pad Down",
|
||||
"D-Pad Left",
|
||||
"D-Pad Right",
|
||||
"A Button",
|
||||
"B Button",
|
||||
"L Trigger",
|
||||
"Z Trigger",
|
||||
"R Trigger",
|
||||
"Start Button",
|
||||
"Save State",
|
||||
"Load State"
|
||||
};
|
||||
|
||||
static_assert( sizeof( s_actionNames) == sizeof(char*) * (size_t)ControllerAction::NUM_ACTIONS );
|
||||
|
||||
ControllerConfigDialog::ControllerConfigDialog( QWidget *parent ) :
|
||||
QDialog( parent ),
|
||||
m_ui( new Ui::ControllerConfigDialog ),
|
||||
m_controllerId( -1 )
|
||||
{
|
||||
m_ui->setupUi( this );
|
||||
m_profile = DefaultProfile::XBox360;
|
||||
|
||||
setWindowIcon( Icon::appIcon() );
|
||||
m_ui->quickConfigureButton->setIcon( Icon::fastForward() );
|
||||
m_ui->saveAsButton->setIcon( Icon::saveAs() );
|
||||
m_ui->saveButton->setIcon( Icon::save() );
|
||||
m_ui->cancelButton->setIcon( Icon::cancel() );
|
||||
|
||||
connect( m_ui->bindingButtonGroup, SIGNAL( buttonClicked(QAbstractButton*) ), this, SLOT( bindSingle(QAbstractButton*) ) );
|
||||
}
|
||||
|
||||
ControllerConfigDialog::~ControllerConfigDialog() {
|
||||
delete m_ui;
|
||||
}
|
||||
|
||||
static void updateButtonText( QAbstractButton *button, const ControllerProfile &profile ) {
|
||||
const Binding &binding = profile.bindings[button->property( "controllerBinding" ).toInt()];
|
||||
switch( binding.type ) {
|
||||
case BindingType::None:
|
||||
button->setText( "Not Bound" );
|
||||
break;
|
||||
case BindingType::Button:
|
||||
button->setText( ("Button "s + std::to_string( binding.buttonOrAxis )).c_str() );
|
||||
break;
|
||||
case BindingType::AxisNegative:
|
||||
button->setText( ("Axis -"s + std::to_string( binding.buttonOrAxis )).c_str() );
|
||||
break;
|
||||
case BindingType::AxisPositive:
|
||||
button->setText( ("Axis +"s + std::to_string( binding.buttonOrAxis )).c_str() );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ControllerConfigDialog::setActiveController(
|
||||
const string &name,
|
||||
const Uuid &uuid,
|
||||
int id,
|
||||
const ControllerProfile &profile
|
||||
) {
|
||||
m_ui->controllerNameLabel->setText( name.c_str() );
|
||||
m_profile = profile;
|
||||
m_controllerUuid = uuid;
|
||||
m_controllerId = id;
|
||||
|
||||
m_ui->rumbleCheckbox->setChecked( profile.rumble );
|
||||
m_ui->sensitivitySpinner->setValue( profile.sensitivity );
|
||||
m_ui->deadzoneSpinner->setValue( profile.deadzone );
|
||||
|
||||
for( QAbstractButton *button : m_ui->bindingButtonGroup->buttons() ) {
|
||||
updateButtonText( button, profile );
|
||||
}
|
||||
|
||||
m_ui->saveButton->setEnabled( !DefaultProfile::exists( profile.name ) );
|
||||
}
|
||||
|
||||
void ControllerConfigDialog::save() {
|
||||
assert( !DefaultProfile::exists( m_profile.name ) );
|
||||
|
||||
m_profile.rumble = m_ui->rumbleCheckbox->isChecked();
|
||||
m_profile.sensitivity = m_ui->sensitivitySpinner->value();
|
||||
m_profile.deadzone = m_ui->deadzoneSpinner->value();
|
||||
|
||||
std::map<string, ControllerProfile> savedProfiles = FileController::loadControllerProfiles();
|
||||
savedProfiles[m_profile.name] = m_profile;
|
||||
FileController::saveControllerProfiles( savedProfiles );
|
||||
|
||||
accept();
|
||||
}
|
||||
|
||||
void ControllerConfigDialog::saveAs() {
|
||||
QString profileName = QInputDialog::getText( this, "Enter Profile Name", "Enter a name for your new controller profile." );
|
||||
if( profileName.isNull() || profileName.isEmpty() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if( DefaultProfile::exists( profileName.toStdString() ) ) {
|
||||
QMessageBox::critical( this, "Failed to Save Profile", "A default controller profile with that name already exists." );
|
||||
return;
|
||||
}
|
||||
|
||||
//TODO: warn if overwriting an existing profile
|
||||
|
||||
m_profile.name = profileName.toStdString();
|
||||
save();
|
||||
}
|
||||
|
||||
void ControllerConfigDialog::bindAll() {
|
||||
Binding previousBindings[(ubyte)ControllerAction::NUM_ACTIONS];
|
||||
std::memcpy( previousBindings, m_profile.bindings, sizeof( m_profile.bindings ) );
|
||||
|
||||
bool cancelled = false;
|
||||
for( QAbstractButton *button : m_ui->bindingButtonGroup->buttons() ) {
|
||||
const int action = button->property( "controllerBinding" ).toInt();
|
||||
|
||||
BindInputDialog dialog( s_actionNames[action], m_controllerId, true );
|
||||
m_profile.bindings[action] = dialog.getBinding( cancelled );
|
||||
updateButtonText( button, m_profile );
|
||||
|
||||
if( cancelled ) break;
|
||||
|
||||
if( m_profile.bindings[action].type != BindingType::None ) {
|
||||
NeutralInputDialog( m_controllerId ).exec();
|
||||
}
|
||||
}
|
||||
|
||||
if( cancelled ) {
|
||||
std::memcpy( m_profile.bindings, previousBindings, sizeof( m_profile.bindings ) );
|
||||
for( QAbstractButton *button : m_ui->bindingButtonGroup->buttons() ) {
|
||||
updateButtonText( button, m_profile );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ControllerConfigDialog::bindSingle( QAbstractButton *button ) {
|
||||
const int action = button->property( "controllerBinding" ).toInt();
|
||||
|
||||
BindInputDialog dialog( s_actionNames[action], m_controllerId, false );
|
||||
m_profile.bindings[action] = dialog.getBinding();
|
||||
|
||||
updateButtonText( button, m_profile );
|
||||
}
|
38
src/ui/controller-config-dialog.hpp
Normal file
@@ -0,0 +1,38 @@
|
||||
#ifndef SRC_UI_CONTROLLER_CONFIG_DIALOG_HPP_
|
||||
#define SRC_UI_CONTROLLER_CONFIG_DIALOG_HPP_
|
||||
|
||||
#include <QDialog>
|
||||
#include <QAbstractButton>
|
||||
#include "src/core/controller.hpp"
|
||||
#include "src/core/uuid.hpp"
|
||||
|
||||
namespace Ui {
|
||||
class ControllerConfigDialog;
|
||||
}
|
||||
|
||||
class ControllerConfigDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
Ui::ControllerConfigDialog *m_ui;
|
||||
ControllerProfile m_profile;
|
||||
Uuid m_controllerUuid;
|
||||
int m_controllerId;
|
||||
|
||||
public:
|
||||
explicit ControllerConfigDialog( QWidget *parent = nullptr );
|
||||
virtual ~ControllerConfigDialog();
|
||||
|
||||
void setActiveController( const string &name, const Uuid &uuid, int id, const ControllerProfile &profile );
|
||||
inline const string &getProfileName() const { return m_profile.name; }
|
||||
|
||||
private slots:
|
||||
void save();
|
||||
void saveAs();
|
||||
void bindAll();
|
||||
void bindSingle( QAbstractButton *button );
|
||||
|
||||
};
|
||||
|
||||
|
||||
#endif /* SRC_UI_CONTROLLER_CONFIG_DIALOG_HPP_ */
|
186
src/ui/controller-select-dialog.cpp
Normal file
@@ -0,0 +1,186 @@
|
||||
#include "src/ui/controller-select-dialog.hpp"
|
||||
#include "ui_controller-select-dialog.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <QMessageBox>
|
||||
#include "src/core/file-controller.hpp"
|
||||
#include "src/core/preset-controllers.hpp"
|
||||
#include "src/ui/controller-config-dialog.hpp"
|
||||
#include "src/ui/icons.hpp"
|
||||
|
||||
ControllerSelectDialog::ControllerSelectDialog( QWidget *parent ) :
|
||||
QDialog( parent ),
|
||||
m_ui( new Ui::ControllerSelectDialog ),
|
||||
m_disableEvents( true )
|
||||
{
|
||||
m_ui->setupUi( this );
|
||||
m_profiles = FileController::loadControllerProfiles();
|
||||
m_profileMap = FileController::loadControllerMappings();
|
||||
|
||||
setWindowIcon( Icon::appIcon() );
|
||||
m_ui->editButton->setIcon( Icon::edit() );
|
||||
m_ui->deleteButton->setIcon( Icon::delet() );
|
||||
|
||||
for( const auto &i : m_profiles ) {
|
||||
m_ui->profileList->addItem( i.first.c_str() );
|
||||
}
|
||||
|
||||
connect( &GamepadController::instance(), SIGNAL( gamepadConnected( GamepadConnectedEvent ) ), this, SLOT( controllerConnected( GamepadConnectedEvent ) ) );
|
||||
connect( &GamepadController::instance(), SIGNAL( gamepadDisconnected( GamepadDisconnectedEvent ) ), this, SLOT( controllerDisconnected( GamepadDisconnectedEvent ) ) );
|
||||
|
||||
m_disableEvents = false;
|
||||
GamepadController::instance().start();
|
||||
refreshDeviceList();
|
||||
}
|
||||
|
||||
ControllerSelectDialog::~ControllerSelectDialog() {
|
||||
GamepadController::instance().stop();
|
||||
delete m_ui;
|
||||
}
|
||||
|
||||
void ControllerSelectDialog::refreshDeviceList() {
|
||||
m_disableEvents = true;
|
||||
|
||||
GamepadId lastSelected = m_ui->deviceList->currentRow();
|
||||
int selectedRow = 0;
|
||||
|
||||
m_ui->deviceList->clear();
|
||||
for( const auto &i : m_devices ) {
|
||||
QListWidgetItem *item = new QListWidgetItem;
|
||||
item->setData( Qt::UserRole, QVariant::fromValue<int>( i.first ) );
|
||||
|
||||
if( !i.second.name.empty() ) {
|
||||
item->setText( i.second.name.c_str() );
|
||||
} else {
|
||||
item->setText( ("Unknown ("s + i.second.uuid.toString() + ')').c_str() );
|
||||
}
|
||||
|
||||
m_ui->deviceList->addItem( item );
|
||||
if( i.first == lastSelected ) {
|
||||
selectedRow = m_ui->deviceList->count() - 1;
|
||||
}
|
||||
}
|
||||
|
||||
if( !m_devices.empty() ) {
|
||||
m_ui->deviceList->setCurrentRow( selectedRow );
|
||||
}
|
||||
|
||||
m_disableEvents = false;
|
||||
deviceSelected();
|
||||
}
|
||||
|
||||
void ControllerSelectDialog::deviceSelected() {
|
||||
if( m_disableEvents ) return;
|
||||
|
||||
if( m_devices.empty() || m_ui->deviceList->currentItem() == nullptr ) {
|
||||
m_ui->profileList->setEnabled( false );
|
||||
m_ui->editButton->setEnabled( false );
|
||||
m_ui->deleteButton->setEnabled( false );
|
||||
return;
|
||||
}
|
||||
|
||||
m_ui->profileList->setEnabled( true );
|
||||
m_ui->editButton->setEnabled( true );
|
||||
|
||||
GamepadId id = m_ui->deviceList->currentItem()->data( Qt::UserRole ).toInt();
|
||||
const ControllerInfo &info = m_devices.at( id );
|
||||
|
||||
string activeProfile;
|
||||
if( m_profileMap.count( info.uuid ) ) {
|
||||
activeProfile = m_profileMap.at( info.uuid );
|
||||
if( m_profiles.count( activeProfile ) <= 0 ) {
|
||||
activeProfile.clear();
|
||||
}
|
||||
}
|
||||
|
||||
if( activeProfile.empty() ) {
|
||||
switch( getControllerType( info.controllerId ) ) {
|
||||
case ControllerType::Gamecube:
|
||||
activeProfile = DefaultProfile::Gamecube.name;
|
||||
break;
|
||||
default:
|
||||
activeProfile = DefaultProfile::XBox360.name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
m_ui->deleteButton->setEnabled( !DefaultProfile::exists( activeProfile ) );
|
||||
|
||||
m_disableEvents = true;
|
||||
for( int i = 0; i < m_ui->profileList->count(); i++ ) {
|
||||
if( m_ui->profileList->item( i )->text().toStdString() == activeProfile ) {
|
||||
m_ui->profileList->setCurrentRow( i );
|
||||
break;
|
||||
}
|
||||
}
|
||||
m_disableEvents = false;
|
||||
}
|
||||
|
||||
void ControllerSelectDialog::profileSelected() {
|
||||
if( m_disableEvents || m_ui->deviceList->currentItem() == nullptr || m_ui->profileList->currentItem() == nullptr ) return;
|
||||
|
||||
const GamepadId id = m_ui->deviceList->currentItem()->data( Qt::UserRole ).toInt();
|
||||
if( m_devices.count( id ) <= 0 ) return;
|
||||
|
||||
const Uuid &uuid = m_devices.at( id ).uuid;
|
||||
const string profile = m_ui->profileList->currentItem()->text().toStdString();
|
||||
m_profileMap[uuid] = profile;
|
||||
m_ui->deleteButton->setEnabled( !DefaultProfile::exists( profile ) );
|
||||
FileController::saveControllerMappings( m_profileMap );
|
||||
}
|
||||
|
||||
void ControllerSelectDialog::editProfile() {
|
||||
const GamepadId deviceId = m_ui->deviceList->currentItem()->data( Qt::UserRole ).toInt();
|
||||
const ControllerInfo &info = m_devices.at( deviceId );
|
||||
|
||||
ControllerConfigDialog dialog;
|
||||
dialog.setActiveController( info.name, info.uuid, deviceId, m_profiles.at( m_ui->profileList->currentItem()->text().toStdString() ) );
|
||||
dialog.exec();
|
||||
|
||||
m_profiles = FileController::loadControllerProfiles();
|
||||
const string &activeProfile = dialog.getProfileName();
|
||||
int profileIndex = 0;
|
||||
int i = 0;
|
||||
|
||||
m_disableEvents = true;
|
||||
m_ui->profileList->clear();
|
||||
for( const auto &p : m_profiles ) {
|
||||
m_ui->profileList->addItem( p.first.c_str() );
|
||||
if( p.first == activeProfile ) {
|
||||
profileIndex = i;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
m_ui->profileList->setCurrentRow( profileIndex );
|
||||
m_disableEvents = false;
|
||||
profileSelected();
|
||||
}
|
||||
|
||||
void ControllerSelectDialog::deleteProfile() {
|
||||
assert( m_ui->profileList->currentItem() != nullptr );
|
||||
|
||||
const string profile = m_ui->profileList->currentItem()->text().toStdString();
|
||||
assert( !DefaultProfile::exists( profile ) );
|
||||
|
||||
if( QMessageBox::question( nullptr, "Confirm Delete", ("Are you sure you want to delete controller profile '"s + profile + "'?").c_str() ) != QMessageBox::Yes ) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_profiles.erase( profile );
|
||||
FileController::saveControllerProfiles( m_profiles );
|
||||
delete m_ui->profileList->takeItem( m_ui->profileList->currentRow() );
|
||||
|
||||
m_ui->profileList->setCurrentRow( 0 );
|
||||
profileSelected();
|
||||
}
|
||||
|
||||
void ControllerSelectDialog::controllerConnected( GamepadConnectedEvent event ) {
|
||||
m_devices[event.id] = event.info;
|
||||
refreshDeviceList();
|
||||
}
|
||||
|
||||
void ControllerSelectDialog::controllerDisconnected( GamepadDisconnectedEvent event ) {
|
||||
m_devices.erase( event.id );
|
||||
refreshDeviceList();
|
||||
}
|
41
src/ui/controller-select-dialog.hpp
Normal file
@@ -0,0 +1,41 @@
|
||||
#ifndef SRC_UI_CONTROLLER_SELECT_DIALOG_HPP_
|
||||
#define SRC_UI_CONTROLLER_SELECT_DIALOG_HPP_
|
||||
|
||||
#include <QDialog>
|
||||
#include <map>
|
||||
#include "src/input/gamepad-controller.hpp"
|
||||
|
||||
namespace Ui {
|
||||
class ControllerSelectDialog;
|
||||
}
|
||||
|
||||
class ControllerSelectDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
Ui::ControllerSelectDialog *m_ui;
|
||||
std::map<GamepadId,ControllerInfo> m_devices;
|
||||
std::map<string, ControllerProfile> m_profiles;
|
||||
HashMap<Uuid,string> m_profileMap;
|
||||
bool m_disableEvents;
|
||||
|
||||
void refreshDeviceList();
|
||||
|
||||
public:
|
||||
explicit ControllerSelectDialog( QWidget *parent = nullptr );
|
||||
virtual ~ControllerSelectDialog();
|
||||
|
||||
private slots:
|
||||
void deviceSelected();
|
||||
void profileSelected();
|
||||
void editProfile();
|
||||
void deleteProfile();
|
||||
|
||||
void controllerConnected( GamepadConnectedEvent event );
|
||||
void controllerDisconnected( GamepadDisconnectedEvent event );
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif /* SRC_UI_CONTROLLER_SELECT_DIALOG_HPP_ */
|
128
src/ui/designer/bind-input-dialog.ui
Normal file
@@ -0,0 +1,128 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>BindInputDialog</class>
|
||||
<widget class="QDialog" name="BindInputDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>287</width>
|
||||
<height>177</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Bind Input</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="inputName">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>14</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>A Button</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>To map a button or axis to this input, either press a button on your controller or hold a control stick or trigger in a different position for a half second.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>10</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="skipButton">
|
||||
<property name="text">
|
||||
<string>Skip</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="go-next-skip">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="cancelButton">
|
||||
<property name="text">
|
||||
<string>Cancel</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="dialog-cancel">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>skipButton</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>BindInputDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>149</x>
|
||||
<y>149</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>277</x>
|
||||
<y>19</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>cancelButton</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>BindInputDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>248</x>
|
||||
<y>155</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>282</x>
|
||||
<y>127</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
818
src/ui/designer/controller-config-dialog.ui
Normal file
@@ -0,0 +1,818 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>ControllerConfigDialog</class>
|
||||
<widget class="QDialog" name="ControllerConfigDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>675</width>
|
||||
<height>475</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Configure Controller</string>
|
||||
</property>
|
||||
<property name="modal">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_19">
|
||||
<property name="text">
|
||||
<string>Configuring Controller:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="controllerNameLabel">
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>MY CONTROLLER NAME</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||
<item>
|
||||
<widget class="QPushButton" name="quickConfigureButton">
|
||||
<property name="text">
|
||||
<string>Quick Configure</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="media-seek-forward">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_4">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="rumbleCheckbox">
|
||||
<property name="text">
|
||||
<string>Enable Rumble</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout" stretch="1,0,1,0,1">
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Analog Up</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QPushButton" name="pushButton">
|
||||
<property name="text">
|
||||
<string>Not Bound</string>
|
||||
</property>
|
||||
<property name="controllerBinding" stdset="0">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">bindingButtonGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Analog Down</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QPushButton" name="pushButton_2">
|
||||
<property name="text">
|
||||
<string>Not Bound</string>
|
||||
</property>
|
||||
<property name="controllerBinding" stdset="0">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">bindingButtonGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Analog Left</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QPushButton" name="pushButton_3">
|
||||
<property name="text">
|
||||
<string>Not Bound</string>
|
||||
</property>
|
||||
<property name="controllerBinding" stdset="0">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">bindingButtonGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Analog Right</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QPushButton" name="pushButton_4">
|
||||
<property name="text">
|
||||
<string>Not Bound</string>
|
||||
</property>
|
||||
<property name="controllerBinding" stdset="0">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">bindingButtonGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>A Button</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QPushButton" name="pushButton_5">
|
||||
<property name="text">
|
||||
<string>Not Bound</string>
|
||||
</property>
|
||||
<property name="controllerBinding" stdset="0">
|
||||
<number>12</number>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">bindingButtonGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>B Button</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QPushButton" name="pushButton_6">
|
||||
<property name="text">
|
||||
<string>Not Bound</string>
|
||||
</property>
|
||||
<property name="controllerBinding" stdset="0">
|
||||
<number>13</number>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">bindingButtonGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="text">
|
||||
<string>C Up</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QPushButton" name="pushButton_7">
|
||||
<property name="text">
|
||||
<string>Not Bound</string>
|
||||
</property>
|
||||
<property name="controllerBinding" stdset="0">
|
||||
<number>4</number>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">bindingButtonGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_8">
|
||||
<property name="text">
|
||||
<string>C Down</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QPushButton" name="pushButton_8">
|
||||
<property name="text">
|
||||
<string>Not Bound</string>
|
||||
</property>
|
||||
<property name="controllerBinding" stdset="0">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">bindingButtonGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_9">
|
||||
<property name="text">
|
||||
<string>C Left</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QPushButton" name="pushButton_9">
|
||||
<property name="text">
|
||||
<string>Not Bound</string>
|
||||
</property>
|
||||
<property name="controllerBinding" stdset="0">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">bindingButtonGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_10">
|
||||
<property name="text">
|
||||
<string>C Right</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QPushButton" name="pushButton_10">
|
||||
<property name="text">
|
||||
<string>Not Bound</string>
|
||||
</property>
|
||||
<property name="controllerBinding" stdset="0">
|
||||
<number>7</number>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">bindingButtonGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_11">
|
||||
<property name="text">
|
||||
<string>Z Trigger</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QPushButton" name="pushButton_11">
|
||||
<property name="text">
|
||||
<string>Not Bound</string>
|
||||
</property>
|
||||
<property name="controllerBinding" stdset="0">
|
||||
<number>15</number>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">bindingButtonGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label_12">
|
||||
<property name="text">
|
||||
<string>R Trigger</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QPushButton" name="pushButton_12">
|
||||
<property name="text">
|
||||
<string>Not Bound</string>
|
||||
</property>
|
||||
<property name="controllerBinding" stdset="0">
|
||||
<number>16</number>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">bindingButtonGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_13">
|
||||
<property name="text">
|
||||
<string>D-Pad Up</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QPushButton" name="pushButton_13">
|
||||
<property name="text">
|
||||
<string>Not Bound</string>
|
||||
</property>
|
||||
<property name="controllerBinding" stdset="0">
|
||||
<number>8</number>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">bindingButtonGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_14">
|
||||
<property name="text">
|
||||
<string>D-Pad Down</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QPushButton" name="pushButton_14">
|
||||
<property name="text">
|
||||
<string>Not Bound</string>
|
||||
</property>
|
||||
<property name="controllerBinding" stdset="0">
|
||||
<number>9</number>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">bindingButtonGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_15">
|
||||
<property name="text">
|
||||
<string>D-Pad Left</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QPushButton" name="pushButton_15">
|
||||
<property name="text">
|
||||
<string>Not Bound</string>
|
||||
</property>
|
||||
<property name="controllerBinding" stdset="0">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">bindingButtonGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_16">
|
||||
<property name="text">
|
||||
<string>D-Pad Right</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QPushButton" name="pushButton_16">
|
||||
<property name="text">
|
||||
<string>Not Bound</string>
|
||||
</property>
|
||||
<property name="controllerBinding" stdset="0">
|
||||
<number>11</number>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">bindingButtonGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_17">
|
||||
<property name="text">
|
||||
<string>Start Button</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QPushButton" name="pushButton_17">
|
||||
<property name="text">
|
||||
<string>Not Bound</string>
|
||||
</property>
|
||||
<property name="controllerBinding" stdset="0">
|
||||
<number>17</number>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">bindingButtonGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label_18">
|
||||
<property name="text">
|
||||
<string>L Trigger</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QPushButton" name="pushButton_18">
|
||||
<property name="text">
|
||||
<string>Not Bound</string>
|
||||
</property>
|
||||
<property name="controllerBinding" stdset="0">
|
||||
<number>14</number>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">bindingButtonGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_4">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout_4">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_21">
|
||||
<property name="text">
|
||||
<string>Sensitivity</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QDoubleSpinBox" name="sensitivitySpinner">
|
||||
<property name="minimum">
|
||||
<double>0.500000000000000</double>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>2.000000000000000</double>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<double>0.010000000000000</double>
|
||||
</property>
|
||||
<property name="value">
|
||||
<double>1.150000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_22">
|
||||
<property name="text">
|
||||
<string>Deadzone</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QDoubleSpinBox" name="deadzoneSpinner">
|
||||
<property name="maximum">
|
||||
<double>1.000000000000000</double>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<double>0.010000000000000</double>
|
||||
</property>
|
||||
<property name="value">
|
||||
<double>0.150000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout_5">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_23">
|
||||
<property name="text">
|
||||
<string>Save State</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QPushButton" name="pushButton_19">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>125</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Not Bound</string>
|
||||
</property>
|
||||
<property name="controllerBinding" stdset="0">
|
||||
<number>18</number>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">bindingButtonGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_24">
|
||||
<property name="text">
|
||||
<string>Load State</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QPushButton" name="pushButton_20">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>125</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Not Bound</string>
|
||||
</property>
|
||||
<property name="controllerBinding" stdset="0">
|
||||
<number>19</number>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">bindingButtonGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_5">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_5">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="saveAsButton">
|
||||
<property name="text">
|
||||
<string>Save As</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="document-save-as">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="default">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">buttonGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="saveButton">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Save</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="document-save">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">buttonGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="cancelButton">
|
||||
<property name="text">
|
||||
<string>Cancel</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="dialog-cancel">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">buttonGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>quickConfigureButton</tabstop>
|
||||
<tabstop>rumbleCheckbox</tabstop>
|
||||
<tabstop>pushButton</tabstop>
|
||||
<tabstop>pushButton_2</tabstop>
|
||||
<tabstop>pushButton_3</tabstop>
|
||||
<tabstop>pushButton_4</tabstop>
|
||||
<tabstop>pushButton_5</tabstop>
|
||||
<tabstop>pushButton_6</tabstop>
|
||||
<tabstop>pushButton_7</tabstop>
|
||||
<tabstop>pushButton_8</tabstop>
|
||||
<tabstop>pushButton_9</tabstop>
|
||||
<tabstop>pushButton_10</tabstop>
|
||||
<tabstop>pushButton_11</tabstop>
|
||||
<tabstop>pushButton_12</tabstop>
|
||||
<tabstop>pushButton_13</tabstop>
|
||||
<tabstop>pushButton_14</tabstop>
|
||||
<tabstop>pushButton_15</tabstop>
|
||||
<tabstop>pushButton_16</tabstop>
|
||||
<tabstop>pushButton_17</tabstop>
|
||||
<tabstop>pushButton_18</tabstop>
|
||||
<tabstop>pushButton_19</tabstop>
|
||||
<tabstop>pushButton_20</tabstop>
|
||||
<tabstop>sensitivitySpinner</tabstop>
|
||||
<tabstop>deadzoneSpinner</tabstop>
|
||||
<tabstop>saveAsButton</tabstop>
|
||||
<tabstop>saveButton</tabstop>
|
||||
<tabstop>cancelButton</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>quickConfigureButton</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>ControllerConfigDialog</receiver>
|
||||
<slot>bindAll()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>83</x>
|
||||
<y>44</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>574</x>
|
||||
<y>14</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>cancelButton</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>ControllerConfigDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>618</x>
|
||||
<y>448</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>671</x>
|
||||
<y>368</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>saveButton</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>ControllerConfigDialog</receiver>
|
||||
<slot>save()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>538</x>
|
||||
<y>457</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>673</x>
|
||||
<y>335</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>saveAsButton</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>ControllerConfigDialog</receiver>
|
||||
<slot>saveAs()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>459</x>
|
||||
<y>454</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>673</x>
|
||||
<y>312</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
<slots>
|
||||
<slot>save()</slot>
|
||||
<slot>bindAll()</slot>
|
||||
<slot>saveAs()</slot>
|
||||
</slots>
|
||||
<buttongroups>
|
||||
<buttongroup name="buttonGroup"/>
|
||||
<buttongroup name="bindingButtonGroup">
|
||||
<property name="exclusive">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</buttongroup>
|
||||
</buttongroups>
|
||||
</ui>
|
225
src/ui/designer/controller-select-dialog.ui
Normal file
@@ -0,0 +1,225 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>ControllerSelectDialog</class>
|
||||
<widget class="QDialog" name="ControllerSelectDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>450</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Select Controller</string>
|
||||
</property>
|
||||
<property name="modal">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Connected Controllers</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>deviceList</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QListWidget" name="deviceList"/>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>16</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Controller Profiles</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>profileList</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QListWidget" name="profileList"/>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout" stretch="1,1,1">
|
||||
<item>
|
||||
<widget class="QPushButton" name="editButton">
|
||||
<property name="text">
|
||||
<string>Edit Profile</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="document-edit">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="deleteButton">
|
||||
<property name="text">
|
||||
<string>Delete Profile</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="delete">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>ControllerSelectDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>224</x>
|
||||
<y>424</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>ControllerSelectDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>292</x>
|
||||
<y>430</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>editButton</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>ControllerSelectDialog</receiver>
|
||||
<slot>editProfile()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>98</x>
|
||||
<y>371</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>397</x>
|
||||
<y>336</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>deviceList</sender>
|
||||
<signal>currentRowChanged(int)</signal>
|
||||
<receiver>ControllerSelectDialog</receiver>
|
||||
<slot>deviceSelected()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>331</x>
|
||||
<y>114</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>397</x>
|
||||
<y>95</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>profileList</sender>
|
||||
<signal>currentRowChanged(int)</signal>
|
||||
<receiver>ControllerSelectDialog</receiver>
|
||||
<slot>profileSelected()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>353</x>
|
||||
<y>241</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>400</x>
|
||||
<y>235</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>deleteButton</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>ControllerSelectDialog</receiver>
|
||||
<slot>deleteProfile()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>200</x>
|
||||
<y>375</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>338</x>
|
||||
<y>375</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
<slots>
|
||||
<slot>deviceSelected()</slot>
|
||||
<slot>editProfile()</slot>
|
||||
<slot>profileSelected()</slot>
|
||||
<slot>deleteProfile()</slot>
|
||||
</slots>
|
||||
</ui>
|
523
src/ui/designer/main-window.ui
Normal file
@@ -0,0 +1,523 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>MainWindow</class>
|
||||
<widget class="QMainWindow" name="MainWindow">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>450</width>
|
||||
<height>600</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Parallel Launcher</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralwidget">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2" stretch="0,1,0,0,0,0">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>14</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Parallel Launcher v1.0.0</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="refreshButton">
|
||||
<property name="toolTip">
|
||||
<string>Refresh ROM List</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="view-refresh">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>24</width>
|
||||
<height>24</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="controllerConfigButton">
|
||||
<property name="toolTip">
|
||||
<string>Configure Controller</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="input-gamepad">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>24</width>
|
||||
<height>24</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="menuButton">
|
||||
<property name="toolTip">
|
||||
<string>Options</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="application-menu">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>24</width>
|
||||
<height>24</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="popupMode">
|
||||
<enum>QToolButton::InstantPopup</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QStackedWidget" name="romView">
|
||||
<widget class="QWidget" name="noRomSourcesPages">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>52</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>No ROMS have been found</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButton">
|
||||
<property name="text">
|
||||
<string>Manage ROM Sources</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_4">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>52</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="romListPage">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||
<item>
|
||||
<widget class="RomListView" name="romList">
|
||||
<property name="horizontalScrollBarPolicy">
|
||||
<enum>Qt::ScrollBarAlwaysOff</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="optimizeCheckbox">
|
||||
<property name="toolTip">
|
||||
<string>Automatically set emulator settings to improve performance</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Optimize for Performance</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="performanceWarningLabel">
|
||||
<property name="styleSheet">
|
||||
<string notr="true">background-color: yellow;
|
||||
border-radius: 8px;
|
||||
padding: 4px;</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string><html><head/><body><p><span style=" font-weight:600;">Warning: </span>Disabling performance optimizations is likely to result in a laggy experience. This option should only be used for speedruns where you want to emulate console CPU lag.</p></body></html></string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::NoTextInteraction</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="pluginGroup">
|
||||
<property name="title">
|
||||
<string>Graphics Plugin</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QRadioButton" name="pluginParallelRadio">
|
||||
<property name="text">
|
||||
<string>ParaLLEl (more accurate)</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">pluginRadioGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="pluginGlideRadio">
|
||||
<property name="text">
|
||||
<string>Glide64 (more romhack compatible)</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">pluginRadioGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="showMorePluginsLinks">
|
||||
<property name="text">
|
||||
<string><a href="#">Show More Plugins</a></string>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::RichText</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="pluginAngrylionRadio">
|
||||
<property name="text">
|
||||
<string>Angrylion</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">pluginRadioGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="pluginGlidenRadio">
|
||||
<property name="text">
|
||||
<string>GlideN64</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">pluginRadioGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="pluginRiceRadio">
|
||||
<property name="text">
|
||||
<string>Rice</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">pluginRadioGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="playMultiplayerButton">
|
||||
<property name="text">
|
||||
<string>Play Multiplayer</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="group">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="playSingleplayerButton">
|
||||
<property name="text">
|
||||
<string>Play Singleplayer</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="media-playback-start">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="autoDefault">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<action name="actionSettings">
|
||||
<property name="text">
|
||||
<string>Settings</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionManageSources">
|
||||
<property name="text">
|
||||
<string>Manage ROM Sources</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionConfigureControllers">
|
||||
<property name="text">
|
||||
<string>Configure Controllers</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>RomListView</class>
|
||||
<extends>QTreeView</extends>
|
||||
<header>src/ui/rom-list-view.hpp</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>pushButton</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>MainWindow</receiver>
|
||||
<slot>manageRomSources()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>253</x>
|
||||
<y>165</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>444</x>
|
||||
<y>72</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>controllerConfigButton</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>MainWindow</receiver>
|
||||
<slot>configureController()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>390</x>
|
||||
<y>25</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>446</x>
|
||||
<y>106</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>optimizeCheckbox</sender>
|
||||
<signal>toggled(bool)</signal>
|
||||
<receiver>MainWindow</receiver>
|
||||
<slot>optimizationsToggled()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>120</x>
|
||||
<y>259</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>445</x>
|
||||
<y>143</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>showMorePluginsLinks</sender>
|
||||
<signal>linkActivated(QString)</signal>
|
||||
<receiver>MainWindow</receiver>
|
||||
<slot>morePluginsToggled()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>80</x>
|
||||
<y>452</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>447</x>
|
||||
<y>180</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>actionManageSources</sender>
|
||||
<signal>triggered()</signal>
|
||||
<receiver>MainWindow</receiver>
|
||||
<slot>manageRomSources()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>-1</x>
|
||||
<y>-1</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>224</x>
|
||||
<y>299</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>actionConfigureControllers</sender>
|
||||
<signal>triggered()</signal>
|
||||
<receiver>MainWindow</receiver>
|
||||
<slot>configureController()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>-1</x>
|
||||
<y>-1</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>224</x>
|
||||
<y>299</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>actionSettings</sender>
|
||||
<signal>triggered()</signal>
|
||||
<receiver>MainWindow</receiver>
|
||||
<slot>editSettings()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>-1</x>
|
||||
<y>-1</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>224</x>
|
||||
<y>299</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>playSingleplayerButton</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>MainWindow</receiver>
|
||||
<slot>playSingleplayer()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>407</x>
|
||||
<y>587</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>449</x>
|
||||
<y>579</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>playMultiplayerButton</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>MainWindow</receiver>
|
||||
<slot>playMultiplayer()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>196</x>
|
||||
<y>588</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>141</x>
|
||||
<y>599</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
<slots>
|
||||
<slot>manageRomSources()</slot>
|
||||
<slot>optimizationsToggled()</slot>
|
||||
<slot>configureController()</slot>
|
||||
<slot>morePluginsToggled()</slot>
|
||||
<slot>editSettings()</slot>
|
||||
<slot>playSingleplayer()</slot>
|
||||
<slot>playMultiplayer()</slot>
|
||||
</slots>
|
||||
<buttongroups>
|
||||
<buttongroup name="pluginRadioGroup"/>
|
||||
</buttongroups>
|
||||
</ui>
|
205
src/ui/designer/manage-groups-dialog.ui
Normal file
@@ -0,0 +1,205 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>ManageGroupsDialog</class>
|
||||
<widget class="QDialog" name="ManageGroupsDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>320</width>
|
||||
<height>335</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Manage Groups</string>
|
||||
</property>
|
||||
<property name="modal">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QListWidget" name="groupsList">
|
||||
<property name="horizontalScrollBarPolicy">
|
||||
<enum>Qt::ScrollBarAlwaysOff</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="deleteButton">
|
||||
<property name="text">
|
||||
<string>Delete</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="delete">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="renameButton">
|
||||
<property name="text">
|
||||
<string>Rename</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="document-edit">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="addButton">
|
||||
<property name="text">
|
||||
<string>New</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="list-add">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>ManageGroupsDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>224</x>
|
||||
<y>309</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>ManageGroupsDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>292</x>
|
||||
<y>315</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>groupsList</sender>
|
||||
<signal>itemSelectionChanged()</signal>
|
||||
<receiver>ManageGroupsDialog</receiver>
|
||||
<slot>updateButtons()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>35</x>
|
||||
<y>88</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>1</x>
|
||||
<y>120</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>deleteButton</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>ManageGroupsDialog</receiver>
|
||||
<slot>deleteGroup()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>55</x>
|
||||
<y>260</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>4</x>
|
||||
<y>176</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>renameButton</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>ManageGroupsDialog</receiver>
|
||||
<slot>renameGroup()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>171</x>
|
||||
<y>255</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>318</x>
|
||||
<y>119</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>addButton</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>ManageGroupsDialog</receiver>
|
||||
<slot>addGroup()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>290</x>
|
||||
<y>258</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>320</x>
|
||||
<y>174</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>ManageGroupsDialog</receiver>
|
||||
<slot>save()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>259</x>
|
||||
<y>315</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>317</x>
|
||||
<y>257</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
<slots>
|
||||
<slot>updateButtons()</slot>
|
||||
<slot>deleteGroup()</slot>
|
||||
<slot>renameGroup()</slot>
|
||||
<slot>addGroup()</slot>
|
||||
<slot>save()</slot>
|
||||
</slots>
|
||||
</ui>
|
273
src/ui/designer/multiplayer-controller-select-dialog.ui
Normal file
@@ -0,0 +1,273 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>MultiplayerControllerSelectDialog</class>
|
||||
<widget class="QDialog" name="MultiplayerControllerSelectDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>250</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Select Controllers</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout" columnstretch="0,1,0">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Port 1</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>device1</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="device1">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>-- None --</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QPushButton" name="pushButton_2">
|
||||
<property name="text">
|
||||
<string>Detect Device</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Port 2</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>device2</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="device2">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>-- None --</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QPushButton" name="pushButton">
|
||||
<property name="text">
|
||||
<string>Detect Device</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Port 3</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>device3</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QComboBox" name="device3">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>-- None --</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="2">
|
||||
<widget class="QPushButton" name="pushButton_3">
|
||||
<property name="text">
|
||||
<string>Detect Device</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Port 4</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>device4</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QComboBox" name="device4">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>-- None --</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="2">
|
||||
<widget class="QPushButton" name="pushButton_4">
|
||||
<property name="text">
|
||||
<string>Detect Device</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="bindSavestateCheckbox">
|
||||
<property name="text">
|
||||
<string>Allow port 1 to save and load states</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>device1</tabstop>
|
||||
<tabstop>pushButton_2</tabstop>
|
||||
<tabstop>device2</tabstop>
|
||||
<tabstop>pushButton</tabstop>
|
||||
<tabstop>device3</tabstop>
|
||||
<tabstop>pushButton_3</tabstop>
|
||||
<tabstop>device4</tabstop>
|
||||
<tabstop>pushButton_4</tabstop>
|
||||
<tabstop>bindSavestateCheckbox</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>MultiplayerControllerSelectDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>224</x>
|
||||
<y>224</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>249</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>MultiplayerControllerSelectDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>292</x>
|
||||
<y>230</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>249</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>pushButton_2</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>MultiplayerControllerSelectDialog</receiver>
|
||||
<slot>detectDevice1()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>339</x>
|
||||
<y>18</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>397</x>
|
||||
<y>25</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>pushButton</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>MultiplayerControllerSelectDialog</receiver>
|
||||
<slot>detectDevice2()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>355</x>
|
||||
<y>64</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>399</x>
|
||||
<y>63</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>pushButton_3</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>MultiplayerControllerSelectDialog</receiver>
|
||||
<slot>detectDevice3()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>368</x>
|
||||
<y>114</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>399</x>
|
||||
<y>103</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>pushButton_4</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>MultiplayerControllerSelectDialog</receiver>
|
||||
<slot>detectDevice4()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>353</x>
|
||||
<y>153</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>396</x>
|
||||
<y>145</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
<slots>
|
||||
<slot>detectDevice1()</slot>
|
||||
<slot>detectDevice2()</slot>
|
||||
<slot>detectDevice3()</slot>
|
||||
<slot>detectDevice4()</slot>
|
||||
</slots>
|
||||
</ui>
|
90
src/ui/designer/neutral-input-dialog.ui
Normal file
@@ -0,0 +1,90 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>NeutralInputDialog</class>
|
||||
<widget class="QDialog" name="NeutralInputDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>287</width>
|
||||
<height>177</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Return to Neutral</string>
|
||||
</property>
|
||||
<property name="modal">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Return all triggers and analog sticks to their neutral position, then press any button on the controller or click the OK button to continue.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>NeutralInputDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>248</x>
|
||||
<y>254</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>NeutralInputDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>316</x>
|
||||
<y>260</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
243
src/ui/designer/now-playing-window.ui
Normal file
@@ -0,0 +1,243 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>NowPlayingWindow</class>
|
||||
<widget class="QMainWindow" name="NowPlayingWindow">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>300</width>
|
||||
<height>175</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Parallel Launcher</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralwidget">
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Now Playing: </string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="romNameLabel">
|
||||
<property name="text">
|
||||
<string><ROM Name></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>2</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="1">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Play Time</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Total</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLabel" name="totalTimeLabel">
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Noto Sans Mono</family>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>00:00:00</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>This Session</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLabel" name="sessionTimeLabel">
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Noto Sans Mono</family>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>00:00:00</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>3</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_4">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="killButton">
|
||||
<property name="text">
|
||||
<string>Kill Emulator</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="process-stop">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>killButton</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>NowPlayingWindow</receiver>
|
||||
<slot>killEmulator()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>215</x>
|
||||
<y>157</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>89</x>
|
||||
<y>153</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
<slots>
|
||||
<slot>killEmulator()</slot>
|
||||
</slots>
|
||||
</ui>
|
455
src/ui/designer/rom-source-dialog.ui
Normal file
@@ -0,0 +1,455 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>RomSourceDialog</class>
|
||||
<widget class="QDialog" name="RomSourceDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>750</width>
|
||||
<height>500</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Manage ROM Sources</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_5" stretch="2,3">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2" stretch="1,0">
|
||||
<item>
|
||||
<widget class="QListWidget" name="romSourceList">
|
||||
<property name="textElideMode">
|
||||
<enum>Qt::ElideLeft</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<widget class="QPushButton" name="deleteSourceButton">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Delete</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="delete">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="addSourceButton">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>New Source</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="list-add">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>ROM Source</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="sourcePath">
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="placeholderText">
|
||||
<string>Browse for a folder</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="browseButton">
|
||||
<property name="text">
|
||||
<string>Browse</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="document-open">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="recursiveCheckbox">
|
||||
<property name="toolTip">
|
||||
<string>Also scan all subfolders</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Recursive</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QFrame" name="recursiveSearchOptions">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="ignoreHiddenCheckbox">
|
||||
<property name="toolTip">
|
||||
<string>Ignore directories that begin with a period character</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Ignore hidden directories</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Max Depth</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="maxDepthSpinner">
|
||||
<property name="minimum">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>9</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>5</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="followSymlinksCheckbox">
|
||||
<property name="toolTip">
|
||||
<string>Follow symbolic links to folders and ROMs</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Follow Symlinks</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Automatically add to groups</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>groupListContainer</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QScrollArea" name="groupListContainer">
|
||||
<property name="horizontalScrollBarPolicy">
|
||||
<enum>Qt::ScrollBarAlwaysOff</enum>
|
||||
</property>
|
||||
<property name="widgetResizable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<widget class="QWidget" name="groupList">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>418</width>
|
||||
<height>136</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_5"/>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_6">
|
||||
<item>
|
||||
<widget class="QPushButton" name="manageGroupsButton">
|
||||
<property name="text">
|
||||
<string>Manage Groups</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_4">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>RomSourceDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>254</x>
|
||||
<y>493</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>RomSourceDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>322</x>
|
||||
<y>493</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>recursiveCheckbox</sender>
|
||||
<signal>toggled(bool)</signal>
|
||||
<receiver>recursiveSearchOptions</receiver>
|
||||
<slot>setEnabled(bool)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>379</x>
|
||||
<y>94</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>505</x>
|
||||
<y>116</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>browseButton</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>RomSourceDialog</receiver>
|
||||
<slot>browse()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>720</x>
|
||||
<y>57</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>749</x>
|
||||
<y>25</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>addSourceButton</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>RomSourceDialog</receiver>
|
||||
<slot>addSource()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>185</x>
|
||||
<y>420</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>4</x>
|
||||
<y>430</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>deleteSourceButton</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>RomSourceDialog</receiver>
|
||||
<slot>deleteSource()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>61</x>
|
||||
<y>414</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>4</x>
|
||||
<y>409</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>romSourceList</sender>
|
||||
<signal>itemSelectionChanged()</signal>
|
||||
<receiver>RomSourceDialog</receiver>
|
||||
<slot>sourceSelected()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>166</x>
|
||||
<y>165</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>3</x>
|
||||
<y>138</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>manageGroupsButton</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>RomSourceDialog</receiver>
|
||||
<slot>manageGroups()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>384</x>
|
||||
<y>429</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>749</x>
|
||||
<y>379</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>RomSourceDialog</receiver>
|
||||
<slot>save()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>700</x>
|
||||
<y>471</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>748</x>
|
||||
<y>419</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
<slots>
|
||||
<slot>browse()</slot>
|
||||
<slot>addSource()</slot>
|
||||
<slot>deleteSource()</slot>
|
||||
<slot>sourceSelected()</slot>
|
||||
<slot>manageGroups()</slot>
|
||||
<slot>save()</slot>
|
||||
</slots>
|
||||
</ui>
|
206
src/ui/designer/save-file-editor-dialog.ui
Normal file
@@ -0,0 +1,206 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>SaveFileEditorDialog</class>
|
||||
<widget class="QDialog" name="SaveFileEditorDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>332</width>
|
||||
<height>223</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Edit SM64 Save File</string>
|
||||
</property>
|
||||
<property name="modal">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string><html><head/><body><p><span style=" font-weight:600;">Warning: </span>This is a save file editor for Super Mario 64 and SM64 romhacks. Attempting to use this save file editor with other ROMs will corrupt your save file.</p></body></html></string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Save Slot:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="saveSlot">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Slot A (Empty)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Slot B (Empty)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Slot C (Empty)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Slot D (Empty)</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="deleteButton">
|
||||
<property name="text">
|
||||
<string>Delete Save Slot</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="delete">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="editButton">
|
||||
<property name="text">
|
||||
<string>Edit Save Slot</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="document-edit">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>26</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Close</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>SaveFileEditorDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>292</x>
|
||||
<y>203</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>222</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>editButton</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>SaveFileEditorDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>261</x>
|
||||
<y>129</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>119</x>
|
||||
<y>162</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>saveSlot</sender>
|
||||
<signal>currentIndexChanged(int)</signal>
|
||||
<receiver>SaveFileEditorDialog</receiver>
|
||||
<slot>slotSelected(int)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>193</x>
|
||||
<y>92</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>42</x>
|
||||
<y>157</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>deleteButton</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>SaveFileEditorDialog</receiver>
|
||||
<slot>deleteSlot()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>95</x>
|
||||
<y>124</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>22</x>
|
||||
<y>117</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
<slots>
|
||||
<slot>slotSelected(int)</slot>
|
||||
<slot>deleteSlot()</slot>
|
||||
</slots>
|
||||
</ui>
|
616
src/ui/designer/save-slot-editor-dialog.ui
Normal file
@@ -0,0 +1,616 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>SaveSlotEditorDialog</class>
|
||||
<widget class="QDialog" name="SaveSlotEditorDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1214</width>
|
||||
<height>722</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Edit Save Slot (SM64)</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_9">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_24">
|
||||
<property name="text">
|
||||
<string>Show flags and stars unused in the vanilla game</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3" stretch="0,1">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_5">
|
||||
<property name="title">
|
||||
<string>Flags</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_8">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QScrollArea" name="scrollArea">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::NoFrame</enum>
|
||||
</property>
|
||||
<property name="verticalScrollBarPolicy">
|
||||
<enum>Qt::ScrollBarAlwaysOn</enum>
|
||||
</property>
|
||||
<property name="widgetResizable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<widget class="QWidget" name="scrollAreaWidgetContents">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>212</width>
|
||||
<height>1006</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_7">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Caps Unlocked</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox">
|
||||
<property name="text">
|
||||
<string>Wing Cap</string>
|
||||
</property>
|
||||
<property name="saveFlag" stdset="0">
|
||||
<UInt>2</UInt>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">flagButtons</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_2">
|
||||
<property name="text">
|
||||
<string>Metal Cap</string>
|
||||
</property>
|
||||
<property name="saveFlag" stdset="0">
|
||||
<UInt>4</UInt>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">flagButtons</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_3">
|
||||
<property name="text">
|
||||
<string>Vanish Cap</string>
|
||||
</property>
|
||||
<property name="saveFlag" stdset="0">
|
||||
<UInt>8</UInt>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">flagButtons</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<property name="title">
|
||||
<string>Keys Collected</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_4">
|
||||
<property name="text">
|
||||
<string>Basement Key</string>
|
||||
</property>
|
||||
<property name="saveFlag" stdset="0">
|
||||
<UInt>16</UInt>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">flagButtons</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_5">
|
||||
<property name="text">
|
||||
<string>Upstairs Key</string>
|
||||
</property>
|
||||
<property name="saveFlag" stdset="0">
|
||||
<UInt>32</UInt>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">flagButtons</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_3">
|
||||
<property name="title">
|
||||
<string>Unlocked Doors</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_6">
|
||||
<property name="text">
|
||||
<string>Basement Key Door</string>
|
||||
</property>
|
||||
<property name="saveFlag" stdset="0">
|
||||
<UInt>64</UInt>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">flagButtons</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_7">
|
||||
<property name="text">
|
||||
<string>Upstairs Key Door</string>
|
||||
</property>
|
||||
<property name="saveFlag" stdset="0">
|
||||
<UInt>128</UInt>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">flagButtons</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_8">
|
||||
<property name="text">
|
||||
<string>Peach's Secret Slide Door</string>
|
||||
</property>
|
||||
<property name="saveFlag" stdset="0">
|
||||
<UInt>1024</UInt>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">flagButtons</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_9">
|
||||
<property name="text">
|
||||
<string>Whomp's Fortress Door</string>
|
||||
</property>
|
||||
<property name="saveFlag" stdset="0">
|
||||
<UInt>2048</UInt>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">flagButtons</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_10">
|
||||
<property name="text">
|
||||
<string>Cool Cool Mountain Door</string>
|
||||
</property>
|
||||
<property name="saveFlag" stdset="0">
|
||||
<UInt>4096</UInt>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">flagButtons</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_11">
|
||||
<property name="text">
|
||||
<string>Jolly Rodger Bay Door</string>
|
||||
</property>
|
||||
<property name="saveFlag" stdset="0">
|
||||
<UInt>8192</UInt>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">flagButtons</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_12">
|
||||
<property name="text">
|
||||
<string>Dark World Door</string>
|
||||
</property>
|
||||
<property name="saveFlag" stdset="0">
|
||||
<UInt>16384</UInt>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">flagButtons</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_13">
|
||||
<property name="text">
|
||||
<string>Fire Sea Door</string>
|
||||
</property>
|
||||
<property name="saveFlag" stdset="0">
|
||||
<UInt>32768</UInt>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">flagButtons</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_14">
|
||||
<property name="text">
|
||||
<string>50 Star Door</string>
|
||||
</property>
|
||||
<property name="saveFlag" stdset="0">
|
||||
<UInt>1048576</UInt>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">flagButtons</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_4">
|
||||
<property name="title">
|
||||
<string>Miscellaneous</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_15">
|
||||
<property name="text">
|
||||
<string>Moat Drained</string>
|
||||
</property>
|
||||
<property name="saveFlag" stdset="0">
|
||||
<UInt>512</UInt>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">flagButtons</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_16">
|
||||
<property name="text">
|
||||
<string>Bowser's Sub Gone</string>
|
||||
</property>
|
||||
<property name="saveFlag" stdset="0">
|
||||
<UInt>256</UInt>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">flagButtons</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="unusedFlagsGroup">
|
||||
<property name="title">
|
||||
<string>Unused</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_17">
|
||||
<property name="text">
|
||||
<string>0x200000</string>
|
||||
</property>
|
||||
<property name="saveFlag" stdset="0">
|
||||
<UInt>2097152</UInt>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">flagButtons</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_18">
|
||||
<property name="text">
|
||||
<string>0x400000</string>
|
||||
</property>
|
||||
<property name="saveFlag" stdset="0">
|
||||
<UInt>4194304</UInt>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">flagButtons</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_19">
|
||||
<property name="text">
|
||||
<string>0x800000</string>
|
||||
</property>
|
||||
<property name="saveFlag" stdset="0">
|
||||
<UInt>8388608</UInt>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">flagButtons</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_6">
|
||||
<property name="title">
|
||||
<string>Lost/Stolen Cap</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_6">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout" stretch="0,1">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Level</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="capLevel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Area</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="capArea">
|
||||
<property name="maximum">
|
||||
<number>7</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>1</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Cap Stolen By:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_20">
|
||||
<property name="toolTip">
|
||||
<string>The flag indicates that Mario's cap is on the ground, however, when loading a save file, a cap that is on the ground is either given back to Mario or moved to the appropriate NPC for the area, so it will never actually be on the ground.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Automatic*</string>
|
||||
</property>
|
||||
<property name="saveFlag" stdset="0">
|
||||
<UInt>65536</UInt>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">flagButtons</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_21">
|
||||
<property name="text">
|
||||
<string>Klepto (Bird)</string>
|
||||
</property>
|
||||
<property name="saveFlag" stdset="0">
|
||||
<UInt>131072</UInt>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">flagButtons</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_22">
|
||||
<property name="text">
|
||||
<string>Ukiki (Monkey)</string>
|
||||
</property>
|
||||
<property name="saveFlag" stdset="0">
|
||||
<UInt>262144</UInt>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">flagButtons</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_23">
|
||||
<property name="text">
|
||||
<string>Mr. Blizzard (Snowman)</string>
|
||||
</property>
|
||||
<property name="saveFlag" stdset="0">
|
||||
<UInt>524288</UInt>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">flagButtons</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_7">
|
||||
<property name="title">
|
||||
<string>Stars and Coins</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_10">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QScrollArea" name="scrollArea_2">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::NoFrame</enum>
|
||||
</property>
|
||||
<property name="widgetResizable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<widget class="QWidget" name="starGrid">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>953</width>
|
||||
<height>610</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout"/>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Save</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>SaveSlotEditorDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>254</x>
|
||||
<y>620</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>SaveSlotEditorDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>322</x>
|
||||
<y>620</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>checkBox_24</sender>
|
||||
<signal>toggled(bool)</signal>
|
||||
<receiver>SaveSlotEditorDialog</receiver>
|
||||
<slot>setShowUnused(bool)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>888</x>
|
||||
<y>12</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>912</x>
|
||||
<y>19</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
<slots>
|
||||
<slot>setShowUnused(bool)</slot>
|
||||
</slots>
|
||||
<buttongroups>
|
||||
<buttongroup name="flagButtons">
|
||||
<property name="exclusive">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</buttongroup>
|
||||
</buttongroups>
|
||||
</ui>
|
450
src/ui/designer/settings-dialog.ui
Normal file
@@ -0,0 +1,450 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>SettingsDialog</class>
|
||||
<widget class="QDialog" name="SettingsDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>350</width>
|
||||
<height>638</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Settings</string>
|
||||
</property>
|
||||
<property name="sizeGripEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="modal">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QFrame" name="themePanel">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2" stretch="0,1">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>Theme (Requires Restart)</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>themeSelect</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="themeSelect"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout" columnstretch="0,1">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Window Scale</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>windowScaleSelect</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="windowScaleSelect">
|
||||
<property name="currentIndex">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>320x240 (x1)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>640x480 (x2)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>960x720 (x3)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>1280x960 (x4)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>1600x1200 (x5)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>1920x1440 (x6)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>2240x1680 (x7)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>2560x1920 (x8)</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>ParaLLEl Upscaling</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>upscalingSelect</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="upscalingSelect">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Auto (x4 - 1280x960)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Native (x1 - 320x240)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>x2 - 640x480</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>x4 - 1280x960</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>x8 - 2560x1920</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Default GFX Plugin</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>pluginSelect</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QComboBox" name="pluginSelect">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>ParaLLEl</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Glide64</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Angrylion</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>GlideN64</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Rice</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="vsyncCheckbox">
|
||||
<property name="text">
|
||||
<string>Vsync (May cause slight audio crackling)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="hideLauncherCheckbox">
|
||||
<property name="text">
|
||||
<string>Hide launcher while playing ROM</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Visible Columns</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Name</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="showFilePathCheckbox">
|
||||
<property name="text">
|
||||
<string>File Path</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="showLastPlayedCheckbox">
|
||||
<property name="text">
|
||||
<string>Last Played</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="showPlayTimeCheckbox">
|
||||
<property name="text">
|
||||
<string>Total Play Time</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="showNotesCheckbox">
|
||||
<property name="text">
|
||||
<string>Notes</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<property name="title">
|
||||
<string>RetroArch Installation</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="useFlatpakCheckbox">
|
||||
<property name="text">
|
||||
<string>Use Flatpak Install</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Config Behaviour</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>configBehaviourSelect</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="configBehaviourSelect">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="currentIndex">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Fresh config each launch</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Persist config changes</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Inherit system config</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>RetroArch Directory</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>configDirInput</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="configDirInput"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>themeSelect</tabstop>
|
||||
<tabstop>windowScaleSelect</tabstop>
|
||||
<tabstop>upscalingSelect</tabstop>
|
||||
<tabstop>pluginSelect</tabstop>
|
||||
<tabstop>vsyncCheckbox</tabstop>
|
||||
<tabstop>hideLauncherCheckbox</tabstop>
|
||||
<tabstop>checkBox</tabstop>
|
||||
<tabstop>showFilePathCheckbox</tabstop>
|
||||
<tabstop>showLastPlayedCheckbox</tabstop>
|
||||
<tabstop>showPlayTimeCheckbox</tabstop>
|
||||
<tabstop>showNotesCheckbox</tabstop>
|
||||
<tabstop>useFlatpakCheckbox</tabstop>
|
||||
<tabstop>configBehaviourSelect</tabstop>
|
||||
<tabstop>configDirInput</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>SettingsDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>224</x>
|
||||
<y>574</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>SettingsDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>292</x>
|
||||
<y>580</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>SettingsDialog</receiver>
|
||||
<slot>save()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>320</x>
|
||||
<y>578</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>349</x>
|
||||
<y>507</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
<slots>
|
||||
<slot>save()</slot>
|
||||
</slots>
|
||||
</ui>
|
86
src/ui/designer/singleplayer-controller-select-dialog.ui
Normal file
@@ -0,0 +1,86 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>SingleplayerControllerSelectDialog</class>
|
||||
<widget class="QDialog" name="SingleplayerControllerSelectDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>418</width>
|
||||
<height>78</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Controller Select</string>
|
||||
</property>
|
||||
<property name="sizeGripEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="modal">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Multiple controllers are connected. Which one do you want to use?</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="controllerLayout"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>SingleplayerControllerSelectDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>224</x>
|
||||
<y>130</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>155</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>SingleplayerControllerSelectDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>292</x>
|
||||
<y>136</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>155</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
18
src/ui/error-notifier.cpp
Normal file
@@ -0,0 +1,18 @@
|
||||
#include "src/ui/error-notifier.hpp"
|
||||
|
||||
#include <QMessageBox>
|
||||
|
||||
static const char *const s_errorPrefix = "The application encountered an unexpected error:\n";
|
||||
|
||||
ErrorNotifier::ErrorNotifier() : QObject() {
|
||||
connect( this, SIGNAL( sendAlert(QString) ), this, SLOT( onAlert(QString) ), Qt::QueuedConnection );
|
||||
}
|
||||
|
||||
void ErrorNotifier::onAlert( QString message ) const {
|
||||
QMessageBox::critical( nullptr, "Unhandled Error", message.prepend( s_errorPrefix ) );
|
||||
}
|
||||
|
||||
ErrorNotifier &ErrorNotifier::instance() {
|
||||
static ErrorNotifier s_instance;
|
||||
return s_instance;
|
||||
}
|
37
src/ui/error-notifier.hpp
Normal file
@@ -0,0 +1,37 @@
|
||||
#ifndef SRC_UI_ERROR_NOTIFIER_HPP_
|
||||
#define SRC_UI_ERROR_NOTIFIER_HPP_
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include "src/types.hpp"
|
||||
|
||||
class ErrorNotifier : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
ErrorNotifier();
|
||||
|
||||
private slots:
|
||||
void onAlert( QString message ) const;
|
||||
|
||||
signals:
|
||||
void sendAlert( QString message );
|
||||
|
||||
private:
|
||||
static ErrorNotifier &instance();
|
||||
inline void alertImpl( const string &message ) {
|
||||
emit sendAlert( message.c_str() );
|
||||
}
|
||||
|
||||
public:
|
||||
~ErrorNotifier() {}
|
||||
|
||||
static inline void alert( const string &message ) {
|
||||
instance().alertImpl( message );
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif /* SRC_UI_ERROR_NOTIFIER_HPP_ */
|
29
src/ui/get-controller-port-dialog.cpp
Normal file
@@ -0,0 +1,29 @@
|
||||
#include "src/ui/get-controller-port-dialog.hpp"
|
||||
|
||||
#include "src/ui/icons.hpp"
|
||||
|
||||
GetControllerPortDialog::GetControllerPortDialog() :
|
||||
QMessageBox(
|
||||
QMessageBox::Information,
|
||||
"Choose Controller",
|
||||
"Press any button on the controller to bind to this port.",
|
||||
QMessageBox::Cancel
|
||||
),
|
||||
m_gamepadId( -1 )
|
||||
{
|
||||
using namespace Icon;
|
||||
setWindowIcon( appIcon() );
|
||||
connect(
|
||||
&GamepadController::instance(),
|
||||
SIGNAL( gamepadButtonPressed( GamepadButtonEvent ) ),
|
||||
this,
|
||||
SLOT( buttonPressed( GamepadButtonEvent ) )
|
||||
);
|
||||
}
|
||||
|
||||
void GetControllerPortDialog::buttonPressed( GamepadButtonEvent event ) {
|
||||
if( event.isPressed ) {
|
||||
m_gamepadId = event.id;
|
||||
accept();
|
||||
}
|
||||
}
|
31
src/ui/get-controller-port-dialog.hpp
Normal file
@@ -0,0 +1,31 @@
|
||||
#ifndef SRC_UI_GET_CONTROLLER_PORT_DIALOG_HPP_
|
||||
#define SRC_UI_GET_CONTROLLER_PORT_DIALOG_HPP_
|
||||
|
||||
#include <QMessageBox>
|
||||
#include "src/input/gamepad-controller.hpp"
|
||||
|
||||
class GetControllerPortDialog : public QMessageBox {
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
GamepadId m_gamepadId;
|
||||
|
||||
GetControllerPortDialog();
|
||||
|
||||
public:
|
||||
virtual ~GetControllerPortDialog() {}
|
||||
|
||||
static inline GamepadId getGamepadId() {
|
||||
GetControllerPortDialog dialog;
|
||||
dialog.exec();
|
||||
return dialog.m_gamepadId;
|
||||
}
|
||||
|
||||
private slots:
|
||||
void buttonPressed( GamepadButtonEvent event );
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif /* SRC_UI_GET_CONTROLLER_PORT_DIALOG_HPP_ */
|
34
src/ui/icons.cpp
Normal file
@@ -0,0 +1,34 @@
|
||||
#include "icons.hpp"
|
||||
|
||||
#define LOAD_ICON( varName, iconName ) const QIcon &Icon::varName() { \
|
||||
static const QIcon s_icon = QIcon::fromTheme( iconName, QIcon( ":/fallback-icons/" iconName ".svg" ) ); \
|
||||
return s_icon; \
|
||||
}
|
||||
|
||||
const QIcon &Icon::appIcon() {
|
||||
static const QIcon s_icon = QIcon( ":/appicon.svg" );
|
||||
return s_icon;
|
||||
}
|
||||
|
||||
LOAD_ICON( menu, "application-menu" )
|
||||
LOAD_ICON( delet, "delete" )
|
||||
LOAD_ICON( cancel, "dialog-cancel" )
|
||||
LOAD_ICON( close, "dialog-close" )
|
||||
LOAD_ICON( dialogError, "dialog-error" )
|
||||
LOAD_ICON( dialogInfo, "dialog-information" )
|
||||
LOAD_ICON( ok, "dialog-ok" )
|
||||
LOAD_ICON( dialogWarning, "dialog-warning" )
|
||||
LOAD_ICON( browse, "document-open" )
|
||||
LOAD_ICON( save, "document-save" )
|
||||
LOAD_ICON( saveAs, "document-save-as" )
|
||||
LOAD_ICON( edit, "document-edit" )
|
||||
LOAD_ICON( gamepad, "input-gamepad" )
|
||||
LOAD_ICON( add, "list-add" )
|
||||
LOAD_ICON( play, "media-playback-start" )
|
||||
LOAD_ICON( fastForward, "media-seek-forward" )
|
||||
LOAD_ICON( pkill, "process-stop" )
|
||||
LOAD_ICON( refresh, "view-refresh" )
|
||||
LOAD_ICON( skip, "go-next-skip" )
|
||||
LOAD_ICON( group, "group" )
|
||||
|
||||
#undef LOAD_ICON
|
32
src/ui/icons.hpp
Normal file
@@ -0,0 +1,32 @@
|
||||
#ifndef SRC_UI_ICONS_HPP_
|
||||
#define SRC_UI_ICONS_HPP_
|
||||
|
||||
#include <QIcon>
|
||||
|
||||
namespace Icon {
|
||||
extern const QIcon &appIcon();
|
||||
extern const QIcon &menu();
|
||||
extern const QIcon &delet();
|
||||
extern const QIcon &cancel();
|
||||
extern const QIcon &close();
|
||||
extern const QIcon &dialogError();
|
||||
extern const QIcon &dialogInfo();
|
||||
extern const QIcon &ok();
|
||||
extern const QIcon &dialogWarning();
|
||||
extern const QIcon &browse();
|
||||
extern const QIcon &save();
|
||||
extern const QIcon &saveAs();
|
||||
extern const QIcon &edit();
|
||||
extern const QIcon &gamepad();
|
||||
extern const QIcon &add();
|
||||
extern const QIcon &play();
|
||||
extern const QIcon &fastForward();
|
||||
extern const QIcon &pkill();
|
||||
extern const QIcon &refresh();
|
||||
extern const QIcon &skip();
|
||||
extern const QIcon &group();
|
||||
}
|
||||
|
||||
|
||||
|
||||
#endif /* SRC_UI_ICONS_HPP_ */
|
456
src/ui/main-window.cpp
Normal file
@@ -0,0 +1,456 @@
|
||||
#include "src/ui/main-window.hpp"
|
||||
#include "ui_main-window.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QMessageBox>
|
||||
#include <QCheckBox>
|
||||
#include <chrono>
|
||||
#include "src/ui/icons.hpp"
|
||||
#include "src/ui/rom-list-model.hpp"
|
||||
#include "src/ui/rom-source-dialog.hpp"
|
||||
#include "src/ui/settings-dialog.hpp"
|
||||
#include "src/ui/controller-select-dialog.hpp"
|
||||
#include "src/ui/process-awaiter.hpp"
|
||||
#include "src/ui/now-playing-window.hpp"
|
||||
#include "src/ui/singleplayer-controller-select-dialog.hpp"
|
||||
#include "src/ui/multiplayer-controller-select-dialog.hpp"
|
||||
#include "src/ui/save-file-editor-dialog.hpp"
|
||||
#include "src/ui/save-slot-editor-dialog.hpp"
|
||||
#include "src/core/file-controller.hpp"
|
||||
#include "src/core/retroarch.hpp"
|
||||
#include "src/core/rom.hpp"
|
||||
#include "src/core/preset-controllers.hpp"
|
||||
#include "src/input/gamepad-controller.hpp"
|
||||
|
||||
MainWindow::MainWindow( QWidget *parent ) :
|
||||
QMainWindow( parent ),
|
||||
m_ui( new Ui::MainWindow ),
|
||||
m_showAllPlugins( false ),
|
||||
m_settings( FileController::loadAppSettings() )
|
||||
{
|
||||
m_ui->setupUi( this );
|
||||
setWindowIcon( Icon::appIcon() );
|
||||
|
||||
m_ui->pluginAngrylionRadio->setVisible( false );
|
||||
m_ui->pluginGlidenRadio->setVisible( false );
|
||||
m_ui->pluginRiceRadio->setVisible( false );
|
||||
|
||||
m_ui->pluginRadioGroup->setId( m_ui->pluginParallelRadio, (int)GfxPlugin::ParaLLEl );
|
||||
m_ui->pluginRadioGroup->setId( m_ui->pluginGlideRadio, (int)GfxPlugin::Glide64 );
|
||||
m_ui->pluginRadioGroup->setId( m_ui->pluginAngrylionRadio, (int)GfxPlugin::Angrylion );
|
||||
m_ui->pluginRadioGroup->setId( m_ui->pluginGlidenRadio, (int)GfxPlugin::GlideN64 );
|
||||
m_ui->pluginRadioGroup->setId( m_ui->pluginRiceRadio, (int)GfxPlugin::Rice );
|
||||
|
||||
m_ui->controllerConfigButton->setIcon( Icon::gamepad() );
|
||||
m_ui->menuButton->setIcon( Icon::menu() );
|
||||
m_ui->refreshButton->setIcon( Icon::refresh() );
|
||||
m_ui->playSingleplayerButton->setIcon( Icon::play() );
|
||||
m_ui->playMultiplayerButton->setIcon( Icon::group() );
|
||||
|
||||
const std::vector<ROM> romList = FileController::loadRomList();
|
||||
m_ui->romView->setCurrentIndex( romList.empty() ? 0 : 1 );
|
||||
m_ui->romList->updateRoms( romList );
|
||||
|
||||
m_ui->menuButton->addAction( m_ui->actionSettings );
|
||||
m_ui->menuButton->addAction( m_ui->actionManageSources );
|
||||
m_ui->menuButton->addAction( m_ui->actionConfigureControllers );
|
||||
|
||||
const UiState uiState = FileController::loadUiState();
|
||||
resize( uiState.windowWidth, uiState.windowHeight );
|
||||
restoreTreeState( uiState.romList );
|
||||
|
||||
if( uiState.showAllPlugins ) {
|
||||
morePluginsToggled();
|
||||
}
|
||||
|
||||
connect( m_ui->refreshButton, SIGNAL(clicked()), &m_romListFetcher, SLOT(fetchAsync()) );
|
||||
connect( &m_romListFetcher, SIGNAL(romListFetched(FetchedRomList)), this, SLOT(romListFetched(FetchedRomList)) );
|
||||
connect( m_ui->romList->selectionModel(), SIGNAL(currentChanged(const QModelIndex&, const QModelIndex&)), this, SLOT(romSelectionChanged()) );
|
||||
connect( m_ui->pluginRadioGroup, SIGNAL(buttonToggled(int,bool)), this, SLOT(pluginChanged(int,bool)) );
|
||||
connect( m_ui->romList, SIGNAL(addTag(const ROM*,string)), this, SLOT(addTag(const ROM*,string)) );
|
||||
connect( m_ui->romList, SIGNAL(removeTag(const ROM*,string)), this, SLOT(removeTag(const ROM*,string)) );
|
||||
connect( m_ui->romList, SIGNAL(editSave(const ROM*,fs::path)), this, SLOT(editSave(const ROM*,fs::path)) );
|
||||
|
||||
reloadSettings();
|
||||
romSelectionChanged();
|
||||
|
||||
m_romListFetcher.fetchAsync();
|
||||
}
|
||||
|
||||
MainWindow::~MainWindow() {
|
||||
m_ui->romList->model()->deleteLater();
|
||||
delete m_ui;
|
||||
}
|
||||
|
||||
void MainWindow::reloadSettings() {
|
||||
m_settings = FileController::loadAppSettings();
|
||||
for( int i = 0; i < 4; i++ ) {
|
||||
m_ui->romList->setColumnHidden( i + 1, ((int)m_settings.visibleColumns & (1 << i)) == 0 );
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::romListFetched( FetchedRomList roms ) {
|
||||
const TreeUiState uiState = saveTreeState();
|
||||
m_ui->romView->setCurrentIndex( roms->empty() ? 0 : 1 );
|
||||
m_ui->romList->updateRoms( *roms );
|
||||
FileController::saveRomList( *roms );
|
||||
restoreTreeState( uiState );
|
||||
}
|
||||
|
||||
void MainWindow::romSelectionChanged() {
|
||||
const ROM *rom = static_cast<const RomListModel*>( m_ui->romList->model() )->tryGetRomInfo( m_ui->romList->currentIndex() );
|
||||
const bool romSelected = (rom != nullptr);
|
||||
|
||||
m_ui->optimizeCheckbox->setEnabled( romSelected );
|
||||
m_ui->pluginGroup->setEnabled( romSelected );
|
||||
m_ui->playSingleplayerButton->setEnabled( romSelected );
|
||||
m_ui->playMultiplayerButton->setEnabled( romSelected );
|
||||
|
||||
if( romSelected ) {
|
||||
const GfxPlugin plugin = (rom->plugin == GfxPlugin::UseDefault) ? m_settings.defaultPlugin : rom->plugin;
|
||||
m_ui->optimizeCheckbox->setChecked( rom->optimize );
|
||||
m_ui->pluginRadioGroup->button( (int)plugin )->setChecked( true );
|
||||
}
|
||||
|
||||
optimizationsToggled();
|
||||
}
|
||||
|
||||
void MainWindow::updateRom( const ROM &updatedRom ) {
|
||||
//TODO: should make this more efficient
|
||||
std::vector<ROM> romList = FileController::loadRomList();
|
||||
for( ROM &rom : romList ) {
|
||||
if( rom.path != updatedRom.path ) continue;
|
||||
rom.plugin = updatedRom.plugin;
|
||||
rom.lastPlayed = updatedRom.lastPlayed;
|
||||
rom.playTime = updatedRom.playTime;
|
||||
rom.tags = updatedRom.tags;
|
||||
rom.notes = updatedRom.notes;
|
||||
rom.optimize = updatedRom.optimize;
|
||||
break;
|
||||
}
|
||||
FileController::saveRomList( romList );
|
||||
const TreeUiState uiState = saveTreeState();
|
||||
m_ui->romList->updateRoms( romList );
|
||||
restoreTreeState( uiState );
|
||||
}
|
||||
|
||||
void MainWindow::optimizationsToggled() {
|
||||
const bool optimizationsDisabled = m_ui->optimizeCheckbox->isEnabled() && !m_ui->optimizeCheckbox->isChecked();
|
||||
m_ui->performanceWarningLabel->setVisible( optimizationsDisabled );
|
||||
|
||||
const ROM *romPtr = m_ui->romList->tryGetSelectedRom();
|
||||
if( romPtr == nullptr ) return;
|
||||
|
||||
if( romPtr->optimize == optimizationsDisabled ) {
|
||||
ROM rom = *romPtr;
|
||||
rom.optimize = !optimizationsDisabled;
|
||||
updateRom( rom );
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::pluginChanged( int pluginId, bool checked ) {
|
||||
if( !checked ) return;
|
||||
const GfxPlugin plugin = (GfxPlugin)pluginId;
|
||||
|
||||
const ROM *romPtr = m_ui->romList->tryGetSelectedRom();
|
||||
if( romPtr == nullptr || romPtr->plugin == plugin ) return;
|
||||
|
||||
ROM rom = *romPtr;
|
||||
rom.plugin = plugin;
|
||||
updateRom( rom );
|
||||
}
|
||||
|
||||
void MainWindow::morePluginsToggled() {
|
||||
m_showAllPlugins = !m_showAllPlugins;
|
||||
m_ui->pluginAngrylionRadio->setVisible( m_showAllPlugins );
|
||||
m_ui->pluginGlidenRadio->setVisible( m_showAllPlugins );
|
||||
m_ui->pluginRiceRadio->setVisible( m_showAllPlugins );
|
||||
m_ui->showMorePluginsLinks->setText( m_showAllPlugins ?
|
||||
"<a href=\"#\">Show Fewer Plugins</a>" :
|
||||
"<a href=\"#\">Show More Plugins</a>"
|
||||
);
|
||||
}
|
||||
|
||||
static const char *s_crashMessageParallel = ""
|
||||
"The emulator exited shortly after launching. If you are able to launch other ROMs without issues, then "
|
||||
"this ROM may contain invalid or unsafe RSP microcode that cannot be run on console accurate graphics "
|
||||
"plugins. Alternatively, if you have a very old onboard graphics card, it is possible that Vulkan is not "
|
||||
"supported on your system. In either case, using the Glide64 graphics plugin might resolve the issue.";
|
||||
|
||||
static const char *s_crashMessageAngrylion = ""
|
||||
"The emulator exited shortly after launching. If you are able to launch other ROMs without issues, then "
|
||||
"this ROM may contain invalid or unsafe RSP microcode that cannot be run on console accurate graphics "
|
||||
"plugins. If this is the case, try running the ROM with the Glide64 graphics plugin instead.";
|
||||
|
||||
#ifndef _WIN32
|
||||
static const char *s_crashMessageFlatpak = ""
|
||||
"The emulator exited shortly after launching. If you are able to launch other ROMs without issues, then "
|
||||
"this ROM is most likely corrupt. If you are not able to launch any ROMs, verify that you have not updated "
|
||||
"your graphics drivers since installing RetroArch in Flatpak. If you have, run `flatpak update` to sync "
|
||||
"the drivers in Flatpak with the rest of your system.";
|
||||
#endif
|
||||
|
||||
static const char *s_crashMessageDefault = ""
|
||||
"The emulator exited shortly after launching. If you are able to launch other ROMs without issues, then "
|
||||
"this ROM is most likely corrupt. If you are not able to launch any ROMs, check that the ParallelN64 "
|
||||
"emulator core is installed and that you are using RetroArch version 1.9 or later.";
|
||||
|
||||
static inline ConnectedGamepad getActiveController( AppSettings &settings ) {
|
||||
std::vector<ConnectedGamepad> connectedControllers = GamepadController::instance().getConnected();
|
||||
while( settings.showNoControllerWarning && connectedControllers.empty() ) {
|
||||
QMessageBox dialog;
|
||||
dialog.setIcon( QMessageBox::Warning );
|
||||
dialog.setWindowTitle( "Controller Not Connected" );
|
||||
dialog.setText( "You do not have a recognized controller connected." );
|
||||
dialog.setStandardButtons( QMessageBox::Retry | QMessageBox::Ok );
|
||||
dialog.setDefaultButton( QMessageBox::Ok );
|
||||
|
||||
QCheckBox ignoreCheckbox( "Don't show this message again", &dialog );
|
||||
dialog.setCheckBox( &ignoreCheckbox );
|
||||
|
||||
if( dialog.exec() == QMessageBox::Retry ) {
|
||||
connectedControllers = GamepadController::instance().getConnected();
|
||||
continue;
|
||||
} else if( ignoreCheckbox.isChecked() ) {
|
||||
settings.showNoControllerWarning = false;
|
||||
FileController::saveAppSettings( settings );
|
||||
}
|
||||
|
||||
return ConnectedGamepad{ -1, ControllerInfo() };
|
||||
}
|
||||
|
||||
if( connectedControllers.size() == 1 ) {
|
||||
return connectedControllers[0];
|
||||
}
|
||||
|
||||
if( settings.preferredController.has_value() ) {
|
||||
for( const ConnectedGamepad &controller : connectedControllers ) {
|
||||
if( controller.info.uuid == settings.preferredController.value() ) {
|
||||
return controller;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SingleplayerControllerSelectDialog dialog( connectedControllers );
|
||||
dialog.exec();
|
||||
return dialog.getSelectedController();
|
||||
}
|
||||
|
||||
static inline ControllerProfile getControllerProfile( const ConnectedGamepad &controller ) {
|
||||
if( controller.id < 0 ) {
|
||||
return DefaultProfile::XBox360;
|
||||
}
|
||||
|
||||
const HashMap<Uuid,string> mappings = FileController::loadControllerMappings();
|
||||
if( mappings.count( controller.info.uuid ) > 0 ) {
|
||||
const string &activeProfile = mappings.at( controller.info.uuid );
|
||||
|
||||
const std::map<string, ControllerProfile> profiles = FileController::loadControllerProfiles();
|
||||
if( profiles.count( activeProfile ) > 0 ) {
|
||||
return profiles.at( activeProfile );
|
||||
}
|
||||
}
|
||||
|
||||
if( getControllerType( controller.info.controllerId ) == ControllerType::Gamecube ) {
|
||||
return DefaultProfile::Gamecube;
|
||||
}
|
||||
|
||||
return DefaultProfile::XBox360;
|
||||
}
|
||||
|
||||
|
||||
void MainWindow::playSingleplayer() {
|
||||
const ConnectedGamepad activeController = getActiveController( m_settings );
|
||||
const std::vector<PlayerController> players = {
|
||||
{ getControllerProfile( activeController ), activeController.info.uuid }
|
||||
};
|
||||
launchEmulator( players, true );
|
||||
}
|
||||
|
||||
void MainWindow::playMultiplayer() {
|
||||
MultiplayerControllerSelectDialog dialog;
|
||||
if( dialog.exec() != QDialog::Accepted ) return;
|
||||
|
||||
const std::array<ConnectedGamepad,4> controllers = dialog.getControllers();
|
||||
std::vector<PlayerController> players;
|
||||
players.reserve( 4 );
|
||||
|
||||
for( size_t i = 0; i < 4; i++ ) {
|
||||
if( controllers[i].id < 0 ) continue;
|
||||
|
||||
while( i > players.size() ) players.push_back({ DefaultProfile::XBox360, Uuid() });
|
||||
players.push_back({ getControllerProfile( controllers[i] ), controllers[i].info.uuid });
|
||||
}
|
||||
|
||||
if( players.empty() ) {
|
||||
players.push_back({ DefaultProfile::XBox360, 0 });
|
||||
}
|
||||
|
||||
launchEmulator( players, dialog.canBindSavestates() );
|
||||
}
|
||||
|
||||
void MainWindow::launchEmulator(
|
||||
const std::vector<PlayerController> &players,
|
||||
bool bindSavestate
|
||||
) {
|
||||
const ROM *rom = m_ui->romList->tryGetSelectedRom();
|
||||
assert( rom != nullptr );
|
||||
|
||||
AsyncProcess *emulator = new AsyncProcess();
|
||||
try {
|
||||
*emulator = RetroArch::launchRom(
|
||||
rom->path,
|
||||
m_settings,
|
||||
players,
|
||||
rom->plugin,
|
||||
m_ui->optimizeCheckbox->isChecked(),
|
||||
bindSavestate
|
||||
);
|
||||
} catch( ... ) {
|
||||
QMessageBox::critical( this, "Emulator Missing", "Failed to launch emulator. It does not appear to be installed." );
|
||||
return;
|
||||
}
|
||||
|
||||
const QRect winGeo = geometry();
|
||||
hide();
|
||||
|
||||
NowPlayingWindow *nowPlayingWindow = nullptr;
|
||||
if( !m_settings.hideWhenPlaying ) {
|
||||
nowPlayingWindow = new NowPlayingWindow( emulator, rom->name, rom->playTime );
|
||||
nowPlayingWindow->show();
|
||||
}
|
||||
|
||||
ProcessAwaiter::QtSafeAwait(
|
||||
emulator,
|
||||
[=]( [[maybe_unused]] int64 exitCode, int64 runtimeMs ) {
|
||||
delete emulator;
|
||||
if( nowPlayingWindow != nullptr ) {
|
||||
nowPlayingWindow->close();
|
||||
nowPlayingWindow->deleteLater();
|
||||
}
|
||||
|
||||
if( runtimeMs > 0 ) {
|
||||
ROM updatedRom = *rom;
|
||||
updatedRom.playTime += runtimeMs;
|
||||
updatedRom.lastPlayed = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch()
|
||||
).count();
|
||||
updateRom( updatedRom );
|
||||
}
|
||||
|
||||
this->setGeometry( winGeo );
|
||||
this->show();
|
||||
QTimer::singleShot( 50, [=](){ this->setGeometry( winGeo ); } );
|
||||
|
||||
if( runtimeMs < 2000 ) {
|
||||
const char *errorMessage;
|
||||
switch( rom->plugin ) {
|
||||
case GfxPlugin::ParaLLEl:
|
||||
errorMessage = s_crashMessageParallel;
|
||||
break;
|
||||
case GfxPlugin::Angrylion:
|
||||
errorMessage = s_crashMessageAngrylion;
|
||||
break;
|
||||
default:
|
||||
#ifdef _WIN32
|
||||
errorMessage = s_crashMessageDefault;
|
||||
#else
|
||||
errorMessage = m_settings.usingFlatpak ? s_crashMessageFlatpak : s_crashMessageDefault;
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
QMessageBox::critical( this, "Possible ROM Error", errorMessage );
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void MainWindow::configureController() {
|
||||
ControllerSelectDialog dialog;
|
||||
dialog.exec();
|
||||
}
|
||||
|
||||
void MainWindow::editSettings() {
|
||||
SettingsDialog *dialog = new SettingsDialog();
|
||||
dialog->exec();
|
||||
delete dialog;
|
||||
reloadSettings();
|
||||
}
|
||||
|
||||
void MainWindow::manageRomSources() {
|
||||
RomSourceDialog *dialog = new RomSourceDialog();
|
||||
dialog->exec();
|
||||
delete dialog;
|
||||
m_romListFetcher.fetchAsync();
|
||||
}
|
||||
|
||||
void MainWindow::closeEvent( QCloseEvent *event ) {
|
||||
const UiState uiState = {
|
||||
saveTreeState(),
|
||||
width(),
|
||||
height(),
|
||||
m_ui->pluginRiceRadio->isVisible()
|
||||
};
|
||||
|
||||
FileController::saveUiState( uiState );
|
||||
QMainWindow::closeEvent( event );
|
||||
QCoreApplication::quit();
|
||||
}
|
||||
|
||||
TreeUiState MainWindow::saveTreeState() const {
|
||||
TreeUiState uiState;
|
||||
|
||||
const ROM *selectedRom = m_ui->romList->tryGetSelectedRom();
|
||||
if( selectedRom != nullptr ) {
|
||||
uiState.selectedRom = selectedRom->path;
|
||||
uiState.selectedGroup = static_cast<const RomListModel*>( m_ui->romList->model() )->tryGetGroup( m_ui->romList->currentIndex().parent() );
|
||||
}
|
||||
|
||||
const int numGroups = m_ui->romList->model()->rowCount();
|
||||
for( int i = 0; i < numGroups; i++ ) {
|
||||
const QModelIndex index = m_ui->romList->model()->index( i, 0 );
|
||||
if( m_ui->romList->isExpanded( index ) ) {
|
||||
uiState.expandedGroups.push_back(
|
||||
static_cast<const RomListModel*>( m_ui->romList->model() )->tryGetGroup( index )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return uiState;
|
||||
}
|
||||
|
||||
void MainWindow::restoreTreeState( const TreeUiState &state ) {
|
||||
for( const string &group : state.expandedGroups ) {
|
||||
const QModelIndex index = static_cast<const RomListModel*>( m_ui->romList->model() )->tryGetIndex( group );
|
||||
if( index.isValid() ) {
|
||||
m_ui->romList->expand( index );
|
||||
}
|
||||
}
|
||||
|
||||
if( !state.selectedRom.empty() ) {
|
||||
m_ui->romList->selectRom( state.selectedGroup, state.selectedRom );
|
||||
}
|
||||
|
||||
romSelectionChanged();
|
||||
}
|
||||
|
||||
|
||||
void MainWindow::addTag( const ROM *rom, string tag ) {
|
||||
ROM updatedRom = *rom;
|
||||
updatedRom.tags.insert( tag );
|
||||
updateRom( updatedRom );
|
||||
}
|
||||
|
||||
void MainWindow::removeTag( const ROM *rom, string tag ) {
|
||||
ROM updatedRom = *rom;
|
||||
updatedRom.tags.erase( tag );
|
||||
updateRom( updatedRom );
|
||||
}
|
||||
|
||||
void MainWindow::editSave( [[maybe_unused]] const ROM *rom, fs::path saveFilePath ) {
|
||||
SaveFileEditorDialog dialog( saveFilePath );
|
||||
if( dialog.exec() == QDialog::Accepted ) {
|
||||
SaveSlotEditorDialog dialog2( saveFilePath, dialog.getSaveSlot() );
|
||||
dialog2.exec();
|
||||
}
|
||||
}
|
61
src/ui/main-window.hpp
Normal file
@@ -0,0 +1,61 @@
|
||||
#ifndef SRC_UI_MAIN_WINDOW_HPP_
|
||||
#define SRC_UI_MAIN_WINDOW_HPP_
|
||||
|
||||
#include <QWidget>
|
||||
#include <QMainWindow>
|
||||
#include <memory>
|
||||
#include "src/ui/rom-list-fetcher.hpp"
|
||||
#include "src/ui/ui-state.hpp"
|
||||
#include "src/core/settings.hpp"
|
||||
#include "src/core/async.hpp"
|
||||
#include "src/core/rom.hpp"
|
||||
|
||||
namespace Ui {
|
||||
class MainWindow;
|
||||
}
|
||||
|
||||
class MainWindow : public QMainWindow {
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
Ui::MainWindow *m_ui;
|
||||
bool m_showAllPlugins;
|
||||
AppSettings m_settings;
|
||||
RomListFetcher m_romListFetcher;
|
||||
|
||||
TreeUiState saveTreeState() const;
|
||||
void restoreTreeState( const TreeUiState &state );
|
||||
void updateRom( const ROM &updatedRom );
|
||||
void launchEmulator( const std::vector<PlayerController> &players, bool bindSavestate );
|
||||
|
||||
private slots:
|
||||
void reloadSettings();
|
||||
|
||||
void romListFetched( FetchedRomList roms );
|
||||
|
||||
void romSelectionChanged();
|
||||
void optimizationsToggled();
|
||||
void morePluginsToggled();
|
||||
void playSingleplayer();
|
||||
void playMultiplayer();
|
||||
void configureController();
|
||||
void manageRomSources();
|
||||
void editSettings();
|
||||
void pluginChanged( int pluginId, bool checked );
|
||||
|
||||
void addTag( const ROM *rom, string tag );
|
||||
void removeTag( const ROM *rom, string tag );
|
||||
void editSave( const ROM *rom, fs::path saveFilePath );
|
||||
|
||||
public:
|
||||
MainWindow( QWidget *parent = nullptr );
|
||||
virtual ~MainWindow();
|
||||
|
||||
protected:
|
||||
virtual void closeEvent( QCloseEvent *event ) override;
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif /* SRC_UI_MAIN_WINDOW_HPP_ */
|