feat/update 3.1.0 (#7)

* Added initial text shaping support

* Fixed some font issues

* (Windows) Added initial text shaping support

* (macOS) Added initial text shaping support

* Disabled building of HarfBuzz-subset on Windows and macOS

* (Android) Added initial text shaping support

* Added the nl_NL locale to locale/languages

* Changed the font VRAM usage calculation to actually only include texture data

* Moved the HarfBuzz segment building to a separate function

Also implemented segment caching and fixed an issue where missing glyphs were not handled correctly

* Moved the text shaping to a separate function

* Fixed a text shaping issue when there was a font change for the last character of a string

* Added support for the pl_PL locale

* Changed two font calculation functions to use shaped text

Also consolidated the HarfBuzz segment creation and shaping into a single function

* Added a hack to make shaped text wrap somehow correctly

* Changed the text shaping function to return the segment vector

* Text shaping segments are no longer created by space characters

* RTL text segments are now flagged as such

* Fixed an issue where text was not correctly centered after line breaks

* Reverted some font changes that were not needed after all

* Changed to having HarfBuzz set the horizontal glyph advance

* Fixed another failure mode for the wrapText shaped text hack

* Added a precaution to prevent crashes in case of broken fonts being used

* Made accurate text layout work correctly using HarfBuzz

* Removed the offensive wrapText hacks and added some optimizations

Also changed the three dots to an actual ellipsis Unicode character when abbreviating text

* Reverted a change in TextComponent as it caused unforeseen issues

* Changed Font::shapeText() to pass the segments vector by reference

* Removed a temporary member variable in Font and replaced it with proper argument passing

* Fixed a regression where text shaping stopped working

* Added sharing of glyph atlas entries between shaped glyph entries that need the same texture

* Added support for the ar_EG locale

* Some font-related code and comments cleanup

* Fixed a source file header typo

* Documentation update

* Removed a lot of unnecessary text processing

* Added the ICU library as a dependency

* (Android) Added the ICU library as a dependency

* (macOS) Added the ICU library as a dependency

* (Windows) Added the ICU library as a dependency

* (Windows) Fixed an MSVC compiler warning

* Replaced all built-in Unicode case conversion logic and lookup tables with facilities from the ICU library

* Documentation update

* Updated the pl_PL translations

* Added a menu title font size adjustment for the pl_PL translations

* Removed support for NetBSD and OpenBSD

* Changed a code comment that referred to BSD Unix

* Documentation update

* Silenced some Clang compiler warnings

* Added experimental support for building on Haiku

* (Haiku) Added a ScreenScraper platform identifier

* (Haiku) Added support for the Sony PlayStation Portable (psp) game system

* (Haiku) Added support for the ScummVM Game Engine (scummvm) game system

* Documentation update

* Updated the pl_PL translations

* Changed ScreenSaver to use TextComponent instead of using Font facilities directly

* Changed Window to use TextComponent instead of using Font facilities directly

* Changed ButtonComponent to use TextComponent instead of using Font facilities directly

* Changed SliderComponent to use TextComponent instead of using Font facilities directly

* Reverted ButtonComponent and SliderComponent to render the debug overlays themselves

* Changed DateTimeEditComponent to use TextComponent instead of using Font facilities directly

* Minor code cleanup

* Changed TextEditComponent to use TextComponent instead of using Font facilities directly

* Changed Font::buildTextCache() and Font::renderTextCache() to protected functions

* Changed a compiler silencing option to only apply to Clang

* (Haiku) Updated CMake configuration to make ES-DE build on Haiku Nightly (but no longer on R1/beta4)

* Documentation update

* (Haiku) Added find rule configuration for RetroArch

Also added a single core for testing purposes

* Removed direct use of Font::wrapText() from OptionListComponent, TextEditComponent and TextListComponent

* Removed direct use of Font::wrapText() from TextComponent

* Fixed an issue where ComponentList could generate elements with negative widths

* Added an assertion to GuiComponent::setSize() to check for negative mSize values

* DateTimeEditComponent no longer renders the debug overlay unless there is a string to display

* (FreeBSD) Added support for building with DEINIT_ON_LAUNCH

* (FreeBSD) Added the man page to the CPack configuration

* (FreeBSD) Added support for rebooting and powering off from inside ES-DE

* (FreeBSD) Added fallback method to locate binary

* Added layout and line wrapping support for shaped text and for mixing of LTR and RTL scripts

* Fixed a special line wrapping scenario where a trailing space should be removed

* (Windows) Fixed some MSVC compiler warnings

* Fixed some Clang compiler warnings

* Fixed an issue where theme names in the theme downloader could get abbreviated

* Added support for the ca_AD locale

* Documentation update

* (Android) Fonts and locales are now copied earlier than the other assets as HarfBuzz and libintl need them earlier in the startup process

* Documentation update

* Added support for the de_DE locale

* (Android) Added a new default find rule entry for Flycast as its application ID has been changed

* Documentation update

* Fixed an issue where text shaping could be permanently disabled after editing text

* Fixed a potential issue where globally disabling text shaping could cause space detection to fail

* Added a check for whether a text element has a width defined when the container property is set

* (Android) Changed ePSXe to use %ROM% instead of %ROMSAF%

* (Haiku) Added support for the PDF viewer

* Updated the el_GR.po, es_ES.po, fr_FR.po, it_IT.po, ja_JP.po, ru_RU.po and zh_CN.po locale files

* Documentation update

* (Haiku) Added correct installation directories to the CMake configuration

* (Haiku) Changed to correct installation directories

* (Haiku) Added support for the correct system resource directories

* (Haiku) Made sure es-pdf-convert is found under all circumstances

* Updated the fr_FR translations

* Updated the es_ES translations

* Updated the it_IT translations

* Added a menu title font size adjustment for the it_IT translations

* Updated the ja_JP translations

* Updated the zh_CN translations

* Fixed an issue where scraping using TheGamesDB would crash the application

* Added an extra check in OptionListComponent to avoid potential crashes

* Removed support for the ca_AD locale

* Added a code comment clarification in FileSystemUtil

* Updated the pl_PL translations

* Some minor code modernization in MameNames

* Fixed an issue where returning from a game would sometimes make the helpsystem use the dimmed theme properties

* (Haiku) Added a resource file

* Added a menu title font size adjustment for the de_DE translations

* (Haiku) Added support for some game systems

* (Haiku) Added a HaikuPorts recipe

* (Haiku) Fixed an URI issue in the HaikuPorts recipe

* Documentation update

* (Haiku) Added configuration for a number of game systems

* Updated the it_IT translations

* Documentation update

* (Haiku) Updated the srcGitRev value in the HaikuPorts recipe

* (Haiku) Added configuration for a number of game systems

* Documentation update

* (Haiku) Updated the srcGitRev value in the HaikuPorts recipe

* (Haiku) Added configuration for a number of game systems

* Documentation update

* (Haiku) Added configuration for a number of game systems

* Documentation update

* (Haiku) Updated the srcGitRev value in the HaikuPorts recipe

* Added basic configuration support and menu entries for theme localization

* Changed a theme loading debug message

* (linear-es-de) Fixed an issue where the system logo for saturnjp was incorrectly showing the western variant

* (modern-es-de) Fixed an issue where the system logo for saturnjp was incorrectly showing the western variant

* Updated the it_IT translations

* Added support for using language variables in the theme configuration

* Added localization support to DateTimeComponent

* Added translations for the automatic collection names when used as theme system variables

* Added localization support for the theme game counter

* Added theme contextual hinting to the custom collection summary text in CollectionSystemsManager

Also added translation support for a string that was previously missed

* Added localization support to the label entries in capabilities.xml

* Fixed a regression where horizontal text containers would sometimes not work correctly

* Fixed an issue where text elements defined as gamecount using the systemdata property could not scroll horizontally

* Added support for including theme files from within the colorScheme and fontSize tag pairs

* Added translations for the automatic collection names (short name versions) when used as theme system variables

* Fixed an incorrect code comment in CollectionSystemsManager

* Added translations for the name and fullname systemdata properties for the text element

* Added translation support for the metadata property for the text element

* Updated all locale (.po) files with the theme engine localization additions

* (linear-es-de) Added translations for en_US, en_GB and sv_SE

* Documentation update

* Updated the fr_FR translations

* (linear-es-de) Added translations for fr_FR

* Updated the ja_JP translations

* Updated the zh_CN translations

* (modern-es-de) Added translations for en_US, en_GB, fr_FR and sv_SE

* Updated the es_ES translations

* Updated the ro_RO translations

* (linear-es-de) Added translations for es_ES

* (linear-es-de) Added translations for ro_RO

* (slate-es-de) Added translations for en_US, en_GB and sv_SE

* (linear-es-de) Updated the es_ES translations

* (modern-es-de) Updated the fr_FR translations

* (linear-es-de) Some minor translation changes

* (modern-es-de) Added translations for ro_RO

* (slate-es-de) Added translations for ro_RO

* Updated the it_IT translations

* Updated the pt_BR translations

* (linear-es-de) Added translations for it_IT

* (modern-es-de) Decreased the helpsystem entry spacing

* (modern-es-de) Added translations for es_ES and it_IT

* (slate-es-de) Added translations for es_ES, fr_FR and it_IT

* (linear-es-de) Added translations for pt_BR

* (modern-es-de) Added translations for pt_BR

* (slate-es-de) Added translations for pt_BR

* (Haiku) Added support for the c64, plus4 and vic20 systems

* Documentation update

* (Haiku) Updated the srcGitRev value in the HaikuPorts recipe

* Updated SDL to 2.30.6 on Android, Windows, macOS and the Linux AppImage builds

* Added an ICU filter configuration file

* (macOS) Reduced the ICU library size via a data filter file

* (Windows) Reduced the ICU library size via a data filter file

* Updated the ru_RU translations

* (linear-es-de) Added translations for ru_RU

* (modern-es-de) Added translations for ru_RU

* (slate-es-de) Added translations for ru_RU

* Added a menu title font size adjustment for the ru_RU translations

* Removed an unnecessary element resize in ScrollableContainer

* Fixed a line breaking issue

* Added theme engine translations for 'unknown' metadata values for developer, publisher, genre and players

* Added theme engine translations for 'never' and 'unknown' date values

* (linear-es-de) Added translations for ja_JP and zh_CN

* (modern-es-de) Added translations for ja_JP and zh_CN

* (slate-es-de) Added translations for ja_JP and zh_CN

* Updated all locales with new theme engine translations

* Fixed an issue where the text element defaultValue property no longer worked correctly

* (modern-es-de) Added some capitalized default metadata values

* Documentation update

* pdated the el_GR translations

* (linear-es-de) Updated the system metadata

* (linear-es-de) Added sv_SE translations for all system hardware types

* Updated the de_DE translations

* Updated the pl_PL translations

* Bundled the July 2024 release of the Mozilla TLS/SSL certificates

* Updated the MAME index files to include ROMs up to MAME version 0.269

* (linear-es-de) Added translations for pl_PL

* Added the VirtualXT RetroArch core as an alternative emulator for the dos and pc systems

* Added the Stella 2023 RetroArch core as an alternative emulator for the atari2600 system

* Removed support for the ar_EG, de_DE, el_GR and nl_NL locales and moved their .po files to an archive directory

* Documentation update

* (modern-es-de) Added translations for pl_PL

* (slate-es-de) Added translations for pl_PL

* Updated SDL to 2.30.7 on Android, Windows, macOS and the Linux AppImage builds

* Updated the fr_FR translations

* Added support for the new Lime3DS binary names on Linux, macOS and Windows

* Added some missing find rules for Lime3DS

* (Windows) Added 'Shortcut' as an alternative emulator for the switch system

Also added the .lnk file extension

* Added jgenesis as an alternative emulator for the famicom, gamegear, gb, gbc, genesis, mastersystem, megacd, megacdjp, megadrive, megadrivejp, nes, segacd, sfc, snes and snesna systems on Linux and Windows

* Documentation update

* Added izapple2 standalone as an alternative emulator for the apple2 system on Linux and Windows

* (Android) Added support for the Microsoft Windows (windows) game system using the Winlator emulator

* (Android) Added Winlator PRoot Cmod standalone as an alternative emulator for the windows system

* Documentation update

* (Android) Added support for the PC Arcade Systems (pcarcade) and Taito Type X (type-x) game systems

* Bumped the version to 3.1.0

* (modern-es-de) Eliminated an annoying debug message

* (linear-es-de) Added some missing metadata files

* (linear-es-de) Added some missing sv_SE translations

* Updated the Winlator emulator names

* Documentation update

* Documentation update for the 3.1.0 release

* Updated latest_release.json for the 3.1.0 release

* Fixed a typo in the changelog

* Documentation update

* (Haiku) Updated the srcGitRev value in the HaikuPorts recipe

* Bumped the version to 3.1.1-alpha

* Video player resources are now completely freed up after finishing view transitions

* Changed a rounding in ScrollableContainer to slightly decrease the risk of glyphs getting cut off at the bottom of the container

* Added the Nanum Square Neo Korean font

* Added support for the ko_KR locale

* Fixed an issue where newly entered ScreenScraper username and password values were positioned incorrectly vertically in the account settings menu

* Documentation update

* Changed the position of the ko_KR language

* Changed the ja_JP position in the languages file

* Fixed an issue where attempting to view media for a game that had no downloaded media paused the playback of all static theme videos

* Documentation update

* Added support for the de_DE locale

* Updated the fr_FR translations

* Documentation update

* Updated the de_DE translations

---------

Co-authored-by: Leon Styhre <leon@leonstyhre.com>
This commit is contained in:
XargonWan 2024-09-18 09:23:26 +09:00 committed by GitHub
parent 76e2fd741f
commit 1167c4be41
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
527 changed files with 71850 additions and 8289 deletions

1
.gitignore vendored
View file

@ -1,6 +1,7 @@
# Object files # Object files
*.o *.o
*.lo *.lo
*.mo
*.slo *.slo
# Shared libraries # Shared libraries

View file

@ -182,6 +182,8 @@ Apart from this it works as you'd expect, ES-DE will start automatically when re
If the operating system runs out of memory when a game is running it will kill ES-DE even if it's set as home app. If this happens ES-DE will reload whenever you return from the game or if you press the home button. This is simply how Android works. If the operating system runs out of memory when a game is running it will kill ES-DE even if it's set as home app. If this happens ES-DE will reload whenever you return from the game or if you press the home button. This is simply how Android works.
Also be aware that the version check that runs on app startup may not be able to complete successfully when ES-DE is set as the home app, as the device may not have network connectivity enabled yet just after rebooting.
It's generally a very good idea to import your native Android apps into ES-DE prior to setting it as the home app, this way you can easily access things like the Settings app. Note however that even if you somehow lock yourself out of the system by setting ES-DE as the home app and not having any native apps added you can still always access the Settings app via the Android notification shade. On most devices you access this by swiping down from the top of the screen. After swiping down, just select the cogwheel icon to start the Settings app. From there you can change the home app to something else than ES-DE, should you need to. It's generally a very good idea to import your native Android apps into ES-DE prior to setting it as the home app, this way you can easily access things like the Settings app. Note however that even if you somehow lock yourself out of the system by setting ES-DE as the home app and not having any native apps added you can still always access the Settings app via the Android notification shade. On most devices you access this by swiping down from the top of the screen. After swiping down, just select the cogwheel icon to start the Settings app. From there you can change the home app to something else than ES-DE, should you need to.
## Known ES-DE problems ## Known ES-DE problems
@ -332,6 +334,20 @@ After installing the emulator, open it and go to the settings tab, then choose "
http://www.arts-union.ru/node/23 http://www.arts-union.ru/node/23
### J2ME Loader
This emulator can be installed from the Play store or the F-Droid store, or it can be downloaded from their GitHub site.
https://play.google.com/store/apps/details?id=ru.playsoftware.j2meloader \
https://f-droid.org/en/packages/ru.playsoftware.j2meloader \
https://github.com/nikita36078/J2ME-Loader/releases
### JL-Mod
This emulator can be downloaded from their GitHub site.
https://github.com/woesss/JL-Mod/releases
### Lime3DS ### Lime3DS
This emulator which is forked from Citra can be downloaded from their GitHub site. This emulator which is forked from Citra can be downloaded from their GitHub site.
@ -347,6 +363,14 @@ Note that for MAME4droid 2024 there's an exception when it comes to setting up t
https://play.google.com/store/apps/details?id=com.seleuco.mame4d2024 \ https://play.google.com/store/apps/details?id=com.seleuco.mame4d2024 \
https://play.google.com/store/apps/details?id=com.seleuco.mame4droid https://play.google.com/store/apps/details?id=com.seleuco.mame4droid
Be aware that MAME4droid 2024 requires specific input configuration for some systems. For instance to navigate the mouse cursor when using touch input you'll need to got into the _Settings_ menu, then _Input_, then _Touch controller_ and change _Mode_ to _Analog Stick_.
If using a physical controller for mouse input (via the thumbstick) then you will need to map the mouse buttons to physical controller buttons. You do this via the MAME input settings. Bring up the MAME menu by pressing both thumbsticks, or by pressing the _Start_ and _Coin_ buttons on the touch overlay. Go into _Input Settings_ then _Input Assignments (this system)_ where you can assign physical buttons to the mouse buttons.
For some systems you will need to explictly set the _Start_ and _Select_ buttons in the same fashion as when configuring the mouse buttons. Otherwise you'll not be able to start any games.
There are a few more things that you may need to configure for some systems, but that's beyond the scope of this document and should be covered by the MAME emulator documentation.
### MasterGear ### MasterGear
This emulator can be installed from the Play store as a paid app. This emulator can be installed from the Play store as a paid app.
@ -387,6 +411,12 @@ Nesoid is not available on the Play store but it can be installed from the F-Dro
https://f-droid.org/en/packages/com.androidemu.nes \ https://f-droid.org/en/packages/com.androidemu.nes \
https://github.com/proninyaroslav/nesoid/releases https://github.com/proninyaroslav/nesoid/releases
### NooDS
Although NooDS is available via the Play store that version does not allow game launching from ES-DE. To get that to work instead use the version from their GitHub site. Also note that this emulator does not support launching of zipped game files.
https://github.com/Hydr8gon/NooDS/releases
### OpenBOR ### OpenBOR
Although OpenBOR is working fine on Android it's not possible to properly integrate it with a frontend, you'll instead need to install your game PAKs into the `/sdcard/OpenBOR/Paks` directory and create dummy .openbor files for your games in `ROMs/openbor` and after launching a game from ES-DE you need to manually start it from inside the OpenBOR GUI. There are more detailed setup instructions in the _OpenBOR_ section of the [User guide](USERGUIDE-DEV.md#openbor). Although OpenBOR is working fine on Android it's not possible to properly integrate it with a frontend, you'll instead need to install your game PAKs into the `/sdcard/OpenBOR/Paks` directory and create dummy .openbor files for your games in `ROMs/openbor` and after launching a game from ES-DE you need to manually start it from inside the OpenBOR GUI. There are more detailed setup instructions in the _OpenBOR_ section of the [User guide](USERGUIDE-DEV.md#openbor).
@ -484,6 +514,17 @@ This PlayStation Vita emulator can be downloaded from their GitHub site. Refer t
https://github.com/Vita3K/Vita3K-Android/releases https://github.com/Vita3K/Vita3K-Android/releases
### Winlator
In order to use Winlator to run Windows games you need to use a specific fork named _Winlator Cmod_ as mainline [Winlator](https://winlator.com/) does not offer frontend support. The Cmod fork can be downloaded from their GitHub page:\
https://github.com/coffincolors/winlator
There are two variants of the fork, Glibc and PRoot, both of which come with some pros and cons with regards to compatibility and performance. The Glibc variant is the default emulator in ES-DE, so to use PRoot instead you'll need to select its alternative emulator entry.
In addition to the official repository there are multiple Winlator builds floating around the Internet, but these have not been extensively tested with ES-DE.
It's beyond the scope of this document to describe how to install games in Winlator, but once it's done and you've created a shortcut to your game from inside the container you can export it via the _Export for Frontend_ option in the Winlator user interface. This will generate a .desktop file that you can place in the `ROMs/pcarcade`, `ROMs/type-x` or `ROMs/windows` folder and launch from ES-DE. You can alternatively set the _Frontend Export Path_ setting from inside the Winlator Settings screen to avoid the manual step of moving the .desktop file.
### Yaba Sanshiro 2 ### Yaba Sanshiro 2
This emulator can be installed from the Play store. Note that only the paid Pro version supports game launching from ES-DE. Also note that .bin/.cue files can't be launched for the time being, only .chd files seem to work. This needs to be fixed in the emulator so nothing can be done in ES-DE to work around that limitation. This emulator can be installed from the Play store. Note that only the paid Pro version supports game launching from ES-DE. Also note that .bin/.cue files can't be launched for the time being, only .chd files seem to work. This needs to be fixed in the emulator so nothing can be done in ES-DE to work around that limitation.
@ -504,6 +545,7 @@ This is clearly not a complete list of Android devices, but rather those we know
| Anbernic | RG505 | 12 | Yes | None | Limited RAM capacity for this device makes it unsuitable for demanding themes and large game collections | | Anbernic | RG505 | 12 | Yes | None | Limited RAM capacity for this device makes it unsuitable for demanding themes and large game collections |
| Anbernic | RG556 | 13 | Yes | None | | | Anbernic | RG556 | 13 | Yes | None | |
| Anbernic | RG ARC | 12 | Yes | None | LineageOS | | Anbernic | RG ARC | 12 | Yes | None | LineageOS |
| Anbernic | RG Cube | 13 | Yes | None | |
| AYANEO | Pocket Air | 12 | Yes | None | | | AYANEO | Pocket Air | 12 | Yes | None | |
| AYANEO | Pocket S | 13 | Yes | None | | | AYANEO | Pocket S | 13 | Yes | None | |
| Ayn | Odin (Base/Pro) | 10 | Yes | None | | | Ayn | Odin (Base/Pro) | 10 | Yes | None | |
@ -528,7 +570,7 @@ This is clearly not a complete list of Android devices, but rather those we know
| Huawei | MatePad 11 (2021) | 13 | Yes | None | | | Huawei | MatePad 11 (2021) | 13 | Yes | None | |
| Infinix | Zero 30 5G | 13 | Yes | None | | | Infinix | Zero 30 5G | 13 | Yes | None | |
| Kinhank | G1 | 11 | No | Unable to install | Possibly 32-bit operating system? | | Kinhank | G1 | 11 | No | Unable to install | Possibly 32-bit operating system? |
| Kinhank | Super Console X5 Pro | 12 (TV) | No | Fails at configurator/onboarding | Seems to run a custom 64-bit Android TV OS | | Kinhank | Super Console X5 Pro | 12 (TV) | No | None | Custom 64-bit Android TV OS |
| Lenovo | Legion Y700 (2022) | 12 | Yes | None | | | Lenovo | Legion Y700 (2022) | 12 | Yes | None | |
| Lenovo | Legion Y700 (2023) | 13 | Yes | None | | | Lenovo | Legion Y700 (2023) | 13 | Yes | None | |
| Lenovo | Xiaoxin Pad Pro 2021 | 11 | Yes | None | | | Lenovo | Xiaoxin Pad Pro 2021 | 11 | Yes | None | |
@ -553,6 +595,9 @@ This is clearly not a complete list of Android devices, but rather those we know
| OnePlus | Open | 14 | Yes | None | | | OnePlus | Open | 14 | Yes | None | |
| Oppo | A15 | 10 | Yes | None | | | Oppo | A15 | 10 | Yes | None | |
| Oppo | Find X5 Pro | 14 | Yes | None | | | Oppo | Find X5 Pro | 14 | Yes | None | |
| Oppo | Reno5 | 12 | Yes | None | |
| Raspberry | Pi 4/400 | 13, 14 | Yes | None | Low-power GPU so ES-DE may run a bit sluggish |
| Raspberry | Pi 5 | 15 | Yes | None | Very poor GPU performance, runs at almost double speed in Raspberry Pi OS so likely a driver issue |
| Razer | Edge | 13 | Yes | None | | | Razer | Edge | 13 | Yes | None | |
| Realme | GT2 | 12 | Yes | None | | | Realme | GT2 | 12 | Yes | None | |
| Retroid | Pocket 2+ | 11 | Yes | None | | | Retroid | Pocket 2+ | 11 | Yes | None | |
@ -615,10 +660,10 @@ The **@** symbol indicates that the emulator is _deprecated_ and will be removed
| 3do | 3DO Interactive Multiplayer | Opera | Real3DOPlayer **(Standalone)** | Yes | | | 3do | 3DO Interactive Multiplayer | Opera | Real3DOPlayer **(Standalone)** | Yes | |
| adam | Coleco Adam | MAME4droid 2024 [Diskette] **(Standalone)** | MAME4droid 2024 [Tape] **(Standalone)**,<br>MAME4droid 2024 [Cartridge] **(Standalone)**,<br>MAME4droid 2024 [Software list] **(Standalone)**,<br>ColEm **(Standalone)** | Yes for MAME4droid 2024 | | | adam | Coleco Adam | MAME4droid 2024 [Diskette] **(Standalone)** | MAME4droid 2024 [Tape] **(Standalone)**,<br>MAME4droid 2024 [Cartridge] **(Standalone)**,<br>MAME4droid 2024 [Software list] **(Standalone)**,<br>ColEm **(Standalone)** | Yes for MAME4droid 2024 | |
| ags | Adventure Game Studio Game Engine | _Placeholder_ | | | | | ags | Adventure Game Studio Game Engine | _Placeholder_ | | | |
| amiga | Commodore Amiga | PUAE | PUAE 2021 | Yes | | | amiga | Commodore Amiga | PUAE | PUAE 2021 | Yes | See the specific _Commodore Amiga and CDTV_ section in the user guide |
| amiga1200 | Commodore Amiga 1200 | PUAE | PUAE 2021 | Yes | | | amiga1200 | Commodore Amiga 1200 | PUAE | PUAE 2021 | Yes | See the specific _Commodore Amiga and CDTV_ section in the user guide |
| amiga600 | Commodore Amiga 600 | PUAE | PUAE 2021 | Yes | | | amiga600 | Commodore Amiga 600 | PUAE | PUAE 2021 | Yes | See the specific _Commodore Amiga and CDTV_ section in the user guide |
| amigacd32 | Commodore Amiga CD32 | PUAE | PUAE 2021 | Yes | | | amigacd32 | Commodore Amiga CD32 | PUAE | PUAE 2021 | Yes | See the specific _Commodore Amiga and CDTV_ section in the user guide |
| amstradcpc | Amstrad CPC | Caprice32 | CrocoDS,<br>MAME4droid 2024 **(Standalone)** | Yes for MAME4droid 2024 | Single archive or disk file | | amstradcpc | Amstrad CPC | Caprice32 | CrocoDS,<br>MAME4droid 2024 **(Standalone)** | Yes for MAME4droid 2024 | Single archive or disk file |
| android | Google Android | _Placeholder_ | | | | | android | Google Android | _Placeholder_ | | | |
| androidapps | Android Apps | _Native apps_ | | No | | | androidapps | Android Apps | _Native apps_ | | No | |
@ -628,9 +673,9 @@ The **@** symbol indicates that the emulator is _deprecated_ and will be removed
| arcade | Arcade | MAME - Current | MAME 2010,<br>MAME 2003-Plus,<br>MAME 2003,<br>MAME 2000,<br>MAME4droid 2024 **(Standalone)**,<br>MAME4droid **(Standalone)**,<br>NEO.emu **(Standalone)**,<br>FinalBurn Neo,<br>FB Alpha 2012,<br>Geolith,<br>Flycast,<br>Flycast **(Standalone)** | Depends | See the specific _Arcade and Neo Geo_ section in the user guide | | arcade | Arcade | MAME - Current | MAME 2010,<br>MAME 2003-Plus,<br>MAME 2003,<br>MAME 2000,<br>MAME4droid 2024 **(Standalone)**,<br>MAME4droid **(Standalone)**,<br>NEO.emu **(Standalone)**,<br>FinalBurn Neo,<br>FB Alpha 2012,<br>Geolith,<br>Flycast,<br>Flycast **(Standalone)** | Depends | See the specific _Arcade and Neo Geo_ section in the user guide |
| arcadia | Emerson Arcadia 2001 | DroidArcadia **(Standalone**) | MAME4droid 2024 **(Standalone)** | No | Single archive or ROM file | | arcadia | Emerson Arcadia 2001 | DroidArcadia **(Standalone**) | MAME4droid 2024 **(Standalone)** | No | Single archive or ROM file |
| archimedes | Acorn Archimedes | MAME4droid 2024 [Model A440/1] **(Standalone)** | MAME4droid 2024 [Model A3000] **(Standalone)**,<br>MAME4droid 2024 [Model A310] **(Standalone)**,<br>MAME4droid 2024 [Model A540] **(Standalone)** | Yes | | | archimedes | Acorn Archimedes | MAME4droid 2024 [Model A440/1] **(Standalone)** | MAME4droid 2024 [Model A3000] **(Standalone)**,<br>MAME4droid 2024 [Model A310] **(Standalone)**,<br>MAME4droid 2024 [Model A540] **(Standalone)** | Yes | |
| arduboy | Arduboy Miniature Game System | Arduous | | No | Single archive or .hex file | | arduboy | Arduboy Miniature Game System | Arduous | Ardens | No | Single archive or .hex file |
| astrocde | Bally Astrocade | MAME4droid 2024 **(Standalone)** | | Yes | Single archive or ROM file | | astrocde | Bally Astrocade | MAME4droid 2024 **(Standalone)** | | Yes | Single archive or ROM file |
| atari2600 | Atari 2600 | Stella | Stella 2014,<br>2600.emu **(Standalone)** | No | Single archive or ROM file | | atari2600 | Atari 2600 | Stella | Stella 2014,<br>Stella 2023,<br>2600.emu **(Standalone)** | No | Single archive or ROM file |
| atari5200 | Atari 5200 | a5200 | Atari800 | Yes | Single archive or ROM file | | atari5200 | Atari 5200 | a5200 | Atari800 | Yes | Single archive or ROM file |
| atari7800 | Atari 7800 ProSystem | ProSystem | MAME4droid 2024 **(Standalone)** | Yes | Single archive or ROM file | | atari7800 | Atari 7800 ProSystem | ProSystem | MAME4droid 2024 **(Standalone)** | Yes | Single archive or ROM file |
| atari800 | Atari 800 | Atari800 | | Yes | | | atari800 | Atari 800 | Atari800 | | Yes | |
@ -657,8 +702,8 @@ The **@** symbol indicates that the emulator is _deprecated_ and will be removed
| daphne | Daphne Arcade LaserDisc Emulator | MAME4droid 2024 **(Standalone)** | DirkSimple | Depends | See the specific _LaserDisc Games_ section in the user guide | | daphne | Daphne Arcade LaserDisc Emulator | MAME4droid 2024 **(Standalone)** | DirkSimple | Depends | See the specific _LaserDisc Games_ section in the user guide |
| desktop | Desktop Applications | _Placeholder_ | | | | | desktop | Desktop Applications | _Placeholder_ | | | |
| doom | Doom | PrBoom | | No | | | doom | Doom | PrBoom | | No | |
| dos | DOS (PC) | DOSBox-Pure | DOSBox-Core,<br>DOSBox-SVN | No | | | dos | DOS (PC) | DOSBox-Pure | DOSBox-Core,<br>DOSBox-SVN,<br>VirtualXT | No | See the specific _DOS / PC_ section in the user guide |
| dragon32 | Dragon Data Dragon 32 | _Placeholder_ | | | | | dragon32 | Dragon Data Dragon 32 | MAME4droid 2024 Dragon 32 [Tape] **(Standalone)** | MAME4droid 2024 Dragon 32 [Cartridge] **(Standalone)**,<br>MAME4droid 2024 Dragon 64 [Tape] **(Standalone)**,<br>MAME4droid 2024 Dragon 64 [Cartridge] **(Standalone)** | Yes | See the specific _Dragon 32 and Tano Dragon_ section in the user guide |
| dreamcast | Sega Dreamcast | Flycast | Flycast **(Standalone)**,<br>Redream **(Standalone)** | No | In separate folder interpreted as a file, with .m3u playlist if multi-disc game | | dreamcast | Sega Dreamcast | Flycast | Flycast **(Standalone)**,<br>Redream **(Standalone)** | No | In separate folder interpreted as a file, with .m3u playlist if multi-disc game |
| easyrpg | EasyRPG Game Engine | EasyRPG | | No | | | easyrpg | EasyRPG Game Engine | EasyRPG | | No | |
| electron | Acorn Electron | MAME4droid 2024 [Tape] **(Standalone)** | MAME4droid 2024 [Diskette DFS] **(Standalone)**,<br>MAME4droid 2024 [Diskette ADFS] **(Standalone)** | Yes | Single archive, or single tape or diskette image file | | electron | Acorn Electron | MAME4droid 2024 [Tape] **(Standalone)** | MAME4droid 2024 [Diskette DFS] **(Standalone)**,<br>MAME4droid 2024 [Diskette ADFS] **(Standalone)** | Yes | Single archive, or single tape or diskette image file |
@ -677,14 +722,14 @@ The **@** symbol indicates that the emulator is _deprecated_ and will be removed
| gamecom | Tiger Electronics Game.com | MAME4droid 2024 **(Standalone)** | | Yes | Single archive or ROM file | | gamecom | Tiger Electronics Game.com | MAME4droid 2024 **(Standalone)** | | Yes | Single archive or ROM file |
| gamegear | Sega Game Gear | Genesis Plus GX | Genesis Plus GX Wide,<br>Gearsystem,<br>SMS Plus GX,<br>PicoDrive,<br>MasterGear **(Standalone)** | No | Single archive or ROM file | | gamegear | Sega Game Gear | Genesis Plus GX | Genesis Plus GX Wide,<br>Gearsystem,<br>SMS Plus GX,<br>PicoDrive,<br>MasterGear **(Standalone)** | No | Single archive or ROM file |
| gb | Nintendo Game Boy | Gambatte | SameBoy,<br>Gearboy,<br>TGB Dual,<br>DoubleCherryGB,<br>Mesen-S,<br>bsnes,<br>mGBA,<br>VBA-M,<br>GBC.emu **(Standalone)**,<br>My OldBoy! **(Standalone**),<br>Pizza Boy GBC **(Standalone)** | No | Single archive or ROM file | | gb | Nintendo Game Boy | Gambatte | SameBoy,<br>Gearboy,<br>TGB Dual,<br>DoubleCherryGB,<br>Mesen-S,<br>bsnes,<br>mGBA,<br>VBA-M,<br>GBC.emu **(Standalone)**,<br>My OldBoy! **(Standalone**),<br>Pizza Boy GBC **(Standalone)** | No | Single archive or ROM file |
| gba | Nintendo Game Boy Advance | mGBA | VBA-M,<br>VBA Next,<br>gpSP,<br>GBA.emu **(Standalone)**,<br>My Boy! **(Standalone)**,<br>Pizza Boy GBA **(Standalone)** | No | Single archive or ROM file | | gba | Nintendo Game Boy Advance | mGBA | VBA-M,<br>VBA Next,<br>gpSP,<br>GBA.emu **(Standalone)**,<br>My Boy! **(Standalone)**,<br>NooDS **(Standalone)**,<br>Pizza Boy GBA **(Standalone)** | No | Single archive or ROM file |
| gbc | Nintendo Game Boy Color | Gambatte | SameBoy,<br>Gearboy,<br>TGB Dual,<br>DoubleCherryGB,<br>Mesen-S,<br>bsnes,<br>mGBA,<br>VBA-M,<br>GBC.emu **(Standalone)**,<br>My OldBoy! **(Standalone**),<br>Pizza Boy GBC **(Standalone)** | No | Single archive or ROM file | | gbc | Nintendo Game Boy Color | Gambatte | SameBoy,<br>Gearboy,<br>TGB Dual,<br>DoubleCherryGB,<br>Mesen-S,<br>bsnes,<br>mGBA,<br>VBA-M,<br>GBC.emu **(Standalone)**,<br>My OldBoy! **(Standalone**),<br>Pizza Boy GBC **(Standalone)** | No | Single archive or ROM file |
| gc | Nintendo GameCube | Dolphin | Dolphin **(Standalone)**,<br>Dolphin MMJR **(Standalone)**,<br>Dolphin MMJR2 **(Standalone)** | No | Disc image file for single-disc games, .m3u playlist for multi-disc games | | gc | Nintendo GameCube | Dolphin | Dolphin **(Standalone)**,<br>Dolphin MMJR **(Standalone)**,<br>Dolphin MMJR2 **(Standalone)** | No | Disc image file for single-disc games, .m3u playlist for multi-disc games |
| genesis | Sega Genesis | Genesis Plus GX | Genesis Plus GX Wide,<br>PicoDrive,<br>MD.emu **(Standalone)** | No | Single archive or ROM file | | genesis | Sega Genesis | Genesis Plus GX | Genesis Plus GX Wide,<br>PicoDrive,<br>MD.emu **(Standalone)** | No | Single archive or ROM file |
| gmaster | Hartung Game Master | MAME4droid 2024 **(Standalone)** | | Yes | Single archive or ROM file | | gmaster | Hartung Game Master | MAME4droid 2024 **(Standalone)** | | Yes | Single archive or ROM file |
| gx4000 | Amstrad GX4000 | Caprice32 | CrocoDS,<br>MAME4droid 2024 **(Standalone)** | No | Single archive or ROM file | | gx4000 | Amstrad GX4000 | Caprice32 | CrocoDS,<br>MAME4droid 2024 **(Standalone)** | No | Single archive or ROM file |
| intellivision | Mattel Electronics Intellivision | FreeIntv | MAME4droid 2024 **(Standalone)** | Yes | Single archive or ROM file | | intellivision | Mattel Electronics Intellivision | FreeIntv | MAME4droid 2024 **(Standalone)** | Yes | Single archive or ROM file |
| j2me | Java 2 Micro Edition (J2ME) | SquirrelJME | | No | Single .jar file | | j2me | Java 2 Micro Edition (J2ME) | J2ME Loader **(Standalone)** | JL-Mod **(Standalone)**,<br>SquirrelJME | No | Single .jar file |
| kodi | Kodi Home Theatre Software | _Placeholder_ | | | | | kodi | Kodi Home Theatre Software | _Placeholder_ | | | |
| laserdisc | LaserDisc Games | MAME4droid 2024 **(Standalone)** | DirkSimple | Depends | See the specific _LaserDisc Games_ section in the user guide | | laserdisc | LaserDisc Games | MAME4droid 2024 **(Standalone)** | DirkSimple | Depends | See the specific _LaserDisc Games_ section in the user guide |
| lcdgames | LCD Handheld Games | Multi (MESS) | MAME4droid 2024 Local Artwork **(Standalone)**,<br>MAME4droid 2024 **(Standalone)**,<br>Handheld Electronic (GW) | No | See the specific _LCD handheld games_ section in the user guide | | lcdgames | LCD Handheld Games | Multi (MESS) | MAME4droid 2024 Local Artwork **(Standalone)**,<br>MAME4droid 2024 **(Standalone)**,<br>Handheld Electronic (GW) | No | See the specific _LCD handheld games_ section in the user guide |
@ -716,7 +761,7 @@ The **@** symbol indicates that the emulator is _deprecated_ and will be removed
| n3ds | Nintendo 3DS | Citra | Citra **(Standalone)**,<br>Citra Canary **(Standalone)**,<br>Citra MMJ **(Standalone)**,<br>Lime3DS **(Standalone)**,<br>Panda3DS **(Standalone)** | No | Single ROM file | | n3ds | Nintendo 3DS | Citra | Citra **(Standalone)**,<br>Citra Canary **(Standalone)**,<br>Citra MMJ **(Standalone)**,<br>Lime3DS **(Standalone)**,<br>Panda3DS **(Standalone)** | No | Single ROM file |
| n64 | Nintendo 64 | Mupen64Plus-Next | M64Plus FZ **(Standalone)**,<br>Mupen64Plus AE **(Standalone)**,<br>ParaLLEl N64 | No | Single archive or ROM file | | n64 | Nintendo 64 | Mupen64Plus-Next | M64Plus FZ **(Standalone)**,<br>Mupen64Plus AE **(Standalone)**,<br>ParaLLEl N64 | No | Single archive or ROM file |
| n64dd | Nintendo 64DD | Mupen64Plus-Next | M64Plus FZ **(Standalone)**,<br>Mupen64Plus AE **(Standalone)**,<br>ParaLLEl N64 | Yes | | | n64dd | Nintendo 64DD | Mupen64Plus-Next | M64Plus FZ **(Standalone)**,<br>Mupen64Plus AE **(Standalone)**,<br>ParaLLEl N64 | Yes | |
| nds | Nintendo DS | melonDS DS | melonDS,<br>melonDS **(Standalone)**,<br>melonDS Nightly **(Standalone)**,<br>DeSmuME,<br>DeSmuME 2015,<br>DraStic **(Standalone)** | No | Single archive or ROM file | | nds | Nintendo DS | melonDS DS | melonDS,<br>melonDS **(Standalone)**,<br>melonDS Nightly **(Standalone)**,<br>DeSmuME,<br>DeSmuME 2015,<br>DraStic **(Standalone)**,<br>NooDS **(Standalone)** | No | Single archive or ROM file |
| neogeo | SNK Neo Geo | FinalBurn Neo | Geolith,<br>NEO.emu **(Standalone)**,<br>MAME4droid 2024 **(Standalone)**,<br>MAME4droid **(Standalone)** | Yes | See the specific _Arcade and Neo Geo_ section in the user guide | | neogeo | SNK Neo Geo | FinalBurn Neo | Geolith,<br>NEO.emu **(Standalone)**,<br>MAME4droid 2024 **(Standalone)**,<br>MAME4droid **(Standalone)** | Yes | See the specific _Arcade and Neo Geo_ section in the user guide |
| neogeocd | SNK Neo Geo CD | NeoCD | FinalBurn Neo,<br>MAME4droid 2024 **(Standalone)** | Yes | .chd (NeoCD and MAME4droid 2024 only) or .cue file | | neogeocd | SNK Neo Geo CD | NeoCD | FinalBurn Neo,<br>MAME4droid 2024 **(Standalone)** | Yes | .chd (NeoCD and MAME4droid 2024 only) or .cue file |
| neogeocdjp | SNK Neo Geo CD [Japan] | NeoCD | FinalBurn Neo,<br>MAME4droid 2024 **(Standalone)** | Yes | .chd (NeoCD and MAME4droid 2024 only) or .cue file | | neogeocdjp | SNK Neo Geo CD [Japan] | NeoCD | FinalBurn Neo,<br>MAME4droid 2024 **(Standalone)** | Yes | .chd (NeoCD and MAME4droid 2024 only) or .cue file |
@ -728,10 +773,10 @@ The **@** symbol indicates that the emulator is _deprecated_ and will be removed
| openbor | OpenBOR Game Engine | OpenBOR **(Standalone)** | | No | See the specific _OpenBOR_ section in the User guide | | openbor | OpenBOR Game Engine | OpenBOR **(Standalone)** | | No | See the specific _OpenBOR_ section in the User guide |
| oric | Tangerine Computer Systems Oric | MAME4droid 2024 **(Standalone)** | | Yes | See the specific _Tangerine Computer Systems Oric_ section in the user guide | | oric | Tangerine Computer Systems Oric | MAME4droid 2024 **(Standalone)** | | Yes | See the specific _Tangerine Computer Systems Oric_ section in the user guide |
| palm | Palm OS | Mu | | | | | palm | Palm OS | Mu | | | |
| pc | IBM PC | DOSBox-Pure | DOSBox-Core,<br>DOSBox-SVN | No | | | pc | IBM PC | DOSBox-Pure | DOSBox-Core,<br>DOSBox-SVN,<br>VirtualXT | No | See the specific _DOS / PC_ section in the user guide |
| pc88 | NEC PC-8800 Series | QUASI88 | | Yes | | | pc88 | NEC PC-8800 Series | QUASI88 | | Yes | |
| pc98 | NEC PC-9800 Series | Neko Project II Kai | Neko Project II | | | | pc98 | NEC PC-9800 Series | Neko Project II Kai | Neko Project II | | |
| pcarcade | PC Arcade Systems | _Placeholder_ | | | | | | pcarcade | PC Arcade Systems | Winlator Cmod Glibc **(Standalone)** | Winlator Cmod PRoot **(Standalone)** | No | See the _Winlator_ section elsewhere in this document |
| pcengine | NEC PC Engine | Beetle PCE | Beetle PCE FAST,<br>Beetle SuperGrafx,<br>PCE.emu **(Standalone)** | No | Single archive or ROM file | | pcengine | NEC PC Engine | Beetle PCE | Beetle PCE FAST,<br>Beetle SuperGrafx,<br>PCE.emu **(Standalone)** | No | Single archive or ROM file |
| pcenginecd | NEC PC Engine CD | Beetle PCE | Beetle PCE FAST,<br>Beetle SuperGrafx,<br>PCE.emu **(Standalone)** | Yes | | | pcenginecd | NEC PC Engine CD | Beetle PCE | Beetle PCE FAST,<br>Beetle SuperGrafx,<br>PCE.emu **(Standalone)** | Yes | |
| pcfx | NEC PC-FX | Beetle PC-FX | | Yes | | | pcfx | NEC PC-FX | Beetle PC-FX | | Yes | |
@ -772,7 +817,7 @@ The **@** symbol indicates that the emulator is _deprecated_ and will be removed
| supracan | Funtech Super A'Can | MAME4droid 2024 **(Standalone)** | | Yes | Single archive or ROM file. You need a supracan.zip archive that contains a valid internal_68k.bin file and an empty file named umc6650.bin | | supracan | Funtech Super A'Can | MAME4droid 2024 **(Standalone)** | | Yes | Single archive or ROM file. You need a supracan.zip archive that contains a valid internal_68k.bin file and an empty file named umc6650.bin |
| switch | Nintendo Switch | Skyline **(Standalone)** | | Yes | | | switch | Nintendo Switch | Skyline **(Standalone)** | | Yes | |
| symbian | Symbian | EKA2L1 **(Standalone)** | | Yes | See the specific _Symbian and Nokia N-Gage_ section in the User guide | | symbian | Symbian | EKA2L1 **(Standalone)** | | Yes | See the specific _Symbian and Nokia N-Gage_ section in the User guide |
| tanodragon | Tano Dragon | _Placeholder_ | | | | | tanodragon | Tano Dragon | MAME4droid 2024 [Tape] **(Standalone)** | MAME4droid 2024 [Cartridge] **(Standalone)** | Yes | See the specific _Dragon 32 and Tano Dragon_ section in the user guide |
| tg16 | NEC TurboGrafx-16 | Beetle PCE | Beetle PCE FAST,<br>Beetle SuperGrafx,<br>PCE.emu **(Standalone)** | No | Single archive or ROM file | | tg16 | NEC TurboGrafx-16 | Beetle PCE | Beetle PCE FAST,<br>Beetle SuperGrafx,<br>PCE.emu **(Standalone)** | No | Single archive or ROM file |
| tg-cd | NEC TurboGrafx-CD | Beetle PCE | Beetle PCE FAST,<br>Beetle SuperGrafx,<br>PCE.emu **(Standalone)** | Yes | | | tg-cd | NEC TurboGrafx-CD | Beetle PCE | Beetle PCE FAST,<br>Beetle SuperGrafx,<br>PCE.emu **(Standalone)** | Yes | |
| ti99 | Texas Instruments TI-99 | MAME4droid 2024 **(Standalone)** | | Yes | See the specific _Texas Instruments TI-99_ section in the user guide | | ti99 | Texas Instruments TI-99 | MAME4droid 2024 **(Standalone)** | | Yes | See the specific _Texas Instruments TI-99_ section in the user guide |
@ -780,7 +825,7 @@ The **@** symbol indicates that the emulator is _deprecated_ and will be removed
| to8 | Thomson TO8 | Theodore | | | | | to8 | Thomson TO8 | Theodore | | | |
| triforce | Namco-Sega-Nintendo Triforce | _Placeholder_ | | | | | triforce | Namco-Sega-Nintendo Triforce | _Placeholder_ | | | |
| trs-80 | Tandy TRS-80 | _Placeholder_ | | | | | trs-80 | Tandy TRS-80 | _Placeholder_ | | | |
| type-x | Taito Type X | _Placeholder_ | | | | | type-x | Taito Type X | Winlator Cmod Glibc **(Standalone)** | Winlator Cmod PRoot **(Standalone)** | No | See the _Winlator_ section elsewhere in this document |
| uzebox | Uzebox Open Source Console | Uzem | | | | | uzebox | Uzebox Open Source Console | Uzem | | | |
| vectrex | GCE Vectrex | vecx | MAME4droid 2024 **(Standalone)** | Yes for MAME4droid 2024 | Single archive or ROM file | | vectrex | GCE Vectrex | vecx | MAME4droid 2024 **(Standalone)** | Yes for MAME4droid 2024 | Single archive or ROM file |
| vic20 | Commodore VIC-20 | VICE xvic | | No | Single archive or tape, cartridge or diskette image file | | vic20 | Commodore VIC-20 | VICE xvic | | No | Single archive or tape, cartridge or diskette image file |
@ -791,7 +836,7 @@ The **@** symbol indicates that the emulator is _deprecated_ and will be removed
| wasm4 | WASM-4 Fantasy Console | WASM-4 | | No | Single .wasm file | | wasm4 | WASM-4 Fantasy Console | WASM-4 | | No | Single .wasm file |
| wii | Nintendo Wii | Dolphin | Dolphin **(Standalone)**,<br>Dolphin MMJR **(Standalone)**,<br>Dolphin MMJR2 **(Standalone)** | No | | | wii | Nintendo Wii | Dolphin | Dolphin **(Standalone)**,<br>Dolphin MMJR **(Standalone)**,<br>Dolphin MMJR2 **(Standalone)** | No | |
| wiiu | Nintendo Wii U | _Placeholder_ | | | | | wiiu | Nintendo Wii U | _Placeholder_ | | | |
| windows | Microsoft Windows | _Placeholder_ | | | | | windows | Microsoft Windows | Winlator Cmod Glibc **(Standalone)** | Winlator Cmod PRoot **(Standalone)** | No | See the _Winlator_ section elsewhere in this document |
| windows3x | Microsoft Windows 3.x | DOSBox-Pure | | No | | | windows3x | Microsoft Windows 3.x | DOSBox-Pure | | No | |
| windows9x | Microsoft Windows 9x | DOSBox-Pure | | No | | | windows9x | Microsoft Windows 9x | DOSBox-Pure | | No | |
| wonderswan | Bandai WonderSwan | Beetle Cygne | Swan.emu **(Standalone)** | No | Single archive or ROM file | | wonderswan | Bandai WonderSwan | Beetle Cygne | Swan.emu **(Standalone)** | No | Single archive or ROM file |
@ -801,6 +846,6 @@ The **@** symbol indicates that the emulator is _deprecated_ and will be removed
| xbox | Microsoft Xbox | _Placeholder_ | | | | | xbox | Microsoft Xbox | _Placeholder_ | | | |
| xbox360 | Microsoft Xbox 360 | _Placeholder_ | | | | | xbox360 | Microsoft Xbox 360 | _Placeholder_ | | | |
| zmachine | Infocom Z-machine | MojoZork | | No | | | zmachine | Infocom Z-machine | MojoZork | | No | |
| zx81 | Sinclair ZX81 | EightyOne | | | | | zx81 | Sinclair ZX81 | EightyOne | | No | |
| zxnext | Sinclair ZX Spectrum Next | _Placeholder_ | | | | | zxnext | Sinclair ZX Spectrum Next | _Placeholder_ | | | |
| zxspectrum | Sinclair ZX Spectrum | Fuse | Speccy **(Standalone)** | No | Single archive or ROM file | | zxspectrum | Sinclair ZX Spectrum | Fuse | Speccy **(Standalone)** | No | Single archive or ROM file |

View file

@ -182,6 +182,8 @@ Apart from this it works as you'd expect, ES-DE will start automatically when re
If the operating system runs out of memory when a game is running it will kill ES-DE even if it's set as home app. If this happens ES-DE will reload whenever you return from the game or if you press the home button. This is simply how Android works. If the operating system runs out of memory when a game is running it will kill ES-DE even if it's set as home app. If this happens ES-DE will reload whenever you return from the game or if you press the home button. This is simply how Android works.
Also be aware that the version check that runs on app startup may not be able to complete successfully when ES-DE is set as the home app, as the device may not have network connectivity enabled yet just after rebooting.
It's generally a very good idea to import your native Android apps into ES-DE prior to setting it as the home app, this way you can easily access things like the Settings app. Note however that even if you somehow lock yourself out of the system by setting ES-DE as the home app and not having any native apps added you can still always access the Settings app via the Android notification shade. On most devices you access this by swiping down from the top of the screen. After swiping down, just select the cogwheel icon to start the Settings app. From there you can change the home app to something else than ES-DE, should you need to. It's generally a very good idea to import your native Android apps into ES-DE prior to setting it as the home app, this way you can easily access things like the Settings app. Note however that even if you somehow lock yourself out of the system by setting ES-DE as the home app and not having any native apps added you can still always access the Settings app via the Android notification shade. On most devices you access this by swiping down from the top of the screen. After swiping down, just select the cogwheel icon to start the Settings app. From there you can change the home app to something else than ES-DE, should you need to.
## Known ES-DE problems ## Known ES-DE problems
@ -332,6 +334,20 @@ After installing the emulator, open it and go to the settings tab, then choose "
http://www.arts-union.ru/node/23 http://www.arts-union.ru/node/23
### J2ME Loader
This emulator can be installed from the Play store or the F-Droid store, or it can be downloaded from their GitHub site.
https://play.google.com/store/apps/details?id=ru.playsoftware.j2meloader \
https://f-droid.org/en/packages/ru.playsoftware.j2meloader \
https://github.com/nikita36078/J2ME-Loader/releases
### JL-Mod
This emulator can be downloaded from their GitHub site.
https://github.com/woesss/JL-Mod/releases
### Lime3DS ### Lime3DS
This emulator which is forked from Citra can be downloaded from their GitHub site. This emulator which is forked from Citra can be downloaded from their GitHub site.
@ -347,6 +363,14 @@ Note that for MAME4droid 2024 there's an exception when it comes to setting up t
https://play.google.com/store/apps/details?id=com.seleuco.mame4d2024 \ https://play.google.com/store/apps/details?id=com.seleuco.mame4d2024 \
https://play.google.com/store/apps/details?id=com.seleuco.mame4droid https://play.google.com/store/apps/details?id=com.seleuco.mame4droid
Be aware that MAME4droid 2024 requires specific input configuration for some systems. For instance to navigate the mouse cursor when using touch input you'll need to got into the _Settings_ menu, then _Input_, then _Touch controller_ and change _Mode_ to _Analog Stick_.
If using a physical controller for mouse input (via the thumbstick) then you will need to map the mouse buttons to physical controller buttons. You do this via the MAME input settings. Bring up the MAME menu by pressing both thumbsticks, or by pressing the _Start_ and _Coin_ buttons on the touch overlay. Go into _Input Settings_ then _Input Assignments (this system)_ where you can assign physical buttons to the mouse buttons.
For some systems you will need to explictly set the _Start_ and _Select_ buttons in the same fashion as when configuring the mouse buttons. Otherwise you'll not be able to start any games.
There are a few more things that you may need to configure for some systems, but that's beyond the scope of this document and should be covered by the MAME emulator documentation.
### MasterGear ### MasterGear
This emulator can be installed from the Play store as a paid app. This emulator can be installed from the Play store as a paid app.
@ -387,6 +411,12 @@ Nesoid is not available on the Play store but it can be installed from the F-Dro
https://f-droid.org/en/packages/com.androidemu.nes \ https://f-droid.org/en/packages/com.androidemu.nes \
https://github.com/proninyaroslav/nesoid/releases https://github.com/proninyaroslav/nesoid/releases
### NooDS
Although NooDS is available via the Play store that version does not allow game launching from ES-DE. To get that to work instead use the version from their GitHub site. Also note that this emulator does not support launching of zipped game files.
https://github.com/Hydr8gon/NooDS/releases
### OpenBOR ### OpenBOR
Although OpenBOR is working fine on Android it's not possible to properly integrate it with a frontend, you'll instead need to install your game PAKs into the `/sdcard/OpenBOR/Paks` directory and create dummy .openbor files for your games in `ROMs/openbor` and after launching a game from ES-DE you need to manually start it from inside the OpenBOR GUI. There are more detailed setup instructions in the _OpenBOR_ section of the [User guide](USERGUIDE.md#openbor). Although OpenBOR is working fine on Android it's not possible to properly integrate it with a frontend, you'll instead need to install your game PAKs into the `/sdcard/OpenBOR/Paks` directory and create dummy .openbor files for your games in `ROMs/openbor` and after launching a game from ES-DE you need to manually start it from inside the OpenBOR GUI. There are more detailed setup instructions in the _OpenBOR_ section of the [User guide](USERGUIDE.md#openbor).
@ -484,6 +514,17 @@ This PlayStation Vita emulator can be downloaded from their GitHub site. Refer t
https://github.com/Vita3K/Vita3K-Android/releases https://github.com/Vita3K/Vita3K-Android/releases
### Winlator
In order to use Winlator to run Windows games you need to use a specific fork named _Winlator Cmod_ as mainline [Winlator](https://winlator.com/) does not offer frontend support. The Cmod fork can be downloaded from their GitHub page:\
https://github.com/coffincolors/winlator
There are two variants of the fork, Glibc and PRoot, both of which come with some pros and cons with regards to compatibility and performance. The Glibc variant is the default emulator in ES-DE, so to use PRoot instead you'll need to select its alternative emulator entry.
In addition to the official repository there are multiple Winlator builds floating around the Internet, but these have not been extensively tested with ES-DE.
It's beyond the scope of this document to describe how to install games in Winlator, but once it's done and you've created a shortcut to your game from inside the container you can export it via the _Export for Frontend_ option in the Winlator user interface. This will generate a .desktop file that you can place in the `ROMs/pcarcade`, `ROMs/type-x` or `ROMs/windows` folder and launch from ES-DE. You can alternatively set the _Frontend Export Path_ setting from inside the Winlator Settings screen to avoid the manual step of moving the .desktop file.
### Yaba Sanshiro 2 ### Yaba Sanshiro 2
This emulator can be installed from the Play store. Note that only the paid Pro version supports game launching from ES-DE. Also note that .bin/.cue files can't be launched for the time being, only .chd files seem to work. This needs to be fixed in the emulator so nothing can be done in ES-DE to work around that limitation. This emulator can be installed from the Play store. Note that only the paid Pro version supports game launching from ES-DE. Also note that .bin/.cue files can't be launched for the time being, only .chd files seem to work. This needs to be fixed in the emulator so nothing can be done in ES-DE to work around that limitation.
@ -504,6 +545,7 @@ This is clearly not a complete list of Android devices, but rather those we know
| Anbernic | RG505 | 12 | Yes | None | Limited RAM capacity for this device makes it unsuitable for demanding themes and large game collections | | Anbernic | RG505 | 12 | Yes | None | Limited RAM capacity for this device makes it unsuitable for demanding themes and large game collections |
| Anbernic | RG556 | 13 | Yes | None | | | Anbernic | RG556 | 13 | Yes | None | |
| Anbernic | RG ARC | 12 | Yes | None | LineageOS | | Anbernic | RG ARC | 12 | Yes | None | LineageOS |
| Anbernic | RG Cube | 13 | Yes | None | |
| AYANEO | Pocket Air | 12 | Yes | None | | | AYANEO | Pocket Air | 12 | Yes | None | |
| AYANEO | Pocket S | 13 | Yes | None | | | AYANEO | Pocket S | 13 | Yes | None | |
| Ayn | Odin (Base/Pro) | 10 | Yes | None | | | Ayn | Odin (Base/Pro) | 10 | Yes | None | |
@ -528,7 +570,7 @@ This is clearly not a complete list of Android devices, but rather those we know
| Huawei | MatePad 11 (2021) | 13 | Yes | None | | | Huawei | MatePad 11 (2021) | 13 | Yes | None | |
| Infinix | Zero 30 5G | 13 | Yes | None | | | Infinix | Zero 30 5G | 13 | Yes | None | |
| Kinhank | G1 | 11 | No | Unable to install | Possibly 32-bit operating system? | | Kinhank | G1 | 11 | No | Unable to install | Possibly 32-bit operating system? |
| Kinhank | Super Console X5 Pro | 12 (TV) | No | Fails at configurator/onboarding | Seems to run a custom 64-bit Android TV OS | | Kinhank | Super Console X5 Pro | 12 (TV) | No | None | Custom 64-bit Android TV OS |
| Lenovo | Legion Y700 (2022) | 12 | Yes | None | | | Lenovo | Legion Y700 (2022) | 12 | Yes | None | |
| Lenovo | Legion Y700 (2023) | 13 | Yes | None | | | Lenovo | Legion Y700 (2023) | 13 | Yes | None | |
| Lenovo | Xiaoxin Pad Pro 2021 | 11 | Yes | None | | | Lenovo | Xiaoxin Pad Pro 2021 | 11 | Yes | None | |
@ -553,6 +595,9 @@ This is clearly not a complete list of Android devices, but rather those we know
| OnePlus | Open | 14 | Yes | None | | | OnePlus | Open | 14 | Yes | None | |
| Oppo | A15 | 10 | Yes | None | | | Oppo | A15 | 10 | Yes | None | |
| Oppo | Find X5 Pro | 14 | Yes | None | | | Oppo | Find X5 Pro | 14 | Yes | None | |
| Oppo | Reno5 | 12 | Yes | None | |
| Raspberry | Pi 4/400 | 13, 14 | Yes | None | Low-power GPU so ES-DE may run a bit sluggish |
| Raspberry | Pi 5 | 15 | Yes | None | Very poor GPU performance, runs at almost double speed in Raspberry Pi OS so likely a driver issue |
| Razer | Edge | 13 | Yes | None | | | Razer | Edge | 13 | Yes | None | |
| Realme | GT2 | 12 | Yes | None | | | Realme | GT2 | 12 | Yes | None | |
| Retroid | Pocket 2+ | 11 | Yes | None | | | Retroid | Pocket 2+ | 11 | Yes | None | |
@ -615,10 +660,10 @@ The **@** symbol indicates that the emulator is _deprecated_ and will be removed
| 3do | 3DO Interactive Multiplayer | Opera | Real3DOPlayer **(Standalone)** | Yes | | | 3do | 3DO Interactive Multiplayer | Opera | Real3DOPlayer **(Standalone)** | Yes | |
| adam | Coleco Adam | MAME4droid 2024 [Diskette] **(Standalone)** | MAME4droid 2024 [Tape] **(Standalone)**,<br>MAME4droid 2024 [Cartridge] **(Standalone)**,<br>MAME4droid 2024 [Software list] **(Standalone)**,<br>ColEm **(Standalone)** | Yes for MAME4droid 2024 | | | adam | Coleco Adam | MAME4droid 2024 [Diskette] **(Standalone)** | MAME4droid 2024 [Tape] **(Standalone)**,<br>MAME4droid 2024 [Cartridge] **(Standalone)**,<br>MAME4droid 2024 [Software list] **(Standalone)**,<br>ColEm **(Standalone)** | Yes for MAME4droid 2024 | |
| ags | Adventure Game Studio Game Engine | _Placeholder_ | | | | | ags | Adventure Game Studio Game Engine | _Placeholder_ | | | |
| amiga | Commodore Amiga | PUAE | PUAE 2021 | Yes | | | amiga | Commodore Amiga | PUAE | PUAE 2021 | Yes | See the specific _Commodore Amiga and CDTV_ section in the user guide |
| amiga1200 | Commodore Amiga 1200 | PUAE | PUAE 2021 | Yes | | | amiga1200 | Commodore Amiga 1200 | PUAE | PUAE 2021 | Yes | See the specific _Commodore Amiga and CDTV_ section in the user guide |
| amiga600 | Commodore Amiga 600 | PUAE | PUAE 2021 | Yes | | | amiga600 | Commodore Amiga 600 | PUAE | PUAE 2021 | Yes | See the specific _Commodore Amiga and CDTV_ section in the user guide |
| amigacd32 | Commodore Amiga CD32 | PUAE | PUAE 2021 | Yes | | | amigacd32 | Commodore Amiga CD32 | PUAE | PUAE 2021 | Yes | See the specific _Commodore Amiga and CDTV_ section in the user guide |
| amstradcpc | Amstrad CPC | Caprice32 | CrocoDS,<br>MAME4droid 2024 **(Standalone)** | Yes for MAME4droid 2024 | Single archive or disk file | | amstradcpc | Amstrad CPC | Caprice32 | CrocoDS,<br>MAME4droid 2024 **(Standalone)** | Yes for MAME4droid 2024 | Single archive or disk file |
| android | Google Android | _Placeholder_ | | | | | android | Google Android | _Placeholder_ | | | |
| androidapps | Android Apps | _Native apps_ | | No | | | androidapps | Android Apps | _Native apps_ | | No | |
@ -628,9 +673,9 @@ The **@** symbol indicates that the emulator is _deprecated_ and will be removed
| arcade | Arcade | MAME - Current | MAME 2010,<br>MAME 2003-Plus,<br>MAME 2003,<br>MAME 2000,<br>MAME4droid 2024 **(Standalone)**,<br>MAME4droid **(Standalone)**,<br>NEO.emu **(Standalone)**,<br>FinalBurn Neo,<br>FB Alpha 2012,<br>Geolith,<br>Flycast,<br>Flycast **(Standalone)** | Depends | See the specific _Arcade and Neo Geo_ section in the user guide | | arcade | Arcade | MAME - Current | MAME 2010,<br>MAME 2003-Plus,<br>MAME 2003,<br>MAME 2000,<br>MAME4droid 2024 **(Standalone)**,<br>MAME4droid **(Standalone)**,<br>NEO.emu **(Standalone)**,<br>FinalBurn Neo,<br>FB Alpha 2012,<br>Geolith,<br>Flycast,<br>Flycast **(Standalone)** | Depends | See the specific _Arcade and Neo Geo_ section in the user guide |
| arcadia | Emerson Arcadia 2001 | DroidArcadia **(Standalone**) | MAME4droid 2024 **(Standalone)** | No | Single archive or ROM file | | arcadia | Emerson Arcadia 2001 | DroidArcadia **(Standalone**) | MAME4droid 2024 **(Standalone)** | No | Single archive or ROM file |
| archimedes | Acorn Archimedes | MAME4droid 2024 [Model A440/1] **(Standalone)** | MAME4droid 2024 [Model A3000] **(Standalone)**,<br>MAME4droid 2024 [Model A310] **(Standalone)**,<br>MAME4droid 2024 [Model A540] **(Standalone)** | Yes | | | archimedes | Acorn Archimedes | MAME4droid 2024 [Model A440/1] **(Standalone)** | MAME4droid 2024 [Model A3000] **(Standalone)**,<br>MAME4droid 2024 [Model A310] **(Standalone)**,<br>MAME4droid 2024 [Model A540] **(Standalone)** | Yes | |
| arduboy | Arduboy Miniature Game System | Arduous | | No | Single archive or .hex file | | arduboy | Arduboy Miniature Game System | Arduous | Ardens | No | Single archive or .hex file |
| astrocde | Bally Astrocade | MAME4droid 2024 **(Standalone)** | | Yes | Single archive or ROM file | | astrocde | Bally Astrocade | MAME4droid 2024 **(Standalone)** | | Yes | Single archive or ROM file |
| atari2600 | Atari 2600 | Stella | Stella 2014,<br>2600.emu **(Standalone)** | No | Single archive or ROM file | | atari2600 | Atari 2600 | Stella | Stella 2014,<br>Stella 2023,<br>2600.emu **(Standalone)** | No | Single archive or ROM file |
| atari5200 | Atari 5200 | a5200 | Atari800 | Yes | Single archive or ROM file | | atari5200 | Atari 5200 | a5200 | Atari800 | Yes | Single archive or ROM file |
| atari7800 | Atari 7800 ProSystem | ProSystem | MAME4droid 2024 **(Standalone)** | Yes | Single archive or ROM file | | atari7800 | Atari 7800 ProSystem | ProSystem | MAME4droid 2024 **(Standalone)** | Yes | Single archive or ROM file |
| atari800 | Atari 800 | Atari800 | | Yes | | | atari800 | Atari 800 | Atari800 | | Yes | |
@ -657,8 +702,8 @@ The **@** symbol indicates that the emulator is _deprecated_ and will be removed
| daphne | Daphne Arcade LaserDisc Emulator | MAME4droid 2024 **(Standalone)** | DirkSimple | Depends | See the specific _LaserDisc Games_ section in the user guide | | daphne | Daphne Arcade LaserDisc Emulator | MAME4droid 2024 **(Standalone)** | DirkSimple | Depends | See the specific _LaserDisc Games_ section in the user guide |
| desktop | Desktop Applications | _Placeholder_ | | | | | desktop | Desktop Applications | _Placeholder_ | | | |
| doom | Doom | PrBoom | | No | | | doom | Doom | PrBoom | | No | |
| dos | DOS (PC) | DOSBox-Pure | DOSBox-Core,<br>DOSBox-SVN | No | | | dos | DOS (PC) | DOSBox-Pure | DOSBox-Core,<br>DOSBox-SVN,<br>VirtualXT | No | See the specific _DOS / PC_ section in the user guide |
| dragon32 | Dragon Data Dragon 32 | _Placeholder_ | | | | | dragon32 | Dragon Data Dragon 32 | MAME4droid 2024 Dragon 32 [Tape] **(Standalone)** | MAME4droid 2024 Dragon 32 [Cartridge] **(Standalone)**,<br>MAME4droid 2024 Dragon 64 [Tape] **(Standalone)**,<br>MAME4droid 2024 Dragon 64 [Cartridge] **(Standalone)** | Yes | See the specific _Dragon 32 and Tano Dragon_ section in the user guide |
| dreamcast | Sega Dreamcast | Flycast | Flycast **(Standalone)**,<br>Redream **(Standalone)** | No | In separate folder interpreted as a file, with .m3u playlist if multi-disc game | | dreamcast | Sega Dreamcast | Flycast | Flycast **(Standalone)**,<br>Redream **(Standalone)** | No | In separate folder interpreted as a file, with .m3u playlist if multi-disc game |
| easyrpg | EasyRPG Game Engine | EasyRPG | | No | | | easyrpg | EasyRPG Game Engine | EasyRPG | | No | |
| electron | Acorn Electron | MAME4droid 2024 [Tape] **(Standalone)** | MAME4droid 2024 [Diskette DFS] **(Standalone)**,<br>MAME4droid 2024 [Diskette ADFS] **(Standalone)** | Yes | Single archive, or single tape or diskette image file | | electron | Acorn Electron | MAME4droid 2024 [Tape] **(Standalone)** | MAME4droid 2024 [Diskette DFS] **(Standalone)**,<br>MAME4droid 2024 [Diskette ADFS] **(Standalone)** | Yes | Single archive, or single tape or diskette image file |
@ -677,14 +722,14 @@ The **@** symbol indicates that the emulator is _deprecated_ and will be removed
| gamecom | Tiger Electronics Game.com | MAME4droid 2024 **(Standalone)** | | Yes | Single archive or ROM file | | gamecom | Tiger Electronics Game.com | MAME4droid 2024 **(Standalone)** | | Yes | Single archive or ROM file |
| gamegear | Sega Game Gear | Genesis Plus GX | Genesis Plus GX Wide,<br>Gearsystem,<br>SMS Plus GX,<br>PicoDrive,<br>MasterGear **(Standalone)** | No | Single archive or ROM file | | gamegear | Sega Game Gear | Genesis Plus GX | Genesis Plus GX Wide,<br>Gearsystem,<br>SMS Plus GX,<br>PicoDrive,<br>MasterGear **(Standalone)** | No | Single archive or ROM file |
| gb | Nintendo Game Boy | Gambatte | SameBoy,<br>Gearboy,<br>TGB Dual,<br>DoubleCherryGB,<br>Mesen-S,<br>bsnes,<br>mGBA,<br>VBA-M,<br>GBC.emu **(Standalone)**,<br>My OldBoy! **(Standalone**),<br>Pizza Boy GBC **(Standalone)** | No | Single archive or ROM file | | gb | Nintendo Game Boy | Gambatte | SameBoy,<br>Gearboy,<br>TGB Dual,<br>DoubleCherryGB,<br>Mesen-S,<br>bsnes,<br>mGBA,<br>VBA-M,<br>GBC.emu **(Standalone)**,<br>My OldBoy! **(Standalone**),<br>Pizza Boy GBC **(Standalone)** | No | Single archive or ROM file |
| gba | Nintendo Game Boy Advance | mGBA | VBA-M,<br>VBA Next,<br>gpSP,<br>GBA.emu **(Standalone)**,<br>My Boy! **(Standalone)**,<br>Pizza Boy GBA **(Standalone)** | No | Single archive or ROM file | | gba | Nintendo Game Boy Advance | mGBA | VBA-M,<br>VBA Next,<br>gpSP,<br>GBA.emu **(Standalone)**,<br>My Boy! **(Standalone)**,<br>NooDS **(Standalone)**,<br>Pizza Boy GBA **(Standalone)** | No | Single archive or ROM file |
| gbc | Nintendo Game Boy Color | Gambatte | SameBoy,<br>Gearboy,<br>TGB Dual,<br>DoubleCherryGB,<br>Mesen-S,<br>bsnes,<br>mGBA,<br>VBA-M,<br>GBC.emu **(Standalone)**,<br>My OldBoy! **(Standalone**),<br>Pizza Boy GBC **(Standalone)** | No | Single archive or ROM file | | gbc | Nintendo Game Boy Color | Gambatte | SameBoy,<br>Gearboy,<br>TGB Dual,<br>DoubleCherryGB,<br>Mesen-S,<br>bsnes,<br>mGBA,<br>VBA-M,<br>GBC.emu **(Standalone)**,<br>My OldBoy! **(Standalone**),<br>Pizza Boy GBC **(Standalone)** | No | Single archive or ROM file |
| gc | Nintendo GameCube | Dolphin | Dolphin **(Standalone)**,<br>Dolphin MMJR **(Standalone)**,<br>Dolphin MMJR2 **(Standalone)** | No | Disc image file for single-disc games, .m3u playlist for multi-disc games | | gc | Nintendo GameCube | Dolphin | Dolphin **(Standalone)**,<br>Dolphin MMJR **(Standalone)**,<br>Dolphin MMJR2 **(Standalone)** | No | Disc image file for single-disc games, .m3u playlist for multi-disc games |
| genesis | Sega Genesis | Genesis Plus GX | Genesis Plus GX Wide,<br>PicoDrive,<br>MD.emu **(Standalone)** | No | Single archive or ROM file | | genesis | Sega Genesis | Genesis Plus GX | Genesis Plus GX Wide,<br>PicoDrive,<br>MD.emu **(Standalone)** | No | Single archive or ROM file |
| gmaster | Hartung Game Master | MAME4droid 2024 **(Standalone)** | | Yes | Single archive or ROM file | | gmaster | Hartung Game Master | MAME4droid 2024 **(Standalone)** | | Yes | Single archive or ROM file |
| gx4000 | Amstrad GX4000 | Caprice32 | CrocoDS,<br>MAME4droid 2024 **(Standalone)** | No | Single archive or ROM file | | gx4000 | Amstrad GX4000 | Caprice32 | CrocoDS,<br>MAME4droid 2024 **(Standalone)** | No | Single archive or ROM file |
| intellivision | Mattel Electronics Intellivision | FreeIntv | MAME4droid 2024 **(Standalone)** | Yes | Single archive or ROM file | | intellivision | Mattel Electronics Intellivision | FreeIntv | MAME4droid 2024 **(Standalone)** | Yes | Single archive or ROM file |
| j2me | Java 2 Micro Edition (J2ME) | SquirrelJME | | No | Single .jar file | | j2me | Java 2 Micro Edition (J2ME) | J2ME Loader **(Standalone)** | JL-Mod **(Standalone)**,<br>SquirrelJME | No | Single .jar file |
| kodi | Kodi Home Theatre Software | _Placeholder_ | | | | | kodi | Kodi Home Theatre Software | _Placeholder_ | | | |
| laserdisc | LaserDisc Games | MAME4droid 2024 **(Standalone)** | DirkSimple | Depends | See the specific _LaserDisc Games_ section in the user guide | | laserdisc | LaserDisc Games | MAME4droid 2024 **(Standalone)** | DirkSimple | Depends | See the specific _LaserDisc Games_ section in the user guide |
| lcdgames | LCD Handheld Games | Multi (MESS) | MAME4droid 2024 Local Artwork **(Standalone)**,<br>MAME4droid 2024 **(Standalone)**,<br>Handheld Electronic (GW) | No | See the specific _LCD handheld games_ section in the user guide | | lcdgames | LCD Handheld Games | Multi (MESS) | MAME4droid 2024 Local Artwork **(Standalone)**,<br>MAME4droid 2024 **(Standalone)**,<br>Handheld Electronic (GW) | No | See the specific _LCD handheld games_ section in the user guide |
@ -716,7 +761,7 @@ The **@** symbol indicates that the emulator is _deprecated_ and will be removed
| n3ds | Nintendo 3DS | Citra | Citra **(Standalone)**,<br>Citra Canary **(Standalone)**,<br>Citra MMJ **(Standalone)**,<br>Lime3DS **(Standalone)**,<br>Panda3DS **(Standalone)** | No | Single ROM file | | n3ds | Nintendo 3DS | Citra | Citra **(Standalone)**,<br>Citra Canary **(Standalone)**,<br>Citra MMJ **(Standalone)**,<br>Lime3DS **(Standalone)**,<br>Panda3DS **(Standalone)** | No | Single ROM file |
| n64 | Nintendo 64 | Mupen64Plus-Next | M64Plus FZ **(Standalone)**,<br>Mupen64Plus AE **(Standalone)**,<br>ParaLLEl N64 | No | Single archive or ROM file | | n64 | Nintendo 64 | Mupen64Plus-Next | M64Plus FZ **(Standalone)**,<br>Mupen64Plus AE **(Standalone)**,<br>ParaLLEl N64 | No | Single archive or ROM file |
| n64dd | Nintendo 64DD | Mupen64Plus-Next | M64Plus FZ **(Standalone)**,<br>Mupen64Plus AE **(Standalone)**,<br>ParaLLEl N64 | Yes | | | n64dd | Nintendo 64DD | Mupen64Plus-Next | M64Plus FZ **(Standalone)**,<br>Mupen64Plus AE **(Standalone)**,<br>ParaLLEl N64 | Yes | |
| nds | Nintendo DS | melonDS DS | melonDS,<br>melonDS **(Standalone)**,<br>melonDS Nightly **(Standalone)**,<br>DeSmuME,<br>DeSmuME 2015,<br>DraStic **(Standalone)** | No | Single archive or ROM file | | nds | Nintendo DS | melonDS DS | melonDS,<br>melonDS **(Standalone)**,<br>melonDS Nightly **(Standalone)**,<br>DeSmuME,<br>DeSmuME 2015,<br>DraStic **(Standalone)**,<br>NooDS **(Standalone)** | No | Single archive or ROM file |
| neogeo | SNK Neo Geo | FinalBurn Neo | Geolith,<br>NEO.emu **(Standalone)**,<br>MAME4droid 2024 **(Standalone)**,<br>MAME4droid **(Standalone)** | Yes | See the specific _Arcade and Neo Geo_ section in the user guide | | neogeo | SNK Neo Geo | FinalBurn Neo | Geolith,<br>NEO.emu **(Standalone)**,<br>MAME4droid 2024 **(Standalone)**,<br>MAME4droid **(Standalone)** | Yes | See the specific _Arcade and Neo Geo_ section in the user guide |
| neogeocd | SNK Neo Geo CD | NeoCD | FinalBurn Neo,<br>MAME4droid 2024 **(Standalone)** | Yes | .chd (NeoCD and MAME4droid 2024 only) or .cue file | | neogeocd | SNK Neo Geo CD | NeoCD | FinalBurn Neo,<br>MAME4droid 2024 **(Standalone)** | Yes | .chd (NeoCD and MAME4droid 2024 only) or .cue file |
| neogeocdjp | SNK Neo Geo CD [Japan] | NeoCD | FinalBurn Neo,<br>MAME4droid 2024 **(Standalone)** | Yes | .chd (NeoCD and MAME4droid 2024 only) or .cue file | | neogeocdjp | SNK Neo Geo CD [Japan] | NeoCD | FinalBurn Neo,<br>MAME4droid 2024 **(Standalone)** | Yes | .chd (NeoCD and MAME4droid 2024 only) or .cue file |
@ -728,10 +773,10 @@ The **@** symbol indicates that the emulator is _deprecated_ and will be removed
| openbor | OpenBOR Game Engine | OpenBOR **(Standalone)** | | No | See the specific _OpenBOR_ section in the User guide | | openbor | OpenBOR Game Engine | OpenBOR **(Standalone)** | | No | See the specific _OpenBOR_ section in the User guide |
| oric | Tangerine Computer Systems Oric | MAME4droid 2024 **(Standalone)** | | Yes | See the specific _Tangerine Computer Systems Oric_ section in the user guide | | oric | Tangerine Computer Systems Oric | MAME4droid 2024 **(Standalone)** | | Yes | See the specific _Tangerine Computer Systems Oric_ section in the user guide |
| palm | Palm OS | Mu | | | | | palm | Palm OS | Mu | | | |
| pc | IBM PC | DOSBox-Pure | DOSBox-Core,<br>DOSBox-SVN | No | | | pc | IBM PC | DOSBox-Pure | DOSBox-Core,<br>DOSBox-SVN,<br>VirtualXT | No | See the specific _DOS / PC_ section in the user guide |
| pc88 | NEC PC-8800 Series | QUASI88 | | Yes | | | pc88 | NEC PC-8800 Series | QUASI88 | | Yes | |
| pc98 | NEC PC-9800 Series | Neko Project II Kai | Neko Project II | | | | pc98 | NEC PC-9800 Series | Neko Project II Kai | Neko Project II | | |
| pcarcade | PC Arcade Systems | _Placeholder_ | | | | | | pcarcade | PC Arcade Systems | Winlator Cmod Glibc **(Standalone)** | Winlator Cmod PRoot **(Standalone)** | No | See the _Winlator_ section elsewhere in this document |
| pcengine | NEC PC Engine | Beetle PCE | Beetle PCE FAST,<br>Beetle SuperGrafx,<br>PCE.emu **(Standalone)** | No | Single archive or ROM file | | pcengine | NEC PC Engine | Beetle PCE | Beetle PCE FAST,<br>Beetle SuperGrafx,<br>PCE.emu **(Standalone)** | No | Single archive or ROM file |
| pcenginecd | NEC PC Engine CD | Beetle PCE | Beetle PCE FAST,<br>Beetle SuperGrafx,<br>PCE.emu **(Standalone)** | Yes | | | pcenginecd | NEC PC Engine CD | Beetle PCE | Beetle PCE FAST,<br>Beetle SuperGrafx,<br>PCE.emu **(Standalone)** | Yes | |
| pcfx | NEC PC-FX | Beetle PC-FX | | Yes | | | pcfx | NEC PC-FX | Beetle PC-FX | | Yes | |
@ -772,7 +817,7 @@ The **@** symbol indicates that the emulator is _deprecated_ and will be removed
| supracan | Funtech Super A'Can | MAME4droid 2024 **(Standalone)** | | Yes | Single archive or ROM file. You need a supracan.zip archive that contains a valid internal_68k.bin file and an empty file named umc6650.bin | | supracan | Funtech Super A'Can | MAME4droid 2024 **(Standalone)** | | Yes | Single archive or ROM file. You need a supracan.zip archive that contains a valid internal_68k.bin file and an empty file named umc6650.bin |
| switch | Nintendo Switch | Skyline **(Standalone)** | | Yes | | | switch | Nintendo Switch | Skyline **(Standalone)** | | Yes | |
| symbian | Symbian | EKA2L1 **(Standalone)** | | Yes | See the specific _Symbian and Nokia N-Gage_ section in the User guide | | symbian | Symbian | EKA2L1 **(Standalone)** | | Yes | See the specific _Symbian and Nokia N-Gage_ section in the User guide |
| tanodragon | Tano Dragon | _Placeholder_ | | | | | tanodragon | Tano Dragon | MAME4droid 2024 [Tape] **(Standalone)** | MAME4droid 2024 [Cartridge] **(Standalone)** | Yes | See the specific _Dragon 32 and Tano Dragon_ section in the user guide |
| tg16 | NEC TurboGrafx-16 | Beetle PCE | Beetle PCE FAST,<br>Beetle SuperGrafx,<br>PCE.emu **(Standalone)** | No | Single archive or ROM file | | tg16 | NEC TurboGrafx-16 | Beetle PCE | Beetle PCE FAST,<br>Beetle SuperGrafx,<br>PCE.emu **(Standalone)** | No | Single archive or ROM file |
| tg-cd | NEC TurboGrafx-CD | Beetle PCE | Beetle PCE FAST,<br>Beetle SuperGrafx,<br>PCE.emu **(Standalone)** | Yes | | | tg-cd | NEC TurboGrafx-CD | Beetle PCE | Beetle PCE FAST,<br>Beetle SuperGrafx,<br>PCE.emu **(Standalone)** | Yes | |
| ti99 | Texas Instruments TI-99 | MAME4droid 2024 **(Standalone)** | | Yes | See the specific _Texas Instruments TI-99_ section in the user guide | | ti99 | Texas Instruments TI-99 | MAME4droid 2024 **(Standalone)** | | Yes | See the specific _Texas Instruments TI-99_ section in the user guide |
@ -780,7 +825,7 @@ The **@** symbol indicates that the emulator is _deprecated_ and will be removed
| to8 | Thomson TO8 | Theodore | | | | | to8 | Thomson TO8 | Theodore | | | |
| triforce | Namco-Sega-Nintendo Triforce | _Placeholder_ | | | | | triforce | Namco-Sega-Nintendo Triforce | _Placeholder_ | | | |
| trs-80 | Tandy TRS-80 | _Placeholder_ | | | | | trs-80 | Tandy TRS-80 | _Placeholder_ | | | |
| type-x | Taito Type X | _Placeholder_ | | | | | type-x | Taito Type X | Winlator Cmod Glibc **(Standalone)** | Winlator Cmod PRoot **(Standalone)** | No | See the _Winlator_ section elsewhere in this document |
| uzebox | Uzebox Open Source Console | Uzem | | | | | uzebox | Uzebox Open Source Console | Uzem | | | |
| vectrex | GCE Vectrex | vecx | MAME4droid 2024 **(Standalone)** | Yes for MAME4droid 2024 | Single archive or ROM file | | vectrex | GCE Vectrex | vecx | MAME4droid 2024 **(Standalone)** | Yes for MAME4droid 2024 | Single archive or ROM file |
| vic20 | Commodore VIC-20 | VICE xvic | | No | Single archive or tape, cartridge or diskette image file | | vic20 | Commodore VIC-20 | VICE xvic | | No | Single archive or tape, cartridge or diskette image file |
@ -791,7 +836,7 @@ The **@** symbol indicates that the emulator is _deprecated_ and will be removed
| wasm4 | WASM-4 Fantasy Console | WASM-4 | | No | Single .wasm file | | wasm4 | WASM-4 Fantasy Console | WASM-4 | | No | Single .wasm file |
| wii | Nintendo Wii | Dolphin | Dolphin **(Standalone)**,<br>Dolphin MMJR **(Standalone)**,<br>Dolphin MMJR2 **(Standalone)** | No | | | wii | Nintendo Wii | Dolphin | Dolphin **(Standalone)**,<br>Dolphin MMJR **(Standalone)**,<br>Dolphin MMJR2 **(Standalone)** | No | |
| wiiu | Nintendo Wii U | _Placeholder_ | | | | | wiiu | Nintendo Wii U | _Placeholder_ | | | |
| windows | Microsoft Windows | _Placeholder_ | | | | | windows | Microsoft Windows | Winlator Cmod Glibc **(Standalone)** | Winlator Cmod PRoot **(Standalone)** | No | See the _Winlator_ section elsewhere in this document |
| windows3x | Microsoft Windows 3.x | DOSBox-Pure | | No | | | windows3x | Microsoft Windows 3.x | DOSBox-Pure | | No | |
| windows9x | Microsoft Windows 9x | DOSBox-Pure | | No | | | windows9x | Microsoft Windows 9x | DOSBox-Pure | | No | |
| wonderswan | Bandai WonderSwan | Beetle Cygne | Swan.emu **(Standalone)** | No | Single archive or ROM file | | wonderswan | Bandai WonderSwan | Beetle Cygne | Swan.emu **(Standalone)** | No | Single archive or ROM file |
@ -801,6 +846,6 @@ The **@** symbol indicates that the emulator is _deprecated_ and will be removed
| xbox | Microsoft Xbox | _Placeholder_ | | | | | xbox | Microsoft Xbox | _Placeholder_ | | | |
| xbox360 | Microsoft Xbox 360 | _Placeholder_ | | | | | xbox360 | Microsoft Xbox 360 | _Placeholder_ | | | |
| zmachine | Infocom Z-machine | MojoZork | | No | | | zmachine | Infocom Z-machine | MojoZork | | No | |
| zx81 | Sinclair ZX81 | EightyOne | | | | | zx81 | Sinclair ZX81 | EightyOne | | No | |
| zxnext | Sinclair ZX Spectrum Next | _Placeholder_ | | | | | zxnext | Sinclair ZX Spectrum Next | _Placeholder_ | | | |
| zxspectrum | Sinclair ZX Spectrum | Fuse | Speccy **(Standalone)** | No | Single archive or ROM file | | zxspectrum | Sinclair ZX Spectrum | Fuse | Speccy **(Standalone)** | No | Single archive or ROM file |

View file

@ -1,5 +1,124 @@
# ES-DE Frontend - Changelog # ES-DE Frontend - Changelog
## Version 3.1.1 (in development)
**Release date:** TBD
### Release overview
3.1 maintenance release.
### Detailed list of changes
* Added translations for German (de_DE)
* Added translations for Korean (ko_KR)
* Decreased the memory footprint under some circumstances by completely freeing up video player resources after finishing view transitions
* Added the Nanum Square Neo Korean font
### Bug fixes
* Attempting to view media for a game that had no downloaded media paused the playback of all static theme videos
* Newly entered ScreenScraper username and password values were positioned incorrectly vertically in the account settings menu
## Version 3.1.0 / 3.1.0-32
**Release date:** 2024-09-13
### Release overview
This release brings full localization support and includes translations to ten new languages. More specifically these are Spanish (Spain), French, Italian, Polish, Portuguese (Brazil), Romanian, Russian, Swedish, Japanese and Simplified Chinese. More languages will follow in future releases.
As part of the localization work there have been substantial changes made to the application, for instance to the text rendering which has been improved with proper text shaping using the HarfBuzz library. Case mappings and boundary analysis are now also performed by the ICU library rather than using inaccurate built-in functions as was previously the case.
As for other notable improvements, entering the wrong ScreenScraper credentials will now display an error popup during scraping, specific subdirectories inside the system folders can be excluded from getting loaded, and the starting time for the screensaver has been greatly reduced on devices with poor disk I/O performance, such as Android. A number of new systems have also been enabled on Android, which brings game system support for this platform one step closer to being on par with the desktop ports.
And talking of ports, this release also brings experimental support for the Haiku operating system.
See the full list below for all changes and bug fixes.
### Detailed list of changes
* Added localization support
* Added text shaping support using the HarfBuzz library
* Replaced all built-in Unicode case conversion logic and lookup tables with facilities from the ICU library
* Added translations for English (United Kingdom) (en_GB)
* Added translations for Spanish (Spain) (es_ES)
* Added translations for French (fr_FR)
* Added translations for Italian (it_IT)
* Added translations for Polish (pl_PL)
* Added translations for Portuguese (Brazil) (pt_BR)
* Added translations for Romanian (ro_RO)
* Added translations for Russian (ru_RU)
* Added translations for Swedish (sv_SE)
* Added translations for Japanese (ja_JP)
* Added translations for Simplified Chinese (zh_CN)
* Dramatically reduced the start time for the video and slideshow screensavers on devices with poor disk I/O performance (like Android)
* Added support for skipping game system subdirectories scanning on startup (by using noload.txt files)
* Added an error popup if incorrect credentials (username and password) are used when scraping using ScreenScraper
* Added a "Dark and red" menu color scheme to improve perceived contrast on low-contrast displays
* (Android) Added support for the PC Arcade Systems (pcarcade) game system using the Winlator emulator
* (Android) Added support for the Taito Type X (type-x) game system using the Winlator emulator
* (Android) Added support for the Microsoft Windows (windows) game system using the Winlator emulator
* (Android) Added support for the Dragon Data Dragon 32 (dragon32) game system
* (Android) Added support for the Tano Dragon (tanodragon) game system
* (Android) Added a new default find rule entry for Flycast as its application ID has been changed
* (Android) Changed the find rule for Ruffle to make game launching work again after a code change in the emulator
* (Android) Changed ePSXe to use %ROM% instead of %ROMSAF% as the latter caused game launching to fail on some devices
* (Android) Added J2ME Loader standalone as the default emulator for the j2me system
* (Android) Added JL-Mod standalone as an alternative emulator for the j2me system
* (Android) Added support for launching individual games directly with EKA2L1 for the symbian system
* Added jgenesis as an alternative emulator for the famicom, gamegear, gb, gbc, genesis, mastersystem and megacd systems on Linux and Windows
* Added jgenesis as an alternative emulator for the megacdjp, megadrive, megadrivejp, nes, segacd, sfc, snes and snesna systems on Linux and Windows
* Added NooDS standalone as an alternative emulator for the gba and nds systems on Android, Linux and Windows
* Added izapple2 standalone as an alternative emulator for the apple2 system on Linux and Windows
* Added MAME standalone as the default emulator for the dragon32 and tanodragon systems on Linux, macOS and Windows
* Added the .7z and .zip file extensions to the dragon32 and tanodragon systems
* Added the Stella 2023 RetroArch core as an alternative emulator for the atari2600 system
* Added the VirtualXT RetroArch core as an alternative emulator for the dos and pc systems
* Added the .img file extension to the dos and pc systems
* Added the Ardens RetroArch core as an alternative emulator for the arduboy system
* Added the .arduboy file extension to the arduboy system
* Added support for the new Lime3DS binary names on Linux, macOS and Windows
* (Windows) Added "Shortcut" as an alternative emulator for the switch system
* (Windows) Added the .lnk file extension to the switch system
* (Linux) Added a systempath find rule for the ppsspp binary name for the PPSSPP emulator
* (Linux) Added a systempath find rule for the ryujinx binary name for the Ryujinx emulator
* (Android) Added support for using the %BASENAME% variable with the %EXTRA% and %EXTRAARRAY% variables
* Text within parantheses is no longer stripped out from the game name popup when adding or removing games from custom collections
* Renamed the "Menu opening effect" setting in the UI settings menu to "Menu opening animation"
* (linear-es-de) Added translations for all supported languages
* (modern-es-de) Added translations for all supported languages
* (slate-es-de) Added partial translations for all supported languages
* Added a "backgroundMargins" property to the datetime element
* Added a "backgroundCornerRadius" property to the datetime element
* Added a check for whether a text element has a width defined when the container property is set
* Added support for including theme files from within the colorScheme and fontSize tag pairs
* Game files with only an extension and no filename will now get skipped on application startup
* StringUtil::toCapitalized() will now capitalize text more accurately by using ICU boundary analysis
* Removed some obsolete code from DateTimeEditComponent
* Added the libintl library as a dependency
* Added the HarfBuzz library as a dependency
* Added the ICU library as a dependency
* Refactored large parts of the text and font code
* Added experimental support for the Haiku operating system
* Added some improvements for building and running on FreeBSD
* Removed support for NetBSD and OpenBSD
* Updated SDL to 2.30.7 on Android, Windows, macOS and the Linux AppImage builds
* Added some extra compiler checking options when building with AddressSanitizer or UndefinedBehaviorSanitizer
* Updated the MAME index files to include ROMs up to MAME version 0.269
* Bundled the July 2024 release of the Mozilla TLS/SSL certificates
### Bug fixes
* When returning from a game the helpsystem was sometimes using the dimmed theme properties
* The StringUtil::toCapitalized() function didn't correctly capitalize multi-byte Unicode characters
* (Windows) Video textures were sometimes not sized and aligned correctly horizontally
* The theme engine game count text was capitalized by default instead of being set as lowercase
* Text elements defined as gamecount using the systemdata property could not scroll horizontally
* (linear-es-de) The system logo and carousel icon for saturnjp was incorrectly showing the western variant
* (modern-es-de) The carousel icon for saturnjp was incorrectly showing the western variant
* There was a typo where the 32:9 aspect ratio was referred to as 32:0
## Version 3.0.3 / 3.0.3-26 ## Version 3.0.3 / 3.0.3-26
**Release date:** 2024-06-14 **Release date:** 2024-06-14

View file

@ -0,0 +1,203 @@
# Copyright (c) 2012, Intel Corporation
# Copyright (c) 2019 Sony Interactive Entertainment Inc.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Intel Corporation nor the names of its contributors may
# be used to endorse or promote products derived from this software without
# specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
# Try to find Harfbuzz include and library directories.
#
# After successful discovery, this will set for inclusion where needed:
# HarfBuzz_INCLUDE_DIRS - containg the HarfBuzz headers
# HarfBuzz_LIBRARIES - containg the HarfBuzz library
#[=======================================================================[.rst:
FindHarfBuzz
--------------
Find HarfBuzz headers and libraries.
Imported Targets
^^^^^^^^^^^^^^^^
``HarfBuzz::HarfBuzz``
The HarfBuzz library, if found.
``HarfBuzz::ICU``
The HarfBuzz ICU library, if found.
Result Variables
^^^^^^^^^^^^^^^^
This will define the following variables in your project:
``HarfBuzz_FOUND``
true if (the requested version of) HarfBuzz is available.
``HarfBuzz_VERSION``
the version of HarfBuzz.
``HarfBuzz_LIBRARIES``
the libraries to link against to use HarfBuzz.
``HarfBuzz_INCLUDE_DIRS``
where to find the HarfBuzz headers.
``HarfBuzz_COMPILE_OPTIONS``
this should be passed to target_compile_options(), if the
target is not used for linking
#]=======================================================================]
find_package(PkgConfig QUIET)
pkg_check_modules(PC_HARFBUZZ QUIET harfbuzz)
set(HarfBuzz_COMPILE_OPTIONS ${PC_HARFBUZZ_CFLAGS_OTHER})
set(HarfBuzz_VERSION ${PC_HARFBUZZ_CFLAGS_VERSION})
find_path(HarfBuzz_INCLUDE_DIR
NAMES hb.h
HINTS ${PC_HARFBUZZ_INCLUDEDIR} ${PC_HARFBUZZ_INCLUDE_DIRS}
PATH_SUFFIXES harfbuzz
)
find_library(HarfBuzz_LIBRARY
NAMES ${HarfBuzz_NAMES} harfbuzz
HINTS ${PC_HARFBUZZ_LIBDIR} ${PC_HARFBUZZ_LIBRARY_DIRS}
)
if (HarfBuzz_INCLUDE_DIR AND NOT HarfBuzz_VERSION)
if (EXISTS "${HarfBuzz_INCLUDE_DIR}/hb-version.h")
file(READ "${HarfBuzz_INCLUDE_DIR}/hb-version.h" _harfbuzz_version_content)
string(REGEX MATCH "#define +HB_VERSION_STRING +\"([0-9]+\\.[0-9]+\\.[0-9]+)\"" _dummy "${_harfbuzz_version_content}")
set(HarfBuzz_VERSION "${CMAKE_MATCH_1}")
endif ()
endif ()
if ("${HarfBuzz_FIND_VERSION}" VERSION_GREATER "${HarfBuzz_VERSION}")
if (HarfBuzz_FIND_REQUIRED)
message(FATAL_ERROR
"Required version (" ${HarfBuzz_FIND_VERSION} ")"
" is higher than found version (" ${HarfBuzz_VERSION} ")")
else ()
message(WARNING
"Required version (" ${HarfBuzz_FIND_VERSION} ")"
" is higher than found version (" ${HarfBuzz_VERSION} ")")
unset(HarfBuzz_VERSION)
unset(HarfBuzz_INCLUDE_DIRS)
unset(HarfBuzz_LIBRARIES)
return ()
endif ()
endif ()
# Find components
if (HarfBuzz_INCLUDE_DIR AND HarfBuzz_LIBRARY)
set(_HarfBuzz_REQUIRED_LIBS_FOUND ON)
set(HarfBuzz_LIBS_FOUND "HarfBuzz (required): ${HarfBuzz_LIBRARY}")
else ()
set(_HarfBuzz_REQUIRED_LIBS_FOUND OFF)
set(HarfBuzz_LIBS_NOT_FOUND "HarfBuzz (required)")
endif ()
if (NOT CMAKE_VERSION VERSION_LESS 3.3)
if ("ICU" IN_LIST HarfBuzz_FIND_COMPONENTS)
pkg_check_modules(PC_HARFBUZZ_ICU QUIET harfbuzz-icu)
set(HarfBuzz_ICU_COMPILE_OPTIONS ${PC_HARFBUZZ_ICU_CFLAGS_OTHER})
find_path(HarfBuzz_ICU_INCLUDE_DIR
NAMES hb-icu.h
HINTS ${PC_HARFBUZZ_ICU_INCLUDEDIR} ${PC_HARFBUZZ_ICU_INCLUDE_DIRS}
PATH_SUFFIXES harfbuzz
)
find_library(HarfBuzz_ICU_LIBRARY
NAMES ${HarfBuzz_ICU_NAMES} harfbuzz-icu
HINTS ${PC_HARFBUZZ_ICU_LIBDIR} ${PC_HARFBUZZ_ICU_LIBRARY_DIRS}
)
if (HarfBuzz_ICU_LIBRARY)
if (HarfBuzz_FIND_REQUIRED_ICU)
list(APPEND HarfBuzz_LIBS_FOUND "ICU (required): ${HarfBuzz_ICU_LIBRARY}")
else ()
list(APPEND HarfBuzz_LIBS_FOUND "ICU (optional): ${HarfBuzz_ICU_LIBRARY}")
endif ()
else ()
if (HarfBuzz_FIND_REQUIRED_ICU)
set(_HarfBuzz_REQUIRED_LIBS_FOUND OFF)
list(APPEND HarfBuzz_LIBS_NOT_FOUND "ICU (required)")
else ()
list(APPEND HarfBuzz_LIBS_NOT_FOUND "ICU (optional)")
endif ()
endif ()
endif ()
endif ()
if (NOT HarfBuzz_FIND_QUIETLY)
if (HarfBuzz_LIBS_FOUND)
message(STATUS "Found the following HarfBuzz libraries:")
foreach (found ${HarfBuzz_LIBS_FOUND})
message(STATUS " ${found}")
endforeach ()
endif ()
if (HarfBuzz_LIBS_NOT_FOUND)
message(STATUS "The following HarfBuzz libraries were not found:")
foreach (found ${HarfBuzz_LIBS_NOT_FOUND})
message(STATUS " ${found}")
endforeach ()
endif ()
endif ()
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(HarfBuzz
FOUND_VAR HarfBuzz_FOUND
REQUIRED_VARS HarfBuzz_INCLUDE_DIR HarfBuzz_LIBRARY _HarfBuzz_REQUIRED_LIBS_FOUND
VERSION_VAR HarfBuzz_VERSION
)
if (NOT CMAKE_VERSION VERSION_LESS 3.1)
if (HarfBuzz_LIBRARY AND NOT TARGET HarfBuzz::HarfBuzz)
add_library(HarfBuzz::HarfBuzz UNKNOWN IMPORTED GLOBAL)
set_target_properties(HarfBuzz::HarfBuzz PROPERTIES
IMPORTED_LOCATION "${HarfBuzz_LIBRARY}"
INTERFACE_COMPILE_OPTIONS "${HarfBuzz_COMPILE_OPTIONS}"
INTERFACE_INCLUDE_DIRECTORIES "${HarfBuzz_INCLUDE_DIR}"
)
endif ()
if (HarfBuzz_ICU_LIBRARY AND NOT TARGET HarfBuzz::ICU)
add_library(HarfBuzz::ICU UNKNOWN IMPORTED GLOBAL)
set_target_properties(HarfBuzz::ICU PROPERTIES
IMPORTED_LOCATION "${HarfBuzz_ICU_LIBRARY}"
INTERFACE_COMPILE_OPTIONS "${HarfBuzz_ICU_COMPILE_OPTIONS}"
INTERFACE_INCLUDE_DIRECTORIES "${HarfBuzz_ICU_INCLUDE_DIR}"
)
endif ()
endif ()
mark_as_advanced(
HarfBuzz_INCLUDE_DIR
HarfBuzz_ICU_INCLUDE_DIR
HarfBuzz_LIBRARY
HarfBuzz_ICU_LIBRARY
)
if (HarfBuzz_FOUND)
set(HarfBuzz_LIBRARIES ${HarfBuzz_LIBRARY} ${HarfBuzz_ICU_LIBRARY})
set(HarfBuzz_INCLUDE_DIRS ${HarfBuzz_INCLUDE_DIR} ${HarfBuzz_ICU_INCLUDE_DIR})
endif ()

View file

@ -0,0 +1,690 @@
# This module can find the International Components for Unicode (ICU) libraries
#
# Requirements:
# - CMake >= 2.8.3 (for new version of find_package_handle_standard_args)
#
# The following variables will be defined for your use:
# - ICU_FOUND : were all of your specified components found?
# - ICU_INCLUDE_DIRS : ICU include directory
# - ICU_LIBRARIES : ICU libraries
# - ICU_VERSION : complete version of ICU (x.y.z)
# - ICU_VERSION_MAJOR : major version of ICU
# - ICU_VERSION_MINOR : minor version of ICU
# - ICU_VERSION_PATCH : patch version of ICU
# - ICU_<COMPONENT>_FOUND : were <COMPONENT> found? (FALSE for non specified component if it is not a dependency)
#
# For windows or non standard installation, define ICU_ROOT_DIR variable to point to the root installation of ICU. Two ways:
# - run cmake with -DICU_ROOT_DIR=<PATH>
# - define an environment variable with the same name before running cmake
# With cmake-gui, before pressing "Configure":
# 1) Press "Add Entry" button
# 2) Add a new entry defined as:
# - Name: ICU_ROOT_DIR
# - Type: choose PATH in the selection list
# - Press "..." button and select the root installation of ICU
#
# Example Usage:
#
# 1. Copy this file in the root of your project source directory
# 2. Then, tell CMake to search this non-standard module in your project directory by adding to your CMakeLists.txt:
# set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR})
# 3. Finally call find_package() once, here are some examples to pick from
#
# Require ICU 4.4 or later
# find_package(ICU 4.4 REQUIRED)
#
# if(ICU_FOUND)
# add_executable(myapp myapp.c)
# include_directories(${ICU_INCLUDE_DIRS})
# target_link_libraries(myapp ${ICU_LIBRARIES})
# # with CMake >= 3.0.0, the last two lines can be replaced by the following
# target_link_libraries(myapp ICU::ICU)
# endif(ICU_FOUND)
########## <ICU finding> ##########
find_package(PkgConfig QUIET)
########## Private ##########
if(NOT DEFINED ICU_PUBLIC_VAR_NS)
set(ICU_PUBLIC_VAR_NS "ICU") # Prefix for all ICU relative public variables
endif(NOT DEFINED ICU_PUBLIC_VAR_NS)
if(NOT DEFINED ICU_PRIVATE_VAR_NS)
set(ICU_PRIVATE_VAR_NS "_${ICU_PUBLIC_VAR_NS}") # Prefix for all ICU relative internal variables
endif(NOT DEFINED ICU_PRIVATE_VAR_NS)
if(NOT DEFINED PC_ICU_PRIVATE_VAR_NS)
set(PC_ICU_PRIVATE_VAR_NS "_PC${ICU_PRIVATE_VAR_NS}") # Prefix for all pkg-config relative internal variables
endif(NOT DEFINED PC_ICU_PRIVATE_VAR_NS)
set(${ICU_PRIVATE_VAR_NS}_HINTS )
# <deprecated>
# for future removal
if(DEFINED ENV{ICU_ROOT})
list(APPEND ${ICU_PRIVATE_VAR_NS}_HINTS "$ENV{ICU_ROOT}")
message(AUTHOR_WARNING "ENV{ICU_ROOT} is deprecated in favor of ENV{ICU_ROOT_DIR}")
endif(DEFINED ENV{ICU_ROOT})
if (DEFINED ICU_ROOT)
list(APPEND ${ICU_PRIVATE_VAR_NS}_HINTS "${ICU_ROOT}")
message(AUTHOR_WARNING "ICU_ROOT is deprecated in favor of ICU_ROOT_DIR")
endif(DEFINED ICU_ROOT)
# </deprecated>
if(DEFINED ENV{ICU_ROOT_DIR})
list(APPEND ${ICU_PRIVATE_VAR_NS}_HINTS "$ENV{ICU_ROOT_DIR}")
endif(DEFINED ENV{ICU_ROOT_DIR})
if (DEFINED ICU_ROOT_DIR)
list(APPEND ${ICU_PRIVATE_VAR_NS}_HINTS "${ICU_ROOT_DIR}")
endif(DEFINED ICU_ROOT_DIR)
set(${ICU_PRIVATE_VAR_NS}_COMPONENTS )
# <icu component name> <library name 1> ... <library name N>
macro(_icu_declare_component _NAME)
list(APPEND ${ICU_PRIVATE_VAR_NS}_COMPONENTS ${_NAME})
set("${ICU_PRIVATE_VAR_NS}_COMPONENTS_${_NAME}" ${ARGN})
endmacro(_icu_declare_component)
_icu_declare_component(data icudata)
_icu_declare_component(uc icuuc) # Common and Data libraries
_icu_declare_component(i18n icui18n icuin) # Internationalization library
_icu_declare_component(io icuio ustdio) # Stream and I/O Library
_icu_declare_component(le icule) # Layout library
_icu_declare_component(lx iculx) # Paragraph Layout library
########## Public ##########
set(${ICU_PUBLIC_VAR_NS}_FOUND FALSE)
set(${ICU_PUBLIC_VAR_NS}_LIBRARIES )
set(${ICU_PUBLIC_VAR_NS}_INCLUDE_DIRS )
set(${ICU_PUBLIC_VAR_NS}_C_FLAGS "")
set(${ICU_PUBLIC_VAR_NS}_CXX_FLAGS "")
set(${ICU_PUBLIC_VAR_NS}_CPP_FLAGS "")
set(${ICU_PUBLIC_VAR_NS}_C_SHARED_FLAGS "")
set(${ICU_PUBLIC_VAR_NS}_CXX_SHARED_FLAGS "")
set(${ICU_PUBLIC_VAR_NS}_CPP_SHARED_FLAGS "")
foreach(${ICU_PRIVATE_VAR_NS}_COMPONENT ${${ICU_PRIVATE_VAR_NS}_COMPONENTS})
string(TOUPPER "${${ICU_PRIVATE_VAR_NS}_COMPONENT}" ${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT)
set("${ICU_PUBLIC_VAR_NS}_${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}_FOUND" FALSE) # may be done in the _icu_declare_component macro
endforeach(${ICU_PRIVATE_VAR_NS}_COMPONENT)
# Check components
if(NOT ${ICU_PUBLIC_VAR_NS}_FIND_COMPONENTS) # uc required at least
set(${ICU_PUBLIC_VAR_NS}_FIND_COMPONENTS uc)
else(NOT ${ICU_PUBLIC_VAR_NS}_FIND_COMPONENTS)
list(APPEND ${ICU_PUBLIC_VAR_NS}_FIND_COMPONENTS uc)
list(REMOVE_DUPLICATES ${ICU_PUBLIC_VAR_NS}_FIND_COMPONENTS)
foreach(${ICU_PRIVATE_VAR_NS}_COMPONENT ${${ICU_PUBLIC_VAR_NS}_FIND_COMPONENTS})
if(NOT DEFINED ${ICU_PRIVATE_VAR_NS}_COMPONENTS_${${ICU_PRIVATE_VAR_NS}_COMPONENT})
message(FATAL_ERROR "Unknown ICU component: ${${ICU_PRIVATE_VAR_NS}_COMPONENT}")
endif(NOT DEFINED ${ICU_PRIVATE_VAR_NS}_COMPONENTS_${${ICU_PRIVATE_VAR_NS}_COMPONENT})
endforeach(${ICU_PRIVATE_VAR_NS}_COMPONENT)
endif(NOT ${ICU_PUBLIC_VAR_NS}_FIND_COMPONENTS)
# if pkg-config is available check components dependencies and append `pkg-config icu-<component> --variable=prefix` to hints
if(PKG_CONFIG_FOUND)
set(${ICU_PRIVATE_VAR_NS}_COMPONENTS_DUP ${${ICU_PUBLIC_VAR_NS}_FIND_COMPONENTS})
foreach(${ICU_PRIVATE_VAR_NS}_COMPONENT ${${ICU_PRIVATE_VAR_NS}_COMPONENTS_DUP})
pkg_check_modules(${PC_ICU_PRIVATE_VAR_NS} "icu-${${ICU_PRIVATE_VAR_NS}_COMPONENT}" QUIET)
if(${PC_ICU_PRIVATE_VAR_NS}_FOUND)
list(APPEND ${ICU_PRIVATE_VAR_NS}_HINTS ${${PC_ICU_PRIVATE_VAR_NS}_PREFIX})
foreach(${PC_ICU_PRIVATE_VAR_NS}_LIBRARY ${${PC_ICU_PRIVATE_VAR_NS}_LIBRARIES})
string(REGEX REPLACE "^icu" "" ${PC_ICU_PRIVATE_VAR_NS}_STRIPPED_LIBRARY ${${PC_ICU_PRIVATE_VAR_NS}_LIBRARY})
if(NOT ${PC_ICU_PRIVATE_VAR_NS}_STRIPPED_LIBRARY STREQUAL "data")
list(FIND ${ICU_PUBLIC_VAR_NS}_FIND_COMPONENTS ${${PC_ICU_PRIVATE_VAR_NS}_STRIPPED_LIBRARY} ${ICU_PRIVATE_VAR_NS}_COMPONENT_INDEX)
if(${ICU_PRIVATE_VAR_NS}_COMPONENT_INDEX EQUAL -1)
message(WARNING "Missing component dependency: ${${PC_ICU_PRIVATE_VAR_NS}_STRIPPED_LIBRARY}. Add it to your find_package(ICU) line as COMPONENTS to fix this warning.")
list(APPEND ${ICU_PUBLIC_VAR_NS}_FIND_COMPONENTS ${${PC_ICU_PRIVATE_VAR_NS}_STRIPPED_LIBRARY})
endif(${ICU_PRIVATE_VAR_NS}_COMPONENT_INDEX EQUAL -1)
endif(NOT ${PC_ICU_PRIVATE_VAR_NS}_STRIPPED_LIBRARY STREQUAL "data")
endforeach(${PC_ICU_PRIVATE_VAR_NS}_LIBRARY)
endif(${PC_ICU_PRIVATE_VAR_NS}_FOUND)
endforeach(${ICU_PRIVATE_VAR_NS}_COMPONENT)
endif(PKG_CONFIG_FOUND)
# list(APPEND ${ICU_PRIVATE_VAR_NS}_HINTS ENV ICU_ROOT_DIR)
# message("${ICU_PRIVATE_VAR_NS}_HINTS = ${${ICU_PRIVATE_VAR_NS}_HINTS}")
# Includes
find_path(
${ICU_PUBLIC_VAR_NS}_INCLUDE_DIR
NAMES unicode/utypes.h utypes.h
HINTS ${${ICU_PRIVATE_VAR_NS}_HINTS}
PATH_SUFFIXES "include"
DOC "Include directories for ICU"
)
if(${ICU_PUBLIC_VAR_NS}_INCLUDE_DIR)
########## <part to keep synced with tests/version/CMakeLists.txt> ##########
if(EXISTS "${${ICU_PUBLIC_VAR_NS}_INCLUDE_DIR}/unicode/uvernum.h") # ICU >= 4.4
file(READ "${${ICU_PUBLIC_VAR_NS}_INCLUDE_DIR}/unicode/uvernum.h" ${ICU_PRIVATE_VAR_NS}_VERSION_HEADER_CONTENTS)
elseif(EXISTS "${${ICU_PUBLIC_VAR_NS}_INCLUDE_DIR}/unicode/uversion.h") # ICU [2;4.4[
file(READ "${${ICU_PUBLIC_VAR_NS}_INCLUDE_DIR}/unicode/uversion.h" ${ICU_PRIVATE_VAR_NS}_VERSION_HEADER_CONTENTS)
elseif(EXISTS "${${ICU_PUBLIC_VAR_NS}_INCLUDE_DIR}/unicode/utypes.h") # ICU [1.4;2[
file(READ "${${ICU_PUBLIC_VAR_NS}_INCLUDE_DIR}/unicode/utypes.h" ${ICU_PRIVATE_VAR_NS}_VERSION_HEADER_CONTENTS)
elseif(EXISTS "${${ICU_PUBLIC_VAR_NS}_INCLUDE_DIR}/utypes.h") # ICU 1.3
file(READ "${${ICU_PUBLIC_VAR_NS}_INCLUDE_DIR}/utypes.h" ${ICU_PRIVATE_VAR_NS}_VERSION_HEADER_CONTENTS)
else()
message(FATAL_ERROR "ICU version header not found")
endif()
if(${ICU_PRIVATE_VAR_NS}_VERSION_HEADER_CONTENTS MATCHES ".*# *define *ICU_VERSION *\"([0-9]+)\".*") # ICU 1.3
# [1.3;1.4[ as #define ICU_VERSION "3" (no patch version, ie all 1.3.X versions will be detected as 1.3.0)
set(${ICU_PUBLIC_VAR_NS}_VERSION_MAJOR "1")
set(${ICU_PUBLIC_VAR_NS}_VERSION_MINOR "${CMAKE_MATCH_1}")
set(${ICU_PUBLIC_VAR_NS}_VERSION_PATCH "0")
elseif(${ICU_PRIVATE_VAR_NS}_VERSION_HEADER_CONTENTS MATCHES ".*# *define *U_ICU_VERSION_MAJOR_NUM *([0-9]+).*")
#
# Since version 4.9.1, ICU release version numbering was totaly changed, see:
# - http://site.icu-project.org/download/49
# - http://userguide.icu-project.org/design#TOC-Version-Numbers-in-ICU
#
set(${ICU_PUBLIC_VAR_NS}_VERSION_MAJOR "${CMAKE_MATCH_1}")
string(REGEX REPLACE ".*# *define *U_ICU_VERSION_MINOR_NUM *([0-9]+).*" "\\1" ${ICU_PUBLIC_VAR_NS}_VERSION_MINOR "${${ICU_PRIVATE_VAR_NS}_VERSION_HEADER_CONTENTS}")
string(REGEX REPLACE ".*# *define *U_ICU_VERSION_PATCHLEVEL_NUM *([0-9]+).*" "\\1" ${ICU_PUBLIC_VAR_NS}_VERSION_PATCH "${${ICU_PRIVATE_VAR_NS}_VERSION_HEADER_CONTENTS}")
elseif(${ICU_PRIVATE_VAR_NS}_VERSION_HEADER_CONTENTS MATCHES ".*# *define *U_ICU_VERSION *\"(([0-9]+)(\\.[0-9]+)*)\".*") # ICU [1.4;1.8[
# [1.4;1.8[ as #define U_ICU_VERSION "1.4.1.2" but it seems that some 1.4.[12](?:\.\d)? have releasing error and appears as 1.4.0
set(${ICU_PRIVATE_VAR_NS}_FULL_VERSION "${CMAKE_MATCH_1}") # copy CMAKE_MATCH_1, no longer valid on the following if
if(${ICU_PRIVATE_VAR_NS}_FULL_VERSION MATCHES "^([0-9]+)\\.([0-9]+)$")
set(${ICU_PUBLIC_VAR_NS}_VERSION_MAJOR "${CMAKE_MATCH_1}")
set(${ICU_PUBLIC_VAR_NS}_VERSION_MINOR "${CMAKE_MATCH_2}")
set(${ICU_PUBLIC_VAR_NS}_VERSION_PATCH "0")
elseif(${ICU_PRIVATE_VAR_NS}_FULL_VERSION MATCHES "^([0-9]+)\\.([0-9]+)\\.([0-9]+)")
set(${ICU_PUBLIC_VAR_NS}_VERSION_MAJOR "${CMAKE_MATCH_1}")
set(${ICU_PUBLIC_VAR_NS}_VERSION_MINOR "${CMAKE_MATCH_2}")
set(${ICU_PUBLIC_VAR_NS}_VERSION_PATCH "${CMAKE_MATCH_3}")
endif()
else()
message(FATAL_ERROR "failed to detect ICU version")
endif()
set(${ICU_PUBLIC_VAR_NS}_VERSION "${${ICU_PUBLIC_VAR_NS}_VERSION_MAJOR}.${${ICU_PUBLIC_VAR_NS}_VERSION_MINOR}.${${ICU_PUBLIC_VAR_NS}_VERSION_PATCH}")
########## </part to keep synced with tests/version/CMakeLists.txt> ##########
endif(${ICU_PUBLIC_VAR_NS}_INCLUDE_DIR)
# Check libraries
if(MSVC)
include(SelectLibraryConfigurations)
endif(MSVC)
foreach(${ICU_PRIVATE_VAR_NS}_COMPONENT ${${ICU_PUBLIC_VAR_NS}_FIND_COMPONENTS})
string(TOUPPER "${${ICU_PRIVATE_VAR_NS}_COMPONENT}" ${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT)
if(MSVC)
set(${ICU_PRIVATE_VAR_NS}_POSSIBLE_RELEASE_NAMES )
set(${ICU_PRIVATE_VAR_NS}_POSSIBLE_DEBUG_NAMES )
foreach(${ICU_PRIVATE_VAR_NS}_BASE_NAME ${${ICU_PRIVATE_VAR_NS}_COMPONENTS_${${ICU_PRIVATE_VAR_NS}_COMPONENT}})
list(APPEND ${ICU_PRIVATE_VAR_NS}_POSSIBLE_RELEASE_NAMES "${${ICU_PRIVATE_VAR_NS}_BASE_NAME}")
list(APPEND ${ICU_PRIVATE_VAR_NS}_POSSIBLE_DEBUG_NAMES "${${ICU_PRIVATE_VAR_NS}_BASE_NAME}d")
list(APPEND ${ICU_PRIVATE_VAR_NS}_POSSIBLE_RELEASE_NAMES "${${ICU_PRIVATE_VAR_NS}_BASE_NAME}${${ICU_PUBLIC_VAR_NS}_VERSION_MAJOR}${${ICU_PUBLIC_VAR_NS}_VERSION_MINOR}")
list(APPEND ${ICU_PRIVATE_VAR_NS}_POSSIBLE_DEBUG_NAMES "${${ICU_PRIVATE_VAR_NS}_BASE_NAME}${${ICU_PUBLIC_VAR_NS}_VERSION_MAJOR}${${ICU_PUBLIC_VAR_NS}_VERSION_MINOR}d")
endforeach(${ICU_PRIVATE_VAR_NS}_BASE_NAME)
find_library(
${ICU_PUBLIC_VAR_NS}_${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}_LIBRARY_RELEASE
NAMES ${${ICU_PRIVATE_VAR_NS}_POSSIBLE_RELEASE_NAMES}
HINTS ${${ICU_PRIVATE_VAR_NS}_HINTS}
DOC "Release library for ICU ${${ICU_PRIVATE_VAR_NS}_COMPONENT} component"
)
find_library(
${ICU_PUBLIC_VAR_NS}_${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}_LIBRARY_DEBUG
NAMES ${${ICU_PRIVATE_VAR_NS}_POSSIBLE_DEBUG_NAMES}
HINTS ${${ICU_PRIVATE_VAR_NS}_HINTS}
DOC "Debug library for ICU ${${ICU_PRIVATE_VAR_NS}_COMPONENT} component"
)
select_library_configurations("${ICU_PUBLIC_VAR_NS}_${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}")
list(APPEND ${ICU_PUBLIC_VAR_NS}_LIBRARY ${${ICU_PUBLIC_VAR_NS}_${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}_LIBRARY})
else(MSVC)
find_library(
${ICU_PUBLIC_VAR_NS}_${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}_LIBRARY
NAMES ${${ICU_PRIVATE_VAR_NS}_COMPONENTS_${${ICU_PRIVATE_VAR_NS}_COMPONENT}}
PATHS ${${ICU_PRIVATE_VAR_NS}_HINTS}
DOC "Library for ICU ${${ICU_PRIVATE_VAR_NS}_COMPONENT} component"
)
if(${ICU_PUBLIC_VAR_NS}_${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}_LIBRARY)
set("${ICU_PUBLIC_VAR_NS}_${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}_FOUND" TRUE)
list(APPEND ${ICU_PUBLIC_VAR_NS}_LIBRARY ${${ICU_PUBLIC_VAR_NS}_${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}_LIBRARY})
endif(${ICU_PUBLIC_VAR_NS}_${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}_LIBRARY)
endif(MSVC)
endforeach(${ICU_PRIVATE_VAR_NS}_COMPONENT)
# Try to find out compiler flags
find_program(${ICU_PUBLIC_VAR_NS}_CONFIG_EXECUTABLE icu-config HINTS ${${ICU_PRIVATE_VAR_NS}_HINTS})
if(${ICU_PUBLIC_VAR_NS}_CONFIG_EXECUTABLE)
execute_process(COMMAND ${${ICU_PUBLIC_VAR_NS}_CONFIG_EXECUTABLE} --cflags OUTPUT_VARIABLE ${ICU_PUBLIC_VAR_NS}_C_FLAGS OUTPUT_STRIP_TRAILING_WHITESPACE)
execute_process(COMMAND ${${ICU_PUBLIC_VAR_NS}_CONFIG_EXECUTABLE} --cxxflags OUTPUT_VARIABLE ${ICU_PUBLIC_VAR_NS}_CXX_FLAGS OUTPUT_STRIP_TRAILING_WHITESPACE)
execute_process(COMMAND ${${ICU_PUBLIC_VAR_NS}_CONFIG_EXECUTABLE} --cppflags OUTPUT_VARIABLE ${ICU_PUBLIC_VAR_NS}_CPP_FLAGS OUTPUT_STRIP_TRAILING_WHITESPACE)
execute_process(COMMAND ${${ICU_PUBLIC_VAR_NS}_CONFIG_EXECUTABLE} --cflags-dynamic OUTPUT_VARIABLE ${ICU_PUBLIC_VAR_NS}_C_SHARED_FLAGS OUTPUT_STRIP_TRAILING_WHITESPACE)
execute_process(COMMAND ${${ICU_PUBLIC_VAR_NS}_CONFIG_EXECUTABLE} --cxxflags-dynamic OUTPUT_VARIABLE ${ICU_PUBLIC_VAR_NS}_CXX_SHARED_FLAGS OUTPUT_STRIP_TRAILING_WHITESPACE)
execute_process(COMMAND ${${ICU_PUBLIC_VAR_NS}_CONFIG_EXECUTABLE} --cppflags-dynamic OUTPUT_VARIABLE ${ICU_PUBLIC_VAR_NS}_CPP_SHARED_FLAGS OUTPUT_STRIP_TRAILING_WHITESPACE)
endif(${ICU_PUBLIC_VAR_NS}_CONFIG_EXECUTABLE)
# Check find_package arguments
include(FindPackageHandleStandardArgs)
if(${ICU_PUBLIC_VAR_NS}_FIND_REQUIRED AND NOT ${ICU_PUBLIC_VAR_NS}_FIND_QUIETLY)
find_package_handle_standard_args(
${ICU_PUBLIC_VAR_NS}
REQUIRED_VARS ${ICU_PUBLIC_VAR_NS}_LIBRARY ${ICU_PUBLIC_VAR_NS}_INCLUDE_DIR
VERSION_VAR ${ICU_PUBLIC_VAR_NS}_VERSION
)
else(${ICU_PUBLIC_VAR_NS}_FIND_REQUIRED AND NOT ${ICU_PUBLIC_VAR_NS}_FIND_QUIETLY)
find_package_handle_standard_args(${ICU_PUBLIC_VAR_NS} "Could NOT find ICU" ${ICU_PUBLIC_VAR_NS}_LIBRARY ${ICU_PUBLIC_VAR_NS}_INCLUDE_DIR)
endif(${ICU_PUBLIC_VAR_NS}_FIND_REQUIRED AND NOT ${ICU_PUBLIC_VAR_NS}_FIND_QUIETLY)
if(${ICU_PUBLIC_VAR_NS}_FOUND)
# <deprecated>
# for compatibility with previous versions, alias old ICU_(MAJOR|MINOR|PATCH)_VERSION to ICU_VERSION_$1
set(${ICU_PUBLIC_VAR_NS}_MAJOR_VERSION ${${ICU_PUBLIC_VAR_NS}_VERSION_MAJOR})
set(${ICU_PUBLIC_VAR_NS}_MINOR_VERSION ${${ICU_PUBLIC_VAR_NS}_VERSION_MINOR})
set(${ICU_PUBLIC_VAR_NS}_PATCH_VERSION ${${ICU_PUBLIC_VAR_NS}_VERSION_PATCH})
# </deprecated>
set(${ICU_PUBLIC_VAR_NS}_LIBRARIES ${${ICU_PUBLIC_VAR_NS}_LIBRARY})
set(${ICU_PUBLIC_VAR_NS}_INCLUDE_DIRS ${${ICU_PUBLIC_VAR_NS}_INCLUDE_DIR})
if(NOT CMAKE_VERSION VERSION_LESS "3.0.0")
if(NOT TARGET ICU::ICU)
add_library(ICU::ICU INTERFACE IMPORTED)
endif(NOT TARGET ICU::ICU)
set_target_properties(ICU::ICU PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${${ICU_PUBLIC_VAR_NS}_INCLUDE_DIR}")
foreach(${ICU_PRIVATE_VAR_NS}_COMPONENT ${${ICU_PUBLIC_VAR_NS}_FIND_COMPONENTS})
string(TOUPPER "${${ICU_PRIVATE_VAR_NS}_COMPONENT}" ${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT)
add_library("ICU::${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}" UNKNOWN IMPORTED)
if(${ICU_PUBLIC_VAR_NS}_${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}_LIBRARY_RELEASE)
set_property(TARGET "ICU::${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}" APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE)
set_target_properties("ICU::${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}" PROPERTIES IMPORTED_LOCATION_RELEASE "${${ICU_PUBLIC_VAR_NS}_${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}_LIBRARY_RELEASE}")
endif(${ICU_PUBLIC_VAR_NS}_${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}_LIBRARY_RELEASE)
if(${ICU_PUBLIC_VAR_NS}_${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}_LIBRARY_DEBUG)
set_property(TARGET "ICU::${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}" APPEND PROPERTY IMPORTED_CONFIGURATIONS DEBUG)
set_target_properties("ICU::${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}" PROPERTIES IMPORTED_LOCATION_DEBUG "${${ICU_PUBLIC_VAR_NS}_${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}_LIBRARY_DEBUG}")
endif(${ICU_PUBLIC_VAR_NS}_${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}_LIBRARY_DEBUG)
if(${ICU_PUBLIC_VAR_NS}_${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}_LIBRARY)
set_target_properties("ICU::${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}" PROPERTIES IMPORTED_LOCATION "${${ICU_PUBLIC_VAR_NS}_${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}_LIBRARY}")
endif(${ICU_PUBLIC_VAR_NS}_${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}_LIBRARY)
set_property(TARGET ICU::ICU APPEND PROPERTY INTERFACE_LINK_LIBRARIES "ICU::${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}")
# set_target_properties("ICU::${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}" PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${${ICU_PUBLIC_VAR_NS}_INCLUDE_DIR}")
endforeach(${ICU_PRIVATE_VAR_NS}_COMPONENT)
endif(NOT CMAKE_VERSION VERSION_LESS "3.0.0")
endif(${ICU_PUBLIC_VAR_NS}_FOUND)
mark_as_advanced(
${ICU_PUBLIC_VAR_NS}_INCLUDE_DIR
${ICU_PUBLIC_VAR_NS}_LIBRARY
)
########## </ICU finding> ##########
########## <resource bundle support> ##########
########## Private ##########
function(_icu_extract_locale_from_rb _BUNDLE_SOURCE _RETURN_VAR_NAME)
file(READ "${_BUNDLE_SOURCE}" _BUNDLE_CONTENTS)
string(REGEX REPLACE "//[^\n]*\n" "" _BUNDLE_CONTENTS_WITHOUT_COMMENTS ${_BUNDLE_CONTENTS})
string(REGEX REPLACE "[ \t\n]" "" _BUNDLE_CONTENTS_WITHOUT_COMMENTS_AND_SPACES ${_BUNDLE_CONTENTS_WITHOUT_COMMENTS})
string(REGEX MATCH "^([a-zA-Z_-]+)(:table)?{" LOCALE_FOUND ${_BUNDLE_CONTENTS_WITHOUT_COMMENTS_AND_SPACES})
set("${_RETURN_VAR_NAME}" "${CMAKE_MATCH_1}" PARENT_SCOPE)
endfunction(_icu_extract_locale_from_rb)
########## Public ##########
#
# Prototype:
# icu_generate_resource_bundle([NAME <name>] [PACKAGE] [DESTINATION <location>] [FILES <list of files>])
#
# Common arguments:
# - NAME <name> : name of output package and to create dummy targets
# - FILES <file 1> ... <file N> : list of resource bundles sources
# - DEPENDS <target1> ... <target N> : required to package as library (shared or static), a list of cmake parent targets to link to
# Note: only (PREVIOUSLY DECLARED) add_executable and add_library as dependencies
# - DESTINATION <location> : optional, directory where to install final binary file(s)
# - FORMAT <name> : optional, one of none (ICU4C binary format, default), java (plain java) or xliff (XML), see below
#
# Arguments depending on FORMAT:
# - none (default):
# * PACKAGE : if present, package all resource bundles together. Default is to stop after building individual *.res files
# * TYPE <name> : one of :
# + common or archive (default) : archive all ressource bundles into a single .dat file
# + library or dll : assemble all ressource bundles into a separate and loadable library (.dll/.so)
# + static : integrate all ressource bundles to targets designed by DEPENDS parameter (as a static library)
# * NO_SHARED_FLAGS : only with TYPE in ['library', 'dll', 'static'], do not append ICU_C(XX)_SHARED_FLAGS to targets given as DEPENDS argument
# - JAVA:
# * BUNDLE <name> : required, prefix for generated classnames
# - XLIFF:
# (none)
#
#
# For an archive, the idea is to generate the following dependencies:
#
# root.txt => root.res \
# |
# en.txt => en.res |
# | => pkglist.txt => application.dat
# fr.txt => fr.res |
# |
# and so on /
#
# Lengend: 'A => B' means B depends on A
#
# Steps (correspond to arrows):
# 1) genrb (from .txt to .res)
# 2) generate a file text (pkglist.txt) with all .res files to put together
# 3) build final archive (from *.res/pkglist.txt to .dat)
#
function(icu_generate_resource_bundle)
##### <check for pkgdata/genrb availability> #####
find_program(${ICU_PUBLIC_VAR_NS}_GENRB_EXECUTABLE genrb HINTS ${${ICU_PRIVATE_VAR_NS}_HINTS})
find_program(${ICU_PUBLIC_VAR_NS}_PKGDATA_EXECUTABLE pkgdata HINTS ${${ICU_PRIVATE_VAR_NS}_HINTS})
if(NOT ${ICU_PUBLIC_VAR_NS}_GENRB_EXECUTABLE)
message(FATAL_ERROR "genrb not found")
endif(NOT ${ICU_PUBLIC_VAR_NS}_GENRB_EXECUTABLE)
if(NOT ${ICU_PUBLIC_VAR_NS}_PKGDATA_EXECUTABLE)
message(FATAL_ERROR "pkgdata not found")
endif(NOT ${ICU_PUBLIC_VAR_NS}_PKGDATA_EXECUTABLE)
##### </check for pkgdata/genrb availability> #####
##### <constants> #####
set(TARGET_SEPARATOR "+")
set(__FUNCTION__ "icu_generate_resource_bundle")
set(PACKAGE_TARGET_PREFIX "ICU${TARGET_SEPARATOR}PKG")
set(RESOURCE_TARGET_PREFIX "ICU${TARGET_SEPARATOR}RB")
##### </constants> #####
##### <hash constants> #####
# filename extension of built resource bundle (without dot)
set(BUNDLES__SUFFIX "res")
set(BUNDLES_JAVA_SUFFIX "java")
set(BUNDLES_XLIFF_SUFFIX "xlf")
# alias: none (default) = common = archive ; dll = library ; static
set(PKGDATA__ALIAS "")
set(PKGDATA_COMMON_ALIAS "")
set(PKGDATA_ARCHIVE_ALIAS "")
set(PKGDATA_DLL_ALIAS "LIBRARY")
set(PKGDATA_LIBRARY_ALIAS "LIBRARY")
set(PKGDATA_STATIC_ALIAS "STATIC")
# filename prefix of built package
set(PKGDATA__PREFIX "")
set(PKGDATA_LIBRARY_PREFIX "${CMAKE_SHARED_LIBRARY_PREFIX}")
set(PKGDATA_STATIC_PREFIX "${CMAKE_STATIC_LIBRARY_PREFIX}")
# filename extension of built package (with dot)
set(PKGDATA__SUFFIX ".dat")
set(PKGDATA_LIBRARY_SUFFIX "${CMAKE_SHARED_LIBRARY_SUFFIX}")
set(PKGDATA_STATIC_SUFFIX "${CMAKE_STATIC_LIBRARY_SUFFIX}")
# pkgdata option mode specific
set(PKGDATA__OPTIONS "-m" "common")
set(PKGDATA_STATIC_OPTIONS "-m" "static")
set(PKGDATA_LIBRARY_OPTIONS "-m" "library")
# cmake library type for output package
set(PKGDATA_LIBRARY__TYPE "")
set(PKGDATA_LIBRARY_STATIC_TYPE STATIC)
set(PKGDATA_LIBRARY_LIBRARY_TYPE SHARED)
##### </hash constants> #####
include(CMakeParseArguments)
cmake_parse_arguments(
PARSED_ARGS # output variable name
# options (true/false) (default value: false)
"PACKAGE;NO_SHARED_FLAGS"
# univalued parameters (default value: "")
"NAME;DESTINATION;TYPE;FORMAT;BUNDLE"
# multivalued parameters (default value: "")
"FILES;DEPENDS"
${ARGN}
)
# assert(${PARSED_ARGS_NAME} != "")
if(NOT PARSED_ARGS_NAME)
message(FATAL_ERROR "${__FUNCTION__}(): no name given, NAME parameter missing")
endif(NOT PARSED_ARGS_NAME)
# assert(length(PARSED_ARGS_FILES) > 0)
list(LENGTH PARSED_ARGS_FILES PARSED_ARGS_FILES_LEN)
if(PARSED_ARGS_FILES_LEN LESS 1)
message(FATAL_ERROR "${__FUNCTION__}() expects at least 1 resource bundle as FILES argument, 0 given")
endif(PARSED_ARGS_FILES_LEN LESS 1)
string(TOUPPER "${PARSED_ARGS_FORMAT}" UPPER_FORMAT)
# assert(${UPPER_FORMAT} in ['', 'java', 'xlif'])
if(NOT DEFINED BUNDLES_${UPPER_FORMAT}_SUFFIX)
message(FATAL_ERROR "${__FUNCTION__}(): unknown FORMAT '${PARSED_ARGS_FORMAT}'")
endif(NOT DEFINED BUNDLES_${UPPER_FORMAT}_SUFFIX)
if(UPPER_FORMAT STREQUAL "JAVA")
# assert(${PARSED_ARGS_BUNDLE} != "")
if(NOT PARSED_ARGS_BUNDLE)
message(FATAL_ERROR "${__FUNCTION__}(): java bundle name expected, BUNDLE parameter missing")
endif(NOT PARSED_ARGS_BUNDLE)
endif(UPPER_FORMAT STREQUAL "JAVA")
if(PARSED_ARGS_PACKAGE)
# assert(${PARSED_ARGS_FORMAT} == "")
if(PARSED_ARGS_FORMAT)
message(FATAL_ERROR "${__FUNCTION__}(): packaging is only supported for binary format, not xlif neither java outputs")
endif(PARSED_ARGS_FORMAT)
string(TOUPPER "${PARSED_ARGS_TYPE}" UPPER_MODE)
# assert(${UPPER_MODE} in ['', 'common', 'archive', 'dll', library'])
if(NOT DEFINED PKGDATA_${UPPER_MODE}_ALIAS)
message(FATAL_ERROR "${__FUNCTION__}(): unknown TYPE '${PARSED_ARGS_TYPE}'")
else(NOT DEFINED PKGDATA_${UPPER_MODE}_ALIAS)
set(TYPE "${PKGDATA_${UPPER_MODE}_ALIAS}")
endif(NOT DEFINED PKGDATA_${UPPER_MODE}_ALIAS)
# Package name: strip file extension if present
get_filename_component(PACKAGE_NAME_WE ${PARSED_ARGS_NAME} NAME_WE)
# Target name to build package
set(PACKAGE_TARGET_NAME "${PACKAGE_TARGET_PREFIX}${TARGET_SEPARATOR}${PACKAGE_NAME_WE}")
# Target name to build intermediate list file
set(PACKAGE_LIST_TARGET_NAME "${PACKAGE_TARGET_NAME}${TARGET_SEPARATOR}PKGLIST")
# Directory (absolute) to set as "current directory" for genrb (does not include package directory, -p)
# We make our "cook" there to prevent any conflict
if(DEFINED CMAKE_PLATFORM_ROOT_BIN) # CMake < 2.8.10
set(RESOURCE_GENRB_CHDIR_DIR "${CMAKE_PLATFORM_ROOT_BIN}/${PACKAGE_TARGET_NAME}.dir/")
else(DEFINED CMAKE_PLATFORM_ROOT_BIN) # CMake >= 2.8.10
set(RESOURCE_GENRB_CHDIR_DIR "${CMAKE_PLATFORM_INFO_DIR}/${PACKAGE_TARGET_NAME}.dir/")
endif(DEFINED CMAKE_PLATFORM_ROOT_BIN)
# Directory (absolute) where resource bundles are built: concatenation of RESOURCE_GENRB_CHDIR_DIR and package name
set(RESOURCE_OUTPUT_DIR "${RESOURCE_GENRB_CHDIR_DIR}/${PACKAGE_NAME_WE}/")
# Output (relative) path for built package
if(MSVC AND TYPE STREQUAL PKGDATA_LIBRARY_ALIAS)
set(PACKAGE_OUTPUT_PATH "${RESOURCE_GENRB_CHDIR_DIR}/${PACKAGE_NAME_WE}/${PKGDATA_${TYPE}_PREFIX}${PACKAGE_NAME_WE}${PKGDATA_${TYPE}_SUFFIX}")
else(MSVC AND TYPE STREQUAL PKGDATA_LIBRARY_ALIAS)
set(PACKAGE_OUTPUT_PATH "${RESOURCE_GENRB_CHDIR_DIR}/${PKGDATA_${TYPE}_PREFIX}${PACKAGE_NAME_WE}${PKGDATA_${TYPE}_SUFFIX}")
endif(MSVC AND TYPE STREQUAL PKGDATA_LIBRARY_ALIAS)
# Output (absolute) path for the list file
set(PACKAGE_LIST_OUTPUT_PATH "${RESOURCE_GENRB_CHDIR_DIR}/pkglist.txt")
file(MAKE_DIRECTORY "${RESOURCE_OUTPUT_DIR}")
else(PARSED_ARGS_PACKAGE)
set(RESOURCE_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/")
# set(RESOURCE_GENRB_CHDIR_DIR "UNUSED")
endif(PARSED_ARGS_PACKAGE)
set(TARGET_RESOURCES )
set(COMPILED_RESOURCES_PATH )
set(COMPILED_RESOURCES_BASENAME )
foreach(RESOURCE_SOURCE ${PARSED_ARGS_FILES})
_icu_extract_locale_from_rb(${RESOURCE_SOURCE} RESOURCE_NAME_WE)
get_filename_component(SOURCE_BASENAME ${RESOURCE_SOURCE} NAME)
get_filename_component(ABSOLUTE_SOURCE ${RESOURCE_SOURCE} ABSOLUTE)
if(UPPER_FORMAT STREQUAL "XLIFF")
if(RESOURCE_NAME_WE STREQUAL "root")
set(XLIFF_LANGUAGE "en")
else(RESOURCE_NAME_WE STREQUAL "root")
string(REGEX REPLACE "[^a-z].*$" "" XLIFF_LANGUAGE "${RESOURCE_NAME_WE}")
endif(RESOURCE_NAME_WE STREQUAL "root")
endif(UPPER_FORMAT STREQUAL "XLIFF")
##### <templates> #####
set(RESOURCE_TARGET_NAME "${RESOURCE_TARGET_PREFIX}${TARGET_SEPARATOR}${PARSED_ARGS_NAME}${TARGET_SEPARATOR}${RESOURCE_NAME_WE}")
set(RESOURCE_OUTPUT__PATH "${RESOURCE_NAME_WE}.res")
if(RESOURCE_NAME_WE STREQUAL "root")
set(RESOURCE_OUTPUT_JAVA_PATH "${PARSED_ARGS_BUNDLE}.java")
else(RESOURCE_NAME_WE STREQUAL "root")
set(RESOURCE_OUTPUT_JAVA_PATH "${PARSED_ARGS_BUNDLE}_${RESOURCE_NAME_WE}.java")
endif(RESOURCE_NAME_WE STREQUAL "root")
set(RESOURCE_OUTPUT_XLIFF_PATH "${RESOURCE_NAME_WE}.xlf")
set(GENRB__OPTIONS "")
set(GENRB_JAVA_OPTIONS "-j" "-b" "${PARSED_ARGS_BUNDLE}")
set(GENRB_XLIFF_OPTIONS "-x" "-l" "${XLIFF_LANGUAGE}")
##### </templates> #####
# build <locale>.txt from <locale>.res
if(PARSED_ARGS_PACKAGE)
add_custom_command(
OUTPUT "${RESOURCE_OUTPUT_DIR}${RESOURCE_OUTPUT_${UPPER_FORMAT}_PATH}"
COMMAND ${CMAKE_COMMAND} -E chdir ${RESOURCE_GENRB_CHDIR_DIR} ${${ICU_PUBLIC_VAR_NS}_GENRB_EXECUTABLE} ${GENRB_${UPPER_FORMAT}_OPTIONS} -d ${PACKAGE_NAME_WE} ${ABSOLUTE_SOURCE}
DEPENDS ${RESOURCE_SOURCE}
)
else(PARSED_ARGS_PACKAGE)
add_custom_command(
OUTPUT "${RESOURCE_OUTPUT_DIR}${RESOURCE_OUTPUT_${UPPER_FORMAT}_PATH}"
COMMAND ${${ICU_PUBLIC_VAR_NS}_GENRB_EXECUTABLE} ${GENRB_${UPPER_FORMAT}_OPTIONS} -d ${RESOURCE_OUTPUT_DIR} ${ABSOLUTE_SOURCE}
DEPENDS ${RESOURCE_SOURCE}
)
endif(PARSED_ARGS_PACKAGE)
# dummy target (ICU+RB+<name>+<locale>) for each locale to build the <locale>.res file from its <locale>.txt by the add_custom_command above
add_custom_target(
"${RESOURCE_TARGET_NAME}" ALL
COMMENT ""
DEPENDS "${RESOURCE_OUTPUT_DIR}${RESOURCE_OUTPUT_${UPPER_FORMAT}_PATH}"
SOURCES ${RESOURCE_SOURCE}
)
if(PARSED_ARGS_DESTINATION AND NOT PARSED_ARGS_PACKAGE)
install(FILES "${RESOURCE_OUTPUT_DIR}${RESOURCE_OUTPUT_${UPPER_FORMAT}_PATH}" DESTINATION ${PARSED_ARGS_DESTINATION} PERMISSIONS OWNER_READ GROUP_READ WORLD_READ)
endif(PARSED_ARGS_DESTINATION AND NOT PARSED_ARGS_PACKAGE)
list(APPEND TARGET_RESOURCES "${RESOURCE_TARGET_NAME}")
list(APPEND COMPILED_RESOURCES_PATH "${RESOURCE_OUTPUT_DIR}${RESOURCE_OUTPUT_${UPPER_FORMAT}_PATH}")
list(APPEND COMPILED_RESOURCES_BASENAME "${RESOURCE_NAME_WE}.${BUNDLES_${UPPER_FORMAT}_SUFFIX}")
endforeach(RESOURCE_SOURCE)
# convert semicolon separated list to a space separated list
# NOTE: if the pkglist.txt file starts (or ends?) with a whitespace, pkgdata add an undefined symbol (named <package>_) for it
string(REPLACE ";" " " COMPILED_RESOURCES_BASENAME "${COMPILED_RESOURCES_BASENAME}")
if(PARSED_ARGS_PACKAGE)
# create a text file (pkglist.txt) with the list of the *.res to package together
add_custom_command(
OUTPUT "${PACKAGE_LIST_OUTPUT_PATH}"
COMMAND ${CMAKE_COMMAND} -E echo "${COMPILED_RESOURCES_BASENAME}" > "${PACKAGE_LIST_OUTPUT_PATH}"
DEPENDS ${COMPILED_RESOURCES_PATH}
)
# run pkgdata from pkglist.txt
add_custom_command(
OUTPUT "${PACKAGE_OUTPUT_PATH}"
COMMAND ${CMAKE_COMMAND} -E chdir ${RESOURCE_GENRB_CHDIR_DIR} ${${ICU_PUBLIC_VAR_NS}_PKGDATA_EXECUTABLE} -F ${PKGDATA_${TYPE}_OPTIONS} -s ${PACKAGE_NAME_WE} -p ${PACKAGE_NAME_WE} ${PACKAGE_LIST_OUTPUT_PATH}
DEPENDS "${PACKAGE_LIST_OUTPUT_PATH}"
VERBATIM
)
if(PKGDATA_LIBRARY_${TYPE}_TYPE)
# assert(${PARSED_ARGS_DEPENDS} != "")
if(NOT PARSED_ARGS_DEPENDS)
message(FATAL_ERROR "${__FUNCTION__}(): static and library mode imply a list of targets to link to, DEPENDS parameter missing")
endif(NOT PARSED_ARGS_DEPENDS)
add_library(${PACKAGE_TARGET_NAME} ${PKGDATA_LIBRARY_${TYPE}_TYPE} IMPORTED)
if(MSVC)
string(REGEX REPLACE "${PKGDATA_LIBRARY_SUFFIX}\$" "${CMAKE_IMPORT_LIBRARY_SUFFIX}" PACKAGE_OUTPUT_LIB "${PACKAGE_OUTPUT_PATH}")
set_target_properties(${PACKAGE_TARGET_NAME} PROPERTIES IMPORTED_LOCATION ${PACKAGE_OUTPUT_PATH} IMPORTED_IMPLIB ${PACKAGE_OUTPUT_LIB})
else(MSVC)
set_target_properties(${PACKAGE_TARGET_NAME} PROPERTIES IMPORTED_LOCATION ${PACKAGE_OUTPUT_PATH})
endif(MSVC)
foreach(DEPENDENCY ${PARSED_ARGS_DEPENDS})
target_link_libraries(${DEPENDENCY} ${PACKAGE_TARGET_NAME})
if(NOT PARSED_ARGS_NO_SHARED_FLAGS)
get_property(ENABLED_LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES)
list(LENGTH "${ENABLED_LANGUAGES}" ENABLED_LANGUAGES_LENGTH)
if(ENABLED_LANGUAGES_LENGTH GREATER 1)
message(WARNING "Project has more than one language enabled, skip automatic shared flags appending")
else(ENABLED_LANGUAGES_LENGTH GREATER 1)
set_property(TARGET "${DEPENDENCY}" APPEND PROPERTY COMPILE_FLAGS "${${ICU_PUBLIC_VAR_NS}_${ENABLED_LANGUAGES}_SHARED_FLAGS}")
endif(ENABLED_LANGUAGES_LENGTH GREATER 1)
endif(NOT PARSED_ARGS_NO_SHARED_FLAGS)
endforeach(DEPENDENCY)
# http://www.mail-archive.com/cmake-commits@cmake.org/msg01135.html
set(PACKAGE_INTERMEDIATE_TARGET_NAME "${PACKAGE_TARGET_NAME}${TARGET_SEPARATOR}DUMMY")
# dummy intermediate target (ICU+PKG+<name>+DUMMY) to link the package to the produced library by running pkgdata (see add_custom_command above)
add_custom_target(
${PACKAGE_INTERMEDIATE_TARGET_NAME}
COMMENT ""
DEPENDS "${PACKAGE_OUTPUT_PATH}"
)
add_dependencies("${PACKAGE_TARGET_NAME}" "${PACKAGE_INTERMEDIATE_TARGET_NAME}")
else(PKGDATA_LIBRARY_${TYPE}_TYPE)
# dummy target (ICU+PKG+<name>) to run pkgdata (see add_custom_command above)
add_custom_target(
"${PACKAGE_TARGET_NAME}" ALL
COMMENT ""
DEPENDS "${PACKAGE_OUTPUT_PATH}"
)
endif(PKGDATA_LIBRARY_${TYPE}_TYPE)
# dummy target (ICU+PKG+<name>+PKGLIST) to build the file pkglist.txt
add_custom_target(
"${PACKAGE_LIST_TARGET_NAME}" ALL
COMMENT ""
DEPENDS "${PACKAGE_LIST_OUTPUT_PATH}"
)
# package => pkglist.txt
add_dependencies("${PACKAGE_TARGET_NAME}" "${PACKAGE_LIST_TARGET_NAME}")
# pkglist.txt => *.res
add_dependencies("${PACKAGE_LIST_TARGET_NAME}" ${TARGET_RESOURCES})
if(PARSED_ARGS_DESTINATION)
install(FILES "${PACKAGE_OUTPUT_PATH}" DESTINATION ${PARSED_ARGS_DESTINATION} PERMISSIONS OWNER_READ GROUP_READ WORLD_READ)
endif(PARSED_ARGS_DESTINATION)
endif(PARSED_ARGS_PACKAGE)
endfunction(icu_generate_resource_bundle)
########## </resource bundle support> ##########
########## <debug> ##########
if(${ICU_PUBLIC_VAR_NS}_DEBUG)
function(icudebug _VARNAME)
if(DEFINED ${ICU_PUBLIC_VAR_NS}_${_VARNAME})
message("${ICU_PUBLIC_VAR_NS}_${_VARNAME} = ${${ICU_PUBLIC_VAR_NS}_${_VARNAME}}")
else(DEFINED ${ICU_PUBLIC_VAR_NS}_${_VARNAME})
message("${ICU_PUBLIC_VAR_NS}_${_VARNAME} = <UNDEFINED>")
endif(DEFINED ${ICU_PUBLIC_VAR_NS}_${_VARNAME})
endfunction(icudebug)
# IN (args)
icudebug("FIND_COMPONENTS")
icudebug("FIND_REQUIRED")
icudebug("FIND_QUIETLY")
icudebug("FIND_VERSION")
# OUT
# Found
icudebug("FOUND")
# Flags
icudebug("C_FLAGS")
icudebug("CPP_FLAGS")
icudebug("CXX_FLAGS")
icudebug("C_SHARED_FLAGS")
icudebug("CPP_SHARED_FLAGS")
icudebug("CXX_SHARED_FLAGS")
# Linking
icudebug("INCLUDE_DIRS")
icudebug("LIBRARIES")
# Version
icudebug("VERSION_MAJOR")
icudebug("VERSION_MINOR")
icudebug("VERSION_PATCH")
icudebug("VERSION")
# <COMPONENT>_(FOUND|LIBRARY)
set(${ICU_PRIVATE_VAR_NS}_COMPONENT_VARIABLES "FOUND" "LIBRARY" "LIBRARY_RELEASE" "LIBRARY_DEBUG")
foreach(${ICU_PRIVATE_VAR_NS}_COMPONENT ${${ICU_PRIVATE_VAR_NS}_COMPONENTS})
string(TOUPPER "${${ICU_PRIVATE_VAR_NS}_COMPONENT}" ${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT)
foreach(${ICU_PRIVATE_VAR_NS}_COMPONENT_VARIABLE ${${ICU_PRIVATE_VAR_NS}_COMPONENT_VARIABLES})
icudebug("${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}_${${ICU_PRIVATE_VAR_NS}_COMPONENT_VARIABLE}")
endforeach(${ICU_PRIVATE_VAR_NS}_COMPONENT_VARIABLE)
endforeach(${ICU_PRIVATE_VAR_NS}_COMPONENT)
endif(${ICU_PUBLIC_VAR_NS}_DEBUG)
########## </debug> ##########

View file

@ -0,0 +1,60 @@
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
# file Copyright.txt or https://cmake.org/licensing for details.
#[=======================================================================[.rst:
FindIntl
--------
Find the Gettext libintl headers and libraries.
This module reports information about the Gettext libintl
installation in several variables. General variables::
Intl_FOUND - true if the libintl headers and libraries were found
Intl_INCLUDE_DIRS - the directory containing the libintl headers
Intl_LIBRARIES - libintl libraries to be linked
The following cache variables may also be set::
Intl_INCLUDE_DIR - the directory containing the libintl headers
Intl_LIBRARY - the libintl library (if any)
.. note::
On some platforms, such as Linux with GNU libc, the gettext
functions are present in the C standard library and libintl
is not required. ``Intl_LIBRARIES`` will be empty in this
case.
.. note::
If you wish to use the Gettext tools (``msgmerge``,
``msgfmt``, etc.), use :module:`FindGettext`.
#]=======================================================================]
# Written by Roger Leigh <rleigh@codelibre.net>
# Find include directory
find_path(Intl_INCLUDE_DIR
NAMES "libintl.h"
DOC "libintl include directory")
mark_as_advanced(Intl_INCLUDE_DIR)
# Find all Intl libraries
find_library(Intl_LIBRARY "intl"
DOC "libintl libraries (if not in the C library)")
mark_as_advanced(Intl_LIBRARY)
include(FindPackageHandleStandardArgs)
FIND_PACKAGE_HANDLE_STANDARD_ARGS(Intl
FOUND_VAR Intl_FOUND
REQUIRED_VARS Intl_INCLUDE_DIR
FAIL_MESSAGE "Failed to find Gettext libintl")
if(Intl_FOUND)
set(Intl_INCLUDE_DIRS "${Intl_INCLUDE_DIR}")
if(Intl_LIBRARY)
set(Intl_LIBRARIES "${Intl_LIBRARY}")
else()
unset(Intl_LIBRARIES)
endif()
endif()

View file

@ -19,7 +19,7 @@ endif()
project(es-de) project(es-de)
# Application version, update this when making a new release. # Application version, update this when making a new release.
set(ES_VERSION 3.0.3) set(ES_VERSION 3.1.1-alpha)
# Set this to ON to show verbose compiler output (e.g. compiler flags, include directories etc.) # Set this to ON to show verbose compiler output (e.g. compiler flags, include directories etc.)
set(CMAKE_VERBOSE_MAKEFILE OFF CACHE BOOL "Show verbose compiler output" FORCE) set(CMAKE_VERBOSE_MAKEFILE OFF CACHE BOOL "Show verbose compiler output" FORCE)
@ -35,6 +35,7 @@ list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/CMake/Utils
option(GL "Set to ON if targeting Desktop OpenGL" ON) option(GL "Set to ON if targeting Desktop OpenGL" ON)
option(GLES "Set to ON if targeting OpenGL ES" OFF) option(GLES "Set to ON if targeting OpenGL ES" OFF)
option(APPLICATION_UPDATER "Set to OFF to build without the application updater" ON) option(APPLICATION_UPDATER "Set to OFF to build without the application updater" ON)
option(COMPILE_LOCALIZATIONS "Set to OFF to skip compilation of localization message catalogs" ON)
option(APPIMAGE_BUILD "Set to ON when building as an AppImage" OFF) option(APPIMAGE_BUILD "Set to ON when building as an AppImage" OFF)
option(AUR_BUILD "Set to ON when building for the AUR" OFF) option(AUR_BUILD "Set to ON when building for the AUR" OFF)
option(FLATPAK_BUILD "Set to ON when building as a Flatpak" OFF) option(FLATPAK_BUILD "Set to ON when building as a Flatpak" OFF)
@ -136,6 +137,9 @@ elseif(NOT EMSCRIPTEN AND NOT ANDROID)
find_package(FFmpeg REQUIRED) find_package(FFmpeg REQUIRED)
find_package(FreeImage REQUIRED) find_package(FreeImage REQUIRED)
find_package(Freetype REQUIRED) find_package(Freetype REQUIRED)
find_package(HarfBuzz REQUIRED)
find_package(ICU REQUIRED)
find_package(Intl REQUIRED)
find_package(Libgit2 REQUIRED) find_package(Libgit2 REQUIRED)
find_package(Pugixml REQUIRED) find_package(Pugixml REQUIRED)
find_package(SDL2 REQUIRED) find_package(SDL2 REQUIRED)
@ -213,7 +217,8 @@ else()
endif() endif()
endif() endif()
if(APPLE AND CMAKE_CXX_COMPILER_VERSION GREATER_EQUAL 15.0.0) # Silence some annoying warnings caused by invalid characters in some FreeImage source comments.
if(CMAKE_CXX_COMPILER_ID MATCHES Clang AND CMAKE_CXX_COMPILER_VERSION GREATER_EQUAL 15.0.0)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-invalid-utf8") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-invalid-utf8")
endif() endif()
@ -269,6 +274,11 @@ if(UBSAN)
endif() endif()
endif() endif()
if(ASAN OR UBSAN)
# Add some extra checks when building with AddressSanitizer or UndefinedBehaviorSanitizer.
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_GLIBCXX_ASSERTIONS -D_FORTIFY_SOURCE=3")
endif()
# The following removes half of the ranlib warnings on macOS regarding no symbols for files # The following removes half of the ranlib warnings on macOS regarding no symbols for files
# that are #ifdef'ed away. There must be a way to remove the other half as well? # that are #ifdef'ed away. There must be a way to remove the other half as well?
if(APPLE) if(APPLE)
@ -284,6 +294,7 @@ endif()
if(ANDROID) if(ANDROID)
set(BUNDLED_CERTS ON) set(BUNDLED_CERTS ON)
set(COMPILE_LOCALIZATIONS OFF)
add_compile_definitions(ANDROID_VERSION_CODE=${ANDROID_VERSION_CODE}) add_compile_definitions(ANDROID_VERSION_CODE=${ANDROID_VERSION_CODE})
add_compile_definitions(ANDROID_APPLICATION_ID="org.es_de.frontend") add_compile_definitions(ANDROID_APPLICATION_ID="org.es_de.frontend")
endif() endif()
@ -359,19 +370,15 @@ if(VIDEO_HW_DECODING)
endif() endif()
if(DEINIT_ON_LAUNCH) if(DEINIT_ON_LAUNCH)
if(CMAKE_SYSTEM_NAME MATCHES Linux) if(CMAKE_SYSTEM_NAME MATCHES Linux OR CMAKE_SYSTEM_NAME MATCHES FreeBSD)
add_compile_definitions(DEINIT_ON_LAUNCH) add_compile_definitions(DEINIT_ON_LAUNCH)
message("-- Building with deinitialization on game launch") message("-- Building with deinitialization on game launch")
else() else()
message(FATAL_ERROR "-- Deinitialization on game launch can only be used on Linux") message(FATAL_ERROR "-- Deinitialization on game launch can only be used on Linux and FreeBSD")
endif() endif()
endif() endif()
if(AUR_BUILD OR FLATPAK_BUILD OR RETRODECK OR RPI) if(AUR_BUILD OR FLATPAK_BUILD OR RETRODECK OR RPI OR HAIKU OR CMAKE_SYSTEM_NAME MATCHES FreeBSD)
set(APPLICATION_UPDATER OFF)
endif()
if(CMAKE_SYSTEM_NAME MATCHES FreeBSD OR CMAKE_SYSTEM_NAME MATCHES NetBSD OR CMAKE_SYSTEM_NAME MATCHES OpenBSD)
set(APPLICATION_UPDATER OFF) set(APPLICATION_UPDATER OFF)
endif() endif()
@ -424,13 +431,11 @@ add_compile_definitions(GLM_FORCE_CXX17)
add_compile_definitions(GLM_FORCE_XYZW_ONLY) add_compile_definitions(GLM_FORCE_XYZW_ONLY)
# For Unix systems, assign the installation prefix. If it's not explicitly set, # For Unix systems, assign the installation prefix. If it's not explicitly set,
# we use /usr on Linux, /usr/pkg on NetBSD and /usr/local on FreeBSD and OpenBSD. # we use /usr on Linux and /usr/local on FreeBSD.
if(NOT WIN32 AND NOT APPLE AND NOT ANDROID) if(NOT WIN32 AND NOT APPLE AND NOT ANDROID)
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
if(CMAKE_SYSTEM_NAME MATCHES Linux) if(CMAKE_SYSTEM_NAME MATCHES Linux)
set(CMAKE_INSTALL_PREFIX /usr CACHE INTERNAL CMAKE_INSTALL_PREFIX) set(CMAKE_INSTALL_PREFIX /usr CACHE INTERNAL CMAKE_INSTALL_PREFIX)
elseif(CMAKE_SYSTEM_NAME MATCHES NetBSD)
set(CMAKE_INSTALL_PREFIX /usr/pkg CACHE INTERNAL CMAKE_INSTALL_PREFIX)
else() else()
set(CMAKE_INSTALL_PREFIX /usr/local CACHE INTERNAL CMAKE_INSTALL_PREFIX) set(CMAKE_INSTALL_PREFIX /usr/local CACHE INTERNAL CMAKE_INSTALL_PREFIX)
endif() endif()
@ -463,7 +468,10 @@ if(APPLE)
${CMAKE_CURRENT_SOURCE_DIR}/external/FFmpeg ${CMAKE_CURRENT_SOURCE_DIR}/external/FFmpeg
${CMAKE_CURRENT_SOURCE_DIR}/external/freeimage/FreeImage/Source ${CMAKE_CURRENT_SOURCE_DIR}/external/freeimage/FreeImage/Source
${CMAKE_CURRENT_SOURCE_DIR}/external/freetype/include ${CMAKE_CURRENT_SOURCE_DIR}/external/freetype/include
${CMAKE_CURRENT_SOURCE_DIR}/external/gettext/gettext-runtime/intl
${CMAKE_CURRENT_SOURCE_DIR}/external/libgit2/include ${CMAKE_CURRENT_SOURCE_DIR}/external/libgit2/include
${CMAKE_CURRENT_SOURCE_DIR}/external/harfbuzz/src
${CMAKE_CURRENT_SOURCE_DIR}/external/icu/icu4c/source/common
${CMAKE_CURRENT_SOURCE_DIR}/external/pugixml/src ${CMAKE_CURRENT_SOURCE_DIR}/external/pugixml/src
${CMAKE_CURRENT_SOURCE_DIR}/external/SDL) ${CMAKE_CURRENT_SOURCE_DIR}/external/SDL)
elseif(WIN32) elseif(WIN32)
@ -473,6 +481,9 @@ elseif(WIN32)
${CMAKE_CURRENT_SOURCE_DIR}/external/ffmpeg/include ${CMAKE_CURRENT_SOURCE_DIR}/external/ffmpeg/include
${CMAKE_CURRENT_SOURCE_DIR}/external/FreeImage/Dist/x64 ${CMAKE_CURRENT_SOURCE_DIR}/external/FreeImage/Dist/x64
${CMAKE_CURRENT_SOURCE_DIR}/external/freetype/include ${CMAKE_CURRENT_SOURCE_DIR}/external/freetype/include
${CMAKE_CURRENT_SOURCE_DIR}/external/gettext/include
${CMAKE_CURRENT_SOURCE_DIR}/external/harfbuzz/src
${CMAKE_CURRENT_SOURCE_DIR}/external/icu/icu4c/source/common
${CMAKE_CURRENT_SOURCE_DIR}/external/libgit2/include ${CMAKE_CURRENT_SOURCE_DIR}/external/libgit2/include
${CMAKE_CURRENT_SOURCE_DIR}/external/pugixml/src ${CMAKE_CURRENT_SOURCE_DIR}/external/pugixml/src
${CMAKE_CURRENT_SOURCE_DIR}/external/SDL2) ${CMAKE_CURRENT_SOURCE_DIR}/external/SDL2)
@ -489,6 +500,9 @@ elseif(ANDROID)
${CMAKE_CURRENT_SOURCE_DIR}/external/ffmpeg-kit/src/ffmpeg ${CMAKE_CURRENT_SOURCE_DIR}/external/ffmpeg-kit/src/ffmpeg
${CMAKE_CURRENT_SOURCE_DIR}/external/freeimage/FreeImage/Source ${CMAKE_CURRENT_SOURCE_DIR}/external/freeimage/FreeImage/Source
${CMAKE_CURRENT_SOURCE_DIR}/external/freetype/include ${CMAKE_CURRENT_SOURCE_DIR}/external/freetype/include
${CMAKE_CURRENT_SOURCE_DIR}/external/gettext/gettext-runtime/intl
${CMAKE_CURRENT_SOURCE_DIR}/external/harfbuzz/src
${CMAKE_CURRENT_SOURCE_DIR}/external/icu/icu4c/source/common
${CMAKE_CURRENT_SOURCE_DIR}/external/libgit2/include ${CMAKE_CURRENT_SOURCE_DIR}/external/libgit2/include
${CMAKE_CURRENT_SOURCE_DIR}/external/pugixml/src ${CMAKE_CURRENT_SOURCE_DIR}/external/pugixml/src
${CMAKE_CURRENT_SOURCE_DIR}/external/SDL_Android) ${CMAKE_CURRENT_SOURCE_DIR}/external/SDL_Android)
@ -498,6 +512,8 @@ else()
${FreeImage_INCLUDE_DIRS} ${FreeImage_INCLUDE_DIRS}
${FREETYPE_INCLUDE_DIRS} ${FREETYPE_INCLUDE_DIRS}
${GIT2_INCLUDE_PATH} ${GIT2_INCLUDE_PATH}
${HarfBuzz_INCLUDE_DIRS}
${ICU_INCLUDE_DIRS}
${PUGIXML_INCLUDE_DIRS} ${PUGIXML_INCLUDE_DIRS}
${SDL2_INCLUDE_DIR}) ${SDL2_INCLUDE_DIR})
endif() endif()
@ -534,6 +550,11 @@ if(APPLE)
${PROJECT_SOURCE_DIR}/libfreeimage.a ${PROJECT_SOURCE_DIR}/libfreeimage.a
${PROJECT_SOURCE_DIR}/libfreetype.6.dylib ${PROJECT_SOURCE_DIR}/libfreetype.6.dylib
${PROJECT_SOURCE_DIR}/libgit2.1.7.dylib ${PROJECT_SOURCE_DIR}/libgit2.1.7.dylib
${PROJECT_SOURCE_DIR}/libharfbuzz.dylib
${PROJECT_SOURCE_DIR}/libicudata.75.dylib
${PROJECT_SOURCE_DIR}/libicui18n.75.dylib
${PROJECT_SOURCE_DIR}/libicuuc.75.dylib
${PROJECT_SOURCE_DIR}/libintl.8.dylib
${PROJECT_SOURCE_DIR}/libpugixml.a ${PROJECT_SOURCE_DIR}/libpugixml.a
${PROJECT_SOURCE_DIR}/libSDL2-2.0.0.dylib) ${PROJECT_SOURCE_DIR}/libSDL2-2.0.0.dylib)
elseif(WIN32) elseif(WIN32)
@ -546,7 +567,12 @@ elseif(WIN32)
${PROJECT_SOURCE_DIR}/FreeImage.lib ${PROJECT_SOURCE_DIR}/FreeImage.lib
${PROJECT_SOURCE_DIR}/git2.lib ${PROJECT_SOURCE_DIR}/git2.lib
${PROJECT_SOURCE_DIR}/glew32.lib ${PROJECT_SOURCE_DIR}/glew32.lib
${PROJECT_SOURCE_DIR}/harfbuzz.lib
${PROJECT_SOURCE_DIR}/icudt.lib
${PROJECT_SOURCE_DIR}/icuin.lib
${PROJECT_SOURCE_DIR}/icuuc.lib
${PROJECT_SOURCE_DIR}/libcurl-x64.lib ${PROJECT_SOURCE_DIR}/libcurl-x64.lib
${PROJECT_SOURCE_DIR}/libintl-8.lib
${PROJECT_SOURCE_DIR}/freetype.lib ${PROJECT_SOURCE_DIR}/freetype.lib
${PROJECT_SOURCE_DIR}/lunasvg.lib ${PROJECT_SOURCE_DIR}/lunasvg.lib
${PROJECT_SOURCE_DIR}/pugixml.lib ${PROJECT_SOURCE_DIR}/pugixml.lib
@ -569,6 +595,11 @@ elseif(ANDROID)
${PROJECT_SOURCE_DIR}/android/libs/${ANDROID_CPU_ARCH}/libfreeimage.so ${PROJECT_SOURCE_DIR}/android/libs/${ANDROID_CPU_ARCH}/libfreeimage.so
${PROJECT_SOURCE_DIR}/android/libs/${ANDROID_CPU_ARCH}/libfreetype.so ${PROJECT_SOURCE_DIR}/android/libs/${ANDROID_CPU_ARCH}/libfreetype.so
${PROJECT_SOURCE_DIR}/android/libs/${ANDROID_CPU_ARCH}/libgit2.so ${PROJECT_SOURCE_DIR}/android/libs/${ANDROID_CPU_ARCH}/libgit2.so
${PROJECT_SOURCE_DIR}/android/libs/${ANDROID_CPU_ARCH}/libharfbuzz.so
${PROJECT_SOURCE_DIR}/android/libs/${ANDROID_CPU_ARCH}/libicudata.a
${PROJECT_SOURCE_DIR}/android/libs/${ANDROID_CPU_ARCH}/libicui18n.a
${PROJECT_SOURCE_DIR}/android/libs/${ANDROID_CPU_ARCH}/libicuuc.a
${PROJECT_SOURCE_DIR}/android/libs/${ANDROID_CPU_ARCH}/libintl.so
${PROJECT_SOURCE_DIR}/android/libs/${ANDROID_CPU_ARCH}/libjpeg.so ${PROJECT_SOURCE_DIR}/android/libs/${ANDROID_CPU_ARCH}/libjpeg.so
${PROJECT_SOURCE_DIR}/android/libs/${ANDROID_CPU_ARCH}/libpoppler.so ${PROJECT_SOURCE_DIR}/android/libs/${ANDROID_CPU_ARCH}/libpoppler.so
${PROJECT_SOURCE_DIR}/android/libs/${ANDROID_CPU_ARCH}/libpugixml.a ${PROJECT_SOURCE_DIR}/android/libs/${ANDROID_CPU_ARCH}/libpugixml.a
@ -613,8 +644,15 @@ else()
${FreeImage_LIBRARIES} ${FreeImage_LIBRARIES}
${FREETYPE_LIBRARIES} ${FREETYPE_LIBRARIES}
${GIT2_LIBRARY} ${GIT2_LIBRARY}
${HarfBuzz_LIBRARIES}
${ICU_LIBRARIES}
${PUGIXML_LIBRARIES} ${PUGIXML_LIBRARIES}
${SDL2_LIBRARY}) ${SDL2_LIBRARY})
if(Intl_LIBRARY)
# On Unix systems where the gettext functions are not part of the C standard library
# we need to explicitly link with the libintl library.
set(COMMON_LIBRARIES ${COMMON_LIBRARIES} ${Intl_LIBRARY})
endif()
endif() endif()
if(NOT WIN32) if(NOT WIN32)
@ -673,15 +711,23 @@ set(dir ${CMAKE_CURRENT_SOURCE_DIR})
set(EXECUTABLE_OUTPUT_PATH ${dir} CACHE PATH "Build directory" FORCE) set(EXECUTABLE_OUTPUT_PATH ${dir} CACHE PATH "Build directory" FORCE)
set(LIBRARY_OUTPUT_PATH ${dir} CACHE PATH "Build directory" FORCE) set(LIBRARY_OUTPUT_PATH ${dir} CACHE PATH "Build directory" FORCE)
# Add each component. if(COMPILE_LOCALIZATIONS)
add_subdirectory(locale)
endif()
add_subdirectory(es-pdf-converter) add_subdirectory(es-pdf-converter)
add_subdirectory(external) add_subdirectory(external)
add_subdirectory(es-core) add_subdirectory(es-core)
add_subdirectory(es-app) add_subdirectory(es-app)
# Make sure that es-pdf-convert is built first, and then that rlottie is built before es-core. # Make sure that es-pdf-convert is built first, and then that rlottie is built before es-core.
# Also set lottie2gif to not be built.
add_dependencies(lunasvg es-pdf-convert) add_dependencies(lunasvg es-pdf-convert)
if(COMPILE_LOCALIZATIONS)
add_dependencies(es-pdf-convert localization)
endif()
add_dependencies(es-core rlottie) add_dependencies(es-core rlottie)
# Set lottie2gif to not be built.
set_target_properties(lottie2gif PROPERTIES EXCLUDE_FROM_ALL 1 EXCLUDE_FROM_DEFAULT_BUILD 1) set_target_properties(lottie2gif PROPERTIES EXCLUDE_FROM_ALL 1 EXCLUDE_FROM_DEFAULT_BUILD 1)

View file

@ -15,6 +15,37 @@ Alec Lofquist
Nils Bonenberger Nils Bonenberger
# Translations
**English (United Kingdom)** \
Weestuarty \
**German** \
Thorsten \
Pest \
**Spanish (Spain)** \
Dani (stshunz) \
**French** \
Fredy27 (neuromancer974) \
**Italian** \
Viler \
**Polish** \
kycho \
**Portuguese (Brazil)** \
MrVictorFull \
**Romanian** \
SilverGreen93 \
**Russian** \
Lulzee \
**Swedish** \
Leon Styhre \
**Japanese** \
冰棍 \
**Korean** \
wakeboxer \
**Simplified Chinese** \
邻家小熊 \
冰棍
# Licenses # Licenses
Please find the individual license files inside the [licenses](https://gitlab.com/es-de/emulationstation-de/-/tree/master/licenses) directory. There is also additional license information in the headers of most source files. Please find the individual license files inside the [licenses](https://gitlab.com/es-de/emulationstation-de/-/tree/master/licenses) directory. There is also additional license information in the headers of most source files.
@ -42,9 +73,18 @@ http://glew.sourceforge.net
GLM \ GLM \
https://github.com/g-truc/glm https://github.com/g-truc/glm
HarfBuzz \
https://harfbuzz.github.io
ICU \
https://icu.unicode.org
libgit2 \ libgit2 \
https://libgit2.org https://libgit2.org
libintl (gettext) \
https://www.gnu.org/software/gettext
libvpx \ libvpx \
https://github.com/webmproject/libvpx https://github.com/webmproject/libvpx
@ -109,10 +149,10 @@ https://fontawesome.com
GNU FreeFont (FreeMono) \ GNU FreeFont (FreeMono) \
https://www.gnu.org/software/freefont https://www.gnu.org/software/freefont
Nanum font \ Nanum Square Neo font \
https://hangeul.naver.com https://campaign.naver.com/nanumsquare_neo
Noto Emoji \ Noto Emoji font \
https://fonts.google.com/noto/specimen/Noto+Emoji https://fonts.google.com/noto/specimen/Noto+Emoji
Ubuntu font \ Ubuntu font \

View file

@ -8,11 +8,11 @@ Yes it's the exact same application, with only some minor differences. This mean
## Why is it named ES-DE as in "Desktop Edition" if this is a release for a mobile operating system? ## Why is it named ES-DE as in "Desktop Edition" if this is a release for a mobile operating system?
First it's branding, it would be very confusing to have different names for the same application when it's available cross-platform. Second, the _Desktop Edition_ part is now a legacy, nowadays instead think of the D as standing for _Deck, Droid_ or _Desktop_. The _EmulationStation Desktop Edition_ subtitle used on the splash screen is only temporary during a transition period, it will be removed. First it's branding, it would be very confusing to have different names for the same application when it's available cross-platform. Second, the _Desktop Edition_ part is now basically legacy. Actually the entire _EmulationStation Desktop Edition_ subtitle used on the splash screen is only temporary during a transition period and it will be removed at some point. The official name of the project is already ES-DE Frontend or ES-DE for short.
## Is it available for free, and is it open source? ## Is it available for free, and is it open source?
The Android release specifically is not free, it's a paid app available for purchase through [Patreon](https://www.patreon.com/es_de) or the [Samsung Galaxy Store](https://galaxystore.samsung.com/detail/org.es_de.frontend.galaxy). And although approximately 99% of the app is open source there are some portions of the code that is closed source. The Android release specifically is not free, it's a paid app available for purchase through [Patreon](https://www.patreon.com/es_de), the [Samsung Galaxy Store](https://galaxystore.samsung.com/detail/org.es_de.frontend.galaxy) and [Huawei AppGallery](https://appgallery.huawei.com/#/app/C111315115). And although the majority of the code is open source there is some Android-specific code that is copyrighted and closed source.
## I bought ES-DE on Patreon, how do I get access to future releases? ## I bought ES-DE on Patreon, how do I get access to future releases?
@ -20,11 +20,15 @@ When a new release is available you will be sent a download link to the email ad
## Can I use ES-DE on more than a single Android device or do I need to buy it multiple times? ## Can I use ES-DE on more than a single Android device or do I need to buy it multiple times?
You only need to buy it once, and then you can use it on all your devices. There are no subscriptions or additional costs, you just buy it once. With that said we do appreciate if you want to support the project by keeping your paid Patreon subscription. You only need to buy the Patreon release once, and then you can use it on all your devices. There are no subscriptions or additional costs, you just buy it once. With that said we do appreciate if you want to support the project by keeping your paid Patreon subscription. The Samsung Galaxy Store and Huawei AppGallery releases may not be available on all your devices, but that is not an ES-DE restriction but rather governed by the availability of these app stores on your different Android devices.
## ES-DE doesn't work on my device, can I get a refund? ## ES-DE doesn't work on my device, can I get a refund?
Although the overwhelming majority of people have successfully got ES-DE to run on their devices (assuming they are fulfilling the basic requirements of 64-bit Android 10 or later) there are some devices that have been problematic. Unfortunately Android is not really a standardized operating system and hardware manufacturers are sometimes applying custom patches and such which may prevent ES-DE from working correctly. We will refund everyone up to one month from the purchase date if they are unable to get ES-DE to run on their device, just send a DM on Patreon and we will issue a refund as soon as possible. Although the overwhelming majority of people have successfully got ES-DE to run on their devices (assuming they are fulfilling the basic requirements of 64-bit Android 10 or later) there are some devices that have been problematic. Unfortunately Android is not really a standardized operating system and hardware manufacturers are sometimes applying custom patches and such which may prevent ES-DE from working correctly. We will refund anyone that bought ES-DE on Patreon within one month from the purchase date if they are unable to get ES-DE to run on their device. Just send a DM on Patreon and we will issue a refund as soon as possible. We are however unfortunately not able to refund purchases on the Samsung Galaxy Store and Huawei AppGallery. But make sure to read the next question below as your device may be compatible after all.
## ES-DE hangs at the onboarding configurator, is the app not compatible with my device?
There are some Android developer options that break ES-DE (and probably many other apps too) so make sure to never change such settings unless you know exactly what you are doing. For instance the option _Don't keep activities_ will make the configurator hang so that you'll never be able to get past the onboarding step.
## I received an update email to my Gmail account but the APK download link doesn't seem to work? ## I received an update email to my Gmail account but the APK download link doesn't seem to work?
@ -42,7 +46,7 @@ The second reason is that the APK is corrupt or not complete. When we make relea
## Can I set ES-DE as my home app/launcher? ## Can I set ES-DE as my home app/launcher?
Yes, as of version 3.0.3 there is experimental support for setting ES-DE as the home app. Read the _Running ES-DE as the Android home app_ section of the [Android documentation](ANDROID.md#running-es-de-as-the-android-home-app) for more information about this functionality. There are currently some minor glitches like sometimes needing to start ES-DE twice after switching between regular mode and home app mode, but apart from that it should hopefully work fine. Yes, read the _Running ES-DE as the Android home app_ section of the [Android documentation](ANDROID.md#running-es-de-as-the-android-home-app) for more information about this functionality.
## Can I launch Android apps and games from inside ES-DE? ## Can I launch Android apps and games from inside ES-DE?
@ -68,7 +72,9 @@ Yes but this is not recommended. It's tedious to setup and not how ES-DE is inte
RetroArch on Android is very unforgiving, if you haven't installed the necessary core or BIOS files it's a high chance that you just see a black screen and it will hang there, possibly until you kill it. And due to the security model in Android it's not possible for ES-DE to check if a core is actually installed prior to attempting to launch RetroArch (on Linux, macOS and Windows a popup is shown if the core file is missing and the game is never actually launched in this case). Also make sure that the core you have installed in RetroArch is the one you actually use in ES-DE. You can select between different cores and emulators for most systems using the _Alternative emulators_ interface in the _Other settings_ menu. RetroArch on Android is very unforgiving, if you haven't installed the necessary core or BIOS files it's a high chance that you just see a black screen and it will hang there, possibly until you kill it. And due to the security model in Android it's not possible for ES-DE to check if a core is actually installed prior to attempting to launch RetroArch (on Linux, macOS and Windows a popup is shown if the core file is missing and the game is never actually launched in this case). Also make sure that the core you have installed in RetroArch is the one you actually use in ES-DE. You can select between different cores and emulators for most systems using the _Alternative emulators_ interface in the _Other settings_ menu.
Also note that the RetroArch release on the Google Play store is not working correctly on some devices, it can be used on its own but game launching fails from ES-DE. These issues have been resolved by a number of people by instead switching to the release from the [RetroArch](https://retroarch.com) website. Another reason for the black screen is if you have multiple users configured on your device and attempt to run RetroArch from a non-primary user while having your ROMs on internal storage. At the time of writing RetroArch does not support external game launching for any other user than the primary user as it can't parse paths such as /storage/emulated/10/.
Also note that the RetroArch release on the Google Play store is not working correctly on most devices. It can be used on its own but game launching fails from ES-DE. These issues are resolved by using a current release from the [RetroArch](https://retroarch.com) website.
## When I launch a game using a standalone emulator, why does it say the game file could not be opened? ## When I launch a game using a standalone emulator, why does it say the game file could not be opened?
@ -92,11 +98,19 @@ No Android may stop applications that are not currently focused if it needs to r
## ES-DE takes a very long time to start, is there a way to improve this? ## ES-DE takes a very long time to start, is there a way to improve this?
Unfortunately disk I/O performance on Android leaves a lot to be desired compared to desktop operating systems. Google has prioritized other things over performance which leads to disk speed being poor overall on this operating system. The main offender is the choice of FAT filesystems such as exFAT for external storage which offer very poor performance for some file operations on which ES-DE relies heavily. Generally speaking a small to medium ROM collection can normally be placed on a FAT-formatted device such as an SD card but the ES-DE directory and more importantly the _downloaded_media_ directory should always be placed on internal storage. For large game collections ES-DE could turn borderline unusable if the ES-DE directory is placed on an SD card or USB memory stick. It's also possible to enable the _Only show games from gamelist.xml files_ option in the _Other settings_ menu to skip checking for game files on startup, but this has multiple implications such as what's displayed inside ES-DE not necessarily reflecting reality any longer. And obviously you'll need gamelist.xml entries for all games you want to show up inside ES-DE. So this option is really a last resort and is generally only recommended for testing purposes. In summary huge game collections are discouraged on Android due to limitations in the operating system itself. Setting up a collection of tens of thousands of games is for sure achievable with ES-DE on Linux, macOS or Windows but it's not really feasible on Android. Unfortunately disk I/O performance on Android is not on par with desktop operating systems. Google has prioritized other things over performance which leads to disk speed being poor overall on this operating system. The main issue is the choice of FAT filesystems such as exFAT for external storage which offer very poor performance for some file operations on which ES-DE relies heavily. The SAF/MediaStore layer also adds a lot of overhead. Generally speaking a small to medium ROM collection can be placed on a FAT-formatted device such as an SD card, but it's recommended to place the ES-DE directory and more importantly the _downloaded_media_ directory on internal storage. For large game collections ES-DE could turn borderline unusable if the _downloaded_media_ directory is placed on an SD card or on a USB memory stick. This seems to be quite device-dependent though and on some devices performance is still acceptable, so you'll need to test it.
## On game launch RetroArch runs an old game instead of the one I just selected, how do I prevent this? One possible improvement to startup times is to enable the _Only show games from gamelist.xml files_ option in the _Other settings_ menu to skip checking for game files on startup, but this has multiple implications such as what's displayed inside ES-DE not necessarily reflecting reality any longer. And obviously you'll need gamelist.xml entries for all games you want to show up inside ES-DE. So this option is really a last resort and is generally only recommended for testing purposes.
There is a video on the official ES-DE YouTube channel on how to configure RetroArch correctly so that it quits completely when you're exiting a game:\ Another option that could speed up startup times under some circumstances is disabling the _Enable theme variant triggers_ setting in the _UI settings_ menu. But whether this has a tangible effect depends on the theme used and to what extent there is scraped media available for your game systems.
Finally, if you keep directories containing texture packs and similar inside your game system folders then these can slow down the startup considerably. To exclude scanning of any such directory you can place a file named `noload.txt` inside the folder and it will get completely skipped on startup.
In summary huge game collections are discouraged on Android due to limitations in the operating system itself. Setting up a collection of tens of thousands of games is for sure achievable with ES-DE on Linux, macOS or Windows but it's not really feasible on Android.
## On game launch the emulator runs an old game instead of the one I just selected, how do I prevent this?
You need to exit the game every time you stop playing, by doing this everything will work correctly. Pressing the home button or manually navigating back to ES-DE without exiting the game is equivalent to pressing "Alt+tab" on a desktop operating system, i.e. the game will still run. The difference from desktop operating systems is that Android pauses the game if you switch away from its window so it may seem like it has closed down, although it actually hasn't. While the procedure to fully exit a game differs between emulators there's a video on the official ES-DE YouTube channel on how to configure RetroArch correctly so that it quits completely when you're exiting a game:\
https://www.youtube.com/watch?v=k5WWacfIn6Y https://www.youtube.com/watch?v=k5WWacfIn6Y
## What type of Android devices are supported ## What type of Android devices are supported

8
FAQ.md
View file

@ -6,11 +6,11 @@ As of the 3.0.0 release the official name for the project and application is ES-
## Is this software available for free, and is it open source? ## Is this software available for free, and is it open source?
ES-DE is available for free on Windows, macOS and Linux and it's released under the MIT open source license. For Android it's a paid app available via [Patreon](https://www.patreon.com/es_de) and this port is partially closed source. ES-DE is available for free on Windows, macOS and Linux and it's released under the MIT open source license. For Android it's a paid app available via [Patreon](https://www.patreon.com/es_de), the [Samsung Galaxy Store](https://galaxystore.samsung.com/detail/org.es_de.frontend.galaxy) and [Huawei AppGallery](https://appgallery.huawei.com/#/app/C111315115) and this port is partially closed source.
## Which operating systems are supported? ## Which operating systems are supported?
ES-DE runs on Windows, macOS and multiple Linux distributions including Ubuntu, Fedora, Arch, Manjaro, SteamOS etc. ES-DE is officially supported on Android, Windows, macOS and multiple Linux distributions including Ubuntu, Fedora, Arch, Manjaro, SteamOS etc. It's also semiofficially supported on some secondary platforms like FreeBSD, the Raspberry Pi and Haiku although these ports may not always be up to date and you may have to build ES-DE yourself.
## What is the relationship between ES-DE and RetroDECK? ## What is the relationship between ES-DE and RetroDECK?
@ -28,6 +28,10 @@ See the _Supported game systems_ section at the bottom of the [User guide](USERG
Menus in ES-DE are not lists but grids, sometimes there is only a list but sometimes there are buttons beneath the list. Enabling the up and down buttons to wrap around would therefore not work consistently as it would sometimes jump to the last row of the list and sometimes to a button, requiring a different number of button presses depending on the menu layout. This type of contextual navigation feels very weird in practice, especially when you have to press the up button twice to get to the bottom of a list. The solution is instead to use the shoulder buttons (which will jump six rows), or the trigger buttons (which will jump to the first and last row). These buttons work consistently throughout the application and avoid the strange side effects just mentioned. Menus in ES-DE are not lists but grids, sometimes there is only a list but sometimes there are buttons beneath the list. Enabling the up and down buttons to wrap around would therefore not work consistently as it would sometimes jump to the last row of the list and sometimes to a button, requiring a different number of button presses depending on the menu layout. This type of contextual navigation feels very weird in practice, especially when you have to press the up button twice to get to the bottom of a list. The solution is instead to use the shoulder buttons (which will jump six rows), or the trigger buttons (which will jump to the first and last row). These buttons work consistently throughout the application and avoid the strange side effects just mentioned.
## I'm using SteamOS and my language settings are not reflected in ES-DE, how can I get language auto-detection to work?
SteamOS does not setup the environment correctly in game mode so it's not possible for ES-DE to detect your configured language. As such you need to manually select your language inside ES-DE using the _Application Language_ option in the _UI Settings_ menu.
## Can I change the system sorting to not sort by full system names? ## Can I change the system sorting to not sort by full system names?
Yes the systems sorting configuration file can be selected via the _Systems sorting_ option in the _UI Settings_ menu. There are four such files bundled with ES-DE to sort by _"Release year", "Manufacturer, release year", "Hardware type, release year"_ and _"Manufacturer, hardware type, release year"_. If you don't want to use any of the bundled files then you can create your own custom sorting file and place it into the ~/ES-DE/custom_systems/ directory. More details about this setup can be found in the _es_systems_sorting.xml_ section of the [Building and advanced configuration](INSTALL.md#es_systems_sortingxml) document. Yes the systems sorting configuration file can be selected via the _Systems sorting_ option in the _UI Settings_ menu. There are four such files bundled with ES-DE to sort by _"Release year", "Manufacturer, release year", "Hardware type, release year"_ and _"Manufacturer, hardware type, release year"_. If you don't want to use any of the bundled files then you can create your own custom sorting file and place it into the ~/ES-DE/custom_systems/ directory. More details about this setup can be found in the _es_systems_sorting.xml_ section of the [Building and advanced configuration](INSTALL.md#es_systems_sortingxml) document.

319
HAIKU.md Normal file
View file

@ -0,0 +1,319 @@
# ES-DE Frontend - Haiku documentation
Note that support for Haiku is currently experimental as the operating system itself is experimental.
There are currently no pre-built packages available so you'll need to build ES-DE yourself. Detailed instructions are available in the _Building on Haiku_ section of the [INSTALL-DEV.md](INSTALL-DEV.md#building-on-haiku) document.
Table of contents:
[[_TOC_]]
## Known ES-DE problems
* Key repeat doesn't work in text editing fields (but it works elsewhere in the application)
* There is no 3D acceleration as the operating system does not support that
## Emulator problems
In contrast with all other platforms which ES-DE runs on, on Haiku emulators which are not working correctly are still included in the configuration. This is done with the belief that things will improve in the future as the operating system matures.
### Atari800
Can't run compressed game files such as those with the .zip extension, and does not seem to be able to correctly emulate any games even if they are uncompressed? (The emulator starts but the games don't.)
### Beetle Lynx
Games don't start, just displays a black screen.
### Beetle PSX HW
Crashes on game start.
### blueMSX
Can't run compressed game files such as those with the .zip extension.
### bsnes
Can't run compressed game files such as those with the .zip extension.
### Caprice32
Can't run compressed game files such as those with the .zip extension.
### DeSmuME
Can't run compressed game files such as those with the .zip extension.
### DOSBox-X (Standalone)
Games can only be launched if ES-DE has been started from the command line, i.e. from a _Terminal_ window. And when existing a game the OS screen resolution is sometimes not reset back to its previous state meaning it has to be manually set to the correct resolution using the operating system's _Screen_ utility.
### EasyRPG
Crashes on game start.
### Flycast
Too slow to be usable in practice, probably due to lack of 3D acceleration.
### fMSX
Can't run compressed game files such as those with the .zip extension.
### FreeIntv
Can't run compressed game files such as those with the .zip extension.
### FS-UAE (Standalone)
This emulator does not seem to accept command-line arguments, meaning games can't be launched from ES-DE.
### Genesis Plus GX Wide
Can't run compressed game files such as those with the .zip extension (it works fine in Genesis Plus GX).
### gpSP
Can't run compressed game files such as those with the .zip extension.
### Hatari
Can't run compressed game files such as those with the .zip extension, and IPF files are not supported.
### MAME (Standalone)
When existing a game the OS screen resolution is sometimes not reset back to its previous state meaning it has to be manually set to the correct resolution using the operating system's _Screen_ utility.
### melonDS
Crashes on game start.
### melonDS (Standalone)
Crashes on game start if attempting to launch a zipped game file.
### Mupen64Plus-Next
Crashes on game start.
### PCSX ReARMed
Games don't run, emulator instantly exits.
### PUAE
Crashes on game start.
### ScummVM (Standalone)
Games can only be launched if ES-DE has been started from the command line, i.e. from a _Terminal_ window.
### Stella
Crashes on game start (Stella 2014 works fine).
### ZEsarUX
Crashes on game start.
## Supported game systems
The **@** symbol indicates that the emulator is _deprecated_ and will be removed in a future ES-DE release.
| System name | Full name | Default emulator | Alternative emulators | Needs BIOS | Recommended game setup |
| :-------------------- | :--------------------------------------------- | :-------------------------------- | :-------------------------------- | :----------- | :----------------------------------- |
| 3do | 3DO Interactive Multiplayer | Opera | | Yes | |
| adam | Coleco Adam | MAME [Diskette] **(Standalone)** | MAME [Tape] **(Standalone)**,<br>MAME [Cartridge] **(Standalone)**,<br>MAME [Software list] **(Standalone)** | Yes | |
| ags | Adventure Game Studio Game Engine | _Placeholder_ | | | |
| amiga | Commodore Amiga | PUAE | FS-UAE **(Standalone)** | Yes | See the specific _Commodore Amiga and CDTV_ section in the user guide |
| amiga1200 | Commodore Amiga 1200 | PUAE | FS-UAE **(Standalone)** | Yes | See the specific _Commodore Amiga and CDTV_ section in the user guide |
| amiga600 | Commodore Amiga 600 | PUAE | FS-UAE **(Standalone)** | Yes | See the specific _Commodore Amiga and CDTV_ section in the user guide |
| amigacd32 | Commodore Amiga CD32 | PUAE | FS-UAE **(Standalone)** | Yes | See the specific _Commodore Amiga and CDTV_ section in the user guide |
| amstradcpc | Amstrad CPC | Caprice32 | MAME **(Standalone)** | Yes for MAME | Single archive or disk file |
| android | Google Android | _Placeholder_ | | | |
| androidapps | Android Apps | _Placeholder_ | | | |
| androidgames | Android Games | _Placeholder_ | | | |
| apple2 | Apple II | Mednafen **(Standalone)** | MAME **(Standalone)** | Yes | See the specific _Apple II_ section in the user guide |
| apple2gs | Apple IIGS | MAME **(Standalone)** | | Yes | See the specific _Apple IIGS_ section in the user guide |
| arcade | Arcade | MAME 2003-Plus | MAME **(Standalone)**,<br>FinalBurn Neo,<br>FinalBurn Neo **(Standalone)**,<br>Geolith,<br>Flycast,<br> _Script_ | Depends | See the specific _Arcade and Neo Geo_ section in the user guide |
| arcadia | Emerson Arcadia 2001 | MAME **(Standalone)** | | No | Single archive or ROM file |
| archimedes | Acorn Archimedes | MAME [Model A440/1] **(Standalone)** | MAME [Model A3000] **(Standalone)**,<br>MAME [Model A310] **(Standalone)**,<br>MAME [Model A540] **(Standalone)** | Yes | |
| arduboy | Arduboy Miniature Game System | _Placeholder_ | | | |
| astrocde | Bally Astrocade | MAME **(Standalone)** | | Yes | Single archive or ROM file |
| atari2600 | Atari 2600 | Stella | Stella 2014 | No | Single archive or ROM file |
| atari5200 | Atari 5200 | Atari800 | | Yes | Single archive or ROM file |
| atari7800 | Atari 7800 ProSystem | MAME **(Standalone)** | | Yes | Single archive or ROM file |
| atari800 | Atari 800 | Atari800 | | Yes | |
| atarijaguar | Atari Jaguar | Virtual Jaguar | MAME **(Standalone)** | Yes for MAME | See the specific _Atari Jaguar and Atari Jaguar CD_ section in the user guide |
| atarijaguarcd | Atari Jaguar CD | _Placeholder_ | | | |
| atarilynx | Atari Lynx | Handy | Beetle Lynx,<br>Mednafen **(Standalone)** | No | Single archive or ROM file |
| atarist | Atari ST [also STE and Falcon] | Hatari | | Yes | Single archive or image file for single-diskette games, .m3u playlist for multi-diskette games |
| atarixe | Atari XE | Atari800 | | Yes | |
| atomiswave | Sammy Corporation Atomiswave | Flycast | | Yes | Single archive file |
| bbcmicro | Acorn Computers BBC Micro | MAME **(Standalone)** | | Yes | Single archive or diskette image file |
| c64 | Commodore 64 | VICE x64sc Accurate | VICE x64 Fast,<br>VICE x64 SuperCPU,<br>VICE x128 | No | Single archive or image file for tape, cartridge or single-diskette games, .m3u playlist for multi-diskette games |
| cdimono1 | Philips CD-i | MAME **(Standalone)** | | Yes | |
| cdtv | Commodore CDTV | PUAE | FS-UAE **(Standalone)** | Yes | See the specific _Commodore Amiga and CDTV_ section in the user guide |
| chailove | ChaiLove Game Engine | _Placeholder_ | | | |
| channelf | Fairchild Channel F | MAME **(Standalone)** | | Yes | Single archive or ROM file |
| coco | Tandy Color Computer | MAME [Cartridge] **(Standalone)** | MAME [Tape] **(Standalone)** | Yes | See the specific _Tandy Color Computer_ section in the user guide |
| colecovision | Coleco ColecoVision | blueMSX | Gearcoleco | Yes | Single archive or ROM file |
| consolearcade | Console Arcade Systems | MAME **(Standalone)** | Flycast,<br>Mednafen [Sega Saturn] **(Standalone)**,<br> _Script_ | Depends | See the specific _Console Arcade Systems_ section in the user guide |
| cps | Capcom Play System | MAME 2003-Plus | MAME **(Standalone)**,<br>FinalBurn Neo,<br>FinalBurn Neo **(Standalone)** | Depends | See the specific _Arcade and Neo Geo_ section in the user guide |
| cps1 | Capcom Play System I | MAME 2003-Plus | MAME **(Standalone)**,<br>FinalBurn Neo,<br>FinalBurn Neo **(Standalone)** | Depends | See the specific _Arcade and Neo Geo_ section in the user guide |
| cps2 | Capcom Play System II | MAME 2003-Plus | MAME **(Standalone)**,<br>FinalBurn Neo,<br>FinalBurn Neo **(Standalone)** | Depends | See the specific _Arcade and Neo Geo_ section in the user guide |
| cps3 | Capcom Play System III | MAME 2003-Plus | MAME **(Standalone)**,<br>FinalBurn Neo,<br>FinalBurn Neo **(Standalone)** | Depends | See the specific _Arcade and Neo Geo_ section in the user guide |
| crvision | VTech CreatiVision | MAME **(Standalone)** | | Yes | Single archive or ROM file |
| daphne | Daphne Arcade LaserDisc Emulator | MAME **(Standalone)** | | Depends | See the specific _LaserDisc Games_ section in the user guide |
| desktop | Desktop Applications | _Suspend ES-DE_ | _Keep ES-DE running_ | No | See the specific _Ports and desktop applications_ section in the user guide |
| doom | Doom | _Script_ | | No | |
| dos | DOS (PC) | DOSBox-Pure | DOSBox,<br>DOSBox-X **(Standalone)** | No | See the specific _DOS / PC_ section in the user guide |
| dragon32 | Dragon Data Dragon 32 | MAME Dragon 32 [Tape] **(Standalone)** | MAME Dragon 32 [Cartridge] **(Standalone)**,<br>MAME Dragon 64 [Tape] **(Standalone)**,<br>MAME Dragon 64 [Cartridge] **(Standalone)** | Yes | See the specific _Dragon 32 and Tano Dragon_ section in the user guide |
| dreamcast | Sega Dreamcast | Flycast | | No | In separate folder interpreted as a file, with .m3u playlist if multi-disc game |
| easyrpg | EasyRPG Game Engine | EasyRPG | | No | See the specific _EasyRPG Game Engine_ section in the user guide |
| electron | Acorn Electron | MAME [Tape] **(Standalone)** | MAME [Diskette DFS] **(Standalone)**,<br>MAME [Diskette ADFS] **(Standalone)** | Yes | Single archive, or single tape or diskette image file |
| emulators | Emulators | _Suspend ES-DE_ | _Keep ES-DE running_ | No | See the specific _Ports and desktop applications_ section in the user guide |
| epic | Epic Games Store | _Placeholder_ | | | |
| famicom | Nintendo Family Computer | Mesen | Nestopia UE,<br>FCEUmm,<br>Mednafen **(Standalone)** | No | Single archive or ROM file |
| fba | FinalBurn Alpha | _Placeholder_ | | | |
| fbneo | FinalBurn Neo | FinalBurn Neo | FinalBurn Neo **(Standalone)** | Yes | See the specific _Arcade and Neo Geo_ section in the user guide |
| fds | Nintendo Famicom Disk System | Mesen | Nestopia UE,<br>FCEUmm,<br>Mednafen **(Standalone)** | Yes | Single archive or ROM file |
| flash | Adobe Flash | _Placeholder_ | | | |
| fm7 | Fujitsu FM-7 | MAME [FM-7 Diskette] **(Standalone)** | MAME [FM-7 Tape] **(Standalone)**,<br>MAME [FM-7 Software list] **(Standalone)**,<br>MAME [FM77AV Diskette] **(Standalone)**,<br>MAME [FM77AV Tape] **(Standalone)**,<br>MAME [FM77AV Software list] **(Standalone)** | Yes | For tape files you need to manually start the cassette player from the MAME menu after the "load" command, as well as entering the "run" command after loading is complete |
| fmtowns | Fujitsu FM Towns | MAME **(Standalone)** | | Yes | See the specific _Fujitsu FM Towns_ section in the user guide |
| fpinball | Future Pinball | _Placeholder_ | | | |
| gamate | Bit Corporation Gamate | MAME **(Standalone)** | | Yes | Single archive or ROM file |
| gameandwatch | Nintendo Game and Watch | MAME Local Artwork **(Standalone)** | MAME **(Standalone)**,<br>Handheld Electronic (GW) | No | See the specific _LCD handheld games_ section in the user guide |
| gamecom | Tiger Electronics Game.com | MAME **(Standalone)** | | Yes | Single archive or ROM file |
| gamegear | Sega Game Gear | Genesis Plus GX | Genesis Plus GX Wide,<br>Gearsystem,<br>PicoDrive,<br>Mednafen **(Standalone)** | No | Single archive or ROM file |
| gb | Nintendo Game Boy | Gambatte | SameBoy,<br>Gearboy,<br>mGBA,<br>mGBA **(Standalone)**,<br>Mednafen **(Standalone)** | No | Single archive or ROM file |
| gba | Nintendo Game Boy Advance | mGBA | mGBA **(Standalone)**,<br>VBA Next,<br>gpSP,<br>Mednafen **(Standalone)** | No | Single archive or ROM file |
| gbc | Nintendo Game Boy Color | Gambatte | SameBoy,<br>Gearboy,<br>mGBA,<br>mGBA **(Standalone)**,<br>Mednafen **(Standalone)** | No | Single archive or ROM file |
| gc | Nintendo GameCube | _Placeholder_ | | | |
| genesis | Sega Genesis | Genesis Plus GX | Genesis Plus GX Wide,<br>PicoDrive,<br>BlastEm,<br>Mednafen **(Standalone)** | No | Single archive or ROM file |
| gmaster | Hartung Game Master | MAME **(Standalone)** | | Yes | Single archive or ROM file |
| gx4000 | Amstrad GX4000 | Caprice32 | MAME **(Standalone)** | No | Single archive or ROM file |
| intellivision | Mattel Electronics Intellivision | FreeIntv | MAME **(Standalone)** | Yes | Single archive or ROM file |
| j2me | Java 2 Micro Edition (J2ME) | _Placeholder_ | | | |
| kodi | Kodi Home Theatre Software | _Placeholder_ | | | |
| laserdisc | LaserDisc Games | MAME **(Standalone)** | | Depends | See the specific _LaserDisc Games_ section in the user guide |
| lcdgames | LCD Handheld Games | MAME Local Artwork **(Standalone)** | MAME **(Standalone)**,<br>Handheld Electronic (GW) | No | See the specific _LCD handheld games_ section in the user guide |
| lowresnx | LowRes NX Fantasy Console | _Placeholder_ | | | |
| lutris | Lutris Open Gaming Platform | _Placeholder_ | | | |
| lutro | Lutro Game Engine | _Placeholder_ | | | |
| macintosh | Apple Macintosh | MAME Mac SE Bootable **(Standalone)** | MAME Mac SE Boot Disk **(Standalone)**,<br>MAME Mac Plus Bootable **(Standalone)**,<br>MAME Mac Plus Boot Disk **(Standalone)** | Yes | See the specific _Apple Macintosh_ section in the user guide |
| mame | Multiple Arcade Machine Emulator | MAME 2003-Plus | MAME **(Standalone)**,<br>FinalBurn Neo,<br>FinalBurn Neo **(Standalone)**,<br>Geolith,<br>Flycast,<br> _Script_ | Depends | See the specific _Arcade and Neo Geo_ section in the user guide |
| mame-advmame | AdvanceMAME | AdvanceMAME **(Standalone)** | | Depends | See the specific _Arcade and Neo Geo_ section in the user guide |
| mastersystem | Sega Master System | Genesis Plus GX | Genesis Plus GX Wide,<br>Gearsystem,<br>PicoDrive,<br>Mednafen **(Standalone)** | No | Single archive or ROM file |
| megacd | Sega Mega-CD | Genesis Plus GX | Genesis Plus GX Wide,<br>PicoDrive | Yes | |
| megacdjp | Sega Mega-CD [Japan] | Genesis Plus GX | Genesis Plus GX Wide,<br>PicoDrive | Yes | |
| megadrive | Sega Mega Drive | Genesis Plus GX | Genesis Plus GX Wide,<br>PicoDrive,<br>BlastEm,<br>Mednafen **(Standalone)** | No | Single archive or ROM file |
| megadrivejp | Sega Mega Drive [Japan] | Genesis Plus GX | Genesis Plus GX Wide,<br>PicoDrive,<br>BlastEm,<br>Mednafen **(Standalone)** | No | Single archive or ROM file |
| megaduck | Creatronic Mega Duck | _Placeholder_ | | | |
| mess | Multi Emulator Super System | _Placeholder_ | | | |
| model2 | Sega Model 2 | _Placeholder_ | | | |
| model2 | Sega Model 2 | MAME **(Standalone)** | | Yes | See the specific _Arcade and Neo Geo_ section in the user guide |
| model3 | Sega Model 3 | _Placeholder_ | | | |
| moto | Thomson MO/TO Series | _Placeholder_ | | | |
| msx | MSX | blueMSX | fMSX | Yes | |
| msx1 | MSX1 | blueMSX | fMSX | Yes | |
| msx2 | MSX2 | blueMSX | fMSX | Yes | |
| msxturbor | MSX Turbo R | blueMSX | | Yes | |
| mugen | M.U.G.E.N Game Engine | _Placeholder_ | | | |
| multivision | Othello Multivision | Gearsystem | | No | Single archive or ROM file |
| n3ds | Nintendo 3DS | _Placeholder_ | | | |
| n64 | Nintendo 64 | Mupen64Plus-Next | Mupen64Plus **(Standalone)**,<br>ParaLLEl N64 | No | Single archive or ROM file |
| n64dd | Nintendo 64DD | ParaLLEl N64 | Mupen64Plus-Next | Yes | See the specific _Nintendo 64DD_ section in the user guide |
| naomi | Sega NAOMI | Flycast | | Yes | Single archive file + .chd file in subdirectory if GD-ROM game |
| naomi2 | Sega NAOMI 2 | Flycast | | Yes | Single archive file + .chd file in subdirectory if GD-ROM game |
| naomigd | Sega NAOMI GD-ROM | Flycast | | Yes | Single archive file + .chd file in subdirectory if GD-ROM game |
| nds | Nintendo DS | melonDS | melonDS **(Standalone)**,<br>DeSmuME | No | Single archive or ROM file |
| neogeo | SNK Neo Geo | FinalBurn Neo | FinalBurn Neo **(Standalone)**,<br>Geolith,<br>MAME **(Standalone)** | Yes | See the specific _Arcade and Neo Geo_ section in the user guide |
| neogeocd | SNK Neo Geo CD | NeoCD | FinalBurn Neo,<br>FinalBurn Neo **(Standalone)**,<br>MAME **(Standalone)** | Yes | .chd (NeoCD and MAME only) or .cue file |
| neogeocdjp | SNK Neo Geo CD [Japan] | NeoCD | FinalBurn Neo,<br>FinalBurn Neo **(Standalone)**,<br>MAME **(Standalone)** | Yes | .chd (NeoCD and MAME only) or .cue file |
| nes | Nintendo Entertainment System | Mesen | Mesen | Nestopia UE,<br>FCEUmm,<br>Mednafen **(Standalone)** | No | Single archive or ROM file |
| ngage | Nokia N-Gage | _Placeholder_ | | | |
| ngp | SNK Neo Geo Pocket | Beetle NeoPop | Mednafen **(Standalone)** | No | Single archive or ROM file |
| ngpc | SNK Neo Geo Pocket Color | Beetle NeoPop | Mednafen **(Standalone)** | No | Single archive or ROM file |
| odyssey2 | Magnavox Odyssey 2 | O2EM | MAME **(Standalone)** | Yes | Single archive or ROM file |
| openbor | OpenBOR Game Engine | _Placeholder_ | | | |
| oric | Tangerine Computer Systems Oric | MAME **(Standalone)** | | Yes | See the specific _Tangerine Computer Systems Oric_ section in the user guide |
| palm | Palm OS | _Placeholder_ | | | |
| pc | IBM PC | DOSBox-Pure | DOSBox,<br>DOSBox-X **(Standalone)** | No | See the specific _DOS / PC_ section in the user guide |
| pc88 | NEC PC-8800 Series | _Placeholder_ | | | |
| pc98 | NEC PC-9800 Series | Neko Project II | | | |
| pcarcade | PC Arcade Systems | _Script_ | | No | |
| pcengine | NEC PC Engine | Beetle PCE | Beetle PCE FAST,<br>Beetle SuperGrafx,<br>Mednafen **(Standalone)** | No | Single archive or ROM file |
| pcenginecd | NEC PC Engine CD | Beetle PCE | Beetle PCE FAST,<br>Beetle SuperGrafx,<br>Mednafen **(Standalone)** | Yes | |
| pcfx | NEC PC-FX | Beetle PC-FX | Mednafen **(Standalone)** | Yes | |
| pico8 | PICO-8 Fantasy Console | _Placeholder_ | | | |
| plus4 | Commodore Plus/4 | VICE xplus4 | | No | Single archive or image file for tape, cartridge or single-diskette games, .m3u playlist for multi-diskette games |
| pokemini | Nintendo Pokémon Mini | _Placeholder_ | | | |
| ports | Ports | _Script_ | OpenLara (Tomb Raider) | No | See the specific _Ports and desktop applications_ section in the user guide |
| ps2 | Sony PlayStation 2 | _Placeholder_ | | | |
| ps3 | Sony PlayStation 3 | _Placeholder_ | | | |
| ps4 | Sony PlayStation 4 | _Placeholder_ | | | |
| psp | Sony PlayStation Portable | PPSSPP **(Standalone)** | | No | Single disc image file |
| psvita | Sony PlayStation Vita | _Placeholder_ | | | |
| psx | Sony PlayStation | Beetle PSX | Beetle PSX HW,<br>PCSX ReARMed,<br>Mednafen **(Standalone)** | Yes | .chd file for single-disc games, .m3u playlist for multi-disc games |
| pv1000 | Casio PV-1000 | MAME **(Standalone)** | | No | Single archive or ROM file |
| quake | Quake | TyrQuake | _Script_ | No | |
| samcoupe | MGT SAM Coupé | _Placeholder_ | | | |
| satellaview | Nintendo Satellaview | Snes9x - Current | bsnes | | |
| saturn | Sega Saturn | Beetle Saturn | Yabause,<br>Mednafen **(Standalone)** | Yes | .chd file for single-disc games, .m3u playlist for multi-disc games |
| saturnjp | Sega Saturn [Japan] | Beetle Saturn | Yabause,<br>Mednafen **(Standalone)** | Yes | .chd file for single-disc games, .m3u playlist for multi-disc games |
| scummvm | ScummVM Game Engine | ScummVM | ScummVM **(Standalone)** | No | See the specific _ScummVM_ section in the user guide |
| scv | Epoch Super Cassette Vision | MAME **(Standalone)** | | Yes | Single archive or ROM file |
| sega32x | Sega Mega Drive 32X | PicoDrive | | No | Single archive or ROM file |
| sega32xjp | Sega Super 32X [Japan] | PicoDrive | | No | Single archive or ROM file |
| sega32xna | Sega Genesis 32X [North America] | PicoDrive | | No | Single archive or ROM file |
| segacd | Sega CD | Genesis Plus GX | Genesis Plus GX Wide,<br>PicoDrive | Yes | |
| sfc | Nintendo SFC (Super Famicom) | Snes9x - Current | bsnes,<br>Mednafen **(Standalone)** | No | Single archive or ROM file |
| sg-1000 | Sega SG-1000 | Genesis Plus GX | Genesis Plus GX Wide,<br>Gearsystem,<br>blueMSX | No | Single archive or ROM file |
| sgb | Nintendo Super Game Boy | SameBoy | mGBA,<br>mGBA **(Standalone)** | | Single archive or ROM file |
| snes | Nintendo SNES (Super Nintendo) | Snes9x - Current | Snes9x - Current | bsnes,<br>Mednafen **(Standalone)** | No | Single archive or ROM file |
| snesna | Nintendo SNES (Super Nintendo) [North America] | Snes9x - Current | Snes9x - Current | bsnes,<br>Mednafen **(Standalone)** | No | Single archive or ROM file |
| solarus | Solarus Game Engine | Solarus **(Standalone)** | | No | Single .solarus game file |
| spectravideo | Spectravideo | blueMSX | | | |
| steam | Valve Steam | _Placeholder_ | | | |
| stv | Sega Titan Video Game System | MAME **(Standalone)** | Mednafen **(Standalone)** | Yes | Single archive file |
| sufami | Bandai SuFami Turbo | Snes9x - Current | bsnes | | |
| supergrafx | NEC SuperGrafx | Beetle SuperGrafx | Beetle PCE,<br>Mednafen **(Standalone)** | No | Single archive or ROM file |
| supervision | Watara Supervision | _Placeholder_ | | | |
| supracan | Funtech Super A'Can | MAME **(Standalone)** | | Yes | Single archive or ROM file. You need a supracan.zip archive that contains a valid internal_68k.bin file and an empty file named umc6650.bin |
| switch | Nintendo Switch | _Placeholder_ | | | |
| symbian | Symbian | _Placeholder_ | | | |
| tanodragon | Tano Dragon | MAME [Tape] **(Standalone)** | MAME [Cartridge] **(Standalone)** | Yes | See the specific _Dragon 32 and Tano Dragon_ section in the user guide |
| tg16 | NEC TurboGrafx-16 | Beetle PCE | Beetle PCE FAST,<br>Beetle SuperGrafx,<br>Mednafen **(Standalone)** | No | Single archive or ROM file |
| tg-cd | NEC TurboGrafx-CD | Beetle PCE | Beetle PCE FAST,<br>Beetle SuperGrafx,<br>Mednafen **(Standalone)** | Yes | |
| ti99 | Texas Instruments TI-99 | MAME **(Standalone)** | | Yes | See the specific _Texas Instruments TI-99_ section in the user guide |
| tic80 | TIC-80 Fantasy Computer | _Placeholder_ | | | |
| to8 | Thomson TO8 | _Placeholder_ | | | |
| triforce | Namco-Sega-Nintendo Triforce | _Placeholder_ | | | |
| trs-80 | Tandy TRS-80 | _Placeholder_ | | | |
| type-x | Taito Type X | _Placeholder_ | | | |
| uzebox | Uzebox Open Source Console | _Placeholder_ | | | |
| vectrex | GCE Vectrex | vecx | MAME **(Standalone)** | Yes for MAME | Single archive or ROM file |
| vic20 | Commodore VIC-20 | VICE xvic | | No | Single archive or tape, cartridge or diskette image file |
| videopac | Philips Videopac G7000 | O2EM | MAME **(Standalone)** | Yes | Single archive or ROM file |
| virtualboy | Nintendo Virtual Boy | Beetle VB | Mednafen **(Standalone)** | No | |
| vpinball | Visual Pinball | _Placeholder_ | | | |
| vsmile | VTech V.Smile | MAME **(Standalone)** | | Yes | Single archive or ROM file |
| wasm4 | WASM-4 Fantasy Console | _Placeholder_ | | | |
| wii | Nintendo Wii | _Placeholder_ | | | |
| wiiu | Nintendo Wii U | _Placeholder_ | | | |
| windows | Microsoft Windows | _Placeholder_ | | | |
| windows3x | Microsoft Windows 3.x | DOSBox-X **(Standalone)** | DOSBox-Pure,<br> _Script (Suspend ES-DE)_,<br> _Script (Keep ES-DE running)_ | No | See the specific _Microsoft Windows 3.x and 9x_ section in the user guide |
| windows9x | Microsoft Windows 9x | DOSBox-X **(Standalone)** | DOSBox-Pure,<br> _Script (Suspend ES-DE)_,<br> _Script (Keep ES-DE running)_ | No | See the specific _Microsoft Windows 3.x and 9x_ section in the user guide |
| wonderswan | Bandai WonderSwan | Beetle Cygne | Mednafen **(Standalone)** | No | Single archive or ROM file |
| wonderswancolor | Bandai WonderSwan Color | Beetle Cygne | Mednafen **(Standalone)** | No | Single archive or ROM file |
| x1 | Sharp X1 | MAME [Diskette] **(Standalone)** | MAME [Tape] **(Standalone)** | Yes | Single archive or diskette/tape file |
| x68000 | Sharp X68000 | MAME **(Standalone)** | | Yes | |
| xbox | Microsoft Xbox | _Placeholder_ | | | |
| xbox360 | Microsoft Xbox 360 | _Placeholder_ | | | |
| zmachine | Infocom Z-machine | _Placeholder_ | | | |
| zx81 | Sinclair ZX81 | EightyOne | | No | |
| zxnext | Sinclair ZX Spectrum Next | ZEsarUX **(Standalone)** | | No | In separate folder interpreted as a file |
| zxspectrum | Sinclair ZX Spectrum | Fuse | Fuse **(Standalone)** | No | Single archive or ROM file |

View file

@ -25,7 +25,7 @@ There are some dependencies that need to be fulfilled in order to build ES-DE. T
All of the required packages can be installed with apt-get: All of the required packages can be installed with apt-get:
``` ```
sudo apt-get install build-essential clang-format git cmake libsdl2-dev libavcodec-dev libavfilter-dev libavformat-dev libavutil-dev libfreeimage-dev libfreetype6-dev libgit2-dev libcurl4-openssl-dev libpugixml-dev libasound2-dev libgl1-mesa-dev libpoppler-cpp-dev sudo apt-get install build-essential clang-format git cmake gettext libharfbuzz-dev libicu-dev libsdl2-dev libavcodec-dev libavfilter-dev libavformat-dev libavutil-dev libfreeimage-dev libfreetype6-dev libgit2-dev libcurl4-openssl-dev libpugixml-dev libasound2-dev libgl1-mesa-dev libpoppler-cpp-dev
``` ```
**Fedora** **Fedora**
@ -40,7 +40,7 @@ https://download1.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -
Then you can use dnf to install all the required packages: Then you can use dnf to install all the required packages:
``` ```
sudo dnf install gcc-c++ clang-tools-extra cmake libasan rpm-build SDL2-devel ffmpeg-devel freeimage-devel freetype-devel libgit2-devel curl-devel pugixml-devel alsa-lib-devel mesa-libGL-devel poppler-cpp-devel sudo dnf install gcc-c++ clang-tools-extra cmake gettext harfbuzz-devel libicu-devel libasan rpm-build SDL2-devel ffmpeg-devel freeimage-devel freetype-devel libgit2-devel curl-devel pugixml-devel alsa-lib-devel mesa-libGL-devel poppler-cpp-devel
``` ```
**Manjaro** **Manjaro**
@ -48,14 +48,14 @@ sudo dnf install gcc-c++ clang-tools-extra cmake libasan rpm-build SDL2-devel ff
Use pacman to install all the required packages: Use pacman to install all the required packages:
``` ```
sudo pacman -S gcc clang make cmake pkgconf sdl2 ffmpeg freeimage freetype2 libgit2 pugixml poppler sudo pacman -S gcc clang make cmake gettext harfbuzz icu pkgconf sdl2 ffmpeg freeimage freetype2 libgit2 pugixml poppler
``` ```
**Raspberry Pi OS** **Raspberry Pi OS**
All of the required packages can be installed with apt-get: All of the required packages can be installed with apt-get:
``` ```
sudo apt-get install clang-format cmake libraspberrypi-dev libsdl2-dev libavcodec-dev libavfilter-dev libavformat-dev libavutil-dev libfreeimage-dev libfreetype6-dev libgit2-dev libcurl4-gnutls-dev libpugixml-dev libpoppler-cpp-dev sudo apt-get install clang-format cmake gettext libharfbuzz-dev libicu-dev libraspberrypi-dev libsdl2-dev libavcodec-dev libavfilter-dev libavformat-dev libavutil-dev libfreeimage-dev libfreetype6-dev libgit2-dev libcurl4-gnutls-dev libpugixml-dev libpoppler-cpp-dev
``` ```
For a 64-bit build it's very important that you include libraspberrypi-dev because if this package is not installed then the file /usr/include/bcm_host.h is not present on the filesystem. This leads to CMake not detecting that it's indeed a Raspberry Pi and it will attempt to make a regular Linux build instead. For a 64-bit build it's very important that you include libraspberrypi-dev because if this package is not installed then the file /usr/include/bcm_host.h is not present on the filesystem. This leads to CMake not detecting that it's indeed a Raspberry Pi and it will attempt to make a regular Linux build instead.
@ -75,44 +75,21 @@ Only the OpenGL ES 3.0 renderer works on Raspberry Pi and it's enabled by defaul
Use pkg to install the dependencies: Use pkg to install the dependencies:
``` ```
pkg install llvm-devel git pkgconf cmake sdl2 ffmpeg freeimage libgit2 pugixml poppler pkg install llvm-devel git pkgconf cmake gettext harfbuzz icu sdl2 ffmpeg freeimage libgit2 pugixml poppler
``` ```
Clang/LLVM and curl should already be included in the base OS installation. Clang/LLVM and curl should already be included in the base OS installation.
**NetBSD** Note that there is a strange issue specifically on FreeBSD 14.1 where the rlottie library refuses to build. This can be resolved by the following workaround:
Use pkgin to install the dependencies:
``` ```
pkgin install clang git cmake pkgconf SDL2 ffmpeg4 freeimage libgit2 pugixml poppler-cpp echo > external/rlottie/format
``` ```
NetBSD ships with GCC by default, and although you should be able to use Clang/LLVM, it's probably easier to just stick to the default compiler environment. The reason why the clang package needs to be installed is to get clang-format onto the system. It's not clear yet whether this is a compiler bug or some other issue.
**OpenBSD**
Use pkg_add to install the dependencies:
```
pkg_add clang-tools-extra cmake pkgconf sdl2 ffmpeg freeimage libgit2 poppler
```
In the same manner as for FreeBSD, Clang/LLVM and curl should already be installed by default.
The pugixml library does exist in the package collection but somehow this version is not properly detected by CMake, so you need to compile this manually as well:
```
git clone https://github.com/zeux/pugixml.git
cd pugixml
git checkout v1.10
cmake .
make
make install
```
**Cloning and compiling ES-DE** **Cloning and compiling ES-DE**
To clone the source repository, run the following: To clone the source repository, run the following:
``` ```
git clone https://gitlab.com/es-de/emulationstation-de.git git clone https://gitlab.com/es-de/emulationstation-de.git
``` ```
@ -133,7 +110,7 @@ cmake -DAPPLICATION_UPDATER=off .
make make
``` ```
Note that the application updater is always disabled when building for the AUR, RetroDECK, Raspberry Pi or BSD Unix. Note that the application updater is always disabled when building for the AUR, RetroDECK, Raspberry Pi or FreeBSD.
On Linux specifically you can build with the DEINIT_ON_LAUNCH option which will deinit the renderer, application window and audio when an emulator is launched. This makes it possible to use ES-DE with KMS/direct framebuffer access to for example make ES-DE a drop-in replacement for RetroPie EmulationStation: On Linux specifically you can build with the DEINIT_ON_LAUNCH option which will deinit the renderer, application window and audio when an emulator is launched. This makes it possible to use ES-DE with KMS/direct framebuffer access to for example make ES-DE a drop-in replacement for RetroPie EmulationStation:
``` ```
@ -188,6 +165,11 @@ It could also be a good idea to use the `TSAN_suppressions` file included in the
TSAN_OPTIONS="suppressions=tools/TSAN_suppressions" ./es-de --debug --resolution 2560 1440 TSAN_OPTIONS="suppressions=tools/TSAN_suppressions" ./es-de --debug --resolution 2560 1440
``` ```
On some Linux distributions you need to modify the _vm.mmap_rnd_bits_ kernel runtime parameter or you'll see an error message such as _FATAL: ThreadSanitizer: unexpected memory mapping 0x58bd90d75000-0x58bd90dbe000_ when attempting to start ES-DE. Setting the parameter to 28 should make ThreadSanitizer work correctly. The following is how it's done on Ubuntu:
```
sudo sysctl vm.mmap_rnd_bits=28
```
To enable UndefinedBehaviorSanitizer which helps with identifying bugs that may otherwise be hard to find, build with the UBSAN option: To enable UndefinedBehaviorSanitizer which helps with identifying bugs that may otherwise be hard to find, build with the UBSAN option:
``` ```
cmake -DCMAKE_BUILD_TYPE=Debug -UBSAN=on . cmake -DCMAKE_BUILD_TYPE=Debug -UBSAN=on .
@ -269,7 +251,7 @@ make -j8
This renderer is generally only needed on the Raspberry Pi and the desktop OpenGL renderer should otherwise be used. This renderer is generally only needed on the Raspberry Pi and the desktop OpenGL renderer should otherwise be used.
By default ES-DE will install under /usr on Linux, /usr/pkg on NetBSD and /usr/local on FreeBSD and OpenBSD although this can be changed by setting the `CMAKE_INSTALL_PREFIX` variable. By default ES-DE will install under /usr on Linux and /usr/local on FreeBSD although this can be changed by setting the `CMAKE_INSTALL_PREFIX` variable.
The following example will build the application for installtion under /opt: The following example will build the application for installtion under /opt:
@ -437,6 +419,54 @@ This is similar to the regular AppImage but does not build with the BUNDLED_CERT
Both _appimagetool_ and _linuxdeploy_ are required for the build process but they will be downloaded automatically by the script if they don't exist. So to force an update to the latest build tools, delete these two AppImages prior to running the build script. Both _appimagetool_ and _linuxdeploy_ are required for the build process but they will be downloaded automatically by the script if they don't exist. So to force an update to the latest build tools, delete these two AppImages prior to running the build script.
## Building on Haiku
It's recommended to run R1/beta5 as the nightly Haiku builds can be quite unstable.
If running Haiku in KVM/Qemu, make sure to use SATA storage intead of VirtIO storage as you may otherwise experience stability issues and filesystem corruption.
**Local build**
Use pkgman to install the required dependencies:
```
pkgman install cmake gettext curl_devel harfbuzz_devel freeimage_devel pugixml_devel libsdl2_devel libgit2_devel freetype_devel ffmpeg6_devel poppler24_devel
```
To clone the ES-DE source repository, run the following:
```
git clone https://gitlab.com/es-de/emulationstation-de.git
```
You can then go ahead and build the application:
```
cd emulationstation-de
cmake .
make -j8
```
Change the -j flag to whatever amount of parallel threads you want to use for the compilation.
**HaikuPorts package build**
Run the following to build the .hpkg package:
```
cd ~
git clone https://github.com/haikuports/haikuports.git --depth=50
mkdir haikuports/games-emulation/es-de
pkgman install haikuporter
cp /boot/system/settings/haikuports.conf ~/config/settings/
cd emulationstation-de
cp es-app/assets/es_de-3.1.0.recipe ~/haikuports/games-emulation/es-de
haikuporter -S --no-source-packages --get-dependencies -j8 es_de
```
The first time you run haikuporter it will take a while as dependencies for all ports will get updated before the ES-DE build process starts.
Following this you can install the package into the running system:
```
cp ~/haikuports/packages/es_de-3.1.0-1-x86_64.hpkg /boot/system/packages
```
## Building on macOS ## Building on macOS
ES-DE for macOS is built using Clang/LLVM which is the default compiler for this operating system. It's pretty straightforward to build software on this OS. The main problem is that there is no native package manager, but as there are several third party package managers available, this can be partly compensated for. The use of one of them, [Homebrew](https://brew.sh), is detailed below. ES-DE for macOS is built using Clang/LLVM which is the default compiler for this operating system. It's pretty straightforward to build software on this OS. The main problem is that there is no native package manager, but as there are several third party package managers available, this can be partly compensated for. The use of one of them, [Homebrew](https://brew.sh), is detailed below.
@ -650,29 +680,23 @@ CPack: - package: /Users/myusername/emulationstation-de/ES-DE_3.0.0-arm64.dmg ge
Only the Microsoft Visual C++ (MSVC) compiler is supported on Windows. Although MinGW/GCC produces higher quality code with ES-DE running around 10% to 25% faster it's unfortunately not sustainable to use it. There are multiple technical issues with third party libraries like severe threading issues with FFmpeg and some libraries like Poppler not being readily available. Only the Microsoft Visual C++ (MSVC) compiler is supported on Windows. Although MinGW/GCC produces higher quality code with ES-DE running around 10% to 25% faster it's unfortunately not sustainable to use it. There are multiple technical issues with third party libraries like severe threading issues with FFmpeg and some libraries like Poppler not being readily available.
**MSVC setup**
Install Git for Windows: \ Install Git for Windows: \
[https://gitforwindows.org](https://gitforwindows.org) https://gitforwindows.org
Download the Visual Studio Build Tools (choose Visual Studio Community edition): \ Download the Visual Studio Build Tools (choose Visual Studio Community edition): \
[https://visualstudio.microsoft.com/downloads](https://visualstudio.microsoft.com/downloads) https://visualstudio.microsoft.com/downloads
During installation, choose the Desktop development with C++ workload with the following options (version details may differ): During installation, choose the Desktop development with C++ workload with the following options:
``` ```
MSVC v143 - VS 2022 C++ x64/x86 build tools (Latest) MSVC v143 - VS 2022 C++ x64/x86 build tools (Latest)
Windows 10 SDK
Just-In-Time debugger Just-In-Time debugger
C++ AddressSanitizer
```
If you will only use MSVC and not MinGW, then also add this option:
```
C++ CMake tools for Windows C++ CMake tools for Windows
C++ AddressSanitizer
Windows 10 SDK (10.0.20348.0)
``` ```
If not installing the CMake version supplied by Microsoft, you need to make sure that you have a recent version on your machine or CMake will not be able to detect MSVC correctly. The Windows SDK version is important, it has to be this precise version or some dependencies may not build correctly.
It's strongly recommended to also install Jom, which is a drop-in replacement for nmake that offers support for building in parallel using multiple CPU cores:\ It's strongly recommended to also install Jom, which is a drop-in replacement for nmake that offers support for building in parallel using multiple CPU cores:\
https://wiki.qt.io/Jom https://wiki.qt.io/Jom
@ -688,7 +712,7 @@ It's important to choose the x64-specific shell and not the x86 variant, as ES-D
**Other preparations** **Other preparations**
In order to get clang-format onto the system you need to download and install Clang/LLVM: \ In order to get clang-format onto the system you need to download and install Clang/LLVM: \
[https://releases.llvm.org](https://releases.llvm.org) https://releases.llvm.org
Just run the installer and make sure to select the option _Add LLVM to the system PATH for current user_. Just run the installer and make sure to select the option _Add LLVM to the system PATH for current user_.
@ -768,7 +792,7 @@ On Windows the certificates supplied with the operating system will not be utili
**Running with OpenGL software rendering** **Running with OpenGL software rendering**
If you are running Windows in a virtualized environment such as QEMU-KVM that does not support HW accelerated OpenGL, you can install the Mesa3D for Windows library, which can be downloaded at [https://fdossena.com/?p=mesa/index.frag](https://fdossena.com/?p=mesa/index.frag). If you are running Windows in a virtualized environment such as QEMU-KVM that does not support HW accelerated OpenGL, you can install the Mesa3D for Windows library, which can be downloaded at https://fdossena.com/?p=mesa/index.frag
You simply extract the opengl32.dll file into the ES-DE directory and this will enable the llvmpipe renderer. The performance will be terrible of course, but everything should work and it should be good enough for test building on Windows without having to reboot your computer to a native Windows installation. (Note that you may need to copy opengl32.dll to your RetroArch installation directory as well to get the emulators to work somehow correctly.) You simply extract the opengl32.dll file into the ES-DE directory and this will enable the llvmpipe renderer. The performance will be terrible of course, but everything should work and it should be good enough for test building on Windows without having to reboot your computer to a native Windows installation. (Note that you may need to copy opengl32.dll to your RetroArch installation directory as well to get the emulators to work somehow correctly.)
@ -778,7 +802,7 @@ Obviously this library is only intended for development and will not be shipped
To create an NSIS installer (Nullsoft Scriptable Install System) you need to first install the NSIS creation tool: To create an NSIS installer (Nullsoft Scriptable Install System) you need to first install the NSIS creation tool:
[https://nsis.sourceforge.io/Download](https://nsis.sourceforge.io/Download) https://nsis.sourceforge.io/Download
Simply install the application using its installer. Simply install the application using its installer.
@ -921,9 +945,9 @@ Of course you would like to get the code formatted according to the clang-format
There are some files shipped with ES-DE that need to be pulled from external resources, the first one being the CA certificate bundle to get TLS/SSL support working on Windows. There are some files shipped with ES-DE that need to be pulled from external resources, the first one being the CA certificate bundle to get TLS/SSL support working on Windows.
The CA certificates shipped with ES-DE come directly from the curl project but they're originally supplied by the Mozilla foundation. See [https://wiki.mozilla.org/CA](https://wiki.mozilla.org/CA) for more information about this certificate bundle. The CA certificates shipped with ES-DE come directly from the curl project but they're originally supplied by the Mozilla foundation. See https://wiki.mozilla.org/CA for more information about this certificate bundle.
The latest version can be downloaded from [https://curl.se/docs/caextract.html](https://curl.se/docs/caextract.html) The latest version can be downloaded from https://curl.se/docs/caextract.html
After downloading the file, rename it from `cacert.pem` to `curl-ca-bundle.crt` and move it to the certificates directory i.e.: After downloading the file, rename it from `cacert.pem` to `curl-ca-bundle.crt` and move it to the certificates directory i.e.:
@ -935,7 +959,7 @@ emulationstation-de/resources/certificates/curl-ca-bundle.crt
ES-DE automatically identifies and excludes MAME BIOS and device files, as well as translating the short MAME ROM names to their full game names. This is done using information from the MAME driver file shipped with the official MAME distribution. The file needs to be converted to an internal format used by ES-DE as the original file is huge and most of the information is not required. ES-DE automatically identifies and excludes MAME BIOS and device files, as well as translating the short MAME ROM names to their full game names. This is done using information from the MAME driver file shipped with the official MAME distribution. The file needs to be converted to an internal format used by ES-DE as the original file is huge and most of the information is not required.
To get hold of the driver file, go to [https://www.mamedev.org/release.php](https://www.mamedev.org/release.php) and select the Windows version, but only download the driver information in XML format and not MAME itself. This file will be named something like `mame0226lx.zip` and unzipping it will give you a filename such as `mame0226.xml`. To get hold of the driver file, go to https://www.mamedev.org/release.php and select the Windows version, but only download the driver information in XML format and not MAME itself. This file will be named something like `mame0226lx.zip` and unzipping it will give you a filename such as `mame0226.xml`.
Move the XML driver file to the resources/MAME directory and then convert it to the ES-DE internal files: Move the XML driver file to the resources/MAME directory and then convert it to the ES-DE internal files:
@ -1762,9 +1786,9 @@ The es_systems.xml file on Android utilizes variables heavily to implement the _
There are two main ways to pass options to emulators, using _extras_ or using the _data_ URI. There can only be a single data URI but there can be an arbitrary amount of extras. To understand more about the way this works, you can read about the _putExtra()_ and and _setData()_ functions here:\ There are two main ways to pass options to emulators, using _extras_ or using the _data_ URI. There can only be a single data URI but there can be an arbitrary amount of extras. To understand more about the way this works, you can read about the _putExtra()_ and and _setData()_ functions here:\
https://developer.android.com/reference/android/content/Intent https://developer.android.com/reference/android/content/Intent
`%EXTRA_` - This passes an _extra_ which contains any additional information that the emulator may support. This is provided as a key/value pair where you define the key name following the literal %EXTRA_ string and terminate it with a % sign and then assign the value using an equal sign. For example %EXTRA_LIBRETRO%=puae_libretro_android.so will pass the extra named _LIBRETRO_ with its value set to _puae_libretro_android.so_. You can pass an unlimited number of extras and you can also use various ROM variables in combination with this as described below. It's also possible to use the `%GAMEDIRRAW%`, `%ROMPATHRAW%` and `%ROMRAW%` variables inside an `%EXTRA_` variable definition, which will expand to the the directory of the game file, the ROM directory and the path to the game file respectively. `%EXTRA_` - This passes an _extra_ which contains any additional information that the emulator may support. This is provided as a key/value pair where you define the key name following the literal %EXTRA_ string and terminate it with a % sign and then assign the value using an equal sign. For example %EXTRA_LIBRETRO%=puae_libretro_android.so will pass the extra named _LIBRETRO_ with its value set to _puae_libretro_android.so_. You can pass an unlimited number of extras and you can also use various ROM variables in combination with this as described below. It's also possible to use the `%BASENAME%`, `%GAMEDIRRAW%`, `%ROMPATHRAW%` and `%ROMRAW%` variables inside an `%EXTRA_` variable definition, which will expand to the basename of the game file, the directory of the game file, the ROM directory and the path to the game file respectively.
`%EXTRAARRAY_` - Defines an array of comma-separated string values following the key name. Only literal strings are supported, so this can't be used in combination with any ROM variables. As commas are used as separator characters, you'll need to escape any comma signs that you want to include in the actual value. For example %EXTRAARRAY_Parameters%=pone,p\\,two,pthree will pass the extra named _Parameters_ with the three separate array entries _pone_, _p,two_ and _pthree_. It's also possible to use the `%GAMEDIRRAW%`, `%ROMPATHRAW%` and `%ROMRAW%` variables inside an `%EXTRAARRAY_` variable definition, which will expand to the the directory of the game file, the ROM directory and the path to the game file respectively. `%EXTRAARRAY_` - Defines an array of comma-separated string values following the key name. Only literal strings and special variables are supported, so this can't be used in combination with any ROM variables. As commas are used as separator characters, you'll need to escape any comma signs that you want to include in the actual value. For example %EXTRAARRAY_Parameters%=pone,p\\,two,pthree will pass the extra named _Parameters_ with the three separate array entries _pone_, _p,two_ and _pthree_. It's also possible to use the `%BASENAME%`, `%GAMEDIRRAW%`, `%ROMPATHRAW%` and `%ROMRAW%` variables inside an `%EXTRAARRAY_` variable definition, which will expand to the basename of the game file, the directory of the game file, the ROM directory and the path to the game file respectively.
`%EXTRABOOL_` - Sets an extra with a boolean value, i.e. true/1 or false/0. `%EXTRABOOL_` - Sets an extra with a boolean value, i.e. true/1 or false/0.
@ -2136,7 +2160,7 @@ Just make sure to not place the portable installation on a network share that us
There are numerous locations throughout ES-DE where custom scripts can be executed if the option to do so has been enabled in the settings. You'll find the option _Enable custom event scripts_ on the Main menu under _Other settings_. By default this setting is deactivated so make sure to enable it to use this feature. There are numerous locations throughout ES-DE where custom scripts can be executed if the option to do so has been enabled in the settings. You'll find the option _Enable custom event scripts_ on the Main menu under _Other settings_. By default this setting is deactivated so make sure to enable it to use this feature.
The approach is quite straightforward, ES-DE will look for any files inside a script directory that corresponds to the event that is triggered and will then attempt to execute all these files (regardless of their file extensions). If you want to have the scripts executed in a certain order you can name them accordingly as they will be sorted and executed in lexicographic order. The sorting is case-sensitive on Unix/Linux and case-insensitive on macOS and Windows. ES-DE will wait for each script to finish its execution before moving on to the next one, so the application will suspend briefly when whatever the script is doing is executing. If you want to avoid this you can setup a wrapper script that executes another script outside the ES-DE scripts directory as a background process. Refer to your operating system documentation on how to accomplish this. The approach is quite straightforward, ES-DE will look for any files inside a script directory that corresponds to the event that is triggered and will then attempt to execute all these files (regardless of their file extensions). If you want to have the scripts executed in a certain order you can name them accordingly as they will be sorted and executed in lexicographic order. The sorting is case-sensitive on Linux and Android and case-insensitive on macOS and Windows. ES-DE will wait for each script to finish its execution before moving on to the next one, so the application will suspend briefly when whatever the script is doing is executing. If you want to avoid this you can setup a wrapper script that executes another script outside the ES-DE scripts directory as a background process. Refer to your operating system documentation on how to accomplish this.
On Windows it's also possible to place .lnk shortcut files in the event directories to have these executed in the same manner as a script. Note that while PowerShell scripts can't be executed directly they can be run via either a .lnk shortcut file or a .bat wrapper script where you explicitly call powershell.exe with the -command flag. Just be aware that by default the execution of PowerShell scripts is disabled on Windows. Further details about PowerShell is beyond the scope of this document. On Windows it's also possible to place .lnk shortcut files in the event directories to have these executed in the same manner as a script. Note that while PowerShell scripts can't be executed directly they can be run via either a .lnk shortcut file or a .bat wrapper script where you explicitly call powershell.exe with the -command flag. Just be aware that by default the execution of PowerShell scripts is disabled on Windows. Further details about PowerShell is beyond the scope of this document.
@ -2163,11 +2187,11 @@ We'll go through two examples:
* Creating a log file that will record the start and end time for each game we play, letting us see how much time we spend on retro-gaming * Creating a log file that will record the start and end time for each game we play, letting us see how much time we spend on retro-gaming
* Changing the system resolution when launching and returning from a game in order to run the emulator at a lower resolution than ES-DE * Changing the system resolution when launching and returning from a game in order to run the emulator at a lower resolution than ES-DE
The following examples are for Unix systems, but it works the same way on macOS and Windows (although .bat batch files are used on Windows instead of shell scripts and any spaces in the parameters are not escaped as is the case on Unix and macOS). The following examples are for Linux systems, but it works the same way on Android, macOS and Windows (although .bat batch files are used on Windows instead of shell scripts and any spaces in the parameters are not escaped as is the case on Linux, Android and macOS).
As can be seen in the table above, the events executed when a game starts and ends are named _game-start_ and _game-end_ As can be seen in the table above, the events executed when a game starts and ends are named _game-start_ and _game-end_
So let's create the folders for these events in the scripts directory. The location is `~/ES-DE/scripts` So let's create the folders for these events inside the application data directory, or more specifically in `~/ES-DE/scripts`
**Game log** **Game log**

View file

@ -23,7 +23,7 @@ There are some dependencies that need to be fulfilled in order to build ES-DE. T
All of the required packages can be installed with apt-get: All of the required packages can be installed with apt-get:
``` ```
sudo apt-get install build-essential clang-format git cmake libsdl2-dev libavcodec-dev libavfilter-dev libavformat-dev libavutil-dev libfreeimage-dev libfreetype6-dev libgit2-dev libcurl4-openssl-dev libpugixml-dev libasound2-dev libgl1-mesa-dev libpoppler-cpp-dev sudo apt-get install build-essential clang-format git cmake gettext libharfbuzz-dev libicu-dev libsdl2-dev libavcodec-dev libavfilter-dev libavformat-dev libavutil-dev libfreeimage-dev libfreetype6-dev libgit2-dev libcurl4-openssl-dev libpugixml-dev libasound2-dev libgl1-mesa-dev libpoppler-cpp-dev
``` ```
**Fedora** **Fedora**
@ -38,7 +38,7 @@ https://download1.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -
Then you can use dnf to install all the required packages: Then you can use dnf to install all the required packages:
``` ```
sudo dnf install gcc-c++ clang-tools-extra cmake libasan rpm-build SDL2-devel ffmpeg-devel freeimage-devel freetype-devel libgit2-devel curl-devel pugixml-devel alsa-lib-devel mesa-libGL-devel poppler-cpp-devel sudo dnf install gcc-c++ clang-tools-extra cmake gettext harfbuzz-devel libicu-devel libasan rpm-build SDL2-devel ffmpeg-devel freeimage-devel freetype-devel libgit2-devel curl-devel pugixml-devel alsa-lib-devel mesa-libGL-devel poppler-cpp-devel
``` ```
**Manjaro** **Manjaro**
@ -46,14 +46,14 @@ sudo dnf install gcc-c++ clang-tools-extra cmake libasan rpm-build SDL2-devel ff
Use pacman to install all the required packages: Use pacman to install all the required packages:
``` ```
sudo pacman -S gcc clang make cmake pkgconf sdl2 ffmpeg freeimage freetype2 libgit2 pugixml poppler sudo pacman -S gcc clang make cmake gettext harfbuzz icu pkgconf sdl2 ffmpeg freeimage freetype2 libgit2 pugixml poppler
``` ```
**Raspberry Pi OS** **Raspberry Pi OS**
All of the required packages can be installed with apt-get: All of the required packages can be installed with apt-get:
``` ```
sudo apt-get install clang-format cmake libraspberrypi-dev libsdl2-dev libavcodec-dev libavfilter-dev libavformat-dev libavutil-dev libfreeimage-dev libfreetype6-dev libgit2-dev libcurl4-gnutls-dev libpugixml-dev libpoppler-cpp-dev sudo apt-get install clang-format cmake gettext libharfbuzz-dev libicu-dev libraspberrypi-dev libsdl2-dev libavcodec-dev libavfilter-dev libavformat-dev libavutil-dev libfreeimage-dev libfreetype6-dev libgit2-dev libcurl4-gnutls-dev libpugixml-dev libpoppler-cpp-dev
``` ```
For a 64-bit build it's very important that you include libraspberrypi-dev because if this package is not installed then the file /usr/include/bcm_host.h is not present on the filesystem. This leads to CMake not detecting that it's indeed a Raspberry Pi and it will attempt to make a regular Linux build instead. For a 64-bit build it's very important that you include libraspberrypi-dev because if this package is not installed then the file /usr/include/bcm_host.h is not present on the filesystem. This leads to CMake not detecting that it's indeed a Raspberry Pi and it will attempt to make a regular Linux build instead.
@ -73,44 +73,21 @@ Only the OpenGL ES 3.0 renderer works on Raspberry Pi and it's enabled by defaul
Use pkg to install the dependencies: Use pkg to install the dependencies:
``` ```
pkg install llvm-devel git pkgconf cmake sdl2 ffmpeg freeimage libgit2 pugixml poppler pkg install llvm-devel git pkgconf cmake gettext harfbuzz icu sdl2 ffmpeg freeimage libgit2 pugixml poppler
``` ```
Clang/LLVM and curl should already be included in the base OS installation. Clang/LLVM and curl should already be included in the base OS installation.
**NetBSD** Note that there is a strange issue specifically on FreeBSD 14.1 where the rlottie library refuses to build. This can be resolved by the following workaround:
Use pkgin to install the dependencies:
``` ```
pkgin install clang git cmake pkgconf SDL2 ffmpeg4 freeimage libgit2 pugixml poppler-cpp echo > external/rlottie/format
``` ```
NetBSD ships with GCC by default, and although you should be able to use Clang/LLVM, it's probably easier to just stick to the default compiler environment. The reason why the clang package needs to be installed is to get clang-format onto the system. It's not clear yet whether this is a compiler bug or some other issue.
**OpenBSD**
Use pkg_add to install the dependencies:
```
pkg_add clang-tools-extra cmake pkgconf sdl2 ffmpeg freeimage libgit2 poppler
```
In the same manner as for FreeBSD, Clang/LLVM and curl should already be installed by default.
The pugixml library does exist in the package collection but somehow this version is not properly detected by CMake, so you need to compile this manually as well:
```
git clone https://github.com/zeux/pugixml.git
cd pugixml
git checkout v1.10
cmake .
make
make install
```
**Cloning and compiling ES-DE** **Cloning and compiling ES-DE**
To clone the source repository, run the following: To clone the source repository, run the following:
``` ```
git clone https://gitlab.com/es-de/emulationstation-de.git git clone https://gitlab.com/es-de/emulationstation-de.git
``` ```
@ -131,7 +108,7 @@ cmake -DAPPLICATION_UPDATER=off .
make make
``` ```
Note that the application updater is always disabled when building for the AUR, RetroDECK, Raspberry Pi or BSD Unix. Note that the application updater is always disabled when building for the AUR, RetroDECK, Raspberry Pi or FreeBSD.
On Linux specifically you can build with the DEINIT_ON_LAUNCH option which will deinit the renderer, application window and audio when an emulator is launched. This makes it possible to use ES-DE with KMS/direct framebuffer access to for example make ES-DE a drop-in replacement for RetroPie EmulationStation: On Linux specifically you can build with the DEINIT_ON_LAUNCH option which will deinit the renderer, application window and audio when an emulator is launched. This makes it possible to use ES-DE with KMS/direct framebuffer access to for example make ES-DE a drop-in replacement for RetroPie EmulationStation:
``` ```
@ -186,6 +163,11 @@ It could also be a good idea to use the `TSAN_suppressions` file included in the
TSAN_OPTIONS="suppressions=tools/TSAN_suppressions" ./es-de --debug --resolution 2560 1440 TSAN_OPTIONS="suppressions=tools/TSAN_suppressions" ./es-de --debug --resolution 2560 1440
``` ```
On some Linux distributions you need to modify the _vm.mmap_rnd_bits_ kernel runtime parameter or you'll see an error message such as _FATAL: ThreadSanitizer: unexpected memory mapping 0x58bd90d75000-0x58bd90dbe000_ when attempting to start ES-DE. Setting the parameter to 28 should make ThreadSanitizer work correctly. The following is how it's done on Ubuntu:
```
sudo sysctl vm.mmap_rnd_bits=28
```
To enable UndefinedBehaviorSanitizer which helps with identifying bugs that may otherwise be hard to find, build with the UBSAN option: To enable UndefinedBehaviorSanitizer which helps with identifying bugs that may otherwise be hard to find, build with the UBSAN option:
``` ```
cmake -DCMAKE_BUILD_TYPE=Debug -UBSAN=on . cmake -DCMAKE_BUILD_TYPE=Debug -UBSAN=on .
@ -267,7 +249,7 @@ make -j8
This renderer is generally only needed on the Raspberry Pi and the desktop OpenGL renderer should otherwise be used. This renderer is generally only needed on the Raspberry Pi and the desktop OpenGL renderer should otherwise be used.
By default ES-DE will install under /usr on Linux, /usr/pkg on NetBSD and /usr/local on FreeBSD and OpenBSD although this can be changed by setting the `CMAKE_INSTALL_PREFIX` variable. By default ES-DE will install under /usr on Linux and /usr/local on FreeBSD although this can be changed by setting the `CMAKE_INSTALL_PREFIX` variable.
The following example will build the application for installtion under /opt: The following example will build the application for installtion under /opt:
@ -435,6 +417,54 @@ This is similar to the regular AppImage but does not build with the BUNDLED_CERT
Both _appimagetool_ and _linuxdeploy_ are required for the build process but they will be downloaded automatically by the script if they don't exist. So to force an update to the latest build tools, delete these two AppImages prior to running the build script. Both _appimagetool_ and _linuxdeploy_ are required for the build process but they will be downloaded automatically by the script if they don't exist. So to force an update to the latest build tools, delete these two AppImages prior to running the build script.
## Building on Haiku
It's recommended to run R1/beta5 as the nightly Haiku builds can be quite unstable.
If running Haiku in KVM/Qemu, make sure to use SATA storage intead of VirtIO storage as you may otherwise experience stability issues and filesystem corruption.
**Local build**
Use pkgman to install the required dependencies:
```
pkgman install cmake gettext curl_devel harfbuzz_devel freeimage_devel pugixml_devel libsdl2_devel libgit2_devel freetype_devel ffmpeg6_devel poppler24_devel
```
To clone the ES-DE source repository, run the following:
```
git clone https://gitlab.com/es-de/emulationstation-de.git
```
You can then go ahead and build the application:
```
cd emulationstation-de
cmake .
make -j8
```
Change the -j flag to whatever amount of parallel threads you want to use for the compilation.
**HaikuPorts package build**
Run the following to build the .hpkg package:
```
cd ~
git clone https://github.com/haikuports/haikuports.git --depth=50
mkdir haikuports/games-emulation/es-de
pkgman install haikuporter
cp /boot/system/settings/haikuports.conf ~/config/settings/
cd emulationstation-de
cp es-app/assets/es_de-3.1.0.recipe ~/haikuports/games-emulation/es-de
haikuporter -S --no-source-packages --get-dependencies -j8 es_de
```
The first time you run haikuporter it will take a while as dependencies for all ports will get updated before the ES-DE build process starts.
Following this you can install the package into the running system:
```
cp ~/haikuports/packages/es_de-3.1.0-1-x86_64.hpkg /boot/system/packages
```
## Building on macOS ## Building on macOS
ES-DE for macOS is built using Clang/LLVM which is the default compiler for this operating system. It's pretty straightforward to build software on this OS. The main problem is that there is no native package manager, but as there are several third party package managers available, this can be partly compensated for. The use of one of them, [Homebrew](https://brew.sh), is detailed below. ES-DE for macOS is built using Clang/LLVM which is the default compiler for this operating system. It's pretty straightforward to build software on this OS. The main problem is that there is no native package manager, but as there are several third party package managers available, this can be partly compensated for. The use of one of them, [Homebrew](https://brew.sh), is detailed below.
@ -648,29 +678,23 @@ CPack: - package: /Users/myusername/emulationstation-de/ES-DE_3.0.0-arm64.dmg ge
Only the Microsoft Visual C++ (MSVC) compiler is supported on Windows. Although MinGW/GCC produces higher quality code with ES-DE running around 10% to 25% faster it's unfortunately not sustainable to use it. There are multiple technical issues with third party libraries like severe threading issues with FFmpeg and some libraries like Poppler not being readily available. Only the Microsoft Visual C++ (MSVC) compiler is supported on Windows. Although MinGW/GCC produces higher quality code with ES-DE running around 10% to 25% faster it's unfortunately not sustainable to use it. There are multiple technical issues with third party libraries like severe threading issues with FFmpeg and some libraries like Poppler not being readily available.
**MSVC setup**
Install Git for Windows: \ Install Git for Windows: \
[https://gitforwindows.org](https://gitforwindows.org) https://gitforwindows.org
Download the Visual Studio Build Tools (choose Visual Studio Community edition): \ Download the Visual Studio Build Tools (choose Visual Studio Community edition): \
[https://visualstudio.microsoft.com/downloads](https://visualstudio.microsoft.com/downloads) https://visualstudio.microsoft.com/downloads
During installation, choose the Desktop development with C++ workload with the following options (version details may differ): During installation, choose the Desktop development with C++ workload with the following options:
``` ```
MSVC v143 - VS 2022 C++ x64/x86 build tools (Latest) MSVC v143 - VS 2022 C++ x64/x86 build tools (Latest)
Windows 10 SDK
Just-In-Time debugger Just-In-Time debugger
C++ AddressSanitizer
```
If you will only use MSVC and not MinGW, then also add this option:
```
C++ CMake tools for Windows C++ CMake tools for Windows
C++ AddressSanitizer
Windows 10 SDK (10.0.20348.0)
``` ```
If not installing the CMake version supplied by Microsoft, you need to make sure that you have a recent version on your machine or CMake will not be able to detect MSVC correctly. The Windows SDK version is important, it has to be this precise version or some dependencies may not build correctly.
It's strongly recommended to also install Jom, which is a drop-in replacement for nmake that offers support for building in parallel using multiple CPU cores:\ It's strongly recommended to also install Jom, which is a drop-in replacement for nmake that offers support for building in parallel using multiple CPU cores:\
https://wiki.qt.io/Jom https://wiki.qt.io/Jom
@ -686,7 +710,7 @@ It's important to choose the x64-specific shell and not the x86 variant, as ES-D
**Other preparations** **Other preparations**
In order to get clang-format onto the system you need to download and install Clang/LLVM: \ In order to get clang-format onto the system you need to download and install Clang/LLVM: \
[https://releases.llvm.org](https://releases.llvm.org) https://releases.llvm.org
Just run the installer and make sure to select the option _Add LLVM to the system PATH for current user_. Just run the installer and make sure to select the option _Add LLVM to the system PATH for current user_.
@ -766,7 +790,7 @@ On Windows the certificates supplied with the operating system will not be utili
**Running with OpenGL software rendering** **Running with OpenGL software rendering**
If you are running Windows in a virtualized environment such as QEMU-KVM that does not support HW accelerated OpenGL, you can install the Mesa3D for Windows library, which can be downloaded at [https://fdossena.com/?p=mesa/index.frag](https://fdossena.com/?p=mesa/index.frag). If you are running Windows in a virtualized environment such as QEMU-KVM that does not support HW accelerated OpenGL, you can install the Mesa3D for Windows library, which can be downloaded at https://fdossena.com/?p=mesa/index.frag
You simply extract the opengl32.dll file into the ES-DE directory and this will enable the llvmpipe renderer. The performance will be terrible of course, but everything should work and it should be good enough for test building on Windows without having to reboot your computer to a native Windows installation. (Note that you may need to copy opengl32.dll to your RetroArch installation directory as well to get the emulators to work somehow correctly.) You simply extract the opengl32.dll file into the ES-DE directory and this will enable the llvmpipe renderer. The performance will be terrible of course, but everything should work and it should be good enough for test building on Windows without having to reboot your computer to a native Windows installation. (Note that you may need to copy opengl32.dll to your RetroArch installation directory as well to get the emulators to work somehow correctly.)
@ -776,7 +800,7 @@ Obviously this library is only intended for development and will not be shipped
To create an NSIS installer (Nullsoft Scriptable Install System) you need to first install the NSIS creation tool: To create an NSIS installer (Nullsoft Scriptable Install System) you need to first install the NSIS creation tool:
[https://nsis.sourceforge.io/Download](https://nsis.sourceforge.io/Download) https://nsis.sourceforge.io/Download
Simply install the application using its installer. Simply install the application using its installer.
@ -919,9 +943,9 @@ Of course you would like to get the code formatted according to the clang-format
There are some files shipped with ES-DE that need to be pulled from external resources, the first one being the CA certificate bundle to get TLS/SSL support working on Windows. There are some files shipped with ES-DE that need to be pulled from external resources, the first one being the CA certificate bundle to get TLS/SSL support working on Windows.
The CA certificates shipped with ES-DE come directly from the curl project but they're originally supplied by the Mozilla foundation. See [https://wiki.mozilla.org/CA](https://wiki.mozilla.org/CA) for more information about this certificate bundle. The CA certificates shipped with ES-DE come directly from the curl project but they're originally supplied by the Mozilla foundation. See https://wiki.mozilla.org/CA for more information about this certificate bundle.
The latest version can be downloaded from [https://curl.se/docs/caextract.html](https://curl.se/docs/caextract.html) The latest version can be downloaded from https://curl.se/docs/caextract.html
After downloading the file, rename it from `cacert.pem` to `curl-ca-bundle.crt` and move it to the certificates directory i.e.: After downloading the file, rename it from `cacert.pem` to `curl-ca-bundle.crt` and move it to the certificates directory i.e.:
@ -933,7 +957,7 @@ emulationstation-de/resources/certificates/curl-ca-bundle.crt
ES-DE automatically identifies and excludes MAME BIOS and device files, as well as translating the short MAME ROM names to their full game names. This is done using information from the MAME driver file shipped with the official MAME distribution. The file needs to be converted to an internal format used by ES-DE as the original file is huge and most of the information is not required. ES-DE automatically identifies and excludes MAME BIOS and device files, as well as translating the short MAME ROM names to their full game names. This is done using information from the MAME driver file shipped with the official MAME distribution. The file needs to be converted to an internal format used by ES-DE as the original file is huge and most of the information is not required.
To get hold of the driver file, go to [https://www.mamedev.org/release.php](https://www.mamedev.org/release.php) and select the Windows version, but only download the driver information in XML format and not MAME itself. This file will be named something like `mame0226lx.zip` and unzipping it will give you a filename such as `mame0226.xml`. To get hold of the driver file, go to https://www.mamedev.org/release.php and select the Windows version, but only download the driver information in XML format and not MAME itself. This file will be named something like `mame0226lx.zip` and unzipping it will give you a filename such as `mame0226.xml`.
Move the XML driver file to the resources/MAME directory and then convert it to the ES-DE internal files: Move the XML driver file to the resources/MAME directory and then convert it to the ES-DE internal files:
@ -1760,9 +1784,9 @@ The es_systems.xml file on Android utilizes variables heavily to implement the _
There are two main ways to pass options to emulators, using _extras_ or using the _data_ URI. There can only be a single data URI but there can be an arbitrary amount of extras. To understand more about the way this works, you can read about the _putExtra()_ and and _setData()_ functions here:\ There are two main ways to pass options to emulators, using _extras_ or using the _data_ URI. There can only be a single data URI but there can be an arbitrary amount of extras. To understand more about the way this works, you can read about the _putExtra()_ and and _setData()_ functions here:\
https://developer.android.com/reference/android/content/Intent https://developer.android.com/reference/android/content/Intent
`%EXTRA_` - This passes an _extra_ which contains any additional information that the emulator may support. This is provided as a key/value pair where you define the key name following the literal %EXTRA_ string and terminate it with a % sign and then assign the value using an equal sign. For example %EXTRA_LIBRETRO%=puae_libretro_android.so will pass the extra named _LIBRETRO_ with its value set to _puae_libretro_android.so_. You can pass an unlimited number of extras and you can also use various ROM variables in combination with this as described below. It's also possible to use the `%GAMEDIRRAW%`, `%ROMPATHRAW%` and `%ROMRAW%` variables inside an `%EXTRA_` variable definition, which will expand to the the directory of the game file, the ROM directory and the path to the game file respectively. `%EXTRA_` - This passes an _extra_ which contains any additional information that the emulator may support. This is provided as a key/value pair where you define the key name following the literal %EXTRA_ string and terminate it with a % sign and then assign the value using an equal sign. For example %EXTRA_LIBRETRO%=puae_libretro_android.so will pass the extra named _LIBRETRO_ with its value set to _puae_libretro_android.so_. You can pass an unlimited number of extras and you can also use various ROM variables in combination with this as described below. It's also possible to use the `%BASENAME%`, `%GAMEDIRRAW%`, `%ROMPATHRAW%` and `%ROMRAW%` variables inside an `%EXTRA_` variable definition, which will expand to the basename of the game file, the directory of the game file, the ROM directory and the path to the game file respectively.
`%EXTRAARRAY_` - Defines an array of comma-separated string values following the key name. Only literal strings are supported, so this can't be used in combination with any ROM variables. As commas are used as separator characters, you'll need to escape any comma signs that you want to include in the actual value. For example %EXTRAARRAY_Parameters%=pone,p\\,two,pthree will pass the extra named _Parameters_ with the three separate array entries _pone_, _p,two_ and _pthree_. It's also possible to use the `%GAMEDIRRAW%`, `%ROMPATHRAW%` and `%ROMRAW%` variables inside an `%EXTRAARRAY_` variable definition, which will expand to the the directory of the game file, the ROM directory and the path to the game file respectively. `%EXTRAARRAY_` - Defines an array of comma-separated string values following the key name. Only literal strings and special variables are supported, so this can't be used in combination with any ROM variables. As commas are used as separator characters, you'll need to escape any comma signs that you want to include in the actual value. For example %EXTRAARRAY_Parameters%=pone,p\\,two,pthree will pass the extra named _Parameters_ with the three separate array entries _pone_, _p,two_ and _pthree_. It's also possible to use the `%BASENAME%`, `%GAMEDIRRAW%`, `%ROMPATHRAW%` and `%ROMRAW%` variables inside an `%EXTRAARRAY_` variable definition, which will expand to the basename of the game file, the directory of the game file, the ROM directory and the path to the game file respectively.
`%EXTRABOOL_` - Sets an extra with a boolean value, i.e. true/1 or false/0. `%EXTRABOOL_` - Sets an extra with a boolean value, i.e. true/1 or false/0.
@ -2134,7 +2158,7 @@ Just make sure to not place the portable installation on a network share that us
There are numerous locations throughout ES-DE where custom scripts can be executed if the option to do so has been enabled in the settings. You'll find the option _Enable custom event scripts_ on the Main menu under _Other settings_. By default this setting is deactivated so make sure to enable it to use this feature. There are numerous locations throughout ES-DE where custom scripts can be executed if the option to do so has been enabled in the settings. You'll find the option _Enable custom event scripts_ on the Main menu under _Other settings_. By default this setting is deactivated so make sure to enable it to use this feature.
The approach is quite straightforward, ES-DE will look for any files inside a script directory that corresponds to the event that is triggered and will then attempt to execute all these files (regardless of their file extensions). If you want to have the scripts executed in a certain order you can name them accordingly as they will be sorted and executed in lexicographic order. The sorting is case-sensitive on Unix/Linux and case-insensitive on macOS and Windows. ES-DE will wait for each script to finish its execution before moving on to the next one, so the application will suspend briefly when whatever the script is doing is executing. If you want to avoid this you can setup a wrapper script that executes another script outside the ES-DE scripts directory as a background process. Refer to your operating system documentation on how to accomplish this. The approach is quite straightforward, ES-DE will look for any files inside a script directory that corresponds to the event that is triggered and will then attempt to execute all these files (regardless of their file extensions). If you want to have the scripts executed in a certain order you can name them accordingly as they will be sorted and executed in lexicographic order. The sorting is case-sensitive on Linux and Android and case-insensitive on macOS and Windows. ES-DE will wait for each script to finish its execution before moving on to the next one, so the application will suspend briefly when whatever the script is doing is executing. If you want to avoid this you can setup a wrapper script that executes another script outside the ES-DE scripts directory as a background process. Refer to your operating system documentation on how to accomplish this.
On Windows it's also possible to place .lnk shortcut files in the event directories to have these executed in the same manner as a script. Note that while PowerShell scripts can't be executed directly they can be run via either a .lnk shortcut file or a .bat wrapper script where you explicitly call powershell.exe with the -command flag. Just be aware that by default the execution of PowerShell scripts is disabled on Windows. Further details about PowerShell is beyond the scope of this document. On Windows it's also possible to place .lnk shortcut files in the event directories to have these executed in the same manner as a script. Note that while PowerShell scripts can't be executed directly they can be run via either a .lnk shortcut file or a .bat wrapper script where you explicitly call powershell.exe with the -command flag. Just be aware that by default the execution of PowerShell scripts is disabled on Windows. Further details about PowerShell is beyond the scope of this document.
@ -2161,11 +2185,11 @@ We'll go through two examples:
* Creating a log file that will record the start and end time for each game we play, letting us see how much time we spend on retro-gaming * Creating a log file that will record the start and end time for each game we play, letting us see how much time we spend on retro-gaming
* Changing the system resolution when launching and returning from a game in order to run the emulator at a lower resolution than ES-DE * Changing the system resolution when launching and returning from a game in order to run the emulator at a lower resolution than ES-DE
The following examples are for Unix systems, but it works the same way on macOS and Windows (although .bat batch files are used on Windows instead of shell scripts and any spaces in the parameters are not escaped as is the case on Unix and macOS). The following examples are for Linux systems, but it works the same way on Android, macOS and Windows (although .bat batch files are used on Windows instead of shell scripts and any spaces in the parameters are not escaped as is the case on Linux, Android and macOS).
As can be seen in the table above, the events executed when a game starts and ends are named _game-start_ and _game-end_ As can be seen in the table above, the events executed when a game starts and ends are named _game-start_ and _game-end_
So let's create the folders for these events in the scripts directory. The location is `~/ES-DE/scripts` So let's create the folders for these events inside the application data directory, or more specifically in `~/ES-DE/scripts`
**Game log** **Game log**

View file

@ -1,3 +1,4 @@
Copyright (c) 2024 Northwestern Software AB
Copyright (c) 2020-2024 Leon Styhre Copyright (c) 2020-2024 Leon Styhre
Copyright (c) 2014 Alec Lofquist Copyright (c) 2014 Alec Lofquist

View file

@ -2,7 +2,7 @@
ES-DE (EmulationStation Desktop Edition) is a frontend for browsing and launching games from your multi-platform collection. ES-DE (EmulationStation Desktop Edition) is a frontend for browsing and launching games from your multi-platform collection.
It's officially supported on Linux, macOS, Windows and Android but can also be used on BSD Unix and the Raspberry Pi if you build it yourself from source code. It's officially supported on Linux, macOS, Windows and Android but can also be used on Haiku and the Raspberry Pi if you build it yourself from source code. There is also an ES-DE package in the FreeBSD ports collection.
Website:\ Website:\
https://es-de.org https://es-de.org
@ -27,11 +27,13 @@ https://gitlab.com/es-de/themes/themes-list
## Download ## Download
Visit https://es-de.org to download the latest ES-DE release or go to the [package registry](https://gitlab.com/es-de/emulationstation-de/-/packages) where you can also find most previous releases. Visit https://es-de.org to download the latest ES-DE release or go to the [package registry](https://gitlab.com/es-de/emulationstation-de/-/packages) where you can also find a number of previous releases.
The Android port of ES-DE is a paid app, which you can get on [Patreon](https://www.patreon.com/es_de) or on the [Samsung Galaxy Store](https://galaxystore.samsung.com/detail/org.es_de.frontend.galaxy). The Android port of ES-DE is a paid app, which you can get on [Patreon](https://www.patreon.com/es_de), the [Samsung Galaxy Store](https://galaxystore.samsung.com/detail/org.es_de.frontend.galaxy) and [Huawei AppGallery](https://appgallery.huawei.com/#/app/C111315115).
If you're using a Raspberry Pi or if you run FreeBSD, NetBSD or OpenBSD then you need to compile from source code as no prebuilt packages are provided for these platforms. A detailed build guide is available in [INSTALL.md](INSTALL.md). If you're using a Raspberry Pi or if you run Haiku, then you need to compile from source code as no prebuilt packages are provided for these platforms. A detailed build guide is available in [INSTALL.md](INSTALL.md).
If your run FreeBSD then ES-DE is available as an [official port](https://www.freshports.org/emulators/es-de).
## Additional information ## Additional information

View file

@ -10,7 +10,6 @@ A more detailed breakdown can be found on the [Kanban](https://gitlab.com/es-de/
* Bulk metadata editor * Bulk metadata editor
* Background music * Background music
* Controller button mappings from inside ES-DE (similar to pad2key in Batocera) * Controller button mappings from inside ES-DE (similar to pad2key in Batocera)
* Localization/multi-language support
* Auto-import tools for Android apps, Steam, Lutris etc. * Auto-import tools for Android apps, Steam, Lutris etc.
**User interface** **User interface**

View file

@ -97,14 +97,14 @@ themes/
The ES-DE theme functionality makes it easy for users to install different themes and to choose between them from the _UI Settings_ menu. The ES-DE theme functionality makes it easy for users to install different themes and to choose between them from the _UI Settings_ menu.
There are two places that ES-DE can load themes from: There are two places that ES-DE can load themes from:
* `[HOME]/.emulationstation/themes/` * `[HOME]/ES-DE/themes/`
* `[INSTALLATION PATH]/themes/` * `[INSTALLATION PATH]/themes/`
An installation path could be something like this: An installation path could be something like this:
``` ```
/usr/share/emulationstation/themes/slate-es-de/ /usr/share/es-de/themes/
/Applications/EmulationStation Desktop Edition.app/Contents/Resources/themes/ /Applications/ES-DE.app/Contents/Resources/themes/
C:\Program Files\EmulationStation-DE\themes\ C:\Program Files\ES-DE\themes\
``` ```
If a theme with the same name exists in both locations, the one in the home directory will be loaded and the other one will be skipped. If a theme with the same name exists in both locations, the one in the home directory will be loaded and the other one will be skipped.
@ -145,7 +145,7 @@ As for more specific changes, the following are the most important ones compared
* The helpsystem `textColorDimmed` and `iconColorDimmed` properties (which apply when opening a menu) were always defined under the system view configuration which meant these properties could not be separately set for the gamelist views. Now these properties work as expected with the possibility to configure separate values for the system and gamelist views * The helpsystem `textColorDimmed` and `iconColorDimmed` properties (which apply when opening a menu) were always defined under the system view configuration which meant these properties could not be separately set for the gamelist views. Now these properties work as expected with the possibility to configure separate values for the system and gamelist views
* When right-aligning the helpsystem using an X origin value of 1, the element is now aligned correctly to the defined position instead of being offset by the entrySpacing width (in RetroPie ES the offset was instead the hardcoded element entry padding) * When right-aligning the helpsystem using an X origin value of 1, the element is now aligned correctly to the defined position instead of being offset by the entrySpacing width (in RetroPie ES the offset was instead the hardcoded element entry padding)
* Correct theme structure is enforced more strictly than before, and deviations will generate error log messages and make the theme loading fail * Correct theme structure is enforced more strictly than before, and deviations will generate error log messages and make the theme loading fail
* Many additional elements and properties have been added, refer to the [Reference](THEMES-DEV.md#reference) section for more information * Many additional elements and properties have been added, refer to the reference section for more information
Attempting to use any of the legacy logic in the new theme structure will make the theme loading fail, for example adding the _extra="true"_ attribute to any element. Attempting to use any of the legacy logic in the new theme structure will make the theme loading fail, for example adding the _extra="true"_ attribute to any element.
@ -298,8 +298,7 @@ This is the element structure:
</ElementTypeHere> </ElementTypeHere>
``` ```
Finally _properties_ control how a particular element looks and behaves, for example its position, size, image path, animation controls etc. The property type determines what kinds of values you can use. You can read about each type below in the Finally _properties_ control how a particular element looks and behaves, for example its position, size, image path, animation controls etc. The property type determines what kinds of values you can use. You can read about each type below in the reference section. Properties are defined like this:
[Reference](THEMES-DEV.md#reference) section. Properties are defined like this:
```xml ```xml
<propertyNameHere>valueHere</propertyNameHere> <propertyNameHere>valueHere</propertyNameHere>
@ -381,12 +380,12 @@ If you are writing a theme it's recommended to enable the _Debug mode_ setting f
Here's an example of launching ES-DE in debug mode at a limited resolution, which will make it run in a window: Here's an example of launching ES-DE in debug mode at a limited resolution, which will make it run in a window:
``` ```
emulationstation --debug --resolution 1280 720 es-de --debug --resolution 1280 720
``` ```
Enforcement of a correct theme configuration is quite strict, and most errors will abort the theme loading, leading to an unthemed system. In each such situation the log output will be very clear of what happened, for instance: Enforcement of a correct theme configuration is quite strict, and most errors will abort the theme loading, leading to an unthemed system. In each such situation the log output will be very clear of what happened, for instance:
``` ```
Jan 28 17:17:30 Error: ThemeData::parseElement(): "/home/myusername/.emulationstation/themes/mytheme-es-de/theme.xml": Property "origin" for element "image" has no value defined (system "collections", theme "custom-collections") Jan 28 17:17:30 Error: ThemeData::parseElement(): "/home/myusername/ES-DE/themes/mytheme-es-de/theme.xml": Property "origin" for element "image" has no value defined (system "collections", theme "custom-collections")
``` ```
Note that an unthemed system means precisely that, the specific system where the error occured will be unthemed but not necessarily the entire theme. The latter can still happen if the error is global such as a missing variable used by all XML files or an error in a file included by all XML files. The approach is to only untheme relevant sections of the theme to be able to pinpoint precisely where the problem lies. Note that an unthemed system means precisely that, the specific system where the error occured will be unthemed but not necessarily the entire theme. The latter can still happen if the error is global such as a missing variable used by all XML files or an error in a file included by all XML files. The approach is to only untheme relevant sections of the theme to be able to pinpoint precisely where the problem lies.
@ -396,7 +395,7 @@ Sanitization for valid data format and structure is done in this manner, but ver
Jan 28 17:25:27 Warn: BadgeComponent: Invalid theme configuration, property "horizontalAlignment" for element "gamelistBadges" defined as "leftr" Jan 28 17:25:27 Warn: BadgeComponent: Invalid theme configuration, property "horizontalAlignment" for element "gamelistBadges" defined as "leftr"
``` ```
Note however that warnings are not printed for all invalid properties as that would lead to an excessive amount of logging code. This is especially true for numeric values which are commonly just clamped to the allowable range without notifying the theme author. So make sure to check the [Reference](THEMES-DEV.md#reference) section of this document for valid values for each property. Note however that warnings are not printed for all invalid properties as that would lead to an excessive amount of logging code. This is especially true for numeric values which are commonly just clamped to the allowable range without notifying the theme author. So make sure to check the reference section of this document for valid values for each property.
For more serious issues where it does not make sense to assign a default value or auto-adjust the configuration, an error log entry is generated and the element will in most instances not get rendered at all. Here's such an example where the imageType property for a video element was accidentally set to _covr_ instead of _cover_: For more serious issues where it does not make sense to assign a default value or auto-adjust the configuration, an error log entry is generated and the element will in most instances not get rendered at all. Here's such an example where the imageType property for a video element was accidentally set to _covr_ instead of _cover_:
@ -407,12 +406,12 @@ Jan 28 17:29:11 Error: VideoComponent: Invalid theme configuration, property "i
Error handling for missing files is handled a bit differently depending on whether the paths have been defined explicitly or via a variable. For explicitly defined paths a warning will be logged for element properties and an error will be triggered for include files. Here's an example of the latter case: Error handling for missing files is handled a bit differently depending on whether the paths have been defined explicitly or via a variable. For explicitly defined paths a warning will be logged for element properties and an error will be triggered for include files. Here's an example of the latter case:
``` ```
Jan 28 17:32:29 Error: ThemeData::parseIncludes(): "/home/myusername/.emulationstation/themes/mytheme-es-de/theme.xml" -> "./colors_dark.xml" not found (resolved to "/home/myusername/.emulationstation/themes/mytheme-es-de/colors_dark.xml") Jan 28 17:32:29 Error: ThemeData::parseIncludes(): "/home/myusername/ES-DE/themes/mytheme-es-de/theme.xml" -> "./colors_dark.xml" not found (resolved to "/home/myusername/ES-DE/themes/mytheme-es-de/colors_dark.xml")
``` ```
However, if a variable has been used to define the include file, only a debug message will be generated if the file is not found: However, if a variable has been used to define the include file, only a debug message will be generated if the file is not found:
``` ```
Jan 28 17:34:03 Debug: ThemeData::parseIncludes(): "/home/myusername/.emulationstation/themes/mytheme-es-de/theme.xml": Couldn't find file "./${system.theme}/colors.xml" which resolves to "/home/myusername/.emulationstation/themes/mytheme-es-de/amiga/colors.xml" Jan 28 17:34:03 Debug: ThemeData::parseIncludes(): "/home/myusername/ES-DE/themes/mytheme-es-de/theme.xml": Couldn't find file "./${system.theme}/colors.xml" which resolves to "/home/myusername/ES-DE/themes/mytheme-es-de/amiga/colors.xml"
``` ```
It works essentially the same way for element path properties as for include files. This distinction between explicit values and variables makes it possible to create a theme configuration where both include files and files for fonts, images, videos etc. will be used if found, and if not found a fallback configuration can still be applied so the system will be themed. It works essentially the same way for element path properties as for include files. This distinction between explicit values and variables makes it possible to create a theme configuration where both include files and files for fonts, images, videos etc. will be used if found, and if not found a fallback configuration can still be applied so the system will be themed.
@ -742,6 +741,204 @@ Here's an example configuration:
</theme> </theme>
``` ```
## Languages
Multilingual support works very similarly to color schemes and font sizes in that it's a variable-based configuration. Due to this you can set any arbitrary property values you want for a certain language, such as different texts or different images and so on. The supported languages for use in themes are the same as for the overall application.
Note that the word _language_ is not technically the correct term as it's rather _locales_ that are used within ES-DE. For instance the en_US and en_GB locales are both for the English language but rather variations for different countries (United States and United Kingdom). Still, as the term _language_ is colloquially used to describe locales in many applications and operating systems this is also used in ES-DE even if it's not entirely correct. The term _language_ is as such also used throughout this document.
While it's possible to use the theme engine language support to set language-specific date formats this is generally discouraged as it's better to use the ISO 8601 (YYYY-MM-DD) standard instead. This is an international standard that makes sense to use in all countries in the world.
The following languages are supported:
| Language | English name | Native name |
| :------------ | :----------------------- | :----------------------- |
| en_US | English (United States) | English (United States) |
| en_GB | English (United Kingdom) | English (United Kingdom) |
| de_DE | German | Deutsch |
| es_ES | Spanish (Spain) | Español (España) |
| fr_FR | French | Français |
| it_IT | Italian | Italiano |
| pl_PL | Polish | Polski |
| pt_BR | Portuguese (Brazil) | Português (Brasil) |
| ro_RO | Romanian | Română |
| ru_RU | Russian | Русский |
| sv_SE | Swedish | Svenska |
| ja_JP | Japanese | 日本語 |
| ko_KR | Korean | 한국어 |
| zh_CN | Simplified Chinese | 简体中文 |
Note that the native name is what is shown inside the _UI Settings_ menu for the _Theme Language_ and _Application Language_ settings.
You can find more information about locales/languages here:\
https://simplelocalize.io/data/locales
The languages a theme supports need to be declared in the `capabilities.xml` file using `<language>` tag pairs, such as the following example:
```xml
<!-- Theme capabilities for mytheme-es-de -->
<themeCapabilities>
<themeName>My theme</themeName>
<language>en_US</language>
<language>es_ES</language>
<language>pt_PR</language>
<language>sv_SE</language>
<language>zh_CN</language>
</themeCapabilities>
```
Although language support is optional for a theme, if you have declared at least one language then you have to include support for en_US as well. Attempting to skip an entry for this language will output an error message and the language configuration will not get loaded. The reason for this is that en_US is the default language for the ES-DE application so all themes have to support it, either implicitly (by not having any multilingual support) or explicitly by declaring it and providing localization for it.
It's also possible to provide label translations for variants, color schemes and transitions, as displayed in the _UI Settings_ menu. This is done using the `language` attribute for these `label` entries, as in this example `capabilities.xml` file:
```xml
<themeCapabilities>
<themeName>My theme</themeName>
<language>en_US</language>
<language>sv_SE</language>
<colorScheme name="dark">
<label language="en_US">Dark</label>
<label language="sv_SE">Mörkt</label>
</colorScheme>
<variant name="withVideos">
<label language="en_US">Textlist with videos</label>
<label language="sv_SE">Textlista med video</label>
<selectable>true</selectable>
<override>
<trigger>noMedia</trigger>
<mediaType>miximage, screenshot, cover, video</mediaType>
<useVariant>noGameMedia</useVariant>
</override>
</variant>
<transitions name="instantAndSlide">
<label language="en_US">instant and slide</label>
<label language="sv_SE">direkt och glidande</label>
<selectable>true</selectable>
<systemToSystem>instant</systemToSystem>
<systemToGamelist>slide</systemToGamelist>
<gamelistToGamelist>instant</gamelistToGamelist>
<gamelistToSystem>slide</gamelistToSystem>
<startupToSystem>slide</startupToSystem>
<startupToGamelist>slide</startupToGamelist>
</transitions>
</themeCapabilities>
```
Leaving out the `language` property will make ES-DE set the language to en_US.
The actual language-specific values in the theme configuration are defined using variables, like the following example:
```xml
<theme>
<language name="en_US">
<variables>
<langLogo>logo.svg</langLogo>
<langLabelDeveloper>Developer</langLabelDeveloper>
<langLabelPublisher>Publisher</langLabelPublisher>
</variables>
</language>
<language name="sv_SE">
<variables>
<langLogo>logo-sv-se.svg</langLogo>
<langLabelDeveloper>Utvecklare</langLabelDeveloper>
<langLabelPublisher>Utgivare</langLabelPublisher>
</variables>
</language>
<view name="gamelist">
<image name="logo">
<pos>0.38 0.1781</pos>
<size>0.158 0.12</size>
<path>./assets/${langLogo}</path>
</image>
<text name="labelDeveloper">
<pos>0.88 0.511</pos>
<size>0.165 0.03</size>
<text>${langLabelDeveloper}</text>
</text>
<text name="labelPublisher">
<pos>0.88 0.5935</pos>
<size>0.165 0.03</size>
<text>${langLabelPublisher}</text>
</text>
</view>
</theme>
```
It could also be a good idea to include the translations from a separate file:
```xml
<theme>
<include>./languages.xml</include>
<view name="gamelist">
<image name="logo">
<pos>0.38 0.1781</pos>
<size>0.158 0.12</size>
<path>./assets/${langLogo}</path>
</image>
<text name="labelDeveloper">
<pos>0.88 0.511</pos>
<size>0.165 0.03</size>
<text>${langLabelDeveloper}</text>
</text>
<text name="labelPublisher">
<pos>0.88 0.5935</pos>
<size>0.165 0.03</size>
<text>${langLabelPublisher}</text>
</text>
</view>
</theme>
```
Including separate files per language is also supported but it's probably not a good idea as it will add a lot of unnecessary files to the theme:
```xml
<theme>
<language name="en_US">
<include>./lang-en-us.xml</include>
</language>
<language name="sv_SE">
<include>./lang-sv-se.xml</include>
</language>
<view name="gamelist">
<image name="logo">
<pos>0.38 0.1781</pos>
<size>0.158 0.12</size>
<path>./assets/${langLogo}</path>
</image>
<text name="labelDeveloper">
<pos>0.88 0.511</pos>
<size>0.165 0.03</size>
<text>${langLabelDeveloper}</text>
</text>
<text name="labelPublisher">
<pos>0.88 0.5935</pos>
<size>0.165 0.03</size>
<text>${langLabelPublisher}</text>
</text>
</view>
</theme>
```
Note the naming convention when using localized versions of files such as images. These should include the locale/language in their name and they should be in lowercase characters, using only dashes as separators. For the default language the locale could be omitted from the filename (as language-specific images and similar will likely be exceptions with most files rather shared across all locales). Here are some examples:
```
logo.svg
logo-fr-fr.svg
logo-pt-br.svg
logo-sv-se.svg
auto-allgames.webp
auto-allgames-fr-fr.webp
auto-allgames-pt-br.webp
auto-allgames-sv-se.webp
```
## Aspect ratios ## Aspect ratios
The aspect ratio support works almost identically to the variants and color schemes with the main difference that the available aspect ratios are hardcoded into ES-DE. The theme can still decide which of the aspect ratios to support (or none at all in which case the theme aspect ratio is left undefined) but it can't create entirely new aspect ratio entries. The aspect ratio support works almost identically to the variants and color schemes with the main difference that the available aspect ratios are hardcoded into ES-DE. The theme can still decide which of the aspect ratios to support (or none at all in which case the theme aspect ratio is left undefined) but it can't create entirely new aspect ratio entries.
@ -900,9 +1097,9 @@ Finally it's possible to apply theme-defined transition profiles on a per-varian
## capabilities.xml ## capabilities.xml
Variants, variant triggers, color schemes, aspect ratios and transition animation profiles need to be declared before they can be used inside the actual theme configuration files and that is done in the `capabilities.xml` file. This file needs to be located in the root of the theme directory, for example: Variants, variant triggers, color schemes, font sizes, languages, aspect ratios and transition animation profiles need to be declared before they can be used inside the actual theme configuration files and that is done in the `capabilities.xml` file. This file needs to be located in the root of the theme directory, for example:
``` ```
~/.emulationstation/themes/mytheme-es-de/capabilities.xml ~/ES-DE/themes/mytheme-es-de/capabilities.xml
``` ```
The capabilities.xml file is mandatory and if it doesn't exist ES-DE will not attempt to load the theme. The capabilities.xml file is mandatory and if it doesn't exist ES-DE will not attempt to load the theme.
@ -914,19 +1111,29 @@ The structure of the file is simple, as can be seen in this example:
<themeCapabilities> <themeCapabilities>
<themeName>My theme</themeName> <themeName>My theme</themeName>
<language>en_US</language>
<language>sv_SE</language>
<aspectRatio>16:9</aspectRatio> <aspectRatio>16:9</aspectRatio>
<aspectRatio>4:3</aspectRatio> <aspectRatio>4:3</aspectRatio>
<aspectRatio>4:3_vertical</aspectRatio> <aspectRatio>4:3_vertical</aspectRatio>
<fontSize>medium</fontSize>
<fontSize>large</fontSize>
<colorScheme name="dark"> <colorScheme name="dark">
<label>Dark mode</label> <label language="en_US">Dark</label>
<label language="sv_SE">Mörkt</label>
</colorScheme> </colorScheme>
<colorScheme name="light"> <colorScheme name="light">
<label>Light mode</label> <label language="en_US">Light</label>
<label language="sv_SE">Ljust</label>
</colorScheme> </colorScheme>
<transitions name="instantAndSlide"> <transitions name="instantAndSlide">
<label language="en_US">instant and slide</label>
<label language="sv_SE">direkt och glidande</label>
<systemToSystem>instant</systemToSystem> <systemToSystem>instant</systemToSystem>
<systemToGamelist>slide</systemToGamelist> <systemToGamelist>slide</systemToGamelist>
<gamelistToGamelist>instant</gamelistToGamelist> <gamelistToGamelist>instant</gamelistToGamelist>
@ -934,7 +1141,8 @@ The structure of the file is simple, as can be seen in this example:
</transitions> </transitions>
<variant name="withVideos"> <variant name="withVideos">
<label>Textlist with videos</label> <label language="en_US">Textlist with videos</label>
<label language="sv_SE">Textlista med video</label>
<selectable>true</selectable> <selectable>true</selectable>
<override> <override>
<trigger>noMedia</trigger> <trigger>noMedia</trigger>
@ -944,7 +1152,8 @@ The structure of the file is simple, as can be seen in this example:
</variant> </variant>
<variant name="withoutVideos"> <variant name="withoutVideos">
<label>Textlist without videos</label> <label language="en_US">Textlist without videos</label>
<label language="sv_SE">Textlista utan video</label>
<selectable>true</selectable> <selectable>true</selectable>
<override> <override>
<trigger>noMedia</trigger> <trigger>noMedia</trigger>
@ -959,7 +1168,8 @@ The structure of the file is simple, as can be seen in this example:
</variant> </variant>
</themeCapabilities> </themeCapabilities>
``` ```
The file format is hopefully mostly self-explanatory; this example provides three aspect ratios, two color schemes, one transition animation profile and three variants, one of which is a variant trigger override. The `<label>` tag for the variants and transitions is the text that will show up in the _UI Settings_ menu, assuming `<selectable>` has been set to true. The same is true for color schemes, although these will always show up in the GUI and can't be disabled.
The file format is hopefully mostly self-explanatory; this example provides two languages, three aspect ratios, two font sizes, two color schemes, one transition animation profile and three variants, one of which is a variant trigger override. The `<label>` tag for the variants and transitions is the text that will show up in the _UI Settings_ menu, assuming `<selectable>` has been set to true. The same is true for color schemes, although these will always show up in the GUI and can't be disabled.
The optional `<themeName>` tag defines the name that will show up in the _Theme_ option in the _UI Settings_ menu. If no such tag is present, then the physical directory name will be displayed instead, for example _MYTHEME-ES-DE_. Note that theme names will always be converted to uppercase characters when displayed in the menu. The optional `<themeName>` tag defines the name that will show up in the _Theme_ option in the _UI Settings_ menu. If no such tag is present, then the physical directory name will be displayed instead, for example _MYTHEME-ES-DE_. Note that theme names will always be converted to uppercase characters when displayed in the menu.
@ -986,7 +1196,7 @@ It's normally not necessary to define all or even most of these for a theme, ins
The declared aspect ratios will always get displayed in the _UI settings_ menu in the order listed in the table above, so they can be declared in any order in the capabilities.xml file. If an unsupported aspect ratio value is entered, a warning will be generated on startup and the entry will not get loaded. The declared aspect ratios will always get displayed in the _UI settings_ menu in the order listed in the table above, so they can be declared in any order in the capabilities.xml file. If an unsupported aspect ratio value is entered, a warning will be generated on startup and the entry will not get loaded.
The use of variants, variant triggers, color schemes, aspect ratios and transition animation profiles is optional, i.e. a theme does not need to provide any of them. There must however be a capabilities.xml file present in the root of the theme directory. So if you don't wish to provide this functionality, simply create an empty file or perhaps add a short XML comment to clarify that the theme does not provide this functionality. In this case the theme will still load and work correctly but the menu options for selecting variants, color schemes and aspect ratios will be grayed out. The use of variants, variant triggers, color schemes, font sizes, languages, aspect ratios and transition animation profiles is optional, i.e. a theme does not need to provide any of them. There must however be a capabilities.xml file present in the root of the theme directory. So if you don't wish to provide this functionality, simply create an empty file or perhaps add a short XML comment to clarify that the theme does not provide this functionality. In this case the theme will still load and work correctly but the menu options for selecting variants, color schemes and aspect ratios will be grayed out.
Note that changes to the capabilities.xml file are not reloaded when using the Ctrl + r key combination, instead ES-DE needs to be restarted to reload any changes to this file. Note that changes to the capabilities.xml file are not reloaded when using the Ctrl + r key combination, instead ES-DE needs to be restarted to reload any changes to this file.
@ -994,7 +1204,7 @@ Note that changes to the capabilities.xml file are not reloaded when using the C
You can include theme files within theme files, for example: You can include theme files within theme files, for example:
`~/.emulationstation/themes/mytheme-es-de/fonts.xml`: `~/ES-DE/themes/mytheme-es-de/fonts.xml`:
```xml ```xml
<theme> <theme>
<view name="gamelist"> <view name="gamelist">
@ -1007,7 +1217,7 @@ You can include theme files within theme files, for example:
</theme> </theme>
``` ```
`~/.emulationstation/themes/mytheme-es-de/snes/theme.xml`: `~/ES-DE/themes/mytheme-es-de/snes/theme.xml`:
```xml ```xml
<theme> <theme>
<include>./../fonts.xml</include> <include>./../fonts.xml</include>
@ -1252,7 +1462,7 @@ Example `navigationsounds.xml` file:
## Element rendering order using zIndex ## Element rendering order using zIndex
You can change the order in which elements are rendered by setting their `zIndex` values. All elements have a default value so you only need to define it for the ones you wish to explicitly change. Elements will be rendered in order from smallest to largest values. A complete description of each element including all supported properties can be found in the [Reference](THEMES-DEV.md#reference) section. You can change the order in which elements are rendered by setting their `zIndex` values. All elements have a default value so you only need to define it for the ones you wish to explicitly change. Elements will be rendered in order from smallest to largest values. A complete description of each element including all supported properties can be found in the reference section.
These are the default zIndex values per element type: These are the default zIndex values per element type:
@ -1281,7 +1491,7 @@ Theme variables can be used to simplify theme construction and there are two typ
### System variables ### System variables
System variables are system specific and are derived from values defined in es_systems.xml (except for collections which are derived from hardcoded application-internal values). System variables are system-specific and derived from values defined in es_systems.xml (except for collections which are derived from hardcoded application-internal values).
* `system.name` * `system.name`
* `system.name.autoCollections` * `system.name.autoCollections`
* `system.name.customCollections` * `system.name.customCollections`
@ -1299,9 +1509,11 @@ System variables are system specific and are derived from values defined in es_s
`system.fullName` expands to the full system name as defined by the `fullname` tag in es_systems.xml\ `system.fullName` expands to the full system name as defined by the `fullname` tag in es_systems.xml\
`system.theme` expands to the theme directory as defined by the `theme` tag in es_systems.xml `system.theme` expands to the theme directory as defined by the `theme` tag in es_systems.xml
Note that all _name_ and _fullName_ values for the built-in collections _all games, favorites, last played and collections_ are translated to the language selected via the _Application Language_ option in the _UI Settings_ menu.
If using variables to load theme assets like images and videos, then use the `.name` versions of these variables as short system names should be stable and not change over time. The `.fullName` values could change in future ES-DE releases or they could be user-customized which would break your theme. If using variables to load theme assets like images and videos, then use the `.name` versions of these variables as short system names should be stable and not change over time. The `.fullName` values could change in future ES-DE releases or they could be user-customized which would break your theme.
The `.autoCollections`, `.customCollections` and `.noCollections` versions of the variables make it possible to differentiate between regular systems, automatic collections (_all games_, _favorites_ and _last played_) and custom collections. This can for example be used to apply different formatting to the names of the collections as opposed to regular systems. The `.autoCollections`, `.customCollections` and `.noCollections` versions of the variables make it possible to differentiate between regular systems, automatic collections (_all games, favorites_ and _last played_) and custom collections. This can for example be used to apply different formatting to the names of the collections as opposed to regular systems.
The below example capitalizes the names of the auto collections while leaving custom collections and regular systems at their default formatting (as they are defined by the user and es_systems.xml respectively). The reason this works is that the .autoCollections, .customCollections and .noCollections variables are mutually exclusive, i.e. a system is either a real system or an automatic collection or a custom collection and never more than one of these. The below example capitalizes the names of the auto collections while leaving custom collections and regular systems at their default formatting (as they are defined by the user and es_systems.xml respectively). The reason this works is that the .autoCollections, .customCollections and .noCollections variables are mutually exclusive, i.e. a system is either a real system or an automatic collection or a custom collection and never more than one of these.
@ -1422,19 +1634,20 @@ It's important to understand how the theme configuration files are parsed in ord
2) Variables 2) Variables
3) Color schemes 3) Color schemes
4) Font sizes 4) Font sizes
5) Included files 5) Languages
6) "General" (non-variant) configuration 6) Included files
7) Variants 7) "General" (non-variant) configuration
8) Aspect ratios 8) Variants
9) Aspect ratios
When including a file using the `<include>` tag (i.e. step 4 above) then all steps listed above are executed for that included file prior to continuing to the next line after the `<include>` tag. When including a file using the `<include>` tag (i.e. step 6 above) then all steps listed above are executed for that included file prior to continuing to the next line after the `<include>` tag.
For any given step, the configuration is parsed in the exact order that it's defined in the XML file. Be mindful of the logic described above as for instance defining variant-specific configuration above general configuration in the same XML file will still have that parsed afterwards. For any given step, the configuration is parsed in the exact order that it's defined in the XML file. Be mindful of the logic described above as for instance defining variant-specific configuration above general configuration in the same XML file will still have that parsed afterwards.
## Property data types ## Property data types
* NORMALIZED_PAIR - two decimal values delimited by a space, for example `0.25 0.5` * NORMALIZED_PAIR - two decimal values delimited by a space, for example `0.25 0.5`
* PATH - path to a resource. If the first character is a tilde (`~`) then it will be expanded to the user's home directory (`$HOME` for Linux, BSD Unix and macOS and `%HOMEPATH%` for Windows) unless overridden using the --home command line option. If the first character is a dot (`.`) then the resource will be searched for relative to the location of the theme file, for example `./myfont.ttf` or `./../core/fonts/myfont.ttf` * PATH - path to a resource. If the first character is a tilde (`~`) then it will be expanded to the user's home directory (`$HOME` for Linux and macOS and `%HOMEPATH%` for Windows) unless overridden using the --home command line option. If the first character is a dot (`.`) then the resource will be searched for relative to the location of the theme file, for example `./myfont.ttf` or `./../core/fonts/myfont.ttf`
* BOOLEAN - `true`/`1` or `false`/`0` * BOOLEAN - `true`/`1` or `false`/`0`
* COLOR - a hexadecimal RGB or RGBA color value consisting of 6 or 8 digits. If a 6 digit value is used then the alpha channel will be set to `FF` (completely opaque) * COLOR - a hexadecimal RGB or RGBA color value consisting of 6 or 8 digits. If a 6 digit value is used then the alpha channel will be set to `FF` (completely opaque)
* UNSIGNED_INTEGER - an unsigned integer value * UNSIGNED_INTEGER - an unsigned integer value
@ -2804,6 +3017,7 @@ Properties:
* `container` - type: BOOLEAN * `container` - type: BOOLEAN
- Whether the text should be placed inside a scrollable container. - Whether the text should be placed inside a scrollable container.
- Default is `true` if `metadata` is set to `description`, otherwise `false` - Default is `true` if `metadata` is set to `description`, otherwise `false`
- This property can only be used if `size` has a width defined.
* `containerType` - type: STRING * `containerType` - type: STRING
- If `container` has been set, then it's possible to select between a vertically or horizontally scrolling type using this property. If selecting the horizontal container then all line breaks in the text will be automatically converted to spaces. If selecting the vertical container then any value defined for `rotation` will be ignored as this container type can't be rotated. - If `container` has been set, then it's possible to select between a vertically or horizontally scrolling type using this property. If selecting the horizontal container then all line breaks in the text will be automatically converted to spaces. If selecting the vertical container then any value defined for `rotation` will be ignored as this container type can't be rotated.
- Valid values are `vertical` or `horizontal` - Valid values are `vertical` or `horizontal`
@ -2943,6 +3157,16 @@ Properties:
- Default is `center` - Default is `center`
* `color` - type: COLOR * `color` - type: COLOR
* `backgroundColor` - type: COLOR * `backgroundColor` - type: COLOR
* `backgroundMargins` - type: NORMALIZED_PAIR
- Adds margins to the text background, assuming it has a color set. The first value of the pair is the left margin and the second value is the right margin, which means it's possible to set these margins completely independently. Margins are applied after all other positioning and sizing calculations and they are rendered outside the text debug rectangle boundaries.
- Minimum value per axis is `0` and maximum value per axis is `0.5`
- Default is `0 0`
- This property can only be used if `backgroundColor` has a value defined.
* `backgroundCornerRadius` - type: FLOAT
- Setting this property higher than zero applies rounded corners to the text background, assuming it has a color set. The radius is a percentage of the screen width. Note that the maximum allowed value is quite arbitrary as the renderer will in practice limit the maximum roundness so it can never go beyond half the text background height. It means that setting this property sufficiently high will produce perfectly rounded sides for the text background. You normally want to combine this property with `backgroundMargins` to add some extra margins.
- Minimum value is `0` and maximum value is `0.5`
- Default is `0` (corners are not rounded)
- This property can only be used if `backgroundColor` has a value defined.
* `letterCase` - type: STRING * `letterCase` - type: STRING
- Valid values are `none`, `uppercase`, `lowercase` or `capitalize` - Valid values are `none`, `uppercase`, `lowercase` or `capitalize`
- Default is `none` (original letter case is retained) - Default is `none` (original letter case is retained)

284
THEMES.md
View file

@ -95,14 +95,14 @@ themes/
The ES-DE theme functionality makes it easy for users to install different themes and to choose between them from the _UI Settings_ menu. The ES-DE theme functionality makes it easy for users to install different themes and to choose between them from the _UI Settings_ menu.
There are two places that ES-DE can load themes from: There are two places that ES-DE can load themes from:
* `[HOME]/.emulationstation/themes/` * `[HOME]/ES-DE/themes/`
* `[INSTALLATION PATH]/themes/` * `[INSTALLATION PATH]/themes/`
An installation path could be something like this: An installation path could be something like this:
``` ```
/usr/share/emulationstation/themes/slate-es-de/ /usr/share/es-de/themes/
/Applications/EmulationStation Desktop Edition.app/Contents/Resources/themes/ /Applications/ES-DE.app/Contents/Resources/themes/
C:\Program Files\EmulationStation-DE\themes\ C:\Program Files\ES-DE\themes\
``` ```
If a theme with the same name exists in both locations, the one in the home directory will be loaded and the other one will be skipped. If a theme with the same name exists in both locations, the one in the home directory will be loaded and the other one will be skipped.
@ -143,7 +143,7 @@ As for more specific changes, the following are the most important ones compared
* The helpsystem `textColorDimmed` and `iconColorDimmed` properties (which apply when opening a menu) were always defined under the system view configuration which meant these properties could not be separately set for the gamelist views. Now these properties work as expected with the possibility to configure separate values for the system and gamelist views * The helpsystem `textColorDimmed` and `iconColorDimmed` properties (which apply when opening a menu) were always defined under the system view configuration which meant these properties could not be separately set for the gamelist views. Now these properties work as expected with the possibility to configure separate values for the system and gamelist views
* When right-aligning the helpsystem using an X origin value of 1, the element is now aligned correctly to the defined position instead of being offset by the entrySpacing width (in RetroPie ES the offset was instead the hardcoded element entry padding) * When right-aligning the helpsystem using an X origin value of 1, the element is now aligned correctly to the defined position instead of being offset by the entrySpacing width (in RetroPie ES the offset was instead the hardcoded element entry padding)
* Correct theme structure is enforced more strictly than before, and deviations will generate error log messages and make the theme loading fail * Correct theme structure is enforced more strictly than before, and deviations will generate error log messages and make the theme loading fail
* Many additional elements and properties have been added, refer to the [Reference](THEMES.md#reference) section for more information * Many additional elements and properties have been added, refer to the reference section for more information
Attempting to use any of the legacy logic in the new theme structure will make the theme loading fail, for example adding the _extra="true"_ attribute to any element. Attempting to use any of the legacy logic in the new theme structure will make the theme loading fail, for example adding the _extra="true"_ attribute to any element.
@ -296,8 +296,7 @@ This is the element structure:
</ElementTypeHere> </ElementTypeHere>
``` ```
Finally _properties_ control how a particular element looks and behaves, for example its position, size, image path, animation controls etc. The property type determines what kinds of values you can use. You can read about each type below in the Finally _properties_ control how a particular element looks and behaves, for example its position, size, image path, animation controls etc. The property type determines what kinds of values you can use. You can read about each type below in the reference section. Properties are defined like this:
[Reference](THEMES.md#reference) section. Properties are defined like this:
```xml ```xml
<propertyNameHere>valueHere</propertyNameHere> <propertyNameHere>valueHere</propertyNameHere>
@ -379,12 +378,12 @@ If you are writing a theme it's recommended to enable the _Debug mode_ setting f
Here's an example of launching ES-DE in debug mode at a limited resolution, which will make it run in a window: Here's an example of launching ES-DE in debug mode at a limited resolution, which will make it run in a window:
``` ```
emulationstation --debug --resolution 1280 720 es-de --debug --resolution 1280 720
``` ```
Enforcement of a correct theme configuration is quite strict, and most errors will abort the theme loading, leading to an unthemed system. In each such situation the log output will be very clear of what happened, for instance: Enforcement of a correct theme configuration is quite strict, and most errors will abort the theme loading, leading to an unthemed system. In each such situation the log output will be very clear of what happened, for instance:
``` ```
Jan 28 17:17:30 Error: ThemeData::parseElement(): "/home/myusername/.emulationstation/themes/mytheme-es-de/theme.xml": Property "origin" for element "image" has no value defined (system "collections", theme "custom-collections") Jan 28 17:17:30 Error: ThemeData::parseElement(): "/home/myusername/ES-DE/themes/mytheme-es-de/theme.xml": Property "origin" for element "image" has no value defined (system "collections", theme "custom-collections")
``` ```
Note that an unthemed system means precisely that, the specific system where the error occured will be unthemed but not necessarily the entire theme. The latter can still happen if the error is global such as a missing variable used by all XML files or an error in a file included by all XML files. The approach is to only untheme relevant sections of the theme to be able to pinpoint precisely where the problem lies. Note that an unthemed system means precisely that, the specific system where the error occured will be unthemed but not necessarily the entire theme. The latter can still happen if the error is global such as a missing variable used by all XML files or an error in a file included by all XML files. The approach is to only untheme relevant sections of the theme to be able to pinpoint precisely where the problem lies.
@ -394,7 +393,7 @@ Sanitization for valid data format and structure is done in this manner, but ver
Jan 28 17:25:27 Warn: BadgeComponent: Invalid theme configuration, property "horizontalAlignment" for element "gamelistBadges" defined as "leftr" Jan 28 17:25:27 Warn: BadgeComponent: Invalid theme configuration, property "horizontalAlignment" for element "gamelistBadges" defined as "leftr"
``` ```
Note however that warnings are not printed for all invalid properties as that would lead to an excessive amount of logging code. This is especially true for numeric values which are commonly just clamped to the allowable range without notifying the theme author. So make sure to check the [Reference](THEMES.md#reference) section of this document for valid values for each property. Note however that warnings are not printed for all invalid properties as that would lead to an excessive amount of logging code. This is especially true for numeric values which are commonly just clamped to the allowable range without notifying the theme author. So make sure to check the reference section of this document for valid values for each property.
For more serious issues where it does not make sense to assign a default value or auto-adjust the configuration, an error log entry is generated and the element will in most instances not get rendered at all. Here's such an example where the imageType property for a video element was accidentally set to _covr_ instead of _cover_: For more serious issues where it does not make sense to assign a default value or auto-adjust the configuration, an error log entry is generated and the element will in most instances not get rendered at all. Here's such an example where the imageType property for a video element was accidentally set to _covr_ instead of _cover_:
@ -405,12 +404,12 @@ Jan 28 17:29:11 Error: VideoComponent: Invalid theme configuration, property "i
Error handling for missing files is handled a bit differently depending on whether the paths have been defined explicitly or via a variable. For explicitly defined paths a warning will be logged for element properties and an error will be triggered for include files. Here's an example of the latter case: Error handling for missing files is handled a bit differently depending on whether the paths have been defined explicitly or via a variable. For explicitly defined paths a warning will be logged for element properties and an error will be triggered for include files. Here's an example of the latter case:
``` ```
Jan 28 17:32:29 Error: ThemeData::parseIncludes(): "/home/myusername/.emulationstation/themes/mytheme-es-de/theme.xml" -> "./colors_dark.xml" not found (resolved to "/home/myusername/.emulationstation/themes/mytheme-es-de/colors_dark.xml") Jan 28 17:32:29 Error: ThemeData::parseIncludes(): "/home/myusername/ES-DE/themes/mytheme-es-de/theme.xml" -> "./colors_dark.xml" not found (resolved to "/home/myusername/ES-DE/themes/mytheme-es-de/colors_dark.xml")
``` ```
However, if a variable has been used to define the include file, only a debug message will be generated if the file is not found: However, if a variable has been used to define the include file, only a debug message will be generated if the file is not found:
``` ```
Jan 28 17:34:03 Debug: ThemeData::parseIncludes(): "/home/myusername/.emulationstation/themes/mytheme-es-de/theme.xml": Couldn't find file "./${system.theme}/colors.xml" which resolves to "/home/myusername/.emulationstation/themes/mytheme-es-de/amiga/colors.xml" Jan 28 17:34:03 Debug: ThemeData::parseIncludes(): "/home/myusername/ES-DE/themes/mytheme-es-de/theme.xml": Couldn't find file "./${system.theme}/colors.xml" which resolves to "/home/myusername/ES-DE/themes/mytheme-es-de/amiga/colors.xml"
``` ```
It works essentially the same way for element path properties as for include files. This distinction between explicit values and variables makes it possible to create a theme configuration where both include files and files for fonts, images, videos etc. will be used if found, and if not found a fallback configuration can still be applied so the system will be themed. It works essentially the same way for element path properties as for include files. This distinction between explicit values and variables makes it possible to create a theme configuration where both include files and files for fonts, images, videos etc. will be used if found, and if not found a fallback configuration can still be applied so the system will be themed.
@ -740,6 +739,202 @@ Here's an example configuration:
</theme> </theme>
``` ```
## Languages
Multilingual support works very similarly to color schemes and font sizes in that it's a variable-based configuration. Due to this you can set any arbitrary property values you want for a certain language, such as different texts or different images and so on. The supported languages for use in themes are the same as for the overall application.
Note that the word _language_ is not technically the correct term as it's rather _locales_ that are used within ES-DE. For instance the en_US and en_GB locales are both for the English language but rather variations for different countries (United States and United Kingdom). Still, as the term _language_ is colloquially used to describe locales in many applications and operating systems this is also used in ES-DE even if it's not entirely correct. The term _language_ is as such also used throughout this document.
While it's possible to use the theme engine language support to set language-specific date formats this is generally discouraged as it's better to use the ISO 8601 (YYYY-MM-DD) standard instead. This is an international standard that makes sense to use in all countries in the world.
The following languages are supported:
| Language | English name | Native name |
| :------------ | :----------------------- | :----------------------- |
| en_US | English (United States) | English (United States) |
| en_GB | English (United Kingdom) | English (United Kingdom) |
| es_ES | Spanish (Spain) | Español (España) |
| fr_FR | French | Français |
| it_IT | Italian | Italiano |
| pl_PL | Polish | Polski |
| pt_BR | Portuguese (Brazil) | Português (Brasil) |
| ro_RO | Romanian | Română |
| ru_RU | Russian | Русский |
| sv_SE | Swedish | Svenska |
| ja_JP | Japanese | 日本語 |
| zh_CN | Simplified Chinese | 简体中文 |
Note that the native name is what is shown inside the _UI Settings_ menu for the _Theme Language_ and _Application Language_ settings.
You can find more information about locales/languages here:\
https://simplelocalize.io/data/locales
The languages a theme supports need to be declared in the `capabilities.xml` file using `<language>` tag pairs, such as the following example:
```xml
<!-- Theme capabilities for mytheme-es-de -->
<themeCapabilities>
<themeName>My theme</themeName>
<language>en_US</language>
<language>es_ES</language>
<language>pt_PR</language>
<language>sv_SE</language>
<language>zh_CN</language>
</themeCapabilities>
```
Although language support is optional for a theme, if you have declared at least one language then you have to include support for en_US as well. Attempting to skip an entry for this language will output an error message and the language configuration will not get loaded. The reason for this is that en_US is the default language for the ES-DE application so all themes have to support it, either implicitly (by not having any multilingual support) or explicitly by declaring it and providing localization for it.
It's also possible to provide label translations for variants, color schemes and transitions, as displayed in the _UI Settings_ menu. This is done using the `language` attribute for these `label` entries, as in this example `capabilities.xml` file:
```xml
<themeCapabilities>
<themeName>My theme</themeName>
<language>en_US</language>
<language>sv_SE</language>
<colorScheme name="dark">
<label language="en_US">Dark</label>
<label language="sv_SE">Mörkt</label>
</colorScheme>
<variant name="withVideos">
<label language="en_US">Textlist with videos</label>
<label language="sv_SE">Textlista med video</label>
<selectable>true</selectable>
<override>
<trigger>noMedia</trigger>
<mediaType>miximage, screenshot, cover, video</mediaType>
<useVariant>noGameMedia</useVariant>
</override>
</variant>
<transitions name="instantAndSlide">
<label language="en_US">instant and slide</label>
<label language="sv_SE">direkt och glidande</label>
<selectable>true</selectable>
<systemToSystem>instant</systemToSystem>
<systemToGamelist>slide</systemToGamelist>
<gamelistToGamelist>instant</gamelistToGamelist>
<gamelistToSystem>slide</gamelistToSystem>
<startupToSystem>slide</startupToSystem>
<startupToGamelist>slide</startupToGamelist>
</transitions>
</themeCapabilities>
```
Leaving out the `language` property will make ES-DE set the language to en_US.
The actual language-specific values in the theme configuration are defined using variables, like the following example:
```xml
<theme>
<language name="en_US">
<variables>
<langLogo>logo.svg</langLogo>
<langLabelDeveloper>Developer</langLabelDeveloper>
<langLabelPublisher>Publisher</langLabelPublisher>
</variables>
</language>
<language name="sv_SE">
<variables>
<langLogo>logo-sv-se.svg</langLogo>
<langLabelDeveloper>Utvecklare</langLabelDeveloper>
<langLabelPublisher>Utgivare</langLabelPublisher>
</variables>
</language>
<view name="gamelist">
<image name="logo">
<pos>0.38 0.1781</pos>
<size>0.158 0.12</size>
<path>./assets/${langLogo}</path>
</image>
<text name="labelDeveloper">
<pos>0.88 0.511</pos>
<size>0.165 0.03</size>
<text>${langLabelDeveloper}</text>
</text>
<text name="labelPublisher">
<pos>0.88 0.5935</pos>
<size>0.165 0.03</size>
<text>${langLabelPublisher}</text>
</text>
</view>
</theme>
```
It could also be a good idea to include the translations from a separate file:
```xml
<theme>
<include>./languages.xml</include>
<view name="gamelist">
<image name="logo">
<pos>0.38 0.1781</pos>
<size>0.158 0.12</size>
<path>./assets/${langLogo}</path>
</image>
<text name="labelDeveloper">
<pos>0.88 0.511</pos>
<size>0.165 0.03</size>
<text>${langLabelDeveloper}</text>
</text>
<text name="labelPublisher">
<pos>0.88 0.5935</pos>
<size>0.165 0.03</size>
<text>${langLabelPublisher}</text>
</text>
</view>
</theme>
```
Including separate files per language is also supported but it's probably not a good idea as it will add a lot of unnecessary files to the theme:
```xml
<theme>
<language name="en_US">
<include>./lang-en-us.xml</include>
</language>
<language name="sv_SE">
<include>./lang-sv-se.xml</include>
</language>
<view name="gamelist">
<image name="logo">
<pos>0.38 0.1781</pos>
<size>0.158 0.12</size>
<path>./assets/${langLogo}</path>
</image>
<text name="labelDeveloper">
<pos>0.88 0.511</pos>
<size>0.165 0.03</size>
<text>${langLabelDeveloper}</text>
</text>
<text name="labelPublisher">
<pos>0.88 0.5935</pos>
<size>0.165 0.03</size>
<text>${langLabelPublisher}</text>
</text>
</view>
</theme>
```
Note the naming convention when using localized versions of files such as images. These should include the locale/language in their name and they should be in lowercase characters, using only dashes as separators. For the default language the locale could be omitted from the filename (as language-specific images and similar will likely be exceptions with most files rather shared across all locales). Here are some examples:
```
logo.svg
logo-fr-fr.svg
logo-pt-br.svg
logo-sv-se.svg
auto-allgames.webp
auto-allgames-fr-fr.webp
auto-allgames-pt-br.webp
auto-allgames-sv-se.webp
```
## Aspect ratios ## Aspect ratios
The aspect ratio support works almost identically to the variants and color schemes with the main difference that the available aspect ratios are hardcoded into ES-DE. The theme can still decide which of the aspect ratios to support (or none at all in which case the theme aspect ratio is left undefined) but it can't create entirely new aspect ratio entries. The aspect ratio support works almost identically to the variants and color schemes with the main difference that the available aspect ratios are hardcoded into ES-DE. The theme can still decide which of the aspect ratios to support (or none at all in which case the theme aspect ratio is left undefined) but it can't create entirely new aspect ratio entries.
@ -898,9 +1093,9 @@ Finally it's possible to apply theme-defined transition profiles on a per-varian
## capabilities.xml ## capabilities.xml
Variants, variant triggers, color schemes, aspect ratios and transition animation profiles need to be declared before they can be used inside the actual theme configuration files and that is done in the `capabilities.xml` file. This file needs to be located in the root of the theme directory, for example: Variants, variant triggers, color schemes, font sizes, languages, aspect ratios and transition animation profiles need to be declared before they can be used inside the actual theme configuration files and that is done in the `capabilities.xml` file. This file needs to be located in the root of the theme directory, for example:
``` ```
~/.emulationstation/themes/mytheme-es-de/capabilities.xml ~/ES-DE/themes/mytheme-es-de/capabilities.xml
``` ```
The capabilities.xml file is mandatory and if it doesn't exist ES-DE will not attempt to load the theme. The capabilities.xml file is mandatory and if it doesn't exist ES-DE will not attempt to load the theme.
@ -912,19 +1107,29 @@ The structure of the file is simple, as can be seen in this example:
<themeCapabilities> <themeCapabilities>
<themeName>My theme</themeName> <themeName>My theme</themeName>
<language>en_US</language>
<language>sv_SE</language>
<aspectRatio>16:9</aspectRatio> <aspectRatio>16:9</aspectRatio>
<aspectRatio>4:3</aspectRatio> <aspectRatio>4:3</aspectRatio>
<aspectRatio>4:3_vertical</aspectRatio> <aspectRatio>4:3_vertical</aspectRatio>
<fontSize>medium</fontSize>
<fontSize>large</fontSize>
<colorScheme name="dark"> <colorScheme name="dark">
<label>Dark mode</label> <label language="en_US">Dark</label>
<label language="sv_SE">Mörkt</label>
</colorScheme> </colorScheme>
<colorScheme name="light"> <colorScheme name="light">
<label>Light mode</label> <label language="en_US">Light</label>
<label language="sv_SE">Ljust</label>
</colorScheme> </colorScheme>
<transitions name="instantAndSlide"> <transitions name="instantAndSlide">
<label language="en_US">instant and slide</label>
<label language="sv_SE">direkt och glidande</label>
<systemToSystem>instant</systemToSystem> <systemToSystem>instant</systemToSystem>
<systemToGamelist>slide</systemToGamelist> <systemToGamelist>slide</systemToGamelist>
<gamelistToGamelist>instant</gamelistToGamelist> <gamelistToGamelist>instant</gamelistToGamelist>
@ -932,7 +1137,8 @@ The structure of the file is simple, as can be seen in this example:
</transitions> </transitions>
<variant name="withVideos"> <variant name="withVideos">
<label>Textlist with videos</label> <label language="en_US">Textlist with videos</label>
<label language="sv_SE">Textlista med video</label>
<selectable>true</selectable> <selectable>true</selectable>
<override> <override>
<trigger>noMedia</trigger> <trigger>noMedia</trigger>
@ -942,7 +1148,8 @@ The structure of the file is simple, as can be seen in this example:
</variant> </variant>
<variant name="withoutVideos"> <variant name="withoutVideos">
<label>Textlist without videos</label> <label language="en_US">Textlist without videos</label>
<label language="sv_SE">Textlista utan video</label>
<selectable>true</selectable> <selectable>true</selectable>
<override> <override>
<trigger>noMedia</trigger> <trigger>noMedia</trigger>
@ -957,7 +1164,8 @@ The structure of the file is simple, as can be seen in this example:
</variant> </variant>
</themeCapabilities> </themeCapabilities>
``` ```
The file format is hopefully mostly self-explanatory; this example provides three aspect ratios, two color schemes, one transition animation profile and three variants, one of which is a variant trigger override. The `<label>` tag for the variants and transitions is the text that will show up in the _UI Settings_ menu, assuming `<selectable>` has been set to true. The same is true for color schemes, although these will always show up in the GUI and can't be disabled.
The file format is hopefully mostly self-explanatory; this example provides two languages, three aspect ratios, two font sizes, two color schemes, one transition animation profile and three variants, one of which is a variant trigger override. The `<label>` tag for the variants and transitions is the text that will show up in the _UI Settings_ menu, assuming `<selectable>` has been set to true. The same is true for color schemes, although these will always show up in the GUI and can't be disabled.
The optional `<themeName>` tag defines the name that will show up in the _Theme_ option in the _UI Settings_ menu. If no such tag is present, then the physical directory name will be displayed instead, for example _MYTHEME-ES-DE_. Note that theme names will always be converted to uppercase characters when displayed in the menu. The optional `<themeName>` tag defines the name that will show up in the _Theme_ option in the _UI Settings_ menu. If no such tag is present, then the physical directory name will be displayed instead, for example _MYTHEME-ES-DE_. Note that theme names will always be converted to uppercase characters when displayed in the menu.
@ -984,7 +1192,7 @@ It's normally not necessary to define all or even most of these for a theme, ins
The declared aspect ratios will always get displayed in the _UI settings_ menu in the order listed in the table above, so they can be declared in any order in the capabilities.xml file. If an unsupported aspect ratio value is entered, a warning will be generated on startup and the entry will not get loaded. The declared aspect ratios will always get displayed in the _UI settings_ menu in the order listed in the table above, so they can be declared in any order in the capabilities.xml file. If an unsupported aspect ratio value is entered, a warning will be generated on startup and the entry will not get loaded.
The use of variants, variant triggers, color schemes, aspect ratios and transition animation profiles is optional, i.e. a theme does not need to provide any of them. There must however be a capabilities.xml file present in the root of the theme directory. So if you don't wish to provide this functionality, simply create an empty file or perhaps add a short XML comment to clarify that the theme does not provide this functionality. In this case the theme will still load and work correctly but the menu options for selecting variants, color schemes and aspect ratios will be grayed out. The use of variants, variant triggers, color schemes, font sizes, languages, aspect ratios and transition animation profiles is optional, i.e. a theme does not need to provide any of them. There must however be a capabilities.xml file present in the root of the theme directory. So if you don't wish to provide this functionality, simply create an empty file or perhaps add a short XML comment to clarify that the theme does not provide this functionality. In this case the theme will still load and work correctly but the menu options for selecting variants, color schemes and aspect ratios will be grayed out.
Note that changes to the capabilities.xml file are not reloaded when using the Ctrl + r key combination, instead ES-DE needs to be restarted to reload any changes to this file. Note that changes to the capabilities.xml file are not reloaded when using the Ctrl + r key combination, instead ES-DE needs to be restarted to reload any changes to this file.
@ -992,7 +1200,7 @@ Note that changes to the capabilities.xml file are not reloaded when using the C
You can include theme files within theme files, for example: You can include theme files within theme files, for example:
`~/.emulationstation/themes/mytheme-es-de/fonts.xml`: `~/ES-DE/themes/mytheme-es-de/fonts.xml`:
```xml ```xml
<theme> <theme>
<view name="gamelist"> <view name="gamelist">
@ -1005,7 +1213,7 @@ You can include theme files within theme files, for example:
</theme> </theme>
``` ```
`~/.emulationstation/themes/mytheme-es-de/snes/theme.xml`: `~/ES-DE/themes/mytheme-es-de/snes/theme.xml`:
```xml ```xml
<theme> <theme>
<include>./../fonts.xml</include> <include>./../fonts.xml</include>
@ -1250,7 +1458,7 @@ Example `navigationsounds.xml` file:
## Element rendering order using zIndex ## Element rendering order using zIndex
You can change the order in which elements are rendered by setting their `zIndex` values. All elements have a default value so you only need to define it for the ones you wish to explicitly change. Elements will be rendered in order from smallest to largest values. A complete description of each element including all supported properties can be found in the [Reference](THEMES.md#reference) section. You can change the order in which elements are rendered by setting their `zIndex` values. All elements have a default value so you only need to define it for the ones you wish to explicitly change. Elements will be rendered in order from smallest to largest values. A complete description of each element including all supported properties can be found in the reference section.
These are the default zIndex values per element type: These are the default zIndex values per element type:
@ -1279,7 +1487,7 @@ Theme variables can be used to simplify theme construction and there are two typ
### System variables ### System variables
System variables are system specific and are derived from values defined in es_systems.xml (except for collections which are derived from hardcoded application-internal values). System variables are system-specific and derived from values defined in es_systems.xml (except for collections which are derived from hardcoded application-internal values).
* `system.name` * `system.name`
* `system.name.autoCollections` * `system.name.autoCollections`
* `system.name.customCollections` * `system.name.customCollections`
@ -1297,9 +1505,11 @@ System variables are system specific and are derived from values defined in es_s
`system.fullName` expands to the full system name as defined by the `fullname` tag in es_systems.xml\ `system.fullName` expands to the full system name as defined by the `fullname` tag in es_systems.xml\
`system.theme` expands to the theme directory as defined by the `theme` tag in es_systems.xml `system.theme` expands to the theme directory as defined by the `theme` tag in es_systems.xml
Note that all _name_ and _fullName_ values for the built-in collections _all games, favorites, last played and collections_ are translated to the language selected via the _Application Language_ option in the _UI Settings_ menu.
If using variables to load theme assets like images and videos, then use the `.name` versions of these variables as short system names should be stable and not change over time. The `.fullName` values could change in future ES-DE releases or they could be user-customized which would break your theme. If using variables to load theme assets like images and videos, then use the `.name` versions of these variables as short system names should be stable and not change over time. The `.fullName` values could change in future ES-DE releases or they could be user-customized which would break your theme.
The `.autoCollections`, `.customCollections` and `.noCollections` versions of the variables make it possible to differentiate between regular systems, automatic collections (_all games_, _favorites_ and _last played_) and custom collections. This can for example be used to apply different formatting to the names of the collections as opposed to regular systems. The `.autoCollections`, `.customCollections` and `.noCollections` versions of the variables make it possible to differentiate between regular systems, automatic collections (_all games, favorites_ and _last played_) and custom collections. This can for example be used to apply different formatting to the names of the collections as opposed to regular systems.
The below example capitalizes the names of the auto collections while leaving custom collections and regular systems at their default formatting (as they are defined by the user and es_systems.xml respectively). The reason this works is that the .autoCollections, .customCollections and .noCollections variables are mutually exclusive, i.e. a system is either a real system or an automatic collection or a custom collection and never more than one of these. The below example capitalizes the names of the auto collections while leaving custom collections and regular systems at their default formatting (as they are defined by the user and es_systems.xml respectively). The reason this works is that the .autoCollections, .customCollections and .noCollections variables are mutually exclusive, i.e. a system is either a real system or an automatic collection or a custom collection and never more than one of these.
@ -1420,19 +1630,20 @@ It's important to understand how the theme configuration files are parsed in ord
2) Variables 2) Variables
3) Color schemes 3) Color schemes
4) Font sizes 4) Font sizes
5) Included files 5) Languages
6) "General" (non-variant) configuration 6) Included files
7) Variants 7) "General" (non-variant) configuration
8) Aspect ratios 8) Variants
9) Aspect ratios
When including a file using the `<include>` tag (i.e. step 4 above) then all steps listed above are executed for that included file prior to continuing to the next line after the `<include>` tag. When including a file using the `<include>` tag (i.e. step 6 above) then all steps listed above are executed for that included file prior to continuing to the next line after the `<include>` tag.
For any given step, the configuration is parsed in the exact order that it's defined in the XML file. Be mindful of the logic described above as for instance defining variant-specific configuration above general configuration in the same XML file will still have that parsed afterwards. For any given step, the configuration is parsed in the exact order that it's defined in the XML file. Be mindful of the logic described above as for instance defining variant-specific configuration above general configuration in the same XML file will still have that parsed afterwards.
## Property data types ## Property data types
* NORMALIZED_PAIR - two decimal values delimited by a space, for example `0.25 0.5` * NORMALIZED_PAIR - two decimal values delimited by a space, for example `0.25 0.5`
* PATH - path to a resource. If the first character is a tilde (`~`) then it will be expanded to the user's home directory (`$HOME` for Linux, BSD Unix and macOS and `%HOMEPATH%` for Windows) unless overridden using the --home command line option. If the first character is a dot (`.`) then the resource will be searched for relative to the location of the theme file, for example `./myfont.ttf` or `./../core/fonts/myfont.ttf` * PATH - path to a resource. If the first character is a tilde (`~`) then it will be expanded to the user's home directory (`$HOME` for Linux and macOS and `%HOMEPATH%` for Windows) unless overridden using the --home command line option. If the first character is a dot (`.`) then the resource will be searched for relative to the location of the theme file, for example `./myfont.ttf` or `./../core/fonts/myfont.ttf`
* BOOLEAN - `true`/`1` or `false`/`0` * BOOLEAN - `true`/`1` or `false`/`0`
* COLOR - a hexadecimal RGB or RGBA color value consisting of 6 or 8 digits. If a 6 digit value is used then the alpha channel will be set to `FF` (completely opaque) * COLOR - a hexadecimal RGB or RGBA color value consisting of 6 or 8 digits. If a 6 digit value is used then the alpha channel will be set to `FF` (completely opaque)
* UNSIGNED_INTEGER - an unsigned integer value * UNSIGNED_INTEGER - an unsigned integer value
@ -2802,6 +3013,7 @@ Properties:
* `container` - type: BOOLEAN * `container` - type: BOOLEAN
- Whether the text should be placed inside a scrollable container. - Whether the text should be placed inside a scrollable container.
- Default is `true` if `metadata` is set to `description`, otherwise `false` - Default is `true` if `metadata` is set to `description`, otherwise `false`
- This property can only be used if `size` has a width defined.
* `containerType` - type: STRING * `containerType` - type: STRING
- If `container` has been set, then it's possible to select between a vertically or horizontally scrolling type using this property. If selecting the horizontal container then all line breaks in the text will be automatically converted to spaces. If selecting the vertical container then any value defined for `rotation` will be ignored as this container type can't be rotated. - If `container` has been set, then it's possible to select between a vertically or horizontally scrolling type using this property. If selecting the horizontal container then all line breaks in the text will be automatically converted to spaces. If selecting the vertical container then any value defined for `rotation` will be ignored as this container type can't be rotated.
- Valid values are `vertical` or `horizontal` - Valid values are `vertical` or `horizontal`
@ -2941,6 +3153,16 @@ Properties:
- Default is `center` - Default is `center`
* `color` - type: COLOR * `color` - type: COLOR
* `backgroundColor` - type: COLOR * `backgroundColor` - type: COLOR
* `backgroundMargins` - type: NORMALIZED_PAIR
- Adds margins to the text background, assuming it has a color set. The first value of the pair is the left margin and the second value is the right margin, which means it's possible to set these margins completely independently. Margins are applied after all other positioning and sizing calculations and they are rendered outside the text debug rectangle boundaries.
- Minimum value per axis is `0` and maximum value per axis is `0.5`
- Default is `0 0`
- This property can only be used if `backgroundColor` has a value defined.
* `backgroundCornerRadius` - type: FLOAT
- Setting this property higher than zero applies rounded corners to the text background, assuming it has a color set. The radius is a percentage of the screen width. Note that the maximum allowed value is quite arbitrary as the renderer will in practice limit the maximum roundness so it can never go beyond half the text background height. It means that setting this property sufficiently high will produce perfectly rounded sides for the text background. You normally want to combine this property with `backgroundMargins` to add some extra margins.
- Minimum value is `0` and maximum value is `0.5`
- Default is `0` (corners are not rounded)
- This property can only be used if `backgroundColor` has a value defined.
* `letterCase` - type: STRING * `letterCase` - type: STRING
- Valid values are `none`, `uppercase`, `lowercase` or `capitalize` - Valid values are `none`, `uppercase`, `lowercase` or `capitalize`
- Default is `none` (original letter case is retained) - Default is `none` (original letter case is retained)

201
TRANSLATIONS.md Normal file
View file

@ -0,0 +1,201 @@
# ES-DE Frontend - Translations
This document is intended for translators who want to contribute localizations to ES-DE.
Table of contents:
[[_TOC_]]
## Introduction
ES-DE has full localization support which means it can be translated to different languages. Adding support for a new locale does however require some minor code changes, so if you're interested in translating to a new locale you need to request support for it first. The best approach is to join our Discord server where we have a dedicated translations channel:
https://discord.gg/42jqqNcHf9
Translation updates are handled manually via this Discord server. As some translators are not familiar with using tools such as Git it was deemed simplest to coordinate all translations there. It means you can upload your translations to the channel and they will be incorporated into the ES-DE source repository.
## License and copyright
ES-DE is released under the MIT license which is a permissive license that allows commercial use. Any translation work will as such be MIT licensed too. This is clearly indicated in the .po message catalog files that are used as the basis for the translation work. By contributing translations to ES-DE you'll also agree to transferring your copyright to the project and its owning company Northwestern Software AB. The copyright owner is also clearly indicated in the .po message catalog files.
## High level approach
There are two types of translations in ES-DE, the first one is specific to Android and contains strings for the onboarding configurator and the second is using [gettext and libintl](https://www.gnu.org/software/gettext) to provide translations to the overall application.
The Android-specific part is quite limited with only a few strings. You can find the latest English version of this file here:
https://gitlab.com/es-de/emulationstation-de/-/blob/master/locale/android_strings.xml
There is not much more to it when it comes to these strings, just translate them and provide the XML file via Discord and they will be added to the Android release.
However the overwhelming majority of the translations are done using gettex/libintl, and the way this works is via so-called _message catalog files_ where there's one such file per supported locale. When adding support for a new locale such a file will be added to the ES-DE repository. These files which have the .po file extension (for _Portable Object_) can be found here:
https://gitlab.com/es-de/emulationstation-de/-/tree/master/locale/po
Note that all .po files are named after the locale. This is always in the form of _language code_ plus _country code_. For example _sv_SE.po_ where _sv_ is the language code for Swedish and _SE_ is the country code for Sweden. There are often country-specific variations. For example there's also an sv_FI locale for Swedish (Finland). If you want to add translations for a specific locale such as German (Austria) or English (United Kingdom) then this is therefore possible.
When using ES-DE the specific locale you have configured in your operating systems will be searched for and applied, and if this does not exist then the default locale for your language will be selected such as falling back to sv_SE if you have sv_FI set as your language. If there is no support at all for your language then a fallback will take place to the default application language _English (United States)_.
You can test your translations quite easily as explained later in this document, and when you want to have your updates added to the ES-DE repository you can share the updated .po file in the Discord server.
When working on translations it's also a good idea to refer to existing translations for other languages as they may provide useful insights for best approaches and such.
## Tools
It's highly recommended to use Poedit when working on translations. This tool is free and open source and is available on Linux, macOS and Windows:
https://poedit.net
Poedit can also compile the .mo files needed by ES-DE to apply the actual translations, so it's required in order to test your translations (unless you use the gettext utilities directly to compile the .mo file).
## Translations in practice
When support has been added to ES-DE for a certain language a corresponding .po file will be added to the ES-DE repository at the following location:
https://gitlab.com/es-de/emulationstation-de/-/tree/master/locale/po
You simply download this file and open it in Poedit to start working on your translations.
The way gettext works is that there's a pair of _msgid_ and _msgstr_ entries per text string, and these will be presented as such inside Poedit. The _msgid_ string is the literal string in the default _English (United States)_ locale as it's presented inside ES-DE. There is a slight exception for hinting as explained later in this document but in general you simply see the literal text that needs translations and then you add your own translation following this. An entry inside the .po file would look something like this:
```
msgid "Permission problems?"
msgstr "Åtkomstproblem?"
```
This is for the Swedish translation in the sv_SE.po file.
In addition to this some strings contain a _format specifier_. This makes it possible to define where a certain value should be placed inside a string. As the order of words differ between languages this is important. But most often it's simply used to parse the actual string that will be visible inside the application. Here's an example to clarify:
```
#, c-format
msgid "ERROR LAUNCHING GAME '%s' (ERROR CODE %i)"
msgstr "KUNDE INTE STARTA SPELET '%s' (FELKOD %i)"
```
The amount and types of format specifiers in the translated msgstr string must match the source msgid string exactly, or otherwise you'll not be able to compile to .po file and your translations won't work.
Finally there are plural entries where there are different translations based on the numerical amount parsed into the string. The following example will select _%i VALD_ if it's singular and _%i VALDA_ if it's plural in Swedish, even though there is no distinction between the two in the English language:
```
#, c-format
msgid "%i SELECTED"
msgid_plural "%i SELECTED"
msgstr[0] "%i VALD"
msgstr[1] "%i VALDA"
```
If you're translating to a language where there is no distinction between the two then you simply set the same value for both entries. If using Poedit all this will be easily handled by the user interface where you'll have separate tabs for the singular and plural entries.
As a general remark the correct letter case is very important for the translated text. Although there are a few instances where text is for example automatically converted to uppercase, in most instances such conversions are not made. This means that in most cases the translated text will appear exactly as entered in the .po file. This approach provides maximum flexibility and of course a number of languages don't even have the concept of letter case so automatic case conversions wouldn't make sense.
## Context information
As there is sometimes ambiguity regarding translated strings, such as the same word having different meanings depending on the context, there is contextual hinting added to a number of the translation strings. Similarly some strings may need short versions for some languages as they may otherwise not fit inside the user interface. For the latter it's really a per-case thing and you'll need to test your translations to see what fits inside the interface and what doesn't. If you need context information added for a string then bring it up in the Discord server and it will get added to the application.
If you are translating to a language with excessively long words (Swedish is such a language) then it may be required to adjust the overall font sizes in ES-DE for this specific language. At the moment this is only applicable to the menu titles as these are quite restricted in length. If you find that you're constantly running out of space for your text then bring it up in the Discord server and a font size adjustment can be made in ES-DE for your specific locale.
Here's an example of a context information that is applicable for the Swedish language:
```
msgid "COMPLETED"
msgstr "SLUTFÖRD"
```
```
msgctxt "metadata"
msgid "COMPLETED"
msgstr "KLARAT"
```
In general _completed_ is translated as _slutförd_ but for example when having played through an entire game (as indicated in the metadata editor for the game) the word _klarat_ makes more sense. Although you could use _slutförd_ for a completed game this sounds pretty strange in Swedish.
However the English translations for this would be identical as there is no real distinction there:
```
msgid "COMPLETED"
msgstr "COMPLETED"
```
```
msgctxt "metadata"
msgid "COMPLETED"
msgstr "COMPLETED"
```
Here's also an example of a short version string:
```
msgid "GAMES DEFAULT SORT ORDER"
msgstr "GAMES DEFAULT SORT ORDER"
```
```
msgctxt "short"
msgid "GAMES DEFAULT SORT ORDER"
msgstr "DEFAULT SORT ORDER"
```
The short version of this string was required as it would otherwise not fit inside the menu header. Note that short strings may only be required for some specific languages, so again you need to test it to see whether you actaully need to provide a short translation or not.
Whenever there's a _msgctxt_ line for a message it will be clearly indicated in Poedit so it's very easy to work with this context information.
## Fuzzy entries
Sometimes when changes are made to translation strings this will cause _fuzzy_ entries to get added to the .po file. This means that gettext detected something has changed but is not sure what to do. In these cases the translator needs to make an explicit decision on how to handle the change. Using Poedit makes the whole process simple as each fuzzy entry is clearly indicated with a _Needs Work_ flag in its user interface.
Say there was the following string in ES-DE:
```
msgid "THEME ASPECT RATIOS"
msgstr "TEMA BILDFÖRHÅLLANDEN"
```
And then it was decided that this should change to _THEME ASPECT RATIO_ instead. When the corresponding code change was done, new .po files were also automatically generated for all languages and committed to the ES-DE repository. However as the string was changed slightly gettext marked it as fuzzy in the .po files, like so:
```
#, fuzzy
msgid "THEME ASPECT RATIO"
msgstr "TEMA BILDFÖRHÅLLANDEN"
```
When an entry is marked as fuzzy it's excluded when compiling the .po file, or in other words it's not getting translated at all.
In this case a slightly updated translation was required, but other times it simply needs to be marked as OK in Poedit. When marking a translation as OK in Poedit or when updating it, the fuzzy flag is removed and the end result would look something like the following for our example:
```
msgid "THEME ASPECT RATIO"
msgstr "TEMA BILDFÖRHÅLLANDE"
```
## Continuous translations
As ES-DE is constantly worked on, translations also need continuous updates. When translation strings are updated or added they will be committed to the ES-DE repository, and such changes will also be discussed and communicated in the Discord server. As well sometimes major features may get added that require additional translation work. To check the status for your translations you can always download the latest .po file from here:
https://gitlab.com/es-de/emulationstation-de/-/tree/master/locale/po
If opening the file in Poedit it will tell you the percentage of translated messages, and if any entries are marked as fuzzy.
## Testing your translations
You can have Poedit compile the binary .mo file whenever you save a .po file. The .mo file (for _Machine Object_) is what ES-DE actually uses to load the translations, i.e. the source .po file is not used when running the application. If not enabled for your setup then you can find this setting inside the Poedit Preferences screen, where it's named _Automatically compile MO file when saving_.
In order to have the .mo file loaded in ES-DE simply create the following directory in your ES-DE application data directory:
```
ES-DE/resources/locale/<locale code>/LC_MESSAGES
```
Then place your .po file there and open it using Poedit. Whenever you save the .po file the .mo file will get generated in the same directory, such as the following example:
```
ES-DE/resources/locale/sv_SE/LC_MESSAGES/sv_SE.mo
ES-DE/resources/locale/sv_SE/LC_MESSAGES/sv_SE.po
```
When there's an .mo file stored there it will override the bundled .mo file and ES-DE will use your local copy instead. This way you can easily test your own translations without having to build ES-DE from source code. Note that you need to restart ES-DE anytime you've compiled a new .mo file.
Also note that this will not work unless support for your language has already been explicitly added to ES-DE.
## Theme translations
In addition to what has been described above there is translation work needed for the actual themes as well. Some portion of the text displayed by the theme engine comes from the application .po files (like the system view game counter and the custom collection summary), but most text is contained within each theme's configuration files.
So to have a fully translated experience it's important to work together with the theme developers so they can incorporate full localization support into their themes. There is also an official system metadata repository available for theme developers to easily include things like game system descriptions and various other information, and the goal is to have this translated to all languages as well.
This repository can be found here:\
https://gitlab.com/es-de/themes/system-metadata
And here is a link to the language section of the theme engine documentation which contains some further relevant information:\
[THEMES.md](THEMES.md#languages)
Discussions regarding theme translations are also covered in the ES-DE Discord server.

View file

@ -24,11 +24,11 @@ You can always close the application immediately using the keyboard, by default
For additional details, read on below. For additional details, read on below.
There are also installation videos available at the ES-DE YouTube channel:\ There are also installation videos available at the ES-DE YouTube channel:\
[https://www.youtube.com/channel/UCosLuC9yIMQPKFBJXgDpvVQ](https://www.youtube.com/channel/UCosLuC9yIMQPKFBJXgDpvVQ) https://www.youtube.com/channel/UCosLuC9yIMQPKFBJXgDpvVQ
## Installation and first startup ## Installation and first startup
To install ES-DE, just download the package or installer from [https://es-de.org](https://es-de.org) and follow the brief instructions below. To install ES-DE, just download the package or installer from https://es-de.org and follow the brief instructions below.
As for display resolutions, the minimum pixel value is 224 and the maximum is 7680. This means that you can run ES-DE at for instance 320x224 all the way up to 7680x4320 (8K UHD). Vertical screen orientation is also supported, as well as ultra-wide resolutions like 3840x1440. As for display resolutions, the minimum pixel value is 224 and the maximum is 7680. This means that you can run ES-DE at for instance 320x224 all the way up to 7680x4320 (8K UHD). Vertical screen orientation is also supported, as well as ultra-wide resolutions like 3840x1440.
@ -67,7 +67,7 @@ Also on first startup the configuration file `es_settings.xml` will be generated
In addition to es_systems.xml there's an `es_find_rules.xml` file that gets loaded as well and which contains rules on how to locate the emulators, i.e. how to find out where they've been installed. In addition to es_systems.xml there's an `es_find_rules.xml` file that gets loaded as well and which contains rules on how to locate the emulators, i.e. how to find out where they've been installed.
There's an application log file created in the ES-DE home directory named `es_log.txt`, refer to this in case of any issues as it should hopefully provide information on what went wrong. Enabling _Debug mode_ in the _Other settings_ menu or starting ES-DE with the --debug flag outputs even more detailed information to this log file. There's also a log file created in the `ES-DE/logs` directory named `es_log.txt`, refer to this in case of any issues as it should hopefully provide information on what went wrong. Enabling _Debug mode_ in the _Other settings_ menu or starting ES-DE with the --debug flag outputs even more detailed information to this log file.
After ES-DE finds at least one game file, it will populate that game system and the application will start. If there are no game files, a dialog will be shown explaining that you need to install your game files into your ROM directory. You will also be given a choice to change that ROM directory path if you don't want to use the default one. As well you have the option to generate the complete game systems directory structure based on information in es_systems.xml. After ES-DE finds at least one game file, it will populate that game system and the application will start. If there are no game files, a dialog will be shown explaining that you need to install your game files into your ROM directory. You will also be given a choice to change that ROM directory path if you don't want to use the default one. As well you have the option to generate the complete game systems directory structure based on information in es_systems.xml.
@ -122,13 +122,13 @@ _This is the dialog shown if no game files were found. It lets you configure the
**Note:** Before upgrading ES-DE, make sure that you have not made any system customizations anywhere in the installation directory structure as these files will be overwritten during the upgrade process. All customizations should go into ~/ES-DE/custom_systems/ as described elsewhere in this guide. None of the upgrade methods mentioned below will ever touch any files inside your ES-DE directory tree. **Note:** Before upgrading ES-DE, make sure that you have not made any system customizations anywhere in the installation directory structure as these files will be overwritten during the upgrade process. All customizations should go into ~/ES-DE/custom_systems/ as described elsewhere in this guide. None of the upgrade methods mentioned below will ever touch any files inside your ES-DE directory tree.
There is a built-in application updater that can automatically update the Linux AppImage releases, and as of ES-DE 2.2.0 there is also support for downloading the Windows and macOS packages. Just be aware that these will still need to be manually installed. Using the application updater is straightforward, just follow the on-screen instructions. For the AppImage releases the old file is retained by renaming it, adding its version to the filename followed by the .OLD extension, for example `ES-DE_x64_SteamDeck.AppImage_3.0.0.OLD` There is a built-in application updater that can automatically upgrade the Linux AppImages, and for Windows and macOS there is support for downloading the packages directly inside ES-DE. Just be aware that these will need to be manually installed. Using the application updater is straightforward, just follow the on-screen instructions. For the AppImage releases the old file is retained by renaming it, adding its version to the filename followed by the .OLD extension, for example `ES-DE_x64_SteamDeck.AppImage_3.0.0.OLD`
Note that the updater will keep whatever filename you had for your running AppImage file, which could potentially be confusing if you for example added version information to the filename. It's always recommend to keep the default AppImage filenames, i.e. `ES-DE_x64.AppImage` and `ES-DE_x64_SteamDeck.AppImage` Note that the updater will keep whatever filename you had for your running AppImage file, which could potentially be confusing if you for example added version information to the filename. It's always recommend to keep the default AppImage filenames, i.e. `ES-DE_x64.AppImage` and `ES-DE_x64_SteamDeck.AppImage`
On Windows and macOS you can specify to which directory you want to save the downloaded file. The default is `C:\Users\myusername\Downloads` on Windows and `/Users/myusername/Downloads` on macOS. On Windows and macOS you can specify to which directory you want to save the downloaded file. The default is `C:\Users\myusername\Downloads` on Windows and `/Users/myusername/Downloads` on macOS.
On Android all updates are made via the app store. Unless you have modifed the option _Check for application updates_ you'll see a popup on application startup whenever there's a new release in the app store. On Android the update process differs depending on whether you have the Patreon release or a release from either the Samsung Galaxy Store or Huawei AppGallery. For the store versions you simply update via the store app. For the Patreon release you'll get an email (sent to the address you used when buying ES-DE there) whenever there is a new version. For all Android releases, unless you have modifed the option _Check for application updates_ you'll see a popup on application startup whenever there's a new release available.
Regardless of package format and operating system it's a good idea to update the ROM directory tree after upgrading to a new version. It's possible that the new ES-DE release adds support for more systems and emulators compared to the version you previously had installed. The easiest way to do this is via the _Create/update system directories_ entry in the _Utilities_ menu. Alternatively the _--create-system-dirs_ command line option can be used. Both methods work identically and will create any missing system directories and also update the systems.txt and systeminfo.txt files. This is a safe operation as it will not overwrite or delete your game files. Regardless of package format and operating system it's a good idea to update the ROM directory tree after upgrading to a new version. It's possible that the new ES-DE release adds support for more systems and emulators compared to the version you previously had installed. The easiest way to do this is via the _Create/update system directories_ entry in the _Utilities_ menu. Alternatively the _--create-system-dirs_ command line option can be used. Both methods work identically and will create any missing system directories and also update the systems.txt and systeminfo.txt files. This is a safe operation as it will not overwrite or delete your game files.
@ -189,7 +189,19 @@ For very specific situations such as when the ROM directory tree is shared with
~/ROMs/nes/noload.txt ~/ROMs/nes/noload.txt
``` ```
Note that if the setting _Only show games from gamelist.xml files_ has been enabled then the noload.txt logic is completely bypassed as this option will make ES-DE load anything present in the gamelist.xml files, regardless of whether the files and directories actually exist. But this option (or the equivalent --gamelist-only command line option) is only intended for troubleshooting and debugging purposes and should not be enabled during normal application usage. Note that if the setting _Only show games from gamelist.xml files_ has been enabled then the noload.txt logic is completely bypassed as this option will make ES-DE load anything present in the gamelist.xml files, regardless of whether the files and directories actually exist.
## Skip loading of individual subdirectories
Sometimes you need to place things inside the ROMs directory tree that will not be visible inside ES-DE, such as texture packs and similar. But as ES-DE always scans all files to determine which ones are valid game files this can add significantly to the application startup time. However loading of such subdirectories can be skipped by placing a `noload.txt` file in the root of the directory, in the same manner as documented in the previous section above regarding disabling of game systems. For example:
```
~/ROMs/psx/textures/noload.txt
```
Just note that you can't clean out stale entries from the gamelist.xml files for any directories that have been hidden in this way. So to get rid of any gamelist.xml entries for such files temporarily remove the noload.txt file, restart or reload ES-DE, run the _Orphaned data cleanup_ utility, then create a new noload.txt file and finally reload or restart ES-DE again.
Note that if the setting _Only show games from gamelist.xml files_ has been enabled then the noload.txt logic is completely bypassed as this option will make ES-DE load anything present in the gamelist.xml files.
## Placing games and other resources on network shares ## Placing games and other resources on network shares
@ -231,7 +243,7 @@ Just make sure to never place games or other resources on network shares using t
Also make sure that you don't use the exFAT filesystem as its very poor disk I/O performance will make ES-DE run really slowly. Using this filesystem will make the theme downloader fail as well. Also make sure that you don't use the exFAT filesystem as its very poor disk I/O performance will make ES-DE run really slowly. Using this filesystem will make the theme downloader fail as well.
In order for ES-DE to run, graphics drivers with OpenGL support have to be installed. If not, the application simply won't start. For really old graphics cards the available drivers may not provide an OpenGL version that is modern enough for ES-DE to work, and in this case a last resort solution would be to install the _Mesa3D for Windows_ library which provides software-based OpenGL rendering. The 64-bit version of this library can be downloaded from [https://fdossena.com/?p=mesa/index.frag](https://fdossena.com/?p=mesa/index.frag) and you simply extract the opengl32.dll file into the ES-DE installation directory. Just be aware that the performance may be quite bad. In order for ES-DE to run, graphics drivers with OpenGL support have to be installed. If not, the application simply won't start. For really old graphics cards the available drivers may not provide an OpenGL version that is modern enough for ES-DE to work, and in this case a last resort solution would be to install the _Mesa3D for Windows_ library which provides software-based OpenGL rendering. The 64-bit version of this library can be downloaded from https://fdossena.com/?p=mesa/index.frag and you simply extract the opengl32.dll file into the ES-DE installation directory. Just be aware that the performance may be quite bad.
On some GPUs with buggy drivers, ES-DE may only display a black screen on startup or when launching a game. The problem can be worked around by specifying a window size for ES-DE that is a single pixel wider than the actual screen resolution. So for example for a 1280x800 display, the resolution can be set to 1281x800 and then rendering should work correctly. This is applied using the --resolution command line option, for example: On some GPUs with buggy drivers, ES-DE may only display a black screen on startup or when launching a game. The problem can be worked around by specifying a window size for ES-DE that is a single pixel wider than the actual screen resolution. So for example for a 1280x800 display, the resolution can be set to 1281x800 and then rendering should work correctly. This is applied using the --resolution command line option, for example:
``` ```
@ -292,27 +304,34 @@ Unless RetroDECK is used, Flatpak releases of some emulators may need some extra
If you are unfamiliar with Linux/Unix operating systems, make sure to at least read up on the concepts of _dotfiles_ (hidden files and directories), _home directories_ (including use of the tilde ~ character) and _symbolic links_ (symlinks): If you are unfamiliar with Linux/Unix operating systems, make sure to at least read up on the concepts of _dotfiles_ (hidden files and directories), _home directories_ (including use of the tilde ~ character) and _symbolic links_ (symlinks):
[https://en.wikipedia.org/wiki/Hidden_file_and_hidden_directory#Unix_and_Unix-like_environments](https://en.wikipedia.org/wiki/Hidden_file_and_hidden_directory#Unix_and_Unix-like_environments) \ https://en.wikipedia.org/wiki/Hidden_file_and_hidden_directory#Unix_and_Unix-like_environments \
[https://en.wikipedia.org/wiki/Home_directory#Unix](https://en.wikipedia.org/wiki/Home_directory#Unix) \ https://en.wikipedia.org/wiki/Home_directory#Unix \
[https://en.wikipedia.org/wiki/Symbolic_link](https://en.wikipedia.org/wiki/Symbolic_link) https://en.wikipedia.org/wiki/Symbolic_link
## Specific notes for Android ## Specific notes for Android
The Android port of ES-DE is quite different than the other versions, so it has its specifics covered by a dedicated [ANDROID.md](ANDROID.md) document. The Android port of ES-DE is quite different than the other versions, so it has its specifics covered by a dedicated [ANDROID.md](ANDROID.md) document.
## Specific notes for Haiku
The [Haiku](https://www.haiku-os.org) port of ES-DE is currently experimental as the OS itself is experimental and has some issues. Still most functionality in ES-DE is working and there is support for a quite large number of systems and emulators. If you're interested in Haiku it's for sure worth trying it out. See the dedicated [HAIKU.md](HAIKU.md) document for more details.
## Specific notes for Raspberry Pi ## Specific notes for Raspberry Pi
By default ES-DE on the Raspberry Pi requires a desktop environment to run, or more specifically a window manager and a sound server (like PulseAudio or PipeWire). It is however possible to use KMS/direct framebuffer access if the DEINIT_ON_LAUNCH flag is used when building ES-DE, as documented in the _Building on Unix_ section of the [INSTALL-DEV.md](INSTALL-DEV.md#building-on-unix) document. For the best experience with the Raspberry Pi it's adviced to run Android on it. There are custom OS builds available here: \
https://konstakang.com/
Note that there are no prebuilt packages for the Raspberry Pi, so you will need to compile ES-DE yourself. Fortunately this is easy to do and the process is documented [here](INSTALL-DEV.md#building-on-unix). If instead going for regular Linux, then by default ES-DE on the Raspberry Pi requires a desktop environment to run, or more specifically a window manager and a sound server (like PulseAudio or PipeWire). It is however possible to use KMS/direct framebuffer access if the DEINIT_ON_LAUNCH flag is used when building ES-DE, as documented in the _Building on Unix_ section of the [INSTALL-DEV.md](INSTALL-DEV.md#building-on-unix) document.
Note that there are no prebuilt Linux packages for the Raspberry Pi, so you will need to compile ES-DE yourself.
The Raspberry Pi 4/400 is the minimum recommended version and earlier boards have not been tested. The Raspberry Pi 4/400 is the minimum recommended version and earlier boards have not been tested.
In general, 720p works fine with the RPi 4, and 1080p is tolerable but not really a nice and smooth experience. Due to the relative weakness of the Rasperry Pi GPU, the video scanline rendering options for the screensaver and media viewer have been disabled. These options can be re-enabled via the menu if you don't mind lower video framerates. In general, 720p works fine with the RPi 4, and 1080p is tolerable on the RPi 5, but due to the relative weakness of the Rasperry Pi GPU, the video scanline rendering options for the screensaver and media viewer have been disabled (only for Linux and not for Android). These options can be re-enabled via the menu if you don't mind lower video framerates.
## Game system customizations ## Game system customizations
The game systems configuration file `es_systems.xml` is located in the ES-DE resources directory which is part of the application installation. As such this file is not intended to be modified directly. If system customizations are required, a separate es_systems.xml file should instead be placed in the `custom_systems` folder in the ES-DE home directory. The game systems configuration file `es_systems.xml` is located in the ES-DE resources directory which is part of the application installation. As such this file is not intended to be modified directly. If system customizations are required, a separate es_systems.xml file should instead be placed in the `custom_systems` folder in the ES-DE application data directory.
On Linux this means `/home/<username>/ES-DE/custom_systems/es_systems.xml`, on macOS `/Users/<username>/ES-DE/custom_systems/es_systems.xml`, on Windows `C:\Users\<username>\ES-DE\custom_systems\es_systems.xml` or `ES-DE\ES-DE\custom_systems\es_systems.xml` depending on whether the installer release or the portable release is used, and on Android it's `ES-DE/custom_systems/es_systems.xml`. On Linux this means `/home/<username>/ES-DE/custom_systems/es_systems.xml`, on macOS `/Users/<username>/ES-DE/custom_systems/es_systems.xml`, on Windows `C:\Users\<username>\ES-DE\custom_systems\es_systems.xml` or `ES-DE\ES-DE\custom_systems\es_systems.xml` depending on whether the installer release or the portable release is used, and on Android it's `ES-DE/custom_systems/es_systems.xml`.
@ -670,6 +689,7 @@ The following emulators are supported in AppImage format when using the bundled
| System name | Emulator | Filename configuration | | System name | Emulator | Filename configuration |
| :------------ | :------------------ | :----------------------------- | | :------------ | :------------------ | :----------------------------- |
| _Multiple_ | RetroArch | RetroArch-Linux*.AppImage | | _Multiple_ | RetroArch | RetroArch-Linux*.AppImage |
| _Multiple_ | jgenesis | jgenesis-cli*.AppImage |
| _Multiple_ | Mesen | Mesen*.AppImage | | _Multiple_ | Mesen | Mesen*.AppImage |
| dreamcast | Flycast | flycast-x86*.AppImage | | dreamcast | Flycast | flycast-x86*.AppImage |
| dreamcast | Flycast Dojo | flycast-dojo*.AppImage | | dreamcast | Flycast Dojo | flycast-dojo*.AppImage |
@ -679,7 +699,7 @@ The following emulators are supported in AppImage format when using the bundled
| macintosh | Basilisk II | BasiliskII*.AppImage | | macintosh | Basilisk II | BasiliskII*.AppImage |
| macintosh | SheepShaver | SheepShaver*.AppImage | | macintosh | SheepShaver | SheepShaver*.AppImage |
| n3ds | Citra | citra-qt*.AppImage | | n3ds | Citra | citra-qt*.AppImage |
| n3ds | Lime3DS | lime3ds-gui*.AppImage | | n3ds | Lime3DS | lime3ds.AppImage |
| n3ds | Panda3DS | Alber-*.AppImage | | n3ds | Panda3DS | Alber-*.AppImage |
| n64/n64dd | Rosalie's Mupen GUI | RMG*.AppImage | | n64/n64dd | Rosalie's Mupen GUI | RMG*.AppImage |
| ngage/symbian | EKA2L1 | EKA2L1*.AppImage | | ngage/symbian | EKA2L1 | EKA2L1*.AppImage |
@ -1120,7 +1140,8 @@ If you want to use MAME standalone then you need to place the following ROM file
a2diskiing.zip a2diskiing.zip
apple2e.zip apple2e.zip
d2fdc.zip d2fdc.zip
votrax.zip votrsc01.zip
votrsc01a.zip
``` ```
Note that you will need to enable UI controls in MAME to be able to exit the emulator via the normal exit key. The following page documents the default keys for exiting and toggling UI mode:\ Note that you will need to enable UI controls in MAME to be able to exit the emulator via the normal exit key. The following page documents the default keys for exiting and toggling UI mode:\
@ -1324,7 +1345,7 @@ There are multiple ways to run these games, for the computer models like the A50
This emulator is by far the most straightforward Amiga emulator to use, it's easy to configure and it runs all file types that ES-DE supports. It can run zipped files too for all supported formats. This emulator is by far the most straightforward Amiga emulator to use, it's easy to configure and it runs all file types that ES-DE supports. It can run zipped files too for all supported formats.
PUAE requires Amiga Kickstart ROMs to run, you can find more information about that topic here:\ PUAE requires Amiga Kickstart ROMs to run, you can find more information about that topic here:\
[https://github.com/libretro/libretro-uae/blob/master/README.md](https://github.com/libretro/libretro-uae/blob/master/README.md) https://github.com/libretro/libretro-uae/blob/master/README.md
For the Amiga computer models the recommended approach is to go for WHDLoad-packaged files in the `.lha` or `.zip` format. While it's also possible to use WHDLoad hard drive images in `.hdf` or `.hdz` format these will only work in PAUE so they are not really recommended as you may want to use another emulator in the future. For the Amiga computer models the recommended approach is to go for WHDLoad-packaged files in the `.lha` or `.zip` format. While it's also possible to use WHDLoad hard drive images in `.hdf` or `.hdz` format these will only work in PAUE so they are not really recommended as you may want to use another emulator in the future.
@ -1452,6 +1473,8 @@ How to configure each emulator is far beyond the scope of this document, but the
For this platform there are two basic approaches for how the setup could be done; either to present each game as a single entry inside ES-DE, or to retain each game's directory structure. The first alternative is more user-friendly, tidy and requires less setup but basically restricts the emulator selection to the DOSBox-Pure RetroArch core. There is an alternative way to setup single entries to work with all DOSBox forks, but it has some drawbacks as discussed below. For this platform there are two basic approaches for how the setup could be done; either to present each game as a single entry inside ES-DE, or to retain each game's directory structure. The first alternative is more user-friendly, tidy and requires less setup but basically restricts the emulator selection to the DOSBox-Pure RetroArch core. There is an alternative way to setup single entries to work with all DOSBox forks, but it has some drawbacks as discussed below.
If you want to emulate older DOS games and applications then there's also support for the VirtualXT RetroArch core, but this emulator can only run .img and .zip files and it probably won't be able to run most games from the 1990s. For these reasons this documentation only covers DOSBox.
If you prefer to present the games as single entries you could compress each game directory into a ZIP file with either the .zip or .dosz file extension. On game launch a menu will be displayed by DOSBox-Pure, asking which file inside the archive you would like to execute. This makes it possible to select the actual game file, or for example a setup utility like SETUP.EXE or INSTALL.EXE. Attempting to launch such an archive file with any other DOSBox fork will fail, or not work as expected. If you prefer to present the games as single entries you could compress each game directory into a ZIP file with either the .zip or .dosz file extension. On game launch a menu will be displayed by DOSBox-Pure, asking which file inside the archive you would like to execute. This makes it possible to select the actual game file, or for example a setup utility like SETUP.EXE or INSTALL.EXE. Attempting to launch such an archive file with any other DOSBox fork will fail, or not work as expected.
Here's an example of a .zip archive setup for use with DOSBox-Pure: Here's an example of a .zip archive setup for use with DOSBox-Pure:
@ -1492,7 +1515,15 @@ Regardless of game setup method, per-game settings can be applied. If using the
### Dragon 32 and Tano Dragon ### Dragon 32 and Tano Dragon
These computers as well as the Dragon 64 are slight varations of the Tandy Color Computer and as these machines are largely compatible with each other they're all emulated using the [XRoar](http://www.6809.org.uk/xroar) emulator. The Dragon 32, Dragon 64 and Tano Dragon are all slight variations of the Tandy Color Computer, so these machines are largely compatible with each other. They're all emulated using MAME standalone (MAME4droid 2024 on Android) or the [XRoar](http://www.6809.org.uk/xroar) emulator.
**MAME**
To use MAME you need the `dragon32.zip` and `dragon_fdc.zip` BIOS files in the ROMs/dragon32 directory and you need the `tanodr64.zip` BIOS file in ROMs/tanodragon.
For the dragon32 system there are four MAME emulator entries for tape and cartridge for the Dragon 32 and Dragon 64 models respectively and for the tanodragon system there are two entries for tape and cartridge.
**XRoar**
This emulator is available for Linux, macOS and Windows, although on Linux you may need to build it from source code depending on which distribution you're using. Refer to the XRoar website for more information. If you manually download or build the emulator yourself then see the [Using manually downloaded emulators on Linux](USERGUIDE-DEV.md#using-manually-downloaded-emulators-on-linux) section of this guide for more details on where you need to install it. This emulator is available for Linux, macOS and Windows, although on Linux you may need to build it from source code depending on which distribution you're using. Refer to the XRoar website for more information. If you manually download or build the emulator yourself then see the [Using manually downloaded emulators on Linux](USERGUIDE-DEV.md#using-manually-downloaded-emulators-on-linux) section of this guide for more details on where you need to install it.
@ -1707,7 +1738,7 @@ For Daphne games the structure will look something like the following, which is
``` ```
The directory name has to keep this naming convention with the name consisting of the Daphne game type (_lair_ for this example) followed by the .daphne extension. This name logic with a short name per game is similar to how it works in MAME and ScummVM. A list of available games can be found here: \ The directory name has to keep this naming convention with the name consisting of the Daphne game type (_lair_ for this example) followed by the .daphne extension. This name logic with a short name per game is similar to how it works in MAME and ScummVM. A list of available games can be found here: \
[http://www.daphne-emu.com/mediawiki/index.php/CmdLine](http://www.daphne-emu.com/mediawiki/index.php/CmdLine) https://www.daphne-emu.com:9443/mediawiki/index.php/CmdLine
In order to get the games to work, simply create an empty file named _\<game\>.daphne_ inside the game directory, for example `lair.daphne` in this case. The _Directories interpreted as files_ functionality will then allow the game to be launched even though it shows up as a single entry inside ES-DE. In order to get the games to work, simply create an empty file named _\<game\>.daphne_ inside the game directory, for example `lair.daphne` in this case. The _Directories interpreted as files_ functionality will then allow the game to be launched even though it shows up as a single entry inside ES-DE.
@ -2212,7 +2243,7 @@ The drawback to using shortcuts is that they're not portable, if you change the
**Linux:** **Linux:**
On Linux you need to supply your own game engine binary as few (if any) games are distributed with the Linux release of OpenBOR. Download the .7z archive from the [https://github.com/DCurrent/openbor](https://github.com/DCurrent/openbor) repository. The file you want is _OpenBOR_3.0_6391.AppImage_ which is located inside the LINUX/OpenBOR folder. If you need an older engine for some specific game, then you may need to download an earlier release instead. On Linux you need to supply your own game engine binary as few (if any) games are distributed with the Linux release of OpenBOR. Download the .7z archive from the https://github.com/DCurrent/openbor repository. The file you want is _OpenBOR_3.0_6391.AppImage_ which is located inside the LINUX/OpenBOR folder. If you need an older engine for some specific game, then you may need to download an earlier release instead.
Copy this file to the game directory and make it executable using the command `chmod +x OpenBOR_3.0_6391.AppImage` Copy this file to the game directory and make it executable using the command `chmod +x OpenBOR_3.0_6391.AppImage`
@ -2271,7 +2302,7 @@ This is what the complete setup could look like:
On Android the Fake-08 core provides a good alternative to the official PICO-8 engine which is not available on this operating system. ES-DE also includes support for the Retro8 RetroArch core across all platforms, but it's borderline unusable and does not seem to be actively developed any longer. On Android the Fake-08 core provides a good alternative to the official PICO-8 engine which is not available on this operating system. ES-DE also includes support for the Retro8 RetroArch core across all platforms, but it's borderline unusable and does not seem to be actively developed any longer.
Neither Fake-08 nor Retro8 support the .png file extension, so in order to use these cores you need to remove that extension from your game files and only keep the .p8 extension, such as this: To use the .png file extension with Fake-08 and Retro8 you need to disable the built-in image viewer in RetroArch, otherwise you'll have to rename your game files to only use the .p8 extension, such as this:
``` ```
~/ROMs/pico8/c_e_l_e_s_t_e-0.p8 ~/ROMs/pico8/c_e_l_e_s_t_e-0.p8
@ -2364,7 +2395,7 @@ ScummVM overlaps a bit with DOS when it comes to the logic of setting it up. It'
Although ScummVM supports launching of .exe files, ES-DE is currently not configured as such and it's instead recommended to create a .scummvm file in each game directory and launch that. This makes for a cleaner setup as you don't need to run game configuration utilities like INSTALL.EXE or SETUP.EXE directly as you would with DOSBox. Rather the game configuration is done within the ScummVM emulator. Although ScummVM supports launching of .exe files, ES-DE is currently not configured as such and it's instead recommended to create a .scummvm file in each game directory and launch that. This makes for a cleaner setup as you don't need to run game configuration utilities like INSTALL.EXE or SETUP.EXE directly as you would with DOSBox. Rather the game configuration is done within the ScummVM emulator.
The .scummvm file must be named using the correct _Game Short Name_ and it must also contain this short name as a single string/word. You can find the complete list of supported ScummVM games with their corresponding short names here:\ The .scummvm file must be named using the correct _Game Short Name_ and it must also contain this short name as a single string/word. You can find the complete list of supported ScummVM games with their corresponding short names here:\
[https://www.scummvm.org/compatibility](https://www.scummvm.org/compatibility) https://www.scummvm.org/compatibility
An example setup could look like the following: An example setup could look like the following:
``` ```
@ -2372,7 +2403,9 @@ An example setup could look like the following:
~/ROMs/scummvm/Flight of the Amazon Queen/queen.scummvm ~/ROMs/scummvm/Flight of the Amazon Queen/queen.scummvm
``` ```
To clarify, the sky.scummvm file should contain just the single word `sky` and likewise the queen.scummvm file should only contain the word `queen`. To clarify, the sky.scummvm file should contain just the single word _sky_ and likewise the queen.scummvm file should only contain the word _queen_.
However, note that ScummVM on Android (and possibly on other operating systems as well) sometimes changes the short name inside its user interface, for example an index could be added so that instead of _sky_ it says _sky-1_ or some variation of that. In this case you need to have this exact string inside the .scummvm file instead of the default name from the compatibility list linked above.
In order to avoid having to display each game as a directory inside ES-DE (that needs to be entered each time you want to launch a game), you can optionally interpret each game directory as a file. Make sure to read the _Directories interpreted as files_ section [here](USERGUIDE-DEV.md#directories-interpreted-as-files) to understand how this functionality works, but essentially the following would be the setup required for our example: In order to avoid having to display each game as a directory inside ES-DE (that needs to be entered each time you want to launch a game), you can optionally interpret each game directory as a file. Make sure to read the _Directories interpreted as files_ section [here](USERGUIDE-DEV.md#directories-interpreted-as-files) to understand how this functionality works, but essentially the following would be the setup required for our example:
``` ```
@ -2496,15 +2529,19 @@ As the Nokia N-Gage was running Symbian it may seem like the _ngage_ and _symbia
**Android** **Android**
Unfortunately there does not seem to be a way to launch individual games from ES-DE on Android specifically, so instead the EKA2L1 user interface will open on game launch and you need to manually start your game from inside the emulator. As games need to be installed upfront in the emulator as described below it's probably a good idea to just setup dummy game files with the .symbian or .ngage file extensions inside the ES-DE ROMs directory tree. These will then appear as indvidual games inside ES-DE and you can add metadata to them, scrape them etc. For the symbian system it's possible to launch individual games directly from ES-DE, but for the ngage system this is unfortunately not possible. Instead the EKA2L1 user interface will open on game launch and you need to manually start your game from inside the emulator. For both the symbian and ngage systems all games need to be installed upfront in EKA2L1.
For N-Gage games it's a good idea to just create empty dummy files with the .ngage file extensions inside the ROMs/ngage directory. These will then appear as indvidual games inside ES-DE and you can add metadata to them, scrape them etc.
For Symbian games you can export JSON launch files from EKA2L1 that can be run directly from ES-DE. Just open EKA2L1, long press the game icon and select _Create launch file_ from the popup list. Then just select the ROMs/symbian directory and the file will be saved there and game launching from ES-DE will work as expected.
Here's an example setup: Here's an example setup:
``` ```
/storage/emulated/0/ROMs/ngage/Asphalt 2.ngage /storage/emulated/0/ROMs/ngage/Asphalt 2.ngage
/storage/emulated/0/ROMs/ngage/Bomberman.ngage /storage/emulated/0/ROMs/ngage/Bomberman.ngage
/storage/emulated/0/ROMs/ngage/CallofDuty.ngage /storage/emulated/0/ROMs/ngage/CallofDuty.ngage
/storage/emulated/0/ROMs/symbian/Animal Farm.symbian /storage/emulated/0/ROMs/symbian/Animal Farm.json
/storage/emulated/0/ROMs/symbian/AnotherWorld.symbian /storage/emulated/0/ROMs/symbian/AnotherWorld.json
``` ```
**General setup** **General setup**
@ -2851,6 +2888,11 @@ This directory can however be changed using the _Game media directory_ setting i
See the [Supported game systems](USERGUIDE-DEV.md#supported-game-systems) table at the bottom of this guide for a list of all system names. See the [Supported game systems](USERGUIDE-DEV.md#supported-game-systems) table at the bottom of this guide for a list of all system names.
An example on Android:
```
/storage/emulated/0/ES-DE/downloaded_media/c64/screenshots/
```
An example on Linux: An example on Linux:
``` ```
/home/myusername/ES-DE/downloaded_media/c64/screenshots/ /home/myusername/ES-DE/downloaded_media/c64/screenshots/
@ -2893,6 +2935,9 @@ The miximages are generated by ES-DE. Normally that takes place automatically wh
The `custom` directory is not created automatically, it's an optional folder where it's possible to place an image per game that can be viewed as the last entry in the media viewer. It's intended for things like diagrams of game controller mappings that you may want to consult before starting a game. These files have to be saved with the .jpg or .png extension and they follow the same naming logic as all other media files, as explained next. The `custom` directory is not created automatically, it's an optional folder where it's possible to place an image per game that can be viewed as the last entry in the media viewer. It's intended for things like diagrams of game controller mappings that you may want to consult before starting a game. These files have to be saved with the .jpg or .png extension and they follow the same naming logic as all other media files, as explained next.
There's a handy spreadsheet here that explains each media type:\
https://docs.google.com/spreadsheets/d/18VJAL44aNxsFOd4pVAONmdWwa7srCSzr2Z2SJEiNKnE/edit?gid=1812680930#gid=1812680930
The media files must correspond exactly to the game files. Take for example this game: The media files must correspond exactly to the game files. Take for example this game:
``` ```
@ -3205,6 +3250,14 @@ Themes could optionally be optimized for different screen aspect ratios. ES-DE s
Transition animations to play when navigating between different gamelists, between systems in the system view and between the system and gamelist views. It's up to the theme author to define what to include for this option. Technically these can be any combination of _instant_, _slide_ or _fade_ transitions. If there are no user-selectable transitions avaialable the setting will be grayed out. Transition animations to play when navigating between different gamelists, between systems in the system view and between the system and gamelist views. It's up to the theme author to define what to include for this option. Technically these can be any combination of _instant_, _slide_ or _fade_ transitions. If there are no user-selectable transitions avaialable the setting will be grayed out.
**Theme language**
If the selected theme has multilingual support then you can select between its supported languages here. This setting is primarily intended for testing purposes and for theme developers, and should as such usually be left at _automatic_ which will select the same theme language as the overall application language (see the next setting below). Note that not all themes may support all languages that the ES-DE application supports. Also note that a portion of the theme translations are contained within the base application itself and as such will not switch language unless you also change the _Application language_ setting accordingly.
**Application language**
Sets the language for the application user interface. If this option is set to _automatic_ then the language will be auto-detected, which means ES-DE will attempt to use whatever language has been selected in the operating system language settings. If there are no translations available in ES-DE for this precise language then a fallback will be done to the closest match, such as _Svenska_ instead of _Svenska (Finland)_. If no close match is available then ES-DE will revert to the default language which is _English (United States)_. It's also possible to explicitly select a supported language, which will override whatever has been set by the operating system. Note that the onboarding configurator for the Android release is unaffected by this setting. Also note that language auto-detection does not work on the Steam Deck when running in game mode, so there it's necessary to select a language explicitly. If you accidentally select a language you didn't intend to, then you can access the application language setting via the second menu entry from the top after opening the main menu, and then after entering this sub-menu by pressing the down button eight times.
**Quick system select** **Quick system select**
The buttons to use to jump between systems in the gamelist view. The options are _Left/right or shoulders_, _Left/right or triggers_, _Shoulders_, _Triggers_, _Left/right_ or _Disabled_. The first two options will apply either left/right or shoulder/trigger buttons depending on the type of primary element used for the gamelist. For example a textlist or a vertical carousel will allow the use of the left and right buttons, but for horizontal carousels and grids these buttons are reserved for navigating the entries so instead the secondary buttons will be used, i.e. the shoulder or trigger buttons. Using these two options therefore leads to a slight inconsistency as different buttons will be used depending on the theme configuration. If instead using any of the single button pair options, i.e. _Shoulders_, _Triggers_ or _Left/right_, the navigation will be consistent regardless of theme configuration but you'll sacrifice the ability to use the selected buttons if the gamelist supports it, such as the ability to jump rows in a textlist using the shoulder and trigger buttons. The buttons to use to jump between systems in the gamelist view. The options are _Left/right or shoulders_, _Left/right or triggers_, _Shoulders_, _Triggers_, _Left/right_ or _Disabled_. The first two options will apply either left/right or shoulder/trigger buttons depending on the type of primary element used for the gamelist. For example a textlist or a vertical carousel will allow the use of the left and right buttons, but for horizontal carousels and grids these buttons are reserved for navigating the entries so instead the secondary buttons will be used, i.e. the shoulder or trigger buttons. Using these two options therefore leads to a slight inconsistency as different buttons will be used depending on the theme configuration. If instead using any of the single button pair options, i.e. _Shoulders_, _Triggers_ or _Left/right_, the navigation will be consistent regardless of theme configuration but you'll sacrifice the ability to use the selected buttons if the gamelist supports it, such as the ability to jump rows in a textlist using the shoulder and trigger buttons.
@ -3223,9 +3276,9 @@ The order in which to sort your gamelists. This can be overriden per game system
**Menu color scheme** **Menu color scheme**
Provides a selection between a _Dark_ and a _Light_ color scheme. This will affect the entire menu system as well as the game launch screen. Provides a selection between the _Dark_, _Dark with red_ and _Light_ color schemes. This will affect the entire menu system as well as the game launch screen.
**Menu opening effect** **Menu opening animation**
Animation to play when opening the main menu or the gamelist options menu. Also sets the animation for the game launch screen. Can be set to _Scale-up_ or _None_. Animation to play when opening the main menu or the gamelist options menu. Also sets the animation for the game launch screen. Can be set to _Scale-up_ or _None_.
@ -3554,7 +3607,7 @@ The metadata for a game is updated by scraping or by manual editing it using the
**Check for application updates** _Not available for some builds_ **Check for application updates** _Not available for some builds_
By default a check for new ES-DE versions will be done on every application startup and a notification will be displayed if there is a new release available for download. Using this option the frequency of these checks can be set to _Always_, _Daily_, _Weekly_, _Monthly_ or _Never_. This setting is not available on some platforms and package formats such as the Linux AUR release and the semi-official BSD Unix and Raspberry Pi releases where pre-built packages are not provided. By default a check for new ES-DE versions will be done on every application startup and a notification will be displayed if there is a new release available for download. Using this option the frequency of these checks can be set to _Always_, _Daily_, _Weekly_, _Monthly_ or _Never_. This setting is not available on some platforms and package formats such as the Linux AUR release and the semi-official FreeBSD and Raspberry Pi releases where pre-built packages are not provided.
**Include prereleases in update checks** _Always enabled for prereleases_ **Include prereleases in update checks** _Always enabled for prereleases_
@ -4039,14 +4092,14 @@ The **@** symbol indicates that the emulator is _deprecated_ and will be removed
| android | Google Android | BlueStacks **(Standalone)** [W] | | No | Shortcut (.lnk) file | | android | Google Android | BlueStacks **(Standalone)** [W] | | No | Shortcut (.lnk) file |
| androidapps | Android Apps | _Placeholder_ | | | | | androidapps | Android Apps | _Placeholder_ | | | |
| androidgames | Android Games | _Placeholder_ | | | | | androidgames | Android Games | _Placeholder_ | | | |
| apple2 | Apple II | LinApple **(Standalone)** [L],<br>Mednafen **(Standalone)** [M],<br>AppleWin **(Standalone)** [W] | Mednafen **(Standalone)** [LW],<br>MAME - Current,<br>MAME **(Standalone)** | Yes for Mednafen and MAME | See the specific _Apple II_ section elsewhere in this guide | | apple2 | Apple II | LinApple **(Standalone)** [L],<br>Mednafen **(Standalone)** [M],<br>AppleWin **(Standalone)** [W] | Mednafen **(Standalone)** [LW],<br>MAME - Current,<br>MAME **(Standalone)**,<br>izapple2 **(Standalone)** [LW] | Yes for Mednafen and MAME | See the specific _Apple II_ section elsewhere in this guide |
| apple2gs | Apple IIGS | MAME - Current | MAME **(Standalone)** | Yes | See the specific _Apple IIGS_ section elsewhere in this guide | | apple2gs | Apple IIGS | MAME - Current | MAME **(Standalone)** | Yes | See the specific _Apple IIGS_ section elsewhere in this guide |
| arcade | Arcade | MAME - Current | MAME 2010,<br>MAME 2003-Plus,<br>MAME 2003,<br>MAME 2000,<br>MAME **(Standalone)**,<br>FinalBurn Neo,<br>FinalBurn Neo **(Standalone)** [LW],<br>FB Alpha 2012,<br>Geolith,<br>Flycast,<br>Flycast **(Standalone)**,<br>Flycast Dojo **(Standalone)**,<br>Kronos [LW],<br>Model 2 Emulator **(Standalone)** [W],<br>Model 2 Emulator [Suspend ES-DE] **(Standalone)** [W],<br>Supermodel **(Standalone)** [LW],<br> _Shortcut or script_ | Depends | See the specific _Arcade and Neo Geo_ section elsewhere in this guide | | arcade | Arcade | MAME - Current | MAME 2010,<br>MAME 2003-Plus,<br>MAME 2003,<br>MAME 2000,<br>MAME **(Standalone)**,<br>FinalBurn Neo,<br>FinalBurn Neo **(Standalone)** [LW],<br>FB Alpha 2012,<br>Geolith,<br>Flycast,<br>Flycast **(Standalone)**,<br>Flycast Dojo **(Standalone)**,<br>Kronos [LW],<br>Model 2 Emulator **(Standalone)** [W],<br>Model 2 Emulator [Suspend ES-DE] **(Standalone)** [W],<br>Supermodel **(Standalone)** [LW],<br> _Shortcut or script_ | Depends | See the specific _Arcade and Neo Geo_ section elsewhere in this guide |
| arcadia | Emerson Arcadia 2001 | MAME - Current | MAME **(Standalone)**,<br>WinArcadia **(Standalone)** | No | Single archive or ROM file | | arcadia | Emerson Arcadia 2001 | MAME - Current | MAME **(Standalone)**,<br>WinArcadia **(Standalone)** | No | Single archive or ROM file |
| archimedes | Acorn Archimedes | MAME [Model A440/1] **(Standalone)** | MAME [Model A3000] **(Standalone)**,<br>MAME [Model A310] **(Standalone)**,<br>MAME [Model A540] **(Standalone)** | Yes | | | archimedes | Acorn Archimedes | MAME [Model A440/1] **(Standalone)** | MAME [Model A3000] **(Standalone)**,<br>MAME [Model A310] **(Standalone)**,<br>MAME [Model A540] **(Standalone)** | Yes | |
| arduboy | Arduboy Miniature Game System | Arduous | | No | Single archive or .hex file | | arduboy | Arduboy Miniature Game System | Arduous | Ardens | No | Single archive or .hex file |
| astrocde | Bally Astrocade | MAME - Current | MAME **(Standalone)** | Yes | Single archive or ROM file | | astrocde | Bally Astrocade | MAME - Current | MAME **(Standalone)** | Yes | Single archive or ROM file |
| atari2600 | Atari 2600 | Stella | Stella 2014,<br>Stella **(Standalone)**,<br>Gopher2600 **(Standalone)** [LW],<br>ares **(Standalone)** | No | Single archive or ROM file | | atari2600 | Atari 2600 | Stella | Stella 2014,<br>Stella 2023,<br>Stella **(Standalone)**,<br>Gopher2600 **(Standalone)** [LW],<br>ares **(Standalone)** | No | Single archive or ROM file |
| atari5200 | Atari 5200 | a5200 | Atari800,<br>Atari800 **(Standalone)**,<br>Altirra **(Standalone)** [W] | Yes except for Altirra | Single archive or ROM file | | atari5200 | Atari 5200 | a5200 | Atari800,<br>Atari800 **(Standalone)**,<br>Altirra **(Standalone)** [W] | Yes except for Altirra | Single archive or ROM file |
| atari7800 | Atari 7800 ProSystem | ProSystem | MAME - Current,<br>MAME **(Standalone)** | Yes | Single archive or ROM file | | atari7800 | Atari 7800 ProSystem | ProSystem | MAME - Current,<br>MAME **(Standalone)** | Yes | Single archive or ROM file |
| atari800 | Atari 800 | Atari800 | Atari800 **(Standalone)**,<br>Altirra **(Standalone)** [W] | Yes except for Altirra | | | atari800 | Atari 800 | Atari800 | Atari800 **(Standalone)**,<br>Altirra **(Standalone)** [W] | Yes except for Altirra | |
@ -4073,30 +4126,30 @@ The **@** symbol indicates that the emulator is _deprecated_ and will be removed
| daphne | Daphne Arcade LaserDisc Emulator | Hypseus [Daphne] **(Standalone)** | Hypseus [Singe] **(Standalone)**,<br>MAME - Current,<br>MAME **(Standalone)**,<br>DirkSimple | Depends | See the specific _LaserDisc Games_ section elsewhere in this guide | | daphne | Daphne Arcade LaserDisc Emulator | Hypseus [Daphne] **(Standalone)** | Hypseus [Singe] **(Standalone)**,<br>MAME - Current,<br>MAME **(Standalone)**,<br>DirkSimple | Depends | See the specific _LaserDisc Games_ section elsewhere in this guide |
| desktop | Desktop Applications | _Suspend ES-DE_ | _Keep ES-DE running_,<br> _AppImage (Suspend ES-DE)_ [L],<br> _AppImage (Keep ES-DE running)_ [L] | No | See the specific _Ports and desktop applications_ section elsewhere in this guide | | desktop | Desktop Applications | _Suspend ES-DE_ | _Keep ES-DE running_,<br> _AppImage (Suspend ES-DE)_ [L],<br> _AppImage (Keep ES-DE running)_ [L] | No | See the specific _Ports and desktop applications_ section elsewhere in this guide |
| doom | Doom | PrBoom | PrBoom+ **(Standalone)**,<br>Boom 3 [LW],<br>Boom 3 xp [LW],<br> _Shortcut or script_ | No | | | doom | Doom | PrBoom | PrBoom+ **(Standalone)**,<br>Boom 3 [LW],<br>Boom 3 xp [LW],<br> _Shortcut or script_ | No | |
| dos | DOS (PC) | DOSBox-Pure | DOSBox-Core,<br>DOSBox-SVN,<br>DOSBox-X **(Standalone)**,<br>DOSBox Staging **(Standalone)** | No | See the specific _DOS / PC_ section elsewhere in this guide | | dos | DOS (PC) | DOSBox-Pure | DOSBox-Core,<br>DOSBox-SVN,<br>DOSBox-X **(Standalone)**,<br>DOSBox Staging **(Standalone)**,<br>VirtualXT | No | See the specific _DOS / PC_ section elsewhere in this guide |
| dragon32 | Dragon Data Dragon 32 | XRoar Dragon 32 **(Standalone)** | XRoar Dragon 64 **(Standalone)** | Yes | See the specific _Dragon 32 and Tano Dragon_ section elsewhere in this guide | | dragon32 | Dragon Data Dragon 32 | MAME Dragon 32 [Tape] **(Standalone)** | MAME Dragon 32 [Cartridge] **(Standalone)**,<br>MAME Dragon 64 [Tape] **(Standalone)**,<br>MAME Dragon 64 [Cartridge] **(Standalone)**,<br>XRoar Dragon 32 **(Standalone)**,<br>XRoar Dragon 64 **(Standalone)** | Yes | See the specific _Dragon 32 and Tano Dragon_ section elsewhere in this guide |
| dreamcast | Sega Dreamcast | Flycast | Flycast **(Standalone)**,<br>Flycast Dojo **(Standalone)**,<br>Redream **(Standalone)**,<br>Demul **(Standalone)** [W] | No | In separate folder interpreted as a file, with .m3u playlist if multi-disc game | | dreamcast | Sega Dreamcast | Flycast | Flycast **(Standalone)**,<br>Flycast Dojo **(Standalone)**,<br>Redream **(Standalone)**,<br>Demul **(Standalone)** [W] | No | In separate folder interpreted as a file, with .m3u playlist if multi-disc game |
| easyrpg | EasyRPG Game Engine | EasyRPG | EasyRPG Player **(Standalone)** | No | See the specific _EasyRPG Game Engine_ section elsewhere in this guide | | easyrpg | EasyRPG Game Engine | EasyRPG | EasyRPG Player **(Standalone)** | No | See the specific _EasyRPG Game Engine_ section elsewhere in this guide |
| electron | Acorn Electron | MAME [Tape] **(Standalone)** | MAME [Diskette DFS] **(Standalone)**,<br>MAME [Diskette ADFS] **(Standalone)** | Yes | Single archive, or single tape or diskette image file | | electron | Acorn Electron | MAME [Tape] **(Standalone)** | MAME [Diskette DFS] **(Standalone)**,<br>MAME [Diskette ADFS] **(Standalone)** | Yes | Single archive, or single tape or diskette image file |
| emulators | Emulators | _Suspend ES-DE_ | _Keep ES-DE running_,<br> _AppImage (Suspend ES-DE)_ [L],<br> _AppImage (Keep ES-DE running)_ [L] | No | See the specific _Ports and desktop applications_ section elsewhere in this guide | | emulators | Emulators | _Suspend ES-DE_ | _Keep ES-DE running_,<br> _AppImage (Suspend ES-DE)_ [L],<br> _AppImage (Keep ES-DE running)_ [L] | No | See the specific _Ports and desktop applications_ section elsewhere in this guide |
| epic | Epic Games Store | Epic Games Store **(Standalone)** | | No | Shortcut (.desktop/.app/.lnk) file | | epic | Epic Games Store | Epic Games Store **(Standalone)** | | No | Shortcut (.desktop/.app/.lnk) file |
| famicom | Nintendo Family Computer | Mesen | Mesen **(Standalone)** [LW],<br>Nestopia UE,<br>Nestopia UE **(Standalone)** [L],<br>FCEUmm,<br>QuickNES,<br>puNES **(Standalone)** [LW],<br>Mednafen **(Standalone)**,<br>ares **(Standalone)**,<br>ares FDS **(Standalone)**,<br>3dSen **(Wine)** [L],<br>3dSen **(Proton)** [L],<br>3dSen **(Standalone)** [W] | No | Single archive or ROM file. For Famicom games in 3D see the specific _Nintendo NES and Famicom in 3D_ section elsewhere in this guide | | famicom | Nintendo Family Computer | Mesen | Mesen **(Standalone)** [LW],<br>Nestopia UE,<br>Nestopia UE **(Standalone)** [L],<br>FCEUmm,<br>QuickNES,<br>puNES **(Standalone)** [LW],<br>Mednafen **(Standalone)**,<br>ares **(Standalone)**,<br>ares FDS **(Standalone)**,<br>jgenesis **(Standalone)** [LW],<br>3dSen **(Wine)** [L],<br>3dSen **(Proton)** [L],<br>3dSen **(Standalone)** [W] | No | Single archive or ROM file. For Famicom games in 3D see the specific _Nintendo NES and Famicom in 3D_ section elsewhere in this guide |
| fba | FinalBurn Alpha | FB Alpha 2012 | FB Alpha 2012 Neo Geo,<br>FB Alpha 2012 CPS-1,<br>FB Alpha 2012 CPS-2,<br>FB Alpha 2012 CPS-3 | Yes | See the specific _Arcade and Neo Geo_ section elsewhere in this guide | | fba | FinalBurn Alpha | FB Alpha 2012 | FB Alpha 2012 Neo Geo,<br>FB Alpha 2012 CPS-1,<br>FB Alpha 2012 CPS-2,<br>FB Alpha 2012 CPS-3 | Yes | See the specific _Arcade and Neo Geo_ section elsewhere in this guide |
| fbneo | FinalBurn Neo | FinalBurn Neo | FinalBurn Neo **(Standalone)** [LW] | Yes | See the specific _Arcade and Neo Geo_ section elsewhere in this guide | | fbneo | FinalBurn Neo | FinalBurn Neo | FinalBurn Neo **(Standalone)** [LW] | Yes | See the specific _Arcade and Neo Geo_ section elsewhere in this guide |
| fds | Nintendo Famicom Disk System | Mesen | Mesen **(Standalone)** [LW],<br>Nestopia UE,<br>Nestopia UE **(Standalone)** [L],<br>FCEUmm,<br>Mednafen **(Standalone)**,<br>ares **(Standalone)** | Yes | Single archive or ROM file | | fds | Nintendo Famicom Disk System | Mesen | Mesen **(Standalone)** [LW],<br>Nestopia UE,<br>Nestopia UE **(Standalone)** [L],<br>FCEUmm,<br>Mednafen **(Standalone)**,<br>ares **(Standalone)** | Yes | Single archive or ROM file |
| flash | Adobe Flash | Ruffle **(Standalone)** | Lightspark **(Standalone)** [L],<br>ArcadeFlashWeb **(Standalone)** [W] | No | Single .swf file | | flash | Adobe Flash | Ruffle **(Standalone)** | Lightspark **(Standalone)** [L],<br>ArcadeFlashWeb **(Standalone)** [W] | No | Single .swf file |
| fm7 | Fujitsu FM-7 | MAME [FM-7 Diskette] **(Standalone)** | MAME [FM-7 Tape] **(Standalone)**,<br>MAME [FM-7 Software list] **(Standalone)**,<br>MAME [FM77AV Diskette] **(Standalone)**,<br>MAME [FM77AV Tape] **(Standalone)**,<br>MAME [FM77AV Software list] **(Standalone)** | Yes | For tape files you need to manually start the cassette player from the MAME menu after the "load" command, as well as entering the "run" command after loading is complete | | fm7 | Fujitsu FM-7 | MAME [FM-7 Diskette] **(Standalone)** | MAME [FM-7 Tape] **(Standalone)**,<br>MAME [FM-7 Software list] **(Standalone)**,<br>MAME [FM77AV Diskette] **(Standalone)**,<br>MAME [FM77AV Tape] **(Standalone)**,<br>MAME [FM77AV Software list] **(Standalone)** | Yes | For tape files you need to manually start the cassette player from the MAME menu after the "load" command, as well as entering the "run" command after loading is complete |
| fmtowns | Fujitsu FM Towns | MAME - Current,<br>MAME **(Standalone)** | Tsugaru **(Standalone)** [LW] | Yes | See the specific _Fujitsu FM Towns_ section elsewhere in this guide | | fmtowns | Fujitsu FM Towns | MAME - Current | MAME **(Standalone)**,<br>Tsugaru **(Standalone)** [LW] | Yes | See the specific _Fujitsu FM Towns_ section elsewhere in this guide |
| fpinball | Future Pinball | Future Pinball **(Standalone)** [W] | | No | | | fpinball | Future Pinball | Future Pinball **(Standalone)** [W] | | No | |
| gamate | Bit Corporation Gamate | MAME - Current | MAME **(Standalone)** | Yes | Single archive or ROM file | | gamate | Bit Corporation Gamate | MAME - Current | MAME **(Standalone)** | Yes | Single archive or ROM file |
| gameandwatch | Nintendo Game and Watch | MAME - Current | MAME Local Artwork **(Standalone)**,<br>MAME **(Standalone)**,<br>Handheld Electronic (GW) | No | See the specific _LCD handheld games_ section elsewhere in this guide | | gameandwatch | Nintendo Game and Watch | MAME - Current | MAME Local Artwork **(Standalone)**,<br>MAME **(Standalone)**,<br>Handheld Electronic (GW) | No | See the specific _LCD handheld games_ section elsewhere in this guide |
| gamecom | Tiger Electronics Game.com | MAME - Current | MAME **(Standalone)** | Yes | Single archive or ROM file | | gamecom | Tiger Electronics Game.com | MAME - Current | MAME **(Standalone)** | Yes | Single archive or ROM file |
| gamegear | Sega Game Gear | Genesis Plus GX | Genesis Plus GX Wide,<br>Gearsystem,<br>SMS Plus GX,<br>PicoDrive,<br>Mednafen **(Standalone)**,<br>Mesen **(Standalone)** [LW],<br>ares **(Standalone)** | No | Single archive or ROM file | | gamegear | Sega Game Gear | Genesis Plus GX | Genesis Plus GX Wide,<br>Gearsystem,<br>SMS Plus GX,<br>PicoDrive,<br>Mednafen **(Standalone)**,<br>Mesen **(Standalone)** [LW],<br>ares **(Standalone)**,<br>jgenesis **(Standalone)** [LW] | No | Single archive or ROM file |
| gb | Nintendo Game Boy | Gambatte | SameBoy,<br>SameBoy **(Standalone)**,<br>Gearboy,<br>Gearboy **(Standalone)** [LW],<br>TGB Dual,<br>DoubleCherryGB [LW],<br>Mesen-S,<br>Mesen **(Standalone)** [LW],<br>bsnes,<br>mGBA,<br>mGBA **(Standalone)**,<br>VBA-M,<br>VBA-M **(Standalone)**,<br>Mednafen **(Standalone)**,<br>ares **(Standalone)**,<br>SkyEmu **(Standalone)** | No | Single archive or ROM file | | gb | Nintendo Game Boy | Gambatte | SameBoy,<br>SameBoy **(Standalone)**,<br>Gearboy,<br>Gearboy **(Standalone)** [LW],<br>TGB Dual,<br>DoubleCherryGB [LW],<br>Mesen-S,<br>Mesen **(Standalone)** [LW],<br>bsnes,<br>mGBA,<br>mGBA **(Standalone)**,<br>VBA-M,<br>VBA-M **(Standalone)**,<br>Mednafen **(Standalone)**,<br>ares **(Standalone)**,<br>SkyEmu **(Standalone)**,<br>jgenesis **(Standalone)** [LW] | No | Single archive or ROM file |
| gba | Nintendo Game Boy Advance | mGBA | mGBA **(Standalone)**,<br>VBA-M,<br>VBA-M **(Standalone)**,<br>VBA Next,<br>gpSP,<br>Mednafen **(Standalone)**,<br>ares **(Standalone)**,<br>SkyEmu **(Standalone)** | Yes for ares | Single archive or ROM file | | gba | Nintendo Game Boy Advance | mGBA | mGBA **(Standalone)**,<br>VBA-M,<br>VBA-M **(Standalone)**,<br>VBA Next,<br>gpSP,<br>Mednafen **(Standalone)**,<br>ares **(Standalone)**,<br>SkyEmu **(Standalone)**,<br>NooDS **(Standalone)** [LW] | Yes for ares | Single archive or ROM file |
| gbc | Nintendo Game Boy Color | Gambatte | SameBoy,<br>SameBoy **(Standalone)**,<br>Gearboy,<br>Gearboy **(Standalone)** [LW],<br>TGB Dual,<br>DoubleCherryGB [LW],<br>Mesen-S,<br>Mesen **(Standalone)** [LW],<br>bsnes,<br>mGBA,<br>mGBA **(Standalone)**,<br>VBA-M,<br>VBA-M **(Standalone)**,<br>Mednafen **(Standalone)**,<br>ares **(Standalone)**,<br>SkyEmu **(Standalone)** | No | Single archive or ROM file | | gbc | Nintendo Game Boy Color | Gambatte | SameBoy,<br>SameBoy **(Standalone)**,<br>Gearboy,<br>Gearboy **(Standalone)** [LW],<br>TGB Dual,<br>DoubleCherryGB [LW],<br>Mesen-S,<br>Mesen **(Standalone)** [LW],<br>bsnes,<br>mGBA,<br>mGBA **(Standalone)**,<br>VBA-M,<br>VBA-M **(Standalone)**,<br>Mednafen **(Standalone)**,<br>ares **(Standalone)**,<br>SkyEmu **(Standalone)**,<br>jgenesis **(Standalone)** [LW] | No | Single archive or ROM file |
| gc | Nintendo GameCube | Dolphin | Dolphin **(Standalone)**,<br>PrimeHack **(Standalone)** [LW],<br>Triforce **(Standalone)** [LW] | No | Disc image file for single-disc games, .m3u playlist for multi-disc games | | gc | Nintendo GameCube | Dolphin | Dolphin **(Standalone)**,<br>PrimeHack **(Standalone)** [LW],<br>Triforce **(Standalone)** [LW] | No | Disc image file for single-disc games, .m3u playlist for multi-disc games |
| genesis | Sega Genesis | Genesis Plus GX | Genesis Plus GX Wide,<br>PicoDrive,<br>BlastEm,<br>BlastEm **(Standalone)** [L],<br>Mednafen **(Standalone)**,<br>ares **(Standalone)** | No | Single archive or ROM file | | genesis | Sega Genesis | Genesis Plus GX | Genesis Plus GX Wide,<br>PicoDrive,<br>BlastEm,<br>BlastEm **(Standalone)** [L],<br>Mednafen **(Standalone)**,<br>ares **(Standalone)**,<br>jgenesis **(Standalone)** [LW] | No | Single archive or ROM file |
| gmaster | Hartung Game Master | MAME - Current | MAME **(Standalone)** | Yes | Single archive or ROM file | | gmaster | Hartung Game Master | MAME - Current | MAME **(Standalone)** | Yes | Single archive or ROM file |
| gx4000 | Amstrad GX4000 | Caprice32 | CrocoDS,<br>MAME **(Standalone)** | No | Single archive or ROM file | | gx4000 | Amstrad GX4000 | Caprice32 | CrocoDS,<br>MAME **(Standalone)** | No | Single archive or ROM file |
| intellivision | Mattel Electronics Intellivision | FreeIntv | MAME - Current,<br>MAME **(Standalone)** | Yes | Single archive or ROM file | | intellivision | Mattel Electronics Intellivision | FreeIntv | MAME - Current,<br>MAME **(Standalone)** | Yes | Single archive or ROM file |
@ -4110,11 +4163,11 @@ The **@** symbol indicates that the emulator is _deprecated_ and will be removed
| macintosh | Apple Macintosh | MAME Mac SE Bootable **(Standalone)** | MAME Mac SE Boot Disk **(Standalone)**,<br>MAME Mac Plus Bootable **(Standalone)**,<br>MAME Mac Plus Boot Disk **(Standalone)**,<br>Basilisk II **(Standalone)**,<br>SheepShaver **(Standalone)** | Yes | See the specific _Apple Macintosh_ section elsewhere in this guide | | macintosh | Apple Macintosh | MAME Mac SE Bootable **(Standalone)** | MAME Mac SE Boot Disk **(Standalone)**,<br>MAME Mac Plus Bootable **(Standalone)**,<br>MAME Mac Plus Boot Disk **(Standalone)**,<br>Basilisk II **(Standalone)**,<br>SheepShaver **(Standalone)** | Yes | See the specific _Apple Macintosh_ section elsewhere in this guide |
| mame | Multiple Arcade Machine Emulator | MAME - Current | MAME 2010,<br>MAME 2003-Plus,<br>MAME 2003,<br>MAME 2000,<br>MAME **(Standalone)**,<br>FinalBurn Neo,<br>FinalBurn Neo **(Standalone)** [LW],<br>FB Alpha 2012,<br>Geolith,<br>Flycast,<br>Flycast **(Standalone)**,<br>Flycast Dojo **(Standalone)**,<br>Kronos [LW],<br>Model 2 Emulator **(Standalone)** [W],<br>Model 2 Emulator [Suspend ES-DE] **(Standalone)** [W],<br>Supermodel **(Standalone)** [LW],<br> _Shortcut or script_ | Depends | See the specific _Arcade and Neo Geo_ section elsewhere in this guide | | mame | Multiple Arcade Machine Emulator | MAME - Current | MAME 2010,<br>MAME 2003-Plus,<br>MAME 2003,<br>MAME 2000,<br>MAME **(Standalone)**,<br>FinalBurn Neo,<br>FinalBurn Neo **(Standalone)** [LW],<br>FB Alpha 2012,<br>Geolith,<br>Flycast,<br>Flycast **(Standalone)**,<br>Flycast Dojo **(Standalone)**,<br>Kronos [LW],<br>Model 2 Emulator **(Standalone)** [W],<br>Model 2 Emulator [Suspend ES-DE] **(Standalone)** [W],<br>Supermodel **(Standalone)** [LW],<br> _Shortcut or script_ | Depends | See the specific _Arcade and Neo Geo_ section elsewhere in this guide |
| mame-advmame | AdvanceMAME | AdvanceMAME **(Standalone)** [LW] | | Depends | See the specific _Arcade and Neo Geo_ section elsewhere in this guide | | mame-advmame | AdvanceMAME | AdvanceMAME **(Standalone)** [LW] | | Depends | See the specific _Arcade and Neo Geo_ section elsewhere in this guide |
| mastersystem | Sega Master System | Genesis Plus GX | Genesis Plus GX Wide,<br>SMS Plus GX,<br>Gearsystem,<br>PicoDrive,<br>Mednafen **(Standalone)**,<br>Mesen **(Standalone)** [LW],<br>ares **(Standalone)** | No | Single archive or ROM file | | mastersystem | Sega Master System | Genesis Plus GX | Genesis Plus GX Wide,<br>SMS Plus GX,<br>Gearsystem,<br>PicoDrive,<br>Mednafen **(Standalone)**,<br>Mesen **(Standalone)** [LW],<br>ares **(Standalone)**,<br>jgenesis **(Standalone)** [LW] | No | Single archive or ROM file |
| megacd | Sega Mega-CD | Genesis Plus GX | Genesis Plus GX Wide,<br>PicoDrive,<br>ares **(Standalone)** | Yes | | | megacd | Sega Mega-CD | Genesis Plus GX | Genesis Plus GX Wide,<br>PicoDrive,<br>ares **(Standalone)**,<br>jgenesis **(Standalone)** [LW] | Yes | |
| megacdjp | Sega Mega-CD [Japan] | Genesis Plus GX | Genesis Plus GX Wide,<br>PicoDrive,<br>ares **(Standalone)** | Yes | | | megacdjp | Sega Mega-CD [Japan] | Genesis Plus GX | Genesis Plus GX Wide,<br>PicoDrive,<br>ares **(Standalone)**,<br>jgenesis **(Standalone)** [LW] | Yes | |
| megadrive | Sega Mega Drive | Genesis Plus GX | Genesis Plus GX Wide,<br>PicoDrive,<br>BlastEm,<br>BlastEm **(Standalone)** [L],<br>Mednafen **(Standalone)**,<br>ares **(Standalone)** | No | Single archive or ROM file | | megadrive | Sega Mega Drive | Genesis Plus GX | Genesis Plus GX Wide,<br>PicoDrive,<br>BlastEm,<br>BlastEm **(Standalone)** [L],<br>Mednafen **(Standalone)**,<br>ares **(Standalone)**,<br>jgenesis **(Standalone)** [LW] | No | Single archive or ROM file |
| megadrivejp | Sega Mega Drive [Japan] | Genesis Plus GX | Genesis Plus GX Wide,<br>PicoDrive,<br>BlastEm,<br>BlastEm **(Standalone)** [L],<br>Mednafen **(Standalone)**,<br>ares **(Standalone)** | No | Single archive or ROM file | | megadrivejp | Sega Mega Drive [Japan] | Genesis Plus GX | Genesis Plus GX Wide,<br>PicoDrive,<br>BlastEm,<br>BlastEm **(Standalone)** [L],<br>Mednafen **(Standalone)**,<br>ares **(Standalone)**,<br>jgenesis **(Standalone)** [LW] | No | Single archive or ROM file |
| megaduck | Creatronic Mega Duck | SameDuck | MAME - Current,<br>MAME **(Standalone)** | No | Single archive or ROM file | | megaduck | Creatronic Mega Duck | SameDuck | MAME - Current,<br>MAME **(Standalone)** | No | Single archive or ROM file |
| mess | Multi Emulator Super System | MESS 2015 | | | | | mess | Multi Emulator Super System | MESS 2015 | | | |
| model2 | Sega Model 2 | Model 2 Emulator **(Standalone)** [W],<br>MAME - Current [LM] | Model 2 Emulator [Suspend ES-DE] **(Standalone)** [W],<br>MAME - Current [W],<br>MAME **(Standalone)**,<br>Model 2 Emulator **(Wine)** [L],<br>Model 2 Emulator **(Proton)** [L] | Yes for MAME | See the specific _Arcade and Neo Geo_ section elsewhere in this guide | | model2 | Sega Model 2 | Model 2 Emulator **(Standalone)** [W],<br>MAME - Current [LM] | Model 2 Emulator [Suspend ES-DE] **(Standalone)** [W],<br>MAME - Current [W],<br>MAME **(Standalone)**,<br>Model 2 Emulator **(Wine)** [L],<br>Model 2 Emulator **(Proton)** [L] | Yes for MAME | See the specific _Arcade and Neo Geo_ section elsewhere in this guide |
@ -4126,17 +4179,17 @@ The **@** symbol indicates that the emulator is _deprecated_ and will be removed
| msxturbor | MSX Turbo R | blueMSX | openMSX **(Standalone)**,<br>openMSX No Machine **(Standalone)** | Yes | | | msxturbor | MSX Turbo R | blueMSX | openMSX **(Standalone)**,<br>openMSX No Machine **(Standalone)** | Yes | |
| mugen | M.U.G.E.N Game Engine | Ikemen GO **(Standalone)** | | No | See the specific _M.U.G.E.N Game Engine_ section elsewhere in this guide | | mugen | M.U.G.E.N Game Engine | Ikemen GO **(Standalone)** | | No | See the specific _M.U.G.E.N Game Engine_ section elsewhere in this guide |
| multivision | Othello Multivision | Gearsystem | Mesen **(Standalone)** [LW] | No | Single archive or ROM file | | multivision | Othello Multivision | Gearsystem | Mesen **(Standalone)** [LW] | No | Single archive or ROM file |
| naomi | Sega NAOMI | Flycast | Flycast **(Standalone)**,<br>Flycast Dojo **(Standalone)**,<br>Demul **(Standalone)** [W] | Yes | Single archive file + .chd file in subdirectory if GD-ROM game |
| naomi2 | Sega NAOMI 2 | Flycast | Flycast **(Standalone)**,<br>Flycast Dojo **(Standalone)**,<br>Demul **(Standalone)** [W] | Yes | Single archive file + .chd file in subdirectory if GD-ROM game |
| naomigd | Sega NAOMI GD-ROM | Flycast | Flycast **(Standalone)**,<br>Flycast Dojo **(Standalone)** | Yes | Single archive file + .chd file in subdirectory if GD-ROM game |
| n3ds | Nintendo 3DS | Citra [LW],<br>Citra **(Standalone)** [M] | Citra 2018 [LW],<br>Citra **(Standalone)** [LW],<br>Lime3DS **(Standalone)**,<br>Panda3DS **(Standalone)** | No | Single ROM file | | n3ds | Nintendo 3DS | Citra [LW],<br>Citra **(Standalone)** [M] | Citra 2018 [LW],<br>Citra **(Standalone)** [LW],<br>Lime3DS **(Standalone)**,<br>Panda3DS **(Standalone)** | No | Single ROM file |
| n64 | Nintendo 64 | Mupen64Plus-Next | Mupen64Plus **(Standalone)**,<br>ParaLLEl N64,<br>simple64 **(Standalone)** [LW],<br>Rosalie's Mupen GUI **(Standalone)** [LW],<br>Project64 **(Standalone)** [W],<br>ares **(Standalone)**,<br>sixtyforce **(Standalone)** [M] | No | Single archive or ROM file | | n64 | Nintendo 64 | Mupen64Plus-Next | Mupen64Plus **(Standalone)**,<br>ParaLLEl N64,<br>simple64 **(Standalone)** [LW],<br>Rosalie's Mupen GUI **(Standalone)** [LW],<br>Project64 **(Standalone)** [W],<br>ares **(Standalone)**,<br>sixtyforce **(Standalone)** [M] | No | Single archive or ROM file |
| n64dd | Nintendo 64DD | ParaLLEl N64 [LW],<br>Mupen64Plus-Next [M] | Mupen64Plus-Next [LW],<br>ParaLLEl N64 [M],<br>Rosalie's Mupen GUI **(Standalone)** [LW],<br>ares **(Standalone)** | Yes | See the specific _Nintendo 64DD_ section elsewhere in this guide | | n64dd | Nintendo 64DD | ParaLLEl N64 [LW],<br>Mupen64Plus-Next [M] | Mupen64Plus-Next [LW],<br>ParaLLEl N64 [M],<br>Rosalie's Mupen GUI **(Standalone)** [LW],<br>ares **(Standalone)** | Yes | See the specific _Nintendo 64DD_ section elsewhere in this guide |
| nds | Nintendo DS | melonDS DS | melonDS @,<br>melonDS **(Standalone)**,<br>DeSmuME,<br>DeSmuME 2015,<br>DeSmuME **(Standalone)** [L],<br>SkyEmu **(Standalone)** | No | Single archive or ROM file | | naomi | Sega NAOMI | Flycast | Flycast **(Standalone)**,<br>Flycast Dojo **(Standalone)**,<br>Demul **(Standalone)** [W] | Yes | Single archive file + .chd file in subdirectory if GD-ROM game |
| naomi2 | Sega NAOMI 2 | Flycast | Flycast **(Standalone)**,<br>Flycast Dojo **(Standalone)**,<br>Demul **(Standalone)** [W] | Yes | Single archive file + .chd file in subdirectory if GD-ROM game |
| naomigd | Sega NAOMI GD-ROM | Flycast | Flycast **(Standalone)**,<br>Flycast Dojo **(Standalone)** | Yes | Single archive file + .chd file in subdirectory if GD-ROM game |
| nds | Nintendo DS | melonDS DS | melonDS @,<br>melonDS **(Standalone)**,<br>DeSmuME,<br>DeSmuME 2015,<br>DeSmuME **(Standalone)** [L],<br>SkyEmu **(Standalone)**,<br>NooDS **(Standalone)** [LW] | No | Single archive or ROM file |
| neogeo | SNK Neo Geo | FinalBurn Neo | FinalBurn Neo **(Standalone)** [LW],<br>Geolith,<br>MAME **(Standalone)** | Yes | See the specific _Arcade and Neo Geo_ section elsewhere in this guide | | neogeo | SNK Neo Geo | FinalBurn Neo | FinalBurn Neo **(Standalone)** [LW],<br>Geolith,<br>MAME **(Standalone)** | Yes | See the specific _Arcade and Neo Geo_ section elsewhere in this guide |
| neogeocd | SNK Neo Geo CD | NeoCD | FinalBurn Neo,<br>FinalBurn Neo **(Standalone)** [L],<br>MAME **(Standalone)** | Yes | .chd (NeoCD and MAME only) or .cue file | | neogeocd | SNK Neo Geo CD | NeoCD | FinalBurn Neo,<br>FinalBurn Neo **(Standalone)** [L],<br>MAME **(Standalone)** | Yes | .chd (NeoCD and MAME only) or .cue file |
| neogeocdjp | SNK Neo Geo CD [Japan] | NeoCD | FinalBurn Neo,<br>FinalBurn Neo **(Standalone)** [L],<br>MAME **(Standalone)** | Yes | .chd (NeoCD and MAME only) or .cue file | | neogeocdjp | SNK Neo Geo CD [Japan] | NeoCD | FinalBurn Neo,<br>FinalBurn Neo **(Standalone)** [L],<br>MAME **(Standalone)** | Yes | .chd (NeoCD and MAME only) or .cue file |
| nes | Nintendo Entertainment System | Mesen | Mesen **(Standalone)** [LW],<br>Nestopia UE,<br>Nestopia UE **(Standalone)** [L],<br>FCEUmm,<br>QuickNES,<br>puNES **(Standalone)** [LW],<br>Mednafen **(Standalone)**,<br>ares **(Standalone)**,<br>ares FDS **(Standalone)**,<br>3dSen **(Wine)** [L],<br>3dSen **(Proton)** [L],<br>3dSen **(Standalone)** [W] | No | Single archive or ROM file. For NES games in 3D see the specific _Nintendo NES and Famicom in 3D_ section elsewhere in this guide | | nes | Nintendo Entertainment System | Mesen | Mesen **(Standalone)** [LW],<br>Nestopia UE,<br>Nestopia UE **(Standalone)** [L],<br>FCEUmm,<br>QuickNES,<br>puNES **(Standalone)** [LW],<br>Mednafen **(Standalone)**,<br>ares **(Standalone)**,<br>ares FDS **(Standalone)**,<br>jgenesis **(Standalone)** [LW],<br>3dSen **(Wine)** [L],<br>3dSen **(Proton)** [L],<br>3dSen **(Standalone)** [W] | No | Single archive or ROM file. For NES games in 3D see the specific _Nintendo NES and Famicom in 3D_ section elsewhere in this guide |
| ngage | Nokia N-Gage | EKA2L1 [Mounted] **(Standalone)** | EKA2L1 [Installed] **(Standalone)**,<br>EKA2L1 [Mounted] **(Wine)** [L],<br>EKA2L1 [Installed] **(Wine)** [L] | Yes | See the specific _Symbian and Nokia N-Gage_ section elsewhere in this guide | | ngage | Nokia N-Gage | EKA2L1 [Mounted] **(Standalone)** | EKA2L1 [Installed] **(Standalone)**,<br>EKA2L1 [Mounted] **(Wine)** [L],<br>EKA2L1 [Installed] **(Wine)** [L] | Yes | See the specific _Symbian and Nokia N-Gage_ section elsewhere in this guide |
| ngp | SNK Neo Geo Pocket | Beetle NeoPop | RACE,<br>Mednafen **(Standalone)**,<br>ares **(Standalone)** | No | Single archive or ROM file | | ngp | SNK Neo Geo Pocket | Beetle NeoPop | RACE,<br>Mednafen **(Standalone)**,<br>ares **(Standalone)** | No | Single archive or ROM file |
| ngpc | SNK Neo Geo Pocket Color | Beetle NeoPop | RACE,<br>Mednafen **(Standalone)**,<br>ares **(Standalone)** | No | Single archive or ROM file | | ngpc | SNK Neo Geo Pocket Color | Beetle NeoPop | RACE,<br>Mednafen **(Standalone)**,<br>ares **(Standalone)** | No | Single archive or ROM file |
@ -4144,7 +4197,7 @@ The **@** symbol indicates that the emulator is _deprecated_ and will be removed
| openbor | OpenBOR Game Engine | OpenBOR **(Standalone)** [LW] | | No | See the specific _OpenBOR_ section elsewhere in this guide | | openbor | OpenBOR Game Engine | OpenBOR **(Standalone)** [LW] | | No | See the specific _OpenBOR_ section elsewhere in this guide |
| oric | Tangerine Computer Systems Oric | MAME **(Standalone)** | Oricutron **(Standalone)** | Yes | See the specific _Tangerine Computer Systems Oric_ section elsewhere in this guide | | oric | Tangerine Computer Systems Oric | MAME **(Standalone)** | Oricutron **(Standalone)** | Yes | See the specific _Tangerine Computer Systems Oric_ section elsewhere in this guide |
| palm | Palm OS | Mu | | | | | palm | Palm OS | Mu | | | |
| pc | IBM PC | DOSBox-Pure | DOSBox-Core,<br>DOSBox-SVN,<br>DOSBox-X **(Standalone)**,<br>DOSBox Staging **(Standalone)** | No | See the specific _DOS / PC_ section elsewhere in this guide | | pc | IBM PC | DOSBox-Pure | DOSBox-Core,<br>DOSBox-SVN,<br>DOSBox-X **(Standalone)**,<br>DOSBox Staging **(Standalone)**,<br>VirtualXT | No | See the specific _DOS / PC_ section elsewhere in this guide |
| pc88 | NEC PC-8800 Series | QUASI88 | QUASI88 **(Standalone)** | Yes | | | pc88 | NEC PC-8800 Series | QUASI88 | QUASI88 **(Standalone)** | Yes | |
| pc98 | NEC PC-9800 Series | Neko Project II Kai | Neko Project II | | | | pc98 | NEC PC-9800 Series | Neko Project II Kai | Neko Project II | | |
| pcarcade | PC Arcade Systems | Wine **(Standalone)** [L],<br> _Shortcut or script_ [MW] | Proton **(Standalone)** [L],<br> _AppImage_ [L],<br> _Shortcut or script_ [L] | No | | | pcarcade | PC Arcade Systems | Wine **(Standalone)** [L],<br> _Shortcut or script_ [MW] | Proton **(Standalone)** [L],<br> _AppImage_ [L],<br> _Shortcut or script_ [L] | No | |
@ -4172,12 +4225,12 @@ The **@** symbol indicates that the emulator is _deprecated_ and will be removed
| sega32x | Sega Mega Drive 32X | PicoDrive | ares **(Standalone)** | No | Single archive or ROM file | | sega32x | Sega Mega Drive 32X | PicoDrive | ares **(Standalone)** | No | Single archive or ROM file |
| sega32xjp | Sega Super 32X [Japan] | PicoDrive | ares **(Standalone)** | No | Single archive or ROM file | | sega32xjp | Sega Super 32X [Japan] | PicoDrive | ares **(Standalone)** | No | Single archive or ROM file |
| sega32xna | Sega Genesis 32X [North America] | PicoDrive | ares **(Standalone)** | No | Single archive or ROM file | | sega32xna | Sega Genesis 32X [North America] | PicoDrive | ares **(Standalone)** | No | Single archive or ROM file |
| segacd | Sega CD | Genesis Plus GX | Genesis Plus GX Wide,<br>PicoDrive,<br>ares **(Standalone)** | Yes | | | segacd | Sega CD | Genesis Plus GX | Genesis Plus GX Wide,<br>PicoDrive,<br>ares **(Standalone)**,<br>jgenesis **(Standalone)** [LW] | Yes | |
| sfc | Nintendo SFC (Super Famicom) | Snes9x - Current | Snes9x 2010,<br>Snes9x 2005 Plus,<br>Snes9x **(Standalone)**,<br>bsnes,<br>bsnes-hd,<br>bsnes-mercury Accuracy,<br>bsnes **(Standalone)** [LW],<br>Beetle Supafaust [LW],<br>Mesen-S,<br>Mesen **(Standalone)** [LW],<br>Mednafen **(Standalone)**,<br>ares **(Standalone)** | No | Single archive or ROM file | | sfc | Nintendo SFC (Super Famicom) | Snes9x - Current | Snes9x 2010,<br>Snes9x 2005 Plus,<br>Snes9x **(Standalone)**,<br>bsnes,<br>bsnes-hd,<br>bsnes-mercury Accuracy,<br>bsnes **(Standalone)** [LW],<br>Beetle Supafaust [LW],<br>Mesen-S,<br>Mesen **(Standalone)** [LW],<br>Mednafen **(Standalone)**,<br>ares **(Standalone)**,<br>jgenesis **(Standalone)** [LW] | No | Single archive or ROM file |
| sg-1000 | Sega SG-1000 | Genesis Plus GX | Genesis Plus GX Wide,<br>Gearsystem,<br>blueMSX,<br>Mesen **(Standalone)** [LW],<br>ares **(Standalone)** | No | Single archive or ROM file | | sg-1000 | Sega SG-1000 | Genesis Plus GX | Genesis Plus GX Wide,<br>Gearsystem,<br>blueMSX,<br>Mesen **(Standalone)** [LW],<br>ares **(Standalone)** | No | Single archive or ROM file |
| sgb | Nintendo Super Game Boy | Mesen-S | Mesen **(Standalone)** [LW],<br>SameBoy,<br>mGBA,<br>mGBA **(Standalone)** | | Single archive or ROM file | | sgb | Nintendo Super Game Boy | Mesen-S | Mesen **(Standalone)** [LW],<br>SameBoy,<br>mGBA,<br>mGBA **(Standalone)** | | Single archive or ROM file |
| snes | Nintendo SNES (Super Nintendo) | Snes9x - Current | Snes9x 2010,<br>Snes9x 2005 Plus,<br>Snes9x **(Standalone)**,<br>bsnes,<br>bsnes-hd,<br>bsnes-mercury Accuracy,<br>bsnes **(Standalone)** [LW],<br>Beetle Supafaust [LW],<br>Mesen-S,<br>Mesen **(Standalone)** [LW],<br>Mednafen **(Standalone)**,<br>ares **(Standalone)** | No | Single archive or ROM file | | snes | Nintendo SNES (Super Nintendo) | Snes9x - Current | Snes9x 2010,<br>Snes9x 2005 Plus,<br>Snes9x **(Standalone)**,<br>bsnes,<br>bsnes-hd,<br>bsnes-mercury Accuracy,<br>bsnes **(Standalone)** [LW],<br>Beetle Supafaust [LW],<br>Mesen-S,<br>Mesen **(Standalone)** [LW],<br>Mednafen **(Standalone)**,<br>ares **(Standalone)**,<br>jgenesis **(Standalone)** [LW] | No | Single archive or ROM file |
| snesna | Nintendo SNES (Super Nintendo) [North America] | Snes9x - Current | Snes9x 2010,<br>Snes9x 2005 Plus,<br>Snes9x **(Standalone)**,<br>bsnes,<br>bsnes-hd,<br>bsnes-mercury Accuracy,<br>bsnes **(Standalone)** [LW],<br>Beetle Supafaust [LW],<br>Mesen-S,<br>Mesen **(Standalone)** [LW],<br>Mednafen **(Standalone)**,<br>ares **(Standalone)** | No | Single archive or ROM file | | snesna | Nintendo SNES (Super Nintendo) [North America] | Snes9x - Current | Snes9x 2010,<br>Snes9x 2005 Plus,<br>Snes9x **(Standalone)**,<br>bsnes,<br>bsnes-hd,<br>bsnes-mercury Accuracy,<br>bsnes **(Standalone)** [LW],<br>Beetle Supafaust [LW],<br>Mesen-S,<br>Mesen **(Standalone)** [LW],<br>Mednafen **(Standalone)**,<br>ares **(Standalone)**,<br>jgenesis **(Standalone)** [LW] | No | Single archive or ROM file |
| solarus | Solarus Game Engine | Solarus **(Standalone)** | | No | Single .solarus game file | | solarus | Solarus Game Engine | Solarus **(Standalone)** | | No | Single .solarus game file |
| spectravideo | Spectravideo | blueMSX | | | | | spectravideo | Spectravideo | blueMSX | | | |
| steam | Valve Steam | Steam **(Standalone)** | | No | See the specific _Steam_ section elsewhere in this guide | | steam | Valve Steam | Steam **(Standalone)** | | No | See the specific _Steam_ section elsewhere in this guide |
@ -4186,9 +4239,9 @@ The **@** symbol indicates that the emulator is _deprecated_ and will be removed
| supergrafx | NEC SuperGrafx | Beetle SuperGrafx | Beetle PCE,<br>Mednafen **(Standalone)**,<br>Mesen **(Standalone)** [LW],<br>ares **(Standalone)** | No | Single archive or ROM file | | supergrafx | NEC SuperGrafx | Beetle SuperGrafx | Beetle PCE,<br>Mednafen **(Standalone)**,<br>Mesen **(Standalone)** [LW],<br>ares **(Standalone)** | No | Single archive or ROM file |
| supervision | Watara Supervision | Potator | MAME - Current,<br>MAME **(Standalone)** | No | Single archive or ROM file | | supervision | Watara Supervision | Potator | MAME - Current,<br>MAME **(Standalone)** | No | Single archive or ROM file |
| supracan | Funtech Super A'Can | MAME - Current | MAME **(Standalone)** | Yes | Single archive or ROM file. You need a supracan.zip archive that contains a valid internal_68k.bin file and an empty file named umc6650.bin | | supracan | Funtech Super A'Can | MAME - Current | MAME **(Standalone)** | Yes | Single archive or ROM file. You need a supracan.zip archive that contains a valid internal_68k.bin file and an empty file named umc6650.bin |
| switch | Nintendo Switch | Ryujinx **(Standalone)** | | Yes | | | switch | Nintendo Switch | Ryujinx **(Standalone)** | _Shortcut_ [W] | Yes | |
| symbian | Symbian | EKA2L1 [Nokia N-Gage] **(Standalone)** | EKA2L1 [Nokia N70] **(Standalone)**,<br>EKA2L1 [Nokia N97] **(Standalone)**,<br>EKA2L1 [Custom device] **(Standalone)** | Yes | See the specific _Symbian and Nokia N-Gage_ section elsewhere in this guide | | symbian | Symbian | EKA2L1 [Nokia N-Gage] **(Standalone)** | EKA2L1 [Nokia N70] **(Standalone)**,<br>EKA2L1 [Nokia N97] **(Standalone)**,<br>EKA2L1 [Custom device] **(Standalone)** | Yes | See the specific _Symbian and Nokia N-Gage_ section elsewhere in this guide |
| tanodragon | Tano Dragon | XRoar **(Standalone)** | | Yes | See the specific _Dragon 32 and Tano Dragon_ section elsewhere in this guide | | tanodragon | Tano Dragon | MAME [Tape] **(Standalone)** | MAME [Cartridge] **(Standalone)**,<br>XRoar **(Standalone)** | Yes | See the specific _Dragon 32 and Tano Dragon_ section elsewhere in this guide |
| tg16 | NEC TurboGrafx-16 | Beetle PCE | Beetle PCE FAST,<br>Beetle SuperGrafx,<br>Mednafen **(Standalone)**,<br>Mesen **(Standalone)** [LW],<br>ares **(Standalone)** | No | Single archive or ROM file | | tg16 | NEC TurboGrafx-16 | Beetle PCE | Beetle PCE FAST,<br>Beetle SuperGrafx,<br>Mednafen **(Standalone)**,<br>Mesen **(Standalone)** [LW],<br>ares **(Standalone)** | No | Single archive or ROM file |
| tg-cd | NEC TurboGrafx-CD | Beetle PCE | Beetle PCE FAST,<br>Beetle SuperGrafx,<br>Mednafen **(Standalone)**,<br>Mesen **(Standalone)** [LW],<br>ares **(Standalone)** | Yes | | | tg-cd | NEC TurboGrafx-CD | Beetle PCE | Beetle PCE FAST,<br>Beetle SuperGrafx,<br>Mednafen **(Standalone)**,<br>Mesen **(Standalone)** [LW],<br>ares **(Standalone)** | Yes | |
| ti99 | Texas Instruments TI-99 | MAME **(Standalone)** | | Yes | See the specific _Texas Instruments TI-99_ section elsewhere in this guide | | ti99 | Texas Instruments TI-99 | MAME **(Standalone)** | | Yes | See the specific _Texas Instruments TI-99_ section elsewhere in this guide |
@ -4200,7 +4253,7 @@ The **@** symbol indicates that the emulator is _deprecated_ and will be removed
| uzebox | Uzebox Open Source Console | Uzem | | | | | uzebox | Uzebox Open Source Console | Uzem | | | |
| vectrex | GCE Vectrex | vecx | MAME - Current,<br>MAME **(Standalone)** | Yes for MAME | Single archive or ROM file | | vectrex | GCE Vectrex | vecx | MAME - Current,<br>MAME **(Standalone)** | Yes for MAME | Single archive or ROM file |
| vic20 | Commodore VIC-20 | VICE xvic | VICE xvic **(Standalone)** | No | Single archive or tape, cartridge or diskette image file | | vic20 | Commodore VIC-20 | VICE xvic | VICE xvic **(Standalone)** | No | Single archive or tape, cartridge or diskette image file |
| videopac | Philips Videopac G7000 | O2EM | MAME - Current,<br>MAME **(Standalone)** | Yes | Single archive or ROM file | | | videopac | Philips Videopac G7000 | O2EM | MAME - Current,<br>MAME **(Standalone)** | Yes | Single archive or ROM file |
| virtualboy | Nintendo Virtual Boy | Beetle VB | Mednafen **(Standalone)** | No | | | virtualboy | Nintendo Virtual Boy | Beetle VB | Mednafen **(Standalone)** | No | |
| vpinball | Visual Pinball | Visual Pinball **(Standalone)** | | No | See the specific _Visual Pinball_ section elsewhere in this guide | | vpinball | Visual Pinball | Visual Pinball **(Standalone)** | | No | See the specific _Visual Pinball_ section elsewhere in this guide |
| vsmile | VTech V.Smile | MAME - Current | MAME **(Standalone)** | Yes | Single archive or ROM file | | vsmile | VTech V.Smile | MAME - Current | MAME **(Standalone)** | Yes | Single archive or ROM file |
@ -4217,6 +4270,6 @@ The **@** symbol indicates that the emulator is _deprecated_ and will be removed
| xbox | Microsoft Xbox | xemu **(Standalone)** | Cxbx-Reloaded **(Standalone)** [W] | Yes for xemu | Single .iso file for xemu or unpacked .iso directory for Cxbx-Reloaded | | xbox | Microsoft Xbox | xemu **(Standalone)** | Cxbx-Reloaded **(Standalone)** [W] | Yes for xemu | Single .iso file for xemu or unpacked .iso directory for Cxbx-Reloaded |
| xbox360 | Microsoft Xbox 360 | xenia **(Standalone)** [W],<br>xenia **(Wine)** [L] | xenia **(Proton)** [L],<br> _Shortcut or script_ [L] | No | See the specific _Microsoft Xbox 360_ section elsewhere in this guide | | xbox360 | Microsoft Xbox 360 | xenia **(Standalone)** [W],<br>xenia **(Wine)** [L] | xenia **(Proton)** [L],<br> _Shortcut or script_ [L] | No | See the specific _Microsoft Xbox 360_ section elsewhere in this guide |
| zmachine | Infocom Z-machine | MojoZork | Gargoyle **(Standalone)** | No | | | zmachine | Infocom Z-machine | MojoZork | Gargoyle **(Standalone)** | No | |
| zx81 | Sinclair ZX81 | EightyOne | | | | | zx81 | Sinclair ZX81 | EightyOne | | No | |
| zxnext | Sinclair ZX Spectrum Next | #CSpect **(Standalone)** [LW],<br>ZEsarUX **(Standalone)** [M] | ZEsarUX **(Standalone)** [LW] | No | In separate folder interpreted as a file | | zxnext | Sinclair ZX Spectrum Next | #CSpect **(Standalone)** [LW],<br>ZEsarUX **(Standalone)** [M] | ZEsarUX **(Standalone)** [LW] | No | In separate folder interpreted as a file |
| zxspectrum | Sinclair ZX Spectrum | Fuse | Fuse **(Standalone)** | No | Single archive or ROM file | | zxspectrum | Sinclair ZX Spectrum | Fuse | Fuse **(Standalone)** | No | Single archive or ROM file |

View file

@ -22,11 +22,11 @@ You can always close the application immediately using the keyboard, by default
For additional details, read on below. For additional details, read on below.
There are also installation videos available at the ES-DE YouTube channel:\ There are also installation videos available at the ES-DE YouTube channel:\
[https://www.youtube.com/channel/UCosLuC9yIMQPKFBJXgDpvVQ](https://www.youtube.com/channel/UCosLuC9yIMQPKFBJXgDpvVQ) https://www.youtube.com/channel/UCosLuC9yIMQPKFBJXgDpvVQ
## Installation and first startup ## Installation and first startup
To install ES-DE, just download the package or installer from [https://es-de.org](https://es-de.org) and follow the brief instructions below. To install ES-DE, just download the package or installer from https://es-de.org and follow the brief instructions below.
As for display resolutions, the minimum pixel value is 224 and the maximum is 7680. This means that you can run ES-DE at for instance 320x224 all the way up to 7680x4320 (8K UHD). Vertical screen orientation is also supported, as well as ultra-wide resolutions like 3840x1440. As for display resolutions, the minimum pixel value is 224 and the maximum is 7680. This means that you can run ES-DE at for instance 320x224 all the way up to 7680x4320 (8K UHD). Vertical screen orientation is also supported, as well as ultra-wide resolutions like 3840x1440.
@ -65,7 +65,7 @@ Also on first startup the configuration file `es_settings.xml` will be generated
In addition to es_systems.xml there's an `es_find_rules.xml` file that gets loaded as well and which contains rules on how to locate the emulators, i.e. how to find out where they've been installed. In addition to es_systems.xml there's an `es_find_rules.xml` file that gets loaded as well and which contains rules on how to locate the emulators, i.e. how to find out where they've been installed.
There's an application log file created in the ES-DE home directory named `es_log.txt`, refer to this in case of any issues as it should hopefully provide information on what went wrong. Enabling _Debug mode_ in the _Other settings_ menu or starting ES-DE with the --debug flag outputs even more detailed information to this log file. There's also a log file created in the `ES-DE/logs` directory named `es_log.txt`, refer to this in case of any issues as it should hopefully provide information on what went wrong. Enabling _Debug mode_ in the _Other settings_ menu or starting ES-DE with the --debug flag outputs even more detailed information to this log file.
After ES-DE finds at least one game file, it will populate that game system and the application will start. If there are no game files, a dialog will be shown explaining that you need to install your game files into your ROM directory. You will also be given a choice to change that ROM directory path if you don't want to use the default one. As well you have the option to generate the complete game systems directory structure based on information in es_systems.xml. After ES-DE finds at least one game file, it will populate that game system and the application will start. If there are no game files, a dialog will be shown explaining that you need to install your game files into your ROM directory. You will also be given a choice to change that ROM directory path if you don't want to use the default one. As well you have the option to generate the complete game systems directory structure based on information in es_systems.xml.
@ -120,13 +120,13 @@ _This is the dialog shown if no game files were found. It lets you configure the
**Note:** Before upgrading ES-DE, make sure that you have not made any system customizations anywhere in the installation directory structure as these files will be overwritten during the upgrade process. All customizations should go into ~/ES-DE/custom_systems/ as described elsewhere in this guide. None of the upgrade methods mentioned below will ever touch any files inside your ES-DE directory tree. **Note:** Before upgrading ES-DE, make sure that you have not made any system customizations anywhere in the installation directory structure as these files will be overwritten during the upgrade process. All customizations should go into ~/ES-DE/custom_systems/ as described elsewhere in this guide. None of the upgrade methods mentioned below will ever touch any files inside your ES-DE directory tree.
There is a built-in application updater that can automatically update the Linux AppImage releases, and as of ES-DE 2.2.0 there is also support for downloading the Windows and macOS packages. Just be aware that these will still need to be manually installed. Using the application updater is straightforward, just follow the on-screen instructions. For the AppImage releases the old file is retained by renaming it, adding its version to the filename followed by the .OLD extension, for example `ES-DE_x64_SteamDeck.AppImage_3.0.0.OLD` There is a built-in application updater that can automatically upgrade the Linux AppImages, and for Windows and macOS there is support for downloading the packages directly inside ES-DE. Just be aware that these will need to be manually installed. Using the application updater is straightforward, just follow the on-screen instructions. For the AppImage releases the old file is retained by renaming it, adding its version to the filename followed by the .OLD extension, for example `ES-DE_x64_SteamDeck.AppImage_3.0.0.OLD`
Note that the updater will keep whatever filename you had for your running AppImage file, which could potentially be confusing if you for example added version information to the filename. It's always recommend to keep the default AppImage filenames, i.e. `ES-DE_x64.AppImage` and `ES-DE_x64_SteamDeck.AppImage` Note that the updater will keep whatever filename you had for your running AppImage file, which could potentially be confusing if you for example added version information to the filename. It's always recommend to keep the default AppImage filenames, i.e. `ES-DE_x64.AppImage` and `ES-DE_x64_SteamDeck.AppImage`
On Windows and macOS you can specify to which directory you want to save the downloaded file. The default is `C:\Users\myusername\Downloads` on Windows and `/Users/myusername/Downloads` on macOS. On Windows and macOS you can specify to which directory you want to save the downloaded file. The default is `C:\Users\myusername\Downloads` on Windows and `/Users/myusername/Downloads` on macOS.
On Android all updates are made via the app store. Unless you have modifed the option _Check for application updates_ you'll see a popup on application startup whenever there's a new release in the app store. On Android the update process differs depending on whether you have the Patreon release or a release from either the Samsung Galaxy Store or Huawei AppGallery. For the store versions you simply update via the store app. For the Patreon release you'll get an email (sent to the address you used when buying ES-DE there) whenever there is a new version. For all Android releases, unless you have modifed the option _Check for application updates_ you'll see a popup on application startup whenever there's a new release available.
Regardless of package format and operating system it's a good idea to update the ROM directory tree after upgrading to a new version. It's possible that the new ES-DE release adds support for more systems and emulators compared to the version you previously had installed. The easiest way to do this is via the _Create/update system directories_ entry in the _Utilities_ menu. Alternatively the _--create-system-dirs_ command line option can be used. Both methods work identically and will create any missing system directories and also update the systems.txt and systeminfo.txt files. This is a safe operation as it will not overwrite or delete your game files. Regardless of package format and operating system it's a good idea to update the ROM directory tree after upgrading to a new version. It's possible that the new ES-DE release adds support for more systems and emulators compared to the version you previously had installed. The easiest way to do this is via the _Create/update system directories_ entry in the _Utilities_ menu. Alternatively the _--create-system-dirs_ command line option can be used. Both methods work identically and will create any missing system directories and also update the systems.txt and systeminfo.txt files. This is a safe operation as it will not overwrite or delete your game files.
@ -187,7 +187,19 @@ For very specific situations such as when the ROM directory tree is shared with
~/ROMs/nes/noload.txt ~/ROMs/nes/noload.txt
``` ```
Note that if the setting _Only show games from gamelist.xml files_ has been enabled then the noload.txt logic is completely bypassed as this option will make ES-DE load anything present in the gamelist.xml files, regardless of whether the files and directories actually exist. But this option (or the equivalent --gamelist-only command line option) is only intended for troubleshooting and debugging purposes and should not be enabled during normal application usage. Note that if the setting _Only show games from gamelist.xml files_ has been enabled then the noload.txt logic is completely bypassed as this option will make ES-DE load anything present in the gamelist.xml files, regardless of whether the files and directories actually exist.
## Skip loading of individual subdirectories
Sometimes you need to place things inside the ROMs directory tree that will not be visible inside ES-DE, such as texture packs and similar. But as ES-DE always scans all files to determine which ones are valid game files this can add significantly to the application startup time. However loading of such subdirectories can be skipped by placing a `noload.txt` file in the root of the directory, in the same manner as documented in the previous section above regarding disabling of game systems. For example:
```
~/ROMs/psx/textures/noload.txt
```
Just note that you can't clean out stale entries from the gamelist.xml files for any directories that have been hidden in this way. So to get rid of any gamelist.xml entries for such files temporarily remove the noload.txt file, restart or reload ES-DE, run the _Orphaned data cleanup_ utility, then create a new noload.txt file and finally reload or restart ES-DE again.
Note that if the setting _Only show games from gamelist.xml files_ has been enabled then the noload.txt logic is completely bypassed as this option will make ES-DE load anything present in the gamelist.xml files.
## Placing games and other resources on network shares ## Placing games and other resources on network shares
@ -229,7 +241,7 @@ Just make sure to never place games or other resources on network shares using t
Also make sure that you don't use the exFAT filesystem as its very poor disk I/O performance will make ES-DE run really slowly. Using this filesystem will make the theme downloader fail as well. Also make sure that you don't use the exFAT filesystem as its very poor disk I/O performance will make ES-DE run really slowly. Using this filesystem will make the theme downloader fail as well.
In order for ES-DE to run, graphics drivers with OpenGL support have to be installed. If not, the application simply won't start. For really old graphics cards the available drivers may not provide an OpenGL version that is modern enough for ES-DE to work, and in this case a last resort solution would be to install the _Mesa3D for Windows_ library which provides software-based OpenGL rendering. The 64-bit version of this library can be downloaded from [https://fdossena.com/?p=mesa/index.frag](https://fdossena.com/?p=mesa/index.frag) and you simply extract the opengl32.dll file into the ES-DE installation directory. Just be aware that the performance may be quite bad. In order for ES-DE to run, graphics drivers with OpenGL support have to be installed. If not, the application simply won't start. For really old graphics cards the available drivers may not provide an OpenGL version that is modern enough for ES-DE to work, and in this case a last resort solution would be to install the _Mesa3D for Windows_ library which provides software-based OpenGL rendering. The 64-bit version of this library can be downloaded from https://fdossena.com/?p=mesa/index.frag and you simply extract the opengl32.dll file into the ES-DE installation directory. Just be aware that the performance may be quite bad.
On some GPUs with buggy drivers, ES-DE may only display a black screen on startup or when launching a game. The problem can be worked around by specifying a window size for ES-DE that is a single pixel wider than the actual screen resolution. So for example for a 1280x800 display, the resolution can be set to 1281x800 and then rendering should work correctly. This is applied using the --resolution command line option, for example: On some GPUs with buggy drivers, ES-DE may only display a black screen on startup or when launching a game. The problem can be worked around by specifying a window size for ES-DE that is a single pixel wider than the actual screen resolution. So for example for a 1280x800 display, the resolution can be set to 1281x800 and then rendering should work correctly. This is applied using the --resolution command line option, for example:
``` ```
@ -290,27 +302,34 @@ Unless RetroDECK is used, Flatpak releases of some emulators may need some extra
If you are unfamiliar with Linux/Unix operating systems, make sure to at least read up on the concepts of _dotfiles_ (hidden files and directories), _home directories_ (including use of the tilde ~ character) and _symbolic links_ (symlinks): If you are unfamiliar with Linux/Unix operating systems, make sure to at least read up on the concepts of _dotfiles_ (hidden files and directories), _home directories_ (including use of the tilde ~ character) and _symbolic links_ (symlinks):
[https://en.wikipedia.org/wiki/Hidden_file_and_hidden_directory#Unix_and_Unix-like_environments](https://en.wikipedia.org/wiki/Hidden_file_and_hidden_directory#Unix_and_Unix-like_environments) \ https://en.wikipedia.org/wiki/Hidden_file_and_hidden_directory#Unix_and_Unix-like_environments \
[https://en.wikipedia.org/wiki/Home_directory#Unix](https://en.wikipedia.org/wiki/Home_directory#Unix) \ https://en.wikipedia.org/wiki/Home_directory#Unix \
[https://en.wikipedia.org/wiki/Symbolic_link](https://en.wikipedia.org/wiki/Symbolic_link) https://en.wikipedia.org/wiki/Symbolic_link
## Specific notes for Android ## Specific notes for Android
The Android port of ES-DE is quite different than the other versions, so it has its specifics covered by a dedicated [ANDROID.md](ANDROID.md) document. The Android port of ES-DE is quite different than the other versions, so it has its specifics covered by a dedicated [ANDROID.md](ANDROID.md) document.
## Specific notes for Haiku
The [Haiku](https://www.haiku-os.org) port of ES-DE is currently experimental as the OS itself is experimental and has some issues. Still most functionality in ES-DE is working and there is support for a quite large number of systems and emulators. If you're interested in Haiku it's for sure worth trying it out. See the dedicated [HAIKU.md](HAIKU.md) document for more details.
## Specific notes for Raspberry Pi ## Specific notes for Raspberry Pi
By default ES-DE on the Raspberry Pi requires a desktop environment to run, or more specifically a window manager and a sound server (like PulseAudio or PipeWire). It is however possible to use KMS/direct framebuffer access if the DEINIT_ON_LAUNCH flag is used when building ES-DE, as documented in the _Building on Unix_ section of the [INSTALL.md](INSTALL.md#building-on-unix) document. For the best experience with the Raspberry Pi it's adviced to run Android on it. There are custom OS builds available here: \
https://konstakang.com/
Note that there are no prebuilt packages for the Raspberry Pi, so you will need to compile ES-DE yourself. Fortunately this is easy to do and the process is documented [here](INSTALL.md#building-on-unix). If instead going for regular Linux, then by default ES-DE on the Raspberry Pi requires a desktop environment to run, or more specifically a window manager and a sound server (like PulseAudio or PipeWire). It is however possible to use KMS/direct framebuffer access if the DEINIT_ON_LAUNCH flag is used when building ES-DE, as documented in the _Building on Unix_ section of the [INSTALL.md](INSTALL.md#building-on-unix) document.
Note that there are no prebuilt Linux packages for the Raspberry Pi, so you will need to compile ES-DE yourself.
The Raspberry Pi 4/400 is the minimum recommended version and earlier boards have not been tested. The Raspberry Pi 4/400 is the minimum recommended version and earlier boards have not been tested.
In general, 720p works fine with the RPi 4, and 1080p is tolerable but not really a nice and smooth experience. Due to the relative weakness of the Rasperry Pi GPU, the video scanline rendering options for the screensaver and media viewer have been disabled. These options can be re-enabled via the menu if you don't mind lower video framerates. In general, 720p works fine with the RPi 4, and 1080p is tolerable on the RPi 5, but due to the relative weakness of the Rasperry Pi GPU, the video scanline rendering options for the screensaver and media viewer have been disabled (only for Linux and not for Android). These options can be re-enabled via the menu if you don't mind lower video framerates.
## Game system customizations ## Game system customizations
The game systems configuration file `es_systems.xml` is located in the ES-DE resources directory which is part of the application installation. As such this file is not intended to be modified directly. If system customizations are required, a separate es_systems.xml file should instead be placed in the `custom_systems` folder in the ES-DE home directory. The game systems configuration file `es_systems.xml` is located in the ES-DE resources directory which is part of the application installation. As such this file is not intended to be modified directly. If system customizations are required, a separate es_systems.xml file should instead be placed in the `custom_systems` folder in the ES-DE application data directory.
On Linux this means `/home/<username>/ES-DE/custom_systems/es_systems.xml`, on macOS `/Users/<username>/ES-DE/custom_systems/es_systems.xml`, on Windows `C:\Users\<username>\ES-DE\custom_systems\es_systems.xml` or `ES-DE\ES-DE\custom_systems\es_systems.xml` depending on whether the installer release or the portable release is used, and on Android it's `ES-DE/custom_systems/es_systems.xml`. On Linux this means `/home/<username>/ES-DE/custom_systems/es_systems.xml`, on macOS `/Users/<username>/ES-DE/custom_systems/es_systems.xml`, on Windows `C:\Users\<username>\ES-DE\custom_systems\es_systems.xml` or `ES-DE\ES-DE\custom_systems\es_systems.xml` depending on whether the installer release or the portable release is used, and on Android it's `ES-DE/custom_systems/es_systems.xml`.
@ -668,6 +687,7 @@ The following emulators are supported in AppImage format when using the bundled
| System name | Emulator | Filename configuration | | System name | Emulator | Filename configuration |
| :------------ | :------------------ | :----------------------------- | | :------------ | :------------------ | :----------------------------- |
| _Multiple_ | RetroArch | RetroArch-Linux*.AppImage | | _Multiple_ | RetroArch | RetroArch-Linux*.AppImage |
| _Multiple_ | jgenesis | jgenesis-cli*.AppImage |
| _Multiple_ | Mesen | Mesen*.AppImage | | _Multiple_ | Mesen | Mesen*.AppImage |
| dreamcast | Flycast | flycast-x86*.AppImage | | dreamcast | Flycast | flycast-x86*.AppImage |
| dreamcast | Flycast Dojo | flycast-dojo*.AppImage | | dreamcast | Flycast Dojo | flycast-dojo*.AppImage |
@ -677,7 +697,7 @@ The following emulators are supported in AppImage format when using the bundled
| macintosh | Basilisk II | BasiliskII*.AppImage | | macintosh | Basilisk II | BasiliskII*.AppImage |
| macintosh | SheepShaver | SheepShaver*.AppImage | | macintosh | SheepShaver | SheepShaver*.AppImage |
| n3ds | Citra | citra-qt*.AppImage | | n3ds | Citra | citra-qt*.AppImage |
| n3ds | Lime3DS | lime3ds-gui*.AppImage | | n3ds | Lime3DS | lime3ds.AppImage |
| n3ds | Panda3DS | Alber-*.AppImage | | n3ds | Panda3DS | Alber-*.AppImage |
| n64/n64dd | Rosalie's Mupen GUI | RMG*.AppImage | | n64/n64dd | Rosalie's Mupen GUI | RMG*.AppImage |
| ngage/symbian | EKA2L1 | EKA2L1*.AppImage | | ngage/symbian | EKA2L1 | EKA2L1*.AppImage |
@ -1118,7 +1138,8 @@ If you want to use MAME standalone then you need to place the following ROM file
a2diskiing.zip a2diskiing.zip
apple2e.zip apple2e.zip
d2fdc.zip d2fdc.zip
votrax.zip votrsc01.zip
votrsc01a.zip
``` ```
Note that you will need to enable UI controls in MAME to be able to exit the emulator via the normal exit key. The following page documents the default keys for exiting and toggling UI mode:\ Note that you will need to enable UI controls in MAME to be able to exit the emulator via the normal exit key. The following page documents the default keys for exiting and toggling UI mode:\
@ -1322,7 +1343,7 @@ There are multiple ways to run these games, for the computer models like the A50
This emulator is by far the most straightforward Amiga emulator to use, it's easy to configure and it runs all file types that ES-DE supports. It can run zipped files too for all supported formats. This emulator is by far the most straightforward Amiga emulator to use, it's easy to configure and it runs all file types that ES-DE supports. It can run zipped files too for all supported formats.
PUAE requires Amiga Kickstart ROMs to run, you can find more information about that topic here:\ PUAE requires Amiga Kickstart ROMs to run, you can find more information about that topic here:\
[https://github.com/libretro/libretro-uae/blob/master/README.md](https://github.com/libretro/libretro-uae/blob/master/README.md) https://github.com/libretro/libretro-uae/blob/master/README.md
For the Amiga computer models the recommended approach is to go for WHDLoad-packaged files in the `.lha` or `.zip` format. While it's also possible to use WHDLoad hard drive images in `.hdf` or `.hdz` format these will only work in PAUE so they are not really recommended as you may want to use another emulator in the future. For the Amiga computer models the recommended approach is to go for WHDLoad-packaged files in the `.lha` or `.zip` format. While it's also possible to use WHDLoad hard drive images in `.hdf` or `.hdz` format these will only work in PAUE so they are not really recommended as you may want to use another emulator in the future.
@ -1450,6 +1471,8 @@ How to configure each emulator is far beyond the scope of this document, but the
For this platform there are two basic approaches for how the setup could be done; either to present each game as a single entry inside ES-DE, or to retain each game's directory structure. The first alternative is more user-friendly, tidy and requires less setup but basically restricts the emulator selection to the DOSBox-Pure RetroArch core. There is an alternative way to setup single entries to work with all DOSBox forks, but it has some drawbacks as discussed below. For this platform there are two basic approaches for how the setup could be done; either to present each game as a single entry inside ES-DE, or to retain each game's directory structure. The first alternative is more user-friendly, tidy and requires less setup but basically restricts the emulator selection to the DOSBox-Pure RetroArch core. There is an alternative way to setup single entries to work with all DOSBox forks, but it has some drawbacks as discussed below.
If you want to emulate older DOS games and applications then there's also support for the VirtualXT RetroArch core, but this emulator can only run .img and .zip files and it probably won't be able to run most games from the 1990s. For these reasons this documentation only covers DOSBox.
If you prefer to present the games as single entries you could compress each game directory into a ZIP file with either the .zip or .dosz file extension. On game launch a menu will be displayed by DOSBox-Pure, asking which file inside the archive you would like to execute. This makes it possible to select the actual game file, or for example a setup utility like SETUP.EXE or INSTALL.EXE. Attempting to launch such an archive file with any other DOSBox fork will fail, or not work as expected. If you prefer to present the games as single entries you could compress each game directory into a ZIP file with either the .zip or .dosz file extension. On game launch a menu will be displayed by DOSBox-Pure, asking which file inside the archive you would like to execute. This makes it possible to select the actual game file, or for example a setup utility like SETUP.EXE or INSTALL.EXE. Attempting to launch such an archive file with any other DOSBox fork will fail, or not work as expected.
Here's an example of a .zip archive setup for use with DOSBox-Pure: Here's an example of a .zip archive setup for use with DOSBox-Pure:
@ -1490,7 +1513,15 @@ Regardless of game setup method, per-game settings can be applied. If using the
### Dragon 32 and Tano Dragon ### Dragon 32 and Tano Dragon
These computers as well as the Dragon 64 are slight varations of the Tandy Color Computer and as these machines are largely compatible with each other they're all emulated using the [XRoar](http://www.6809.org.uk/xroar) emulator. The Dragon 32, Dragon 64 and Tano Dragon are all slight variations of the Tandy Color Computer, so these machines are largely compatible with each other. They're all emulated using MAME standalone (MAME4droid 2024 on Android) or the [XRoar](http://www.6809.org.uk/xroar) emulator.
**MAME**
To use MAME you need the `dragon32.zip` and `dragon_fdc.zip` BIOS files in the ROMs/dragon32 directory and you need the `tanodr64.zip` BIOS file in ROMs/tanodragon.
For the dragon32 system there are four MAME emulator entries for tape and cartridge for the Dragon 32 and Dragon 64 models respectively and for the tanodragon system there are two entries for tape and cartridge.
**XRoar**
This emulator is available for Linux, macOS and Windows, although on Linux you may need to build it from source code depending on which distribution you're using. Refer to the XRoar website for more information. If you manually download or build the emulator yourself then see the [Using manually downloaded emulators on Linux](USERGUIDE.md#using-manually-downloaded-emulators-on-linux) section of this guide for more details on where you need to install it. This emulator is available for Linux, macOS and Windows, although on Linux you may need to build it from source code depending on which distribution you're using. Refer to the XRoar website for more information. If you manually download or build the emulator yourself then see the [Using manually downloaded emulators on Linux](USERGUIDE.md#using-manually-downloaded-emulators-on-linux) section of this guide for more details on where you need to install it.
@ -1705,7 +1736,7 @@ For Daphne games the structure will look something like the following, which is
``` ```
The directory name has to keep this naming convention with the name consisting of the Daphne game type (_lair_ for this example) followed by the .daphne extension. This name logic with a short name per game is similar to how it works in MAME and ScummVM. A list of available games can be found here: \ The directory name has to keep this naming convention with the name consisting of the Daphne game type (_lair_ for this example) followed by the .daphne extension. This name logic with a short name per game is similar to how it works in MAME and ScummVM. A list of available games can be found here: \
[http://www.daphne-emu.com/mediawiki/index.php/CmdLine](http://www.daphne-emu.com/mediawiki/index.php/CmdLine) https://www.daphne-emu.com:9443/mediawiki/index.php/CmdLine
In order to get the games to work, simply create an empty file named _\<game\>.daphne_ inside the game directory, for example `lair.daphne` in this case. The _Directories interpreted as files_ functionality will then allow the game to be launched even though it shows up as a single entry inside ES-DE. In order to get the games to work, simply create an empty file named _\<game\>.daphne_ inside the game directory, for example `lair.daphne` in this case. The _Directories interpreted as files_ functionality will then allow the game to be launched even though it shows up as a single entry inside ES-DE.
@ -2210,7 +2241,7 @@ The drawback to using shortcuts is that they're not portable, if you change the
**Linux:** **Linux:**
On Linux you need to supply your own game engine binary as few (if any) games are distributed with the Linux release of OpenBOR. Download the .7z archive from the [https://github.com/DCurrent/openbor](https://github.com/DCurrent/openbor) repository. The file you want is _OpenBOR_3.0_6391.AppImage_ which is located inside the LINUX/OpenBOR folder. If you need an older engine for some specific game, then you may need to download an earlier release instead. On Linux you need to supply your own game engine binary as few (if any) games are distributed with the Linux release of OpenBOR. Download the .7z archive from the https://github.com/DCurrent/openbor repository. The file you want is _OpenBOR_3.0_6391.AppImage_ which is located inside the LINUX/OpenBOR folder. If you need an older engine for some specific game, then you may need to download an earlier release instead.
Copy this file to the game directory and make it executable using the command `chmod +x OpenBOR_3.0_6391.AppImage` Copy this file to the game directory and make it executable using the command `chmod +x OpenBOR_3.0_6391.AppImage`
@ -2269,7 +2300,7 @@ This is what the complete setup could look like:
On Android the Fake-08 core provides a good alternative to the official PICO-8 engine which is not available on this operating system. ES-DE also includes support for the Retro8 RetroArch core across all platforms, but it's borderline unusable and does not seem to be actively developed any longer. On Android the Fake-08 core provides a good alternative to the official PICO-8 engine which is not available on this operating system. ES-DE also includes support for the Retro8 RetroArch core across all platforms, but it's borderline unusable and does not seem to be actively developed any longer.
Neither Fake-08 nor Retro8 support the .png file extension, so in order to use these cores you need to remove that extension from your game files and only keep the .p8 extension, such as this: To use the .png file extension with Fake-08 and Retro8 you need to disable the built-in image viewer in RetroArch, otherwise you'll have to rename your game files to only use the .p8 extension, such as this:
``` ```
~/ROMs/pico8/c_e_l_e_s_t_e-0.p8 ~/ROMs/pico8/c_e_l_e_s_t_e-0.p8
@ -2362,7 +2393,7 @@ ScummVM overlaps a bit with DOS when it comes to the logic of setting it up. It'
Although ScummVM supports launching of .exe files, ES-DE is currently not configured as such and it's instead recommended to create a .scummvm file in each game directory and launch that. This makes for a cleaner setup as you don't need to run game configuration utilities like INSTALL.EXE or SETUP.EXE directly as you would with DOSBox. Rather the game configuration is done within the ScummVM emulator. Although ScummVM supports launching of .exe files, ES-DE is currently not configured as such and it's instead recommended to create a .scummvm file in each game directory and launch that. This makes for a cleaner setup as you don't need to run game configuration utilities like INSTALL.EXE or SETUP.EXE directly as you would with DOSBox. Rather the game configuration is done within the ScummVM emulator.
The .scummvm file must be named using the correct _Game Short Name_ and it must also contain this short name as a single string/word. You can find the complete list of supported ScummVM games with their corresponding short names here:\ The .scummvm file must be named using the correct _Game Short Name_ and it must also contain this short name as a single string/word. You can find the complete list of supported ScummVM games with their corresponding short names here:\
[https://www.scummvm.org/compatibility](https://www.scummvm.org/compatibility) https://www.scummvm.org/compatibility
An example setup could look like the following: An example setup could look like the following:
``` ```
@ -2370,7 +2401,9 @@ An example setup could look like the following:
~/ROMs/scummvm/Flight of the Amazon Queen/queen.scummvm ~/ROMs/scummvm/Flight of the Amazon Queen/queen.scummvm
``` ```
To clarify, the sky.scummvm file should contain just the single word `sky` and likewise the queen.scummvm file should only contain the word `queen`. To clarify, the sky.scummvm file should contain just the single word _sky_ and likewise the queen.scummvm file should only contain the word _queen_.
However, note that ScummVM on Android (and possibly on other operating systems as well) sometimes changes the short name inside its user interface, for example an index could be added so that instead of _sky_ it says _sky-1_ or some variation of that. In this case you need to have this exact string inside the .scummvm file instead of the default name from the compatibility list linked above.
In order to avoid having to display each game as a directory inside ES-DE (that needs to be entered each time you want to launch a game), you can optionally interpret each game directory as a file. Make sure to read the _Directories interpreted as files_ section [here](USERGUIDE.md#directories-interpreted-as-files) to understand how this functionality works, but essentially the following would be the setup required for our example: In order to avoid having to display each game as a directory inside ES-DE (that needs to be entered each time you want to launch a game), you can optionally interpret each game directory as a file. Make sure to read the _Directories interpreted as files_ section [here](USERGUIDE.md#directories-interpreted-as-files) to understand how this functionality works, but essentially the following would be the setup required for our example:
``` ```
@ -2494,15 +2527,19 @@ As the Nokia N-Gage was running Symbian it may seem like the _ngage_ and _symbia
**Android** **Android**
Unfortunately there does not seem to be a way to launch individual games from ES-DE on Android specifically, so instead the EKA2L1 user interface will open on game launch and you need to manually start your game from inside the emulator. As games need to be installed upfront in the emulator as described below it's probably a good idea to just setup dummy game files with the .symbian or .ngage file extensions inside the ES-DE ROMs directory tree. These will then appear as indvidual games inside ES-DE and you can add metadata to them, scrape them etc. For the symbian system it's possible to launch individual games directly from ES-DE, but for the ngage system this is unfortunately not possible. Instead the EKA2L1 user interface will open on game launch and you need to manually start your game from inside the emulator. For both the symbian and ngage systems all games need to be installed upfront in EKA2L1.
For N-Gage games it's a good idea to just create empty dummy files with the .ngage file extensions inside the ROMs/ngage directory. These will then appear as indvidual games inside ES-DE and you can add metadata to them, scrape them etc.
For Symbian games you can export JSON launch files from EKA2L1 that can be run directly from ES-DE. Just open EKA2L1, long press the game icon and select _Create launch file_ from the popup list. Then just select the ROMs/symbian directory and the file will be saved there and game launching from ES-DE will work as expected.
Here's an example setup: Here's an example setup:
``` ```
/storage/emulated/0/ROMs/ngage/Asphalt 2.ngage /storage/emulated/0/ROMs/ngage/Asphalt 2.ngage
/storage/emulated/0/ROMs/ngage/Bomberman.ngage /storage/emulated/0/ROMs/ngage/Bomberman.ngage
/storage/emulated/0/ROMs/ngage/CallofDuty.ngage /storage/emulated/0/ROMs/ngage/CallofDuty.ngage
/storage/emulated/0/ROMs/symbian/Animal Farm.symbian /storage/emulated/0/ROMs/symbian/Animal Farm.json
/storage/emulated/0/ROMs/symbian/AnotherWorld.symbian /storage/emulated/0/ROMs/symbian/AnotherWorld.json
``` ```
**General setup** **General setup**
@ -2849,6 +2886,11 @@ This directory can however be changed using the _Game media directory_ setting i
See the [Supported game systems](USERGUIDE.md#supported-game-systems) table at the bottom of this guide for a list of all system names. See the [Supported game systems](USERGUIDE.md#supported-game-systems) table at the bottom of this guide for a list of all system names.
An example on Android:
```
/storage/emulated/0/ES-DE/downloaded_media/c64/screenshots/
```
An example on Linux: An example on Linux:
``` ```
/home/myusername/ES-DE/downloaded_media/c64/screenshots/ /home/myusername/ES-DE/downloaded_media/c64/screenshots/
@ -2891,6 +2933,9 @@ The miximages are generated by ES-DE. Normally that takes place automatically wh
The `custom` directory is not created automatically, it's an optional folder where it's possible to place an image per game that can be viewed as the last entry in the media viewer. It's intended for things like diagrams of game controller mappings that you may want to consult before starting a game. These files have to be saved with the .jpg or .png extension and they follow the same naming logic as all other media files, as explained next. The `custom` directory is not created automatically, it's an optional folder where it's possible to place an image per game that can be viewed as the last entry in the media viewer. It's intended for things like diagrams of game controller mappings that you may want to consult before starting a game. These files have to be saved with the .jpg or .png extension and they follow the same naming logic as all other media files, as explained next.
There's a handy spreadsheet here that explains each media type:\
https://docs.google.com/spreadsheets/d/18VJAL44aNxsFOd4pVAONmdWwa7srCSzr2Z2SJEiNKnE/edit?gid=1812680930#gid=1812680930
The media files must correspond exactly to the game files. Take for example this game: The media files must correspond exactly to the game files. Take for example this game:
``` ```
@ -3203,6 +3248,14 @@ Themes could optionally be optimized for different screen aspect ratios. ES-DE s
Transition animations to play when navigating between different gamelists, between systems in the system view and between the system and gamelist views. It's up to the theme author to define what to include for this option. Technically these can be any combination of _instant_, _slide_ or _fade_ transitions. If there are no user-selectable transitions avaialable the setting will be grayed out. Transition animations to play when navigating between different gamelists, between systems in the system view and between the system and gamelist views. It's up to the theme author to define what to include for this option. Technically these can be any combination of _instant_, _slide_ or _fade_ transitions. If there are no user-selectable transitions avaialable the setting will be grayed out.
**Theme language**
If the selected theme has multilingual support then you can select between its supported languages here. This setting is primarily intended for testing purposes and for theme developers, and should as such usually be left at _automatic_ which will select the same theme language as the overall application language (see the next setting below). Note that not all themes may support all languages that the ES-DE application supports. Also note that a portion of the theme translations are contained within the base application itself and as such will not switch language unless you also change the _Application language_ setting accordingly.
**Application language**
Sets the language for the application user interface. If this option is set to _automatic_ then the language will be auto-detected, which means ES-DE will attempt to use whatever language has been selected in the operating system language settings. If there are no translations available in ES-DE for this precise language then a fallback will be done to the closest match, such as _Svenska_ instead of _Svenska (Finland)_. If no close match is available then ES-DE will revert to the default language which is _English (United States)_. It's also possible to explicitly select a supported language, which will override whatever has been set by the operating system. Note that the onboarding configurator for the Android release is unaffected by this setting. Also note that language auto-detection does not work on the Steam Deck when running in game mode, so there it's necessary to select a language explicitly. If you accidentally select a language you didn't intend to, then you can access the application language setting via the second menu entry from the top after opening the main menu, and then after entering this sub-menu by pressing the down button eight times.
**Quick system select** **Quick system select**
The buttons to use to jump between systems in the gamelist view. The options are _Left/right or shoulders_, _Left/right or triggers_, _Shoulders_, _Triggers_, _Left/right_ or _Disabled_. The first two options will apply either left/right or shoulder/trigger buttons depending on the type of primary element used for the gamelist. For example a textlist or a vertical carousel will allow the use of the left and right buttons, but for horizontal carousels and grids these buttons are reserved for navigating the entries so instead the secondary buttons will be used, i.e. the shoulder or trigger buttons. Using these two options therefore leads to a slight inconsistency as different buttons will be used depending on the theme configuration. If instead using any of the single button pair options, i.e. _Shoulders_, _Triggers_ or _Left/right_, the navigation will be consistent regardless of theme configuration but you'll sacrifice the ability to use the selected buttons if the gamelist supports it, such as the ability to jump rows in a textlist using the shoulder and trigger buttons. The buttons to use to jump between systems in the gamelist view. The options are _Left/right or shoulders_, _Left/right or triggers_, _Shoulders_, _Triggers_, _Left/right_ or _Disabled_. The first two options will apply either left/right or shoulder/trigger buttons depending on the type of primary element used for the gamelist. For example a textlist or a vertical carousel will allow the use of the left and right buttons, but for horizontal carousels and grids these buttons are reserved for navigating the entries so instead the secondary buttons will be used, i.e. the shoulder or trigger buttons. Using these two options therefore leads to a slight inconsistency as different buttons will be used depending on the theme configuration. If instead using any of the single button pair options, i.e. _Shoulders_, _Triggers_ or _Left/right_, the navigation will be consistent regardless of theme configuration but you'll sacrifice the ability to use the selected buttons if the gamelist supports it, such as the ability to jump rows in a textlist using the shoulder and trigger buttons.
@ -3221,9 +3274,9 @@ The order in which to sort your gamelists. This can be overriden per game system
**Menu color scheme** **Menu color scheme**
Provides a selection between a _Dark_ and a _Light_ color scheme. This will affect the entire menu system as well as the game launch screen. Provides a selection between the _Dark_, _Dark with red_ and _Light_ color schemes. This will affect the entire menu system as well as the game launch screen.
**Menu opening effect** **Menu opening animation**
Animation to play when opening the main menu or the gamelist options menu. Also sets the animation for the game launch screen. Can be set to _Scale-up_ or _None_. Animation to play when opening the main menu or the gamelist options menu. Also sets the animation for the game launch screen. Can be set to _Scale-up_ or _None_.
@ -3552,7 +3605,7 @@ The metadata for a game is updated by scraping or by manual editing it using the
**Check for application updates** _Not available for some builds_ **Check for application updates** _Not available for some builds_
By default a check for new ES-DE versions will be done on every application startup and a notification will be displayed if there is a new release available for download. Using this option the frequency of these checks can be set to _Always_, _Daily_, _Weekly_, _Monthly_ or _Never_. This setting is not available on some platforms and package formats such as the Linux AUR release and the semi-official BSD Unix and Raspberry Pi releases where pre-built packages are not provided. By default a check for new ES-DE versions will be done on every application startup and a notification will be displayed if there is a new release available for download. Using this option the frequency of these checks can be set to _Always_, _Daily_, _Weekly_, _Monthly_ or _Never_. This setting is not available on some platforms and package formats such as the Linux AUR release and the semiofficial FreeBSD, Raspberry Pi and Haiku releases where pre-built packages are not provided.
**Include prereleases in update checks** _Always enabled for prereleases_ **Include prereleases in update checks** _Always enabled for prereleases_
@ -4037,14 +4090,14 @@ The **@** symbol indicates that the emulator is _deprecated_ and will be removed
| android | Google Android | BlueStacks **(Standalone)** [W] | | No | Shortcut (.lnk) file | | android | Google Android | BlueStacks **(Standalone)** [W] | | No | Shortcut (.lnk) file |
| androidapps | Android Apps | _Placeholder_ | | | | | androidapps | Android Apps | _Placeholder_ | | | |
| androidgames | Android Games | _Placeholder_ | | | | | androidgames | Android Games | _Placeholder_ | | | |
| apple2 | Apple II | LinApple **(Standalone)** [L],<br>Mednafen **(Standalone)** [M],<br>AppleWin **(Standalone)** [W] | Mednafen **(Standalone)** [LW],<br>MAME - Current,<br>MAME **(Standalone)** | Yes for Mednafen and MAME | See the specific _Apple II_ section elsewhere in this guide | | apple2 | Apple II | LinApple **(Standalone)** [L],<br>Mednafen **(Standalone)** [M],<br>AppleWin **(Standalone)** [W] | Mednafen **(Standalone)** [LW],<br>MAME - Current,<br>MAME **(Standalone)**,<br>izapple2 **(Standalone)** [LW] | Yes for Mednafen and MAME | See the specific _Apple II_ section elsewhere in this guide |
| apple2gs | Apple IIGS | MAME - Current | MAME **(Standalone)** | Yes | See the specific _Apple IIGS_ section elsewhere in this guide | | apple2gs | Apple IIGS | MAME - Current | MAME **(Standalone)** | Yes | See the specific _Apple IIGS_ section elsewhere in this guide |
| arcade | Arcade | MAME - Current | MAME 2010,<br>MAME 2003-Plus,<br>MAME 2003,<br>MAME 2000,<br>MAME **(Standalone)**,<br>FinalBurn Neo,<br>FinalBurn Neo **(Standalone)** [LW],<br>FB Alpha 2012,<br>Geolith,<br>Flycast,<br>Flycast **(Standalone)**,<br>Flycast Dojo **(Standalone)**,<br>Kronos [LW],<br>Model 2 Emulator **(Standalone)** [W],<br>Model 2 Emulator [Suspend ES-DE] **(Standalone)** [W],<br>Supermodel **(Standalone)** [LW],<br> _Shortcut or script_ | Depends | See the specific _Arcade and Neo Geo_ section elsewhere in this guide | | arcade | Arcade | MAME - Current | MAME 2010,<br>MAME 2003-Plus,<br>MAME 2003,<br>MAME 2000,<br>MAME **(Standalone)**,<br>FinalBurn Neo,<br>FinalBurn Neo **(Standalone)** [LW],<br>FB Alpha 2012,<br>Geolith,<br>Flycast,<br>Flycast **(Standalone)**,<br>Flycast Dojo **(Standalone)**,<br>Kronos [LW],<br>Model 2 Emulator **(Standalone)** [W],<br>Model 2 Emulator [Suspend ES-DE] **(Standalone)** [W],<br>Supermodel **(Standalone)** [LW],<br> _Shortcut or script_ | Depends | See the specific _Arcade and Neo Geo_ section elsewhere in this guide |
| arcadia | Emerson Arcadia 2001 | MAME - Current | MAME **(Standalone)**,<br>WinArcadia **(Standalone)** | No | Single archive or ROM file | | arcadia | Emerson Arcadia 2001 | MAME - Current | MAME **(Standalone)**,<br>WinArcadia **(Standalone)** | No | Single archive or ROM file |
| archimedes | Acorn Archimedes | MAME [Model A440/1] **(Standalone)** | MAME [Model A3000] **(Standalone)**,<br>MAME [Model A310] **(Standalone)**,<br>MAME [Model A540] **(Standalone)** | Yes | | | archimedes | Acorn Archimedes | MAME [Model A440/1] **(Standalone)** | MAME [Model A3000] **(Standalone)**,<br>MAME [Model A310] **(Standalone)**,<br>MAME [Model A540] **(Standalone)** | Yes | |
| arduboy | Arduboy Miniature Game System | Arduous | | No | Single archive or .hex file | | arduboy | Arduboy Miniature Game System | Arduous | Ardens | No | Single archive or .hex file |
| astrocde | Bally Astrocade | MAME - Current | MAME **(Standalone)** | Yes | Single archive or ROM file | | astrocde | Bally Astrocade | MAME - Current | MAME **(Standalone)** | Yes | Single archive or ROM file |
| atari2600 | Atari 2600 | Stella | Stella 2014,<br>Stella **(Standalone)**,<br>Gopher2600 **(Standalone)** [LW],<br>ares **(Standalone)** | No | Single archive or ROM file | | atari2600 | Atari 2600 | Stella | Stella 2014,<br>Stella 2023,<br>Stella **(Standalone)**,<br>Gopher2600 **(Standalone)** [LW],<br>ares **(Standalone)** | No | Single archive or ROM file |
| atari5200 | Atari 5200 | a5200 | Atari800,<br>Atari800 **(Standalone)**,<br>Altirra **(Standalone)** [W] | Yes except for Altirra | Single archive or ROM file | | atari5200 | Atari 5200 | a5200 | Atari800,<br>Atari800 **(Standalone)**,<br>Altirra **(Standalone)** [W] | Yes except for Altirra | Single archive or ROM file |
| atari7800 | Atari 7800 ProSystem | ProSystem | MAME - Current,<br>MAME **(Standalone)** | Yes | Single archive or ROM file | | atari7800 | Atari 7800 ProSystem | ProSystem | MAME - Current,<br>MAME **(Standalone)** | Yes | Single archive or ROM file |
| atari800 | Atari 800 | Atari800 | Atari800 **(Standalone)**,<br>Altirra **(Standalone)** [W] | Yes except for Altirra | | | atari800 | Atari 800 | Atari800 | Atari800 **(Standalone)**,<br>Altirra **(Standalone)** [W] | Yes except for Altirra | |
@ -4071,30 +4124,30 @@ The **@** symbol indicates that the emulator is _deprecated_ and will be removed
| daphne | Daphne Arcade LaserDisc Emulator | Hypseus [Daphne] **(Standalone)** | Hypseus [Singe] **(Standalone)**,<br>MAME - Current,<br>MAME **(Standalone)**,<br>DirkSimple | Depends | See the specific _LaserDisc Games_ section elsewhere in this guide | | daphne | Daphne Arcade LaserDisc Emulator | Hypseus [Daphne] **(Standalone)** | Hypseus [Singe] **(Standalone)**,<br>MAME - Current,<br>MAME **(Standalone)**,<br>DirkSimple | Depends | See the specific _LaserDisc Games_ section elsewhere in this guide |
| desktop | Desktop Applications | _Suspend ES-DE_ | _Keep ES-DE running_,<br> _AppImage (Suspend ES-DE)_ [L],<br> _AppImage (Keep ES-DE running)_ [L] | No | See the specific _Ports and desktop applications_ section elsewhere in this guide | | desktop | Desktop Applications | _Suspend ES-DE_ | _Keep ES-DE running_,<br> _AppImage (Suspend ES-DE)_ [L],<br> _AppImage (Keep ES-DE running)_ [L] | No | See the specific _Ports and desktop applications_ section elsewhere in this guide |
| doom | Doom | PrBoom | PrBoom+ **(Standalone)**,<br>Boom 3 [LW],<br>Boom 3 xp [LW],<br> _Shortcut or script_ | No | | | doom | Doom | PrBoom | PrBoom+ **(Standalone)**,<br>Boom 3 [LW],<br>Boom 3 xp [LW],<br> _Shortcut or script_ | No | |
| dos | DOS (PC) | DOSBox-Pure | DOSBox-Core,<br>DOSBox-SVN,<br>DOSBox-X **(Standalone)**,<br>DOSBox Staging **(Standalone)** | No | See the specific _DOS / PC_ section elsewhere in this guide | | dos | DOS (PC) | DOSBox-Pure | DOSBox-Core,<br>DOSBox-SVN,<br>DOSBox-X **(Standalone)**,<br>DOSBox Staging **(Standalone)**,<br>VirtualXT | No | See the specific _DOS / PC_ section elsewhere in this guide |
| dragon32 | Dragon Data Dragon 32 | XRoar Dragon 32 **(Standalone)** | XRoar Dragon 64 **(Standalone)** | Yes | See the specific _Dragon 32 and Tano Dragon_ section elsewhere in this guide | | dragon32 | Dragon Data Dragon 32 | MAME Dragon 32 [Tape] **(Standalone)** | MAME Dragon 32 [Cartridge] **(Standalone)**,<br>MAME Dragon 64 [Tape] **(Standalone)**,<br>MAME Dragon 64 [Cartridge] **(Standalone)**,<br>XRoar Dragon 32 **(Standalone)**,<br>XRoar Dragon 64 **(Standalone)** | Yes | See the specific _Dragon 32 and Tano Dragon_ section elsewhere in this guide |
| dreamcast | Sega Dreamcast | Flycast | Flycast **(Standalone)**,<br>Flycast Dojo **(Standalone)**,<br>Redream **(Standalone)**,<br>Demul **(Standalone)** [W] | No | In separate folder interpreted as a file, with .m3u playlist if multi-disc game | | dreamcast | Sega Dreamcast | Flycast | Flycast **(Standalone)**,<br>Flycast Dojo **(Standalone)**,<br>Redream **(Standalone)**,<br>Demul **(Standalone)** [W] | No | In separate folder interpreted as a file, with .m3u playlist if multi-disc game |
| easyrpg | EasyRPG Game Engine | EasyRPG | EasyRPG Player **(Standalone)** | No | See the specific _EasyRPG Game Engine_ section elsewhere in this guide | | easyrpg | EasyRPG Game Engine | EasyRPG | EasyRPG Player **(Standalone)** | No | See the specific _EasyRPG Game Engine_ section elsewhere in this guide |
| electron | Acorn Electron | MAME [Tape] **(Standalone)** | MAME [Diskette DFS] **(Standalone)**,<br>MAME [Diskette ADFS] **(Standalone)** | Yes | Single archive, or single tape or diskette image file | | electron | Acorn Electron | MAME [Tape] **(Standalone)** | MAME [Diskette DFS] **(Standalone)**,<br>MAME [Diskette ADFS] **(Standalone)** | Yes | Single archive, or single tape or diskette image file |
| emulators | Emulators | _Suspend ES-DE_ | _Keep ES-DE running_,<br> _AppImage (Suspend ES-DE)_ [L],<br> _AppImage (Keep ES-DE running)_ [L] | No | See the specific _Ports and desktop applications_ section elsewhere in this guide | | emulators | Emulators | _Suspend ES-DE_ | _Keep ES-DE running_,<br> _AppImage (Suspend ES-DE)_ [L],<br> _AppImage (Keep ES-DE running)_ [L] | No | See the specific _Ports and desktop applications_ section elsewhere in this guide |
| epic | Epic Games Store | Epic Games Store **(Standalone)** | | No | Shortcut (.desktop/.app/.lnk) file | | epic | Epic Games Store | Epic Games Store **(Standalone)** | | No | Shortcut (.desktop/.app/.lnk) file |
| famicom | Nintendo Family Computer | Mesen | Mesen **(Standalone)** [LW],<br>Nestopia UE,<br>Nestopia UE **(Standalone)** [L],<br>FCEUmm,<br>QuickNES,<br>puNES **(Standalone)** [LW],<br>Mednafen **(Standalone)**,<br>ares **(Standalone)**,<br>ares FDS **(Standalone)**,<br>3dSen **(Wine)** [L],<br>3dSen **(Proton)** [L],<br>3dSen **(Standalone)** [W] | No | Single archive or ROM file. For Famicom games in 3D see the specific _Nintendo NES and Famicom in 3D_ section elsewhere in this guide | | famicom | Nintendo Family Computer | Mesen | Mesen **(Standalone)** [LW],<br>Nestopia UE,<br>Nestopia UE **(Standalone)** [L],<br>FCEUmm,<br>QuickNES,<br>puNES **(Standalone)** [LW],<br>Mednafen **(Standalone)**,<br>ares **(Standalone)**,<br>ares FDS **(Standalone)**,<br>jgenesis **(Standalone)** [LW],<br>3dSen **(Wine)** [L],<br>3dSen **(Proton)** [L],<br>3dSen **(Standalone)** [W] | No | Single archive or ROM file. For Famicom games in 3D see the specific _Nintendo NES and Famicom in 3D_ section elsewhere in this guide |
| fba | FinalBurn Alpha | FB Alpha 2012 | FB Alpha 2012 Neo Geo,<br>FB Alpha 2012 CPS-1,<br>FB Alpha 2012 CPS-2,<br>FB Alpha 2012 CPS-3 | Yes | See the specific _Arcade and Neo Geo_ section elsewhere in this guide | | fba | FinalBurn Alpha | FB Alpha 2012 | FB Alpha 2012 Neo Geo,<br>FB Alpha 2012 CPS-1,<br>FB Alpha 2012 CPS-2,<br>FB Alpha 2012 CPS-3 | Yes | See the specific _Arcade and Neo Geo_ section elsewhere in this guide |
| fbneo | FinalBurn Neo | FinalBurn Neo | FinalBurn Neo **(Standalone)** [LW] | Yes | See the specific _Arcade and Neo Geo_ section elsewhere in this guide | | fbneo | FinalBurn Neo | FinalBurn Neo | FinalBurn Neo **(Standalone)** [LW] | Yes | See the specific _Arcade and Neo Geo_ section elsewhere in this guide |
| fds | Nintendo Famicom Disk System | Mesen | Mesen **(Standalone)** [LW],<br>Nestopia UE,<br>Nestopia UE **(Standalone)** [L],<br>FCEUmm,<br>Mednafen **(Standalone)**,<br>ares **(Standalone)** | Yes | Single archive or ROM file | | fds | Nintendo Famicom Disk System | Mesen | Mesen **(Standalone)** [LW],<br>Nestopia UE,<br>Nestopia UE **(Standalone)** [L],<br>FCEUmm,<br>Mednafen **(Standalone)**,<br>ares **(Standalone)** | Yes | Single archive or ROM file |
| flash | Adobe Flash | Ruffle **(Standalone)** | Lightspark **(Standalone)** [L],<br>ArcadeFlashWeb **(Standalone)** [W] | No | Single .swf file | | flash | Adobe Flash | Ruffle **(Standalone)** | Lightspark **(Standalone)** [L],<br>ArcadeFlashWeb **(Standalone)** [W] | No | Single .swf file |
| fm7 | Fujitsu FM-7 | MAME [FM-7 Diskette] **(Standalone)** | MAME [FM-7 Tape] **(Standalone)**,<br>MAME [FM-7 Software list] **(Standalone)**,<br>MAME [FM77AV Diskette] **(Standalone)**,<br>MAME [FM77AV Tape] **(Standalone)**,<br>MAME [FM77AV Software list] **(Standalone)** | Yes | For tape files you need to manually start the cassette player from the MAME menu after the "load" command, as well as entering the "run" command after loading is complete | | fm7 | Fujitsu FM-7 | MAME [FM-7 Diskette] **(Standalone)** | MAME [FM-7 Tape] **(Standalone)**,<br>MAME [FM-7 Software list] **(Standalone)**,<br>MAME [FM77AV Diskette] **(Standalone)**,<br>MAME [FM77AV Tape] **(Standalone)**,<br>MAME [FM77AV Software list] **(Standalone)** | Yes | For tape files you need to manually start the cassette player from the MAME menu after the "load" command, as well as entering the "run" command after loading is complete |
| fmtowns | Fujitsu FM Towns | MAME - Current,<br>MAME **(Standalone)** | Tsugaru **(Standalone)** [LW] | Yes | See the specific _Fujitsu FM Towns_ section elsewhere in this guide | | fmtowns | Fujitsu FM Towns | MAME - Current | MAME **(Standalone)**,<br>Tsugaru **(Standalone)** [LW] | Yes | See the specific _Fujitsu FM Towns_ section elsewhere in this guide |
| fpinball | Future Pinball | Future Pinball **(Standalone)** [W] | | No | | | fpinball | Future Pinball | Future Pinball **(Standalone)** [W] | | No | |
| gamate | Bit Corporation Gamate | MAME - Current | MAME **(Standalone)** | Yes | Single archive or ROM file | | gamate | Bit Corporation Gamate | MAME - Current | MAME **(Standalone)** | Yes | Single archive or ROM file |
| gameandwatch | Nintendo Game and Watch | MAME - Current | MAME Local Artwork **(Standalone)**,<br>MAME **(Standalone)**,<br>Handheld Electronic (GW) | No | See the specific _LCD handheld games_ section elsewhere in this guide | | gameandwatch | Nintendo Game and Watch | MAME - Current | MAME Local Artwork **(Standalone)**,<br>MAME **(Standalone)**,<br>Handheld Electronic (GW) | No | See the specific _LCD handheld games_ section elsewhere in this guide |
| gamecom | Tiger Electronics Game.com | MAME - Current | MAME **(Standalone)** | Yes | Single archive or ROM file | | gamecom | Tiger Electronics Game.com | MAME - Current | MAME **(Standalone)** | Yes | Single archive or ROM file |
| gamegear | Sega Game Gear | Genesis Plus GX | Genesis Plus GX Wide,<br>Gearsystem,<br>SMS Plus GX,<br>PicoDrive,<br>Mednafen **(Standalone)**,<br>Mesen **(Standalone)** [LW],<br>ares **(Standalone)** | No | Single archive or ROM file | | gamegear | Sega Game Gear | Genesis Plus GX | Genesis Plus GX Wide,<br>Gearsystem,<br>SMS Plus GX,<br>PicoDrive,<br>Mednafen **(Standalone)**,<br>Mesen **(Standalone)** [LW],<br>ares **(Standalone)**,<br>jgenesis **(Standalone)** [LW] | No | Single archive or ROM file |
| gb | Nintendo Game Boy | Gambatte | SameBoy,<br>SameBoy **(Standalone)**,<br>Gearboy,<br>Gearboy **(Standalone)** [LW],<br>TGB Dual,<br>DoubleCherryGB [LW],<br>Mesen-S,<br>Mesen **(Standalone)** [LW],<br>bsnes,<br>mGBA,<br>mGBA **(Standalone)**,<br>VBA-M,<br>VBA-M **(Standalone)**,<br>Mednafen **(Standalone)**,<br>ares **(Standalone)**,<br>SkyEmu **(Standalone)** | No | Single archive or ROM file | | gb | Nintendo Game Boy | Gambatte | SameBoy,<br>SameBoy **(Standalone)**,<br>Gearboy,<br>Gearboy **(Standalone)** [LW],<br>TGB Dual,<br>DoubleCherryGB [LW],<br>Mesen-S,<br>Mesen **(Standalone)** [LW],<br>bsnes,<br>mGBA,<br>mGBA **(Standalone)**,<br>VBA-M,<br>VBA-M **(Standalone)**,<br>Mednafen **(Standalone)**,<br>ares **(Standalone)**,<br>SkyEmu **(Standalone)**,<br>jgenesis **(Standalone)** [LW] | No | Single archive or ROM file |
| gba | Nintendo Game Boy Advance | mGBA | mGBA **(Standalone)**,<br>VBA-M,<br>VBA-M **(Standalone)**,<br>VBA Next,<br>gpSP,<br>Mednafen **(Standalone)**,<br>ares **(Standalone)**,<br>SkyEmu **(Standalone)** | Yes for ares | Single archive or ROM file | | gba | Nintendo Game Boy Advance | mGBA | mGBA **(Standalone)**,<br>VBA-M,<br>VBA-M **(Standalone)**,<br>VBA Next,<br>gpSP,<br>Mednafen **(Standalone)**,<br>ares **(Standalone)**,<br>SkyEmu **(Standalone)**,<br>NooDS **(Standalone)** [LW] | Yes for ares | Single archive or ROM file |
| gbc | Nintendo Game Boy Color | Gambatte | SameBoy,<br>SameBoy **(Standalone)**,<br>Gearboy,<br>Gearboy **(Standalone)** [LW],<br>TGB Dual,<br>DoubleCherryGB [LW],<br>Mesen-S,<br>Mesen **(Standalone)** [LW],<br>bsnes,<br>mGBA,<br>mGBA **(Standalone)**,<br>VBA-M,<br>VBA-M **(Standalone)**,<br>Mednafen **(Standalone)**,<br>ares **(Standalone)**,<br>SkyEmu **(Standalone)** | No | Single archive or ROM file | | gbc | Nintendo Game Boy Color | Gambatte | SameBoy,<br>SameBoy **(Standalone)**,<br>Gearboy,<br>Gearboy **(Standalone)** [LW],<br>TGB Dual,<br>DoubleCherryGB [LW],<br>Mesen-S,<br>Mesen **(Standalone)** [LW],<br>bsnes,<br>mGBA,<br>mGBA **(Standalone)**,<br>VBA-M,<br>VBA-M **(Standalone)**,<br>Mednafen **(Standalone)**,<br>ares **(Standalone)**,<br>SkyEmu **(Standalone)**,<br>jgenesis **(Standalone)** [LW] | No | Single archive or ROM file |
| gc | Nintendo GameCube | Dolphin | Dolphin **(Standalone)**,<br>PrimeHack **(Standalone)** [LW],<br>Triforce **(Standalone)** [LW] | No | Disc image file for single-disc games, .m3u playlist for multi-disc games | | gc | Nintendo GameCube | Dolphin | Dolphin **(Standalone)**,<br>PrimeHack **(Standalone)** [LW],<br>Triforce **(Standalone)** [LW] | No | Disc image file for single-disc games, .m3u playlist for multi-disc games |
| genesis | Sega Genesis | Genesis Plus GX | Genesis Plus GX Wide,<br>PicoDrive,<br>BlastEm,<br>BlastEm **(Standalone)** [L],<br>Mednafen **(Standalone)**,<br>ares **(Standalone)** | No | Single archive or ROM file | | genesis | Sega Genesis | Genesis Plus GX | Genesis Plus GX Wide,<br>PicoDrive,<br>BlastEm,<br>BlastEm **(Standalone)** [L],<br>Mednafen **(Standalone)**,<br>ares **(Standalone)**,<br>jgenesis **(Standalone)** [LW] | No | Single archive or ROM file |
| gmaster | Hartung Game Master | MAME - Current | MAME **(Standalone)** | Yes | Single archive or ROM file | | gmaster | Hartung Game Master | MAME - Current | MAME **(Standalone)** | Yes | Single archive or ROM file |
| gx4000 | Amstrad GX4000 | Caprice32 | CrocoDS,<br>MAME **(Standalone)** | No | Single archive or ROM file | | gx4000 | Amstrad GX4000 | Caprice32 | CrocoDS,<br>MAME **(Standalone)** | No | Single archive or ROM file |
| intellivision | Mattel Electronics Intellivision | FreeIntv | MAME - Current,<br>MAME **(Standalone)** | Yes | Single archive or ROM file | | intellivision | Mattel Electronics Intellivision | FreeIntv | MAME - Current,<br>MAME **(Standalone)** | Yes | Single archive or ROM file |
@ -4108,11 +4161,11 @@ The **@** symbol indicates that the emulator is _deprecated_ and will be removed
| macintosh | Apple Macintosh | MAME Mac SE Bootable **(Standalone)** | MAME Mac SE Boot Disk **(Standalone)**,<br>MAME Mac Plus Bootable **(Standalone)**,<br>MAME Mac Plus Boot Disk **(Standalone)**,<br>Basilisk II **(Standalone)**,<br>SheepShaver **(Standalone)** | Yes | See the specific _Apple Macintosh_ section elsewhere in this guide | | macintosh | Apple Macintosh | MAME Mac SE Bootable **(Standalone)** | MAME Mac SE Boot Disk **(Standalone)**,<br>MAME Mac Plus Bootable **(Standalone)**,<br>MAME Mac Plus Boot Disk **(Standalone)**,<br>Basilisk II **(Standalone)**,<br>SheepShaver **(Standalone)** | Yes | See the specific _Apple Macintosh_ section elsewhere in this guide |
| mame | Multiple Arcade Machine Emulator | MAME - Current | MAME 2010,<br>MAME 2003-Plus,<br>MAME 2003,<br>MAME 2000,<br>MAME **(Standalone)**,<br>FinalBurn Neo,<br>FinalBurn Neo **(Standalone)** [LW],<br>FB Alpha 2012,<br>Geolith,<br>Flycast,<br>Flycast **(Standalone)**,<br>Flycast Dojo **(Standalone)**,<br>Kronos [LW],<br>Model 2 Emulator **(Standalone)** [W],<br>Model 2 Emulator [Suspend ES-DE] **(Standalone)** [W],<br>Supermodel **(Standalone)** [LW],<br> _Shortcut or script_ | Depends | See the specific _Arcade and Neo Geo_ section elsewhere in this guide | | mame | Multiple Arcade Machine Emulator | MAME - Current | MAME 2010,<br>MAME 2003-Plus,<br>MAME 2003,<br>MAME 2000,<br>MAME **(Standalone)**,<br>FinalBurn Neo,<br>FinalBurn Neo **(Standalone)** [LW],<br>FB Alpha 2012,<br>Geolith,<br>Flycast,<br>Flycast **(Standalone)**,<br>Flycast Dojo **(Standalone)**,<br>Kronos [LW],<br>Model 2 Emulator **(Standalone)** [W],<br>Model 2 Emulator [Suspend ES-DE] **(Standalone)** [W],<br>Supermodel **(Standalone)** [LW],<br> _Shortcut or script_ | Depends | See the specific _Arcade and Neo Geo_ section elsewhere in this guide |
| mame-advmame | AdvanceMAME | AdvanceMAME **(Standalone)** [LW] | | Depends | See the specific _Arcade and Neo Geo_ section elsewhere in this guide | | mame-advmame | AdvanceMAME | AdvanceMAME **(Standalone)** [LW] | | Depends | See the specific _Arcade and Neo Geo_ section elsewhere in this guide |
| mastersystem | Sega Master System | Genesis Plus GX | Genesis Plus GX Wide,<br>SMS Plus GX,<br>Gearsystem,<br>PicoDrive,<br>Mednafen **(Standalone)**,<br>Mesen **(Standalone)** [LW],<br>ares **(Standalone)** | No | Single archive or ROM file | | mastersystem | Sega Master System | Genesis Plus GX | Genesis Plus GX Wide,<br>SMS Plus GX,<br>Gearsystem,<br>PicoDrive,<br>Mednafen **(Standalone)**,<br>Mesen **(Standalone)** [LW],<br>ares **(Standalone)**,<br>jgenesis **(Standalone)** [LW] | No | Single archive or ROM file |
| megacd | Sega Mega-CD | Genesis Plus GX | Genesis Plus GX Wide,<br>PicoDrive,<br>ares **(Standalone)** | Yes | | | megacd | Sega Mega-CD | Genesis Plus GX | Genesis Plus GX Wide,<br>PicoDrive,<br>ares **(Standalone)**,<br>jgenesis **(Standalone)** [LW] | Yes | |
| megacdjp | Sega Mega-CD [Japan] | Genesis Plus GX | Genesis Plus GX Wide,<br>PicoDrive,<br>ares **(Standalone)** | Yes | | | megacdjp | Sega Mega-CD [Japan] | Genesis Plus GX | Genesis Plus GX Wide,<br>PicoDrive,<br>ares **(Standalone)**,<br>jgenesis **(Standalone)** [LW] | Yes | |
| megadrive | Sega Mega Drive | Genesis Plus GX | Genesis Plus GX Wide,<br>PicoDrive,<br>BlastEm,<br>BlastEm **(Standalone)** [L],<br>Mednafen **(Standalone)**,<br>ares **(Standalone)** | No | Single archive or ROM file | | megadrive | Sega Mega Drive | Genesis Plus GX | Genesis Plus GX Wide,<br>PicoDrive,<br>BlastEm,<br>BlastEm **(Standalone)** [L],<br>Mednafen **(Standalone)**,<br>ares **(Standalone)**,<br>jgenesis **(Standalone)** [LW] | No | Single archive or ROM file |
| megadrivejp | Sega Mega Drive [Japan] | Genesis Plus GX | Genesis Plus GX Wide,<br>PicoDrive,<br>BlastEm,<br>BlastEm **(Standalone)** [L],<br>Mednafen **(Standalone)**,<br>ares **(Standalone)** | No | Single archive or ROM file | | megadrivejp | Sega Mega Drive [Japan] | Genesis Plus GX | Genesis Plus GX Wide,<br>PicoDrive,<br>BlastEm,<br>BlastEm **(Standalone)** [L],<br>Mednafen **(Standalone)**,<br>ares **(Standalone)**,<br>jgenesis **(Standalone)** [LW] | No | Single archive or ROM file |
| megaduck | Creatronic Mega Duck | SameDuck | MAME - Current,<br>MAME **(Standalone)** | No | Single archive or ROM file | | megaduck | Creatronic Mega Duck | SameDuck | MAME - Current,<br>MAME **(Standalone)** | No | Single archive or ROM file |
| mess | Multi Emulator Super System | MESS 2015 | | | | | mess | Multi Emulator Super System | MESS 2015 | | | |
| model2 | Sega Model 2 | Model 2 Emulator **(Standalone)** [W],<br>MAME - Current [LM] | Model 2 Emulator [Suspend ES-DE] **(Standalone)** [W],<br>MAME - Current [W],<br>MAME **(Standalone)**,<br>Model 2 Emulator **(Wine)** [L],<br>Model 2 Emulator **(Proton)** [L] | Yes for MAME | See the specific _Arcade and Neo Geo_ section elsewhere in this guide | | model2 | Sega Model 2 | Model 2 Emulator **(Standalone)** [W],<br>MAME - Current [LM] | Model 2 Emulator [Suspend ES-DE] **(Standalone)** [W],<br>MAME - Current [W],<br>MAME **(Standalone)**,<br>Model 2 Emulator **(Wine)** [L],<br>Model 2 Emulator **(Proton)** [L] | Yes for MAME | See the specific _Arcade and Neo Geo_ section elsewhere in this guide |
@ -4124,17 +4177,17 @@ The **@** symbol indicates that the emulator is _deprecated_ and will be removed
| msxturbor | MSX Turbo R | blueMSX | openMSX **(Standalone)**,<br>openMSX No Machine **(Standalone)** | Yes | | | msxturbor | MSX Turbo R | blueMSX | openMSX **(Standalone)**,<br>openMSX No Machine **(Standalone)** | Yes | |
| mugen | M.U.G.E.N Game Engine | Ikemen GO **(Standalone)** | | No | See the specific _M.U.G.E.N Game Engine_ section elsewhere in this guide | | mugen | M.U.G.E.N Game Engine | Ikemen GO **(Standalone)** | | No | See the specific _M.U.G.E.N Game Engine_ section elsewhere in this guide |
| multivision | Othello Multivision | Gearsystem | Mesen **(Standalone)** [LW] | No | Single archive or ROM file | | multivision | Othello Multivision | Gearsystem | Mesen **(Standalone)** [LW] | No | Single archive or ROM file |
| naomi | Sega NAOMI | Flycast | Flycast **(Standalone)**,<br>Flycast Dojo **(Standalone)**,<br>Demul **(Standalone)** [W] | Yes | Single archive file + .chd file in subdirectory if GD-ROM game |
| naomi2 | Sega NAOMI 2 | Flycast | Flycast **(Standalone)**,<br>Flycast Dojo **(Standalone)**,<br>Demul **(Standalone)** [W] | Yes | Single archive file + .chd file in subdirectory if GD-ROM game |
| naomigd | Sega NAOMI GD-ROM | Flycast | Flycast **(Standalone)**,<br>Flycast Dojo **(Standalone)** | Yes | Single archive file + .chd file in subdirectory if GD-ROM game |
| n3ds | Nintendo 3DS | Citra [LW],<br>Citra **(Standalone)** [M] | Citra 2018 [LW],<br>Citra **(Standalone)** [LW],<br>Lime3DS **(Standalone)**,<br>Panda3DS **(Standalone)** | No | Single ROM file | | n3ds | Nintendo 3DS | Citra [LW],<br>Citra **(Standalone)** [M] | Citra 2018 [LW],<br>Citra **(Standalone)** [LW],<br>Lime3DS **(Standalone)**,<br>Panda3DS **(Standalone)** | No | Single ROM file |
| n64 | Nintendo 64 | Mupen64Plus-Next | Mupen64Plus **(Standalone)**,<br>ParaLLEl N64,<br>simple64 **(Standalone)** [LW],<br>Rosalie's Mupen GUI **(Standalone)** [LW],<br>Project64 **(Standalone)** [W],<br>ares **(Standalone)**,<br>sixtyforce **(Standalone)** [M] | No | Single archive or ROM file | | n64 | Nintendo 64 | Mupen64Plus-Next | Mupen64Plus **(Standalone)**,<br>ParaLLEl N64,<br>simple64 **(Standalone)** [LW],<br>Rosalie's Mupen GUI **(Standalone)** [LW],<br>Project64 **(Standalone)** [W],<br>ares **(Standalone)**,<br>sixtyforce **(Standalone)** [M] | No | Single archive or ROM file |
| n64dd | Nintendo 64DD | ParaLLEl N64 [LW],<br>Mupen64Plus-Next [M] | Mupen64Plus-Next [LW],<br>ParaLLEl N64 [M],<br>Rosalie's Mupen GUI **(Standalone)** [LW],<br>ares **(Standalone)** | Yes | See the specific _Nintendo 64DD_ section elsewhere in this guide | | n64dd | Nintendo 64DD | ParaLLEl N64 [LW],<br>Mupen64Plus-Next [M] | Mupen64Plus-Next [LW],<br>ParaLLEl N64 [M],<br>Rosalie's Mupen GUI **(Standalone)** [LW],<br>ares **(Standalone)** | Yes | See the specific _Nintendo 64DD_ section elsewhere in this guide |
| nds | Nintendo DS | melonDS DS | melonDS @,<br>melonDS **(Standalone)**,<br>DeSmuME,<br>DeSmuME 2015,<br>DeSmuME **(Standalone)** [L],<br>SkyEmu **(Standalone)** | No | Single archive or ROM file | | naomi | Sega NAOMI | Flycast | Flycast **(Standalone)**,<br>Flycast Dojo **(Standalone)**,<br>Demul **(Standalone)** [W] | Yes | Single archive file + .chd file in subdirectory if GD-ROM game |
| naomi2 | Sega NAOMI 2 | Flycast | Flycast **(Standalone)**,<br>Flycast Dojo **(Standalone)**,<br>Demul **(Standalone)** [W] | Yes | Single archive file + .chd file in subdirectory if GD-ROM game |
| naomigd | Sega NAOMI GD-ROM | Flycast | Flycast **(Standalone)**,<br>Flycast Dojo **(Standalone)** | Yes | Single archive file + .chd file in subdirectory if GD-ROM game |
| nds | Nintendo DS | melonDS DS | melonDS @,<br>melonDS **(Standalone)**,<br>DeSmuME,<br>DeSmuME 2015,<br>DeSmuME **(Standalone)** [L],<br>SkyEmu **(Standalone)**,<br>NooDS **(Standalone)** [LW] | No | Single archive or ROM file |
| neogeo | SNK Neo Geo | FinalBurn Neo | FinalBurn Neo **(Standalone)** [LW],<br>Geolith,<br>MAME **(Standalone)** | Yes | See the specific _Arcade and Neo Geo_ section elsewhere in this guide | | neogeo | SNK Neo Geo | FinalBurn Neo | FinalBurn Neo **(Standalone)** [LW],<br>Geolith,<br>MAME **(Standalone)** | Yes | See the specific _Arcade and Neo Geo_ section elsewhere in this guide |
| neogeocd | SNK Neo Geo CD | NeoCD | FinalBurn Neo,<br>FinalBurn Neo **(Standalone)** [L],<br>MAME **(Standalone)** | Yes | .chd (NeoCD and MAME only) or .cue file | | neogeocd | SNK Neo Geo CD | NeoCD | FinalBurn Neo,<br>FinalBurn Neo **(Standalone)** [L],<br>MAME **(Standalone)** | Yes | .chd (NeoCD and MAME only) or .cue file |
| neogeocdjp | SNK Neo Geo CD [Japan] | NeoCD | FinalBurn Neo,<br>FinalBurn Neo **(Standalone)** [L],<br>MAME **(Standalone)** | Yes | .chd (NeoCD and MAME only) or .cue file | | neogeocdjp | SNK Neo Geo CD [Japan] | NeoCD | FinalBurn Neo,<br>FinalBurn Neo **(Standalone)** [L],<br>MAME **(Standalone)** | Yes | .chd (NeoCD and MAME only) or .cue file |
| nes | Nintendo Entertainment System | Mesen | Mesen **(Standalone)** [LW],<br>Nestopia UE,<br>Nestopia UE **(Standalone)** [L],<br>FCEUmm,<br>QuickNES,<br>puNES **(Standalone)** [LW],<br>Mednafen **(Standalone)**,<br>ares **(Standalone)**,<br>ares FDS **(Standalone)**,<br>3dSen **(Wine)** [L],<br>3dSen **(Proton)** [L],<br>3dSen **(Standalone)** [W] | No | Single archive or ROM file. For NES games in 3D see the specific _Nintendo NES and Famicom in 3D_ section elsewhere in this guide | | nes | Nintendo Entertainment System | Mesen | Mesen **(Standalone)** [LW],<br>Nestopia UE,<br>Nestopia UE **(Standalone)** [L],<br>FCEUmm,<br>QuickNES,<br>puNES **(Standalone)** [LW],<br>Mednafen **(Standalone)**,<br>ares **(Standalone)**,<br>ares FDS **(Standalone)**,<br>jgenesis **(Standalone)** [LW],<br>3dSen **(Wine)** [L],<br>3dSen **(Proton)** [L],<br>3dSen **(Standalone)** [W] | No | Single archive or ROM file. For NES games in 3D see the specific _Nintendo NES and Famicom in 3D_ section elsewhere in this guide |
| ngage | Nokia N-Gage | EKA2L1 [Mounted] **(Standalone)** | EKA2L1 [Installed] **(Standalone)**,<br>EKA2L1 [Mounted] **(Wine)** [L],<br>EKA2L1 [Installed] **(Wine)** [L] | Yes | See the specific _Symbian and Nokia N-Gage_ section elsewhere in this guide | | ngage | Nokia N-Gage | EKA2L1 [Mounted] **(Standalone)** | EKA2L1 [Installed] **(Standalone)**,<br>EKA2L1 [Mounted] **(Wine)** [L],<br>EKA2L1 [Installed] **(Wine)** [L] | Yes | See the specific _Symbian and Nokia N-Gage_ section elsewhere in this guide |
| ngp | SNK Neo Geo Pocket | Beetle NeoPop | RACE,<br>Mednafen **(Standalone)**,<br>ares **(Standalone)** | No | Single archive or ROM file | | ngp | SNK Neo Geo Pocket | Beetle NeoPop | RACE,<br>Mednafen **(Standalone)**,<br>ares **(Standalone)** | No | Single archive or ROM file |
| ngpc | SNK Neo Geo Pocket Color | Beetle NeoPop | RACE,<br>Mednafen **(Standalone)**,<br>ares **(Standalone)** | No | Single archive or ROM file | | ngpc | SNK Neo Geo Pocket Color | Beetle NeoPop | RACE,<br>Mednafen **(Standalone)**,<br>ares **(Standalone)** | No | Single archive or ROM file |
@ -4142,7 +4195,7 @@ The **@** symbol indicates that the emulator is _deprecated_ and will be removed
| openbor | OpenBOR Game Engine | OpenBOR **(Standalone)** [LW] | | No | See the specific _OpenBOR_ section elsewhere in this guide | | openbor | OpenBOR Game Engine | OpenBOR **(Standalone)** [LW] | | No | See the specific _OpenBOR_ section elsewhere in this guide |
| oric | Tangerine Computer Systems Oric | MAME **(Standalone)** | Oricutron **(Standalone)** | Yes | See the specific _Tangerine Computer Systems Oric_ section elsewhere in this guide | | oric | Tangerine Computer Systems Oric | MAME **(Standalone)** | Oricutron **(Standalone)** | Yes | See the specific _Tangerine Computer Systems Oric_ section elsewhere in this guide |
| palm | Palm OS | Mu | | | | | palm | Palm OS | Mu | | | |
| pc | IBM PC | DOSBox-Pure | DOSBox-Core,<br>DOSBox-SVN,<br>DOSBox-X **(Standalone)**,<br>DOSBox Staging **(Standalone)** | No | See the specific _DOS / PC_ section elsewhere in this guide | | pc | IBM PC | DOSBox-Pure | DOSBox-Core,<br>DOSBox-SVN,<br>DOSBox-X **(Standalone)**,<br>DOSBox Staging **(Standalone)**,<br>VirtualXT | No | See the specific _DOS / PC_ section elsewhere in this guide |
| pc88 | NEC PC-8800 Series | QUASI88 | QUASI88 **(Standalone)** | Yes | | | pc88 | NEC PC-8800 Series | QUASI88 | QUASI88 **(Standalone)** | Yes | |
| pc98 | NEC PC-9800 Series | Neko Project II Kai | Neko Project II | | | | pc98 | NEC PC-9800 Series | Neko Project II Kai | Neko Project II | | |
| pcarcade | PC Arcade Systems | Wine **(Standalone)** [L],<br> _Shortcut or script_ [MW] | Proton **(Standalone)** [L],<br> _AppImage_ [L],<br> _Shortcut or script_ [L] | No | | | pcarcade | PC Arcade Systems | Wine **(Standalone)** [L],<br> _Shortcut or script_ [MW] | Proton **(Standalone)** [L],<br> _AppImage_ [L],<br> _Shortcut or script_ [L] | No | |
@ -4170,12 +4223,12 @@ The **@** symbol indicates that the emulator is _deprecated_ and will be removed
| sega32x | Sega Mega Drive 32X | PicoDrive | ares **(Standalone)** | No | Single archive or ROM file | | sega32x | Sega Mega Drive 32X | PicoDrive | ares **(Standalone)** | No | Single archive or ROM file |
| sega32xjp | Sega Super 32X [Japan] | PicoDrive | ares **(Standalone)** | No | Single archive or ROM file | | sega32xjp | Sega Super 32X [Japan] | PicoDrive | ares **(Standalone)** | No | Single archive or ROM file |
| sega32xna | Sega Genesis 32X [North America] | PicoDrive | ares **(Standalone)** | No | Single archive or ROM file | | sega32xna | Sega Genesis 32X [North America] | PicoDrive | ares **(Standalone)** | No | Single archive or ROM file |
| segacd | Sega CD | Genesis Plus GX | Genesis Plus GX Wide,<br>PicoDrive,<br>ares **(Standalone)** | Yes | | | segacd | Sega CD | Genesis Plus GX | Genesis Plus GX Wide,<br>PicoDrive,<br>ares **(Standalone)**,<br>jgenesis **(Standalone)** [LW] | Yes | |
| sfc | Nintendo SFC (Super Famicom) | Snes9x - Current | Snes9x 2010,<br>Snes9x 2005 Plus,<br>Snes9x **(Standalone)**,<br>bsnes,<br>bsnes-hd,<br>bsnes-mercury Accuracy,<br>bsnes **(Standalone)** [LW],<br>Beetle Supafaust [LW],<br>Mesen-S,<br>Mesen **(Standalone)** [LW],<br>Mednafen **(Standalone)**,<br>ares **(Standalone)** | No | Single archive or ROM file | | sfc | Nintendo SFC (Super Famicom) | Snes9x - Current | Snes9x 2010,<br>Snes9x 2005 Plus,<br>Snes9x **(Standalone)**,<br>bsnes,<br>bsnes-hd,<br>bsnes-mercury Accuracy,<br>bsnes **(Standalone)** [LW],<br>Beetle Supafaust [LW],<br>Mesen-S,<br>Mesen **(Standalone)** [LW],<br>Mednafen **(Standalone)**,<br>ares **(Standalone)**,<br>jgenesis **(Standalone)** [LW] | No | Single archive or ROM file |
| sg-1000 | Sega SG-1000 | Genesis Plus GX | Genesis Plus GX Wide,<br>Gearsystem,<br>blueMSX,<br>Mesen **(Standalone)** [LW],<br>ares **(Standalone)** | No | Single archive or ROM file | | sg-1000 | Sega SG-1000 | Genesis Plus GX | Genesis Plus GX Wide,<br>Gearsystem,<br>blueMSX,<br>Mesen **(Standalone)** [LW],<br>ares **(Standalone)** | No | Single archive or ROM file |
| sgb | Nintendo Super Game Boy | Mesen-S | Mesen **(Standalone)** [LW],<br>SameBoy,<br>mGBA,<br>mGBA **(Standalone)** | | Single archive or ROM file | | sgb | Nintendo Super Game Boy | Mesen-S | Mesen **(Standalone)** [LW],<br>SameBoy,<br>mGBA,<br>mGBA **(Standalone)** | | Single archive or ROM file |
| snes | Nintendo SNES (Super Nintendo) | Snes9x - Current | Snes9x 2010,<br>Snes9x 2005 Plus,<br>Snes9x **(Standalone)**,<br>bsnes,<br>bsnes-hd,<br>bsnes-mercury Accuracy,<br>bsnes **(Standalone)** [LW],<br>Beetle Supafaust [LW],<br>Mesen-S,<br>Mesen **(Standalone)** [LW],<br>Mednafen **(Standalone)**,<br>ares **(Standalone)** | No | Single archive or ROM file | | snes | Nintendo SNES (Super Nintendo) | Snes9x - Current | Snes9x 2010,<br>Snes9x 2005 Plus,<br>Snes9x **(Standalone)**,<br>bsnes,<br>bsnes-hd,<br>bsnes-mercury Accuracy,<br>bsnes **(Standalone)** [LW],<br>Beetle Supafaust [LW],<br>Mesen-S,<br>Mesen **(Standalone)** [LW],<br>Mednafen **(Standalone)**,<br>ares **(Standalone)**,<br>jgenesis **(Standalone)** [LW] | No | Single archive or ROM file |
| snesna | Nintendo SNES (Super Nintendo) [North America] | Snes9x - Current | Snes9x 2010,<br>Snes9x 2005 Plus,<br>Snes9x **(Standalone)**,<br>bsnes,<br>bsnes-hd,<br>bsnes-mercury Accuracy,<br>bsnes **(Standalone)** [LW],<br>Beetle Supafaust [LW],<br>Mesen-S,<br>Mesen **(Standalone)** [LW],<br>Mednafen **(Standalone)**,<br>ares **(Standalone)** | No | Single archive or ROM file | | snesna | Nintendo SNES (Super Nintendo) [North America] | Snes9x - Current | Snes9x 2010,<br>Snes9x 2005 Plus,<br>Snes9x **(Standalone)**,<br>bsnes,<br>bsnes-hd,<br>bsnes-mercury Accuracy,<br>bsnes **(Standalone)** [LW],<br>Beetle Supafaust [LW],<br>Mesen-S,<br>Mesen **(Standalone)** [LW],<br>Mednafen **(Standalone)**,<br>ares **(Standalone)**,<br>jgenesis **(Standalone)** [LW] | No | Single archive or ROM file |
| solarus | Solarus Game Engine | Solarus **(Standalone)** | | No | Single .solarus game file | | solarus | Solarus Game Engine | Solarus **(Standalone)** | | No | Single .solarus game file |
| spectravideo | Spectravideo | blueMSX | | | | | spectravideo | Spectravideo | blueMSX | | | |
| steam | Valve Steam | Steam **(Standalone)** | | No | See the specific _Steam_ section elsewhere in this guide | | steam | Valve Steam | Steam **(Standalone)** | | No | See the specific _Steam_ section elsewhere in this guide |
@ -4184,9 +4237,9 @@ The **@** symbol indicates that the emulator is _deprecated_ and will be removed
| supergrafx | NEC SuperGrafx | Beetle SuperGrafx | Beetle PCE,<br>Mednafen **(Standalone)**,<br>Mesen **(Standalone)** [LW],<br>ares **(Standalone)** | No | Single archive or ROM file | | supergrafx | NEC SuperGrafx | Beetle SuperGrafx | Beetle PCE,<br>Mednafen **(Standalone)**,<br>Mesen **(Standalone)** [LW],<br>ares **(Standalone)** | No | Single archive or ROM file |
| supervision | Watara Supervision | Potator | MAME - Current,<br>MAME **(Standalone)** | No | Single archive or ROM file | | supervision | Watara Supervision | Potator | MAME - Current,<br>MAME **(Standalone)** | No | Single archive or ROM file |
| supracan | Funtech Super A'Can | MAME - Current | MAME **(Standalone)** | Yes | Single archive or ROM file. You need a supracan.zip archive that contains a valid internal_68k.bin file and an empty file named umc6650.bin | | supracan | Funtech Super A'Can | MAME - Current | MAME **(Standalone)** | Yes | Single archive or ROM file. You need a supracan.zip archive that contains a valid internal_68k.bin file and an empty file named umc6650.bin |
| switch | Nintendo Switch | Ryujinx **(Standalone)** | | Yes | | | switch | Nintendo Switch | Ryujinx **(Standalone)** | _Shortcut_ [W] | Yes | |
| symbian | Symbian | EKA2L1 [Nokia N-Gage] **(Standalone)** | EKA2L1 [Nokia N70] **(Standalone)**,<br>EKA2L1 [Nokia N97] **(Standalone)**,<br>EKA2L1 [Custom device] **(Standalone)** | Yes | See the specific _Symbian and Nokia N-Gage_ section elsewhere in this guide | | symbian | Symbian | EKA2L1 [Nokia N-Gage] **(Standalone)** | EKA2L1 [Nokia N70] **(Standalone)**,<br>EKA2L1 [Nokia N97] **(Standalone)**,<br>EKA2L1 [Custom device] **(Standalone)** | Yes | See the specific _Symbian and Nokia N-Gage_ section elsewhere in this guide |
| tanodragon | Tano Dragon | XRoar **(Standalone)** | | Yes | See the specific _Dragon 32 and Tano Dragon_ section elsewhere in this guide | | tanodragon | Tano Dragon | MAME [Tape] **(Standalone)** | MAME [Cartridge] **(Standalone)**,<br>XRoar **(Standalone)** | Yes | See the specific _Dragon 32 and Tano Dragon_ section elsewhere in this guide |
| tg16 | NEC TurboGrafx-16 | Beetle PCE | Beetle PCE FAST,<br>Beetle SuperGrafx,<br>Mednafen **(Standalone)**,<br>Mesen **(Standalone)** [LW],<br>ares **(Standalone)** | No | Single archive or ROM file | | tg16 | NEC TurboGrafx-16 | Beetle PCE | Beetle PCE FAST,<br>Beetle SuperGrafx,<br>Mednafen **(Standalone)**,<br>Mesen **(Standalone)** [LW],<br>ares **(Standalone)** | No | Single archive or ROM file |
| tg-cd | NEC TurboGrafx-CD | Beetle PCE | Beetle PCE FAST,<br>Beetle SuperGrafx,<br>Mednafen **(Standalone)**,<br>Mesen **(Standalone)** [LW],<br>ares **(Standalone)** | Yes | | | tg-cd | NEC TurboGrafx-CD | Beetle PCE | Beetle PCE FAST,<br>Beetle SuperGrafx,<br>Mednafen **(Standalone)**,<br>Mesen **(Standalone)** [LW],<br>ares **(Standalone)** | Yes | |
| ti99 | Texas Instruments TI-99 | MAME **(Standalone)** | | Yes | See the specific _Texas Instruments TI-99_ section elsewhere in this guide | | ti99 | Texas Instruments TI-99 | MAME **(Standalone)** | | Yes | See the specific _Texas Instruments TI-99_ section elsewhere in this guide |
@ -4198,7 +4251,7 @@ The **@** symbol indicates that the emulator is _deprecated_ and will be removed
| uzebox | Uzebox Open Source Console | Uzem | | | | | uzebox | Uzebox Open Source Console | Uzem | | | |
| vectrex | GCE Vectrex | vecx | MAME - Current,<br>MAME **(Standalone)** | Yes for MAME | Single archive or ROM file | | vectrex | GCE Vectrex | vecx | MAME - Current,<br>MAME **(Standalone)** | Yes for MAME | Single archive or ROM file |
| vic20 | Commodore VIC-20 | VICE xvic | VICE xvic **(Standalone)** | No | Single archive or tape, cartridge or diskette image file | | vic20 | Commodore VIC-20 | VICE xvic | VICE xvic **(Standalone)** | No | Single archive or tape, cartridge or diskette image file |
| videopac | Philips Videopac G7000 | O2EM | MAME - Current,<br>MAME **(Standalone)** | Yes | Single archive or ROM file | | | videopac | Philips Videopac G7000 | O2EM | MAME - Current,<br>MAME **(Standalone)** | Yes | Single archive or ROM file |
| virtualboy | Nintendo Virtual Boy | Beetle VB | Mednafen **(Standalone)** | No | | | virtualboy | Nintendo Virtual Boy | Beetle VB | Mednafen **(Standalone)** | No | |
| vpinball | Visual Pinball | Visual Pinball **(Standalone)** | | No | See the specific _Visual Pinball_ section elsewhere in this guide | | vpinball | Visual Pinball | Visual Pinball **(Standalone)** | | No | See the specific _Visual Pinball_ section elsewhere in this guide |
| vsmile | VTech V.Smile | MAME - Current | MAME **(Standalone)** | Yes | Single archive or ROM file | | vsmile | VTech V.Smile | MAME - Current | MAME **(Standalone)** | Yes | Single archive or ROM file |
@ -4215,6 +4268,6 @@ The **@** symbol indicates that the emulator is _deprecated_ and will be removed
| xbox | Microsoft Xbox | xemu **(Standalone)** | Cxbx-Reloaded **(Standalone)** [W] | Yes for xemu | Single .iso file for xemu or unpacked .iso directory for Cxbx-Reloaded | | xbox | Microsoft Xbox | xemu **(Standalone)** | Cxbx-Reloaded **(Standalone)** [W] | Yes for xemu | Single .iso file for xemu or unpacked .iso directory for Cxbx-Reloaded |
| xbox360 | Microsoft Xbox 360 | xenia **(Standalone)** [W],<br>xenia **(Wine)** [L] | xenia **(Proton)** [L],<br> _Shortcut or script_ [L] | No | See the specific _Microsoft Xbox 360_ section elsewhere in this guide | | xbox360 | Microsoft Xbox 360 | xenia **(Standalone)** [W],<br>xenia **(Wine)** [L] | xenia **(Proton)** [L],<br> _Shortcut or script_ [L] | No | See the specific _Microsoft Xbox 360_ section elsewhere in this guide |
| zmachine | Infocom Z-machine | MojoZork | Gargoyle **(Standalone)** | No | | | zmachine | Infocom Z-machine | MojoZork | Gargoyle **(Standalone)** | No | |
| zx81 | Sinclair ZX81 | EightyOne | | | | | zx81 | Sinclair ZX81 | EightyOne | | No | |
| zxnext | Sinclair ZX Spectrum Next | #CSpect **(Standalone)** [LW],<br>ZEsarUX **(Standalone)** [M] | ZEsarUX **(Standalone)** [LW] | No | In separate folder interpreted as a file | | zxnext | Sinclair ZX Spectrum Next | #CSpect **(Standalone)** [LW],<br>ZEsarUX **(Standalone)** [M] | ZEsarUX **(Standalone)** [LW] | No | In separate folder interpreted as a file |
| zxspectrum | Sinclair ZX Spectrum | Fuse | Fuse **(Standalone)** | No | Single archive or ROM file | | zxspectrum | Sinclair ZX Spectrum | Fuse | Fuse **(Standalone)** | No | Single archive or ROM file |

View file

@ -161,8 +161,14 @@ if(WIN32)
../freetype.dll ../freetype.dll
../git2.dll ../git2.dll
../glew32.dll ../glew32.dll
../harfbuzz.dll
../icudt75.dll
../icuin75.dll
../icuuc75.dll
../libcrypto-3-x64.dll ../libcrypto-3-x64.dll
../libcurl-x64.dll ../libcurl-x64.dll
../libiconv-2.dll
../libintl-8.dll
../libssl-3-x64.dll ../libssl-3-x64.dll
../lunasvg.dll ../lunasvg.dll
../pugixml.dll ../pugixml.dll
@ -236,6 +242,16 @@ elseif(APPLE)
PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS)
install(FILES ${CMAKE_SOURCE_DIR}/libgit2.1.7.dylib install(FILES ${CMAKE_SOURCE_DIR}/libgit2.1.7.dylib
PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS)
install(FILES ${CMAKE_SOURCE_DIR}/libharfbuzz.dylib
PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS)
install(FILES ${CMAKE_SOURCE_DIR}/libicudata.75.dylib
PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS)
install(FILES ${CMAKE_SOURCE_DIR}/libicui18n.75.dylib
PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS)
install(FILES ${CMAKE_SOURCE_DIR}/libicuuc.75.dylib
PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS)
install(FILES ${CMAKE_SOURCE_DIR}/libintl.8.dylib
PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS)
install(FILES ${CMAKE_SOURCE_DIR}/libjpeg.62.dylib install(FILES ${CMAKE_SOURCE_DIR}/libjpeg.62.dylib
PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS)
install(FILES ${CMAKE_SOURCE_DIR}/libopenjp2.7.dylib install(FILES ${CMAKE_SOURCE_DIR}/libopenjp2.7.dylib
@ -255,10 +271,27 @@ elseif(APPLE)
install(DIRECTORY ${CMAKE_SOURCE_DIR}/themes/modern-es-de DESTINATION ../Resources/themes) install(DIRECTORY ${CMAKE_SOURCE_DIR}/themes/modern-es-de DESTINATION ../Resources/themes)
install(DIRECTORY ${CMAKE_SOURCE_DIR}/themes/slate-es-de DESTINATION ../Resources/themes) install(DIRECTORY ${CMAKE_SOURCE_DIR}/themes/slate-es-de DESTINATION ../Resources/themes)
install(DIRECTORY ${CMAKE_SOURCE_DIR}/licenses DESTINATION ../Resources) install(DIRECTORY ${CMAKE_SOURCE_DIR}/licenses DESTINATION ../Resources)
elseif(HAIKU)
install(TARGETS es-de RUNTIME DESTINATION apps)
install(TARGETS es-pdf-convert RUNTIME DESTINATION apps)
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/assets/es-de.6.gz
DESTINATION documentation/man/man6)
install(FILES ${CMAKE_SOURCE_DIR}/LICENSE
DESTINATION data/es-de)
install(DIRECTORY ${CMAKE_SOURCE_DIR}/licenses
DESTINATION data/es-de)
install(DIRECTORY ${CMAKE_SOURCE_DIR}/themes/linear-es-de
DESTINATION data/es-de/themes)
install(DIRECTORY ${CMAKE_SOURCE_DIR}/themes/modern-es-de
DESTINATION data/es-de/themes)
install(DIRECTORY ${CMAKE_SOURCE_DIR}/themes/slate-es-de
DESTINATION data/es-de/themes)
install(DIRECTORY ${CMAKE_SOURCE_DIR}/resources
DESTINATION data/es-de)
elseif(NOT ANDROID) elseif(NOT ANDROID)
install(TARGETS es-de RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) install(TARGETS es-de RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/bin)
install(TARGETS es-pdf-convert RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) install(TARGETS es-pdf-convert RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/bin)
if(CMAKE_SYSTEM_NAME MATCHES Linux) if(CMAKE_SYSTEM_NAME MATCHES Linux OR CMAKE_SYSTEM_NAME MATCHES FreeBSD)
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/assets/es-de.6.gz install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/assets/es-de.6.gz
DESTINATION ${CMAKE_INSTALL_PREFIX}/share/man/man6) DESTINATION ${CMAKE_INSTALL_PREFIX}/share/man/man6)
else() else()
@ -306,7 +339,7 @@ elseif(WIN32)
else() else()
set(CPACK_RESOURCE_FILE_LICENSE ${CMAKE_SOURCE_DIR}/LICENSE) set(CPACK_RESOURCE_FILE_LICENSE ${CMAKE_SOURCE_DIR}/LICENSE)
endif() endif()
set(CPACK_PACKAGE_VENDOR "Leon Styhre") set(CPACK_PACKAGE_VENDOR "Northwestern Software AB")
# Use the shorter x64 descriptor for the x86_64/AMD64 architecture. # Use the shorter x64 descriptor for the x86_64/AMD64 architecture.
if(CMAKE_SYSTEM_PROCESSOR MATCHES x86_64 OR CMAKE_SYSTEM_PROCESSOR MATCHES AMD64) if(CMAKE_SYSTEM_PROCESSOR MATCHES x86_64 OR CMAKE_SYSTEM_PROCESSOR MATCHES AMD64)
@ -352,7 +385,7 @@ else()
set(CPACK_GENERATOR DEB) set(CPACK_GENERATOR DEB)
endif() endif()
set(CPACK_DEBIAN_FILE_NAME es-de_${CPACK_PACKAGE_VERSION}-${CPU_ARCHITECTURE}.deb) set(CPACK_DEBIAN_FILE_NAME es-de_${CPACK_PACKAGE_VERSION}-${CPU_ARCHITECTURE}.deb)
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Leon Styhre <info@es-de.org>") set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Leon Styhre <leon.styhre@nw-soft.com>")
set(CPACK_DEBIAN_PACKAGE_HOMEPAGE https://es-de.org) set(CPACK_DEBIAN_PACKAGE_HOMEPAGE https://es-de.org)
set(CPACK_DEBIAN_PACKAGE_SECTION games) set(CPACK_DEBIAN_PACKAGE_SECTION games)
set(CPACK_DEBIAN_PACKAGE_PRIORITY optional) set(CPACK_DEBIAN_PACKAGE_PRIORITY optional)

View file

@ -24,7 +24,7 @@ BEGIN
VALUE "FileDescription", "ES-DE\0" VALUE "FileDescription", "ES-DE\0"
VALUE "FileVersion", RESOURCE_VERSION_STRING VALUE "FileVersion", RESOURCE_VERSION_STRING
VALUE "InternalName", "ES-DE.exe\0" VALUE "InternalName", "ES-DE.exe\0"
VALUE "LegalCopyright", "Copyright (c) 2020-2024 Leon Styhre\0" VALUE "LegalCopyright", "Copyright (c) 2024 Northwestern Software AB\0"
VALUE "LegalTrademarks", "\0" VALUE "LegalTrademarks", "\0"
VALUE "OriginalFilename", "ES-DE.exe\0" VALUE "OriginalFilename", "ES-DE.exe\0"
VALUE "ProductName", "ES-DE\0" VALUE "ProductName", "ES-DE\0"

View file

@ -3,7 +3,7 @@
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>
<string>3.0.3</string> <string>3.1.1-alpha</string>
<key>CFBundleDevelopmentRegion</key> <key>CFBundleDevelopmentRegion</key>
<string>English</string> <string>English</string>
<key>CFBundleDisplayName</key> <key>CFBundleDisplayName</key>
@ -11,7 +11,7 @@
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>ES-DE</string> <string>ES-DE</string>
<key>CFBundleGetInfoString</key> <key>CFBundleGetInfoString</key>
<string>ES-DE 3.0.3</string> <string>ES-DE 3.1.1</string>
<key>CFBundleIconFile</key> <key>CFBundleIconFile</key>
<string>ES-DE.icns</string> <string>ES-DE.icns</string>
<key>CFBundleName</key> <key>CFBundleName</key>
@ -21,9 +21,9 @@
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>ESDE</string> <string>ESDE</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>3.0.3</string> <string>3.1.1-alpha</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>3.0.3</string> <string>3.1.1-alpha</string>
<key>CFBundleInfoDictionaryVersion</key> <key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string> <string>6.0</string>
<key>LSApplicationCategoryType</key> <key>LSApplicationCategoryType</key>
@ -39,7 +39,8 @@
<key>NSMainNibFile</key> <key>NSMainNibFile</key>
<string>ES-DE</string> <string>ES-DE</string>
<key>NSHumanReadableCopyright</key> <key>NSHumanReadableCopyright</key>
<string>Copyright (c) 2020-2024 Leon Styhre <string>Copyright (c) 2024 Northwestern Software AB
Copyright (c) 2020-2024 Leon Styhre
Copyright (c) 2014 Alec Lofquist Copyright (c) 2014 Alec Lofquist
Licensed under the MIT license</string> Licensed under the MIT license</string>
</dict> </dict>

View file

@ -1,3 +1,4 @@
Copyright (c) 2024 Northwestern Software AB
Copyright (c) 2020-2024 Leon Styhre Copyright (c) 2020-2024 Leon Styhre
Copyright (c) 2014 Alec Lofquist Copyright (c) 2014 Alec Lofquist

View file

@ -1,3 +1,4 @@
Copyright (c) 2024 Northwestern Software AB
Copyright (c) 2020-2024 Leon Styhre Copyright (c) 2020-2024 Leon Styhre
Copyright (c) 2014 Alec Lofquist Copyright (c) 2014 Alec Lofquist

View file

@ -2,7 +2,7 @@ ES-DE Frontend - Portable installation on Windows
------------------------------------------------- -------------------------------------------------
ES-DE release: ES-DE release:
3.0.3 3.1.1-alpha
The latest version can be downloaded from https://es-de.org The latest version can be downloaded from https://es-de.org
@ -81,8 +81,10 @@ Emulators\Gearboy\Gearboy.exe
Emulators\gopher2600\gopher2600_windows_amd64.exe Emulators\gopher2600\gopher2600_windows_amd64.exe
Emulators\hatari\hatari.exe Emulators\hatari\hatari.exe
Emulators\Hypseus Singe\hypseus.exe Emulators\Hypseus Singe\hypseus.exe
Emulators\izapple2\izapple2sdl_windows_amd64.exe
Emulators\jgenesis\jgenesis-cli.exe
Emulators\KEmulator\KEmulator.exe Emulators\KEmulator\KEmulator.exe
Emulators\lime3ds\lime3ds-gui.exe Emulators\lime3ds\lime3ds.exe
Emulators\m2emulator\EMULATOR.EXE Emulators\m2emulator\EMULATOR.EXE
Emulators\mame\mame.exe Emulators\mame\mame.exe
Emulators\mednafen\mednafen.exe Emulators\mednafen\mednafen.exe
@ -90,6 +92,7 @@ Emulators\melonDS\melonDS.exe
Emulators\Mesen\Mesen.exe Emulators\Mesen\Mesen.exe
Emulators\mGBA\mGBA.exe Emulators\mGBA\mGBA.exe
Emulators\mupen64plus\mupen64plus-ui-console.exe Emulators\mupen64plus\mupen64plus-ui-console.exe
Emulators\noods\noods.exe
Emulators\openMSX\openmsx.exe Emulators\openMSX\openmsx.exe
Emulators\Oricutron\oricutron.exe Emulators\Oricutron\oricutron.exe
Emulators\Panda3DS\Alber.exe Emulators\Panda3DS\Alber.exe

Binary file not shown.

View file

@ -0,0 +1,65 @@
resource app_flags B_SINGLE_LAUNCH;
resource app_version {
major = @MAJOR@,
middle = @MIDDLE@,
minor = @MINOR@,
variety = B_APPV_FINAL,
internal = 0,
short_info = "ES-DE",
long_info = "ES-DE Frontend"
};
resource app_signature "@APP_SIGNATURE@";
resource vector_icon {
$"6E6369660503A0001C010004063F05FE04003F05FF05020DCC7DB781CC7DB781"
$"CC7DB721CBCEB6D3CC2EB6D3C49DB6D3B63BB6D3BD6CB6D3B5DBB6D3B510B74E"
$"B554B70AB489B7D5B37BB8E3B402B85CB337B92720BA0E20B9AE20BEB220C7FB"
$"204920C85BB3AEC8A9B34EC8A9BADFC8A9C941C8A9C210C8A9C9A1C8A9CA6CC8"
$"2ECA28C872CAF3C7A7CC01C699CB7AC720CC45C655CC7CC56ECC7CC5CECC7CC0"
$"CACC7DB781CC7DBC26CC7DB7810217C974C089C974C089C8EDC002C7DF3EC866"
$"BF7BC79BBEB0C6B5BE78C715BE78C650BE78C586BE78C5EBBE78C526BE78C4D7"
$"BE78C4D7BE78C4D7BE78C553BDFCC50FBE40C6B3BC9CC974B9DBC814BB3BC9B8"
$"B997C941B95FC9A1B95FC729B95FC2F9B95FC511B95FC299B95FC1CFB9DBC213"
$"B997BD0ABEA0B380C82AB845C365B363C846B333C876B348C861B353C896B3AE"
$"C8AAB37EC8AAB3AEC8AAB3AFC8AAB3AEC8AAB5BBC8AAB9D4C8AAB7C7C8AABD45"
$"C8AAC428C8AAC0B7C8AA4CC8AAC4D7C8A9C4D7C8A9C4D8C8A9C553C82DC50FC8"
$"71C6B4C6CDC974C40CC814C56DC9B8C3C8C9F0C2E2C9F0C342C9F0C27DC9F0C1"
$"B3C9F0C218C9F0C153C974C089C9B8C0CDC974C089C974C089C974C089C974C0"
$"890222C2F9C104C2F9C104C299C104C1CFC089C213C0CDC148C002C03A3EC0C1"
$"BF7BBFF6BEB0BFBEBDC9BFBFBE2ABFBEBD64BFBEBC9ABFBEBCFFBFBEBC3AC03A"
$"BB70BFF6BBB4C0C1BAE9C1CFB9DBC148BA62C213B997C2F9B95FC299B95FC511"
$"B95FC941B95FC729B95FC9A1B95FC974B9DBC9B8B997C8EDBA62C7DFBB70C866"
$"BAE9C79BBBB4C6B5BBECC715BBECC576BBECC2F9BBECC438BBECC299BBECC24B"
$"BC9AC24BBC3AC24BBCFFC24BBDC9C24BBD64C24BBE2AC2F9BE78C299BE78C438"
$"BE78C6B5BE78C576BE78C715BE78C7DF3EC79BBEB0C866BF7BC974C089C8EDC0"
$"02C9B8C0CDC9F0C1B3C9F0C153C9F0C218C9F0C2E2C9F0C27DC9F0C342C974C4"
$"0CC9B8C3C8C8EDC493C7DFC5A2C866C51BC79BC5E6C6B5C61DC715C61DC576C6"
$"1DC2F9C61DC438C61DC299C61DC1CFC5A2C213C5E6C148C51BC03AC40CC0C1C4"
$"93BFF6C3C8C06DC391C00DC391C285C391C6B5C391C49DC391C715C391C764C2"
$"E2C764C342C764C27DC764C1B3C764C218C764C153C764C104C764C104C764C1"
$"04C6B5C104C715C104C576C104C2F9C104C438C104C2F9C104021B20BF3020BF"
$"3020BED020BE7C20BE7F20BE7AB37BBDFCB337BE40B4DCBC9CB79DB9DBB63CBB"
$"3BB7E1B997B8C7B95FB867B95FBADFB95FBF0FB95FBCF7B95FBF6FB95FBF42B9"
$"DBBF86B997BDE2BB3BBB21BDFCBC81BC9CBADDBE40BAA5BE78BAA5BE78BAA5BE"
$"78BB54BE78BAF4BE78BBB9BE78BC83BE78BC1EBE78BCE3BE78BD25BF27BD2CBE"
$"C7BD1FBF78BD13C01CBD19BFCABD0DC07CBCE7C0EBBCF9C0D9BCD5C0FCBC4BC1"
$"86BC8FC142BBC7C20BBABEC313BB43C28FBA7AC357BA42C390BA42C38FBA41C3"
$"90BAF0C391BA8FC391BB4EC391BC0BC391BBACC391BC6BC391BD3AC407BCF3C3"
$"C6BDC7C489BEE0C58CBE54C50ABF27C5CDBF5FC606BF61C604BF5DC608BEE0C6"
$"85BF24C641BE29C73CBCBCC8A9BD73C7F3B9B7C8AAB3AFC8AAB6B3C8AAB34EC8"
$"AA20C7FB20C85B20C50C20BF3020C21E20BF30021DB8C7C391B8C7C391B867C3"
$"91B819C2E2B819C342B819C27DB819C1B3B819C218B819C153B8C7C104B867C1"
$"04BA06C104BC83C104BB44C104BCE3C104BD31C055BD31C0B6BD31BFF1BD31BF"
$"27BD31BF8CBD31BEC7BC83BE78BCE3BE78BB44BE78B8C7BE78BA06BE78B867BE"
$"78B819BDC9B819BE2AB819BD64B819BC9AB819BCFFB819BC3AB8C7BBECB867BB"
$"ECBA06BBECBC83BBECBB44BBECBCE3BBECBDADBB70BD69BBB4BE34BAE9BF42B9"
$"DBBEBBBA62BF86B997BF0FB95FBF6FB95FBCF7B95FB8C7B95FBADFB95FB867B9"
$"5FB79DB9DBB7E1B997B716BA62B608BB70B68FBAE9B5C4BBB4B58CBC9AB58CBC"
$"3AB58CBEB2B58CC2E2B58CC0CAB58CC342B608C40CB5C4C3C8B68FC494B79DC5"
$"A2B716C51BB7E1C5E6B8C7C61DB867C61DBADFC61DBF0FC61DBCF7C61DBF6FC6"
$"1DBF42C5A2BF86C5E6BEBBC51BBDADC40CBE34C493BD69C3C8BC83C391BCE3C3"
$"91BB44C391B8C7C391BA06C391B8C7C391050A000100000A010101000A020102"
$"000A030103000A04010400"
};

View file

@ -0,0 +1,93 @@
SUMMARY="Gaming frontend for your multi-platform collection"
DESCRIPTION="ES-DE (EmulationStation Desktop Edition) is a frontend for browsing and launching games from your \
multi-platform collection. It comes preconfigured for use with a large selection \
of emulators and game engines."
HOMEPAGE="https://es-de.org"
COPYRIGHT="2024 Northwestern Software AB"
LICENSE="MIT"
REVISION="1"
srcGitRev="842c9966eb73efb3436a4df2dfdd66063ce7361c"
SOURCE_URI="https://gitlab.com/es-de/emulationstation-de/-/archive/$srcGitRev/emulationstation-de-$srcGitRev.tar.gz"
CHECKSUM_SHA256="52c820beddba7e08014f589120c087d6b84b79a88b6213bf13a4e025fd728285"
SOURCE_FILENAME="emulationstation-de-$portVersion-$srcGitRev.tar.gz"
SOURCE_DIR="emulationstation-de-$srcGitRev"
ARCHITECTURES="all !x86_gcc2"
SECONDARY_ARCHITECTURES="x86"
PROVIDES="
es_de = $portVersion
app:es_de = $portVersion
"
REQUIRES="
haiku$secondaryArchSuffix
lib:libGL$secondaryArchSuffix
lib:libavcodec$secondaryArchSuffix
lib:libavformat$secondaryArchSuffix
lib:libavutil$secondaryArchSuffix
lib:libcurl$secondaryArchSuffix
lib:libfreeimage$secondaryArchSuffix
lib:libfreetype$secondaryArchSuffix
lib:libharfbuzz$secondaryArchSuffix
lib:libgit2$secondaryArchSuffix
lib:libicuuc$secondaryArchSuffix
lib:libintl$secondaryArchSuffix
lib:libpoppler$secondaryArchSuffix
lib:libpugixml$secondaryArchSuffix
lib:libSDL2_2.0$secondaryArchSuffix
"
BUILD_REQUIRES="
haiku${secondaryArchSuffix}_devel
devel:libGL$secondaryArchSuffix
devel:libavcodec$secondaryArchSuffix
devel:libavformat$secondaryArchSuffix
devel:libavutil$secondaryArchSuffix
devel:libcurl$secondaryArchSuffix
devel:libfreeimage$secondaryArchSuffix
devel:libfreetype$secondaryArchSuffix
devel:libharfbuzz$secondaryArchSuffix
devel:libgit2$secondaryArchSuffix
devel:libicuuc$secondaryArchSuffix
devel:libintl$secondaryArchSuffix
devel:libpoppler$secondaryArchSuffix
devel:libpugixml$secondaryArchSuffix
devel:libSDL2_2.0$secondaryArchSuffix
"
BUILD_PREREQUIRES="
cmd:g++$secondaryArchSuffix
cmd:ld$secondaryArchSuffix
cmd:cmake$secondaryArchSuffix
cmd:make
cmd:msgfmt$secondaryArchSuffix
cmd:pkg_config$secondaryArchSuffix
"
BUILD()
{
cmake $cmakeDirArgs -DCMAKE_BUILD_TYPE=Release .
make $jobArgs
}
INSTALL()
{
make install
local APP_SIGNATURE="application/x-vnd.ES-DE"
local MAJOR="`echo "$portVersion" | cut -d. -f1`"
local MIDDLE="`echo "$portVersion" | cut -d. -f2`"
local MINOR="`echo "$portVersion" | cut -d. -f3`"
sed \
-e "s|@APP_SIGNATURE@|$APP_SIGNATURE|" \
-e "s|@MAJOR@|$MAJOR|" \
-e "s|@MIDDLE@|$MIDDLE|" \
-e "s|@MINOR@|$MINOR|" \
$sourceDir/es-app/assets/es-de_haiku.rdef.in > es-de.rdef
addResourcesToBinaries es-de.rdef \
"$appsDir"/es-de
addAppDeskbarSymlink "$appsDir"/es-de "ES-DE"
}

View file

@ -0,0 +1,28 @@
{
"resourceFilters": [
{
"categories": [
"coll_tree"
],
"rules": [
"-/collations/*",
"-/collations/*"
]
}
],
"featureFilters": {
"confusables": "exclude",
"curr_supplemental": "exclude",
"curr_tree": "exclude",
"lang_tree": "exclude",
"region_tree": "exclude",
"rbnf_tree": "exclude",
"stringprep": "exclude",
"zone_supplemental": "exclude",
"zone_tree": "exclude",
"translit": "exclude",
"unames": "exclude",
"unit_tree": "exclude",
"locales_tree": "exclude"
}
}

View file

@ -0,0 +1,642 @@
/* Message catalogs for internationalization.
Copyright (C) 1995-1997, 2000-2016, 2018-2024 Free Software Foundation, Inc.
This program 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 2.1 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. */
#ifndef _LIBINTL_H
#define _LIBINTL_H 1
#include <locale.h>
#if (defined __APPLE__ && defined __MACH__) && 0
# include <xlocale.h>
#endif
/* The LC_MESSAGES locale category is the category used by the functions
gettext() and dgettext(). It is specified in POSIX, but not in ANSI C.
On systems that don't define it, use an arbitrary value instead.
On Solaris, <locale.h> defines __LOCALE_H (or _LOCALE_H in Solaris 2.5)
then includes <libintl.h> (i.e. this file!) and then only defines
LC_MESSAGES. To avoid a redefinition warning, don't define LC_MESSAGES
in this case. */
#if !defined LC_MESSAGES && !(defined __LOCALE_H || (defined _LOCALE_H && defined __sun))
# define LC_MESSAGES 1729
#endif
/* We define an additional symbol to signal that we use the GNU
implementation of gettext. */
#define __USE_GNU_GETTEXT 1
/* Provide information about the supported file formats. Returns the
maximum minor revision number supported for a given major revision. */
#define __GNU_GETTEXT_SUPPORTED_REVISION(major) \
((major) == 0 || (major) == 1 ? 1 : -1)
/* Resolve a platform specific conflict on DJGPP. GNU gettext takes
precedence over _conio_gettext. */
#ifdef __DJGPP__
# undef gettext
#endif
#ifdef __cplusplus
extern "C" {
#endif
/* Version number: (major<<16) + (minor<<8) + subminor */
#define LIBINTL_VERSION 0x001605
extern int libintl_version;
/* We redirect the functions to those prefixed with "libintl_". This is
necessary, because some systems define gettext/textdomain/... in the C
library (namely, Solaris 2.4 and newer, and GNU libc 2.0 and newer).
If we used the unprefixed names, there would be cases where the
definition in the C library would override the one in the libintl.so
shared library. Recall that on ELF systems, the symbols are looked
up in the following order:
1. in the executable,
2. in the shared libraries specified on the link command line, in order,
3. in the dependencies of the shared libraries specified on the link
command line,
4. in the dlopen()ed shared libraries, in the order in which they were
dlopen()ed.
The definition in the C library would override the one in libintl.so if
either
* -lc is given on the link command line and -lintl isn't, or
* -lc is given on the link command line before -lintl, or
* libintl.so is a dependency of a dlopen()ed shared library but not
linked to the executable at link time.
Since Solaris gettext() behaves differently than GNU gettext(), this
would be unacceptable.
The redirection happens by default through macros in C, so that &gettext
is independent of the compilation unit, but through inline functions in
C++, in order not to interfere with the name mangling of class fields or
class methods called 'gettext'. */
/* The user can define _INTL_REDIRECT_INLINE or _INTL_REDIRECT_MACROS.
If he doesn't, we choose the method. A third possible method is
_INTL_REDIRECT_ASM, supported only by GCC. */
#if !(defined _INTL_REDIRECT_INLINE || defined _INTL_REDIRECT_MACROS)
# if defined __GNUC__ && __GNUC__ >= 2 && !(defined __APPLE_CC__ && __APPLE_CC__ > 1) && !defined __MINGW32__ && !(__GNUC__ == 2 && defined _AIX) && (defined __STDC__ || defined __cplusplus)
# define _INTL_REDIRECT_ASM
# else
# ifdef __cplusplus
# define _INTL_REDIRECT_INLINE
# else
# define _INTL_REDIRECT_MACROS
# endif
# endif
#endif
/* Auxiliary macros. */
#ifdef _INTL_REDIRECT_ASM
# define _INTL_ASM(cname) __asm__ (_INTL_ASMNAME (__USER_LABEL_PREFIX__, #cname))
# define _INTL_ASMNAME(prefix,cnamestring) _INTL_STRINGIFY (prefix) cnamestring
# define _INTL_STRINGIFY(prefix) #prefix
#else
# define _INTL_ASM(cname)
#endif
/* _INTL_MAY_RETURN_STRING_ARG(n) declares that the given function may return
its n-th argument literally. This enables GCC to warn for example about
printf (gettext ("foo %y")). */
#if defined __GNUC__ && __GNUC__ >= 3 && !(defined __APPLE_CC__ && __APPLE_CC__ > 1 && !(defined __clang__ && __clang__ && __clang_major__ >= 3) && defined __cplusplus)
# define _INTL_MAY_RETURN_STRING_ARG(n) __attribute__ ((__format_arg__ (n)))
#else
# define _INTL_MAY_RETURN_STRING_ARG(n)
#endif
/* _INTL_ATTRIBUTE_FORMAT ((ARCHETYPE, STRING-INDEX, FIRST-TO-CHECK))
declares that the STRING-INDEXth function argument is a format string of
style ARCHETYPE, which is one of:
printf, gnu_printf
scanf, gnu_scanf,
strftime, gnu_strftime,
strfmon,
or the same thing prefixed and suffixed with '__'.
If FIRST-TO-CHECK is not 0, arguments starting at FIRST-TO_CHECK
are suitable for the format string. */
/* Applies to: functions. */
#if (defined __GNUC__ && __GNUC__ + (__GNUC_MINOR__ >= 7) > 2) || defined __clang__
# define _INTL_ATTRIBUTE_FORMAT(spec) __attribute__ ((__format__ spec))
#else
# define _INTL_ATTRIBUTE_FORMAT(spec)
#endif
/* _INTL_ATTRIBUTE_SPEC_PRINTF_STANDARD
An __attribute__ __format__ specifier for a function that takes a format
string and arguments, where the format string directives are the ones
standardized by ISO C99 and POSIX. */
/* __gnu_printf__ is supported in GCC >= 4.4. */
#if defined __GNUC__ && __GNUC__ + (__GNUC_MINOR__ >= 4) > 4
# define _INTL_ATTRIBUTE_SPEC_PRINTF_STANDARD __gnu_printf__
#else
# define _INTL_ATTRIBUTE_SPEC_PRINTF_STANDARD __printf__
#endif
/* _INTL_ATTRIBUTE_FORMAT_PRINTF_STANDARD
indicates to GCC that the function takes a format string and arguments,
where the format string directives are the ones standardized by ISO C99
and POSIX. */
#define _INTL_ATTRIBUTE_FORMAT_PRINTF_STANDARD(formatstring_parameter, first_argument) \
_INTL_ATTRIBUTE_FORMAT ((_INTL_ATTRIBUTE_SPEC_PRINTF_STANDARD, formatstring_parameter, first_argument))
/* Look up MSGID in the current default message catalog for the current
LC_MESSAGES locale. If not found, returns MSGID itself (the default
text). */
#ifdef _INTL_REDIRECT_INLINE
extern char *libintl_gettext (const char *__msgid)
_INTL_MAY_RETURN_STRING_ARG (1);
static inline
_INTL_MAY_RETURN_STRING_ARG (1)
char *gettext (const char *__msgid)
{
return libintl_gettext (__msgid);
}
#else
# ifdef _INTL_REDIRECT_MACROS
# define gettext libintl_gettext
# endif
extern char *gettext (const char *__msgid)
_INTL_ASM (libintl_gettext)
_INTL_MAY_RETURN_STRING_ARG (1);
#endif
/* Look up MSGID in the DOMAINNAME message catalog for the current
LC_MESSAGES locale. */
#ifdef _INTL_REDIRECT_INLINE
extern char *libintl_dgettext (const char *__domainname, const char *__msgid)
_INTL_MAY_RETURN_STRING_ARG (2);
static inline
_INTL_MAY_RETURN_STRING_ARG (2)
char *dgettext (const char *__domainname, const char *__msgid)
{
return libintl_dgettext (__domainname, __msgid);
}
#else
# ifdef _INTL_REDIRECT_MACROS
# define dgettext libintl_dgettext
# endif
extern char *dgettext (const char *__domainname, const char *__msgid)
_INTL_ASM (libintl_dgettext)
_INTL_MAY_RETURN_STRING_ARG (2);
#endif
/* Look up MSGID in the DOMAINNAME message catalog for the current CATEGORY
locale. */
#ifdef _INTL_REDIRECT_INLINE
extern char *libintl_dcgettext (const char *__domainname, const char *__msgid,
int __category)
_INTL_MAY_RETURN_STRING_ARG (2);
static inline
_INTL_MAY_RETURN_STRING_ARG (2)
char *dcgettext (const char *__domainname, const char *__msgid, int __category)
{
return libintl_dcgettext (__domainname, __msgid, __category);
}
#else
# ifdef _INTL_REDIRECT_MACROS
# define dcgettext libintl_dcgettext
# endif
extern char *dcgettext (const char *__domainname, const char *__msgid,
int __category)
_INTL_ASM (libintl_dcgettext)
_INTL_MAY_RETURN_STRING_ARG (2);
#endif
/* Similar to 'gettext' but select the plural form corresponding to the
number N. */
#ifdef _INTL_REDIRECT_INLINE
extern char *libintl_ngettext (const char *__msgid1, const char *__msgid2,
unsigned long int __n)
_INTL_MAY_RETURN_STRING_ARG (1) _INTL_MAY_RETURN_STRING_ARG (2);
static inline
_INTL_MAY_RETURN_STRING_ARG (1) _INTL_MAY_RETURN_STRING_ARG (2)
char *ngettext (const char *__msgid1, const char *__msgid2,
unsigned long int __n)
{
return libintl_ngettext (__msgid1, __msgid2, __n);
}
#else
# ifdef _INTL_REDIRECT_MACROS
# define ngettext libintl_ngettext
# endif
extern char *ngettext (const char *__msgid1, const char *__msgid2,
unsigned long int __n)
_INTL_ASM (libintl_ngettext)
_INTL_MAY_RETURN_STRING_ARG (1) _INTL_MAY_RETURN_STRING_ARG (2);
#endif
/* Similar to 'dgettext' but select the plural form corresponding to the
number N. */
#ifdef _INTL_REDIRECT_INLINE
extern char *libintl_dngettext (const char *__domainname, const char *__msgid1,
const char *__msgid2, unsigned long int __n)
_INTL_MAY_RETURN_STRING_ARG (2) _INTL_MAY_RETURN_STRING_ARG (3);
static inline
_INTL_MAY_RETURN_STRING_ARG (2) _INTL_MAY_RETURN_STRING_ARG (3)
char *dngettext (const char *__domainname, const char *__msgid1,
const char *__msgid2, unsigned long int __n)
{
return libintl_dngettext (__domainname, __msgid1, __msgid2, __n);
}
#else
# ifdef _INTL_REDIRECT_MACROS
# define dngettext libintl_dngettext
# endif
extern char *dngettext (const char *__domainname,
const char *__msgid1, const char *__msgid2,
unsigned long int __n)
_INTL_ASM (libintl_dngettext)
_INTL_MAY_RETURN_STRING_ARG (2) _INTL_MAY_RETURN_STRING_ARG (3);
#endif
/* Similar to 'dcgettext' but select the plural form corresponding to the
number N. */
#ifdef _INTL_REDIRECT_INLINE
extern char *libintl_dcngettext (const char *__domainname,
const char *__msgid1, const char *__msgid2,
unsigned long int __n, int __category)
_INTL_MAY_RETURN_STRING_ARG (2) _INTL_MAY_RETURN_STRING_ARG (3);
static inline
_INTL_MAY_RETURN_STRING_ARG (2) _INTL_MAY_RETURN_STRING_ARG (3)
char *dcngettext (const char *__domainname,
const char *__msgid1, const char *__msgid2,
unsigned long int __n, int __category)
{
return libintl_dcngettext (__domainname, __msgid1, __msgid2, __n, __category);
}
#else
# ifdef _INTL_REDIRECT_MACROS
# define dcngettext libintl_dcngettext
# endif
extern char *dcngettext (const char *__domainname,
const char *__msgid1, const char *__msgid2,
unsigned long int __n, int __category)
_INTL_ASM (libintl_dcngettext)
_INTL_MAY_RETURN_STRING_ARG (2) _INTL_MAY_RETURN_STRING_ARG (3);
#endif
/* Set the current default message catalog to DOMAINNAME.
If DOMAINNAME is null, return the current default.
If DOMAINNAME is "", reset to the default of "messages". */
# ifdef _INTL_REDIRECT_INLINE
extern char *libintl_textdomain (const char *__domainname);
static inline char *textdomain (const char *__domainname)
{
return libintl_textdomain (__domainname);
}
# else
# ifdef _INTL_REDIRECT_MACROS
# define textdomain libintl_textdomain
# endif
extern char *textdomain (const char *__domainname)
_INTL_ASM (libintl_textdomain);
# endif
/* Specify that the DOMAINNAME message catalog will be found
in DIRNAME rather than in the system locale data base. */
# ifdef _INTL_REDIRECT_INLINE
extern char *libintl_bindtextdomain (const char *__domainname,
const char *__dirname);
static inline char *bindtextdomain (const char *__domainname,
const char *__dirname)
{
return libintl_bindtextdomain (__domainname, __dirname);
}
# else
# ifdef _INTL_REDIRECT_MACROS
# define bindtextdomain libintl_bindtextdomain
# endif
extern char *bindtextdomain (const char *__domainname, const char *__dirname)
_INTL_ASM (libintl_bindtextdomain);
# endif
# if defined _WIN32 && !defined __CYGWIN__
/* Specify that the DOMAINNAME message catalog will be found
in WDIRNAME rather than in the system locale data base. */
# ifdef _INTL_REDIRECT_INLINE
extern wchar_t *libintl_wbindtextdomain (const char *__domainname,
const wchar_t *__wdirname);
static inline wchar_t *wbindtextdomain (const char *__domainname,
const wchar_t *__wdirname)
{
return libintl_wbindtextdomain (__domainname, __wdirname);
}
# else
# ifdef _INTL_REDIRECT_MACROS
# define wbindtextdomain libintl_wbindtextdomain
# endif
extern wchar_t *wbindtextdomain (const char *__domainname,
const wchar_t *__wdirname)
_INTL_ASM (libintl_wbindtextdomain);
# endif
# endif
/* Specify the character encoding in which the messages from the
DOMAINNAME message catalog will be returned. */
# ifdef _INTL_REDIRECT_INLINE
extern char *libintl_bind_textdomain_codeset (const char *__domainname,
const char *__codeset);
static inline char *bind_textdomain_codeset (const char *__domainname,
const char *__codeset)
{
return libintl_bind_textdomain_codeset (__domainname, __codeset);
}
# else
# ifdef _INTL_REDIRECT_MACROS
# define bind_textdomain_codeset libintl_bind_textdomain_codeset
# endif
extern char *bind_textdomain_codeset (const char *__domainname,
const char *__codeset)
_INTL_ASM (libintl_bind_textdomain_codeset);
# endif
/* Support for format strings with positions in *printf(), following the
POSIX/XSI specification.
Note: These replacements for the *printf() functions are visible only
in source files that #include <libintl.h> or #include "gettext.h".
Packages that use *printf() in source files that don't refer to _()
or gettext() but for which the format string could be the return value
of _() or gettext() need to add this #include. Oh well. */
/* Note: In C++ mode, it is not sufficient to redefine a symbol at the
preprocessor macro level, such as
#define sprintf libintl_sprintf
Some programs may reference std::sprintf after including <libintl.h>.
Therefore we must make sure that std::libintl_sprintf is defined and
identical to ::libintl_sprintf.
The user can define _INTL_CXX_NO_CLOBBER_STD_NAMESPACE to avoid this.
In such cases, they will not benefit from the overrides when using
the 'std' namespace, and they will need to do the references to the
'std' namespace *before* including <libintl.h> or "gettext.h". */
#if !0
# include <stdio.h>
# include <stddef.h>
/* Get va_list. */
# if (defined __STDC__ && __STDC__) || defined __cplusplus || defined _MSC_VER
# include <stdarg.h>
# else
# include <varargs.h>
# endif
# if !((defined fprintf && defined _GL_STDIO_H) || defined GNULIB_overrides_fprintf) /* don't override gnulib */
# undef fprintf
# define fprintf libintl_fprintf
extern int fprintf (FILE *, const char *, ...)
_INTL_ATTRIBUTE_FORMAT_PRINTF_STANDARD (2, 3);
# if defined __cplusplus && !defined _INTL_CXX_NO_CLOBBER_STD_NAMESPACE
namespace std { using ::libintl_fprintf; }
# endif
# endif
# if !((defined vfprintf && defined _GL_STDIO_H) || defined GNULIB_overrides_vfprintf) /* don't override gnulib */
# undef vfprintf
# define vfprintf libintl_vfprintf
extern int vfprintf (FILE *, const char *, va_list)
_INTL_ATTRIBUTE_FORMAT_PRINTF_STANDARD (2, 0);
# if defined __cplusplus && !defined _INTL_CXX_NO_CLOBBER_STD_NAMESPACE
namespace std { using ::libintl_vfprintf; }
# endif
# endif
# if !((defined printf && defined _GL_STDIO_H) || defined GNULIB_overrides_printf) /* don't override gnulib */
# undef printf
# if defined __NetBSD__ || defined __BEOS__ || defined __CYGWIN__ || defined __MINGW32__
/* Don't break __attribute__((format(printf,M,N))).
This redefinition is only possible because the libc in NetBSD, Cygwin,
mingw does not have a function __printf__.
Alternatively, we could have done this redirection only when compiling with
__GNUC__, together with a symbol redirection:
extern int printf (const char *, ...)
__asm__ (#__USER_LABEL_PREFIX__ "libintl_printf");
But doing it now would introduce a binary incompatibility with already
distributed versions of libintl on these systems. */
# define libintl_printf __printf__
# endif
# define printf libintl_printf
extern int printf (const char *, ...)
_INTL_ATTRIBUTE_FORMAT_PRINTF_STANDARD (1, 2);
# if defined __cplusplus && !defined _INTL_CXX_NO_CLOBBER_STD_NAMESPACE
namespace std { using ::libintl_printf; }
# endif
# endif
# if !((defined vprintf && defined _GL_STDIO_H) || defined GNULIB_overrides_vprintf) /* don't override gnulib */
# undef vprintf
# define vprintf libintl_vprintf
extern int vprintf (const char *, va_list)
_INTL_ATTRIBUTE_FORMAT_PRINTF_STANDARD (1, 0);
# if defined __cplusplus && !defined _INTL_CXX_NO_CLOBBER_STD_NAMESPACE
namespace std { using ::libintl_vprintf; }
# endif
# endif
# if !((defined sprintf && defined _GL_STDIO_H) || defined GNULIB_overrides_sprintf) /* don't override gnulib */
# undef sprintf
# define sprintf libintl_sprintf
extern int sprintf (char *, const char *, ...)
_INTL_ATTRIBUTE_FORMAT_PRINTF_STANDARD (2, 3);
# if defined __cplusplus && !defined _INTL_CXX_NO_CLOBBER_STD_NAMESPACE
namespace std { using ::libintl_sprintf; }
# endif
# endif
# if !((defined vsprintf && defined _GL_STDIO_H) || defined GNULIB_overrides_vsprintf) /* don't override gnulib */
# undef vsprintf
# define vsprintf libintl_vsprintf
extern int vsprintf (char *, const char *, va_list)
_INTL_ATTRIBUTE_FORMAT_PRINTF_STANDARD (2, 0);
# if defined __cplusplus && !defined _INTL_CXX_NO_CLOBBER_STD_NAMESPACE
namespace std { using ::libintl_vsprintf; }
# endif
# endif
# if 1
# if !((defined snprintf && defined _GL_STDIO_H) || defined GNULIB_overrides_snprintf) /* don't override gnulib */
# undef snprintf
# define snprintf libintl_snprintf
extern int snprintf (char *, size_t, const char *, ...)
_INTL_ATTRIBUTE_FORMAT_PRINTF_STANDARD (3, 4);
# if defined __cplusplus && !defined _INTL_CXX_NO_CLOBBER_STD_NAMESPACE
namespace std { using ::libintl_snprintf; }
# endif
# endif
# if !((defined vsnprintf && defined _GL_STDIO_H) || defined GNULIB_overrides_vsnprintf) /* don't override gnulib */
# undef vsnprintf
# define vsnprintf libintl_vsnprintf
extern int vsnprintf (char *, size_t, const char *, va_list)
_INTL_ATTRIBUTE_FORMAT_PRINTF_STANDARD (3, 0);
# if defined __cplusplus && !defined _INTL_CXX_NO_CLOBBER_STD_NAMESPACE
namespace std { using ::libintl_vsnprintf; }
# endif
# endif
# endif
# if 1
# if !((defined asprintf && defined _GL_STDIO_H) || defined GNULIB_overrides_asprintf) /* don't override gnulib */
# undef asprintf
# define asprintf libintl_asprintf
extern int asprintf (char **, const char *, ...)
_INTL_ATTRIBUTE_FORMAT_PRINTF_STANDARD (2, 3);
# if defined __cplusplus && !defined _INTL_CXX_NO_CLOBBER_STD_NAMESPACE
namespace std { using ::libintl_asprintf; }
# endif
# endif
# if !((defined vasprintf && defined _GL_STDIO_H) || defined GNULIB_overrides_vasprintf) /* don't override gnulib */
# undef vasprintf
# define vasprintf libintl_vasprintf
extern int vasprintf (char **, const char *, va_list)
_INTL_ATTRIBUTE_FORMAT_PRINTF_STANDARD (2, 0);
# if defined __cplusplus && !defined _INTL_CXX_NO_CLOBBER_STD_NAMESPACE
namespace std { using ::libintl_vasprintf; }
# endif
# endif
# endif
# if 1
# undef fwprintf
# define fwprintf libintl_fwprintf
extern int fwprintf (FILE *, const wchar_t *, ...);
# if defined __cplusplus && !defined _INTL_CXX_NO_CLOBBER_STD_NAMESPACE
namespace std { using ::libintl_fwprintf; }
# endif
# undef vfwprintf
# define vfwprintf libintl_vfwprintf
extern int vfwprintf (FILE *, const wchar_t *, va_list);
# if defined __cplusplus && !defined _INTL_CXX_NO_CLOBBER_STD_NAMESPACE
namespace std { using ::libintl_vfwprintf; }
# endif
# undef wprintf
# define wprintf libintl_wprintf
extern int wprintf (const wchar_t *, ...);
# if defined __cplusplus && !defined _INTL_CXX_NO_CLOBBER_STD_NAMESPACE
namespace std { using ::libintl_wprintf; }
# endif
# undef vwprintf
# define vwprintf libintl_vwprintf
extern int vwprintf (const wchar_t *, va_list);
# if defined __cplusplus && !defined _INTL_CXX_NO_CLOBBER_STD_NAMESPACE
namespace std { using ::libintl_vwprintf; }
# endif
# undef swprintf
# define swprintf libintl_swprintf
extern int swprintf (wchar_t *, size_t, const wchar_t *, ...);
# if defined __cplusplus && !defined _INTL_CXX_NO_CLOBBER_STD_NAMESPACE
namespace std { using ::libintl_swprintf; }
# endif
# undef vswprintf
# define vswprintf libintl_vswprintf
extern int vswprintf (wchar_t *, size_t, const wchar_t *, va_list);
# if defined __cplusplus && !defined _INTL_CXX_NO_CLOBBER_STD_NAMESPACE
namespace std { using ::libintl_vswprintf; }
# endif
# endif
#endif
/* Support for retrieving the name of a locale_t object. */
#if 0
# ifndef GNULIB_defined_newlocale /* don't override gnulib */
# undef newlocale
# define newlocale libintl_newlocale
extern locale_t newlocale (int, const char *, locale_t);
# if defined __cplusplus && !defined _INTL_CXX_NO_CLOBBER_STD_NAMESPACE
namespace std { using ::libintl_newlocale; }
# endif
# endif
# ifndef GNULIB_defined_duplocale /* don't override gnulib */
# undef duplocale
# define duplocale libintl_duplocale
extern locale_t duplocale (locale_t);
# if defined __cplusplus && !defined _INTL_CXX_NO_CLOBBER_STD_NAMESPACE
namespace std { using ::libintl_duplocale; }
# endif
# endif
# ifndef GNULIB_defined_freelocale /* don't override gnulib */
# undef freelocale
# define freelocale libintl_freelocale
extern void freelocale (locale_t);
# if defined __cplusplus && !defined _INTL_CXX_NO_CLOBBER_STD_NAMESPACE
namespace std { using ::libintl_freelocale; }
# endif
# endif
#endif
/* Support for the locale chosen by the user. */
#if (defined __APPLE__ && defined __MACH__) || defined _WIN32 || defined __CYGWIN__
# ifndef GNULIB_defined_setlocale /* don't override gnulib */
# undef setlocale
# define setlocale libintl_setlocale
extern char *setlocale (int, const char *);
# if defined __cplusplus && !defined _INTL_CXX_NO_CLOBBER_STD_NAMESPACE
namespace std { using ::libintl_setlocale; }
# endif
# endif
# if 0
# undef newlocale
# define newlocale libintl_newlocale
/* Declare newlocale() only if the system headers define the 'locale_t' type. */
# if !(defined __CYGWIN__ && !defined LC_ALL_MASK)
extern locale_t newlocale (int, const char *, locale_t);
# if defined __cplusplus && !defined _INTL_CXX_NO_CLOBBER_STD_NAMESPACE
namespace std { using ::libintl_newlocale; }
# endif
# endif
# endif
#endif
/* Support for relocatable packages. */
/* Sets the original and the current installation prefix of the package.
Relocation simply replaces a pathname starting with the original prefix
by the corresponding pathname with the current prefix instead. Both
prefixes should be directory names without trailing slash (i.e. use ""
instead of "/"). */
#define libintl_set_relocation_prefix libintl_set_relocation_prefix
extern void
libintl_set_relocation_prefix (const char *orig_prefix,
const char *curr_prefix);
#ifdef __cplusplus
}
#endif
#endif /* libintl.h */

View file

@ -38,6 +38,9 @@
</screenshot> </screenshot>
</screenshots> </screenshots>
<releases> <releases>
<release version="3.1.0" date="2024-09-13">
<url>https://gitlab.com/es-de/emulationstation-de/-/releases</url>
</release>
<release version="3.0.3" date="2024-06-14"> <release version="3.0.3" date="2024-06-14">
<url>https://gitlab.com/es-de/emulationstation-de/-/releases</url> <url>https://gitlab.com/es-de/emulationstation-de/-/releases</url>
</release> </release>

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// //
// ES-DE // ES-DE Frontend
// ApplicationUpdater.cpp // ApplicationUpdater.cpp
// //
// Checks for application updates. // Checks for application updates.
@ -13,6 +13,7 @@
#include "Log.h" #include "Log.h"
#include "Settings.h" #include "Settings.h"
#include "resources/ResourceManager.h" #include "resources/ResourceManager.h"
#include "utils/LocalizationUtil.h"
#include "utils/StringUtil.h" #include "utils/StringUtil.h"
#include "utils/TimeUtil.h" #include "utils/TimeUtil.h"
@ -198,8 +199,11 @@ void ApplicationUpdater::update()
return; return;
// Everything else is some sort of error. // Everything else is some sort of error.
std::string errorMessage {"Network error (status: "}; std::string errorMessage {_("Network error (status:")};
errorMessage.append(std::to_string(reqStatus)).append(") - ").append(mRequest->getErrorMsg()); errorMessage.append(" ")
.append(std::to_string(reqStatus))
.append(") - ")
.append(mRequest->getErrorMsg());
throw std::runtime_error(errorMessage); throw std::runtime_error(errorMessage);
} }
@ -435,21 +439,25 @@ void ApplicationUpdater::compareVersions()
.append("), release date: ") .append("), release date: ")
.append(releaseType->date); .append(releaseType->date);
mResults.append("New ");
if (releaseType == &mPrerelease) { if (releaseType == &mPrerelease) {
mResults.append("prerelease available:\n") mResults.append(_("New prerelease available:"))
.append("\n")
.append(releaseType->version) .append(releaseType->version)
.append(" (") .append(" (")
.append(releaseType->date) .append(releaseType->date)
.append(")"); .append(")");
} }
else { else {
mResults.append("release available: ").append(releaseType->version); mResults.append(_("New release available:"))
.append(" ")
.append(releaseType->version);
} }
if (mPackageType == PackageType::UNKNOWN) if (mPackageType == PackageType::UNKNOWN)
mResults.append("\nFor more information visit\n").append("https://es-de.org"); mResults.append("\n")
.append(_("For more information visit"))
.append("\n")
.append("https://es-de.org");
if (mPackage.message != "") if (mPackage.message != "")
mResults.append("\n").append(mPackage.message); mResults.append("\n").append(mPackage.message);

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// //
// ES-DE // ES-DE Frontend
// ApplicationUpdater.h // ApplicationUpdater.h
// //
// Checks for application updates. // Checks for application updates.

View file

@ -30,6 +30,7 @@
#include "UIModeController.h" #include "UIModeController.h"
#include "Window.h" #include "Window.h"
#include "utils/FileSystemUtil.h" #include "utils/FileSystemUtil.h"
#include "utils/LocalizationUtil.h"
#include "utils/StringUtil.h" #include "utils/StringUtil.h"
#include "utils/TimeUtil.h" #include "utils/TimeUtil.h"
#include "views/GamelistView.h" #include "views/GamelistView.h"
@ -46,7 +47,7 @@ CollectionSystemsManager::CollectionSystemsManager() noexcept
{ {
// clang-format off // clang-format off
CollectionSystemDecl systemDecls[] { CollectionSystemDecl systemDecls[] {
// Type Name Long name Theme folder isCustom // Type Name Full name Theme folder isCustom
{AUTO_ALL_GAMES, "all", "all games", "auto-allgames", false}, {AUTO_ALL_GAMES, "all", "all games", "auto-allgames", false},
{AUTO_LAST_PLAYED, "recent", "last played", "auto-lastplayed", false}, {AUTO_LAST_PLAYED, "recent", "last played", "auto-lastplayed", false},
{AUTO_FAVORITES, "favorites", "favorites", "auto-favorites", false}, {AUTO_FAVORITES, "favorites", "favorites", "auto-favorites", false},
@ -54,6 +55,14 @@ CollectionSystemsManager::CollectionSystemsManager() noexcept
}; };
// clang-format on // clang-format on
#if defined(GETTEXT_DUMMY_ENTRIES)
// This is just to get gettext msgid entries added to the PO message catalog files.
_("all games");
_("last played");
_("favorites");
_("collections");
#endif
// Create a map of the collections. // Create a map of the collections.
std::vector<CollectionSystemDecl> tempSystemDecl {std::vector<CollectionSystemDecl>( std::vector<CollectionSystemDecl> tempSystemDecl {std::vector<CollectionSystemDecl>(
systemDecls, systemDecls + sizeof(systemDecls) / sizeof(systemDecls[0]))}; systemDecls, systemDecls + sizeof(systemDecls) / sizeof(systemDecls[0]))};
@ -388,10 +397,12 @@ void CollectionSystemsManager::updateCollectionSystem(FileData* file, Collection
parentRootFolder->getSortTypeString()), parentRootFolder->getSortTypeString()),
favoritesSorting); favoritesSorting);
mWindow->queueInfoPopup( mWindow->queueInfoPopup(
"DISABLED '" + Utils::String::format(
_("DISABLED '%s' IN '%s'"),
Utils::String::toUpper( Utils::String::toUpper(
Utils::String::removeParenthesis(file->getName())) + Utils::String::removeParenthesis(file->getName()))
"' IN '" + Utils::String::toUpper(sysData.system->getName()) + "'", .c_str(),
Utils::String::toUpper(sysData.system->getName()).c_str()),
4000); 4000);
} }
else { else {
@ -659,18 +670,21 @@ void CollectionSystemsManager::setEditMode(const std::string& collectionName, bo
else else
editButton = "'Y'"; editButton = "'Y'";
} }
mWindow->queueInfoPopup("EDITING '" + Utils::String::toUpper(collectionName) + mWindow->queueInfoPopup(
"' COLLECTION, ADD/REMOVE GAMES WITH " + editButton, Utils::String::format(_("EDITING '%s' COLLECTION, ADD/REMOVE GAMES WITH %s"),
10000); Utils::String::toUpper(collectionName).c_str(),
editButton.c_str()),
10000);
} }
} }
void CollectionSystemsManager::exitEditMode(bool showPopup) void CollectionSystemsManager::exitEditMode(bool showPopup)
{ {
if (showPopup) { if (showPopup) {
mWindow->queueInfoPopup("FINISHED EDITING '" + Utils::String::toUpper(mEditingCollection) + mWindow->queueInfoPopup(
"' COLLECTION", Utils::String::format(_("FINISHED EDITING '%s' COLLECTION"),
4000); Utils::String::toUpper(mEditingCollection).c_str()),
4000);
} }
mIsEditingCustom = false; mIsEditingCustom = false;
@ -774,17 +788,20 @@ const bool CollectionSystemsManager::toggleGameInCollection(FileData* file)
ViewController::getInstance()->reloadGamelistView( ViewController::getInstance()->reloadGamelistView(
mAutoCollectionSystemsData["favorites"].system); mAutoCollectionSystemsData["favorites"].system);
} }
std::string sysTemp {sysName};
if (sysTemp == "Favorites")
sysTemp = _("Favorites");
if (adding) { if (adding) {
mWindow->queueInfoPopup( mWindow->queueInfoPopup(Utils::String::format(_("ADDED '%s' TO '%s'"),
"ADDED '" + Utils::String::toUpper(Utils::String::removeParenthesis(name)) + Utils::String::toUpper(name).c_str(),
"' TO '" + Utils::String::toUpper(sysName) + "'", Utils::String::toUpper(sysTemp).c_str()),
4000); 4000);
} }
else { else {
mWindow->queueInfoPopup( mWindow->queueInfoPopup(Utils::String::format(_("REMOVED '%s' FROM '%s'"),
"REMOVED '" + Utils::String::toUpper(Utils::String::removeParenthesis(name)) + Utils::String::toUpper(name).c_str(),
"' FROM '" + Utils::String::toUpper(sysName) + "'", Utils::String::toUpper(sysTemp).c_str()),
4000); 4000);
} }
return true; return true;
} }
@ -815,7 +832,7 @@ FileData* CollectionSystemsManager::updateCollectionFolderMetadata(SystemData* s
FileData* rootFolder {sys->getRootFolder()}; FileData* rootFolder {sys->getRootFolder()};
FileFilterIndex* idx {rootFolder->getSystem()->getIndex()}; FileFilterIndex* idx {rootFolder->getSystem()->getIndex()};
std::string desc {"This collection is empty"}; std::string desc {_("This collection is empty")};
std::vector<FileData*> gamesList; std::vector<FileData*> gamesList;
std::vector<FileData*> gamesListRandom; std::vector<FileData*> gamesListRandom;
@ -867,48 +884,58 @@ FileData* CollectionSystemsManager::updateCollectionFolderMetadata(SystemData* s
}; };
switch (gameCount) { switch (gameCount) {
case 1: { case 1: {
desc = "This collection contains 1 game: '"; desc = Utils::String::format(
desc.append(gamesList[0]->metadata.get("name")) _p("theme", "This collection contains 1 game: '%s [%s]'"),
.append(" [") gamesList[0]->metadata.get("name").c_str(),
.append(caseConversion( caseConversion(gamesList[0]->getSourceFileData()->getSystem()->getName())
gamesList[0]->getSourceFileData()->getSystem()->getName())) .c_str());
.append("]'");
break; break;
} }
case 2: { case 2: {
desc = "This collection contains 2 games: '"; desc = Utils::String::format(
desc.append(gamesList[0]->metadata.get("name")) _p("theme", "This collection contains 2 games: '%s [%s]' and '%s [%s]'"),
.append(" [") gamesList[0]->metadata.get("name").c_str(),
.append(caseConversion( caseConversion(gamesList[0]->getSourceFileData()->getSystem()->getName())
gamesList[0]->getSourceFileData()->getSystem()->getName())) .c_str(),
.append("]' and '") gamesList[1]->metadata.get("name").c_str(),
.append(gamesList[1]->metadata.get("name")) caseConversion(gamesList[1]->getSourceFileData()->getSystem()->getName())
.append(" [") .c_str());
.append(caseConversion( break;
gamesList[1]->getSourceFileData()->getSystem()->getName())) }
.append("]'"); case 3: {
desc = Utils::String::format(
_p("theme",
"This collection contains 3 games: '%s [%s]', '%s [%s]' and '%s [%s]'"),
gamesList[0]->metadata.get("name").c_str(),
caseConversion(gamesList[0]->getSourceFileData()->getSystem()->getName())
.c_str(),
gamesList[1]->metadata.get("name").c_str(),
caseConversion(gamesList[1]->getSourceFileData()->getSystem()->getName())
.c_str(),
gamesList[2]->metadata.get("name").c_str(),
caseConversion(gamesList[2]->getSourceFileData()->getSystem()->getName())
.c_str());
break; break;
} }
default: { default: {
desc = "This collection contains "; desc = Utils::String::format(
desc.append(std::to_string(gameCount)) _np("theme",
.append(" games: '") "This collection contains %i games: '%s [%s]', '%s [%s]' and '%s "
.append(gamesList[0]->metadata.get("name")) "[%s]', "
.append(" [") "among others",
.append(caseConversion( "This collection contains %i games: '%s [%s]', '%s [%s]' and '%s "
gamesList[0]->getSourceFileData()->getSystem()->getName())) "[%s]', "
.append("]', '") "among others",
.append(gamesList[1]->metadata.get("name")) gameCount),
.append(" [") gameCount, gamesList[0]->metadata.get("name").c_str(),
.append(caseConversion( caseConversion(gamesList[0]->getSourceFileData()->getSystem()->getName())
gamesList[1]->getSourceFileData()->getSystem()->getName())) .c_str(),
.append("]' and '") gamesList[1]->metadata.get("name").c_str(),
.append(gamesList[2]->metadata.get("name")) caseConversion(gamesList[1]->getSourceFileData()->getSystem()->getName())
.append(" [") .c_str(),
.append(caseConversion( gamesList[2]->metadata.get("name").c_str(),
gamesList[2]->getSourceFileData()->getSystem()->getName())) caseConversion(gamesList[2]->getSourceFileData()->getSystem()->getName())
.append("]'"); .c_str());
desc.append(gameCount == 3 ? "" : ", among others");
break; break;
} }
} }
@ -916,29 +943,35 @@ FileData* CollectionSystemsManager::updateCollectionFolderMetadata(SystemData* s
else { else {
switch (gameCount) { switch (gameCount) {
case 1: { case 1: {
desc = "This collection contains 1 game: '"; desc =
desc.append(gamesList[0]->metadata.get("name")).append("'"); Utils::String::format(_p("theme", "This collection contains 1 game: '%s'"),
gamesList[0]->metadata.get("name").c_str());
break; break;
} }
case 2: { case 2: {
desc = "This collection contains 2 games: '"; desc = Utils::String::format(
desc.append(gamesList[0]->metadata.get("name")) _p("theme", "This collection contains 2 games: '%s' and '%s'"),
.append("' and '") gamesList[0]->metadata.get("name").c_str(),
.append(gamesList[1]->metadata.get("name")) gamesList[1]->metadata.get("name").c_str());
.append("'"); break;
}
case 3: {
desc = Utils::String::format(
_p("theme", "This collection contains 3 games: '%s', '%s' and '%s'"),
gamesList[0]->metadata.get("name").c_str(),
gamesList[1]->metadata.get("name").c_str(),
gamesList[2]->metadata.get("name").c_str());
break; break;
} }
default: { default: {
desc = "This collection contains "; desc = Utils::String::format(
desc.append(std::to_string(gameCount)) _np("theme",
.append(" games: '") "This collection contains %i games: '%s', '%s' and '%s', among others",
.append(gamesList[0]->metadata.get("name")) "This collection contains %i games: '%s', '%s' and '%s', among others",
.append("', '") gameCount),
.append(gamesList[1]->metadata.get("name")) gameCount, gamesList[0]->metadata.get("name").c_str(),
.append("' and '") gamesList[1]->metadata.get("name").c_str(),
.append(gamesList[2]->metadata.get("name")) gamesList[2]->metadata.get("name").c_str());
.append("'");
desc.append(gameCount == 3 ? "" : ", among others");
break; break;
} }
} }
@ -946,9 +979,9 @@ FileData* CollectionSystemsManager::updateCollectionFolderMetadata(SystemData* s
} }
if (idx->isFiltered()) { if (idx->isFiltered()) {
desc.append("\n\n'") desc.append("\n\n").append(Utils::String::format(
.append(rootFolder->getSystem()->getFullName()) _p("theme", "'%s' is filtered so there may be more games available"),
.append("' is filtered so there may be more games available"); rootFolder->getSystem()->getFullName().c_str()));
} }
rootFolder->metadata.set("desc", desc); rootFolder->metadata.set("desc", desc);
@ -1030,7 +1063,9 @@ void CollectionSystemsManager::deleteCustomCollection(const std::string& collect
<< configFile << "\""; << configFile << "\"";
#endif #endif
mWindow->queueInfoPopup( mWindow->queueInfoPopup(
"DELETED COLLECTION '" + Utils::String::toUpper(collectionName) + "'", 5000); Utils::String::format(_("DELETED COLLECTION '%s'"),
Utils::String::toUpper(collectionName).c_str()),
5000);
} }
else { else {
LOG(LogError) << "Attempted to delete custom collection \"" + collectionName + "\" " + LOG(LogError) << "Attempted to delete custom collection \"" + collectionName + "\" " +

View file

@ -49,6 +49,7 @@ FileData::FileData(FileType type,
, mUpdateChildrenLastPlayed {false} , mUpdateChildrenLastPlayed {false}
, mUpdateChildrenMostPlayed {false} , mUpdateChildrenMostPlayed {false}
, mDeletionFlag {false} , mDeletionFlag {false}
, mNoLoad {false}
{ {
// Metadata needs at least a name field (since that's what getName() will return). // Metadata needs at least a name field (since that's what getName() will return).
if ((system->hasPlatformId(PlatformIds::ARCADE) || if ((system->hasPlatformId(PlatformIds::ARCADE) ||
@ -1046,9 +1047,11 @@ void FileData::launchGame()
LOG(LogError) << "Raw emulator launch command:"; LOG(LogError) << "Raw emulator launch command:";
LOG(LogError) << commandRaw; LOG(LogError) << commandRaw;
window->queueInfoPopup("ERROR: MISSING PRE-COMMAND FIND RULES CONFIGURATION FOR '" + window->queueInfoPopup(
preCommand.first + "'", Utils::String::format(
6000); _("ERROR: MISSING PRE-COMMAND FIND RULES CONFIGURATION FOR '%s'"),
preCommand.first.c_str()),
6000);
window->setAllowTextScrolling(true); window->setAllowTextScrolling(true);
window->setAllowFileAnimation(true); window->setAllowFileAnimation(true);
return; return;
@ -1070,11 +1073,13 @@ void FileData::launchGame()
if (emulatorName == "") if (emulatorName == "")
window->queueInfoPopup( window->queueInfoPopup(
"ERROR: COULDN'T FIND PRE-COMMAND, HAS IT BEEN PROPERLY INSTALLED?", 6000); _("ERROR: COULDN'T FIND PRE-COMMAND, HAS IT BEEN PROPERLY INSTALLED?"), 6000);
else else
window->queueInfoPopup("ERROR: COULDN'T FIND PRE-COMMAND '" + emulatorName + window->queueInfoPopup(
"', HAS IT BEEN PROPERLY INSTALLED?", Utils::String::format(
6000); _("ERROR: COULDN'T FIND PRE-COMMAND '%s', HAS IT BEEN PROPERLY INSTALLED?"),
emulatorName.c_str()),
6000);
window->setAllowTextScrolling(true); window->setAllowTextScrolling(true);
window->setAllowFileAnimation(true); window->setAllowFileAnimation(true);
@ -1169,7 +1174,9 @@ void FileData::launchGame()
LOG(LogError) << commandRaw; LOG(LogError) << commandRaw;
window->queueInfoPopup( window->queueInfoPopup(
"ERROR: MISSING EMULATOR FIND RULES CONFIGURATION FOR '" + emulator.first + "'", 6000); Utils::String::format(_("ERROR: MISSING EMULATOR FIND RULES CONFIGURATION FOR '%s'"),
emulator.first.c_str()),
6000);
window->setAllowTextScrolling(true); window->setAllowTextScrolling(true);
window->setAllowFileAnimation(true); window->setAllowFileAnimation(true);
return; return;
@ -1204,24 +1211,28 @@ void FileData::launchGame()
#endif #endif
if (isAndroidApp) { if (isAndroidApp) {
if (emulatorName == "" || emulatorName == "%FILEINJECT%") { if (emulatorName == "" || emulatorName == "%FILEINJECT%") {
window->queueInfoPopup("ERROR: COULDN'T FIND APP, HAS IT BEEN PROPERLY INSTALLED?", window->queueInfoPopup(
6000); _("ERROR: COULDN'T FIND APP, HAS IT BEEN PROPERLY INSTALLED?"), 6000);
} }
else { else {
window->queueInfoPopup("ERROR: COULDN'T FIND APP '" + emulatorName + window->queueInfoPopup(
"', HAS IT BEEN PROPERLY INSTALLED?", Utils::String::format(
6000); _("ERROR: COULDN'T FIND APP '%s', HAS IT BEEN PROPERLY INSTALLED?"),
emulatorName.c_str()),
6000);
} }
} }
else { else {
if (emulatorName == "") { if (emulatorName == "") {
window->queueInfoPopup( window->queueInfoPopup(
"ERROR: COULDN'T FIND EMULATOR, HAS IT BEEN PROPERLY INSTALLED?", 6000); _("ERROR: COULDN'T FIND EMULATOR, HAS IT BEEN PROPERLY INSTALLED?"), 6000);
} }
else { else {
window->queueInfoPopup("ERROR: COULDN'T FIND EMULATOR '" + emulatorName + window->queueInfoPopup(
"', HAS IT BEEN PROPERLY INSTALLED?", Utils::String::format(
6000); _("ERROR: COULDN'T FIND EMULATOR '%s', HAS IT BEEN PROPERLY INSTALLED?"),
emulatorName.c_str()),
6000);
} }
} }
@ -1294,8 +1305,9 @@ void FileData::launchGame()
LOG(LogError) << commandRaw; LOG(LogError) << commandRaw;
window->queueInfoPopup( window->queueInfoPopup(
"ERROR: COULDN'T FIND EMULATOR CORE FILE '" + Utils::String::format(
Utils::String::toUpper(Utils::FileSystem::getFileName(coreFile)) + "'", _("ERROR: COULDN'T FIND EMULATOR CORE FILE '%s'"),
Utils::String::toUpper(Utils::FileSystem::getFileName(coreFile)).c_str()),
6000); 6000);
window->setAllowTextScrolling(true); window->setAllowTextScrolling(true);
window->setAllowFileAnimation(true); window->setAllowFileAnimation(true);
@ -1316,7 +1328,7 @@ void FileData::launchGame()
LOG(LogError) << "Raw emulator launch command:"; LOG(LogError) << "Raw emulator launch command:";
LOG(LogError) << commandRaw; LOG(LogError) << commandRaw;
window->queueInfoPopup("ERROR: INVALID ENTRY IN SYSTEMS CONFIGURATION FILE", 6000); window->queueInfoPopup(_("ERROR: INVALID ENTRY IN SYSTEMS CONFIGURATION FILE"), 6000);
window->setAllowTextScrolling(true); window->setAllowTextScrolling(true);
window->setAllowFileAnimation(true); window->setAllowFileAnimation(true);
return; return;
@ -1330,7 +1342,10 @@ void FileData::launchGame()
LOG(LogError) << "Raw emulator launch command:"; LOG(LogError) << "Raw emulator launch command:";
LOG(LogError) << commandRaw; LOG(LogError) << commandRaw;
window->queueInfoPopup("ERROR: MISSING CORE CONFIGURATION FOR '" + coreEntry + "'", 6000); window->queueInfoPopup(
Utils::String::format(_("ERROR: MISSING CORE CONFIGURATION FOR '%s'"),
coreEntry.c_str()),
6000);
window->setAllowTextScrolling(true); window->setAllowTextScrolling(true);
window->setAllowFileAnimation(true); window->setAllowFileAnimation(true);
return; return;
@ -1399,7 +1414,7 @@ void FileData::launchGame()
LOG(LogError) << "Raw emulator launch command:"; LOG(LogError) << "Raw emulator launch command:";
LOG(LogError) << commandRaw; LOG(LogError) << commandRaw;
window->queueInfoPopup("ERROR: INVALID ENTRY IN SYSTEMS CONFIGURATION FILE", 6000); window->queueInfoPopup(_("ERROR: INVALID ENTRY IN SYSTEMS CONFIGURATION FILE"), 6000);
window->setAllowTextScrolling(true); window->setAllowTextScrolling(true);
window->setAllowFileAnimation(true); window->setAllowFileAnimation(true);
return; return;
@ -1415,8 +1430,9 @@ void FileData::launchGame()
LOG(LogError) << Utils::String::vectorToDelimitedString(emulatorCorePaths, ", "); LOG(LogError) << Utils::String::vectorToDelimitedString(emulatorCorePaths, ", ");
window->queueInfoPopup( window->queueInfoPopup(
"ERROR: COULDN'T FIND EMULATOR CORE FILE '" + Utils::String::format(
Utils::String::toUpper(coreName.substr(0, coreName.size()) + "'"), _("ERROR: COULDN'T FIND EMULATOR CORE FILE '%s'"),
Utils::String::toUpper(coreName.substr(0, coreName.size())).c_str()),
6000); 6000);
window->setAllowTextScrolling(true); window->setAllowTextScrolling(true);
window->setAllowFileAnimation(true); window->setAllowFileAnimation(true);
@ -1463,7 +1479,8 @@ void FileData::launchGame()
LOG(LogError) << "Raw emulator launch command:"; LOG(LogError) << "Raw emulator launch command:";
LOG(LogError) << commandRaw; LOG(LogError) << commandRaw;
window->queueInfoPopup("ERROR: INVALID %STARTDIR% VARIABLE ENTRY", 6000); window->queueInfoPopup(
Utils::String::format(_("ERROR: INVALID %s VARIABLE ENTRY"), "%STARTDIR%"), 6000);
window->setAllowTextScrolling(true); window->setAllowTextScrolling(true);
window->setAllowFileAnimation(true); window->setAllowFileAnimation(true);
return; return;
@ -1504,9 +1521,11 @@ void FileData::launchGame()
LOG(LogError) << "Raw emulator launch command:"; LOG(LogError) << "Raw emulator launch command:";
LOG(LogError) << commandRaw; LOG(LogError) << commandRaw;
window->queueInfoPopup("ERROR: DIRECTORY DEFINED BY %STARTDIR% COULD NOT BE " window->queueInfoPopup(
"CREATED, PERMISSION PROBLEMS?", Utils::String::format(_("ERROR: DIRECTORY DEFINED BY %s COULD NOT BE "
6000); "CREATED, PERMISSION PROBLEMS?"),
"%STARTDIR%"),
6000);
window->setAllowTextScrolling(true); window->setAllowTextScrolling(true);
window->setAllowFileAnimation(true); window->setAllowFileAnimation(true);
return; return;
@ -1562,7 +1581,8 @@ void FileData::launchGame()
LOG(LogError) << "Raw emulator launch command:"; LOG(LogError) << "Raw emulator launch command:";
LOG(LogError) << commandRaw; LOG(LogError) << commandRaw;
window->queueInfoPopup("ERROR: INVALID %INJECT% VARIABLE ENTRY", 6000); window->queueInfoPopup(
Utils::String::format(_("ERROR: INVALID %s VARIABLE ENTRY"), "%INJECT%"), 6000);
window->setAllowTextScrolling(true); window->setAllowTextScrolling(true);
window->setAllowFileAnimation(true); window->setAllowFileAnimation(true);
return; return;
@ -1674,7 +1694,8 @@ void FileData::launchGame()
else { else {
LOG(LogError) << "App or alias file \"" << romPath LOG(LogError) << "App or alias file \"" << romPath
<< "\" doesn't exist or is unreadable"; << "\" doesn't exist or is unreadable";
window->queueInfoPopup("ERROR: APP OR ALIAS FILE DOESN'T EXIST OR IS UNREADABLE", 6000); window->queueInfoPopup(_("ERROR: APP OR ALIAS FILE DOESN'T EXIST OR IS UNREADABLE"),
6000);
window->setAllowTextScrolling(true); window->setAllowTextScrolling(true);
window->setAllowFileAnimation(true); window->setAllowFileAnimation(true);
return; return;
@ -1728,7 +1749,7 @@ void FileData::launchGame()
desktopFileStream.close(); desktopFileStream.close();
if (!validFile || !execEntry) { if (!validFile || !execEntry) {
LOG(LogError) << "File is invalid or unreadable"; LOG(LogError) << "File is invalid or unreadable";
window->queueInfoPopup("ERROR: DESKTOP FILE IS INVALID OR UNREADABLE", 6000); window->queueInfoPopup(_("ERROR: DESKTOP FILE IS INVALID OR UNREADABLE"), 6000);
window->setAllowTextScrolling(true); window->setAllowTextScrolling(true);
window->setAllowFileAnimation(true); window->setAllowFileAnimation(true);
return; return;
@ -1736,7 +1757,7 @@ void FileData::launchGame()
} }
else { else {
LOG(LogError) << "Desktop file \"" << romPath << "\" doesn't exist or is unreadable"; LOG(LogError) << "Desktop file \"" << romPath << "\" doesn't exist or is unreadable";
window->queueInfoPopup("ERROR: DESKTOP FILE DOESN'T EXIST OR IS UNREADABLE", 6000); window->queueInfoPopup(_("ERROR: DESKTOP FILE DOESN'T EXIST OR IS UNREADABLE"), 6000);
window->setAllowTextScrolling(true); window->setAllowTextScrolling(true);
window->setAllowFileAnimation(true); window->setAllowFileAnimation(true);
return; return;
@ -1794,7 +1815,8 @@ void FileData::launchGame()
LOG(LogError) << "Raw emulator launch command:"; LOG(LogError) << "Raw emulator launch command:";
LOG(LogError) << commandRaw; LOG(LogError) << commandRaw;
window->queueInfoPopup("ERROR: INVALID ENTRY IN SYSTEMS CONFIGURATION FILE", 6000); window->queueInfoPopup(_("ERROR: INVALID ENTRY IN SYSTEMS CONFIGURATION FILE"),
6000);
window->setAllowTextScrolling(true); window->setAllowTextScrolling(true);
window->setAllowFileAnimation(true); window->setAllowFileAnimation(true);
return; return;
@ -1858,8 +1880,8 @@ void FileData::launchGame()
LOG(LogError) << "Raw emulator launch command:"; LOG(LogError) << "Raw emulator launch command:";
LOG(LogError) << commandRaw; LOG(LogError) << commandRaw;
window->queueInfoPopup("ERROR: INVALID ENTRY IN SYSTEMS CONFIGURATION FILE", window->queueInfoPopup(
6000); _("ERROR: INVALID ENTRY IN SYSTEMS CONFIGURATION FILE"), 6000);
window->setAllowTextScrolling(true); window->setAllowTextScrolling(true);
window->setAllowFileAnimation(true); window->setAllowFileAnimation(true);
return; return;
@ -1879,6 +1901,7 @@ void FileData::launchGame()
extraValue = extraValue =
Utils::String::replace(extraValue, "%ROMPATHRAW%", getROMDirectory()); Utils::String::replace(extraValue, "%ROMPATHRAW%", getROMDirectory());
extraValue = Utils::String::replace(extraValue, "%ROMRAW%", romRaw); extraValue = Utils::String::replace(extraValue, "%ROMRAW%", romRaw);
extraValue = Utils::String::replace(extraValue, "%BASENAME%", baseName);
extraValue = Utils::String::replace(extraValue, "//", "/"); extraValue = Utils::String::replace(extraValue, "//", "/");
if (variable == "%EXTRA_") if (variable == "%EXTRA_")
@ -2023,10 +2046,11 @@ returnValue = Utils::Platform::launchGameUnix(command, startDirectory, runInBack
if (returnValue != 0) { if (returnValue != 0) {
LOG(LogWarning) << "Launch terminated with nonzero return value " << returnValue; LOG(LogWarning) << "Launch terminated with nonzero return value " << returnValue;
window->queueInfoPopup("ERROR LAUNCHING GAME '" + window->queueInfoPopup(
Utils::String::toUpper(metadata.get("name")) + "' (ERROR CODE " + Utils::String::format(_("ERROR LAUNCHING GAME '%s' (ERROR CODE %i)"),
Utils::String::toUpper(std::to_string(returnValue) + ")"), Utils::String::toUpper(metadata.get("name")).c_str(),
6000); returnValue),
6000);
window->setAllowTextScrolling(true); window->setAllowTextScrolling(true);
window->setAllowFileAnimation(true); window->setAllowFileAnimation(true);
} }
@ -2102,8 +2126,13 @@ returnValue = Utils::Platform::launchGameUnix(command, startDirectory, runInBack
gameToUpdate->metadata.get("lastplayed")); gameToUpdate->metadata.get("lastplayed"));
} }
CollectionSystemsManager::getInstance()->refreshCollectionSystems(gameToUpdate); // We make an explicit call to close the launch screen instead of waiting for
// AnimationController to do it as that would be done too late. This is so because on
// gamelist reload the helpsystem uses the state of the launch screen to select between
// the dimmed and undimmed element properties.
window->closeLaunchScreen();
CollectionSystemsManager::getInstance()->refreshCollectionSystems(gameToUpdate);
gameToUpdate->mSystem->onMetaDataSavePoint(); gameToUpdate->mSystem->onMetaDataSavePoint();
} }

View file

@ -97,6 +97,8 @@ public:
const bool getDeletionFlag() const { return mDeletionFlag; } const bool getDeletionFlag() const { return mDeletionFlag; }
void setDeletionFlag(bool setting) { mDeletionFlag = setting; } void setDeletionFlag(bool setting) { mDeletionFlag = setting; }
const bool getNoLoad() const { return mNoLoad; }
void setNoLoad(bool state) { mNoLoad = state; }
const bool isPlaceHolder() const { return mType == PLACEHOLDER; } const bool isPlaceHolder() const { return mType == PLACEHOLDER; }
void refreshMetadata() { metadata = mSourceFileData->metadata; } void refreshMetadata() { metadata = mSourceFileData->metadata; }
@ -154,6 +156,10 @@ public:
const std::string& getSortTypeString() const { return mSortTypeString; } const std::string& getSortTypeString() const { return mSortTypeString; }
const FileData::SortType& getSortTypeFromString(const std::string& desc) const; const FileData::SortType& getSortTypeFromString(const std::string& desc) const;
static inline std::vector<std::string> sImageExtensions {".png", ".jpg"};
static inline std::vector<std::string> sVideoExtensions {".mp4", ".mkv", ".avi",
".mp4", ".wmv", ".mov"};
protected: protected:
FileData* mSourceFileData; FileData* mSourceFileData;
FileData* mParent; FileData* mParent;
@ -171,9 +177,7 @@ private:
std::vector<FileData*> mChildrenLastPlayed; std::vector<FileData*> mChildrenLastPlayed;
std::vector<FileData*> mChildrenMostPlayed; std::vector<FileData*> mChildrenMostPlayed;
std::function<void()> mUpdateListCallback; std::function<void()> mUpdateListCallback;
static inline std::vector<std::string> sImageExtensions {".png", ".jpg"};
static inline std::vector<std::string> sVideoExtensions {".mp4", ".mkv", ".avi",
".mp4", ".wmv", ".mov"};
// The pair includes all games, and favorite games. // The pair includes all games, and favorite games.
std::pair<unsigned int, unsigned int> mGameCount; std::pair<unsigned int, unsigned int> mGameCount;
bool mOnlyFolders; bool mOnlyFolders;
@ -182,6 +186,7 @@ private:
bool mUpdateChildrenMostPlayed; bool mUpdateChildrenMostPlayed;
// Used for flagging a game for deletion from its gamelist.xml file. // Used for flagging a game for deletion from its gamelist.xml file.
bool mDeletionFlag; bool mDeletionFlag;
bool mNoLoad;
}; };
class CollectionFileData : public FileData class CollectionFileData : public FileData

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// //
// ES-DE // ES-DE Frontend
// FileFilterIndex.cpp // FileFilterIndex.cpp
// //
// Gamelist filters. // Gamelist filters.
@ -12,6 +12,7 @@
#include "Log.h" #include "Log.h"
#include "Settings.h" #include "Settings.h"
#include "UIModeController.h" #include "UIModeController.h"
#include "utils/LocalizationUtil.h"
#include "utils/StringUtil.h" #include "utils/StringUtil.h"
#include "views/ViewController.h" #include "views/ViewController.h"
@ -40,23 +41,29 @@ FileFilterIndex::FileFilterIndex()
// clang-format off // clang-format off
FilterDataDecl filterDecls[] = { FilterDataDecl filterDecls[] = {
//type //allKeys //filteredBy //filteredKeys //primaryKey //hasSecondaryKey //secondaryKey //menuLabel //type //allKeys //filteredBy //filteredKeys //primaryKey //hasSecondaryKey //secondaryKey //menuLabel
{RATINGS_FILTER, &mRatingsIndexAllKeys, &mFilterByRatings, &mRatingsIndexFilteredKeys, "rating", false, "", "RATING"}, {RATINGS_FILTER, &mRatingsIndexAllKeys, &mFilterByRatings, &mRatingsIndexFilteredKeys, "rating", false, "", _("RATING")},
{DEVELOPER_FILTER, &mDeveloperIndexAllKeys, &mFilterByDeveloper, &mDeveloperIndexFilteredKeys, "developer", false, "", "DEVELOPER"}, {DEVELOPER_FILTER, &mDeveloperIndexAllKeys, &mFilterByDeveloper, &mDeveloperIndexFilteredKeys, "developer", false, "", _("DEVELOPER")},
{PUBLISHER_FILTER, &mPublisherIndexAllKeys, &mFilterByPublisher, &mPublisherIndexFilteredKeys, "publisher", false, "", "PUBLISHER"}, {PUBLISHER_FILTER, &mPublisherIndexAllKeys, &mFilterByPublisher, &mPublisherIndexFilteredKeys, "publisher", false, "", _("PUBLISHER")},
{GENRE_FILTER, &mGenreIndexAllKeys, &mFilterByGenre, &mGenreIndexFilteredKeys, "genre", true, "genre", "GENRE"}, {GENRE_FILTER, &mGenreIndexAllKeys, &mFilterByGenre, &mGenreIndexFilteredKeys, "genre", true, "genre", _("GENRE")},
{PLAYER_FILTER, &mPlayersIndexAllKeys, &mFilterByPlayers, &mPlayersIndexFilteredKeys, "players", false, "", "PLAYERS"}, {PLAYER_FILTER, &mPlayersIndexAllKeys, &mFilterByPlayers, &mPlayersIndexFilteredKeys, "players", false, "", _("PLAYERS")},
{FAVORITES_FILTER, &mFavoritesIndexAllKeys, &mFilterByFavorites, &mFavoritesIndexFilteredKeys, "favorite", false, "", "FAVORITE"}, {FAVORITES_FILTER, &mFavoritesIndexAllKeys, &mFilterByFavorites, &mFavoritesIndexFilteredKeys, "favorite", false, "", _("FAVORITE")},
{COMPLETED_FILTER, &mCompletedIndexAllKeys, &mFilterByCompleted, &mCompletedIndexFilteredKeys, "completed", false, "", "COMPLETED"}, {COMPLETED_FILTER, &mCompletedIndexAllKeys, &mFilterByCompleted, &mCompletedIndexFilteredKeys, "completed", false, "", _("COMPLETED")},
{KIDGAME_FILTER, &mKidGameIndexAllKeys, &mFilterByKidGame, &mKidGameIndexFilteredKeys, "kidgame", false, "", "KIDGAME"}, {KIDGAME_FILTER, &mKidGameIndexAllKeys, &mFilterByKidGame, &mKidGameIndexFilteredKeys, "kidgame", false, "", _("KIDGAME")},
{HIDDEN_FILTER, &mHiddenIndexAllKeys, &mFilterByHidden, &mHiddenIndexFilteredKeys, "hidden", false, "", "HIDDEN"}, {HIDDEN_FILTER, &mHiddenIndexAllKeys, &mFilterByHidden, &mHiddenIndexFilteredKeys, "hidden", false, "", _("HIDDEN")},
{BROKEN_FILTER, &mBrokenIndexAllKeys, &mFilterByBroken, &mBrokenIndexFilteredKeys, "broken", false, "", "BROKEN"}, {BROKEN_FILTER, &mBrokenIndexAllKeys, &mFilterByBroken, &mBrokenIndexFilteredKeys, "broken", false, "", _("BROKEN")},
{CONTROLLER_FILTER, &mControllerIndexAllKeys, &mFilterByController, &mControllerIndexFilteredKeys, "controller", false, "", "CONTROLLER"}, {CONTROLLER_FILTER, &mControllerIndexAllKeys, &mFilterByController, &mControllerIndexFilteredKeys, "controller", false, "", _("CONTROLLER")},
{ALTEMULATOR_FILTER, &mAltemulatorIndexAllKeys, &mFilterByAltemulator, &mAltemulatorIndexFilteredKeys, "altemulator", false, "", "ALTERNATIVE EMULATOR"} {ALTEMULATOR_FILTER, &mAltemulatorIndexAllKeys, &mFilterByAltemulator, &mAltemulatorIndexFilteredKeys, "altemulator", false, "", _("ALTERNATIVE EMULATOR")}
}; };
// clang-format on // clang-format on
filterDataDecl = std::vector<FilterDataDecl>( filterDataDecl = std::vector<FilterDataDecl>(
filterDecls, filterDecls + sizeof(filterDecls) / sizeof(filterDecls[0])); filterDecls, filterDecls + sizeof(filterDecls) / sizeof(filterDecls[0]));
#if defined(GETTEXT_DUMMY_ENTRIES)
// This is just to get gettext msgid entries added to the PO message catalog files.
_("FALSE");
_("TRUE");
#endif
} }
FileFilterIndex::~FileFilterIndex() FileFilterIndex::~FileFilterIndex()
@ -148,10 +155,10 @@ std::string FileFilterIndex::getIndexableKey(FileData* game,
ratingNumber = 0; ratingNumber = 0;
if (ratingNumber == 5) if (ratingNumber == 5)
key = "5 STARS"; key = "5";
else else
key = std::to_string(ratingNumber) + " - " + key = std::to_string(ratingNumber) + " - " +
std::to_string(ratingNumber) + ".5 STARS"; std::to_string(ratingNumber) + ".5";
} }
catch (int e) { catch (int e) {
LOG(LogError) << "Error parsing Rating (invalid value, exception nr.): " LOG(LogError) << "Error parsing Rating (invalid value, exception nr.): "
@ -239,10 +246,10 @@ std::string FileFilterIndex::getIndexableKey(FileData* game,
if ((type == GENRE_FILTER || type == PLAYER_FILTER || type == DEVELOPER_FILTER || if ((type == GENRE_FILTER || type == PLAYER_FILTER || type == DEVELOPER_FILTER ||
type == PUBLISHER_FILTER) && type == PUBLISHER_FILTER) &&
Utils::String::toUpper(key) == UNKNOWN_LABEL) Utils::String::toUpper(key) == UNKNOWN_LABEL)
key = ViewController::CROSSEDCIRCLE_CHAR + " UNKNOWN"; key = ViewController::CROSSEDCIRCLE_CHAR + " " + _("UNKNOWN");
else if ((type == CONTROLLER_FILTER || type == ALTEMULATOR_FILTER) && key.empty()) else if ((type == CONTROLLER_FILTER || type == ALTEMULATOR_FILTER) && key.empty())
key = ViewController::CROSSEDCIRCLE_CHAR + " NONE SELECTED"; key = ViewController::CROSSEDCIRCLE_CHAR + " " + _("NONE SELECTED");
else if (key.empty() || (type == RATINGS_FILTER && key == "0 STARS")) else if (key.empty() || (type == RATINGS_FILTER && key == "0"))
key = UNKNOWN_LABEL; key = UNKNOWN_LABEL;
return key; return key;

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// //
// ES-DE // ES-DE Frontend
// FileFilterIndex.h // FileFilterIndex.h
// //
// Gamelist filters. // Gamelist filters.
@ -9,7 +9,7 @@
#ifndef ES_APP_FILE_FILTER_INDEX_H #ifndef ES_APP_FILE_FILTER_INDEX_H
#define ES_APP_FILE_FILTER_INDEX_H #define ES_APP_FILE_FILTER_INDEX_H
#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) #if defined(__APPLE__) || defined(__FreeBSD__)
#include <sstream> #include <sstream>
#endif #endif

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// //
// ES-DE // ES-DE Frontend
// FileSorts.cpp // FileSorts.cpp
// //
// Gamelist sorting functions. // Gamelist sorting functions.
@ -10,6 +10,7 @@
#include "FileSorts.h" #include "FileSorts.h"
#include "SystemData.h" #include "SystemData.h"
#include "utils/LocalizationUtil.h"
#include "utils/StringUtil.h" #include "utils/StringUtil.h"
#include <algorithm> #include <algorithm>
@ -274,4 +275,30 @@ namespace FileSorts
return system1.compare(system2) > 0; return system1.compare(system2) > 0;
} }
#if defined(GETTEXT_DUMMY_ENTRIES)
void gettextMessageCatalogEntries()
{
_("name, ascending");
_("name, descending");
_("rating, ascending");
_("rating, descending");
_("release date, ascending");
_("release date, descending");
_("developer, ascending");
_("developer, descending");
_("publisher, ascending");
_("publisher, descending");
_("genre, ascending");
_("genre, descending");
_("players, ascending");
_("players, descending");
_("last played, ascending");
_("last played, descending");
_("times played, ascending");
_("times played, descending");
_("system, ascending");
_("system, descending");
}
#endif
} // namespace FileSorts } // namespace FileSorts

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// //
// ES-DE // ES-DE Frontend
// FileSorts.h // FileSorts.h
// //
// Gamelist sorting functions. // Gamelist sorting functions.
@ -37,6 +37,11 @@ namespace FileSorts
bool compareSystem(const FileData* file1, const FileData* file2); bool compareSystem(const FileData* file1, const FileData* file2);
bool compareSystemDescending(const FileData* file1, const FileData* file2); bool compareSystemDescending(const FileData* file1, const FileData* file2);
#if defined(GETTEXT_DUMMY_ENTRIES)
// This is just to get gettext msgid entries added to the PO message catalog files.
void gettextMessageCatalogEntries();
#endif
extern const std::vector<FileData::SortType> SortTypes; extern const std::vector<FileData::SortType> SortTypes;
} // namespace FileSorts } // namespace FileSorts

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// //
// ES-DE // ES-DE Frontend
// GamelistFileParser.cpp // GamelistFileParser.cpp
// //
// Parses and updates the gamelist.xml files. // Parses and updates the gamelist.xml files.
@ -59,15 +59,20 @@ namespace GamelistFileParser
if (found) if (found)
treeNode = children.at(key); treeNode = children.at(key);
if (treeNode->getNoLoad())
return treeNode;
// This is the end. // This is the end.
if (path_it == --pathList.end()) { if (path_it == --pathList.end()) {
if (found) if (found)
return treeNode; return treeNode;
if (type == FOLDER) { if (type == FOLDER) {
LOG(LogWarning) << "A folder defined in gamelist.xml does not exist or " if (!Utils::FileSystem::exists(path + "/noload.txt")) {
"contains no valid games: \"" LOG(LogWarning) << "A folder defined in gamelist.xml does not exist or "
<< path << "\""; "contains no valid games: \""
<< path << "\"";
}
return nullptr; return nullptr;
} }
@ -132,12 +137,21 @@ namespace GamelistFileParser
if (!Utils::FileSystem::exists(xmlpath)) { if (!Utils::FileSystem::exists(xmlpath)) {
LOG(LogDebug) << "GamelistFileParser::parseGamelist(): System \"" << system->getName() LOG(LogDebug) << "GamelistFileParser::parseGamelist(): System \"" << system->getName()
<< "\" does not have a gamelist.xml file"; << "\" does not have a gamelist.xml file";
// Get rid of any orphaned noload.txt folder entries.
for (auto child : system->getRootFolder()->getChildrenRecursive()) {
if (child->getNoLoad())
delete child;
}
return; return;
} }
if (Utils::FileSystem::getFileSize(xmlpath) == 0) { if (Utils::FileSystem::getFileSize(xmlpath) == 0) {
LOG(LogWarning) << "GamelistFileParser::parseGamelist(): System \"" << system->getName() LOG(LogWarning) << "GamelistFileParser::parseGamelist(): System \"" << system->getName()
<< "\" has an empty gamelist.xml file"; << "\" has an empty gamelist.xml file";
for (auto child : system->getRootFolder()->getChildrenRecursive()) {
if (child->getNoLoad())
delete child;
}
return; return;
} }
@ -231,6 +245,9 @@ namespace GamelistFileParser
FileData* file {findOrCreateFile(system, path, type)}; FileData* file {findOrCreateFile(system, path, type)};
if (file != nullptr && file->getNoLoad())
continue;
// Don't load entries with the wrong type. This should very rarely (if ever) happen. // Don't load entries with the wrong type. This should very rarely (if ever) happen.
if (file != nullptr && ((tag == "game" && file->getType() == FOLDER) || if (file != nullptr && ((tag == "game" && file->getType() == FOLDER) ||
(tag == "folder" && file->getType() == GAME))) { (tag == "folder" && file->getType() == GAME))) {
@ -240,13 +257,15 @@ namespace GamelistFileParser
} }
if (!file) { if (!file) {
if (!Utils::FileSystem::exists(path + "/noload.txt")) {
#if defined(_WIN64) #if defined(_WIN64)
LOG(LogWarning) LOG(LogWarning)
<< "Couldn't process \"" << Utils::String::replace(path, "/", "\\") << "Couldn't process \"" << Utils::String::replace(path, "/", "\\")
<< "\", skipping entry"; << "\", skipping entry";
#else #else
LOG(LogWarning) << "Couldn't process \"" << path << "\", skipping entry"; LOG(LogWarning) << "Couldn't process \"" << path << "\", skipping entry";
#endif #endif
}
continue; continue;
} }
else if (!file->isArcadeAsset()) { else if (!file->isArcadeAsset()) {
@ -296,6 +315,11 @@ namespace GamelistFileParser
} }
} }
} }
// Get rid of any orphaned noload.txt folder entries.
for (auto child : system->getRootFolder()->getChildrenRecursive()) {
if (child->getNoLoad())
delete child;
}
} }
} }

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// //
// ES-DE // ES-DE Frontend
// GamelistFileParser.h // GamelistFileParser.h
// //
// Parses and updates the gamelist.xml files. // Parses and updates the gamelist.xml files.

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// //
// ES-DE // ES-DE Frontend
// MediaViewer.cpp // MediaViewer.cpp
// //
// Fullscreen game media viewer. // Fullscreen game media viewer.
@ -10,6 +10,7 @@
#include "Sound.h" #include "Sound.h"
#include "components/VideoFFmpegComponent.h" #include "components/VideoFFmpegComponent.h"
#include "utils/LocalizationUtil.h"
#include "views/ViewController.h" #include "views/ViewController.h"
#define KEY_REPEAT_START_DELAY 600 #define KEY_REPEAT_START_DELAY 600
@ -44,8 +45,6 @@ bool MediaViewer::startMediaViewer(FileData* game)
mKeyRepeatDir = 0; mKeyRepeatDir = 0;
mKeyRepeatTimer = 0; mKeyRepeatTimer = 0;
ViewController::getInstance()->pauseViewVideos();
mShowMediaTypes = Settings::getInstance()->getBool("MediaViewerShowTypes"); mShowMediaTypes = Settings::getInstance()->getBool("MediaViewerShowTypes");
if (Settings::getInstance()->getString("MediaViewerHelpPrompts") == "disabled") if (Settings::getInstance()->getString("MediaViewerHelpPrompts") == "disabled")
@ -68,6 +67,7 @@ bool MediaViewer::startMediaViewer(FileData* game)
if (!mHasVideo && !mHasImages) if (!mHasVideo && !mHasImages)
return false; return false;
ViewController::getInstance()->pauseViewVideos();
Window::getInstance()->stopInfoPopup(); Window::getInstance()->stopInfoPopup();
HelpStyle style; HelpStyle style;
@ -79,7 +79,7 @@ bool MediaViewer::startMediaViewer(FileData* game)
mEntryCount = std::to_string(mImages.size() + (mVideo == nullptr ? 0 : 1)); mEntryCount = std::to_string(mImages.size() + (mVideo == nullptr ? 0 : 1));
mMediaType = mMediaType =
std::make_unique<TextComponent>((mHasVideo ? "VIDEO" : mImageFiles[0].second.mediaType), std::make_unique<TextComponent>((mHasVideo ? _("VIDEO") : mImageFiles[0].second.mediaType),
Font::get(FONT_SIZE_MINI, FONT_PATH_REGULAR), 0xAAAAAAFF); Font::get(FONT_SIZE_MINI, FONT_PATH_REGULAR), 0xAAAAAAFF);
mMediaType->setOrigin(0.0f, 0.5f); mMediaType->setOrigin(0.0f, 0.5f);
@ -246,11 +246,11 @@ void MediaViewer::render(const glm::mat4& /*parentTrans*/)
std::vector<HelpPrompt> MediaViewer::getHelpPrompts() std::vector<HelpPrompt> MediaViewer::getHelpPrompts()
{ {
std::vector<HelpPrompt> prompts; std::vector<HelpPrompt> prompts;
prompts.push_back(HelpPrompt("left/right", "browse")); prompts.push_back(HelpPrompt("left/right", _("browse")));
if (mHasManual) if (mHasManual)
prompts.push_back(HelpPrompt("up", "pdf manual")); prompts.push_back(HelpPrompt("up", _("pdf manual")));
prompts.push_back(HelpPrompt("lt", "first")); prompts.push_back(HelpPrompt("lt", _("first")));
prompts.push_back(HelpPrompt("rt", "last")); prompts.push_back(HelpPrompt("rt", _("last")));
return prompts; return prompts;
} }
@ -280,34 +280,34 @@ void MediaViewer::findMedia()
} }
if (!mHasVideo && (mediaFile = mGame->getScreenshotPath()) != "") { if (!mHasVideo && (mediaFile = mGame->getScreenshotPath()) != "") {
mImageFiles.push_back(std::make_pair(mediaFile, ImageInfo("SCREENSHOT", false))); mImageFiles.push_back(std::make_pair(mediaFile, ImageInfo(_("SCREENSHOT"), false)));
mScreenshotIndex = 0; mScreenshotIndex = 0;
} }
if ((mediaFile = mGame->getCoverPath()) != "") if ((mediaFile = mGame->getCoverPath()) != "")
mImageFiles.push_back(std::make_pair(mediaFile, ImageInfo("BOX COVER", true))); mImageFiles.push_back(std::make_pair(mediaFile, ImageInfo(_("BOX COVER"), true)));
if ((mediaFile = mGame->getBackCoverPath()) != "") if ((mediaFile = mGame->getBackCoverPath()) != "")
mImageFiles.push_back(std::make_pair(mediaFile, ImageInfo("BOX BACK COVER", true))); mImageFiles.push_back(std::make_pair(mediaFile, ImageInfo(_("BOX BACK COVER"), true)));
if ((mediaFile = mGame->getTitleScreenPath()) != "") { if ((mediaFile = mGame->getTitleScreenPath()) != "") {
mImageFiles.push_back(std::make_pair(mediaFile, ImageInfo("TITLE SCREEN", false))); mImageFiles.push_back(std::make_pair(mediaFile, ImageInfo(_("TITLE SCREEN"), false)));
mTitleScreenIndex = static_cast<int>(mImageFiles.size() - 1); mTitleScreenIndex = static_cast<int>(mImageFiles.size() - 1);
} }
if (mHasVideo && (mediaFile = mGame->getScreenshotPath()) != "") { if (mHasVideo && (mediaFile = mGame->getScreenshotPath()) != "") {
mImageFiles.push_back(std::make_pair(mediaFile, ImageInfo("SCREENSHOT", false))); mImageFiles.push_back(std::make_pair(mediaFile, ImageInfo(_("SCREENSHOT"), false)));
mScreenshotIndex = static_cast<int>(mImageFiles.size() - 1); mScreenshotIndex = static_cast<int>(mImageFiles.size() - 1);
} }
if ((mediaFile = mGame->getFanArtPath()) != "") if ((mediaFile = mGame->getFanArtPath()) != "")
mImageFiles.push_back(std::make_pair(mediaFile, ImageInfo("FAN ART", true))); mImageFiles.push_back(std::make_pair(mediaFile, ImageInfo(_("FAN ART"), true)));
if ((mediaFile = mGame->getMiximagePath()) != "") if ((mediaFile = mGame->getMiximagePath()) != "")
mImageFiles.push_back(std::make_pair(mediaFile, ImageInfo("MIXIMAGE", true))); mImageFiles.push_back(std::make_pair(mediaFile, ImageInfo(_("MIXIMAGE"), true)));
if ((mediaFile = mGame->getCustomImagePath()) != "") if ((mediaFile = mGame->getCustomImagePath()) != "")
mImageFiles.push_back(std::make_pair(mediaFile, ImageInfo("CUSTOM", true))); mImageFiles.push_back(std::make_pair(mediaFile, ImageInfo(_("CUSTOM"), true)));
if (!mImageFiles.empty()) if (!mImageFiles.empty())
mHasImages = true; mHasImages = true;
@ -406,7 +406,7 @@ void MediaViewer::showPrevious()
} }
else if (mCurrentImageIndex == 0 && mHasVideo) { else if (mCurrentImageIndex == 0 && mHasVideo) {
mDisplayingImage = false; mDisplayingImage = false;
mMediaType->setText("VIDEO"); mMediaType->setText(_("VIDEO"));
playVideo(); playVideo();
return; return;
} }
@ -425,7 +425,8 @@ void MediaViewer::showFirst()
return; return;
mCurrentImageIndex = 0; mCurrentImageIndex = 0;
mMediaType->setText((mHasVideo ? "VIDEO" : mImageFiles[mCurrentImageIndex].second.mediaType)); mMediaType->setText(
(mHasVideo ? _("VIDEO") : mImageFiles[mCurrentImageIndex].second.mediaType));
if (mHasVideo) { if (mHasVideo) {
mDisplayingImage = false; mDisplayingImage = false;

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// //
// ES-DE // ES-DE Frontend
// MediaViewer.h // MediaViewer.h
// //
// Fullscreen game media viewer. // Fullscreen game media viewer.

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// //
// ES-DE // ES-DE Frontend
// MetaData.cpp // MetaData.cpp
// //
// Static data for default metadata values as well as functions // Static data for default metadata values as well as functions
@ -11,6 +11,7 @@
#include "Log.h" #include "Log.h"
#include "utils/FileSystemUtil.h" #include "utils/FileSystemUtil.h"
#include "utils/LocalizationUtil.h"
#include <pugixml.hpp> #include <pugixml.hpp>
@ -21,50 +22,50 @@ namespace
// saving the values in GuiMetaDataEd. // saving the values in GuiMetaDataEd.
MetaDataDecl gameDecls[] { MetaDataDecl gameDecls[] {
// Key Type Default value Statistic Name in GuiMetaDataEd Prompt in GuiMetaDataEd Scrape // Key Type Default value Statistic Name in GuiMetaDataEd Prompt in GuiMetaDataEd Scrape
{"name", MD_STRING, "", false, "name", "enter name", true}, {"name", MD_STRING, "", false, "NAME", "ENTER NAME", true},
{"sortname", MD_STRING, "", false, "sortname", "enter sortname", false}, {"sortname", MD_STRING, "", false, "SORTNAME", "ENTER SORTNAME", false},
{"collectionsortname", MD_STRING, "", false, "custom collections sortname", "enter collections sortname", false}, {"collectionsortname", MD_STRING, "", false, "CUSTOM COLLECTIONS SORTNAME", "ENTER COLLECTIONS SORTNAME", false},
{"desc", MD_MULTILINE_STRING, "", false, "description", "enter description", true}, {"desc", MD_MULTILINE_STRING, "", false, "DESCRIPTION", "ENTER DESCRIPTION", true},
{"rating", MD_RATING, "0", false, "rating", "enter rating", true}, {"rating", MD_RATING, "0", false, "RATING", "ENTER RATING", true},
{"releasedate", MD_DATE, "19700101T000000", false, "release date", "enter release date", true}, {"releasedate", MD_DATE, "19700101T000000", false, "RELEASE DATE", "ENTER RELEASE DATE", true},
{"developer", MD_STRING, "unknown", false, "developer", "enter developer", true}, {"developer", MD_STRING, "unknown", false, "DEVELOPER", "ENTER DEVELOPER", true},
{"publisher", MD_STRING, "unknown", false, "publisher", "enter publisher", true}, {"publisher", MD_STRING, "unknown", false, "PUBLISHER", "ENTER PUBLISHER", true},
{"genre", MD_STRING, "unknown", false, "genre", "enter genre", true}, {"genre", MD_STRING, "unknown", false, "GENRE", "ENTER GENRE", true},
{"players", MD_STRING, "unknown", false, "players", "enter number of players", true}, {"players", MD_STRING, "unknown", false, "PLAYERS", "ENTER NUMBER OF PLAYERS", true},
{"favorite", MD_BOOL, "false", false, "favorite", "enter favorite off/on", false}, {"favorite", MD_BOOL, "false", false, "FAVORITE", "ENTER FAVORITE OFF/ON", false},
{"completed", MD_BOOL, "false", false, "completed", "enter completed off/on", false}, {"completed", MD_BOOL, "false", false, "COMPLETED", "ENTER COMPLETED OFF/ON", false},
{"kidgame", MD_BOOL, "false", false, "kidgame", "enter kidgame off/on", false}, {"kidgame", MD_BOOL, "false", false, "KIDGAME", "ENTER KIDGAME OFF/ON", false},
{"hidden", MD_BOOL, "false", false, "hidden", "enter hidden off/on", false}, {"hidden", MD_BOOL, "false", false, "HIDDEN", "ENTER HIDDEN OFF/ON", false},
{"broken", MD_BOOL, "false", false, "broken/not working", "enter broken off/on", false}, {"broken", MD_BOOL, "false", false, "BROKEN/NOT WORKING", "ENTER BROKEN OFF/ON", false},
{"nogamecount", MD_BOOL, "false", false, "exclude from game counter", "enter don't count as game off/on", false}, {"nogamecount", MD_BOOL, "false", false, "EXCLUDE FROM GAME COUNTER", "ENTER DON'T COUNT AS GAME OFF/ON", false},
{"nomultiscrape", MD_BOOL, "false", false, "exclude from multi-scraper", "enter no multi-scrape off/on", false}, {"nomultiscrape", MD_BOOL, "false", false, "EXCLUDE FROM MULTI-SCRAPER", "ENTER NO MULTI-SCRAPE OFF/ON", false},
{"hidemetadata", MD_BOOL, "false", false, "hide metadata fields", "enter hide metadata off/on", false}, {"hidemetadata", MD_BOOL, "false", false, "HIDE METADATA FIELDS", "ENTER HIDE METADATA OFF/ON", false},
{"playcount", MD_INT, "0", false, "times played", "enter number of times played", false}, {"playcount", MD_INT, "0", false, "TIMES PLAYED", "ENTER NUMBER OF TIMES PLAYED", false},
{"controller", MD_CONTROLLER, "", false, "controller", "select controller", true}, {"controller", MD_CONTROLLER, "", false, "CONTROLLER", "SELECT CONTROLLER", true},
{"altemulator", MD_ALT_EMULATOR, "", false, "alternative emulator", "select alternative emulator", false}, {"altemulator", MD_ALT_EMULATOR, "", false, "ALTERNATIVE EMULATOR", "SELECT ALTERNATIVE EMULATOR", false},
{"lastplayed", MD_TIME, "0", true, "last played", "enter last played date", false} {"lastplayed", MD_TIME, "0", true, "LAST PLAYED", "ENTER LAST PLAYED DATE", false}
}; };
MetaDataDecl folderDecls[] { MetaDataDecl folderDecls[] {
// Key Type Default value Statistic Name in GuiMetaDataEd Prompt in GuiMetaDataEd Scrape // Key Type Default value Statistic Name in GuiMetaDataEd Prompt in GuiMetaDataEd Scrape
{"name", MD_STRING, "", false, "name", "enter name", true}, {"name", MD_STRING, "", false, "NAME", "ENTER NAME", true},
{"desc", MD_MULTILINE_STRING, "", false, "description", "enter description", true}, {"desc", MD_MULTILINE_STRING, "", false, "DESCRIPTION", "ENTER DESCRIPTION", true},
{"rating", MD_RATING, "0", false, "rating", "enter rating", true}, {"rating", MD_RATING, "0", false, "RATING", "ENTER RATING", true},
{"releasedate", MD_DATE, "19700101T000000", false, "release date", "enter release date", true}, {"releasedate", MD_DATE, "19700101T000000", false, "RELEASE DATE", "ENTER RELEASE DATE", true},
{"developer", MD_STRING, "unknown", false, "developer", "enter developer", true}, {"developer", MD_STRING, "unknown", false, "DEVELOPER", "ENTER DEVELOPER", true},
{"publisher", MD_STRING, "unknown", false, "publisher", "enter publisher", true}, {"publisher", MD_STRING, "unknown", false, "PUBLISHER", "ENTER PUBLISHER", true},
{"genre", MD_STRING, "unknown", false, "genre", "enter genre", true}, {"genre", MD_STRING, "unknown", false, "GENRE", "ENTER GENRE", true},
{"players", MD_STRING, "unknown", false, "players", "enter number of players", true}, {"players", MD_STRING, "unknown", false, "PLAYERS", "ENTER NUMBER OF PLAYERS", true},
{"favorite", MD_BOOL, "false", false, "favorite", "enter favorite off/on", false}, {"favorite", MD_BOOL, "false", false, "FAVORITE", "ENTER FAVORITE OFF/ON", false},
{"completed", MD_BOOL, "false", false, "completed", "enter completed off/on", false}, {"completed", MD_BOOL, "false", false, "COMPLETED", "ENTER COMPLETED OFF/ON", false},
{"kidgame", MD_BOOL, "false", false, "kidgame (only affects badges)", "enter kidgame off/on", false}, {"kidgame", MD_BOOL, "false", false, "KIDGAME (ONLY AFFECTS BADGES)", "ENTER KIDGAME OFF/ON", false},
{"hidden", MD_BOOL, "false", false, "hidden", "enter hidden off/on", false}, {"hidden", MD_BOOL, "false", false, "HIDDEN", "ENTER HIDDEN OFF/ON", false},
{"broken", MD_BOOL, "false", false, "broken/not working", "enter broken off/on", false}, {"broken", MD_BOOL, "false", false, "BROKEN/NOT WORKING", "ENTER BROKEN OFF/ON", false},
{"nomultiscrape", MD_BOOL, "false", false, "exclude from multi-scraper", "enter no multi-scrape off/on", false}, {"nomultiscrape", MD_BOOL, "false", false, "EXCLUDE FROM MULTI-SCRAPER", "ENTER NO MULTI-SCRAPE OFF/ON", false},
{"hidemetadata", MD_BOOL, "false", false, "hide metadata fields", "enter hide metadata off/on", false}, {"hidemetadata", MD_BOOL, "false", false, "HIDE METADATA FIELDS", "ENTER HIDE METADATA OFF/ON", false},
{"controller", MD_CONTROLLER, "", false, "controller", "select controller", true}, {"controller", MD_CONTROLLER, "", false, "CONTROLLER", "SELECT CONTROLLER", true},
{"folderlink", MD_FOLDER_LINK, "", false, "folder link", "select folder link", false}, {"folderlink", MD_FOLDER_LINK, "", false, "FOLDER LINK", "SELECT FOLDER LINK", false},
{"lastplayed", MD_TIME, "0", true, "last played", "enter last played date", false} {"lastplayed", MD_TIME, "0", true, "LAST PLAYED", "ENTER LAST PLAYED DATE", false}
}; };
// clang-format on // clang-format on
@ -184,3 +185,46 @@ void MetaDataList::resetChangedFlag()
// Reset the change flag. // Reset the change flag.
mWasChanged = false; mWasChanged = false;
} }
#if defined(GETTEXT_DUMMY_ENTRIES)
void gettextMessageCatalogEntries()
{
_p("metadata", "NAME");
_p("metadata", "ENTER NAME");
_p("metadata", "SORTNAME");
_p("metadata", "ENTER SORTNAME");
_p("metadata", "CUSTOM COLLECTIONS SORTNAME");
_p("metadata", "ENTER COLLECTIONS SORTNAME");
_p("metadata", "DESCRIPTION");
_p("metadata", "ENTER DESCRIPTION");
_p("metadata", "RATING");
_p("metadata", "RELEASE DATE");
_p("metadata", "DEVELOPER");
_p("metadata", "ENTER DEVELOPER");
_p("metadata", "PUBLISHER");
_p("metadata", "ENTER PUBLISHER");
_p("metadata", "GENRE");
_p("metadata", "ENTER GENRE");
_p("metadata", "PLAYERS");
_p("metadata", "ENTER NUMBER OF PLAYERS");
_p("metadata", "FAVORITE");
_p("metadata", "COMPLETED");
_p("metadata", "KIDGAME");
_p("metadata", "KIDGAME (ONLY AFFECTS BADGES)");
_p("metadata", "HIDDEN");
_p("metadata", "BROKEN/NOT WORKING");
_p("metadata", "EXCLUDE FROM GAME COUNTER");
_p("metadata", "EXCLUDE FROM MULTI-SCRAPER");
_p("metadata", "HIDE METADATA FIELDS");
_p("metadata", "TIMES PLAYED");
_p("metadata", "ENTER NUMBER OF TIMES PLAYED");
_p("metadata", "CONTROLLER");
_p("metadata", "SELECT CONTROLLER");
_p("metadata", "ALTERNATIVE EMULATOR");
_p("metadata", "SELECT ALTERNATIVE EMULATOR");
_p("metadata", "FOLDER LINK");
_p("metadata", "SELECT FOLDER LINK");
_p("metadata", "LAST PLAYED");
_p("metadata", "ENTER LAST PLAYED DATE");
}
#endif

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// //
// ES-DE // ES-DE Frontend
// MetaData.h // MetaData.h
// //
// Static data for default metadata values as well as functions // Static data for default metadata values as well as functions
@ -10,7 +10,7 @@
#ifndef ES_APP_META_DATA_H #ifndef ES_APP_META_DATA_H
#define ES_APP_META_DATA_H #define ES_APP_META_DATA_H
#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) #if defined(__APPLE__) || defined(__FreeBSD__)
#include <sstream> #include <sstream>
#endif #endif
@ -95,6 +95,11 @@ private:
std::map<std::string, std::string> mMap; std::map<std::string, std::string> mMap;
std::string mNoResult = ""; std::string mNoResult = "";
bool mWasChanged; bool mWasChanged;
#if defined(GETTEXT_DUMMY_ENTRIES)
// This is just to get gettext msgid entries added to the PO message catalog files.
void gettextMessageCatalogEntries();
#endif
}; };
#endif // ES_APP_META_DATA_H #endif // ES_APP_META_DATA_H

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// //
// ES-DE // ES-DE Frontend
// MiximageGenerator.cpp // MiximageGenerator.cpp
// //
// Generates miximages from screenshots, marquees, 3D boxes/covers and physical media images. // Generates miximages from screenshots, marquees, 3D boxes/covers and physical media images.
@ -12,6 +12,7 @@
#include "Log.h" #include "Log.h"
#include "Settings.h" #include "Settings.h"
#include "SystemData.h" #include "SystemData.h"
#include "utils/LocalizationUtil.h"
#include "utils/StringUtil.h" #include "utils/StringUtil.h"
#include <chrono> #include <chrono>
@ -45,7 +46,7 @@ void MiximageGenerator::startThread(std::promise<bool>* miximagePromise)
if ((mScreenshotPath = mGame->getScreenshotPath()) == "") { if ((mScreenshotPath = mGame->getScreenshotPath()) == "") {
LOG(LogDebug) << "MiximageGenerator::MiximageGenerator(): " LOG(LogDebug) << "MiximageGenerator::MiximageGenerator(): "
"No screenshot image found, aborting"; "No screenshot image found, aborting";
mResultMessage = "No screenshot image found, couldn't generate miximage"; mResultMessage = _("No screenshot found, couldn't generate miximage");
mMiximagePromise->set_value(true); mMiximagePromise->set_value(true);
return; return;
} }
@ -136,7 +137,7 @@ bool MiximageGenerator::generateImage()
if (fileFormat == FIF_UNKNOWN) { if (fileFormat == FIF_UNKNOWN) {
LOG(LogError) << "Screenshot image in unknown image format, aborting"; LOG(LogError) << "Screenshot image in unknown image format, aborting";
mMessage = "Screenshot image in unknown format, couldn't generate miximage"; mMessage = _("Screenshot in unknown format, couldn't generate miximage");
return true; return true;
} }
@ -151,13 +152,13 @@ bool MiximageGenerator::generateImage()
} }
else { else {
LOG(LogError) << "Screenshot file format not supported"; LOG(LogError) << "Screenshot file format not supported";
mMessage = "Screenshot image in unsupported format, couldn't generate miximage"; mMessage = _("Screenshot in unsupported format, couldn't generate miximage");
return true; return true;
} }
if (!screenshotFile) { if (!screenshotFile) {
LOG(LogError) << "Error loading screenshot image, corrupt file?"; LOG(LogError) << "Error loading screenshot image, corrupt file?";
mMessage = "Error loading screenshot image, couldn't generate miximage"; mMessage = _("Error loading screenshot, couldn't generate miximage");
return true; return true;
} }
@ -195,7 +196,7 @@ bool MiximageGenerator::generateImage()
#endif #endif
if (!marqueeFile) { if (!marqueeFile) {
LOG(LogError) << "Couldn't load marquee image, corrupt file?"; LOG(LogError) << "Couldn't load marquee image, corrupt file?";
mMessage = "Error loading marquee image, corrupt file?"; mMessage = _("Error loading marquee image, corrupt file?");
mMarquee = false; mMarquee = false;
} }
} }
@ -234,7 +235,7 @@ bool MiximageGenerator::generateImage()
#endif #endif
if (!boxFile) { if (!boxFile) {
LOG(LogError) << "Couldn't load 3D box image, corrupt file?"; LOG(LogError) << "Couldn't load 3D box image, corrupt file?";
mMessage = "Error loading 3d box image, corrupt file?"; mMessage = _("Error loading 3d box image, corrupt file?");
mBox3D = false; mBox3D = false;
} }
} }
@ -272,7 +273,7 @@ bool MiximageGenerator::generateImage()
#endif #endif
if (!boxFile) { if (!boxFile) {
LOG(LogError) << "Couldn't load box cover image, corrupt file?"; LOG(LogError) << "Couldn't load box cover image, corrupt file?";
mMessage = "Error loading box cover image, corrupt file?"; mMessage = _("Error loading box cover image, corrupt file?");
mCover = false; mCover = false;
} }
} }
@ -312,7 +313,7 @@ bool MiximageGenerator::generateImage()
#endif #endif
if (!physicalMediaFile) { if (!physicalMediaFile) {
LOG(LogError) << "Couldn't load physical media image, corrupt file?"; LOG(LogError) << "Couldn't load physical media image, corrupt file?";
mMessage = "Error loading physical media image, corrupt file?"; mMessage = _("Error loading physical media image, corrupt file?");
mPhysicalMedia = false; mPhysicalMedia = false;
} }
} }

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// //
// ES-DE // ES-DE Frontend
// MiximageGenerator.h // MiximageGenerator.h
// //
// Generates miximages from screenshots, marquees, 3D boxes/covers and physical media images. // Generates miximages from screenshots, marquees, 3D boxes/covers and physical media images.

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// //
// ES-DE // ES-DE Frontend
// PDFViewer.cpp // PDFViewer.cpp
// //
// Parses and renders pages using the Poppler library via the external es-pdf-convert binary. // Parses and renders pages using the Poppler library via the external es-pdf-convert binary.
@ -11,6 +11,7 @@
#include "Log.h" #include "Log.h"
#include "Sound.h" #include "Sound.h"
#include "utils/FileSystemUtil.h" #include "utils/FileSystemUtil.h"
#include "utils/LocalizationUtil.h"
#include "utils/StringUtil.h" #include "utils/StringUtil.h"
#include "views/ViewController.h" #include "views/ViewController.h"
@ -185,7 +186,8 @@ bool PDFViewer::startPDFViewer(FileData* game)
mEntryCount = std::to_string(mPages.size()); mEntryCount = std::to_string(mPages.size());
mEntryNumText = std::make_unique<TextComponent>( mEntryNumText = std::make_unique<TextComponent>(
"PAGE 1 OF " + mEntryCount, Font::get(FONT_SIZE_MINI, FONT_PATH_REGULAR), 0xAAAAAAFF); Utils::String::format(_("PAGE %s OF %s"), "1", mEntryCount.c_str()),
Font::get(FONT_SIZE_MINI, FONT_PATH_REGULAR), 0xAAAAAAFF);
mEntryNumText->setOrigin(0.0f, 0.5f); mEntryNumText->setOrigin(0.0f, 0.5f);
if (mHelpInfoPosition == HelpInfoPosition::TOP) { if (mHelpInfoPosition == HelpInfoPosition::TOP) {
@ -706,17 +708,17 @@ std::vector<HelpPrompt> PDFViewer::getHelpPrompts()
{ {
std::vector<HelpPrompt> prompts; std::vector<HelpPrompt> prompts;
if (mZoom > 1.0f) { if (mZoom > 1.0f) {
prompts.push_back(HelpPrompt("up/down/left/right", "pan")); prompts.push_back(HelpPrompt("up/down/left/right", _("pan")));
prompts.push_back(HelpPrompt("ltrt", "reset")); prompts.push_back(HelpPrompt("ltrt", _("reset")));
} }
else { else {
prompts.push_back(HelpPrompt("left/right", "browse")); prompts.push_back(HelpPrompt("left/right", _("browse")));
prompts.push_back(HelpPrompt("down", "game media")); prompts.push_back(HelpPrompt("down", _("game media")));
prompts.push_back(HelpPrompt("lt", "first")); prompts.push_back(HelpPrompt("lt", _("first")));
prompts.push_back(HelpPrompt("rt", "last")); prompts.push_back(HelpPrompt("rt", _("last")));
} }
prompts.push_back(HelpPrompt("lr", "zoom")); prompts.push_back(HelpPrompt("lr", _("zoom")));
return prompts; return prompts;
} }
@ -728,7 +730,8 @@ void PDFViewer::showNextPage()
NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND); NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND);
++mCurrentPage; ++mCurrentPage;
mEntryNumText->setText("PAGE " + std::to_string(mCurrentPage) + " OF " + mEntryCount); mEntryNumText->setText(Utils::String::format(
_("PAGE %s OF %s"), std::to_string(mCurrentPage).c_str(), mEntryCount.c_str()));
convertPage(mCurrentPage); convertPage(mCurrentPage);
} }
@ -739,7 +742,8 @@ void PDFViewer::showPreviousPage()
NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND); NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND);
--mCurrentPage; --mCurrentPage;
mEntryNumText->setText("PAGE " + std::to_string(mCurrentPage) + " OF " + mEntryCount); mEntryNumText->setText(Utils::String::format(
_("PAGE %s OF %s"), std::to_string(mCurrentPage).c_str(), mEntryCount.c_str()));
convertPage(mCurrentPage); convertPage(mCurrentPage);
} }
@ -828,7 +832,8 @@ void PDFViewer::navigateLeftTrigger()
NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND); NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND);
mCurrentPage = 1; mCurrentPage = 1;
mEntryNumText->setText("PAGE " + std::to_string(mCurrentPage) + " OF " + mEntryCount); mEntryNumText->setText(Utils::String::format(
_("PAGE %s OF %s"), std::to_string(mCurrentPage).c_str(), mEntryCount.c_str()));
convertPage(mCurrentPage); convertPage(mCurrentPage);
} }
@ -849,6 +854,7 @@ void PDFViewer::navigateRightTrigger()
NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND); NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND);
mCurrentPage = mPageCount; mCurrentPage = mPageCount;
mEntryNumText->setText("PAGE " + std::to_string(mCurrentPage) + " OF " + mEntryCount); mEntryNumText->setText(Utils::String::format(
_("PAGE %s OF %s"), std::to_string(mCurrentPage).c_str(), mEntryCount.c_str()));
convertPage(mCurrentPage); convertPage(mCurrentPage);
} }

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// //
// ES-DE // ES-DE Frontend
// PDFViewer.h // PDFViewer.h
// //
// Parses and renders pages using the Poppler library via the external es-pdf-convert binary. // Parses and renders pages using the Poppler library via the external es-pdf-convert binary.

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// //
// ES-DE // ES-DE Frontend
// PlatformId.cpp // PlatformId.cpp
// //
// Index of all supported systems/platforms. // Index of all supported systems/platforms.

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// //
// ES-DE // ES-DE Frontend
// PlatformId.h // PlatformId.h
// //
// Index of all supported systems/platforms. // Index of all supported systems/platforms.

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// //
// ES-DE // ES-DE Frontend
// Screensaver.cpp // Screensaver.cpp
// //
// Screensaver, supporting the following types: // Screensaver, supporting the following types:
@ -54,6 +54,8 @@ Screensaver::Screensaver()
void Screensaver::startScreensaver(bool generateMediaList) void Screensaver::startScreensaver(bool generateMediaList)
{ {
ViewController::getInstance()->pauseViewVideos(); ViewController::getInstance()->pauseViewVideos();
mGameOverlay = std::make_unique<TextComponent>("", Font::get(FONT_SIZE_SMALL), 0xFFFFFFFF,
ALIGN_LEFT, ALIGN_CENTER, glm::ivec2 {1, 1});
mScreensaverType = Settings::getInstance()->getString("ScreensaverType"); mScreensaverType = Settings::getInstance()->getString("ScreensaverType");
// In case there is an invalid entry in the es_settings.xml file. // In case there is an invalid entry in the es_settings.xml file.
@ -67,13 +69,6 @@ void Screensaver::startScreensaver(bool generateMediaList)
mFallbackScreensaver = false; mFallbackScreensaver = false;
mOpacity = 0.0f; mOpacity = 0.0f;
// Keep a reference to the default fonts, so they don't keep getting destroyed/recreated.
if (mGameOverlayFont.empty()) {
mGameOverlayFont.push_back(Font::get(FONT_SIZE_SMALL));
mGameOverlayFont.push_back(Font::get(FONT_SIZE_MEDIUM));
mGameOverlayFont.push_back(Font::get(FONT_SIZE_LARGE));
}
// Set mPreviousGame which will be used to avoid showing the same game again during // Set mPreviousGame which will be used to avoid showing the same game again during
// the random selection. // the random selection.
if ((mScreensaverType == "slideshow" || mScreensaverType == "video") && mCurrentGame != nullptr) if ((mScreensaverType == "slideshow" || mScreensaverType == "video") && mCurrentGame != nullptr)
@ -196,6 +191,7 @@ void Screensaver::stopScreensaver()
{ {
mImageScreensaver.reset(); mImageScreensaver.reset();
mVideoScreensaver.reset(); mVideoScreensaver.reset();
mGameOverlay.reset();
mScreensaverActive = false; mScreensaverActive = false;
mDimValue = 1.0f; mDimValue = 1.0f;
@ -203,9 +199,6 @@ void Screensaver::stopScreensaver()
mTextFadeIn = 0; mTextFadeIn = 0;
mSaturationAmount = 1.0f; mSaturationAmount = 1.0f;
if (mGameOverlay)
mGameOverlay.reset();
ViewController::getInstance()->startViewVideos(); ViewController::getInstance()->startViewVideos();
} }
@ -298,8 +291,7 @@ void Screensaver::renderScreensaver()
if (Settings::getInstance()->getBool("ScreensaverSlideshowScanlines")) if (Settings::getInstance()->getBool("ScreensaverSlideshowScanlines"))
mRenderer->shaderPostprocessing(Renderer::Shader::SCANLINES); mRenderer->shaderPostprocessing(Renderer::Shader::SCANLINES);
if (Settings::getInstance()->getBool("ScreensaverSlideshowGameInfo") && if (Settings::getInstance()->getBool("ScreensaverSlideshowGameInfo") &&
!Settings::getInstance()->getBool("ScreensaverSlideshowCustomImages") && !Settings::getInstance()->getBool("ScreensaverSlideshowCustomImages")) {
mGameOverlay) {
mRenderer->setMatrix(mRenderer->getIdentity()); mRenderer->setMatrix(mRenderer->getIdentity());
if (mGameOverlayRectangleCoords.size() == 4) { if (mGameOverlayRectangleCoords.size() == 4) {
mRenderer->drawRect( mRenderer->drawRect(
@ -311,7 +303,7 @@ void Screensaver::renderScreensaver()
mGameOverlay.get()->setColor(0xFFFFFF00 | mTextFadeIn); mGameOverlay.get()->setColor(0xFFFFFF00 | mTextFadeIn);
if (mTextFadeIn > 50) if (mTextFadeIn > 50)
mGameOverlayFont.at(0)->renderTextCache(mGameOverlay.get()); mGameOverlay->render(trans);
if (mTextFadeIn < 255) if (mTextFadeIn < 255)
mTextFadeIn = glm::clamp(mTextFadeIn + 2 + mTextFadeIn / 6, 0, 255); mTextFadeIn = glm::clamp(mTextFadeIn + 2 + mTextFadeIn / 6, 0, 255);
} }
@ -340,7 +332,7 @@ void Screensaver::renderScreensaver()
if (shaders != 0) if (shaders != 0)
mRenderer->shaderPostprocessing(shaders, videoParameters); mRenderer->shaderPostprocessing(shaders, videoParameters);
if (Settings::getInstance()->getBool("ScreensaverVideoGameInfo") && mGameOverlay) { if (Settings::getInstance()->getBool("ScreensaverVideoGameInfo")) {
mRenderer->setMatrix(mRenderer->getIdentity()); mRenderer->setMatrix(mRenderer->getIdentity());
if (mGameOverlayRectangleCoords.size() == 4) { if (mGameOverlayRectangleCoords.size() == 4) {
mRenderer->drawRect( mRenderer->drawRect(
@ -352,7 +344,7 @@ void Screensaver::renderScreensaver()
mGameOverlay.get()->setColor(0xFFFFFF00 | mTextFadeIn); mGameOverlay.get()->setColor(0xFFFFFF00 | mTextFadeIn);
if (mTextFadeIn > 50) if (mTextFadeIn > 50)
mGameOverlayFont.at(0)->renderTextCache(mGameOverlay.get()); mGameOverlay->render(trans);
if (mTextFadeIn < 255) if (mTextFadeIn < 255)
mTextFadeIn = glm::clamp(mTextFadeIn + 2 + mTextFadeIn / 6, 0, 255); mTextFadeIn = glm::clamp(mTextFadeIn + 2 + mTextFadeIn / 6, 0, 255);
} }
@ -418,6 +410,32 @@ void Screensaver::generateImageList()
if (!(*it)->isGameSystem() || (*it)->isCollection()) if (!(*it)->isGameSystem() || (*it)->isCollection())
continue; continue;
// This method of building an inventory of all image files isn't pretty, but to use the
// FileData::getImagePath() function leads to unacceptable performance issues on some
// platforms like Android that offer very poor disk I/O performance. To instead list
// all files recursively is much faster as this avoids stat() function calls which are
// very expensive on such problematic platforms.
const std::string mediaDirMiximages {
FileData::getMediaDirectory() + (*it)->getRootFolder()->getSystemName() + "/miximages"};
const std::string mediaDirScreenshots {FileData::getMediaDirectory() +
(*it)->getRootFolder()->getSystemName() +
"/screenshots"};
const std::string mediaDirTitlescreens {FileData::getMediaDirectory() +
(*it)->getRootFolder()->getSystemName() +
"/titlescreens"};
const std::string mediaDirCovers {FileData::getMediaDirectory() +
(*it)->getRootFolder()->getSystemName() + "/covers"};
const Utils::FileSystem::StringList dirContentMiximages {
Utils::FileSystem::getDirContent(mediaDirMiximages, true)};
const Utils::FileSystem::StringList dirContentScreenshots {
Utils::FileSystem::getDirContent(mediaDirScreenshots, true)};
const Utils::FileSystem::StringList dirContentTitlescreens {
Utils::FileSystem::getDirContent(mediaDirTitlescreens, true)};
const Utils::FileSystem::StringList dirContentCovers {
Utils::FileSystem::getDirContent(mediaDirCovers, true)};
std::string subFolders;
std::vector<FileData*> allFiles {(*it)->getRootFolder()->getFilesRecursive(GAME, true)}; std::vector<FileData*> allFiles {(*it)->getRootFolder()->getFilesRecursive(GAME, true)};
for (auto it2 = allFiles.cbegin(); it2 != allFiles.cend(); ++it2) { for (auto it2 = allFiles.cbegin(); it2 != allFiles.cend(); ++it2) {
// Only include games suitable for children if we're in Kid UI mode. // Only include games suitable for children if we're in Kid UI mode.
@ -426,9 +444,36 @@ void Screensaver::generateImageList()
continue; continue;
if (favoritesOnly && (*it2)->metadata.get("favorite") != "true") if (favoritesOnly && (*it2)->metadata.get("favorite") != "true")
continue; continue;
std::string imagePath {(*it2)->getImagePath()};
if (imagePath != "") subFolders = Utils::String::replace(Utils::FileSystem::getParent((*it2)->getPath()),
mImageFiles.push_back((*it2)); (*it)->getStartPath(), "");
const std::string gamePath {subFolders + "/" + (*it2)->getDisplayName()};
for (auto& extension : FileData::sImageExtensions) {
if (std::find(dirContentMiximages.cbegin(), dirContentMiximages.cend(),
mediaDirMiximages + gamePath + extension) !=
dirContentMiximages.cend()) {
mImageFiles.push_back((*it2));
break;
}
if (std::find(dirContentScreenshots.cbegin(), dirContentScreenshots.cend(),
mediaDirScreenshots + gamePath + extension) !=
dirContentScreenshots.cend()) {
mImageFiles.push_back((*it2));
break;
}
if (std::find(dirContentTitlescreens.cbegin(), dirContentTitlescreens.cend(),
mediaDirTitlescreens + gamePath + extension) !=
dirContentTitlescreens.cend()) {
mImageFiles.push_back((*it2));
break;
}
if (std::find(dirContentCovers.cbegin(), dirContentCovers.cend(),
mediaDirCovers + gamePath + extension) != dirContentCovers.cend()) {
mImageFiles.push_back((*it2));
break;
}
}
} }
} }
@ -445,6 +490,18 @@ void Screensaver::generateVideoList()
if (!(*it)->isGameSystem() || (*it)->isCollection()) if (!(*it)->isGameSystem() || (*it)->isCollection())
continue; continue;
// This method of building an inventory of all video files isn't pretty, but to use the
// FileData::getVideoPath() function leads to unacceptable performance issues on some
// platforms like Android that offer very poor disk I/O performance. To instead list
// all files recursively is much faster as this avoids stat() function calls which are
// very expensive on such problematic platforms.
const std::string mediaDir {FileData::getMediaDirectory() +
(*it)->getRootFolder()->getSystemName() + "/videos"};
const Utils::FileSystem::StringList dirContent {
Utils::FileSystem::getDirContent(mediaDir, true)};
std::string subFolders;
std::vector<FileData*> allFiles {(*it)->getRootFolder()->getFilesRecursive(GAME, true)}; std::vector<FileData*> allFiles {(*it)->getRootFolder()->getFilesRecursive(GAME, true)};
for (auto it2 = allFiles.cbegin(); it2 != allFiles.cend(); ++it2) { for (auto it2 = allFiles.cbegin(); it2 != allFiles.cend(); ++it2) {
// Only include games suitable for children if we're in Kid UI mode. // Only include games suitable for children if we're in Kid UI mode.
@ -453,9 +510,18 @@ void Screensaver::generateVideoList()
continue; continue;
if (favoritesOnly && (*it2)->metadata.get("favorite") != "true") if (favoritesOnly && (*it2)->metadata.get("favorite") != "true")
continue; continue;
std::string videoPath {(*it2)->getVideoPath()};
if (videoPath != "") subFolders = Utils::String::replace(Utils::FileSystem::getParent((*it2)->getPath()),
mVideoFiles.push_back((*it2)); (*it)->getStartPath(), "");
const std::string gamePath {subFolders + "/" + (*it2)->getDisplayName()};
for (auto& extension : FileData::sVideoExtensions) {
if (std::find(dirContent.cbegin(), dirContent.cend(),
mediaDir + gamePath + extension) != dirContent.cend()) {
mVideoFiles.push_back((*it2));
break;
}
}
} }
} }
@ -615,8 +681,8 @@ void Screensaver::generateOverlayInfo()
if (mGameName == "" || mSystemName == "") if (mGameName == "" || mSystemName == "")
return; return;
float posX {mRenderer->getScreenWidth() * 0.023f}; const float posX {mRenderer->getScreenWidth() * 0.023f};
float posY {mRenderer->getScreenHeight() * 0.02f}; const float posY {mRenderer->getScreenHeight() * 0.02f};
const bool favoritesOnly { const bool favoritesOnly {
(mScreensaverType == "video" && (mScreensaverType == "video" &&
@ -633,28 +699,14 @@ void Screensaver::generateOverlayInfo()
const std::string systemName {Utils::String::toUpper(mSystemName)}; const std::string systemName {Utils::String::toUpper(mSystemName)};
const std::string overlayText {gameName + "\n" + systemName}; const std::string overlayText {gameName + "\n" + systemName};
mGameOverlay = std::unique_ptr<TextCache>( mGameOverlay->setText(overlayText);
mGameOverlayFont.at(0)->buildTextCache(overlayText, posX, posY, 0xFFFFFFFF)); mGameOverlay->setPosition(posX, posY);
float textSizeX {0.0f}; const float marginX {mRenderer->getScreenWidth() * 0.01f};
float textSizeY {mGameOverlayFont[0].get()->sizeText(overlayText).y};
// There is a weird issue with sizeText() where the X size value is returned
// as too large if there are two rows in a string and the second row is longer
// than the first row. Possibly it's the newline character that is somehow
// injected in the size calculation. Regardless, this workaround is working
// fine for the time being.
if (mGameOverlayFont[0].get()->sizeText(gameName).x >
mGameOverlayFont[0].get()->sizeText(systemName).x)
textSizeX = mGameOverlayFont[0].get()->sizeText(gameName).x;
else
textSizeX = mGameOverlayFont[0].get()->sizeText(systemName).x;
float marginX {mRenderer->getScreenWidth() * 0.01f};
mGameOverlayRectangleCoords.clear(); mGameOverlayRectangleCoords.clear();
mGameOverlayRectangleCoords.push_back(posX - marginX); mGameOverlayRectangleCoords.push_back(posX - marginX);
mGameOverlayRectangleCoords.push_back(posY); mGameOverlayRectangleCoords.push_back(posY);
mGameOverlayRectangleCoords.push_back(textSizeX + marginX * 2.0f); mGameOverlayRectangleCoords.push_back(mGameOverlay->getSize().x + marginX * 2.0f);
mGameOverlayRectangleCoords.push_back(textSizeY); mGameOverlayRectangleCoords.push_back(mGameOverlay->getSize().y);
} }

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// //
// ES-DE // ES-DE Frontend
// Screensaver.h // Screensaver.h
// //
// Screensaver, supporting the following types: // Screensaver, supporting the following types:
@ -12,8 +12,8 @@
#include "Window.h" #include "Window.h"
#include "components/ImageComponent.h" #include "components/ImageComponent.h"
#include "components/TextComponent.h"
#include "components/VideoComponent.h" #include "components/VideoComponent.h"
#include "resources/Font.h"
class Screensaver : public Window::Screensaver class Screensaver : public Window::Screensaver
{ {
@ -54,6 +54,8 @@ private:
std::vector<std::string> mCustomFilesInventory; std::vector<std::string> mCustomFilesInventory;
std::unique_ptr<ImageComponent> mImageScreensaver; std::unique_ptr<ImageComponent> mImageScreensaver;
std::unique_ptr<VideoComponent> mVideoScreensaver; std::unique_ptr<VideoComponent> mVideoScreensaver;
std::unique_ptr<TextComponent> mGameOverlay;
std::vector<float> mGameOverlayRectangleCoords;
FileData* mCurrentGame; FileData* mCurrentGame;
FileData* mPreviousGame; FileData* mPreviousGame;
@ -73,10 +75,6 @@ private:
unsigned char mRectangleFadeIn; unsigned char mRectangleFadeIn;
unsigned char mTextFadeIn; unsigned char mTextFadeIn;
float mSaturationAmount; float mSaturationAmount;
std::unique_ptr<TextCache> mGameOverlay;
std::vector<std::shared_ptr<Font>> mGameOverlayFont;
std::vector<float> mGameOverlayRectangleCoords;
}; };
#endif // ES_APP_SCREENSAVER_H #endif // ES_APP_SCREENSAVER_H

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// //
// ES-DE // ES-DE Frontend
// SystemData.cpp // SystemData.cpp
// //
// Provides data structures for the game systems and populates and indexes them based // Provides data structures for the game systems and populates and indexes them based
@ -22,6 +22,7 @@
#include "UIModeController.h" #include "UIModeController.h"
#include "resources/ResourceManager.h" #include "resources/ResourceManager.h"
#include "utils/FileSystemUtil.h" #include "utils/FileSystemUtil.h"
#include "utils/LocalizationUtil.h"
#include "utils/StringUtil.h" #include "utils/StringUtil.h"
#include "views/GamelistView.h" #include "views/GamelistView.h"
#include "views/ViewController.h" #include "views/ViewController.h"
@ -61,6 +62,9 @@ void FindRules::loadFindRules()
#elif defined(__APPLE__) #elif defined(__APPLE__)
filePath = filePath =
ResourceManager::getInstance().getResourcePath(":/systems/macos/es_find_rules.xml", false); ResourceManager::getInstance().getResourcePath(":/systems/macos/es_find_rules.xml", false);
#elif defined(__HAIKU__)
filePath =
ResourceManager::getInstance().getResourcePath(":/systems/haiku/es_find_rules.xml", false);
#else #else
filePath = filePath =
ResourceManager::getInstance().getResourcePath(":/systems/unix/es_find_rules.xml", false); ResourceManager::getInstance().getResourcePath(":/systems/unix/es_find_rules.xml", false);
@ -260,7 +264,8 @@ SystemData::SystemData(const std::string& name,
} }
// This placeholder can be used later in the gamelist view. // This placeholder can be used later in the gamelist view.
mPlaceholder = new FileData(PLACEHOLDER, "<No Entries Found>", getSystemEnvData(), this); mPlaceholder =
new FileData(PLACEHOLDER, "<" + _("No Entries Found") + ">", getSystemEnvData(), this);
setIsGameSystemStatus(); setIsGameSystemStatus();
loadTheme(ThemeTriggers::TriggerType::NONE); loadTheme(ThemeTriggers::TriggerType::NONE);
@ -349,6 +354,12 @@ bool SystemData::populateFolder(FileData* folder)
!(isDirectory && extension == ".")) { !(isDirectory && extension == ".")) {
FileData* newGame {new FileData(GAME, filePath, mEnvData, this)}; FileData* newGame {new FileData(GAME, filePath, mEnvData, this)};
if (newGame->metadata.get("name") == "") {
LOG(LogWarning) << "Skipped \"" << filePath << "\" as it has no filename";
delete newGame;
continue;
}
// If adding a configured file extension to a directory it will get interpreted as // If adding a configured file extension to a directory it will get interpreted as
// a regular file. This is useful for displaying multi-file/multi-disc games as single // a regular file. This is useful for displaying multi-file/multi-disc games as single
// entries or for emulators that can get directories passed to them as command line // entries or for emulators that can get directories passed to them as command line
@ -412,6 +423,20 @@ bool SystemData::populateFolder(FileData* folder)
} }
} }
if (Utils::FileSystem::exists(filePath + "/noload.txt")) {
#if defined(_WIN64)
LOG(LogInfo) << "Skipped folder \"" << Utils::String::replace(filePath, "/", "\\")
<< "\" as a noload.txt file is present";
#else
LOG(LogInfo) << "Skipped folder \"" << filePath
<< "\" as a noload.txt file is present";
#endif
FileData* newFolder {new FileData(FOLDER, filePath, mEnvData, this)};
newFolder->setNoLoad(true);
folder->addChild(newFolder);
continue;
}
FileData* newFolder {new FileData(FOLDER, filePath, mEnvData, this)}; FileData* newFolder {new FileData(FOLDER, filePath, mEnvData, this)};
populateFolder(newFolder); populateFolder(newFolder);
@ -984,6 +1009,8 @@ std::vector<std::string> SystemData::getConfigPath()
path = ResourceManager::getInstance().getResourcePath(":/systems/windows/es_systems.xml", true); path = ResourceManager::getInstance().getResourcePath(":/systems/windows/es_systems.xml", true);
#elif defined(__APPLE__) #elif defined(__APPLE__)
path = ResourceManager::getInstance().getResourcePath(":/systems/macos/es_systems.xml", true); path = ResourceManager::getInstance().getResourcePath(":/systems/macos/es_systems.xml", true);
#elif defined(__HAIKU__)
path = ResourceManager::getInstance().getResourcePath(":/systems/haiku/es_systems.xml", true);
#else #else
path = ResourceManager::getInstance().getResourcePath(":/systems/unix/es_systems.xml", true); path = ResourceManager::getInstance().getResourcePath(":/systems/unix/es_systems.xml", true);
#endif #endif
@ -1577,14 +1604,29 @@ void SystemData::loadTheme(ThemeTriggers::TriggerType trigger)
// to the variables that are not applicable. This will be used in ThemeData to make sure // to the variables that are not applicable. This will be used in ThemeData to make sure
// unpopulated system variables do not lead to theme loading errors. // unpopulated system variables do not lead to theme loading errors.
std::map<std::string, std::string> sysData; std::map<std::string, std::string> sysData;
sysData.insert(std::pair<std::string, std::string>("system.name", getName())); std::string name {getName()};
std::string fullName {getFullName()};
#if defined(GETTEXT_DUMMY_ENTRIES)
_p("theme", "all");
_p("theme", "all games");
_p("theme", "recent");
_p("theme", "last played");
_p("theme", "favorites");
_p("theme", "collections");
#endif
// Always translate fullName for the automatic collections.
if (isCollection() && !isCustomCollection()) {
name = _p("theme", name.c_str());
fullName = _p("theme", fullName.c_str());
}
sysData.insert(std::pair<std::string, std::string>("system.name", name));
sysData.insert(std::pair<std::string, std::string>("system.theme", getThemeFolder())); sysData.insert(std::pair<std::string, std::string>("system.theme", getThemeFolder()));
sysData.insert(std::pair<std::string, std::string>("system.fullName", getFullName())); sysData.insert(std::pair<std::string, std::string>("system.fullName", fullName));
if (isCollection() && isCustomCollection()) { if (isCollection() && isCustomCollection()) {
sysData.insert( sysData.insert(
std::pair<std::string, std::string>("system.name.customCollections", getName())); std::pair<std::string, std::string>("system.name.customCollections", name));
sysData.insert(std::pair<std::string, std::string>("system.fullName.customCollections", sysData.insert(
getFullName())); std::pair<std::string, std::string>("system.fullName.customCollections", fullName));
sysData.insert(std::pair<std::string, std::string>("system.theme.customCollections", sysData.insert(std::pair<std::string, std::string>("system.theme.customCollections",
getThemeFolder())); getThemeFolder()));
sysData.insert( sysData.insert(
@ -1600,9 +1642,9 @@ void SystemData::loadTheme(ThemeTriggers::TriggerType trigger)
} }
else if (isCollection()) { else if (isCollection()) {
sysData.insert( sysData.insert(
std::pair<std::string, std::string>("system.name.autoCollections", getName())); std::pair<std::string, std::string>("system.name.autoCollections", name));
sysData.insert(std::pair<std::string, std::string>("system.fullName.autoCollections", sysData.insert(
getFullName())); std::pair<std::string, std::string>("system.fullName.autoCollections", fullName));
sysData.insert(std::pair<std::string, std::string>("system.theme.autoCollections", sysData.insert(std::pair<std::string, std::string>("system.theme.autoCollections",
getThemeFolder())); getThemeFolder()));
sysData.insert( sysData.insert(
@ -1617,10 +1659,9 @@ void SystemData::loadTheme(ThemeTriggers::TriggerType trigger)
sysData.insert(std::pair<std::string, std::string>("system.theme.noCollections", "\b")); sysData.insert(std::pair<std::string, std::string>("system.theme.noCollections", "\b"));
} }
else { else {
sysData.insert(std::pair<std::string, std::string>("system.name.noCollections", name));
sysData.insert( sysData.insert(
std::pair<std::string, std::string>("system.name.noCollections", getName())); std::pair<std::string, std::string>("system.fullName.noCollections", fullName));
sysData.insert(std::pair<std::string, std::string>("system.fullName.noCollections",
getFullName()));
sysData.insert(std::pair<std::string, std::string>("system.theme.noCollections", sysData.insert(std::pair<std::string, std::string>("system.theme.noCollections",
getThemeFolder())); getThemeFolder()));
sysData.insert( sysData.insert(

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// //
// ES-DE // ES-DE Frontend
// SystemData.h // SystemData.h
// //
// Provides data structures for the game systems and populates and indexes them based // Provides data structures for the game systems and populates and indexes them based

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// //
// ES-DE // ES-DE Frontend
// UIModeController.cpp // UIModeController.cpp
// //
// Handling of application user interface modes (full, kiosk and kid). // Handling of application user interface modes (full, kiosk and kid).

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// //
// ES-DE // ES-DE Frontend
// UIModeController.h // UIModeController.h
// //
// Handling of application user interface modes (full, kiosk and kid). // Handling of application user interface modes (full, kiosk and kid).

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// //
// ES-DE // ES-DE Frontend
// VolumeControl.cpp // VolumeControl.cpp
// //
// Controls system audio volume. // Controls system audio volume.

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// //
// ES-DE // ES-DE Frontend
// VolumeControl.h // VolumeControl.h
// //
// Controls system audio volume. // Controls system audio volume.

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// //
// ES-DE // ES-DE Frontend
// GuiAlternativeEmulators.cpp // GuiAlternativeEmulators.cpp
// //
// User interface to select between alternative emulators per system // User interface to select between alternative emulators per system
@ -11,14 +11,15 @@
#include "GamelistFileParser.h" #include "GamelistFileParser.h"
#include "SystemData.h" #include "SystemData.h"
#include "utils/LocalizationUtil.h"
#include "views/ViewController.h" #include "views/ViewController.h"
GuiAlternativeEmulators::GuiAlternativeEmulators() GuiAlternativeEmulators::GuiAlternativeEmulators()
: mMenu {"ALTERNATIVE EMULATORS"} : mMenu {_("ALTERNATIVE EMULATORS")}
, mHasSystems {false} , mHasSystems {false}
{ {
addChild(&mMenu); addChild(&mMenu);
mMenu.addButton("BACK", "back", [this] { delete this; }); mMenu.addButton(_("BACK"), _("back"), [this] { delete this; });
// Horizontal sizes for the system and label entries. // Horizontal sizes for the system and label entries.
float systemSizeX {mMenu.getSize().x / 3.27f}; float systemSizeX {mMenu.getSize().x / 3.27f};
@ -41,7 +42,8 @@ GuiAlternativeEmulators::GuiAlternativeEmulators()
std::string name {(*it)->getName()}; std::string name {(*it)->getName()};
std::shared_ptr<TextComponent> systemText { std::shared_ptr<TextComponent> systemText {
std::make_shared<TextComponent>(name, Font::get(FONT_SIZE_MEDIUM), mMenuColorPrimary)}; std::make_shared<TextComponent>(name, Font::get(FONT_SIZE_MEDIUM), mMenuColorPrimary,
ALIGN_LEFT, ALIGN_CENTER, glm::ivec2 {0, 0})};
systemText->setSize(systemSizeX, systemText->getSize().y); systemText->setSize(systemSizeX, systemText->getSize().y);
row.addElement(systemText, false); row.addElement(systemText, false);
@ -64,22 +66,23 @@ GuiAlternativeEmulators::GuiAlternativeEmulators()
bool invalidEntry {false}; bool invalidEntry {false};
if (label.empty()) { if (label.empty()) {
label = ViewController::EXCLAMATION_CHAR + " INVALID ENTRY"; label = ViewController::EXCLAMATION_CHAR + " " + _("INVALID ENTRY");
invalidEntry = true; invalidEntry = true;
} }
std::shared_ptr<TextComponent> labelText; std::shared_ptr<TextComponent> labelText;
if (label == (*it)->getSystemEnvData()->mLaunchCommands.front().second) { if (label == (*it)->getSystemEnvData()->mLaunchCommands.front().second) {
labelText = labelText = std::make_shared<TextComponent>(
std::make_shared<TextComponent>(label, Font::get(FONT_SIZE_MEDIUM, FONT_PATH_LIGHT), label, Font::get(FONT_SIZE_MEDIUM, FONT_PATH_LIGHT), mMenuColorPrimary, ALIGN_RIGHT,
mMenuColorPrimary, ALIGN_RIGHT); ALIGN_CENTER, glm::ivec2 {0, 0});
} }
else { else {
// Mark any non-default value with bold and add a gear symbol as well. // Mark any non-default value with bold and add a gear symbol as well.
labelText = std::make_shared<TextComponent>( labelText = std::make_shared<TextComponent>(
label + (!invalidEntry ? " " + ViewController::GEAR_CHAR : ""), label + (!invalidEntry ? " " + ViewController::GEAR_CHAR : ""),
Font::get(FONT_SIZE_MEDIUM, FONT_PATH_BOLD), mMenuColorPrimary, ALIGN_RIGHT); Font::get(FONT_SIZE_MEDIUM, FONT_PATH_BOLD), mMenuColorPrimary, ALIGN_RIGHT,
ALIGN_CENTER, glm::ivec2 {0, 0});
} }
// Mark invalid entries with red color. // Mark invalid entries with red color.
@ -97,7 +100,8 @@ GuiAlternativeEmulators::GuiAlternativeEmulators()
*std::find(SystemData::sSystemVector.cbegin(), SystemData::sSystemVector.cend(), *it)}; *std::find(SystemData::sSystemVector.cbegin(), SystemData::sSystemVector.cend(), *it)};
row.makeAcceptInputHandler([this, systemEntry, labelText] { row.makeAcceptInputHandler([this, systemEntry, labelText] {
if (labelText->getValue() == ViewController::CROSSEDCIRCLE_CHAR + " CLEARED ENTRY") if (labelText->getValue() ==
ViewController::CROSSEDCIRCLE_CHAR + " " + _("CLEARED ENTRY"))
return; return;
selectorWindow(systemEntry); selectorWindow(systemEntry);
}); });
@ -111,7 +115,7 @@ GuiAlternativeEmulators::GuiAlternativeEmulators()
if (!mHasSystems) { if (!mHasSystems) {
ComponentListRow row; ComponentListRow row;
std::shared_ptr<TextComponent> systemText {std::make_shared<TextComponent>( std::shared_ptr<TextComponent> systemText {std::make_shared<TextComponent>(
ViewController::EXCLAMATION_CHAR + " NO ALTERNATIVE EMULATORS DEFINED", ViewController::EXCLAMATION_CHAR + " " + _("NO ALTERNATIVE EMULATORS DEFINED"),
Font::get(FONT_SIZE_MEDIUM), mMenuColorPrimary, ALIGN_CENTER)}; Font::get(FONT_SIZE_MEDIUM), mMenuColorPrimary, ALIGN_CENTER)};
row.addElement(systemText, true); row.addElement(systemText, true);
mMenu.addRow(row); mMenu.addRow(row);
@ -141,7 +145,7 @@ void GuiAlternativeEmulators::updateMenu(const std::string& systemName,
void GuiAlternativeEmulators::selectorWindow(SystemData* system) void GuiAlternativeEmulators::selectorWindow(SystemData* system)
{ {
auto s = new GuiSettings(system->getFullName()); auto s = new GuiSettings(Utils::String::toUpper(system->getFullName()));
std::string selectedLabel {system->getAlternativeEmulator()}; std::string selectedLabel {system->getAlternativeEmulator()};
std::string label; std::string label;
@ -150,7 +154,7 @@ void GuiAlternativeEmulators::selectorWindow(SystemData* system)
ComponentListRow row; ComponentListRow row;
if (entry.second == "") if (entry.second == "")
label = ViewController::CROSSEDCIRCLE_CHAR + " CLEAR INVALID ENTRY"; label = ViewController::CROSSEDCIRCLE_CHAR + " " + _("CLEAR INVALID ENTRY");
else else
label = entry.second; label = entry.second;
@ -159,7 +163,8 @@ void GuiAlternativeEmulators::selectorWindow(SystemData* system)
labelText->setSelectable(true); labelText->setSelectable(true);
if (system->getSystemEnvData()->mLaunchCommands.front().second == label) if (system->getSystemEnvData()->mLaunchCommands.front().second == label)
labelText->setValue(labelText->getValue().append(" [DEFAULT]")); labelText->setValue(
labelText->getValue().append(" [").append(_("DEFAULT")).append("]"));
row.addElement(labelText, true); row.addElement(labelText, true);
row.makeAcceptInputHandler([this, s, system, labelText, entry, selectedLabel] { row.makeAcceptInputHandler([this, s, system, labelText, entry, selectedLabel] {
@ -173,7 +178,7 @@ void GuiAlternativeEmulators::selectorWindow(SystemData* system)
if (entry.second == system->getSystemEnvData()->mLaunchCommands.front().second) { if (entry.second == system->getSystemEnvData()->mLaunchCommands.front().second) {
if (system->getSystemEnvData()->mLaunchCommands.front().second == "") { if (system->getSystemEnvData()->mLaunchCommands.front().second == "") {
updateMenu(system->getName(), updateMenu(system->getName(),
ViewController::CROSSEDCIRCLE_CHAR + " CLEARED ENTRY", ViewController::CROSSEDCIRCLE_CHAR + " " + _("CLEARED ENTRY"),
(entry.second == (entry.second ==
system->getSystemEnvData()->mLaunchCommands.front().second)); system->getSystemEnvData()->mLaunchCommands.front().second));
} }
@ -256,8 +261,8 @@ bool GuiAlternativeEmulators::input(InputConfig* config, Input input)
std::vector<HelpPrompt> GuiAlternativeEmulators::getHelpPrompts() std::vector<HelpPrompt> GuiAlternativeEmulators::getHelpPrompts()
{ {
std::vector<HelpPrompt> prompts {mMenu.getHelpPrompts()}; std::vector<HelpPrompt> prompts {mMenu.getHelpPrompts()};
prompts.push_back(HelpPrompt("b", "back")); prompts.push_back(HelpPrompt("b", _("back")));
if (mHasSystems) if (mHasSystems)
prompts.push_back(HelpPrompt("a", "select")); prompts.push_back(HelpPrompt("a", _("select")));
return prompts; return prompts;
} }

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// //
// ES-DE // ES-DE Frontend
// GuiAlternativeEmulators.h // GuiAlternativeEmulators.h
// //
// User interface to select between alternative emulators per system // User interface to select between alternative emulators per system

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// //
// ES-DE // ES-DE Frontend
// GuiApplicationUpdater.cpp // GuiApplicationUpdater.cpp
// //
// Installs application updates. // Installs application updates.
@ -12,6 +12,7 @@
#include "ApplicationVersion.h" #include "ApplicationVersion.h"
#include "guis/GuiTextEditKeyboardPopup.h" #include "guis/GuiTextEditKeyboardPopup.h"
#include "guis/GuiTextEditPopup.h" #include "guis/GuiTextEditPopup.h"
#include "utils/LocalizationUtil.h"
#include "utils/PlatformUtil.h" #include "utils/PlatformUtil.h"
#include <SDL2/SDL_timer.h> #include <SDL2/SDL_timer.h>
@ -42,17 +43,19 @@ GuiApplicationUpdater::GuiApplicationUpdater()
setDownloadPath(); setDownloadPath();
// Set up grid. // Set up grid.
mTitle = std::make_shared<TextComponent>("APPLICATION UPDATER", Font::get(FONT_SIZE_LARGE), mTitle = std::make_shared<TextComponent>(
mMenuColorTitle, ALIGN_CENTER); _("APPLICATION UPDATER"),
Font::get(FONT_SIZE_LARGE * Utils::Localization::sMenuTitleScaleFactor), mMenuColorTitle,
ALIGN_CENTER);
mGrid.setEntry(mTitle, glm::ivec2 {0, 0}, false, true, glm::ivec2 {4, 1}, mGrid.setEntry(mTitle, glm::ivec2 {0, 0}, false, true, glm::ivec2 {4, 1},
GridFlags::BORDER_BOTTOM); GridFlags::BORDER_BOTTOM);
mStatusHeader = std::make_shared<TextComponent>( mStatusHeader = std::make_shared<TextComponent>(
"INSTALLATION STEPS:", Font::get(FONT_SIZE_MINI), mMenuColorPrimary, ALIGN_LEFT); _("INSTALLATION STEPS:"), Font::get(FONT_SIZE_MINI), mMenuColorPrimary, ALIGN_LEFT);
mGrid.setEntry(mStatusHeader, glm::ivec2 {1, 1}, false, true, glm::ivec2 {2, 1}); mGrid.setEntry(mStatusHeader, glm::ivec2 {1, 1}, false, true, glm::ivec2 {2, 1});
const std::string step1Text {mLinuxAppImage ? "DOWNLOAD NEW RELEASE" : const std::string step1Text {mLinuxAppImage ? _("DOWNLOAD NEW RELEASE") :
"DOWNLOAD NEW RELEASE TO THIS DIRECTORY:"}; _("DOWNLOAD NEW RELEASE TO THIS DIRECTORY:")};
mProcessStep1 = std::make_shared<TextComponent>(step1Text, Font::get(FONT_SIZE_MEDIUM), mProcessStep1 = std::make_shared<TextComponent>(step1Text, Font::get(FONT_SIZE_MEDIUM),
mMenuColorPrimary, ALIGN_LEFT); mMenuColorPrimary, ALIGN_LEFT);
mGrid.setEntry(mProcessStep1, glm::ivec2 {1, 2}, false, true, glm::ivec2 {2, 1}); mGrid.setEntry(mProcessStep1, glm::ivec2 {1, 2}, false, true, glm::ivec2 {2, 1});
@ -62,21 +65,21 @@ GuiApplicationUpdater::GuiApplicationUpdater()
Utils::String::replace(Utils::FileSystem::getParent(mDownloadPackageFilename), "/", "\\")}; Utils::String::replace(Utils::FileSystem::getParent(mDownloadPackageFilename), "/", "\\")};
#else #else
const std::string step2Text {mLinuxAppImage ? const std::string step2Text {mLinuxAppImage ?
"INSTALL PACKAGE" : _("INSTALL PACKAGE") :
Utils::FileSystem::getParent(mDownloadPackageFilename)}; Utils::FileSystem::getParent(mDownloadPackageFilename)};
#endif #endif
mProcessStep2 = std::make_shared<TextComponent>(step2Text, Font::get(FONT_SIZE_MEDIUM), mProcessStep2 = std::make_shared<TextComponent>(step2Text, Font::get(FONT_SIZE_MEDIUM),
mMenuColorPrimary, ALIGN_LEFT); mMenuColorPrimary, ALIGN_LEFT);
mGrid.setEntry(mProcessStep2, glm::ivec2 {1, 3}, false, true, glm::ivec2 {2, 1}); mGrid.setEntry(mProcessStep2, glm::ivec2 {1, 3}, false, true, glm::ivec2 {2, 1});
const std::string step3Text {mLinuxAppImage ? "QUIT AND MANUALLY RESTART ES-DE" : const std::string step3Text {mLinuxAppImage ? _("QUIT AND MANUALLY RESTART ES-DE") :
"QUIT AND MANUALLY UPGRADE ES-DE"}; _("QUIT AND MANUALLY UPGRADE ES-DE")};
mProcessStep3 = std::make_shared<TextComponent>(step3Text, Font::get(FONT_SIZE_MEDIUM), mProcessStep3 = std::make_shared<TextComponent>(step3Text, Font::get(FONT_SIZE_MEDIUM),
mMenuColorPrimary, ALIGN_LEFT); mMenuColorPrimary, ALIGN_LEFT);
mGrid.setEntry(mProcessStep3, glm::ivec2 {1, 4}, false, true, glm::ivec2 {2, 1}); mGrid.setEntry(mProcessStep3, glm::ivec2 {1, 4}, false, true, glm::ivec2 {2, 1});
mStatusMessageHeader = std::make_shared<TextComponent>( mStatusMessageHeader = std::make_shared<TextComponent>(
"STATUS MESSAGE:", Font::get(FONT_SIZE_MINI), mMenuColorPrimary, ALIGN_LEFT); _("STATUS MESSAGE:"), Font::get(FONT_SIZE_MINI), mMenuColorPrimary, ALIGN_LEFT);
mGrid.setEntry(mStatusMessageHeader, glm::ivec2 {1, 6}, false, true, glm::ivec2 {2, 1}); mGrid.setEntry(mStatusMessageHeader, glm::ivec2 {1, 6}, false, true, glm::ivec2 {2, 1});
mStatusMessage = std::make_shared<TextComponent>("", Font::get(FONT_SIZE_SMALL), mStatusMessage = std::make_shared<TextComponent>("", Font::get(FONT_SIZE_SMALL),
@ -90,32 +93,34 @@ GuiApplicationUpdater::GuiApplicationUpdater()
// Buttons. // Buttons.
std::vector<std::shared_ptr<ButtonComponent>> buttons; std::vector<std::shared_ptr<ButtonComponent>> buttons;
mButton1 = std::make_shared<ButtonComponent>("DOWNLOAD", "download new release", [this]() { mButton1 =
if (!mDownloading) { std::make_shared<ButtonComponent>(_("DOWNLOAD"), _("download new release"), [this]() {
if (!mLinuxAppImage) { if (!mDownloading) {
if (!Utils::FileSystem::exists( if (!mLinuxAppImage) {
Utils::FileSystem::getParent(mDownloadPackageFilename))) { if (!Utils::FileSystem::exists(
mMessage = "Download directory does not exist"; Utils::FileSystem::getParent(mDownloadPackageFilename))) {
return; mMessage = _("Download directory does not exist");
return;
}
} }
mMessage = "";
mStatusMessage->setText(mMessage);
mDownloadPercentage = 0;
mDownloading = true;
if (mThread) {
mThread->join();
mThread.reset();
}
mThread =
std::make_unique<std::thread>(&GuiApplicationUpdater::downloadPackage, this);
} }
mMessage = ""; });
mStatusMessage->setText(mMessage);
mDownloadPercentage = 0;
mDownloading = true;
if (mThread) {
mThread->join();
mThread.reset();
}
mThread = std::make_unique<std::thread>(&GuiApplicationUpdater::downloadPackage, this);
}
});
buttons.push_back(mButton1); buttons.push_back(mButton1);
if (!mLinuxAppImage) { if (!mLinuxAppImage) {
mButton2 = std::make_shared<ButtonComponent>( mButton2 = std::make_shared<ButtonComponent>(
"CHANGE DIRECTORY", "change download directory", [this]() { _("CHANGE DIRECTORY"), _("change download directory"), [this]() {
if (mDownloading || mHasDownloaded) if (mDownloading || mHasDownloaded)
return; return;
#if defined(_WIN64) #if defined(_WIN64)
@ -156,40 +161,40 @@ GuiApplicationUpdater::GuiApplicationUpdater()
}; };
if (Settings::getInstance()->getBool("VirtualKeyboard")) { if (Settings::getInstance()->getBool("VirtualKeyboard")) {
mWindow->pushGui(new GuiTextEditKeyboardPopup( mWindow->pushGui(new GuiTextEditKeyboardPopup(
getHelpStyle(), 0.0f, "ENTER DOWNLOAD DIRECTORY", currentDownloadDirectory, getHelpStyle(), 0.0f, _("ENTER DOWNLOAD DIRECTORY"),
directoryFunc, false)); currentDownloadDirectory, directoryFunc, false));
} }
else { else {
mWindow->pushGui( mWindow->pushGui(
new GuiTextEditPopup(getHelpStyle(), "ENTER DOWNLOAD DIRECTORY", new GuiTextEditPopup(getHelpStyle(), _("ENTER DOWNLOAD DIRECTORY"),
currentDownloadDirectory, directoryFunc, false)); currentDownloadDirectory, directoryFunc, false));
} }
}); });
buttons.push_back(mButton2); buttons.push_back(mButton2);
} }
mButton3 = std::make_shared<ButtonComponent>("CANCEL", "cancel", [this]() { mButton3 = std::make_shared<ButtonComponent>(_("CANCEL"), _("cancel"), [this]() {
mAbortDownload = true; mAbortDownload = true;
if (mThread) { if (mThread) {
mThread->join(); mThread->join();
mThread.reset(); mThread.reset();
} }
if (mDownloading) { if (mDownloading) {
mWindow->pushGui(
new GuiMsgBox(getHelpStyle(), "DOWNLOAD ABORTED\nNO PACKAGE SAVED TO DISK", "OK",
nullptr, "", nullptr, "", nullptr, nullptr, true, true,
(mRenderer->getIsVerticalOrientation() ?
0.70f :
0.45f * (1.778f / mRenderer->getScreenAspectRatio()))));
}
else if (mHasDownloaded || mReadyToInstall) {
mWindow->pushGui(new GuiMsgBox( mWindow->pushGui(new GuiMsgBox(
getHelpStyle(), "PACKAGE WAS DOWNLOADED AND\nCAN BE MANUALLY INSTALLED", "OK", getHelpStyle(), _("DOWNLOAD ABORTED") + "\n" + _("NO PACKAGE SAVED TO DISK"),
nullptr, "", nullptr, "", nullptr, nullptr, true, true, _("OK"), nullptr, "", nullptr, "", nullptr, nullptr, true, true,
(mRenderer->getIsVerticalOrientation() ? (mRenderer->getIsVerticalOrientation() ?
0.70f : 0.70f :
0.45f * (1.778f / mRenderer->getScreenAspectRatio())))); 0.45f * (1.778f / mRenderer->getScreenAspectRatio()))));
} }
else if (mHasDownloaded || mReadyToInstall) {
mWindow->pushGui(new GuiMsgBox(
getHelpStyle(), _("PACKAGE WAS DOWNLOADED AND CAN BE MANUALLY INSTALLED"), _("OK"),
nullptr, "", nullptr, "", nullptr, nullptr, true, true,
(mRenderer->getIsVerticalOrientation() ?
0.60f :
0.35f * (1.778f / mRenderer->getScreenAspectRatio()))));
}
delete this; delete this;
}); });
@ -217,7 +222,7 @@ GuiApplicationUpdater::GuiApplicationUpdater()
std::round(mRenderer->getScreenHeight() * 0.13f)); std::round(mRenderer->getScreenHeight() * 0.13f));
mBusyAnim.setSize(mSize); mBusyAnim.setSize(mSize);
mBusyAnim.setText("DOWNLOADING 100%"); mBusyAnim.setText(_("DOWNLOADING 100%"));
mBusyAnim.onSizeChanged(); mBusyAnim.onSizeChanged();
} }
@ -270,8 +275,9 @@ bool GuiApplicationUpdater::downloadPackage()
break; break;
} }
else if (reqStatus != HttpReq::REQ_IN_PROGRESS) { else if (reqStatus != HttpReq::REQ_IN_PROGRESS) {
std::string errorMessage {"Network error (status: "}; std::string errorMessage {_("Network error (status:")};
errorMessage.append(std::to_string(reqStatus)) errorMessage.append(" ")
.append(std::to_string(reqStatus))
.append(") - ") .append(") - ")
.append(mRequest->getErrorMsg()); .append(mRequest->getErrorMsg());
mRequest.reset(); mRequest.reset();
@ -301,7 +307,7 @@ bool GuiApplicationUpdater::downloadPackage()
mRequest.reset(); mRequest.reset();
if (Utils::Math::md5Hash(fileContents, false) != mPackage.md5) { if (Utils::Math::md5Hash(fileContents, false) != mPackage.md5) {
const std::string errorMessage {"Downloaded file does not match expected MD5 checksum"}; const std::string errorMessage {_("Downloaded file does not match expected MD5 checksum")};
LOG(LogError) << errorMessage; LOG(LogError) << errorMessage;
std::unique_lock<std::mutex> lock {mMutex}; std::unique_lock<std::mutex> lock {mMutex};
mMessage = "Error: " + errorMessage; mMessage = "Error: " + errorMessage;
@ -317,11 +323,9 @@ bool GuiApplicationUpdater::downloadPackage()
LOG(LogInfo) << "Temporary package file already exists, deleting it"; LOG(LogInfo) << "Temporary package file already exists, deleting it";
Utils::FileSystem::removeFile(mDownloadPackageFilename); Utils::FileSystem::removeFile(mDownloadPackageFilename);
if (Utils::FileSystem::exists(mDownloadPackageFilename)) { if (Utils::FileSystem::exists(mDownloadPackageFilename)) {
const std::string errorMessage { LOG(LogError) << "Couldn't delete temporary package file, permission problems?";
"Couldn't delete temporary package file, permission problems?"};
LOG(LogError) << errorMessage;
std::unique_lock<std::mutex> lock {mMutex}; std::unique_lock<std::mutex> lock {mMutex};
mMessage = "Error: " + errorMessage; mMessage = _("Error: Couldn't delete temporary package file, permission problems?");
return true; return true;
} }
} }
@ -334,7 +338,7 @@ bool GuiApplicationUpdater::downloadPackage()
LOG(LogError) << "Couldn't write package file \"" << mDownloadPackageFilename LOG(LogError) << "Couldn't write package file \"" << mDownloadPackageFilename
<< "\", permission problems?"; << "\", permission problems?";
std::unique_lock<std::mutex> lock {mMutex}; std::unique_lock<std::mutex> lock {mMutex};
mMessage = "Error: Couldn't write package file, permission problems?"; mMessage = _("Error: Couldn't write package file, permission problems?");
return true; return true;
} }
@ -353,10 +357,9 @@ bool GuiApplicationUpdater::downloadPackage()
(std::filesystem::perms::owner_all | std::filesystem::perms::group_all | (std::filesystem::perms::owner_all | std::filesystem::perms::group_all |
std::filesystem::perms::others_read | std::filesystem::perms::others_exec)) { std::filesystem::perms::others_read | std::filesystem::perms::others_exec)) {
Utils::FileSystem::removeFile(mDownloadPackageFilename); Utils::FileSystem::removeFile(mDownloadPackageFilename);
const std::string errorMessage {"Couldn't set permissions on AppImage file"}; LOG(LogError) << "Couldn't set permissions on AppImage file";
LOG(LogError) << errorMessage;
std::unique_lock<std::mutex> lock {mMutex}; std::unique_lock<std::mutex> lock {mMutex};
mMessage = "Error: " + errorMessage; mMessage = _("Error: Couldn't set permissions on AppImage file");
return true; return true;
} }
} }
@ -364,7 +367,8 @@ bool GuiApplicationUpdater::downloadPackage()
LOG(LogInfo) << "Successfully downloaded package file \"" << mDownloadPackageFilename << "\""; LOG(LogInfo) << "Successfully downloaded package file \"" << mDownloadPackageFilename << "\"";
std::unique_lock<std::mutex> lock {mMutex}; std::unique_lock<std::mutex> lock {mMutex};
mMessage = "Downloaded " + Utils::FileSystem::getFileName(mDownloadPackageFilename); mMessage = Utils::String::format(
_("Downloaded %s"), Utils::FileSystem::getFileName(mDownloadPackageFilename).c_str());
mDownloading = false; mDownloading = false;
mReadyToInstall = true; mReadyToInstall = true;
@ -398,9 +402,8 @@ bool GuiApplicationUpdater::installAppImage()
readFile.open(mDownloadPackageFilename.c_str(), std::ofstream::binary); readFile.open(mDownloadPackageFilename.c_str(), std::ofstream::binary);
if (readFile.fail()) { if (readFile.fail()) {
const std::string errorMessage {"Couldn't open AppImage update file for reading"}; LOG(LogError) << "Couldn't open AppImage update file for reading";
LOG(LogError) << errorMessage; mMessage = _("Error: Couldn't open AppImage update file for reading");
mMessage = "Error: " + errorMessage;
mHasDownloaded = false; mHasDownloaded = false;
return true; return true;
} }
@ -413,9 +416,8 @@ bool GuiApplicationUpdater::installAppImage()
readFile.close(); readFile.close();
if (Utils::Math::md5Hash(fileData, false) != mPackage.md5) { if (Utils::Math::md5Hash(fileData, false) != mPackage.md5) {
const std::string errorMessage {"Downloaded file does not match expected MD5 checksum"}; LOG(LogError) << "Downloaded file does not match expected MD5 checksum";
LOG(LogError) << errorMessage; mMessage = _("Error: Downloaded file does not match expected MD5 checksum");
mMessage = "Error: " + errorMessage;
mHasDownloaded = false; mHasDownloaded = false;
return true; return true;
} }
@ -423,10 +425,8 @@ bool GuiApplicationUpdater::installAppImage()
const std::string packageOldFile {packageTargetFile + "_" + PROGRAM_VERSION_STRING + ".OLD"}; const std::string packageOldFile {packageTargetFile + "_" + PROGRAM_VERSION_STRING + ".OLD"};
if (Utils::FileSystem::renameFile(packageTargetFile, packageOldFile, true)) { if (Utils::FileSystem::renameFile(packageTargetFile, packageOldFile, true)) {
const std::string errorMessage { LOG(LogError) << "Couldn't rename running AppImage file, permission problems?";
"Couldn't rename running AppImage file, permission problems?"}; mMessage = _("Error: Couldn't rename running AppImage file, permission problems?");
LOG(LogError) << errorMessage;
mMessage = "Error: " + errorMessage;
LOG(LogInfo) << "Attempting to rename \"" << packageOldFile LOG(LogInfo) << "Attempting to rename \"" << packageOldFile
<< "\" back to running AppImage"; << "\" back to running AppImage";
Utils::FileSystem::renameFile(packageOldFile, packageTargetFile, true); Utils::FileSystem::renameFile(packageOldFile, packageTargetFile, true);
@ -437,10 +437,8 @@ bool GuiApplicationUpdater::installAppImage()
LOG(LogInfo) << "Renamed running AppImage to \"" << packageOldFile << "\""; LOG(LogInfo) << "Renamed running AppImage to \"" << packageOldFile << "\"";
if (Utils::FileSystem::renameFile(mDownloadPackageFilename, packageTargetFile, true)) { if (Utils::FileSystem::renameFile(mDownloadPackageFilename, packageTargetFile, true)) {
const std::string errorMessage { LOG(LogError) << "Couldn't replace running AppImage file, permission problems?";
"Couldn't replace running AppImage file, permission problems?"}; mMessage = _("Error: Couldn't replace running AppImage file, permission problems?");
LOG(LogError) << errorMessage;
mMessage = "Error: " + errorMessage;
LOG(LogInfo) << "Attempting to rename \"" << packageOldFile LOG(LogInfo) << "Attempting to rename \"" << packageOldFile
<< "\" back to running AppImage"; << "\" back to running AppImage";
Utils::FileSystem::renameFile(packageOldFile, packageTargetFile, true); Utils::FileSystem::renameFile(packageOldFile, packageTargetFile, true);
@ -451,7 +449,8 @@ bool GuiApplicationUpdater::installAppImage()
LOG(LogInfo) << "Package was successfully installed as \"" << packageTargetFile << "\""; LOG(LogInfo) << "Package was successfully installed as \"" << packageTargetFile << "\"";
std::unique_lock<std::mutex> lock {mMutex}; std::unique_lock<std::mutex> lock {mMutex};
mMessage = "Successfully installed as " + Utils::FileSystem::getFileName(packageTargetFile); mMessage = Utils::String::format(_("Successfully installed as %s"),
Utils::FileSystem::getFileName(packageTargetFile).c_str());
mHasInstalled = true; mHasInstalled = true;
return false; return false;
@ -468,13 +467,13 @@ void GuiApplicationUpdater::update(int deltaTime)
} }
if (mDownloading) { if (mDownloading) {
mBusyAnim.setText("DOWNLOADING " + std::to_string(mDownloadPercentage) + "%"); mBusyAnim.setText(_("DOWNLOADING") + " " + std::to_string(mDownloadPercentage) + "%");
mBusyAnim.update(deltaTime); mBusyAnim.update(deltaTime);
} }
else if (mLinuxAppImage && mReadyToInstall) { else if (mLinuxAppImage && mReadyToInstall) {
mProcessStep1->setText(ViewController::TICKMARK_CHAR + " " + mProcessStep1->getValue()); mProcessStep1->setText(ViewController::TICKMARK_CHAR + " " + mProcessStep1->getValue());
mProcessStep1->setColor(mMenuColorGreen); mProcessStep1->setColor(mMenuColorGreen);
mButton1->setText("INSTALL", "install package", true, false); mButton1->setText(_("INSTALL"), _("install package"), true, false);
mButton1->setPressedFunc([this] { mButton1->setPressedFunc([this] {
if (!mInstalling) { if (!mInstalling) {
mMessage = ""; mMessage = "";
@ -494,10 +493,10 @@ void GuiApplicationUpdater::update(int deltaTime)
mProcessStep1->setText(ViewController::TICKMARK_CHAR + " " + mProcessStep1->getValue()); mProcessStep1->setText(ViewController::TICKMARK_CHAR + " " + mProcessStep1->getValue());
mProcessStep1->setColor(mMenuColorGreen); mProcessStep1->setColor(mMenuColorGreen);
} }
mChangelogMessage->setText("Find the detailed changelog at https://es-de.org"); mChangelogMessage->setText(_("Find the detailed changelog at") + " https://es-de.org");
mGrid.removeEntry(mButtons); mGrid.removeEntry(mButtons);
mGrid.setEntry(MenuComponent::makeButtonGrid(std::vector<std::shared_ptr<ButtonComponent>> { mGrid.setEntry(MenuComponent::makeButtonGrid(std::vector<std::shared_ptr<ButtonComponent>> {
std::make_shared<ButtonComponent>("QUIT", "quit application", std::make_shared<ButtonComponent>(_("QUIT"), _("quit application"),
[this]() { [this]() {
delete this; delete this;
Utils::Platform::quitES(); Utils::Platform::quitES();

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// //
// ES-DE // ES-DE Frontend
// GuiApplicationUpdater.h // GuiApplicationUpdater.h
// //
// Installs application updates. // Installs application updates.

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// //
// ES-DE // ES-DE Frontend
// GuiCollectionSystemsOptions.cpp // GuiCollectionSystemsOptions.cpp
// //
// User interface for the game collection settings. // User interface for the game collection settings.
@ -16,6 +16,7 @@
#include "guis/GuiSettings.h" #include "guis/GuiSettings.h"
#include "guis/GuiTextEditKeyboardPopup.h" #include "guis/GuiTextEditKeyboardPopup.h"
#include "guis/GuiTextEditPopup.h" #include "guis/GuiTextEditPopup.h"
#include "utils/LocalizationUtil.h"
#include "utils/StringUtil.h" #include "utils/StringUtil.h"
#include "views/ViewController.h" #include "views/ViewController.h"
@ -28,14 +29,13 @@ GuiCollectionSystemsOptions::GuiCollectionSystemsOptions(std::string title)
// Finish editing custom collection. // Finish editing custom collection.
if (CollectionSystemsManager::getInstance()->isEditing()) { if (CollectionSystemsManager::getInstance()->isEditing()) {
ComponentListRow row; ComponentListRow row;
row.addElement( const std::string editingText {Utils::String::format(
std::make_shared<TextComponent>( _("FINISH EDITING '%s' COLLECTION"),
"FINISH EDITING '" + Utils::String::toUpper(CollectionSystemsManager::getInstance()->getEditingCollection())
Utils::String::toUpper( .c_str())};
CollectionSystemsManager::getInstance()->getEditingCollection()) + row.addElement(std::make_shared<TextComponent>(editingText, Font::get(FONT_SIZE_MEDIUM),
"' COLLECTION", mMenuColorPrimary),
Font::get(FONT_SIZE_MEDIUM), mMenuColorPrimary), true);
true);
row.makeAcceptInputHandler([this] { row.makeAcceptInputHandler([this] {
CollectionSystemsManager::getInstance()->exitEditMode(); CollectionSystemsManager::getInstance()->exitEditMode();
mWindow->invalidateCachedBackground(); mWindow->invalidateCachedBackground();
@ -46,16 +46,16 @@ GuiCollectionSystemsOptions::GuiCollectionSystemsOptions(std::string title)
// Automatic collections. // Automatic collections.
mCollectionSystemsAuto = std::make_shared<OptionListComponent<std::string>>( mCollectionSystemsAuto = std::make_shared<OptionListComponent<std::string>>(
getHelpStyle(), "SELECT COLLECTIONS", true); getHelpStyle(), _("SELECT COLLECTIONS"), true);
std::map<std::string, CollectionSystemData, StringComparator> autoSystems { std::map<std::string, CollectionSystemData, StringComparator> autoSystems {
CollectionSystemsManager::getInstance()->getAutoCollectionSystems()}; CollectionSystemsManager::getInstance()->getAutoCollectionSystems()};
// Add automatic systems. // Add automatic systems.
for (std::map<std::string, CollectionSystemData, StringComparator>::const_iterator it = for (std::map<std::string, CollectionSystemData, StringComparator>::const_iterator it =
autoSystems.cbegin(); autoSystems.cbegin();
it != autoSystems.cend(); ++it) it != autoSystems.cend(); ++it)
mCollectionSystemsAuto->add(it->second.decl.fullName, it->second.decl.name, mCollectionSystemsAuto->add(Utils::String::toUpper(_(it->second.decl.fullName.c_str())),
it->second.isEnabled); it->second.decl.name, it->second.isEnabled);
addWithLabel("AUTOMATIC GAME COLLECTIONS", mCollectionSystemsAuto); addWithLabel(_("AUTOMATIC GAME COLLECTIONS"), mCollectionSystemsAuto);
addSaveFunc([this, autoSystems] { addSaveFunc([this, autoSystems] {
std::string autoSystemsSelected {Utils::String::vectorToDelimitedString( std::string autoSystemsSelected {Utils::String::vectorToDelimitedString(
mCollectionSystemsAuto->getSelectedObjects(), ",", true)}; mCollectionSystemsAuto->getSelectedObjects(), ",", true)};
@ -96,17 +96,17 @@ GuiCollectionSystemsOptions::GuiCollectionSystemsOptions(std::string title)
// Custom collections. // Custom collections.
mCollectionSystemsCustom = std::make_shared<OptionListComponent<std::string>>( mCollectionSystemsCustom = std::make_shared<OptionListComponent<std::string>>(
getHelpStyle(), "SELECT COLLECTIONS", true); getHelpStyle(), _("SELECT COLLECTIONS"), true);
std::map<std::string, CollectionSystemData, StringComparator> customSystems { std::map<std::string, CollectionSystemData, StringComparator> customSystems {
CollectionSystemsManager::getInstance()->getCustomCollectionSystems()}; CollectionSystemsManager::getInstance()->getCustomCollectionSystems()};
// Add custom systems. // Add custom systems.
for (std::map<std::string, CollectionSystemData, StringComparator>::const_iterator it = for (std::map<std::string, CollectionSystemData, StringComparator>::const_iterator it =
customSystems.cbegin(); customSystems.cbegin();
it != customSystems.cend(); ++it) it != customSystems.cend(); ++it)
mCollectionSystemsCustom->add(it->second.decl.fullName, it->second.decl.name, mCollectionSystemsCustom->add(Utils::String::toUpper(it->second.decl.fullName),
it->second.isEnabled); it->second.decl.name, it->second.isEnabled);
addWithLabel("CUSTOM GAME COLLECTIONS", mCollectionSystemsCustom); addWithLabel(_("CUSTOM GAME COLLECTIONS"), mCollectionSystemsCustom);
addSaveFunc([this, customSystems] { addSaveFunc([this, customSystems] {
if (!mDeletedCustomCollection) { if (!mDeletedCustomCollection) {
std::string customSystemsSelected {Utils::String::vectorToDelimitedString( std::string customSystemsSelected {Utils::String::vectorToDelimitedString(
@ -166,7 +166,7 @@ GuiCollectionSystemsOptions::GuiCollectionSystemsOptions(std::string title)
if (unusedFolders.size() > 0) { if (unusedFolders.size() > 0) {
ComponentListRow row; ComponentListRow row;
auto themeCollection = auto themeCollection =
std::make_shared<TextComponent>("CREATE NEW CUSTOM COLLECTION FROM THEME", std::make_shared<TextComponent>(_("CREATE NEW CUSTOM COLLECTION FROM THEME"),
Font::get(FONT_SIZE_MEDIUM), mMenuColorPrimary); Font::get(FONT_SIZE_MEDIUM), mMenuColorPrimary);
auto bracketThemeCollection = std::make_shared<ImageComponent>(); auto bracketThemeCollection = std::make_shared<ImageComponent>();
bracketThemeCollection->setResize( bracketThemeCollection->setResize(
@ -176,10 +176,10 @@ GuiCollectionSystemsOptions::GuiCollectionSystemsOptions(std::string title)
row.addElement(themeCollection, true); row.addElement(themeCollection, true);
row.addElement(bracketThemeCollection, false); row.addElement(bracketThemeCollection, false);
row.makeAcceptInputHandler([this, unusedFolders] { row.makeAcceptInputHandler([this, unusedFolders] {
auto ss = new GuiSettings("SELECT THEME FOLDER"); auto ss = new GuiSettings(_("SELECT THEME FOLDER"));
std::shared_ptr<OptionListComponent<std::string>> folderThemes { std::shared_ptr<OptionListComponent<std::string>> folderThemes {
std::make_shared<OptionListComponent<std::string>>(getHelpStyle(), std::make_shared<OptionListComponent<std::string>>(getHelpStyle(),
"SELECT THEME FOLDER", true)}; _("SELECT THEME FOLDER"), true)};
// Add custom systems. // Add custom systems.
for (auto it = unusedFolders.cbegin(); it != unusedFolders.cend(); ++it) { for (auto it = unusedFolders.cbegin(); it != unusedFolders.cend(); ++it) {
ComponentListRow row; ComponentListRow row;
@ -201,7 +201,7 @@ GuiCollectionSystemsOptions::GuiCollectionSystemsOptions(std::string title)
// Create new custom collection. // Create new custom collection.
ComponentListRow row; ComponentListRow row;
auto newCollection = std::make_shared<TextComponent>( auto newCollection = std::make_shared<TextComponent>(
"CREATE NEW CUSTOM COLLECTION", Font::get(FONT_SIZE_MEDIUM), mMenuColorPrimary); _("CREATE NEW CUSTOM COLLECTION"), Font::get(FONT_SIZE_MEDIUM), mMenuColorPrimary);
auto bracketNewCollection = std::make_shared<ImageComponent>(); auto bracketNewCollection = std::make_shared<ImageComponent>();
bracketNewCollection->setResize( bracketNewCollection->setResize(
glm::vec2 {0.0f, Font::get(FONT_SIZE_MEDIUM)->getLetterHeight()}); glm::vec2 {0.0f, Font::get(FONT_SIZE_MEDIUM)->getLetterHeight()});
@ -224,15 +224,15 @@ GuiCollectionSystemsOptions::GuiCollectionSystemsOptions(std::string title)
const float verticalPosition { const float verticalPosition {
mRenderer->getIsVerticalOrientation() ? getMenu().getPosition().y : 0.0f}; mRenderer->getIsVerticalOrientation() ? getMenu().getPosition().y : 0.0f};
mWindow->pushGui(new GuiTextEditKeyboardPopup( mWindow->pushGui(new GuiTextEditKeyboardPopup(
getHelpStyle(), verticalPosition, "New Collection Name", "", createCollectionCall, getHelpStyle(), verticalPosition, _("NEW COLLECTION NAME"), "",
false, "CREATE", "CREATE COLLECTION?")); createCollectionCall, false, _("CREATE"), _("CREATE COLLECTION?")));
}); });
} }
else { else {
row.makeAcceptInputHandler([this, createCollectionCall] { row.makeAcceptInputHandler([this, createCollectionCall] {
mWindow->pushGui(new GuiTextEditPopup(getHelpStyle(), "New Collection Name", "", mWindow->pushGui(new GuiTextEditPopup(getHelpStyle(), _("NEW COLLECTION NAME"), "",
createCollectionCall, false, "CREATE", createCollectionCall, false, _("CREATE"),
"CREATE COLLECTION?")); _("CREATE COLLECTION?")));
}); });
} }
addRow(row); addRow(row);
@ -240,7 +240,7 @@ GuiCollectionSystemsOptions::GuiCollectionSystemsOptions(std::string title)
// Delete custom collection. // Delete custom collection.
row.elements.clear(); row.elements.clear();
auto deleteCollection = std::make_shared<TextComponent>( auto deleteCollection = std::make_shared<TextComponent>(
"DELETE CUSTOM COLLECTION", Font::get(FONT_SIZE_MEDIUM), mMenuColorPrimary); _("DELETE CUSTOM COLLECTION"), Font::get(FONT_SIZE_MEDIUM), mMenuColorPrimary);
auto bracketDeleteCollection = std::make_shared<ImageComponent>(); auto bracketDeleteCollection = std::make_shared<ImageComponent>();
bracketDeleteCollection->setResize( bracketDeleteCollection->setResize(
glm::vec2 {0.0f, Font::get(FONT_SIZE_MEDIUM)->getLetterHeight()}); glm::vec2 {0.0f, Font::get(FONT_SIZE_MEDIUM)->getLetterHeight()});
@ -249,7 +249,7 @@ GuiCollectionSystemsOptions::GuiCollectionSystemsOptions(std::string title)
row.addElement(deleteCollection, true); row.addElement(deleteCollection, true);
row.addElement(bracketDeleteCollection, false); row.addElement(bracketDeleteCollection, false);
row.makeAcceptInputHandler([this, customSystems] { row.makeAcceptInputHandler([this, customSystems] {
auto ss = new GuiSettings("COLLECTION TO DELETE"); auto ss = new GuiSettings(_("COLLECTION TO DELETE"));
std::shared_ptr<OptionListComponent<std::string>> customCollections { std::shared_ptr<OptionListComponent<std::string>> customCollections {
std::make_shared<OptionListComponent<std::string>>(getHelpStyle(), "", true)}; std::make_shared<OptionListComponent<std::string>>(getHelpStyle(), "", true)};
for (std::map<std::string, CollectionSystemData, StringComparator>::const_iterator it = for (std::map<std::string, CollectionSystemData, StringComparator>::const_iterator it =
@ -260,11 +260,10 @@ GuiCollectionSystemsOptions::GuiCollectionSystemsOptions(std::string title)
std::function<void()> deleteCollectionCall = [this, name] { std::function<void()> deleteCollectionCall = [this, name] {
mWindow->pushGui(new GuiMsgBox( mWindow->pushGui(new GuiMsgBox(
getHelpStyle(), getHelpStyle(),
"THIS WILL PERMANENTLY\nDELETE THE COLLECTION\n'" + Utils::String::format(
Utils::String::toUpper(name) + _("THIS WILL PERMANENTLY DELETE THE COLLECTION\n'%s'\nARE YOU SURE?"),
"'\n" Utils::String::toUpper(name).c_str()),
"ARE YOU SURE?", _("YES"),
"YES",
[this, name] { [this, name] {
if (CollectionSystemsManager::getInstance()->isEditing()) if (CollectionSystemsManager::getInstance()->isEditing())
CollectionSystemsManager::getInstance()->exitEditMode(); CollectionSystemsManager::getInstance()->exitEditMode();
@ -299,7 +298,10 @@ GuiCollectionSystemsOptions::GuiCollectionSystemsOptions(std::string title)
CollectionSystemsManager::getInstance()->deleteCustomCollection(name); CollectionSystemsManager::getInstance()->deleteCustomCollection(name);
return true; return true;
}, },
"NO", [] { return false; })); _("NO"), [] { return false; }, "", nullptr, nullptr, false, true,
(mRenderer->getIsVerticalOrientation() ?
0.43f :
0.28f * (1.778f / mRenderer->getScreenAspectRatio()))));
}; };
row.makeAcceptInputHandler(deleteCollectionCall); row.makeAcceptInputHandler(deleteCollectionCall);
auto customCollection = std::make_shared<TextComponent>( auto customCollection = std::make_shared<TextComponent>(
@ -317,17 +319,18 @@ GuiCollectionSystemsOptions::GuiCollectionSystemsOptions(std::string title)
// Custom collections grouping. // Custom collections grouping.
auto collectionCustomGrouping = std::make_shared<OptionListComponent<std::string>>( auto collectionCustomGrouping = std::make_shared<OptionListComponent<std::string>>(
getHelpStyle(), "GROUP CUSTOM COLLECTIONS", false); getHelpStyle(), _("GROUP CUSTOM COLLECTIONS"), false);
const std::string& selectedCustomGrouping { const std::string& selectedCustomGrouping {
Settings::getInstance()->getString("CollectionCustomGrouping")}; Settings::getInstance()->getString("CollectionCustomGrouping")};
collectionCustomGrouping->add("IF UNTHEMED", "unthemed", selectedCustomGrouping == "unthemed"); collectionCustomGrouping->add(_("IF UNTHEMED"), "unthemed",
collectionCustomGrouping->add("ALWAYS", "always", selectedCustomGrouping == "always"); selectedCustomGrouping == "unthemed");
collectionCustomGrouping->add("NEVER", "never", selectedCustomGrouping == "never"); collectionCustomGrouping->add(_("ALWAYS"), "always", selectedCustomGrouping == "always");
collectionCustomGrouping->add(_("NEVER"), "never", selectedCustomGrouping == "never");
// If there are no objects returned, then there must be a manually modified entry in the // If there are no objects returned, then there must be a manually modified entry in the
// configuration file. Simply set custom collections grouping to "unthemed" in this case. // configuration file. Simply set custom collections grouping to "unthemed" in this case.
if (collectionCustomGrouping->getSelectedObjects().size() == 0) if (collectionCustomGrouping->getSelectedObjects().size() == 0)
collectionCustomGrouping->selectEntry(0); collectionCustomGrouping->selectEntry(0);
addWithLabel("GROUP CUSTOM COLLECTIONS", collectionCustomGrouping); addWithLabel(_("GROUP CUSTOM COLLECTIONS"), collectionCustomGrouping);
addSaveFunc([this, collectionCustomGrouping] { addSaveFunc([this, collectionCustomGrouping] {
if (collectionCustomGrouping->getSelected() != if (collectionCustomGrouping->getSelected() !=
Settings::getInstance()->getString("CollectionCustomGrouping")) { Settings::getInstance()->getString("CollectionCustomGrouping")) {
@ -348,7 +351,7 @@ GuiCollectionSystemsOptions::GuiCollectionSystemsOptions(std::string title)
// Sort favorites on top for custom collections. // Sort favorites on top for custom collections.
auto fav_first_custom = std::make_shared<SwitchComponent>(); auto fav_first_custom = std::make_shared<SwitchComponent>();
fav_first_custom->setState(Settings::getInstance()->getBool("FavFirstCustom")); fav_first_custom->setState(Settings::getInstance()->getBool("FavFirstCustom"));
addWithLabel("SORT FAVORITES ON TOP FOR CUSTOM COLLECTIONS", fav_first_custom); addWithLabel(_("SORT FAVORITES ON TOP FOR CUSTOM COLLECTIONS"), fav_first_custom);
addSaveFunc([this, fav_first_custom] { addSaveFunc([this, fav_first_custom] {
if (fav_first_custom->getState() != Settings::getInstance()->getBool("FavFirstCustom")) { if (fav_first_custom->getState() != Settings::getInstance()->getBool("FavFirstCustom")) {
Settings::getInstance()->setBool("FavFirstCustom", fav_first_custom->getState()); Settings::getInstance()->setBool("FavFirstCustom", fav_first_custom->getState());
@ -363,7 +366,7 @@ GuiCollectionSystemsOptions::GuiCollectionSystemsOptions(std::string title)
// Display star markings for custom collections. // Display star markings for custom collections.
auto fav_star_custom = std::make_shared<SwitchComponent>(); auto fav_star_custom = std::make_shared<SwitchComponent>();
fav_star_custom->setState(Settings::getInstance()->getBool("FavStarCustom")); fav_star_custom->setState(Settings::getInstance()->getBool("FavStarCustom"));
addWithLabel("DISPLAY STAR MARKINGS FOR CUSTOM COLLECTIONS", fav_star_custom); addWithLabel(_("DISPLAY STAR MARKINGS FOR CUSTOM COLLECTIONS"), fav_star_custom);
addSaveFunc([this, fav_star_custom] { addSaveFunc([this, fav_star_custom] {
if (fav_star_custom->getState() != Settings::getInstance()->getBool("FavStarCustom")) { if (fav_star_custom->getState() != Settings::getInstance()->getBool("FavStarCustom")) {
Settings::getInstance()->setBool("FavStarCustom", fav_star_custom->getState()); Settings::getInstance()->setBool("FavStarCustom", fav_star_custom->getState());

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// //
// ES-DE // ES-DE Frontend
// GuiCollectionSystemsOptions.h // GuiCollectionSystemsOptions.h
// //
// User interface for the game collection settings. // User interface for the game collection settings.

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// //
// ES-DE // ES-DE Frontend
// GuiGamelistFilter.cpp // GuiGamelistFilter.cpp
// //
// User interface for the gamelist filters. // User interface for the gamelist filters.
@ -16,11 +16,12 @@
#include "components/OptionListComponent.h" #include "components/OptionListComponent.h"
#include "guis/GuiTextEditKeyboardPopup.h" #include "guis/GuiTextEditKeyboardPopup.h"
#include "guis/GuiTextEditPopup.h" #include "guis/GuiTextEditPopup.h"
#include "utils/LocalizationUtil.h"
#include "utils/StringUtil.h" #include "utils/StringUtil.h"
GuiGamelistFilter::GuiGamelistFilter(SystemData* system, GuiGamelistFilter::GuiGamelistFilter(SystemData* system,
std::function<void(bool)> filterChangedCallback) std::function<void(bool)> filterChangedCallback)
: mMenu {"FILTER GAMELIST"} : mMenu {_("FILTER GAMELIST")}
, mSystem {system} , mSystem {system}
, mFiltersChangedCallback {filterChangedCallback} , mFiltersChangedCallback {filterChangedCallback}
, mFiltersChanged {false} , mFiltersChanged {false}
@ -39,8 +40,8 @@ void GuiGamelistFilter::initializeMenu()
// Show filtered menu. // Show filtered menu.
row.elements.clear(); row.elements.clear();
row.addElement(std::make_shared<TextComponent>("RESET ALL FILTERS", Font::get(FONT_SIZE_MEDIUM), row.addElement(std::make_shared<TextComponent>(_("RESET ALL FILTERS"),
mMenuColorPrimary), Font::get(FONT_SIZE_MEDIUM), mMenuColorPrimary),
true); true);
row.makeAcceptInputHandler(std::bind(&GuiGamelistFilter::resetAllFilters, this)); row.makeAcceptInputHandler(std::bind(&GuiGamelistFilter::resetAllFilters, this));
mMenu.addRow(row); mMenu.addRow(row);
@ -48,7 +49,7 @@ void GuiGamelistFilter::initializeMenu()
addFiltersToMenu(); addFiltersToMenu();
mMenu.addButton("BACK", "back", std::bind(&GuiGamelistFilter::applyFilters, this)); mMenu.addButton(_("BACK"), _("back"), std::bind(&GuiGamelistFilter::applyFilters, this));
mMenu.setPosition((Renderer::getScreenWidth() - mMenu.getSize().x) / 2.0f, mMenu.setPosition((Renderer::getScreenWidth() - mMenu.getSize().x) / 2.0f,
Renderer::getScreenHeight() * 0.13f); Renderer::getScreenHeight() * 0.13f);
@ -87,11 +88,13 @@ void GuiGamelistFilter::addFiltersToMenu()
ComponentListRow row; ComponentListRow row;
auto lbl = std::make_shared<TextComponent>( auto lbl = std::make_shared<TextComponent>(
Utils::String::toUpper(ViewController::KEYBOARD_CHAR + " GAME NAME"), Utils::String::toUpper(ViewController::KEYBOARD_CHAR + " " + _("GAME NAME")),
Font::get(FONT_SIZE_MEDIUM), mMenuColorPrimary); Font::get(FONT_SIZE_MEDIUM), mMenuColorPrimary);
mTextFilterField = std::make_shared<TextComponent>("", Font::get(FONT_SIZE_MEDIUM), mTextFilterField = std::make_shared<TextComponent>("", Font::get(FONT_SIZE_MEDIUM),
mMenuColorPrimary, ALIGN_RIGHT); mMenuColorPrimary, ALIGN_RIGHT);
mTextFilterField->setSize(
0.0f, mTextFilterField->getFont()->getHeight(mTextFilterField->getLineSpacing()));
// Don't show the free text filter entry unless there are any games in the system. // Don't show the free text filter entry unless there are any games in the system.
if (mSystem->getRootFolder()->getChildren().size() > 0) { if (mSystem->getRootFolder()->getChildren().size() > 0) {
@ -122,15 +125,15 @@ void GuiGamelistFilter::addFiltersToMenu()
const float verticalPosition { const float verticalPosition {
Renderer::getIsVerticalOrientation() ? mMenu.getPosition().y : 0.0f}; Renderer::getIsVerticalOrientation() ? mMenu.getPosition().y : 0.0f};
mWindow->pushGui(new GuiTextEditKeyboardPopup( mWindow->pushGui(new GuiTextEditKeyboardPopup(
getHelpStyle(), verticalPosition, "GAME NAME", mTextFilterField->getValue(), getHelpStyle(), verticalPosition, _("GAME NAME"), mTextFilterField->getValue(),
updateVal, false, "OK", "APPLY CHANGES?")); updateVal, false, _("OK"), _("APPLY CHANGES?")));
}); });
} }
else { else {
row.makeAcceptInputHandler([this, updateVal] { row.makeAcceptInputHandler([this, updateVal] {
mWindow->pushGui(new GuiTextEditPopup(getHelpStyle(), "GAME NAME", mWindow->pushGui(new GuiTextEditPopup(getHelpStyle(), _("GAME NAME"),
mTextFilterField->getValue(), updateVal, false, mTextFilterField->getValue(), updateVal, false,
"OK", "APPLY CHANGES?")); _("OK"), _("APPLY CHANGES?")));
}); });
} }
@ -180,7 +183,7 @@ void GuiGamelistFilter::addFiltersToMenu()
if (allKeys->size() == 1 || allKeys->empty()) { if (allKeys->size() == 1 || allKeys->empty()) {
optionList->setEnabled(false); optionList->setEnabled(false);
optionList->setOpacity(DISABLED_OPACITY); optionList->setOpacity(DISABLED_OPACITY);
optionList->setOverrideMultiText("NOTHING TO FILTER"); optionList->setOverrideMultiText(_("NOTHING TO FILTER"));
} }
if (type == CONTROLLER_FILTER) { if (type == CONTROLLER_FILTER) {
@ -189,14 +192,22 @@ void GuiGamelistFilter::addFiltersToMenu()
BadgeComponent::getDisplayName(Utils::String::toLower(it.first))}; BadgeComponent::getDisplayName(Utils::String::toLower(it.first))};
if (displayName == "unknown") if (displayName == "unknown")
displayName = it.first; displayName = it.first;
optionList->add(displayName, it.first, optionList->add(Utils::String::toUpper(displayName), it.first,
mFilterIndex->isKeyBeingFilteredBy(it.first, type)); mFilterIndex->isKeyBeingFilteredBy(it.first, type));
} }
} }
else { else {
for (auto it : *allKeys) if (type == FAVORITES_FILTER || type == COMPLETED_FILTER || type == KIDGAME_FILTER ||
optionList->add(it.first, it.first, type == HIDDEN_FILTER || type == BROKEN_FILTER) {
mFilterIndex->isKeyBeingFilteredBy(it.first, type)); for (auto it : *allKeys)
optionList->add(_(it.first.c_str()), it.first,
mFilterIndex->isKeyBeingFilteredBy(it.first, type));
}
else {
for (auto it : *allKeys)
optionList->add(it.first, it.first,
mFilterIndex->isKeyBeingFilteredBy(it.first, type));
}
} }
if (allKeys->size() == 0) if (allKeys->size() == 0)
@ -245,7 +256,7 @@ bool GuiGamelistFilter::input(InputConfig* config, Input input)
std::vector<HelpPrompt> GuiGamelistFilter::getHelpPrompts() std::vector<HelpPrompt> GuiGamelistFilter::getHelpPrompts()
{ {
std::vector<HelpPrompt> prompts {mMenu.getHelpPrompts()}; std::vector<HelpPrompt> prompts {mMenu.getHelpPrompts()};
prompts.push_back(HelpPrompt("b", "back")); prompts.push_back(HelpPrompt("b", _("back")));
prompts.push_back(HelpPrompt("a", "select")); prompts.push_back(HelpPrompt("a", _("select")));
return prompts; return prompts;
} }

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// //
// ES-DE // ES-DE Frontend
// GuiGamelistFilter.h // GuiGamelistFilter.h
// //
// User interface for the gamelist filters. // User interface for the gamelist filters.

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// //
// ES-DE // ES-DE Frontend
// GuiGamelistOptions.cpp // GuiGamelistOptions.cpp
// //
// Gamelist options menu for the 'Jump to...' quick selector, // Gamelist options menu for the 'Jump to...' quick selector,
@ -27,12 +27,13 @@
#include "UIModeController.h" #include "UIModeController.h"
#include "guis/GuiGamelistFilter.h" #include "guis/GuiGamelistFilter.h"
#include "scrapers/Scraper.h" #include "scrapers/Scraper.h"
#include "utils/LocalizationUtil.h"
#include "views/ViewController.h" #include "views/ViewController.h"
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
GuiGamelistOptions::GuiGamelistOptions(SystemData* system) GuiGamelistOptions::GuiGamelistOptions(SystemData* system)
: mMenu {"GAMELIST OPTIONS"} : mMenu {_("GAMELIST OPTIONS")}
, mSystem {system} , mSystem {system}
, mFiltersChanged {false} , mFiltersChanged {false}
, mCancelled {false} , mCancelled {false}
@ -102,7 +103,7 @@ GuiGamelistOptions::GuiGamelistOptions(SystemData* system)
mCurrentFirstCharacter = Utils::String::getFirstCharacter(file->getSortName()); mCurrentFirstCharacter = Utils::String::getFirstCharacter(file->getSortName());
} }
mJumpToLetterList = std::make_shared<LetterList>(getHelpStyle(), "JUMP TO...", false); mJumpToLetterList = std::make_shared<LetterList>(getHelpStyle(), _("JUMP TO..."), false);
// Enable key repeat so that the left or right button can be held to cycle through // Enable key repeat so that the left or right button can be held to cycle through
// the letters. // the letters.
@ -116,12 +117,12 @@ GuiGamelistOptions::GuiGamelistOptions(SystemData* system)
} }
if (system->getName() != "recent") if (system->getName() != "recent")
mMenu.addWithLabel("JUMP TO..", mJumpToLetterList); mMenu.addWithLabel(_("JUMP TO..."), mJumpToLetterList);
// Add the sorting entry, unless this is the grouped custom collections list. // Add the sorting entry, unless this is the grouped custom collections list.
if (!mIsCustomCollectionGroup) { if (!mIsCustomCollectionGroup) {
// Sort list by selected sort type (persistent throughout the program session). // Sort list by selected sort type (persistent throughout the program session).
mListSort = std::make_shared<SortList>(getHelpStyle(), "SORT GAMES BY", false); mListSort = std::make_shared<SortList>(getHelpStyle(), _("SORT GAMES BY"), false);
FileData* root {nullptr}; FileData* root {nullptr};
if (mIsCustomCollection) if (mIsCustomCollection)
root = getGamelist()->getCursor()->getSystem()->getRootFolder(); root = getGamelist()->getCursor()->getSystem()->getRootFolder();
@ -137,9 +138,11 @@ GuiGamelistOptions::GuiGamelistOptions(SystemData* system)
for (unsigned int i {0}; i < numSortTypes; ++i) { for (unsigned int i {0}; i < numSortTypes; ++i) {
const FileData::SortType& sort {FileSorts::SortTypes.at(i)}; const FileData::SortType& sort {FileSorts::SortTypes.at(i)};
if (sort.description == sortType) if (sort.description == sortType)
mListSort->add(sort.description, &sort, true); mListSort->add(Utils::String::toUpper(_(sort.description.c_str())), &sort,
true);
else else
mListSort->add(sort.description, &sort, false); mListSort->add(Utils::String::toUpper(_(sort.description.c_str())), &sort,
false);
} }
// Enable key repeat so that the left or right button can be held to cycle through // Enable key repeat so that the left or right button can be held to cycle through
@ -148,7 +151,7 @@ GuiGamelistOptions::GuiGamelistOptions(SystemData* system)
// Don't show the sort type option if the gamelist type is recent/last played. // Don't show the sort type option if the gamelist type is recent/last played.
if (system->getName() != "recent") if (system->getName() != "recent")
mMenu.addWithLabel("SORT GAMES BY", mListSort); mMenu.addWithLabel(_("SORT GAMES BY"), mListSort);
} }
} }
@ -157,8 +160,9 @@ GuiGamelistOptions::GuiGamelistOptions(SystemData* system)
if (!mIsCustomCollectionGroup && system->getRootFolder()->getChildren().size() > 0) { if (!mIsCustomCollectionGroup && system->getRootFolder()->getChildren().size() > 0) {
if (system->getName() != "recent" && Settings::getInstance()->getBool("GamelistFilters")) { if (system->getName() != "recent" && Settings::getInstance()->getBool("GamelistFilters")) {
row.elements.clear(); row.elements.clear();
row.addElement(std::make_shared<TextComponent>( row.addElement(std::make_shared<TextComponent>(_("FILTER GAMELIST"),
"FILTER GAMELIST", Font::get(FONT_SIZE_MEDIUM), mMenuColorPrimary), Font::get(FONT_SIZE_MEDIUM),
mMenuColorPrimary),
true); true);
row.addElement(mMenu.makeArrow(), false); row.addElement(mMenu.makeArrow(), false);
row.makeAcceptInputHandler(std::bind(&GuiGamelistOptions::openGamelistFilter, this)); row.makeAcceptInputHandler(std::bind(&GuiGamelistOptions::openGamelistFilter, this));
@ -170,7 +174,7 @@ GuiGamelistOptions::GuiGamelistOptions(SystemData* system)
mSystem->getRootFolder()->getChildren().size() == 0 && !mIsCustomCollectionGroup && mSystem->getRootFolder()->getChildren().size() == 0 && !mIsCustomCollectionGroup &&
!mIsCustomCollection) { !mIsCustomCollection) {
row.elements.clear(); row.elements.clear();
row.addElement(std::make_shared<TextComponent>("THIS SYSTEM HAS NO GAMES", row.addElement(std::make_shared<TextComponent>(_("THIS SYSTEM HAS NO GAMES"),
Font::get(FONT_SIZE_MEDIUM), Font::get(FONT_SIZE_MEDIUM),
mMenuColorPrimary), mMenuColorPrimary),
true); true);
@ -182,7 +186,7 @@ GuiGamelistOptions::GuiGamelistOptions(SystemData* system)
if (CollectionSystemsManager::getInstance()->getEditingCollection() != if (CollectionSystemsManager::getInstance()->getEditingCollection() !=
getGamelist()->getCursor()->getSystem()->getName()) { getGamelist()->getCursor()->getSystem()->getName()) {
row.elements.clear(); row.elements.clear();
row.addElement(std::make_shared<TextComponent>("ADD/REMOVE GAMES TO THIS COLLECTION", row.addElement(std::make_shared<TextComponent>(_("ADD/REMOVE GAMES TO THIS COLLECTION"),
Font::get(FONT_SIZE_MEDIUM), Font::get(FONT_SIZE_MEDIUM),
mMenuColorPrimary), mMenuColorPrimary),
true); true);
@ -193,15 +197,14 @@ GuiGamelistOptions::GuiGamelistOptions(SystemData* system)
if (UIModeController::getInstance()->isUIModeFull() && if (UIModeController::getInstance()->isUIModeFull() &&
CollectionSystemsManager::getInstance()->isEditing()) { CollectionSystemsManager::getInstance()->isEditing()) {
const std::string editingText {Utils::String::format(
_("FINISH EDITING '%s' COLLECTION"),
Utils::String::toUpper(CollectionSystemsManager::getInstance()->getEditingCollection())
.c_str())};
row.elements.clear(); row.elements.clear();
row.addElement( row.addElement(std::make_shared<TextComponent>(editingText, Font::get(FONT_SIZE_MEDIUM),
std::make_shared<TextComponent>( mMenuColorPrimary),
"FINISH EDITING '" + true);
Utils::String::toUpper(
CollectionSystemsManager::getInstance()->getEditingCollection()) +
"' COLLECTION",
Font::get(FONT_SIZE_MEDIUM), mMenuColorPrimary),
true);
row.makeAcceptInputHandler(std::bind(&GuiGamelistOptions::exitEditMode, this)); row.makeAcceptInputHandler(std::bind(&GuiGamelistOptions::exitEditMode, this));
mMenu.addRow(row); mMenu.addRow(row);
} }
@ -210,7 +213,7 @@ GuiGamelistOptions::GuiGamelistOptions(SystemData* system)
if (UIModeController::getInstance()->isUIModeFull() && !mFromPlaceholder && if (UIModeController::getInstance()->isUIModeFull() && !mFromPlaceholder &&
!(mSystem->isCollection() && file->getType() == FOLDER)) { !(mSystem->isCollection() && file->getType() == FOLDER)) {
row.elements.clear(); row.elements.clear();
row.addElement(std::make_shared<TextComponent>("EDIT THIS FOLDER'S METADATA", row.addElement(std::make_shared<TextComponent>(_("EDIT THIS FOLDER'S METADATA"),
Font::get(FONT_SIZE_MEDIUM), Font::get(FONT_SIZE_MEDIUM),
mMenuColorPrimary), mMenuColorPrimary),
true); true);
@ -223,7 +226,7 @@ GuiGamelistOptions::GuiGamelistOptions(SystemData* system)
if (UIModeController::getInstance()->isUIModeFull() && !mFromPlaceholder && if (UIModeController::getInstance()->isUIModeFull() && !mFromPlaceholder &&
!(mSystem->isCollection() && file->getType() == FOLDER)) { !(mSystem->isCollection() && file->getType() == FOLDER)) {
row.elements.clear(); row.elements.clear();
row.addElement(std::make_shared<TextComponent>("EDIT THIS GAME'S METADATA", row.addElement(std::make_shared<TextComponent>(_("EDIT THIS GAME'S METADATA"),
Font::get(FONT_SIZE_MEDIUM), Font::get(FONT_SIZE_MEDIUM),
mMenuColorPrimary), mMenuColorPrimary),
true); true);
@ -235,7 +238,7 @@ GuiGamelistOptions::GuiGamelistOptions(SystemData* system)
if (file->getType() == FOLDER && file->metadata.get("folderlink") != "") { if (file->getType() == FOLDER && file->metadata.get("folderlink") != "") {
row.elements.clear(); row.elements.clear();
row.addElement(std::make_shared<TextComponent>("ENTER FOLDER (OVERRIDE FOLDER LINK)", row.addElement(std::make_shared<TextComponent>(_("ENTER FOLDER (OVERRIDE FOLDER LINK)"),
Font::get(FONT_SIZE_MEDIUM), Font::get(FONT_SIZE_MEDIUM),
mMenuColorPrimary), mMenuColorPrimary),
true); true);
@ -250,14 +253,14 @@ GuiGamelistOptions::GuiGamelistOptions(SystemData* system)
// Buttons. The logic to apply or cancel settings are handled by the destructor. // Buttons. The logic to apply or cancel settings are handled by the destructor.
if ((!mIsCustomCollectionGroup && system->getRootFolder()->getChildren().size() == 0) || if ((!mIsCustomCollectionGroup && system->getRootFolder()->getChildren().size() == 0) ||
system->getName() == "recent") { system->getName() == "recent") {
mMenu.addButton("CLOSE", "close", [&] { mMenu.addButton(_("CLOSE"), _("close"), [&] {
mCancelled = true; mCancelled = true;
delete this; delete this;
}); });
} }
else { else {
mMenu.addButton("APPLY", "apply", [&] { delete this; }); mMenu.addButton(_("APPLY"), _("apply"), [&] { delete this; });
mMenu.addButton("CANCEL", "cancel", [&] { mMenu.addButton(_("CANCEL"), _("cancel"), [&] {
mCancelled = true; mCancelled = true;
delete this; delete this;
}); });
@ -596,14 +599,14 @@ std::vector<HelpPrompt> GuiGamelistOptions::getHelpPrompts()
auto prompts = mMenu.getHelpPrompts(); auto prompts = mMenu.getHelpPrompts();
if (mSystem->getRootFolder()->getChildren().size() > 0 || mIsCustomCollectionGroup || if (mSystem->getRootFolder()->getChildren().size() > 0 || mIsCustomCollectionGroup ||
mIsCustomCollection || CollectionSystemsManager::getInstance()->isEditing()) mIsCustomCollection || CollectionSystemsManager::getInstance()->isEditing())
prompts.push_back(HelpPrompt("a", "select")); prompts.push_back(HelpPrompt("a", _("select")));
if (mSystem->getRootFolder()->getChildren().size() > 0 && mSystem->getName() != "recent") { if (mSystem->getRootFolder()->getChildren().size() > 0 && mSystem->getName() != "recent") {
prompts.push_back(HelpPrompt("b", "close (apply)")); prompts.push_back(HelpPrompt("b", _("close (apply)")));
prompts.push_back(HelpPrompt("back", "close (cancel)")); prompts.push_back(HelpPrompt("back", _("close (cancel)")));
} }
else { else {
prompts.push_back(HelpPrompt("b", "close")); prompts.push_back(HelpPrompt("b", _("close")));
prompts.push_back(HelpPrompt("back", "close")); prompts.push_back(HelpPrompt("back", _("close")));
} }
return prompts; return prompts;
} }

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// //
// ES-DE // ES-DE Frontend
// GuiGamelistOptions.h // GuiGamelistOptions.h
// //
// Gamelist options menu for the 'Jump to...' quick selector, // Gamelist options menu for the 'Jump to...' quick selector,

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// //
// ES-DE // ES-DE Frontend
// GuiLaunchScreen.cpp // GuiLaunchScreen.cpp
// //
// Screen shown when launching a game. // Screen shown when launching a game.
@ -12,6 +12,7 @@
#include "SystemData.h" #include "SystemData.h"
#include "components/ComponentGrid.h" #include "components/ComponentGrid.h"
#include "components/TextComponent.h" #include "components/TextComponent.h"
#include "utils/LocalizationUtil.h"
#include "utils/StringUtil.h" #include "utils/StringUtil.h"
GuiLaunchScreen::GuiLaunchScreen() GuiLaunchScreen::GuiLaunchScreen()
@ -54,7 +55,7 @@ void GuiLaunchScreen::displayLaunchScreen(FileData* game)
// Title. // Title.
mTitle = std::make_shared<TextComponent>( mTitle = std::make_shared<TextComponent>(
"LAUNCHING GAME", _("LAUNCHING GAME"),
Font::get(titleFontSize * Font::get(titleFontSize *
std::min(Renderer::getScreenHeight(), Renderer::getScreenWidth())), std::min(Renderer::getScreenHeight(), Renderer::getScreenWidth())),
mMenuColorTertiary, ALIGN_CENTER); mMenuColorTertiary, ALIGN_CENTER);

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// //
// ES-DE // ES-DE Frontend
// GuiLaunchScreen.h // GuiLaunchScreen.h
// //
// Screen shown when launching a game. // Screen shown when launching a game.

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// //
// ES-DE // ES-DE Frontend
// GuiMediaViewerOptions.cpp // GuiMediaViewerOptions.cpp
// //
// User interface for the media viewer options. // User interface for the media viewer options.
@ -12,23 +12,24 @@
#include "Settings.h" #include "Settings.h"
#include "components/OptionListComponent.h" #include "components/OptionListComponent.h"
#include "components/SwitchComponent.h" #include "components/SwitchComponent.h"
#include "utils/LocalizationUtil.h"
GuiMediaViewerOptions::GuiMediaViewerOptions(const std::string& title) GuiMediaViewerOptions::GuiMediaViewerOptions(const std::string& title)
: GuiSettings {title} : GuiSettings {title}
{ {
// Help prompts. // Help prompts.
auto mediaViewerHelpPrompts = auto mediaViewerHelpPrompts = std::make_shared<OptionListComponent<std::string>>(
std::make_shared<OptionListComponent<std::string>>(getHelpStyle(), "HELP PROMPTS", false); getHelpStyle(), _("HELP PROMPTS"), false);
std::string selectedHelpPrompts {Settings::getInstance()->getString("MediaViewerHelpPrompts")}; std::string selectedHelpPrompts {Settings::getInstance()->getString("MediaViewerHelpPrompts")};
mediaViewerHelpPrompts->add("TOP", "top", selectedHelpPrompts == "top"); mediaViewerHelpPrompts->add(_("TOP"), "top", selectedHelpPrompts == "top");
mediaViewerHelpPrompts->add("BOTTOM", "bottom", selectedHelpPrompts == "bottom"); mediaViewerHelpPrompts->add(_("BOTTOM"), "bottom", selectedHelpPrompts == "bottom");
mediaViewerHelpPrompts->add("DISABLED", "disabled", selectedHelpPrompts == "disabled"); mediaViewerHelpPrompts->add(_("DISABLED"), "disabled", selectedHelpPrompts == "disabled");
// If there are no objects returned, then there must be a manually modified entry in the // If there are no objects returned, then there must be a manually modified entry in the
// configuration file. Simply set the help prompts to "top" in this case. // configuration file. Simply set the help prompts to "top" in this case.
if (mediaViewerHelpPrompts->getSelectedObjects().size() == 0) if (mediaViewerHelpPrompts->getSelectedObjects().size() == 0)
mediaViewerHelpPrompts->selectEntry(0); mediaViewerHelpPrompts->selectEntry(0);
addWithLabel("HELP PROMPTS", mediaViewerHelpPrompts); addWithLabel(_("HELP PROMPTS"), mediaViewerHelpPrompts);
addSaveFunc([mediaViewerHelpPrompts, this] { addSaveFunc([mediaViewerHelpPrompts, this] {
if (mediaViewerHelpPrompts->getSelected() != if (mediaViewerHelpPrompts->getSelected() !=
Settings::getInstance()->getString("MediaViewerHelpPrompts")) { Settings::getInstance()->getString("MediaViewerHelpPrompts")) {
@ -41,7 +42,7 @@ GuiMediaViewerOptions::GuiMediaViewerOptions(const std::string& title)
// Display media types. // Display media types.
auto mediaViewerShowTypes = std::make_shared<SwitchComponent>(); auto mediaViewerShowTypes = std::make_shared<SwitchComponent>();
mediaViewerShowTypes->setState(Settings::getInstance()->getBool("MediaViewerShowTypes")); mediaViewerShowTypes->setState(Settings::getInstance()->getBool("MediaViewerShowTypes"));
addWithLabel("DISPLAY MEDIA TYPES", mediaViewerShowTypes); addWithLabel(_("DISPLAY MEDIA TYPES"), mediaViewerShowTypes);
addSaveFunc([mediaViewerShowTypes, this] { addSaveFunc([mediaViewerShowTypes, this] {
if (mediaViewerShowTypes->getState() != if (mediaViewerShowTypes->getState() !=
Settings::getInstance()->getBool("MediaViewerShowTypes")) { Settings::getInstance()->getBool("MediaViewerShowTypes")) {
@ -54,7 +55,7 @@ GuiMediaViewerOptions::GuiMediaViewerOptions(const std::string& title)
// Keep videos running when viewing images. // Keep videos running when viewing images.
auto keepVideoRunning = std::make_shared<SwitchComponent>(); auto keepVideoRunning = std::make_shared<SwitchComponent>();
keepVideoRunning->setState(Settings::getInstance()->getBool("MediaViewerKeepVideoRunning")); keepVideoRunning->setState(Settings::getInstance()->getBool("MediaViewerKeepVideoRunning"));
addWithLabel("KEEP VIDEOS RUNNING WHEN VIEWING IMAGES", keepVideoRunning); addWithLabel(_("KEEP VIDEOS RUNNING WHEN VIEWING IMAGES"), keepVideoRunning);
addSaveFunc([keepVideoRunning, this] { addSaveFunc([keepVideoRunning, this] {
if (keepVideoRunning->getState() != if (keepVideoRunning->getState() !=
Settings::getInstance()->getBool("MediaViewerKeepVideoRunning")) { Settings::getInstance()->getBool("MediaViewerKeepVideoRunning")) {
@ -67,7 +68,7 @@ GuiMediaViewerOptions::GuiMediaViewerOptions(const std::string& title)
// Stretch videos to screen resolution. // Stretch videos to screen resolution.
auto stretchVideos = std::make_shared<SwitchComponent>(); auto stretchVideos = std::make_shared<SwitchComponent>();
stretchVideos->setState(Settings::getInstance()->getBool("MediaViewerStretchVideos")); stretchVideos->setState(Settings::getInstance()->getBool("MediaViewerStretchVideos"));
addWithLabel("STRETCH VIDEOS TO SCREEN RESOLUTION", stretchVideos); addWithLabel(_("STRETCH VIDEOS TO SCREEN RESOLUTION"), stretchVideos);
addSaveFunc([stretchVideos, this] { addSaveFunc([stretchVideos, this] {
if (stretchVideos->getState() != if (stretchVideos->getState() !=
Settings::getInstance()->getBool("MediaViewerStretchVideos")) { Settings::getInstance()->getBool("MediaViewerStretchVideos")) {
@ -79,7 +80,7 @@ GuiMediaViewerOptions::GuiMediaViewerOptions(const std::string& title)
// Render scanlines for videos using a shader. // Render scanlines for videos using a shader.
auto videoScanlines = std::make_shared<SwitchComponent>(); auto videoScanlines = std::make_shared<SwitchComponent>();
videoScanlines->setState(Settings::getInstance()->getBool("MediaViewerVideoScanlines")); videoScanlines->setState(Settings::getInstance()->getBool("MediaViewerVideoScanlines"));
addWithLabel("RENDER SCANLINES FOR VIDEOS", videoScanlines); addWithLabel(_("RENDER SCANLINES FOR VIDEOS"), videoScanlines);
addSaveFunc([videoScanlines, this] { addSaveFunc([videoScanlines, this] {
if (videoScanlines->getState() != if (videoScanlines->getState() !=
Settings::getInstance()->getBool("MediaViewerVideoScanlines")) { Settings::getInstance()->getBool("MediaViewerVideoScanlines")) {
@ -92,7 +93,7 @@ GuiMediaViewerOptions::GuiMediaViewerOptions(const std::string& title)
// Render blur for videos using a shader. // Render blur for videos using a shader.
auto videoBlur = std::make_shared<SwitchComponent>(); auto videoBlur = std::make_shared<SwitchComponent>();
videoBlur->setState(Settings::getInstance()->getBool("MediaViewerVideoBlur")); videoBlur->setState(Settings::getInstance()->getBool("MediaViewerVideoBlur"));
addWithLabel("RENDER BLUR FOR VIDEOS", videoBlur); addWithLabel(_("RENDER BLUR FOR VIDEOS"), videoBlur);
addSaveFunc([videoBlur, this] { addSaveFunc([videoBlur, this] {
if (videoBlur->getState() != Settings::getInstance()->getBool("MediaViewerVideoBlur")) { if (videoBlur->getState() != Settings::getInstance()->getBool("MediaViewerVideoBlur")) {
Settings::getInstance()->setBool("MediaViewerVideoBlur", videoBlur->getState()); Settings::getInstance()->setBool("MediaViewerVideoBlur", videoBlur->getState());
@ -104,7 +105,7 @@ GuiMediaViewerOptions::GuiMediaViewerOptions(const std::string& title)
auto screenshotScanlines = std::make_shared<SwitchComponent>(); auto screenshotScanlines = std::make_shared<SwitchComponent>();
screenshotScanlines->setState( screenshotScanlines->setState(
Settings::getInstance()->getBool("MediaViewerScreenshotScanlines")); Settings::getInstance()->getBool("MediaViewerScreenshotScanlines"));
addWithLabel("RENDER SCANLINES FOR SCREENSHOTS AND TITLES", screenshotScanlines); addWithLabel(_("RENDER SCANLINES FOR SCREENSHOTS AND TITLES"), screenshotScanlines);
addSaveFunc([screenshotScanlines, this] { addSaveFunc([screenshotScanlines, this] {
if (screenshotScanlines->getState() != if (screenshotScanlines->getState() !=
Settings::getInstance()->getBool("MediaViewerScreenshotScanlines")) { Settings::getInstance()->getBool("MediaViewerScreenshotScanlines")) {

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// //
// ES-DE // ES-DE Frontend
// GuiMediaViewerOptions.h // GuiMediaViewerOptions.h
// //
// User interface for the media viewer options. // User interface for the media viewer options.

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// //
// ES-DE // ES-DE Frontend
// GuiMenu.h // GuiMenu.h
// //
// Main menu. // Main menu.

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// //
// ES-DE // ES-DE Frontend
// GuiMetaDataEd.cpp // GuiMetaDataEd.cpp
// //
// Game metadata edit user interface. // Game metadata edit user interface.
@ -29,6 +29,7 @@
#include "guis/GuiTextEditKeyboardPopup.h" #include "guis/GuiTextEditKeyboardPopup.h"
#include "guis/GuiTextEditPopup.h" #include "guis/GuiTextEditPopup.h"
#include "resources/Font.h" #include "resources/Font.h"
#include "utils/LocalizationUtil.h"
#include "utils/StringUtil.h" #include "utils/StringUtil.h"
#define TITLE_HEIGHT \ #define TITLE_HEIGHT \
@ -70,8 +71,9 @@ GuiMetaDataEd::GuiMetaDataEd(MetaDataList* md,
addChild(&mBackground); addChild(&mBackground);
addChild(&mGrid); addChild(&mGrid);
mTitle = std::make_shared<TextComponent>("EDIT METADATA", Font::get(FONT_SIZE_LARGE), mTitle = std::make_shared<TextComponent>(
mMenuColorTitle, ALIGN_CENTER); _("EDIT METADATA"), Font::get(FONT_SIZE_LARGE * Utils::Localization::sMenuTitleScaleFactor),
mMenuColorTitle, ALIGN_CENTER);
mGrid.setEntry(mTitle, glm::ivec2 {0, 0}, false, true, glm::ivec2 {2, 2}); mGrid.setEntry(mTitle, glm::ivec2 {0, 0}, false, true, glm::ivec2 {2, 2});
// Extract possible subfolders from the path. // Extract possible subfolders from the path.
@ -137,7 +139,7 @@ GuiMetaDataEd::GuiMetaDataEd(MetaDataList* md,
it->type == MD_ALT_EMULATOR) { it->type == MD_ALT_EMULATOR) {
ed = std::make_shared<TextComponent>("", Font::get(FONT_SIZE_SMALL, FONT_PATH_LIGHT), ed = std::make_shared<TextComponent>("", Font::get(FONT_SIZE_SMALL, FONT_PATH_LIGHT),
mMenuColorPrimary, ALIGN_RIGHT); mMenuColorPrimary, ALIGN_RIGHT);
assert(ed); ed->setSize(0.0f, ed->getFont()->getHeight());
ed->setValue(mMetaData->get(it->key)); ed->setValue(mMetaData->get(it->key));
mEditors.push_back(ed); mEditors.push_back(ed);
continue; continue;
@ -147,7 +149,7 @@ GuiMetaDataEd::GuiMetaDataEd(MetaDataList* md,
// entry instead of for instance the spacer. That is so because ComponentList // entry instead of for instance the spacer. That is so because ComponentList
// always looks for the help prompt at the back of the element stack. // always looks for the help prompt at the back of the element stack.
ComponentListRow row; ComponentListRow row;
auto lbl = std::make_shared<TextComponent>(Utils::String::toUpper(it->displayName), auto lbl = std::make_shared<TextComponent>(_p("metadata", it->displayName.c_str()),
Font::get(FONT_SIZE_SMALL), mMenuColorPrimary); Font::get(FONT_SIZE_SMALL), mMenuColorPrimary);
row.addElement(lbl, true); // Label. row.addElement(lbl, true); // Label.
@ -195,6 +197,7 @@ GuiMetaDataEd::GuiMetaDataEd(MetaDataList* md,
ed = ed =
std::make_shared<TextComponent>("", Font::get(FONT_SIZE_SMALL, FONT_PATH_LIGHT), std::make_shared<TextComponent>("", Font::get(FONT_SIZE_SMALL, FONT_PATH_LIGHT),
mMenuColorPrimary, ALIGN_RIGHT); mMenuColorPrimary, ALIGN_RIGHT);
ed->setSize(0.0f, ed->getFont()->getHeight());
row.addElement(ed, true); row.addElement(ed, true);
auto spacer = std::make_shared<GuiComponent>(); auto spacer = std::make_shared<GuiComponent>();
@ -207,7 +210,7 @@ GuiMetaDataEd::GuiMetaDataEd(MetaDataList* md,
bracket->setColorShift(mMenuColorPrimary); bracket->setColorShift(mMenuColorPrimary);
row.addElement(bracket, false); row.addElement(bracket, false);
const std::string title {it->displayPrompt}; const std::string title {_p("metadata", it->displayPrompt.c_str())};
// OK callback (apply new value to ed). // OK callback (apply new value to ed).
auto updateVal = [ed, originalValue](const std::string& newVal) { auto updateVal = [ed, originalValue](const std::string& newVal) {
@ -251,7 +254,7 @@ GuiMetaDataEd::GuiMetaDataEd(MetaDataList* md,
if (ed->getValue() != "") { if (ed->getValue() != "") {
ComponentListRow row; ComponentListRow row;
std::shared_ptr<TextComponent> clearText {std::make_shared<TextComponent>( std::shared_ptr<TextComponent> clearText {std::make_shared<TextComponent>(
ViewController::CROSSEDCIRCLE_CHAR + " CLEAR ENTRY", ViewController::CROSSEDCIRCLE_CHAR + " " + _("CLEAR ENTRY"),
Font::get(FONT_SIZE_MEDIUM), mMenuColorPrimary)}; Font::get(FONT_SIZE_MEDIUM), mMenuColorPrimary)};
clearText->setSelectable(true); clearText->setSelectable(true);
row.addElement(clearText, true); row.addElement(clearText, true);
@ -281,6 +284,7 @@ GuiMetaDataEd::GuiMetaDataEd(MetaDataList* md,
ed = ed =
std::make_shared<TextComponent>("", Font::get(FONT_SIZE_SMALL, FONT_PATH_LIGHT), std::make_shared<TextComponent>("", Font::get(FONT_SIZE_SMALL, FONT_PATH_LIGHT),
mMenuColorPrimary, ALIGN_RIGHT); mMenuColorPrimary, ALIGN_RIGHT);
ed->setSize(0.0f, ed->getFont()->getHeight());
row.addElement(ed, true); row.addElement(ed, true);
auto spacer = std::make_shared<GuiComponent>(); auto spacer = std::make_shared<GuiComponent>();
@ -293,8 +297,9 @@ GuiMetaDataEd::GuiMetaDataEd(MetaDataList* md,
bracket->setColorShift(mMenuColorPrimary); bracket->setColorShift(mMenuColorPrimary);
row.addElement(bracket, false); row.addElement(bracket, false);
const std::string title {mRenderer->getIsVerticalOrientation() ? "select emulator" : const std::string title {mRenderer->getIsVerticalOrientation() ?
it->displayPrompt}; _("SELECT EMULATOR") :
_p("metadata", it->displayPrompt.c_str())};
// OK callback (apply new value to ed). // OK callback (apply new value to ed).
auto updateVal = [this, ed, originalValue](const std::string& newVal) { auto updateVal = [this, ed, originalValue](const std::string& newVal) {
@ -334,7 +339,7 @@ GuiMetaDataEd::GuiMetaDataEd(MetaDataList* md,
GuiSettings* s {nullptr}; GuiSettings* s {nullptr};
if (mInvalidEmulatorEntry && singleEntry) if (mInvalidEmulatorEntry && singleEntry)
s = new GuiSettings("CLEAR INVALID ENTRY"); s = new GuiSettings(_("CLEAR INVALID ENTRY"));
else else
s = new GuiSettings(title); s = new GuiSettings(title);
@ -346,7 +351,7 @@ GuiMetaDataEd::GuiMetaDataEd(MetaDataList* md,
"", ViewController::EXCLAMATION_CHAR + " " + originalValue)); "", ViewController::EXCLAMATION_CHAR + " " + originalValue));
else if (ed->getValue() != "") else if (ed->getValue() != "")
launchCommands.push_back(std::make_pair( launchCommands.push_back(std::make_pair(
"", ViewController::CROSSEDCIRCLE_CHAR + " CLEAR ENTRY")); "", ViewController::CROSSEDCIRCLE_CHAR + " " + _("CLEAR ENTRY")));
for (auto entry : launchCommands) { for (auto entry : launchCommands) {
if (mInvalidEmulatorEntry && singleEntry && if (mInvalidEmulatorEntry && singleEntry &&
@ -372,10 +377,16 @@ GuiMetaDataEd::GuiMetaDataEd(MetaDataList* md,
scraperParams.system->getSystemEnvData() scraperParams.system->getSystemEnvData()
->mLaunchCommands.front() ->mLaunchCommands.front()
.second == label) .second == label)
labelText->setValue(labelText->getValue().append(" [SYSTEM-WIDE]")); labelText->setValue(labelText->getValue()
.append(" [")
.append(_("SYSTEM-WIDE"))
.append("]"));
if (scraperParams.system->getAlternativeEmulator() == label) if (scraperParams.system->getAlternativeEmulator() == label)
labelText->setValue(labelText->getValue().append(" [SYSTEM-WIDE]")); labelText->setValue(labelText->getValue()
.append(" [")
.append(_("SYSTEM-WIDE"))
.append("]"));
row.addElement(labelText, true); row.addElement(labelText, true);
row.makeAcceptInputHandler( row.makeAcceptInputHandler(
@ -421,6 +432,7 @@ GuiMetaDataEd::GuiMetaDataEd(MetaDataList* md,
ed = ed =
std::make_shared<TextComponent>("", Font::get(FONT_SIZE_SMALL, FONT_PATH_LIGHT), std::make_shared<TextComponent>("", Font::get(FONT_SIZE_SMALL, FONT_PATH_LIGHT),
mMenuColorPrimary, ALIGN_RIGHT); mMenuColorPrimary, ALIGN_RIGHT);
ed->setSize(0.0f, ed->getFont()->getHeight());
row.addElement(ed, true); row.addElement(ed, true);
auto spacer = std::make_shared<GuiComponent>(); auto spacer = std::make_shared<GuiComponent>();
@ -433,7 +445,7 @@ GuiMetaDataEd::GuiMetaDataEd(MetaDataList* md,
bracket->setColorShift(mMenuColorPrimary); bracket->setColorShift(mMenuColorPrimary);
row.addElement(bracket, false); row.addElement(bracket, false);
const std::string title {it->displayPrompt}; const std::string title {_p("metadata", it->displayPrompt.c_str())};
std::vector<FileData*> children; std::vector<FileData*> children;
if (originalValue != "") if (originalValue != "")
@ -510,7 +522,7 @@ GuiMetaDataEd::GuiMetaDataEd(MetaDataList* md,
if (ed->getValue() != "") { if (ed->getValue() != "") {
ComponentListRow row; ComponentListRow row;
std::shared_ptr<TextComponent> clearText {std::make_shared<TextComponent>( std::shared_ptr<TextComponent> clearText {std::make_shared<TextComponent>(
ViewController::CROSSEDCIRCLE_CHAR + " CLEAR ENTRY", ViewController::CROSSEDCIRCLE_CHAR + " " + _("CLEAR ENTRY"),
Font::get(FONT_SIZE_MEDIUM), mMenuColorPrimary)}; Font::get(FONT_SIZE_MEDIUM), mMenuColorPrimary)};
clearText->setSelectable(true); clearText->setSelectable(true);
row.addElement(clearText, true); row.addElement(clearText, true);
@ -541,6 +553,8 @@ GuiMetaDataEd::GuiMetaDataEd(MetaDataList* md,
ed = ed =
std::make_shared<TextComponent>("", Font::get(FONT_SIZE_SMALL, FONT_PATH_LIGHT), std::make_shared<TextComponent>("", Font::get(FONT_SIZE_SMALL, FONT_PATH_LIGHT),
mMenuColorPrimary, ALIGN_RIGHT); mMenuColorPrimary, ALIGN_RIGHT);
ed->setRemoveLineBreaks(true);
ed->setSize(0.0f, ed->getFont()->getHeight());
row.addElement(ed, true); row.addElement(ed, true);
auto spacer = std::make_shared<GuiComponent>(); auto spacer = std::make_shared<GuiComponent>();
@ -554,7 +568,7 @@ GuiMetaDataEd::GuiMetaDataEd(MetaDataList* md,
row.addElement(bracket, false); row.addElement(bracket, false);
bool multiLine {it->type == MD_MULTILINE_STRING}; bool multiLine {it->type == MD_MULTILINE_STRING};
const std::string title {it->displayPrompt}; const std::string title {_p("metadata", it->displayPrompt.c_str())};
gamePath = Utils::FileSystem::getStem(scraperParams.game->getPath()); gamePath = Utils::FileSystem::getStem(scraperParams.game->getPath());
@ -585,7 +599,7 @@ GuiMetaDataEd::GuiMetaDataEd(MetaDataList* md,
else if (newVal == "" && else if (newVal == "" &&
(currentKey == "developer" || currentKey == "publisher" || (currentKey == "developer" || currentKey == "publisher" ||
currentKey == "genre" || currentKey == "players")) { currentKey == "genre" || currentKey == "players")) {
ed->setValue("unknown"); ed->setValue(_("unknown"));
if (originalValue == "unknown") if (originalValue == "unknown")
ed->setColor(mMenuColorPrimary); ed->setColor(mMenuColorPrimary);
else else
@ -593,10 +607,17 @@ GuiMetaDataEd::GuiMetaDataEd(MetaDataList* md,
} }
else { else {
ed->setValue(newVal); ed->setValue(newVal);
if (newVal == originalValue) if ((currentKey == "developer" || currentKey == "publisher" ||
currentKey == "genre" || currentKey == "players") &&
newVal == _("unknown") && newVal == _(originalValue.c_str())) {
ed->setColor(mMenuColorPrimary); ed->setColor(mMenuColorPrimary);
else }
ed->setColor(mMenuColorBlue); else {
if (newVal == originalValue)
ed->setColor(mMenuColorPrimary);
else
ed->setColor(mMenuColorBlue);
}
} }
}; };
@ -606,14 +627,14 @@ GuiMetaDataEd::GuiMetaDataEd(MetaDataList* md,
mRenderer->getIsVerticalOrientation() ? mPosition.y : 0.0f}; mRenderer->getIsVerticalOrientation() ? mPosition.y : 0.0f};
mWindow->pushGui(new GuiTextEditKeyboardPopup( mWindow->pushGui(new GuiTextEditKeyboardPopup(
getHelpStyle(), verticalPosition, title, ed->getValue(), updateVal, getHelpStyle(), verticalPosition, title, ed->getValue(), updateVal,
multiLine, "apply", "APPLY CHANGES?", "", "")); multiLine, _("APPLY"), _("APPLY CHANGES?"), "", ""));
}); });
} }
else { else {
row.makeAcceptInputHandler([this, title, ed, updateVal, multiLine] { row.makeAcceptInputHandler([this, title, ed, updateVal, multiLine] {
mWindow->pushGui(new GuiTextEditPopup(getHelpStyle(), title, ed->getValue(), mWindow->pushGui(new GuiTextEditPopup(getHelpStyle(), title, ed->getValue(),
updateVal, multiLine, "APPLY", updateVal, multiLine, _("APPLY"),
"APPLY CHANGES?")); _("APPLY CHANGES?")));
}); });
} }
break; break;
@ -637,7 +658,12 @@ GuiMetaDataEd::GuiMetaDataEd(MetaDataList* md,
ed->setValue(ViewController::EXCLAMATION_CHAR + " " + mMetaData->get(it->key)); ed->setValue(ViewController::EXCLAMATION_CHAR + " " + mMetaData->get(it->key));
} }
else { else {
ed->setValue(mMetaData->get(it->key)); if ((currentKey == "developer" || currentKey == "publisher" || currentKey == "genre" ||
currentKey == "players") &&
mMetaData->get(it->key) == "unknown")
ed->setValue(_(mMetaData->get(it->key).c_str()));
else
ed->setValue(mMetaData->get(it->key));
} }
mEditors.push_back(ed); mEditors.push_back(ed);
@ -647,14 +673,14 @@ GuiMetaDataEd::GuiMetaDataEd(MetaDataList* md,
if (!scraperParams.system->hasPlatformId(PlatformIds::PLATFORM_IGNORE)) if (!scraperParams.system->hasPlatformId(PlatformIds::PLATFORM_IGNORE))
buttons.push_back(std::make_shared<ButtonComponent>( buttons.push_back(std::make_shared<ButtonComponent>(
"SCRAPE", "scrape", std::bind(&GuiMetaDataEd::fetch, this))); _("SCRAPE"), _("scrape"), std::bind(&GuiMetaDataEd::fetch, this)));
buttons.push_back(std::make_shared<ButtonComponent>("SAVE", "save metadata", [&] { buttons.push_back(std::make_shared<ButtonComponent>(_("SAVE"), _("save metadata"), [&] {
save(); save();
delete this; delete this;
})); }));
buttons.push_back( buttons.push_back(
std::make_shared<ButtonComponent>("CANCEL", "cancel changes", [&] { delete this; })); std::make_shared<ButtonComponent>(_("CANCEL"), _("cancel changes"), [&] { delete this; }));
if (scraperParams.game->getType() == FOLDER) { if (scraperParams.game->getType() == FOLDER) {
if (mClearGameFunc) { if (mClearGameFunc) {
auto clearSelf = [&] { auto clearSelf = [&] {
@ -662,16 +688,19 @@ GuiMetaDataEd::GuiMetaDataEd(MetaDataList* md,
delete this; delete this;
}; };
auto clearSelfBtnFunc = [this, clearSelf] { auto clearSelfBtnFunc = [this, clearSelf] {
mWindow->pushGui(new GuiMsgBox(getHelpStyle(), mWindow->pushGui(new GuiMsgBox(
"THIS WILL DELETE ANY MEDIA FILES AND\n" getHelpStyle(),
"THE GAMELIST.XML ENTRY FOR THIS FOLDER,\n" _("THIS WILL DELETE ANY MEDIA FILES AND "
"BUT NEITHER THE FOLDER ITSELF OR ANY\n" "THE GAMELIST.XML ENTRY FOR THIS FOLDER, "
"CONTENT INSIDE IT WILL BE REMOVED\n" "BUT NEITHER THE DIRECTORY ITSELF OR ANY "
"ARE YOU SURE?", "CONTENT INSIDE IT WILL BE REMOVED\nARE YOU SURE?"),
"YES", clearSelf, "NO", nullptr)); _("YES"), clearSelf, _("NO"), nullptr, "", nullptr, nullptr, false, true,
(mRenderer->getIsVerticalOrientation() ?
0.70f :
0.46f * (1.778f / mRenderer->getScreenAspectRatio()))));
}; };
buttons.push_back( buttons.push_back(
std::make_shared<ButtonComponent>("CLEAR", "clear folder", clearSelfBtnFunc)); std::make_shared<ButtonComponent>(_("CLEAR"), _("clear folder"), clearSelfBtnFunc));
} }
} }
else { else {
@ -681,16 +710,19 @@ GuiMetaDataEd::GuiMetaDataEd(MetaDataList* md,
delete this; delete this;
}; };
auto clearSelfBtnFunc = [this, clearSelf] { auto clearSelfBtnFunc = [this, clearSelf] {
mWindow->pushGui(new GuiMsgBox(getHelpStyle(), mWindow->pushGui(new GuiMsgBox(
"THIS WILL DELETE ANY MEDIA FILES\n" getHelpStyle(),
"AND THE GAMELIST.XML ENTRY FOR\n" _("THIS WILL DELETE ANY MEDIA FILES "
"THIS GAME, BUT THE GAME FILE\n" "AND THE GAMELIST.XML ENTRY FOR "
"ITSELF WILL NOT BE REMOVED\n" "THIS GAME, BUT THE GAME FILE "
"ARE YOU SURE?", "ITSELF WILL NOT BE REMOVED\nARE YOU SURE?"),
"YES", clearSelf, "NO", nullptr)); _("YES"), clearSelf, _("NO"), nullptr, "", nullptr, nullptr, false, true,
(mRenderer->getIsVerticalOrientation() ?
0.70f :
0.46f * (1.778f / mRenderer->getScreenAspectRatio()))));
}; };
buttons.push_back( buttons.push_back(
std::make_shared<ButtonComponent>("CLEAR", "clear file", clearSelfBtnFunc)); std::make_shared<ButtonComponent>(_("CLEAR"), _("clear file"), clearSelfBtnFunc));
} }
// For the special case where a directory has a supported file extension and is therefore // For the special case where a directory has a supported file extension and is therefore
@ -701,15 +733,19 @@ GuiMetaDataEd::GuiMetaDataEd(MetaDataList* md,
delete this; delete this;
}; };
auto deleteGameBtnFunc = [this, deleteFilesAndSelf] { auto deleteGameBtnFunc = [this, deleteFilesAndSelf] {
mWindow->pushGui(new GuiMsgBox(getHelpStyle(), mWindow->pushGui(
"THIS WILL DELETE THE GAME\n" new GuiMsgBox(getHelpStyle(),
"FILE, ANY MEDIA FILES AND\n" _("THIS WILL DELETE THE GAME "
"THE GAMELIST.XML ENTRY\n" "FILE, ANY MEDIA FILES AND "
"ARE YOU SURE?", "THE GAMELIST.XML ENTRY\nARE YOU SURE?"),
"YES", deleteFilesAndSelf, "NO", nullptr)); _("YES"), deleteFilesAndSelf, _("NO"), nullptr, "", nullptr,
nullptr, false, true,
(mRenderer->getIsVerticalOrientation() ?
0.70f :
0.46f * (1.778f / mRenderer->getScreenAspectRatio()))));
}; };
buttons.push_back( buttons.push_back(std::make_shared<ButtonComponent>(_("DELETE"), _("delete game"),
std::make_shared<ButtonComponent>("DELETE", "delete game", deleteGameBtnFunc)); deleteGameBtnFunc));
} }
} }
@ -764,7 +800,7 @@ void GuiMetaDataEd::save()
bool setGameAsCounted {false}; bool setGameAsCounted {false};
int offset {0}; int offset {0};
for (unsigned int i = 0; i < mEditors.size(); ++i) { for (unsigned int i {0}; i < mEditors.size(); ++i) {
// The offset is needed to make the editor and metadata fields match up if we're // The offset is needed to make the editor and metadata fields match up if we're
// skipping the custom collections sortname field (which we do if not editing the // skipping the custom collections sortname field (which we do if not editing the
// game from within a custom collection gamelist). // game from within a custom collection gamelist).
@ -783,7 +819,7 @@ void GuiMetaDataEd::save()
continue; continue;
if (key == "controller" && mEditors.at(i)->getValue() != "") { if (key == "controller" && mEditors.at(i)->getValue() != "") {
std::string shortName = BadgeComponent::getShortName(mEditors.at(i)->getValue()); std::string shortName {BadgeComponent::getShortName(mEditors.at(i)->getValue())};
if (shortName != "unknown") if (shortName != "unknown")
mMetaData->set(key, shortName); mMetaData->set(key, shortName);
continue; continue;
@ -799,7 +835,13 @@ void GuiMetaDataEd::save()
setGameAsCounted = true; setGameAsCounted = true;
} }
mMetaData->set(key, mEditors.at(i)->getValue()); if ((key == "developer" || key == "publisher" || key == "genre" || key == "players") &&
mEditors.at(i)->getValue() == _("unknown")) {
mMetaData->set(key, "unknown");
}
else {
mMetaData->set(key, mEditors.at(i)->getValue());
}
} }
// If hidden games are not shown and the hide flag was set for the entry, then write the // If hidden games are not shown and the hide flag was set for the entry, then write the
@ -960,6 +1002,11 @@ void GuiMetaDataEd::close()
std::string mMetaDataValue {mMetaData->get(key)}; std::string mMetaDataValue {mMetaData->get(key)};
std::string mEditorsValue {mEditors.at(i)->getValue()}; std::string mEditorsValue {mEditors.at(i)->getValue()};
if ((key == "developer" || key == "publisher" || key == "genre" || key == "players") &&
mEditorsValue == _("unknown")) {
mEditorsValue = "unknown";
}
if (key == "controller" && mEditors.at(i)->getValue() != "") { if (key == "controller" && mEditors.at(i)->getValue() != "") {
std::string shortName = BadgeComponent::getShortName(mEditors.at(i)->getValue()); std::string shortName = BadgeComponent::getShortName(mEditors.at(i)->getValue());
if (shortName == "unknown" || mMetaDataValue == shortName) if (shortName == "unknown" || mMetaDataValue == shortName)
@ -992,12 +1039,12 @@ void GuiMetaDataEd::close()
if (metadataUpdated) { if (metadataUpdated) {
// Changes were made, ask if the user wants to save them. // Changes were made, ask if the user wants to save them.
mWindow->pushGui(new GuiMsgBox( mWindow->pushGui(new GuiMsgBox(
getHelpStyle(), "SAVE CHANGES?", "YES", getHelpStyle(), _("SAVE CHANGES?"), _("YES"),
[this, closeFunc] { [this, closeFunc] {
save(); save();
closeFunc(); closeFunc();
}, },
"NO", closeFunc, "", nullptr, nullptr, true)); _("NO"), closeFunc, "", nullptr, nullptr, true));
} }
else { else {
// Always save if the media files have been changed (i.e. newly scraped images). // Always save if the media files have been changed (i.e. newly scraped images).
@ -1028,7 +1075,7 @@ bool GuiMetaDataEd::input(InputConfig* config, Input input)
std::vector<HelpPrompt> GuiMetaDataEd::getHelpPrompts() std::vector<HelpPrompt> GuiMetaDataEd::getHelpPrompts()
{ {
std::vector<HelpPrompt> prompts {mGrid.getHelpPrompts()}; std::vector<HelpPrompt> prompts {mGrid.getHelpPrompts()};
prompts.push_back(HelpPrompt("y", "scrape")); prompts.push_back(HelpPrompt("y", _("scrape")));
prompts.push_back(HelpPrompt("b", "back")); prompts.push_back(HelpPrompt("b", _("back")));
return prompts; return prompts;
} }

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// //
// ES-DE // ES-DE Frontend
// GuiMetaDataEd.h // GuiMetaDataEd.h
// //
// Game metadata edit user interface. // Game metadata edit user interface.

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// //
// ES-DE // ES-DE Frontend
// GuiOfflineGenerator.cpp // GuiOfflineGenerator.cpp
// //
// User interface for the miximage offline generator. // User interface for the miximage offline generator.
@ -11,6 +11,7 @@
#include "SystemData.h" #include "SystemData.h"
#include "components/MenuComponent.h" #include "components/MenuComponent.h"
#include "utils/LocalizationUtil.h"
GuiOfflineGenerator::GuiOfflineGenerator(const std::queue<FileData*>& gameQueue) GuiOfflineGenerator::GuiOfflineGenerator(const std::queue<FileData*>& gameQueue)
: mGameQueue {gameQueue} : mGameQueue {gameQueue}
@ -36,17 +37,21 @@ GuiOfflineGenerator::GuiOfflineGenerator(const std::queue<FileData*>& gameQueue)
// Header. // Header.
mTitle = std::make_shared<TextComponent>( mTitle = std::make_shared<TextComponent>(
"MIXIMAGE OFFLINE GENERATOR", Font::get(FONT_SIZE_LARGE), mMenuColorTitle, ALIGN_CENTER); _("MIXIMAGE OFFLINE GENERATOR"),
Font::get(FONT_SIZE_LARGE * Utils::Localization::sMenuTitleScaleFactor), mMenuColorTitle,
ALIGN_CENTER);
mGrid.setEntry(mTitle, glm::ivec2 {0, 0}, false, true, glm::ivec2 {6, 1}); mGrid.setEntry(mTitle, glm::ivec2 {0, 0}, false, true, glm::ivec2 {6, 1});
mStatus = std::make_shared<TextComponent>("NOT STARTED", Font::get(FONT_SIZE_MEDIUM), mStatus = std::make_shared<TextComponent>(_("NOT STARTED"), Font::get(FONT_SIZE_MEDIUM),
mMenuColorPrimary, ALIGN_CENTER); mMenuColorPrimary, ALIGN_CENTER);
mGrid.setEntry(mStatus, glm::ivec2 {0, 1}, false, true, glm::ivec2 {6, 1}); mGrid.setEntry(mStatus, glm::ivec2 {0, 1}, false, true, glm::ivec2 {6, 1});
mGameCounter = std::make_shared<TextComponent>( const std::string gameProcessText {Utils::String::format(
std::to_string(mGamesProcessed) + " OF " + std::to_string(mTotalGames) + _n("%i OF %i GAME PROCESSED", "%i OF %i GAMES PROCESSED", mTotalGames), mGamesProcessed,
(mTotalGames == 1 ? " GAME " : " GAMES ") + "PROCESSED", mTotalGames)};
Font::get(FONT_SIZE_SMALL), mMenuColorSecondary, ALIGN_CENTER);
mGameCounter = std::make_shared<TextComponent>(gameProcessText, Font::get(FONT_SIZE_SMALL),
mMenuColorSecondary, ALIGN_CENTER);
mGrid.setEntry(mGameCounter, glm::ivec2 {0, 2}, false, true, glm::ivec2 {6, 1}); mGrid.setEntry(mGameCounter, glm::ivec2 {0, 2}, false, true, glm::ivec2 {6, 1});
// Spacer row with top border. // Spacer row with top border.
@ -58,7 +63,7 @@ GuiOfflineGenerator::GuiOfflineGenerator(const std::queue<FileData*>& gameQueue)
glm::ivec2 {1, 7}); glm::ivec2 {1, 7});
// Generated label. // Generated label.
mGeneratedLbl = std::make_shared<TextComponent>("Generated:", Font::get(FONT_SIZE_SMALL), mGeneratedLbl = std::make_shared<TextComponent>(_("Generated:"), Font::get(FONT_SIZE_SMALL),
mMenuColorSecondary, ALIGN_LEFT); mMenuColorSecondary, ALIGN_LEFT);
mGrid.setEntry(mGeneratedLbl, glm::ivec2 {1, 4}, false, true, glm::ivec2 {1, 1}); mGrid.setEntry(mGeneratedLbl, glm::ivec2 {1, 4}, false, true, glm::ivec2 {1, 1});
@ -69,7 +74,7 @@ GuiOfflineGenerator::GuiOfflineGenerator(const std::queue<FileData*>& gameQueue)
mGrid.setEntry(mGeneratedVal, glm::ivec2 {2, 4}, false, true, glm::ivec2 {1, 1}); mGrid.setEntry(mGeneratedVal, glm::ivec2 {2, 4}, false, true, glm::ivec2 {1, 1});
// Overwritten label. // Overwritten label.
mOverwrittenLbl = std::make_shared<TextComponent>("Overwritten:", Font::get(FONT_SIZE_SMALL), mOverwrittenLbl = std::make_shared<TextComponent>(_("Overwritten:"), Font::get(FONT_SIZE_SMALL),
mMenuColorSecondary, ALIGN_LEFT); mMenuColorSecondary, ALIGN_LEFT);
mGrid.setEntry(mOverwrittenLbl, glm::ivec2 {1, 5}, false, true, glm::ivec2 {1, 1}); mGrid.setEntry(mOverwrittenLbl, glm::ivec2 {1, 5}, false, true, glm::ivec2 {1, 1});
@ -80,8 +85,8 @@ GuiOfflineGenerator::GuiOfflineGenerator(const std::queue<FileData*>& gameQueue)
mGrid.setEntry(mOverwrittenVal, glm::ivec2 {2, 5}, false, true, glm::ivec2 {1, 1}); mGrid.setEntry(mOverwrittenVal, glm::ivec2 {2, 5}, false, true, glm::ivec2 {1, 1});
// Skipping label. // Skipping label.
const std::string skipLabel {mRenderer->getIsVerticalOrientation() ? "Skipped:" : const std::string skipLabel {mRenderer->getIsVerticalOrientation() ? _("Skipped:") :
"Skipped (existing):"}; _("Skipped (existing):")};
mSkippedLbl = std::make_shared<TextComponent>(skipLabel, Font::get(FONT_SIZE_SMALL), mSkippedLbl = std::make_shared<TextComponent>(skipLabel, Font::get(FONT_SIZE_SMALL),
mMenuColorSecondary, ALIGN_LEFT); mMenuColorSecondary, ALIGN_LEFT);
mGrid.setEntry(mSkippedLbl, glm::ivec2 {1, 6}, false, true, glm::ivec2 {1, 1}); mGrid.setEntry(mSkippedLbl, glm::ivec2 {1, 6}, false, true, glm::ivec2 {1, 1});
@ -92,7 +97,7 @@ GuiOfflineGenerator::GuiOfflineGenerator(const std::queue<FileData*>& gameQueue)
mGrid.setEntry(mSkippedVal, glm::ivec2 {2, 6}, false, true, glm::ivec2 {1, 1}); mGrid.setEntry(mSkippedVal, glm::ivec2 {2, 6}, false, true, glm::ivec2 {1, 1});
// Failed label. // Failed label.
mFailedLbl = std::make_shared<TextComponent>("Failed:", Font::get(FONT_SIZE_SMALL), mFailedLbl = std::make_shared<TextComponent>(_("Failed:"), Font::get(FONT_SIZE_SMALL),
mMenuColorSecondary, ALIGN_LEFT); mMenuColorSecondary, ALIGN_LEFT);
mGrid.setEntry(mFailedLbl, glm::ivec2 {1, 7}, false, true, glm::ivec2 {1, 1}); mGrid.setEntry(mFailedLbl, glm::ivec2 {1, 7}, false, true, glm::ivec2 {1, 1});
@ -102,13 +107,14 @@ GuiOfflineGenerator::GuiOfflineGenerator(const std::queue<FileData*>& gameQueue)
mGrid.setEntry(mFailedVal, glm::ivec2 {2, 7}, false, true, glm::ivec2 {1, 1}); mGrid.setEntry(mFailedVal, glm::ivec2 {2, 7}, false, true, glm::ivec2 {1, 1});
// Processing label. // Processing label.
mProcessingLbl = std::make_shared<TextComponent>("Processing: ", Font::get(FONT_SIZE_SMALL), mProcessingLbl = std::make_shared<TextComponent>(_("Processing:"), Font::get(FONT_SIZE_SMALL),
mMenuColorSecondary, ALIGN_LEFT); mMenuColorSecondary, ALIGN_LEFT);
mGrid.setEntry(mProcessingLbl, glm::ivec2 {3, 4}, false, true, glm::ivec2 {1, 1}); mGrid.setEntry(mProcessingLbl, glm::ivec2 {3, 4}, false, true, glm::ivec2 {1, 1});
// Processing value. // Processing value.
mProcessingVal = std::make_shared<TextComponent>("", Font::get(FONT_SIZE_SMALL), mProcessingVal = std::make_shared<TextComponent>("", Font::get(FONT_SIZE_SMALL),
mMenuColorSecondary, ALIGN_LEFT); mMenuColorSecondary, ALIGN_LEFT);
mProcessingVal->setRemoveLineBreaks(true);
mGrid.setEntry(mProcessingVal, glm::ivec2 {4, 4}, false, true, glm::ivec2 {1, 1}); mGrid.setEntry(mProcessingVal, glm::ivec2 {4, 4}, false, true, glm::ivec2 {1, 1});
// Spacer row. // Spacer row.
@ -117,12 +123,13 @@ GuiOfflineGenerator::GuiOfflineGenerator(const std::queue<FileData*>& gameQueue)
// Last error message label. // Last error message label.
mLastErrorLbl = std::make_shared<TextComponent>( mLastErrorLbl = std::make_shared<TextComponent>(
"Last error message:", Font::get(FONT_SIZE_SMALL), mMenuColorSecondary, ALIGN_LEFT); _("Last error message:"), Font::get(FONT_SIZE_SMALL), mMenuColorSecondary, ALIGN_LEFT);
mGrid.setEntry(mLastErrorLbl, glm::ivec2 {1, 9}, false, true, glm::ivec2 {4, 1}); mGrid.setEntry(mLastErrorLbl, glm::ivec2 {1, 9}, false, true, glm::ivec2 {4, 1});
// Last error message value. // Last error message value.
mLastErrorVal = std::make_shared<TextComponent>("", Font::get(FONT_SIZE_SMALL), mLastErrorVal = std::make_shared<TextComponent>("", Font::get(FONT_SIZE_SMALL),
mMenuColorSecondary, ALIGN_LEFT); mMenuColorSecondary, ALIGN_LEFT);
mLastErrorVal->setRemoveLineBreaks(true);
mGrid.setEntry(mLastErrorVal, glm::ivec2 {1, 10}, false, true, glm::ivec2 {4, 1}); mGrid.setEntry(mLastErrorVal, glm::ivec2 {1, 10}, false, true, glm::ivec2 {4, 1});
// Right spacer. // Right spacer.
@ -136,32 +143,33 @@ GuiOfflineGenerator::GuiOfflineGenerator(const std::queue<FileData*>& gameQueue)
// Buttons. // Buttons.
std::vector<std::shared_ptr<ButtonComponent>> buttons; std::vector<std::shared_ptr<ButtonComponent>> buttons;
mStartPauseButton = std::make_shared<ButtonComponent>("START", "start processing", [this]() { mStartPauseButton =
if (!mProcessing) { std::make_shared<ButtonComponent>(_("START"), _("start processing"), [this]() {
mProcessing = true; if (!mProcessing) {
mPaused = false; mProcessing = true;
mStartPauseButton->setText("PAUSE", "pause processing"); mPaused = false;
mCloseButton->setText("CLOSE", "close (abort processing)"); mStartPauseButton->setText(_("PAUSE"), _("pause processing"));
mStatus->setText("RUNNING..."); mCloseButton->setText(_("CLOSE"), _("close (abort processing)"));
if (mGamesProcessed == 0) { mStatus->setText(_("RUNNING..."));
LOG(LogInfo) << "GuiOfflineGenerator: Processing " << mTotalGames << " games"; if (mGamesProcessed == 0) {
LOG(LogInfo) << "GuiOfflineGenerator: Processing " << mTotalGames << " games";
}
} }
} else {
else { if (mMiximageGeneratorThread.joinable())
if (mMiximageGeneratorThread.joinable()) mMiximageGeneratorThread.join();
mMiximageGeneratorThread.join(); mPaused = true;
mPaused = true; update(1);
update(1); mProcessing = false;
mProcessing = false; this->mStartPauseButton->setText(_("START"), _("start processing"));
this->mStartPauseButton->setText("START", "start processing"); this->mCloseButton->setText(_("CLOSE"), _("close (abort processing)"));
this->mCloseButton->setText("CLOSE", "close (abort processing)"); mStatus->setText(_("PAUSED"));
mStatus->setText("PAUSED"); }
} });
});
buttons.push_back(mStartPauseButton); buttons.push_back(mStartPauseButton);
mCloseButton = std::make_shared<ButtonComponent>("CLOSE", "close", [this]() { mCloseButton = std::make_shared<ButtonComponent>(_("CLOSE"), _("close"), [this]() {
if (mGamesProcessed != 0 && mGamesProcessed != mTotalGames) { if (mGamesProcessed != 0 && mGamesProcessed != mTotalGames) {
LOG(LogInfo) << "GuiOfflineGenerator: Aborted after processing " << mGamesProcessed LOG(LogInfo) << "GuiOfflineGenerator: Aborted after processing " << mGamesProcessed
<< (mGamesProcessed == 1 ? " game (" : " games (") << mImagesGenerated << (mGamesProcessed == 1 ? " game (" : " games (") << mImagesGenerated
@ -318,19 +326,20 @@ void GuiOfflineGenerator::update(int deltaTime)
} }
// Update the statistics. // Update the statistics.
mStatus->setText("RUNNING"); mStatus->setText(_("RUNNING"));
mGameCounter->setText(std::to_string(mGamesProcessed) + " OF " + std::to_string(mTotalGames) + mGameCounter->setText(Utils::String::format(
(mTotalGames == 1 ? " GAME " : " GAMES ") + "PROCESSED"); _n("%i OF %i GAME PROCESSED", "%i OF %i GAMES PROCESSED", mTotalGames), mGamesProcessed,
mTotalGames));
mGeneratedVal->setText(std::to_string(mImagesGenerated)); mGeneratedVal->setText(std::to_string(mImagesGenerated));
mFailedVal->setText(std::to_string(mGamesFailed)); mFailedVal->setText(std::to_string(mGamesFailed));
mOverwrittenVal->setText(std::to_string(mImagesOverwritten)); mOverwrittenVal->setText(std::to_string(mImagesOverwritten));
if (mGamesProcessed == mTotalGames) { if (mGamesProcessed == mTotalGames) {
mStatus->setText("COMPLETED"); mStatus->setText(_("COMPLETED"));
mStartPauseButton->setText("DONE", "done (close)"); mStartPauseButton->setText(_("DONE"), _("done (close)"));
mStartPauseButton->setPressedFunc([this]() { delete this; }); mStartPauseButton->setPressedFunc([this]() { delete this; });
mCloseButton->setText("CLOSE", "close"); mCloseButton->setText(_("CLOSE"), _("close"));
mProcessingVal->setText(""); mProcessingVal->setText("");
LOG(LogInfo) << "GuiOfflineGenerator: Completed processing (" << mImagesGenerated LOG(LogInfo) << "GuiOfflineGenerator: Completed processing (" << mImagesGenerated
<< (mImagesGenerated == 1 ? " image " : " images ") << "generated, " << (mImagesGenerated == 1 ? " image " : " images ") << "generated, "

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// //
// ES-DE // ES-DE Frontend
// GuiOfflineGenerator.h // GuiOfflineGenerator.h
// //
// User interface for the miximage offline generator. // User interface for the miximage offline generator.

View file

@ -10,6 +10,7 @@
#include "CollectionSystemsManager.h" #include "CollectionSystemsManager.h"
#include "utils/FileSystemUtil.h" #include "utils/FileSystemUtil.h"
#include "utils/LocalizationUtil.h"
#include "utils/PlatformUtil.h" #include "utils/PlatformUtil.h"
#include "views/ViewController.h" #include "views/ViewController.h"
@ -62,22 +63,22 @@ GuiOrphanedDataCleanup::GuiOrphanedDataCleanup(std::function<void()> reloadCallb
#endif #endif
mMediaDescription = mMediaDescription =
"THIS WILL REMOVE ALL MEDIA FILES WHERE NO MATCHING GAME FILES CAN BE FOUND. " _("THIS WILL REMOVE ALL MEDIA FILES WHERE NO MATCHING GAME FILES CAN BE FOUND. "
"THESE FILES WILL BE MOVED TO A CLEANUP FOLDER INSIDE YOUR GAME MEDIA " "THESE FILES WILL BE MOVED TO A CLEANUP FOLDER INSIDE YOUR GAME MEDIA "
"DIRECTORY. YOU CAN MANUALLY DELETE THIS FOLDER WHEN YOU ARE SURE IT'S NO " "DIRECTORY. YOU CAN MANUALLY DELETE THIS FOLDER WHEN YOU ARE SURE IT'S NO "
"LONGER NEEDED."; "LONGER NEEDED.");
mGamelistDescription = mGamelistDescription = _(
"THIS WILL REMOVE ALL ENTRIES FROM YOUR GAMELIST XML FILES WHERE NO MATCHING " "THIS WILL REMOVE ALL ENTRIES FROM YOUR GAMELIST XML FILES WHERE NO MATCHING "
"GAME FILES CAN BE FOUND. BACKUPS OF THE ORIGINAL FILES WILL BE SAVED TO A CLEANUP FOLDER " "GAME FILES CAN BE FOUND. BACKUPS OF THE ORIGINAL FILES WILL BE SAVED TO A CLEANUP FOLDER "
"INSIDE YOUR GAMELISTS DIRECTORY. YOU CAN MANUALLY DELETE THIS FOLDER WHEN YOU ARE SURE " "INSIDE YOUR GAMELISTS DIRECTORY. YOU CAN MANUALLY DELETE THIS FOLDER WHEN YOU ARE SURE "
"IT'S NO LONGER NEEDED."; "IT'S NO LONGER NEEDED.");
mCollectionsDescription = mCollectionsDescription = _(
"THIS WILL REMOVE ALL ENTRIES FROM YOUR CUSTOM COLLECTIONS CONFIGURATION FILES WHERE NO " "THIS WILL REMOVE ALL ENTRIES FROM YOUR CUSTOM COLLECTIONS CONFIGURATION FILES WHERE NO "
"MATCHING GAME FILES CAN BE FOUND. BACKUPS OF THE ORIGINAL FILES WILL BE SAVED TO A " "MATCHING GAME FILES CAN BE FOUND. BACKUPS OF THE ORIGINAL FILES WILL BE SAVED TO A "
"CLEANUP FOLDER INSIDE YOUR COLLECTIONS DIRECTORY. ONLY CURRENTLY ENABLED COLLECTIONS WILL " "CLEANUP FOLDER INSIDE YOUR COLLECTIONS DIRECTORY. ONLY CURRENTLY ENABLED COLLECTIONS WILL "
"BE PROCESSED."; "BE PROCESSED.");
// Stop any ongoing custom collections editing. // Stop any ongoing custom collections editing.
if (CollectionSystemsManager::getInstance()->isEditing()) if (CollectionSystemsManager::getInstance()->isEditing())
@ -89,12 +90,14 @@ GuiOrphanedDataCleanup::GuiOrphanedDataCleanup(std::function<void()> reloadCallb
} }
// Set up grid. // Set up grid.
mTitle = std::make_shared<TextComponent>("ORPHANED DATA CLEANUP", Font::get(FONT_SIZE_LARGE), mTitle = std::make_shared<TextComponent>(
mMenuColorTitle, ALIGN_CENTER); _("ORPHANED DATA CLEANUP"),
Font::get(FONT_SIZE_LARGE * Utils::Localization::sMenuTitleScaleFactor), mMenuColorTitle,
ALIGN_CENTER);
mGrid.setEntry(mTitle, glm::ivec2 {0, 0}, false, true, glm::ivec2 {4, 1}, mGrid.setEntry(mTitle, glm::ivec2 {0, 0}, false, true, glm::ivec2 {4, 1},
GridFlags::BORDER_NONE); GridFlags::BORDER_NONE);
mStatus = std::make_shared<TextComponent>("NOT STARTED", Font::get(FONT_SIZE_MEDIUM), mStatus = std::make_shared<TextComponent>(_("NOT STARTED"), Font::get(FONT_SIZE_MEDIUM),
mMenuColorPrimary, ALIGN_CENTER); mMenuColorPrimary, ALIGN_CENTER);
mGrid.setEntry(mStatus, glm::ivec2 {0, 1}, false, true, glm::ivec2 {4, 1}, mGrid.setEntry(mStatus, glm::ivec2 {0, 1}, false, true, glm::ivec2 {4, 1},
GridFlags::BORDER_NONE); GridFlags::BORDER_NONE);
@ -103,18 +106,20 @@ GuiOrphanedDataCleanup::GuiOrphanedDataCleanup(std::function<void()> reloadCallb
mGrid.setEntry(std::make_shared<GuiComponent>(), glm::ivec2 {0, 2}, false, false, mGrid.setEntry(std::make_shared<GuiComponent>(), glm::ivec2 {0, 2}, false, false,
glm::ivec2 {4, 1}, GridFlags::BORDER_BOTTOM); glm::ivec2 {4, 1}, GridFlags::BORDER_BOTTOM);
mDescriptionHeader = std::make_shared<TextComponent>("DESCRIPTION:", Font::get(FONT_SIZE_MINI), mDescriptionHeader = std::make_shared<TextComponent>(
mMenuColorPrimary, ALIGN_LEFT); _("DESCRIPTION:"), Font::get(FONT_SIZE_MINI), mMenuColorPrimary, ALIGN_LEFT);
mGrid.setEntry(mDescriptionHeader, glm::ivec2 {1, 3}, false, true, glm::ivec2 {2, 1}); mGrid.setEntry(mDescriptionHeader, glm::ivec2 {1, 3}, false, true, glm::ivec2 {2, 1});
mDescription = std::make_shared<TextComponent>( mDescription = std::make_shared<TextComponent>(
mMediaDescription, mMediaDescription,
Font::get(mRenderer->getScreenAspectRatio() < 1.6f ? FONT_SIZE_SMALL : FONT_SIZE_MEDIUM), Font::get(mRenderer->getScreenAspectRatio() < 1.6f ? FONT_SIZE_SMALL : FONT_SIZE_MEDIUM),
mMenuColorPrimary, ALIGN_LEFT, ALIGN_TOP); mMenuColorPrimary, ALIGN_LEFT, ALIGN_TOP);
mGrid.setEntry(mDescription, glm::ivec2 {1, 4}, false, true, glm::ivec2 {2, 1}); mDescription->setNoSizeUpdate(true);
mGrid.setEntry(mDescription, glm::ivec2 {1, 4}, false, true, glm::ivec2 {2, 1},
GridFlags::BORDER_NONE, GridFlags::UPDATE_ALWAYS, glm::ivec2 {0, 1});
mEntryCountHeader = std::make_shared<TextComponent>( mEntryCountHeader = std::make_shared<TextComponent>(
"TOTAL ENTRIES REMOVED:", Font::get(FONT_SIZE_SMALL), mMenuColorPrimary, ALIGN_LEFT); _("TOTAL ENTRIES REMOVED:"), Font::get(FONT_SIZE_SMALL), mMenuColorPrimary, ALIGN_LEFT);
mGrid.setEntry(mEntryCountHeader, glm::ivec2 {1, 6}, false, true, glm::ivec2 {1, 1}); mGrid.setEntry(mEntryCountHeader, glm::ivec2 {1, 6}, false, true, glm::ivec2 {1, 1});
mEntryCount = std::make_shared<TextComponent>("0", Font::get(FONT_SIZE_SMALL), mEntryCount = std::make_shared<TextComponent>("0", Font::get(FONT_SIZE_SMALL),
@ -122,7 +127,7 @@ GuiOrphanedDataCleanup::GuiOrphanedDataCleanup(std::function<void()> reloadCallb
mGrid.setEntry(mEntryCount, glm::ivec2 {2, 6}, false, true, glm::ivec2 {1, 1}); mGrid.setEntry(mEntryCount, glm::ivec2 {2, 6}, false, true, glm::ivec2 {1, 1});
mSystemProcessingHeader = std::make_shared<TextComponent>( mSystemProcessingHeader = std::make_shared<TextComponent>(
"LAST PROCESSED SYSTEM:", Font::get(FONT_SIZE_SMALL), mMenuColorPrimary, ALIGN_LEFT); _("LAST PROCESSED SYSTEM:"), Font::get(FONT_SIZE_SMALL), mMenuColorPrimary, ALIGN_LEFT);
mGrid.setEntry(mSystemProcessingHeader, glm::ivec2 {1, 7}, false, true, glm::ivec2 {1, 1}); mGrid.setEntry(mSystemProcessingHeader, glm::ivec2 {1, 7}, false, true, glm::ivec2 {1, 1});
mSystemProcessing = std::make_shared<TextComponent>("", Font::get(FONT_SIZE_SMALL), mSystemProcessing = std::make_shared<TextComponent>("", Font::get(FONT_SIZE_SMALL),
@ -130,7 +135,7 @@ GuiOrphanedDataCleanup::GuiOrphanedDataCleanup(std::function<void()> reloadCallb
mGrid.setEntry(mSystemProcessing, glm::ivec2 {2, 7}, false, true, glm::ivec2 {1, 1}); mGrid.setEntry(mSystemProcessing, glm::ivec2 {2, 7}, false, true, glm::ivec2 {1, 1});
mErrorHeader = std::make_shared<TextComponent>( mErrorHeader = std::make_shared<TextComponent>(
"LAST ERROR MESSAGE:", Font::get(FONT_SIZE_SMALL), mMenuColorPrimary, ALIGN_LEFT); _("LAST ERROR MESSAGE:"), Font::get(FONT_SIZE_SMALL), mMenuColorPrimary, ALIGN_LEFT);
mGrid.setEntry(mErrorHeader, glm::ivec2 {1, 8}, false, true, glm::ivec2 {1, 1}); mGrid.setEntry(mErrorHeader, glm::ivec2 {1, 8}, false, true, glm::ivec2 {1, 1});
mError = mError =
@ -144,7 +149,7 @@ GuiOrphanedDataCleanup::GuiOrphanedDataCleanup(std::function<void()> reloadCallb
// Buttons. // Buttons.
std::vector<std::shared_ptr<ButtonComponent>> buttons; std::vector<std::shared_ptr<ButtonComponent>> buttons;
mButton1 = std::make_shared<ButtonComponent>("MEDIA", "start processing", [this]() { mButton1 = std::make_shared<ButtonComponent>(_("MEDIA"), _("start processing"), [this]() {
if (mIsProcessing && mStopProcessing) if (mIsProcessing && mStopProcessing)
return; return;
if (mIsProcessing) { if (mIsProcessing) {
@ -165,14 +170,14 @@ GuiOrphanedDataCleanup::GuiOrphanedDataCleanup(std::function<void()> reloadCallb
mErrorMessage = ""; mErrorMessage = "";
mError->setValue(""); mError->setValue("");
mEntryCount->setValue("0"); mEntryCount->setValue("0");
mStatus->setValue("RUNNING MEDIA CLEANUP"); mStatus->setValue(_("RUNNING MEDIA CLEANUP"));
mButton1->setText("STOP", "stop processing", true, false); mButton1->setText(_("STOP"), _("stop processing"), true, false);
mThread = std::make_unique<std::thread>(&GuiOrphanedDataCleanup::cleanupMediaFiles, this); mThread = std::make_unique<std::thread>(&GuiOrphanedDataCleanup::cleanupMediaFiles, this);
}); });
buttons.push_back(mButton1); buttons.push_back(mButton1);
mButton2 = std::make_shared<ButtonComponent>("GAMELISTS", "start processing", [this]() { mButton2 = std::make_shared<ButtonComponent>(_("GAMELISTS"), _("start processing"), [this]() {
if (mIsProcessing && mStopProcessing) if (mIsProcessing && mStopProcessing)
return; return;
if (mIsProcessing) { if (mIsProcessing) {
@ -193,8 +198,8 @@ GuiOrphanedDataCleanup::GuiOrphanedDataCleanup(std::function<void()> reloadCallb
mErrorMessage = ""; mErrorMessage = "";
mError->setValue(""); mError->setValue("");
mEntryCount->setValue("0"); mEntryCount->setValue("0");
mStatus->setValue("RUNNING GAMELISTS CLEANUP"); mStatus->setValue(_("RUNNING GAMELISTS CLEANUP"));
mButton2->setText("STOP", "stop processing", true, false); mButton2->setText(_("STOP"), _("stop processing"), true, false);
// Write any gamelist.xml changes before proceeding with the cleanup. // Write any gamelist.xml changes before proceeding with the cleanup.
if (Settings::getInstance()->getString("SaveGamelistsMode") == "on exit") { if (Settings::getInstance()->getString("SaveGamelistsMode") == "on exit") {
for (auto system : SystemData::sSystemVector) for (auto system : SystemData::sSystemVector)
@ -204,7 +209,7 @@ GuiOrphanedDataCleanup::GuiOrphanedDataCleanup(std::function<void()> reloadCallb
}); });
buttons.push_back(mButton2); buttons.push_back(mButton2);
mButton3 = std::make_shared<ButtonComponent>("COLLECTIONS", "start processing", [this]() { mButton3 = std::make_shared<ButtonComponent>(_("COLLECTIONS"), _("start processing"), [this]() {
if (mIsProcessing && mStopProcessing) if (mIsProcessing && mStopProcessing)
return; return;
if (mIsProcessing) { if (mIsProcessing) {
@ -212,8 +217,8 @@ GuiOrphanedDataCleanup::GuiOrphanedDataCleanup(std::function<void()> reloadCallb
return; return;
} }
if (!mHasCustomCollections) { if (!mHasCustomCollections) {
mStatus->setValue("COLLECTIONS CLEANUP FAILED"); mStatus->setValue(_("COLLECTIONS CLEANUP FAILED"));
mError->setValue("There are no enabled custom collections"); mError->setValue(_("There are no enabled custom collections"));
mEntryCount->setValue("0"); mEntryCount->setValue("0");
mSystemProcessing->setValue(""); mSystemProcessing->setValue("");
return; return;
@ -232,13 +237,13 @@ GuiOrphanedDataCleanup::GuiOrphanedDataCleanup(std::function<void()> reloadCallb
mErrorMessage = ""; mErrorMessage = "";
mError->setValue(""); mError->setValue("");
mEntryCount->setValue("0"); mEntryCount->setValue("0");
mStatus->setValue("RUNNING COLLECTIONS CLEANUP"); mStatus->setValue(_("RUNNING COLLECTIONS CLEANUP"));
mButton3->setText("STOP", "stop processing", true, false); mButton3->setText(_("STOP"), _("stop processing"), true, false);
mThread = std::make_unique<std::thread>(&GuiOrphanedDataCleanup::cleanupCollections, this); mThread = std::make_unique<std::thread>(&GuiOrphanedDataCleanup::cleanupCollections, this);
}); });
buttons.push_back(mButton3); buttons.push_back(mButton3);
mButton4 = std::make_shared<ButtonComponent>("CLOSE", "close", [this]() { mButton4 = std::make_shared<ButtonComponent>(_("CLOSE"), _("close"), [this]() {
if (mIsProcessing) { if (mIsProcessing) {
mStopProcessing = true; mStopProcessing = true;
if (mThread) { if (mThread) {
@ -285,7 +290,7 @@ GuiOrphanedDataCleanup::GuiOrphanedDataCleanup(std::function<void()> reloadCallb
std::round(mRenderer->getScreenHeight() * 0.1f)); std::round(mRenderer->getScreenHeight() * 0.1f));
mBusyAnim.setSize(mSize); mBusyAnim.setSize(mSize);
mBusyAnim.setText("PROCESSING"); mBusyAnim.setText(_("PROCESSING"));
mBusyAnim.onSizeChanged(); mBusyAnim.onSizeChanged();
} }
@ -328,7 +333,8 @@ void GuiOrphanedDataCleanup::cleanupMediaFiles()
<< "\""; << "\"";
{ {
std::unique_lock<std::mutex> lock {mMutex}; std::unique_lock<std::mutex> lock {mMutex};
mErrorMessage = "A flatten.txt file was found, skipping \"" + currentSystem + "\""; mErrorMessage = Utils::String::format(
_("A flatten.txt file was found, skipping \"%s\""), currentSystem.c_str());
} }
continue; continue;
} }
@ -421,7 +427,7 @@ void GuiOrphanedDataCleanup::cleanupMediaFiles()
LOG(LogError) << "Couldn't create target directory \"" << fileDirectory << "\""; LOG(LogError) << "Couldn't create target directory \"" << fileDirectory << "\"";
{ {
std::unique_lock<std::mutex> lock {mMutex}; std::unique_lock<std::mutex> lock {mMutex};
mErrorMessage = "Couldn't create target directory, permission problems?"; mErrorMessage = _("Couldn't create target directory, permission problems?");
} }
mFailed = true; mFailed = true;
mIsProcessing = false; mIsProcessing = false;
@ -431,7 +437,7 @@ void GuiOrphanedDataCleanup::cleanupMediaFiles()
LOG(LogError) << "Couldn't move file \"" << file << "\""; LOG(LogError) << "Couldn't move file \"" << file << "\"";
{ {
std::unique_lock<std::mutex> lock {mMutex}; std::unique_lock<std::mutex> lock {mMutex};
mErrorMessage = "Couldn't move media file, permission problems?"; mErrorMessage = _("Couldn't move media file, permission problems?");
} }
mFailed = true; mFailed = true;
mIsProcessing = false; mIsProcessing = false;
@ -521,7 +527,8 @@ void GuiOrphanedDataCleanup::cleanupGamelists()
<< "\""; << "\"";
{ {
std::unique_lock<std::mutex> lock {mMutex}; std::unique_lock<std::mutex> lock {mMutex};
mErrorMessage = "A flatten.txt file was found, skipping \"" + currentSystem + "\""; mErrorMessage = Utils::String::format(
_("A flatten.txt file was found, skipping \"%s\""), currentSystem.c_str());
} }
continue; continue;
} }
@ -548,8 +555,8 @@ void GuiOrphanedDataCleanup::cleanupGamelists()
LOG(LogError) << "Couldn't parse file \"" << gamelistFile << "\""; LOG(LogError) << "Couldn't parse file \"" << gamelistFile << "\"";
{ {
std::unique_lock<std::mutex> lock {mMutex}; std::unique_lock<std::mutex> lock {mMutex};
mErrorMessage = mErrorMessage = Utils::String::format(
"Couldn't parse gamelist.xml file for \"" + system->getName() + "\""; _("Couldn't parse gamelist.xml file for \"%s\""), system->getName().c_str());
} }
SDL_Delay(500); SDL_Delay(500);
continue; continue;
@ -574,7 +581,8 @@ void GuiOrphanedDataCleanup::cleanupGamelists()
{ {
std::unique_lock<std::mutex> lock {mMutex}; std::unique_lock<std::mutex> lock {mMutex};
mErrorMessage = mErrorMessage =
"Couldn't find a gamelist tag in file for system \"" + system->getName() + "\""; Utils::String::format(_("Couldn't find a gamelist tag in file for \"%s\""),
system->getName().c_str());
} }
SDL_Delay(500); SDL_Delay(500);
continue; continue;
@ -589,7 +597,8 @@ void GuiOrphanedDataCleanup::cleanupGamelists()
LOG(LogError) << "Couldn't remove temporary file \"" << tempFile << "\""; LOG(LogError) << "Couldn't remove temporary file \"" << tempFile << "\"";
{ {
std::unique_lock<std::mutex> lock {mMutex}; std::unique_lock<std::mutex> lock {mMutex};
mErrorMessage = "Couldn't delete temporary gamelist file, permission problems?"; mErrorMessage =
_("Couldn't delete temporary gamelist file, permission problems?");
} }
mFailed = true; mFailed = true;
mIsProcessing = false; mIsProcessing = false;
@ -621,7 +630,8 @@ void GuiOrphanedDataCleanup::cleanupGamelists()
LOG(LogError) << "Couldn't write to temporary file \"" << tempFile << "\""; LOG(LogError) << "Couldn't write to temporary file \"" << tempFile << "\"";
{ {
std::unique_lock<std::mutex> lock {mMutex}; std::unique_lock<std::mutex> lock {mMutex};
mErrorMessage = "Couldn't write to temporary gamelist file, permission problems?"; mErrorMessage =
_("Couldn't write to temporary gamelist file, permission problems?");
} }
// If we couldn't write to the file this will probably fail as well. // If we couldn't write to the file this will probably fail as well.
Utils::FileSystem::removeFile(tempFile); Utils::FileSystem::removeFile(tempFile);
@ -697,7 +707,8 @@ void GuiOrphanedDataCleanup::cleanupGamelists()
LOG(LogError) << "Couldn't write to temporary file \"" << tempFile << "\""; LOG(LogError) << "Couldn't write to temporary file \"" << tempFile << "\"";
{ {
std::unique_lock<std::mutex> lock {mMutex}; std::unique_lock<std::mutex> lock {mMutex};
mErrorMessage = "Couldn't write to temporary gamelist file, permission problems?"; mErrorMessage =
_("Couldn't write to temporary gamelist file, permission problems?");
} }
Utils::FileSystem::removeFile(tempFile); Utils::FileSystem::removeFile(tempFile);
mFailed = true; mFailed = true;
@ -725,7 +736,7 @@ void GuiOrphanedDataCleanup::cleanupGamelists()
LOG(LogError) << "Couldn't create backup directory \"" << targetDirectory << "\""; LOG(LogError) << "Couldn't create backup directory \"" << targetDirectory << "\"";
{ {
std::unique_lock<std::mutex> lock {mMutex}; std::unique_lock<std::mutex> lock {mMutex};
mErrorMessage = "Couldn't create backup directory, permission problems?"; mErrorMessage = _("Couldn't create backup directory, permission problems?");
} }
mFailed = true; mFailed = true;
} }
@ -742,7 +753,7 @@ void GuiOrphanedDataCleanup::cleanupGamelists()
LOG(LogError) << "Couldn't move file \"" << gamelistFile << "\""; LOG(LogError) << "Couldn't move file \"" << gamelistFile << "\"";
{ {
std::unique_lock<std::mutex> lock {mMutex}; std::unique_lock<std::mutex> lock {mMutex};
mErrorMessage = "Couldn't move old gamelist file, permission problems?"; mErrorMessage = _("Couldn't move old gamelist file, permission problems?");
} }
mFailed = true; mFailed = true;
} }
@ -751,7 +762,7 @@ void GuiOrphanedDataCleanup::cleanupGamelists()
{ {
std::unique_lock<std::mutex> lock {mMutex}; std::unique_lock<std::mutex> lock {mMutex};
mErrorMessage = mErrorMessage =
"Couldn't move temporary gamelist file, permission problems?"; _("Couldn't move temporary gamelist file, permission problems?");
} }
mFailed = true; mFailed = true;
// Attempt to move back the old gamelist.xml file. // Attempt to move back the old gamelist.xml file.
@ -773,7 +784,7 @@ void GuiOrphanedDataCleanup::cleanupGamelists()
LOG(LogError) << "Couldn't remove temporary file \"" << tempFile << "\""; LOG(LogError) << "Couldn't remove temporary file \"" << tempFile << "\"";
{ {
std::unique_lock<std::mutex> lock {mMutex}; std::unique_lock<std::mutex> lock {mMutex};
mErrorMessage = "Couldn't delete temporary gamelist file, permission problems?"; mErrorMessage = _("Couldn't delete temporary gamelist file, permission problems?");
} }
mFailed = true; mFailed = true;
} }
@ -832,7 +843,7 @@ void GuiOrphanedDataCleanup::cleanupCollections()
<< collectionFile << "\""; << collectionFile << "\"";
{ {
std::unique_lock<std::mutex> lock {mMutex}; std::unique_lock<std::mutex> lock {mMutex};
mErrorMessage = "Couldn't find custom collection configuration file"; mErrorMessage = _("Couldn't find custom collection configuration file");
} }
mFailed = true; mFailed = true;
mIsProcessing = false; mIsProcessing = false;
@ -860,7 +871,7 @@ void GuiOrphanedDataCleanup::cleanupCollections()
<< collectionFile << "\""; << collectionFile << "\"";
{ {
std::unique_lock<std::mutex> lock {mMutex}; std::unique_lock<std::mutex> lock {mMutex};
mErrorMessage = "Couldn't open custom collection configuration file"; mErrorMessage = _("Couldn't open custom collection configuration file");
} }
mFailed = true; mFailed = true;
mIsProcessing = false; mIsProcessing = false;
@ -897,7 +908,7 @@ void GuiOrphanedDataCleanup::cleanupCollections()
{ {
std::unique_lock<std::mutex> lock {mMutex}; std::unique_lock<std::mutex> lock {mMutex};
mErrorMessage = mErrorMessage =
"Couldn't delete temporary collection file, permission problems?"; _("Couldn't delete temporary collection file, permission problems?");
} }
mFailed = true; mFailed = true;
mIsProcessing = false; mIsProcessing = false;
@ -922,7 +933,7 @@ void GuiOrphanedDataCleanup::cleanupCollections()
LOG(LogError) << "Couldn't create backup directory \"" << targetDirectory << "\""; LOG(LogError) << "Couldn't create backup directory \"" << targetDirectory << "\"";
{ {
std::unique_lock<std::mutex> lock {mMutex}; std::unique_lock<std::mutex> lock {mMutex};
mErrorMessage = "Couldn't create backup directory, permission problems?"; mErrorMessage = _("Couldn't create backup directory, permission problems?");
} }
mFailed = true; mFailed = true;
mIsProcessing = false; mIsProcessing = false;
@ -941,7 +952,8 @@ void GuiOrphanedDataCleanup::cleanupCollections()
<< tempFile << "\""; << tempFile << "\"";
{ {
std::unique_lock<std::mutex> lock {mMutex}; std::unique_lock<std::mutex> lock {mMutex};
mErrorMessage = "Couldn't write to temporary collection configuration file"; mErrorMessage =
_("Couldn't write to temporary collection configuration file");
} }
mFailed = true; mFailed = true;
mIsProcessing = false; mIsProcessing = false;
@ -970,7 +982,8 @@ void GuiOrphanedDataCleanup::cleanupCollections()
<< "\" to backup directory"; << "\" to backup directory";
{ {
std::unique_lock<std::mutex> lock {mMutex}; std::unique_lock<std::mutex> lock {mMutex};
mErrorMessage = "Couldn't move old collection file, permission problems?"; mErrorMessage =
_("Couldn't move old collection file, permission problems?");
} }
// Attempt to move back the old collection file. // Attempt to move back the old collection file.
Utils::FileSystem::renameFile( Utils::FileSystem::renameFile(
@ -983,7 +996,7 @@ void GuiOrphanedDataCleanup::cleanupCollections()
{ {
std::unique_lock<std::mutex> lock {mMutex}; std::unique_lock<std::mutex> lock {mMutex};
mErrorMessage = mErrorMessage =
"Couldn't move temporary collection file, permission problems?"; _("Couldn't move temporary collection file, permission problems?");
} }
// Attempt to move back the old collection file. // Attempt to move back the old collection file.
Utils::FileSystem::renameFile( Utils::FileSystem::renameFile(
@ -1006,7 +1019,8 @@ void GuiOrphanedDataCleanup::cleanupCollections()
LOG(LogError) << "Couldn't remove temporary file \"" << tempFile << "\""; LOG(LogError) << "Couldn't remove temporary file \"" << tempFile << "\"";
{ {
std::unique_lock<std::mutex> lock {mMutex}; std::unique_lock<std::mutex> lock {mMutex};
mErrorMessage = "Couldn't delete temporary collection file, permission problems?"; mErrorMessage =
_("Couldn't delete temporary collection file, permission problems?");
} }
mFailed = true; mFailed = true;
} }
@ -1038,20 +1052,28 @@ void GuiOrphanedDataCleanup::update(int deltaTime)
mError->setValue(mErrorMessage); mError->setValue(mErrorMessage);
} }
else if (mCompleted) { else if (mCompleted) {
std::string message {mStopProcessing ? "ABORTED" : "COMPLETED"}; std::string message;
if (mCleanupType == CleanupType::MEDIA) { if (mCleanupType == CleanupType::MEDIA) {
mButton1->setText("MEDIA", "start processing"); mButton1->setText(_("MEDIA"), _("start processing"));
message.append(" MEDIA "); if (mStopProcessing)
message = _("ABORTED MEDIA CLEANUP");
else
message = _("COMPLETED MEDIA CLEANUP");
} }
else if (mCleanupType == CleanupType::GAMELISTS) { else if (mCleanupType == CleanupType::GAMELISTS) {
mButton2->setText("GAMELISTS", "start processing"); mButton2->setText(_("GAMELISTS"), _("start processing"));
message.append(" GAMELISTS "); if (mStopProcessing)
message = _("ABORTED GAMELIST CLEANUP");
else
message = _("COMPLETED GAMELIST CLEANUP");
} }
else { else {
mButton3->setText("COLLECTIONS", "start processing"); mButton3->setText(_("COLLECTIONS"), _("start processing"));
message.append(" COLLECTIONS "); if (mStopProcessing)
message = _("ABORTED COLLECTIONS CLEANUP");
else
message = _("COMPLETED COLLECTIONS CLEANUP");
} }
message.append("CLEANUP");
mStatus->setValue(message); mStatus->setValue(message);
if (mError->getValue() != mErrorMessage) if (mError->getValue() != mErrorMessage)
mError->setValue(mErrorMessage); mError->setValue(mErrorMessage);
@ -1060,16 +1082,16 @@ void GuiOrphanedDataCleanup::update(int deltaTime)
else if (mFailed) { else if (mFailed) {
std::string message; std::string message;
if (mCleanupType == CleanupType::MEDIA) { if (mCleanupType == CleanupType::MEDIA) {
mButton1->setText("MEDIA", "start processing"); mButton1->setText(_("MEDIA"), _("start processing"));
message.append("MEDIA CLEANUP FAILED"); message.append(_("MEDIA CLEANUP FAILED"));
} }
else if (mCleanupType == CleanupType::GAMELISTS) { else if (mCleanupType == CleanupType::GAMELISTS) {
mButton2->setText("GAMELISTS", "start processing"); mButton2->setText(_("GAMELISTS"), _("start processing"));
message.append("GAMELISTS CLEANUP FAILED"); message.append(_("GAMELISTS CLEANUP FAILED"));
} }
else { else {
mButton3->setText("COLLECTIONS", "start processing"); mButton3->setText(_("COLLECTIONS"), _("start processing"));
message.append("COLLECTIONS CLEANUP FAILED"); message.append(_("COLLECTIONS CLEANUP FAILED"));
} }
mStatus->setValue(message); mStatus->setValue(message);
{ {
@ -1167,7 +1189,7 @@ bool GuiOrphanedDataCleanup::input(InputConfig* config, Input input)
} }
else if (mCursorPos == 3) { else if (mCursorPos == 3) {
mDescription->setValue( mDescription->setValue(
mNeedsReloading ? "THE APPLICATION WILL RELOAD WHEN CLOSING THIS UTILITY." : mNeedsReloading ? _("THE APPLICATION WILL RELOAD WHEN CLOSING THIS UTILITY.") :
""); "");
} }
} }

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// //
// ES-DE // ES-DE Frontend
// GuiScraperMenu.cpp // GuiScraperMenu.cpp
// //
// Game media scraper, including settings as well as the scraping start button. // Game media scraper, including settings as well as the scraping start button.
@ -18,6 +18,7 @@
#include "guis/GuiMsgBox.h" #include "guis/GuiMsgBox.h"
#include "guis/GuiOfflineGenerator.h" #include "guis/GuiOfflineGenerator.h"
#include "guis/GuiScraperMulti.h" #include "guis/GuiScraperMulti.h"
#include "utils/LocalizationUtil.h"
GuiScraperMenu::GuiScraperMenu(std::string title) GuiScraperMenu::GuiScraperMenu(std::string title)
: mRenderer {Renderer::getInstance()} : mRenderer {Renderer::getInstance()}
@ -25,60 +26,61 @@ GuiScraperMenu::GuiScraperMenu(std::string title)
{ {
// Scraper service. // Scraper service.
mScraper = mScraper =
std::make_shared<OptionListComponent<std::string>>(getHelpStyle(), "SCRAPE FROM", false); std::make_shared<OptionListComponent<std::string>>(getHelpStyle(), _("SCRAPE FROM"), false);
std::vector<std::string> scrapers = getScraperList(); std::vector<std::string> scrapers = getScraperList();
// Select either the first entry or the one read from the settings, // Select either the first entry or the one read from the settings,
// just in case the scraper from settings has vanished. // just in case the scraper from settings has vanished.
for (auto it = scrapers.cbegin(); it != scrapers.cend(); ++it) for (auto it = scrapers.cbegin(); it != scrapers.cend(); ++it)
mScraper->add(*it, *it, *it == Settings::getInstance()->getString("Scraper")); mScraper->add(Utils::String::toUpper(*it), *it,
*it == Settings::getInstance()->getString("Scraper"));
// If there are no objects returned, then there must be a manually modified entry in the // If there are no objects returned, then there must be a manually modified entry in the
// configuration file. Simply set the scraper to "screenscraper" in this case. // configuration file. Simply set the scraper to "screenscraper" in this case.
if (mScraper->getSelectedObjects().size() == 0) if (mScraper->getSelectedObjects().size() == 0)
mScraper->selectEntry(0); mScraper->selectEntry(0);
mMenu.addWithLabel("SCRAPE FROM", mScraper); mMenu.addWithLabel(_("SCRAPE FROM"), mScraper);
// Search filters, getSearches() will generate a queue of games to scrape // Search filters, getSearches() will generate a queue of games to scrape
// based on the outcome of the checks below. // based on the outcome of the checks below.
mFilters = std::make_shared<OptionListComponent<GameFilterFunc>>(getHelpStyle(), mFilters = std::make_shared<OptionListComponent<GameFilterFunc>>(
"SCRAPE THESE GAMES", false); getHelpStyle(), _("SCRAPE THESE GAMES"), false);
mFilters->add( mFilters->add(
"ALL GAMES", _("ALL GAMES"),
[](SystemData*, FileData*) -> bool { [](SystemData*, FileData*) -> bool {
// All games. // All games.
return true; return true;
}, },
false); false);
mFilters->add( mFilters->add(
"FAVORITE GAMES", _("FAVORITE GAMES"),
[](SystemData*, FileData* g) -> bool { [](SystemData*, FileData* g) -> bool {
// Favorite games. // Favorite games.
return g->getFavorite(); return g->getFavorite();
}, },
false); false);
mFilters->add( mFilters->add(
"NO METADATA", _("NO METADATA"),
[](SystemData*, FileData* g) -> bool { [](SystemData*, FileData* g) -> bool {
// No metadata. // No metadata.
return g->metadata.get("desc").empty(); return g->metadata.get("desc").empty();
}, },
false); false);
mFilters->add( mFilters->add(
"NO GAME IMAGE", _("NO GAME IMAGE"),
[](SystemData*, FileData* g) -> bool { [](SystemData*, FileData* g) -> bool {
// No game image. // No game image.
return g->getImagePath().empty(); return g->getImagePath().empty();
}, },
false); false);
mFilters->add( mFilters->add(
"NO GAME VIDEO", _("NO GAME VIDEO"),
[](SystemData*, FileData* g) -> bool { [](SystemData*, FileData* g) -> bool {
// No game video. // No game video.
return g->getVideoPath().empty(); return g->getVideoPath().empty();
}, },
false); false);
mFilters->add( mFilters->add(
"FOLDERS ONLY", _("FOLDERS ONLY"),
[](SystemData*, FileData* g) -> bool { [](SystemData*, FileData* g) -> bool {
// Folders only. // Folders only.
return g->getType() == FOLDER; return g->getType() == FOLDER;
@ -86,7 +88,7 @@ GuiScraperMenu::GuiScraperMenu(std::string title)
false); false);
mFilters->selectEntry(Settings::getInstance()->getInt("ScraperFilter")); mFilters->selectEntry(Settings::getInstance()->getInt("ScraperFilter"));
mMenu.addWithLabel("SCRAPE THESE GAMES", mFilters); mMenu.addWithLabel(_("SCRAPE THESE GAMES"), mFilters);
mMenu.addSaveFunc([this] { mMenu.addSaveFunc([this] {
if (mScraper->getSelected() != Settings::getInstance()->getString("Scraper")) { if (mScraper->getSelected() != Settings::getInstance()->getString("Scraper")) {
@ -102,22 +104,23 @@ GuiScraperMenu::GuiScraperMenu(std::string title)
// Add systems (all systems with an existing platform ID are listed). // Add systems (all systems with an existing platform ID are listed).
mSystems = std::make_shared<OptionListComponent<SystemData*>>(getHelpStyle(), mSystems = std::make_shared<OptionListComponent<SystemData*>>(getHelpStyle(),
"SCRAPE THESE SYSTEMS", true); _("SCRAPE THESE SYSTEMS"), true);
for (unsigned int i {0}; i < SystemData::sSystemVector.size(); ++i) { for (unsigned int i {0}; i < SystemData::sSystemVector.size(); ++i) {
if (!SystemData::sSystemVector[i]->hasPlatformId(PlatformIds::PLATFORM_IGNORE)) { if (!SystemData::sSystemVector[i]->hasPlatformId(PlatformIds::PLATFORM_IGNORE)) {
mSystems->add(SystemData::sSystemVector[i]->getFullName(), SystemData::sSystemVector[i], mSystems->add(Utils::String::toUpper(SystemData::sSystemVector[i]->getFullName()),
SystemData::sSystemVector[i],
!SystemData::sSystemVector[i]->getPlatformIds().empty()); !SystemData::sSystemVector[i]->getPlatformIds().empty());
SystemData::sSystemVector[i]->getScrapeFlag() ? mSystems->selectEntry(i) : SystemData::sSystemVector[i]->getScrapeFlag() ? mSystems->selectEntry(i) :
mSystems->unselectEntry(i); mSystems->unselectEntry(i);
} }
} }
mMenu.addWithLabel("SCRAPE THESE SYSTEMS", mSystems); mMenu.addWithLabel(_("SCRAPE THESE SYSTEMS"), mSystems);
addEntry("ACCOUNT SETTINGS", mMenuColorPrimary, true, [this] { addEntry(_("ACCOUNT SETTINGS"), mMenuColorPrimary, true, [this] {
// Open the account options menu. // Open the account options menu.
openAccountOptions(); openAccountOptions();
}); });
addEntry("CONTENT SETTINGS", mMenuColorPrimary, true, [this] { addEntry(_("CONTENT SETTINGS"), mMenuColorPrimary, true, [this] {
// If the scraper service has been changed before entering this menu, then save the // If the scraper service has been changed before entering this menu, then save the
// settings so that the specific options supported by the respective scrapers // settings so that the specific options supported by the respective scrapers
// can be enabled or disabled. // can be enabled or disabled.
@ -125,11 +128,11 @@ GuiScraperMenu::GuiScraperMenu(std::string title)
mMenu.save(); mMenu.save();
openContentOptions(); openContentOptions();
}); });
addEntry("MIXIMAGE SETTINGS", mMenuColorPrimary, true, [this] { addEntry(_("MIXIMAGE SETTINGS"), mMenuColorPrimary, true, [this] {
// Open the miximage options menu. // Open the miximage options menu.
openMiximageOptions(); openMiximageOptions();
}); });
addEntry("OTHER SETTINGS", mMenuColorPrimary, true, [this] { addEntry(_("OTHER SETTINGS"), mMenuColorPrimary, true, [this] {
// If the scraper service has been changed before entering this menu, then save the // If the scraper service has been changed before entering this menu, then save the
// settings so that the specific options supported by the respective scrapers // settings so that the specific options supported by the respective scrapers
// can be enabled or disabled. // can be enabled or disabled.
@ -140,8 +143,8 @@ GuiScraperMenu::GuiScraperMenu(std::string title)
addChild(&mMenu); addChild(&mMenu);
mMenu.addButton("START", "start scraper", std::bind(&GuiScraperMenu::pressedStart, this)); mMenu.addButton(_("START"), _("start scraper"), std::bind(&GuiScraperMenu::pressedStart, this));
mMenu.addButton("BACK", "back", [&] { delete this; }); mMenu.addButton(_("BACK"), _("back"), [&] { delete this; });
setSize(mMenu.getSize()); setSize(mMenu.getSize());
@ -172,13 +175,15 @@ GuiScraperMenu::~GuiScraperMenu()
void GuiScraperMenu::openAccountOptions() void GuiScraperMenu::openAccountOptions()
{ {
auto s = new GuiSettings("ACCOUNT SETTINGS"); auto s = new GuiSettings(_("ACCOUNT SETTINGS"));
// ScreenScraper username. // ScreenScraper username.
auto scraperUsernameScreenScraper = std::make_shared<TextComponent>( auto scraperUsernameScreenScraper = std::make_shared<TextComponent>(
"", Font::get(FONT_SIZE_MEDIUM), mMenuColorPrimary, ALIGN_RIGHT); "", Font::get(FONT_SIZE_MEDIUM), mMenuColorPrimary, ALIGN_RIGHT);
s->addEditableTextComponent("SCREENSCRAPER USERNAME", scraperUsernameScreenScraper, s->addEditableTextComponent(_("SCREENSCRAPER USERNAME"), scraperUsernameScreenScraper,
Settings::getInstance()->getString("ScraperUsernameScreenScraper")); Settings::getInstance()->getString("ScraperUsernameScreenScraper"));
scraperUsernameScreenScraper->setSize(0.0f,
scraperUsernameScreenScraper->getFont()->getHeight());
s->addSaveFunc([scraperUsernameScreenScraper, s] { s->addSaveFunc([scraperUsernameScreenScraper, s] {
if (scraperUsernameScreenScraper->getValue() != if (scraperUsernameScreenScraper->getValue() !=
Settings::getInstance()->getString("ScraperUsernameScreenScraper")) { Settings::getInstance()->getString("ScraperUsernameScreenScraper")) {
@ -197,8 +202,10 @@ void GuiScraperMenu::openAccountOptions()
scraperPasswordScreenScraper->setHiddenValue( scraperPasswordScreenScraper->setHiddenValue(
Settings::getInstance()->getString("ScraperPasswordScreenScraper")); Settings::getInstance()->getString("ScraperPasswordScreenScraper"));
} }
s->addEditableTextComponent("SCREENSCRAPER PASSWORD", scraperPasswordScreenScraper, s->addEditableTextComponent(_("SCREENSCRAPER PASSWORD"), scraperPasswordScreenScraper,
passwordMasked, "", true); passwordMasked, "", true);
scraperPasswordScreenScraper->setSize(0.0f,
scraperPasswordScreenScraper->getFont()->getHeight());
s->addSaveFunc([scraperPasswordScreenScraper, s] { s->addSaveFunc([scraperPasswordScreenScraper, s] {
if (scraperPasswordScreenScraper->getHiddenValue() != if (scraperPasswordScreenScraper->getHiddenValue() !=
Settings::getInstance()->getString("ScraperPasswordScreenScraper")) { Settings::getInstance()->getString("ScraperPasswordScreenScraper")) {
@ -212,7 +219,7 @@ void GuiScraperMenu::openAccountOptions()
auto scraperUseAccountScreenScraper = std::make_shared<SwitchComponent>(); auto scraperUseAccountScreenScraper = std::make_shared<SwitchComponent>();
scraperUseAccountScreenScraper->setState( scraperUseAccountScreenScraper->setState(
Settings::getInstance()->getBool("ScraperUseAccountScreenScraper")); Settings::getInstance()->getBool("ScraperUseAccountScreenScraper"));
s->addWithLabel("USE THIS ACCOUNT FOR SCREENSCRAPER", scraperUseAccountScreenScraper); s->addWithLabel(_("USE THIS ACCOUNT FOR SCREENSCRAPER"), scraperUseAccountScreenScraper);
s->addSaveFunc([scraperUseAccountScreenScraper, s] { s->addSaveFunc([scraperUseAccountScreenScraper, s] {
if (scraperUseAccountScreenScraper->getState() != if (scraperUseAccountScreenScraper->getState() !=
Settings::getInstance()->getBool("ScraperUseAccountScreenScraper")) { Settings::getInstance()->getBool("ScraperUseAccountScreenScraper")) {
@ -227,12 +234,12 @@ void GuiScraperMenu::openAccountOptions()
void GuiScraperMenu::openContentOptions() void GuiScraperMenu::openContentOptions()
{ {
auto s = new GuiSettings("CONTENT SETTINGS"); auto s = new GuiSettings(_("CONTENT SETTINGS"));
// Scrape game names. // Scrape game names.
auto scrapeGameNames = std::make_shared<SwitchComponent>(); auto scrapeGameNames = std::make_shared<SwitchComponent>();
scrapeGameNames->setState(Settings::getInstance()->getBool("ScrapeGameNames")); scrapeGameNames->setState(Settings::getInstance()->getBool("ScrapeGameNames"));
s->addWithLabel("GAME NAMES", scrapeGameNames); s->addWithLabel(_("GAME NAMES"), scrapeGameNames);
s->addSaveFunc([scrapeGameNames, s] { s->addSaveFunc([scrapeGameNames, s] {
if (scrapeGameNames->getState() != Settings::getInstance()->getBool("ScrapeGameNames")) { if (scrapeGameNames->getState() != Settings::getInstance()->getBool("ScrapeGameNames")) {
Settings::getInstance()->setBool("ScrapeGameNames", scrapeGameNames->getState()); Settings::getInstance()->setBool("ScrapeGameNames", scrapeGameNames->getState());
@ -243,7 +250,7 @@ void GuiScraperMenu::openContentOptions()
// Scrape ratings. // Scrape ratings.
auto scrapeRatings = std::make_shared<SwitchComponent>(); auto scrapeRatings = std::make_shared<SwitchComponent>();
scrapeRatings->setState(Settings::getInstance()->getBool("ScrapeRatings")); scrapeRatings->setState(Settings::getInstance()->getBool("ScrapeRatings"));
s->addWithLabel("RATINGS", scrapeRatings); s->addWithLabel(_("RATINGS"), scrapeRatings);
s->addSaveFunc([scrapeRatings, s] { s->addSaveFunc([scrapeRatings, s] {
if (scrapeRatings->getState() != Settings::getInstance()->getBool("ScrapeRatings")) { if (scrapeRatings->getState() != Settings::getInstance()->getBool("ScrapeRatings")) {
Settings::getInstance()->setBool("ScrapeRatings", scrapeRatings->getState()); Settings::getInstance()->setBool("ScrapeRatings", scrapeRatings->getState());
@ -286,7 +293,7 @@ void GuiScraperMenu::openContentOptions()
// Scrape other metadata. // Scrape other metadata.
auto scrapeMetadata = std::make_shared<SwitchComponent>(); auto scrapeMetadata = std::make_shared<SwitchComponent>();
scrapeMetadata->setState(Settings::getInstance()->getBool("ScrapeMetadata")); scrapeMetadata->setState(Settings::getInstance()->getBool("ScrapeMetadata"));
s->addWithLabel("OTHER METADATA", scrapeMetadata); s->addWithLabel(_("OTHER METADATA"), scrapeMetadata);
s->addSaveFunc([scrapeMetadata, s] { s->addSaveFunc([scrapeMetadata, s] {
if (scrapeMetadata->getState() != Settings::getInstance()->getBool("ScrapeMetadata")) { if (scrapeMetadata->getState() != Settings::getInstance()->getBool("ScrapeMetadata")) {
Settings::getInstance()->setBool("ScrapeMetadata", scrapeMetadata->getState()); Settings::getInstance()->setBool("ScrapeMetadata", scrapeMetadata->getState());
@ -297,7 +304,7 @@ void GuiScraperMenu::openContentOptions()
// Scrape videos. // Scrape videos.
auto scrapeVideos = std::make_shared<SwitchComponent>(); auto scrapeVideos = std::make_shared<SwitchComponent>();
scrapeVideos->setState(Settings::getInstance()->getBool("ScrapeVideos")); scrapeVideos->setState(Settings::getInstance()->getBool("ScrapeVideos"));
s->addWithLabel("VIDEOS", scrapeVideos); s->addWithLabel(_("VIDEOS"), scrapeVideos);
s->addSaveFunc([scrapeVideos, s] { s->addSaveFunc([scrapeVideos, s] {
if (scrapeVideos->getState() != Settings::getInstance()->getBool("ScrapeVideos")) { if (scrapeVideos->getState() != Settings::getInstance()->getBool("ScrapeVideos")) {
Settings::getInstance()->setBool("ScrapeVideos", scrapeVideos->getState()); Settings::getInstance()->setBool("ScrapeVideos", scrapeVideos->getState());
@ -317,7 +324,7 @@ void GuiScraperMenu::openContentOptions()
// Scrape screenshots images. // Scrape screenshots images.
auto scrapeScreenshots = std::make_shared<SwitchComponent>(); auto scrapeScreenshots = std::make_shared<SwitchComponent>();
scrapeScreenshots->setState(Settings::getInstance()->getBool("ScrapeScreenshots")); scrapeScreenshots->setState(Settings::getInstance()->getBool("ScrapeScreenshots"));
s->addWithLabel("SCREENSHOT IMAGES", scrapeScreenshots); s->addWithLabel(_("SCREENSHOT IMAGES"), scrapeScreenshots);
s->addSaveFunc([scrapeScreenshots, s] { s->addSaveFunc([scrapeScreenshots, s] {
if (scrapeScreenshots->getState() != if (scrapeScreenshots->getState() !=
Settings::getInstance()->getBool("ScrapeScreenshots")) { Settings::getInstance()->getBool("ScrapeScreenshots")) {
@ -329,7 +336,7 @@ void GuiScraperMenu::openContentOptions()
// Scrape title screen images. // Scrape title screen images.
auto scrapeTitleScreens = std::make_shared<SwitchComponent>(); auto scrapeTitleScreens = std::make_shared<SwitchComponent>();
scrapeTitleScreens->setState(Settings::getInstance()->getBool("ScrapeTitleScreens")); scrapeTitleScreens->setState(Settings::getInstance()->getBool("ScrapeTitleScreens"));
s->addWithLabel("TITLE SCREEN IMAGES", scrapeTitleScreens); s->addWithLabel(_("TITLE SCREEN IMAGES"), scrapeTitleScreens);
s->addSaveFunc([scrapeTitleScreens, s] { s->addSaveFunc([scrapeTitleScreens, s] {
if (scrapeTitleScreens->getState() != if (scrapeTitleScreens->getState() !=
Settings::getInstance()->getBool("ScrapeTitleScreens")) { Settings::getInstance()->getBool("ScrapeTitleScreens")) {
@ -341,7 +348,7 @@ void GuiScraperMenu::openContentOptions()
// Scrape box cover images. // Scrape box cover images.
auto scrapeCovers = std::make_shared<SwitchComponent>(); auto scrapeCovers = std::make_shared<SwitchComponent>();
scrapeCovers->setState(Settings::getInstance()->getBool("ScrapeCovers")); scrapeCovers->setState(Settings::getInstance()->getBool("ScrapeCovers"));
s->addWithLabel("BOX COVER IMAGES", scrapeCovers); s->addWithLabel(_("BOX COVER IMAGES"), scrapeCovers);
s->addSaveFunc([scrapeCovers, s] { s->addSaveFunc([scrapeCovers, s] {
if (scrapeCovers->getState() != Settings::getInstance()->getBool("ScrapeCovers")) { if (scrapeCovers->getState() != Settings::getInstance()->getBool("ScrapeCovers")) {
Settings::getInstance()->setBool("ScrapeCovers", scrapeCovers->getState()); Settings::getInstance()->setBool("ScrapeCovers", scrapeCovers->getState());
@ -352,7 +359,7 @@ void GuiScraperMenu::openContentOptions()
// Scrape box back cover images. // Scrape box back cover images.
auto scrapeBackCovers = std::make_shared<SwitchComponent>(); auto scrapeBackCovers = std::make_shared<SwitchComponent>();
scrapeBackCovers->setState(Settings::getInstance()->getBool("ScrapeBackCovers")); scrapeBackCovers->setState(Settings::getInstance()->getBool("ScrapeBackCovers"));
s->addWithLabel("BOX BACK COVER IMAGES", scrapeBackCovers); s->addWithLabel(_("BOX BACK COVER IMAGES"), scrapeBackCovers);
s->addSaveFunc([scrapeBackCovers, s] { s->addSaveFunc([scrapeBackCovers, s] {
if (scrapeBackCovers->getState() != Settings::getInstance()->getBool("ScrapeBackCovers")) { if (scrapeBackCovers->getState() != Settings::getInstance()->getBool("ScrapeBackCovers")) {
Settings::getInstance()->setBool("ScrapeBackCovers", scrapeBackCovers->getState()); Settings::getInstance()->setBool("ScrapeBackCovers", scrapeBackCovers->getState());
@ -363,7 +370,7 @@ void GuiScraperMenu::openContentOptions()
// Scrape marquee images. // Scrape marquee images.
auto scrapeMarquees = std::make_shared<SwitchComponent>(); auto scrapeMarquees = std::make_shared<SwitchComponent>();
scrapeMarquees->setState(Settings::getInstance()->getBool("ScrapeMarquees")); scrapeMarquees->setState(Settings::getInstance()->getBool("ScrapeMarquees"));
s->addWithLabel("MARQUEE (WHEEL) IMAGES", scrapeMarquees); s->addWithLabel(_("MARQUEE (WHEEL) IMAGES"), scrapeMarquees);
s->addSaveFunc([scrapeMarquees, s] { s->addSaveFunc([scrapeMarquees, s] {
if (scrapeMarquees->getState() != Settings::getInstance()->getBool("ScrapeMarquees")) { if (scrapeMarquees->getState() != Settings::getInstance()->getBool("ScrapeMarquees")) {
Settings::getInstance()->setBool("ScrapeMarquees", scrapeMarquees->getState()); Settings::getInstance()->setBool("ScrapeMarquees", scrapeMarquees->getState());
@ -374,7 +381,7 @@ void GuiScraperMenu::openContentOptions()
// Scrape 3D box images. // Scrape 3D box images.
auto scrape3dBoxes = std::make_shared<SwitchComponent>(); auto scrape3dBoxes = std::make_shared<SwitchComponent>();
scrape3dBoxes->setState(Settings::getInstance()->getBool("Scrape3DBoxes")); scrape3dBoxes->setState(Settings::getInstance()->getBool("Scrape3DBoxes"));
s->addWithLabel("3D BOX IMAGES", scrape3dBoxes); s->addWithLabel(_("3D BOX IMAGES"), scrape3dBoxes);
s->addSaveFunc([scrape3dBoxes, s] { s->addSaveFunc([scrape3dBoxes, s] {
if (scrape3dBoxes->getState() != Settings::getInstance()->getBool("Scrape3DBoxes")) { if (scrape3dBoxes->getState() != Settings::getInstance()->getBool("Scrape3DBoxes")) {
Settings::getInstance()->setBool("Scrape3DBoxes", scrape3dBoxes->getState()); Settings::getInstance()->setBool("Scrape3DBoxes", scrape3dBoxes->getState());
@ -395,7 +402,7 @@ void GuiScraperMenu::openContentOptions()
// Scrape physical media images. // Scrape physical media images.
auto scrapePhysicalMedia = std::make_shared<SwitchComponent>(); auto scrapePhysicalMedia = std::make_shared<SwitchComponent>();
scrapePhysicalMedia->setState(Settings::getInstance()->getBool("ScrapePhysicalMedia")); scrapePhysicalMedia->setState(Settings::getInstance()->getBool("ScrapePhysicalMedia"));
s->addWithLabel("PHYSICAL MEDIA IMAGES", scrapePhysicalMedia); s->addWithLabel(_("PHYSICAL MEDIA IMAGES"), scrapePhysicalMedia);
s->addSaveFunc([scrapePhysicalMedia, s] { s->addSaveFunc([scrapePhysicalMedia, s] {
if (scrapePhysicalMedia->getState() != if (scrapePhysicalMedia->getState() !=
Settings::getInstance()->getBool("ScrapePhysicalMedia")) { Settings::getInstance()->getBool("ScrapePhysicalMedia")) {
@ -418,7 +425,7 @@ void GuiScraperMenu::openContentOptions()
// Scrape fan art images. // Scrape fan art images.
auto scrapeFanArt = std::make_shared<SwitchComponent>(); auto scrapeFanArt = std::make_shared<SwitchComponent>();
scrapeFanArt->setState(Settings::getInstance()->getBool("ScrapeFanArt")); scrapeFanArt->setState(Settings::getInstance()->getBool("ScrapeFanArt"));
s->addWithLabel("FAN ART IMAGES", scrapeFanArt); s->addWithLabel(_("FAN ART IMAGES"), scrapeFanArt);
s->addSaveFunc([scrapeFanArt, s] { s->addSaveFunc([scrapeFanArt, s] {
if (scrapeFanArt->getState() != Settings::getInstance()->getBool("ScrapeFanArt")) { if (scrapeFanArt->getState() != Settings::getInstance()->getBool("ScrapeFanArt")) {
Settings::getInstance()->setBool("ScrapeFanArt", scrapeFanArt->getState()); Settings::getInstance()->setBool("ScrapeFanArt", scrapeFanArt->getState());
@ -429,7 +436,7 @@ void GuiScraperMenu::openContentOptions()
// Scrape game manuals. // Scrape game manuals.
auto scrapeManuals = std::make_shared<SwitchComponent>(); auto scrapeManuals = std::make_shared<SwitchComponent>();
scrapeManuals->setState(Settings::getInstance()->getBool("ScrapeManuals")); scrapeManuals->setState(Settings::getInstance()->getBool("ScrapeManuals"));
s->addWithLabel("GAME MANUALS", scrapeManuals); s->addWithLabel(_("GAME MANUALS"), scrapeManuals);
s->addSaveFunc([scrapeManuals, s] { s->addSaveFunc([scrapeManuals, s] {
if (scrapeManuals->getState() != Settings::getInstance()->getBool("ScrapeManuals")) { if (scrapeManuals->getState() != Settings::getInstance()->getBool("ScrapeManuals")) {
Settings::getInstance()->setBool("ScrapeManuals", scrapeManuals->getState()); Settings::getInstance()->setBool("ScrapeManuals", scrapeManuals->getState());
@ -452,20 +459,20 @@ void GuiScraperMenu::openContentOptions()
void GuiScraperMenu::openMiximageOptions() void GuiScraperMenu::openMiximageOptions()
{ {
auto s = new GuiSettings("MIXIMAGE SETTINGS"); auto s = new GuiSettings(_("MIXIMAGE SETTINGS"));
// Miximage resolution. // Miximage resolution.
auto miximageResolution = std::make_shared<OptionListComponent<std::string>>( auto miximageResolution = std::make_shared<OptionListComponent<std::string>>(
getHelpStyle(), "MIXIMAGE RESOLUTION", false); getHelpStyle(), _("MIXIMAGE RESOLUTION"), false);
std::string selectedResolution {Settings::getInstance()->getString("MiximageResolution")}; std::string selectedResolution {Settings::getInstance()->getString("MiximageResolution")};
miximageResolution->add("1280x960", "1280x960", selectedResolution == "1280x960"); miximageResolution->add("1280X960", "1280x960", selectedResolution == "1280x960");
miximageResolution->add("1920x1440", "1920x1440", selectedResolution == "1920x1440"); miximageResolution->add("1920X1440", "1920x1440", selectedResolution == "1920x1440");
miximageResolution->add("640x480", "640x480", selectedResolution == "640x480"); miximageResolution->add("640X480", "640x480", selectedResolution == "640x480");
// If there are no objects returned, then there must be a manually modified entry in the // If there are no objects returned, then there must be a manually modified entry in the
// configuration file. Simply set the resolution to "1280x960" in this case. // configuration file. Simply set the resolution to "1280x960" in this case.
if (miximageResolution->getSelectedObjects().size() == 0) if (miximageResolution->getSelectedObjects().size() == 0)
miximageResolution->selectEntry(0); miximageResolution->selectEntry(0);
s->addWithLabel("MIXIMAGE RESOLUTION", miximageResolution); s->addWithLabel(_("MIXIMAGE RESOLUTION"), miximageResolution);
s->addSaveFunc([miximageResolution, s] { s->addSaveFunc([miximageResolution, s] {
if (miximageResolution->getSelected() != if (miximageResolution->getSelected() !=
Settings::getInstance()->getString("MiximageResolution")) { Settings::getInstance()->getString("MiximageResolution")) {
@ -477,17 +484,17 @@ void GuiScraperMenu::openMiximageOptions()
// Horizontally oriented screenshots fit. // Horizontally oriented screenshots fit.
auto miximageHorizontalFit = std::make_shared<OptionListComponent<std::string>>( auto miximageHorizontalFit = std::make_shared<OptionListComponent<std::string>>(
getHelpStyle(), "HORIZONTAL SCREENSHOT FIT", false); getHelpStyle(), _p("short", "HORIZONTAL SCREENSHOT FIT"), false);
const std::string selectedHorizontalFit { const std::string selectedHorizontalFit {
Settings::getInstance()->getString("MiximageScreenshotHorizontalFit")}; Settings::getInstance()->getString("MiximageScreenshotHorizontalFit")};
miximageHorizontalFit->add("contain", "contain", selectedHorizontalFit == "contain"); miximageHorizontalFit->add(_("CONTAIN"), "contain", selectedHorizontalFit == "contain");
miximageHorizontalFit->add("crop", "crop", selectedHorizontalFit == "crop"); miximageHorizontalFit->add(_("CROP"), "crop", selectedHorizontalFit == "crop");
miximageHorizontalFit->add("stretch", "stretch", selectedHorizontalFit == "stretch"); miximageHorizontalFit->add(_("STRETCH"), "stretch", selectedHorizontalFit == "stretch");
// If there are no objects returned, then there must be a manually modified entry in the // If there are no objects returned, then there must be a manually modified entry in the
// configuration file. Simply set the horizontal screenshot fit to "crop" in this case. // configuration file. Simply set the horizontal screenshot fit to "crop" in this case.
if (miximageHorizontalFit->getSelectedObjects().size() == 0) if (miximageHorizontalFit->getSelectedObjects().size() == 0)
miximageHorizontalFit->selectEntry(1); miximageHorizontalFit->selectEntry(1);
s->addWithLabel("HORIZONTAL SCREENSHOT FIT", miximageHorizontalFit); s->addWithLabel(_("HORIZONTAL SCREENSHOT FIT"), miximageHorizontalFit);
s->addSaveFunc([miximageHorizontalFit, s] { s->addSaveFunc([miximageHorizontalFit, s] {
if (miximageHorizontalFit->getSelected() != if (miximageHorizontalFit->getSelected() !=
Settings::getInstance()->getString("MiximageScreenshotHorizontalFit")) { Settings::getInstance()->getString("MiximageScreenshotHorizontalFit")) {
@ -499,17 +506,17 @@ void GuiScraperMenu::openMiximageOptions()
// Vertically oriented screenshots fit. // Vertically oriented screenshots fit.
auto miximageVerticalFit = std::make_shared<OptionListComponent<std::string>>( auto miximageVerticalFit = std::make_shared<OptionListComponent<std::string>>(
getHelpStyle(), "VERTICAL SCREENSHOT FIT", false); getHelpStyle(), _p("short", "VERTICAL SCREENSHOT FIT"), false);
const std::string selectedVerticalFit { const std::string selectedVerticalFit {
Settings::getInstance()->getString("MiximageScreenshotVerticalFit")}; Settings::getInstance()->getString("MiximageScreenshotVerticalFit")};
miximageVerticalFit->add("contain", "contain", selectedVerticalFit == "contain"); miximageVerticalFit->add(_("CONTAIN"), "contain", selectedVerticalFit == "contain");
miximageVerticalFit->add("crop", "crop", selectedVerticalFit == "crop"); miximageVerticalFit->add(_("CROP"), "crop", selectedVerticalFit == "crop");
miximageVerticalFit->add("stretch", "stretch", selectedVerticalFit == "stretch"); miximageVerticalFit->add(_("STRETCH"), "stretch", selectedVerticalFit == "stretch");
// If there are no objects returned, then there must be a manually modified entry in the // If there are no objects returned, then there must be a manually modified entry in the
// configuration file. Simply set the vertical screenshot fit to "contain" in this case. // configuration file. Simply set the vertical screenshot fit to "contain" in this case.
if (miximageVerticalFit->getSelectedObjects().size() == 0) if (miximageVerticalFit->getSelectedObjects().size() == 0)
miximageVerticalFit->selectEntry(0); miximageVerticalFit->selectEntry(0);
s->addWithLabel("VERTICAL SCREENSHOT FIT", miximageVerticalFit); s->addWithLabel(_("VERTICAL SCREENSHOT FIT"), miximageVerticalFit);
s->addSaveFunc([miximageVerticalFit, s] { s->addSaveFunc([miximageVerticalFit, s] {
if (miximageVerticalFit->getSelected() != if (miximageVerticalFit->getSelected() !=
Settings::getInstance()->getString("MiximageScreenshotVerticalFit")) { Settings::getInstance()->getString("MiximageScreenshotVerticalFit")) {
@ -521,16 +528,16 @@ void GuiScraperMenu::openMiximageOptions()
// Screenshots aspect ratio threshold. // Screenshots aspect ratio threshold.
auto miximageAspectThreshold = std::make_shared<OptionListComponent<std::string>>( auto miximageAspectThreshold = std::make_shared<OptionListComponent<std::string>>(
getHelpStyle(), "ASPECT RATIO THRESHOLD", false); getHelpStyle(), _p("short", "SCREENSHOT ASPECT RATIO THRESHOLD"), false);
const std::string selectedAspectThreshold { const std::string selectedAspectThreshold {
Settings::getInstance()->getString("MiximageScreenshotAspectThreshold")}; Settings::getInstance()->getString("MiximageScreenshotAspectThreshold")};
miximageAspectThreshold->add("high", "high", selectedAspectThreshold == "high"); miximageAspectThreshold->add(_("HIGH"), "high", selectedAspectThreshold == "high");
miximageAspectThreshold->add("low", "low", selectedAspectThreshold == "low"); miximageAspectThreshold->add(_("LOW"), "low", selectedAspectThreshold == "low");
// If there are no objects returned, then there must be a manually modified entry in the // If there are no objects returned, then there must be a manually modified entry in the
// configuration file. Simply set the screenshot aspect threshold to "high" in this case. // configuration file. Simply set the screenshot aspect threshold to "high" in this case.
if (miximageAspectThreshold->getSelectedObjects().size() == 0) if (miximageAspectThreshold->getSelectedObjects().size() == 0)
miximageAspectThreshold->selectEntry(0); miximageAspectThreshold->selectEntry(0);
s->addWithLabel("SCREENSHOT ASPECT RATIO THRESHOLD", miximageAspectThreshold); s->addWithLabel(_("SCREENSHOT ASPECT RATIO THRESHOLD"), miximageAspectThreshold);
s->addSaveFunc([miximageAspectThreshold, s] { s->addSaveFunc([miximageAspectThreshold, s] {
if (miximageAspectThreshold->getSelected() != if (miximageAspectThreshold->getSelected() !=
Settings::getInstance()->getString("MiximageScreenshotAspectThreshold")) { Settings::getInstance()->getString("MiximageScreenshotAspectThreshold")) {
@ -542,16 +549,16 @@ void GuiScraperMenu::openMiximageOptions()
// Blank areas fill color. // Blank areas fill color.
auto miximageBlankAreasColor = std::make_shared<OptionListComponent<std::string>>( auto miximageBlankAreasColor = std::make_shared<OptionListComponent<std::string>>(
getHelpStyle(), "BLANK AREAS FILL COLOR", false); getHelpStyle(), _("BLANK AREAS FILL COLOR"), false);
const std::string selectedBlankAreasColor { const std::string selectedBlankAreasColor {
Settings::getInstance()->getString("MiximageScreenshotBlankAreasColor")}; Settings::getInstance()->getString("MiximageScreenshotBlankAreasColor")};
miximageBlankAreasColor->add("black", "black", selectedBlankAreasColor == "black"); miximageBlankAreasColor->add(_("BLACK"), "black", selectedBlankAreasColor == "black");
miximageBlankAreasColor->add("frame", "frame", selectedBlankAreasColor == "frame"); miximageBlankAreasColor->add(_("FRAME"), "frame", selectedBlankAreasColor == "frame");
// If there are no objects returned, then there must be a manually modified entry in the // If there are no objects returned, then there must be a manually modified entry in the
// configuration file. Simply set the blank area fill color to "black" in this case. // configuration file. Simply set the blank area fill color to "black" in this case.
if (miximageBlankAreasColor->getSelectedObjects().size() == 0) if (miximageBlankAreasColor->getSelectedObjects().size() == 0)
miximageBlankAreasColor->selectEntry(0); miximageBlankAreasColor->selectEntry(0);
s->addWithLabel("BLANK AREAS FILL COLOR", miximageBlankAreasColor); s->addWithLabel(_("BLANK AREAS FILL COLOR"), miximageBlankAreasColor);
s->addSaveFunc([miximageBlankAreasColor, s] { s->addSaveFunc([miximageBlankAreasColor, s] {
if (miximageBlankAreasColor->getSelected() != if (miximageBlankAreasColor->getSelected() !=
Settings::getInstance()->getString("MiximageScreenshotBlankAreasColor")) { Settings::getInstance()->getString("MiximageScreenshotBlankAreasColor")) {
@ -563,15 +570,15 @@ void GuiScraperMenu::openMiximageOptions()
// Screenshot scaling method. // Screenshot scaling method.
auto miximageScaling = std::make_shared<OptionListComponent<std::string>>( auto miximageScaling = std::make_shared<OptionListComponent<std::string>>(
getHelpStyle(), "SCREENSHOT SCALING", false); getHelpStyle(), _p("short", "SCREENSHOT SCALING METHOD"), false);
std::string selectedScaling {Settings::getInstance()->getString("MiximageScreenshotScaling")}; std::string selectedScaling {Settings::getInstance()->getString("MiximageScreenshotScaling")};
miximageScaling->add("sharp", "sharp", selectedScaling == "sharp"); miximageScaling->add(_("SHARP"), "sharp", selectedScaling == "sharp");
miximageScaling->add("smooth", "smooth", selectedScaling == "smooth"); miximageScaling->add(_("SMOOTH"), "smooth", selectedScaling == "smooth");
// If there are no objects returned, then there must be a manually modified entry in the // If there are no objects returned, then there must be a manually modified entry in the
// configuration file. Simply set the scaling method to "sharp" in this case. // configuration file. Simply set the scaling method to "sharp" in this case.
if (miximageScaling->getSelectedObjects().size() == 0) if (miximageScaling->getSelectedObjects().size() == 0)
miximageScaling->selectEntry(0); miximageScaling->selectEntry(0);
s->addWithLabel("SCREENSHOT SCALING METHOD", miximageScaling); s->addWithLabel(_("SCREENSHOT SCALING METHOD"), miximageScaling);
s->addSaveFunc([miximageScaling, s] { s->addSaveFunc([miximageScaling, s] {
if (miximageScaling->getSelected() != if (miximageScaling->getSelected() !=
Settings::getInstance()->getString("MiximageScreenshotScaling")) { Settings::getInstance()->getString("MiximageScreenshotScaling")) {
@ -583,16 +590,16 @@ void GuiScraperMenu::openMiximageOptions()
// Box/cover size. // Box/cover size.
auto miximageBoxSize = auto miximageBoxSize =
std::make_shared<OptionListComponent<std::string>>(getHelpStyle(), "BOX SIZE", false); std::make_shared<OptionListComponent<std::string>>(getHelpStyle(), _("BOX SIZE"), false);
std::string selectedBoxSize {Settings::getInstance()->getString("MiximageBoxSize")}; std::string selectedBoxSize {Settings::getInstance()->getString("MiximageBoxSize")};
miximageBoxSize->add("small", "small", selectedBoxSize == "small"); miximageBoxSize->add(_("SMALL"), "small", selectedBoxSize == "small");
miximageBoxSize->add("medium", "medium", selectedBoxSize == "medium"); miximageBoxSize->add(_("MEDIUM"), "medium", selectedBoxSize == "medium");
miximageBoxSize->add("large", "large", selectedBoxSize == "large"); miximageBoxSize->add(_("LARGE"), "large", selectedBoxSize == "large");
// If there are no objects returned, then there must be a manually modified entry in the // If there are no objects returned, then there must be a manually modified entry in the
// configuration file. Simply set the box size to "medium" in this case. // configuration file. Simply set the box size to "medium" in this case.
if (miximageBoxSize->getSelectedObjects().size() == 0) if (miximageBoxSize->getSelectedObjects().size() == 0)
miximageBoxSize->selectEntry(0); miximageBoxSize->selectEntry(0);
s->addWithLabel("BOX SIZE", miximageBoxSize); s->addWithLabel(_("BOX SIZE"), miximageBoxSize);
s->addSaveFunc([miximageBoxSize, s] { s->addSaveFunc([miximageBoxSize, s] {
if (miximageBoxSize->getSelected() != if (miximageBoxSize->getSelected() !=
Settings::getInstance()->getString("MiximageBoxSize")) { Settings::getInstance()->getString("MiximageBoxSize")) {
@ -603,17 +610,17 @@ void GuiScraperMenu::openMiximageOptions()
// Physical media size. // Physical media size.
auto miximagePhysicalMediaSize = std::make_shared<OptionListComponent<std::string>>( auto miximagePhysicalMediaSize = std::make_shared<OptionListComponent<std::string>>(
getHelpStyle(), "PHYSICAL MEDIA SIZE", false); getHelpStyle(), _("PHYSICAL MEDIA SIZE"), false);
std::string selectedPhysicalMediaSize { std::string selectedPhysicalMediaSize {
Settings::getInstance()->getString("MiximagePhysicalMediaSize")}; Settings::getInstance()->getString("MiximagePhysicalMediaSize")};
miximagePhysicalMediaSize->add("small", "small", selectedPhysicalMediaSize == "small"); miximagePhysicalMediaSize->add(_("SMALL"), "small", selectedPhysicalMediaSize == "small");
miximagePhysicalMediaSize->add("medium", "medium", selectedPhysicalMediaSize == "medium"); miximagePhysicalMediaSize->add(_("MEDIUM"), "medium", selectedPhysicalMediaSize == "medium");
miximagePhysicalMediaSize->add("large", "large", selectedPhysicalMediaSize == "large"); miximagePhysicalMediaSize->add(_("LARGE"), "large", selectedPhysicalMediaSize == "large");
// If there are no objects returned, then there must be a manually modified entry in the // If there are no objects returned, then there must be a manually modified entry in the
// configuration file. Simply set the physical media size to "medium" in this case. // configuration file. Simply set the physical media size to "medium" in this case.
if (miximagePhysicalMediaSize->getSelectedObjects().size() == 0) if (miximagePhysicalMediaSize->getSelectedObjects().size() == 0)
miximagePhysicalMediaSize->selectEntry(0); miximagePhysicalMediaSize->selectEntry(0);
s->addWithLabel("PHYSICAL MEDIA SIZE", miximagePhysicalMediaSize); s->addWithLabel(_("PHYSICAL MEDIA SIZE"), miximagePhysicalMediaSize);
s->addSaveFunc([miximagePhysicalMediaSize, s] { s->addSaveFunc([miximagePhysicalMediaSize, s] {
if (miximagePhysicalMediaSize->getSelected() != if (miximagePhysicalMediaSize->getSelected() !=
Settings::getInstance()->getString("MiximagePhysicalMediaSize")) { Settings::getInstance()->getString("MiximagePhysicalMediaSize")) {
@ -626,7 +633,7 @@ void GuiScraperMenu::openMiximageOptions()
// Whether to generate miximages when scraping. // Whether to generate miximages when scraping.
auto miximageGenerate = std::make_shared<SwitchComponent>(); auto miximageGenerate = std::make_shared<SwitchComponent>();
miximageGenerate->setState(Settings::getInstance()->getBool("MiximageGenerate")); miximageGenerate->setState(Settings::getInstance()->getBool("MiximageGenerate"));
s->addWithLabel("GENERATE MIXIMAGES WHEN SCRAPING", miximageGenerate); s->addWithLabel(_("GENERATE MIXIMAGES WHEN SCRAPING"), miximageGenerate);
s->addSaveFunc([miximageGenerate, s] { s->addSaveFunc([miximageGenerate, s] {
if (miximageGenerate->getState() != Settings::getInstance()->getBool("MiximageGenerate")) { if (miximageGenerate->getState() != Settings::getInstance()->getBool("MiximageGenerate")) {
Settings::getInstance()->setBool("MiximageGenerate", miximageGenerate->getState()); Settings::getInstance()->setBool("MiximageGenerate", miximageGenerate->getState());
@ -637,7 +644,7 @@ void GuiScraperMenu::openMiximageOptions()
// Whether to overwrite miximages (both for the scraper and offline generator). // Whether to overwrite miximages (both for the scraper and offline generator).
auto miximageOverwrite = std::make_shared<SwitchComponent>(); auto miximageOverwrite = std::make_shared<SwitchComponent>();
miximageOverwrite->setState(Settings::getInstance()->getBool("MiximageOverwrite")); miximageOverwrite->setState(Settings::getInstance()->getBool("MiximageOverwrite"));
s->addWithLabel("OVERWRITE MIXIMAGES (SCRAPER/OFFLINE GENERATOR)", miximageOverwrite); s->addWithLabel(_("OVERWRITE MIXIMAGES (SCRAPER/OFFLINE GENERATOR)"), miximageOverwrite);
s->addSaveFunc([miximageOverwrite, s] { s->addSaveFunc([miximageOverwrite, s] {
if (miximageOverwrite->getState() != if (miximageOverwrite->getState() !=
Settings::getInstance()->getBool("MiximageOverwrite")) { Settings::getInstance()->getBool("MiximageOverwrite")) {
@ -650,7 +657,7 @@ void GuiScraperMenu::openMiximageOptions()
auto miximageRemoveLetterboxes = std::make_shared<SwitchComponent>(); auto miximageRemoveLetterboxes = std::make_shared<SwitchComponent>();
miximageRemoveLetterboxes->setState( miximageRemoveLetterboxes->setState(
Settings::getInstance()->getBool("MiximageRemoveLetterboxes")); Settings::getInstance()->getBool("MiximageRemoveLetterboxes"));
s->addWithLabel("REMOVE LETTERBOXES FROM SCREENSHOTS", miximageRemoveLetterboxes); s->addWithLabel(_("REMOVE LETTERBOXES FROM SCREENSHOTS"), miximageRemoveLetterboxes);
s->addSaveFunc([miximageRemoveLetterboxes, s] { s->addSaveFunc([miximageRemoveLetterboxes, s] {
if (miximageRemoveLetterboxes->getState() != if (miximageRemoveLetterboxes->getState() !=
Settings::getInstance()->getBool("MiximageRemoveLetterboxes")) { Settings::getInstance()->getBool("MiximageRemoveLetterboxes")) {
@ -664,7 +671,7 @@ void GuiScraperMenu::openMiximageOptions()
auto miximageRemovePillarboxes = std::make_shared<SwitchComponent>(); auto miximageRemovePillarboxes = std::make_shared<SwitchComponent>();
miximageRemovePillarboxes->setState( miximageRemovePillarboxes->setState(
Settings::getInstance()->getBool("MiximageRemovePillarboxes")); Settings::getInstance()->getBool("MiximageRemovePillarboxes"));
s->addWithLabel("REMOVE PILLARBOXES FROM SCREENSHOTS", miximageRemovePillarboxes); s->addWithLabel(_("REMOVE PILLARBOXES FROM SCREENSHOTS"), miximageRemovePillarboxes);
s->addSaveFunc([miximageRemovePillarboxes, s] { s->addSaveFunc([miximageRemovePillarboxes, s] {
if (miximageRemovePillarboxes->getState() != if (miximageRemovePillarboxes->getState() !=
Settings::getInstance()->getBool("MiximageRemovePillarboxes")) { Settings::getInstance()->getBool("MiximageRemovePillarboxes")) {
@ -678,7 +685,7 @@ void GuiScraperMenu::openMiximageOptions()
auto miximageRotateBoxes = std::make_shared<SwitchComponent>(); auto miximageRotateBoxes = std::make_shared<SwitchComponent>();
miximageRotateBoxes->setState( miximageRotateBoxes->setState(
Settings::getInstance()->getBool("MiximageRotateHorizontalBoxes")); Settings::getInstance()->getBool("MiximageRotateHorizontalBoxes"));
s->addWithLabel("ROTATE HORIZONTALLY ORIENTED BOXES", miximageRotateBoxes); s->addWithLabel(_("ROTATE HORIZONTALLY ORIENTED BOXES"), miximageRotateBoxes);
s->addSaveFunc([miximageRotateBoxes, s] { s->addSaveFunc([miximageRotateBoxes, s] {
if (miximageRotateBoxes->getState() != if (miximageRotateBoxes->getState() !=
Settings::getInstance()->getBool("MiximageRotateHorizontalBoxes")) { Settings::getInstance()->getBool("MiximageRotateHorizontalBoxes")) {
@ -691,7 +698,7 @@ void GuiScraperMenu::openMiximageOptions()
// Whether to include marquee images. // Whether to include marquee images.
auto miximageIncludeMarquee = std::make_shared<SwitchComponent>(); auto miximageIncludeMarquee = std::make_shared<SwitchComponent>();
miximageIncludeMarquee->setState(Settings::getInstance()->getBool("MiximageIncludeMarquee")); miximageIncludeMarquee->setState(Settings::getInstance()->getBool("MiximageIncludeMarquee"));
s->addWithLabel("INCLUDE MARQUEE IMAGE", miximageIncludeMarquee); s->addWithLabel(_("INCLUDE MARQUEE IMAGE"), miximageIncludeMarquee);
s->addSaveFunc([miximageIncludeMarquee, s] { s->addSaveFunc([miximageIncludeMarquee, s] {
if (miximageIncludeMarquee->getState() != if (miximageIncludeMarquee->getState() !=
Settings::getInstance()->getBool("MiximageIncludeMarquee")) { Settings::getInstance()->getBool("MiximageIncludeMarquee")) {
@ -704,7 +711,7 @@ void GuiScraperMenu::openMiximageOptions()
// Whether to include box images. // Whether to include box images.
auto miximageIncludeBox = std::make_shared<SwitchComponent>(); auto miximageIncludeBox = std::make_shared<SwitchComponent>();
miximageIncludeBox->setState(Settings::getInstance()->getBool("MiximageIncludeBox")); miximageIncludeBox->setState(Settings::getInstance()->getBool("MiximageIncludeBox"));
s->addWithLabel("INCLUDE BOX IMAGE", miximageIncludeBox); s->addWithLabel(_("INCLUDE BOX IMAGE"), miximageIncludeBox);
s->addSaveFunc([miximageIncludeBox, s] { s->addSaveFunc([miximageIncludeBox, s] {
if (miximageIncludeBox->getState() != if (miximageIncludeBox->getState() !=
Settings::getInstance()->getBool("MiximageIncludeBox")) { Settings::getInstance()->getBool("MiximageIncludeBox")) {
@ -716,7 +723,7 @@ void GuiScraperMenu::openMiximageOptions()
// Whether to use cover image if there is no 3D box image. // Whether to use cover image if there is no 3D box image.
auto miximageCoverFallback = std::make_shared<SwitchComponent>(); auto miximageCoverFallback = std::make_shared<SwitchComponent>();
miximageCoverFallback->setState(Settings::getInstance()->getBool("MiximageCoverFallback")); miximageCoverFallback->setState(Settings::getInstance()->getBool("MiximageCoverFallback"));
s->addWithLabel("USE COVER IMAGE IF 3D BOX IS MISSING", miximageCoverFallback); s->addWithLabel(_("USE COVER IMAGE IF 3D BOX IS MISSING"), miximageCoverFallback);
s->addSaveFunc([miximageCoverFallback, s] { s->addSaveFunc([miximageCoverFallback, s] {
if (miximageCoverFallback->getState() != if (miximageCoverFallback->getState() !=
Settings::getInstance()->getBool("MiximageCoverFallback")) { Settings::getInstance()->getBool("MiximageCoverFallback")) {
@ -730,7 +737,7 @@ void GuiScraperMenu::openMiximageOptions()
auto miximageIncludePhysicalMedia = std::make_shared<SwitchComponent>(); auto miximageIncludePhysicalMedia = std::make_shared<SwitchComponent>();
miximageIncludePhysicalMedia->setState( miximageIncludePhysicalMedia->setState(
Settings::getInstance()->getBool("MiximageIncludePhysicalMedia")); Settings::getInstance()->getBool("MiximageIncludePhysicalMedia"));
s->addWithLabel("INCLUDE PHYSICAL MEDIA IMAGE", miximageIncludePhysicalMedia); s->addWithLabel(_("INCLUDE PHYSICAL MEDIA IMAGE"), miximageIncludePhysicalMedia);
s->addSaveFunc([miximageIncludePhysicalMedia, s] { s->addSaveFunc([miximageIncludePhysicalMedia, s] {
if (miximageIncludePhysicalMedia->getState() != if (miximageIncludePhysicalMedia->getState() !=
Settings::getInstance()->getBool("MiximageIncludePhysicalMedia")) { Settings::getInstance()->getBool("MiximageIncludePhysicalMedia")) {
@ -743,7 +750,7 @@ void GuiScraperMenu::openMiximageOptions()
// Miximage offline generator. // Miximage offline generator.
ComponentListRow offlineGeneratorRow; ComponentListRow offlineGeneratorRow;
offlineGeneratorRow.elements.clear(); offlineGeneratorRow.elements.clear();
offlineGeneratorRow.addElement(std::make_shared<TextComponent>("OFFLINE GENERATOR", offlineGeneratorRow.addElement(std::make_shared<TextComponent>(_("OFFLINE GENERATOR"),
Font::get(FONT_SIZE_MEDIUM), Font::get(FONT_SIZE_MEDIUM),
mMenuColorPrimary), mMenuColorPrimary),
true); true);
@ -759,9 +766,15 @@ void GuiScraperMenu::openOfflineGenerator(GuiSettings* settings)
{ {
if (mSystems->getSelectedObjects().empty()) { if (mSystems->getSelectedObjects().empty()) {
mWindow->pushGui(new GuiMsgBox(getHelpStyle(), mWindow->pushGui(new GuiMsgBox(getHelpStyle(),
"THE OFFLINE GENERATOR USES THE SAME SYSTEM\n" _("THE OFFLINE GENERATOR USES THE SAME SYSTEM "
"SELECTIONS AS THE SCRAPER, SO PLEASE SELECT\n" "SELECTIONS AS THE SCRAPER, SO PLEASE SELECT "
"AT LEAST ONE SYSTEM TO GENERATE IMAGES FOR")); "AT LEAST ONE SYSTEM TO GENERATE IMAGES FOR"),
_("OK"), nullptr, "", nullptr, "", nullptr, nullptr, false,
true,
(mRenderer->getIsVerticalOrientation() ?
0.80f :
0.50f * (1.778f / mRenderer->getScreenAspectRatio()))));
return; return;
} }
@ -791,23 +804,23 @@ void GuiScraperMenu::openOfflineGenerator(GuiSettings* settings)
void GuiScraperMenu::openOtherOptions() void GuiScraperMenu::openOtherOptions()
{ {
auto s = new GuiSettings("OTHER SETTINGS"); auto s = new GuiSettings(_("OTHER SETTINGS"));
// Scraper region. // Scraper region.
auto scraperRegion = auto scraperRegion =
std::make_shared<OptionListComponent<std::string>>(getHelpStyle(), "REGION", false); std::make_shared<OptionListComponent<std::string>>(getHelpStyle(), _("REGION"), false);
std::string selectedScraperRegion {Settings::getInstance()->getString("ScraperRegion")}; std::string selectedScraperRegion {Settings::getInstance()->getString("ScraperRegion")};
// clang-format off // clang-format off
scraperRegion->add("Europe", "eu", selectedScraperRegion == "eu"); scraperRegion->add(_("EUROPE"), "eu", selectedScraperRegion == "eu");
scraperRegion->add("Japan", "jp", selectedScraperRegion == "jp"); scraperRegion->add(_("JAPAN"), "jp", selectedScraperRegion == "jp");
scraperRegion->add("USA", "us", selectedScraperRegion == "us"); scraperRegion->add(_("USA"), "us", selectedScraperRegion == "us");
scraperRegion->add("World", "wor", selectedScraperRegion == "wor"); scraperRegion->add(_("WORLD"), "wor", selectedScraperRegion == "wor");
// clang-format on // clang-format on
// If there are no objects returned, then there must be a manually modified entry in the // If there are no objects returned, then there must be a manually modified entry in the
// configuration file. Simply set the region to "Europe" in this case. // configuration file. Simply set the region to "Europe" in this case.
if (scraperRegion->getSelectedObjects().size() == 0) if (scraperRegion->getSelectedObjects().size() == 0)
scraperRegion->selectEntry(0); scraperRegion->selectEntry(0);
s->addWithLabel("REGION", scraperRegion); s->addWithLabel(_("REGION"), scraperRegion);
s->addSaveFunc([scraperRegion, s] { s->addSaveFunc([scraperRegion, s] {
if (scraperRegion->getSelected() != Settings::getInstance()->getString("ScraperRegion")) { if (scraperRegion->getSelected() != Settings::getInstance()->getString("ScraperRegion")) {
Settings::getInstance()->setString("ScraperRegion", scraperRegion->getSelected()); Settings::getInstance()->setString("ScraperRegion", scraperRegion->getSelected());
@ -826,35 +839,35 @@ void GuiScraperMenu::openOtherOptions()
// Scraper language. // Scraper language.
auto scraperLanguage = std::make_shared<OptionListComponent<std::string>>( auto scraperLanguage = std::make_shared<OptionListComponent<std::string>>(
getHelpStyle(), "PREFERRED LANGUAGE", false); getHelpStyle(), _("PREFERRED LANGUAGE"), false);
std::string selectedScraperLanguage {Settings::getInstance()->getString("ScraperLanguage")}; std::string selectedScraperLanguage {Settings::getInstance()->getString("ScraperLanguage")};
// clang-format off // clang-format off
scraperLanguage->add("English", "en", selectedScraperLanguage == "en"); scraperLanguage->add("ENGLISH", "en", selectedScraperLanguage == "en");
scraperLanguage->add("Español", "es", selectedScraperLanguage == "es"); scraperLanguage->add("ČEŠTINA", "cz", selectedScraperLanguage == "cz");
scraperLanguage->add("Português", "pt", selectedScraperLanguage == "pt"); scraperLanguage->add("DANSK", "da", selectedScraperLanguage == "da");
scraperLanguage->add("Français", "fr", selectedScraperLanguage == "fr"); scraperLanguage->add("DEUTSCH", "de", selectedScraperLanguage == "de");
scraperLanguage->add("Deutsch", "de", selectedScraperLanguage == "de"); scraperLanguage->add("ESPAÑOL", "es", selectedScraperLanguage == "es");
scraperLanguage->add("Italiano", "it", selectedScraperLanguage == "it"); scraperLanguage->add("FRANÇAIS", "fr", selectedScraperLanguage == "fr");
scraperLanguage->add("Nederlands", "nl", selectedScraperLanguage == "nl"); scraperLanguage->add("ITALIANO", "it", selectedScraperLanguage == "it");
scraperLanguage->add("MAGYAR", "hu", selectedScraperLanguage == "hu");
scraperLanguage->add("NEDERLANDS", "nl", selectedScraperLanguage == "nl");
scraperLanguage->add("NORSK", "no", selectedScraperLanguage == "no");
scraperLanguage->add("POLSKI", "pl", selectedScraperLanguage == "pl");
scraperLanguage->add("PORTUGUÊS", "pt", selectedScraperLanguage == "pt");
scraperLanguage->add("РУССКИЙ", "ru", selectedScraperLanguage == "ru");
scraperLanguage->add("SLOVENČINA", "sk", selectedScraperLanguage == "sk");
scraperLanguage->add("SUOMI", "fi", selectedScraperLanguage == "fi");
scraperLanguage->add("SVENSKA", "sv", selectedScraperLanguage == "sv");
scraperLanguage->add("TÜRKÇE", "tr", selectedScraperLanguage == "tr");
scraperLanguage->add("日本語", "ja", selectedScraperLanguage == "ja"); scraperLanguage->add("日本語", "ja", selectedScraperLanguage == "ja");
scraperLanguage->add("简体中文", "zh", selectedScraperLanguage == "zh");
scraperLanguage->add("한국어", "ko", selectedScraperLanguage == "ko"); scraperLanguage->add("한국어", "ko", selectedScraperLanguage == "ko");
scraperLanguage->add("Русский", "ru", selectedScraperLanguage == "ru"); scraperLanguage->add("简体中文", "zh", selectedScraperLanguage == "zh");
scraperLanguage->add("Dansk", "da", selectedScraperLanguage == "da");
scraperLanguage->add("Suomi", "fi", selectedScraperLanguage == "fi");
scraperLanguage->add("Svenska", "sv", selectedScraperLanguage == "sv");
scraperLanguage->add("Magyar", "hu", selectedScraperLanguage == "hu");
scraperLanguage->add("Norsk", "no", selectedScraperLanguage == "no");
scraperLanguage->add("Polski", "pl", selectedScraperLanguage == "pl");
scraperLanguage->add("Čeština", "cz", selectedScraperLanguage == "cz");
scraperLanguage->add("Slovenčina", "sk", selectedScraperLanguage == "sk");
scraperLanguage->add("Türkçe", "tr", selectedScraperLanguage == "tr");
// clang-format on // clang-format on
// If there are no objects returned, then there must be a manually modified entry in the // If there are no objects returned, then there must be a manually modified entry in the
// configuration file. Simply set the language to "English" in this case. // configuration file. Simply set the language to "English" in this case.
if (scraperLanguage->getSelectedObjects().size() == 0) if (scraperLanguage->getSelectedObjects().size() == 0)
scraperLanguage->selectEntry(0); scraperLanguage->selectEntry(0);
s->addWithLabel("PREFERRED LANGUAGE", scraperLanguage); s->addWithLabel(_("PREFERRED LANGUAGE"), scraperLanguage);
s->addSaveFunc([scraperLanguage, s] { s->addSaveFunc([scraperLanguage, s] {
if (scraperLanguage->getSelected() != if (scraperLanguage->getSelected() !=
Settings::getInstance()->getString("ScraperLanguage")) { Settings::getInstance()->getString("ScraperLanguage")) {
@ -877,7 +890,7 @@ void GuiScraperMenu::openOtherOptions()
mScraperRetryOnErrorCount = std::make_shared<SliderComponent>(0.0f, 10.0f, 1.0f); mScraperRetryOnErrorCount = std::make_shared<SliderComponent>(0.0f, 10.0f, 1.0f);
mScraperRetryOnErrorCount->setValue( mScraperRetryOnErrorCount->setValue(
static_cast<float>(Settings::getInstance()->getInt("ScraperRetryOnErrorCount"))); static_cast<float>(Settings::getInstance()->getInt("ScraperRetryOnErrorCount")));
s->addWithLabel("AUTOMATIC RETRIES ON ERROR", mScraperRetryOnErrorCount); s->addWithLabel(_("AUTOMATIC RETRIES ON ERROR"), mScraperRetryOnErrorCount);
s->addSaveFunc([this, s] { s->addSaveFunc([this, s] {
if (mScraperRetryOnErrorCount->getValue() != if (mScraperRetryOnErrorCount->getValue() !=
static_cast<float>(Settings::getInstance()->getInt("ScraperRetryOnErrorCount"))) { static_cast<float>(Settings::getInstance()->getInt("ScraperRetryOnErrorCount"))) {
@ -892,7 +905,7 @@ void GuiScraperMenu::openOtherOptions()
auto scraperRetryOnErrorTimer = std::make_shared<SliderComponent>(1.0f, 30.0f, 1.0f, "s"); auto scraperRetryOnErrorTimer = std::make_shared<SliderComponent>(1.0f, 30.0f, 1.0f, "s");
scraperRetryOnErrorTimer->setValue( scraperRetryOnErrorTimer->setValue(
static_cast<float>(Settings::getInstance()->getInt("ScraperRetryOnErrorTimer"))); static_cast<float>(Settings::getInstance()->getInt("ScraperRetryOnErrorTimer")));
s->addWithLabel("RETRY ATTEMPT TIMER", scraperRetryOnErrorTimer); s->addWithLabel(_("RETRY ATTEMPT TIMER"), scraperRetryOnErrorTimer);
s->addSaveFunc([scraperRetryOnErrorTimer, s] { s->addSaveFunc([scraperRetryOnErrorTimer, s] {
if (scraperRetryOnErrorTimer->getValue() != if (scraperRetryOnErrorTimer->getValue() !=
static_cast<float>(Settings::getInstance()->getInt("ScraperRetryOnErrorTimer"))) { static_cast<float>(Settings::getInstance()->getInt("ScraperRetryOnErrorTimer"))) {
@ -915,7 +928,7 @@ void GuiScraperMenu::openOtherOptions()
std::make_shared<SliderComponent>(32.0f, 800.0f, 32.0f, "MiB"); std::make_shared<SliderComponent>(32.0f, 800.0f, 32.0f, "MiB");
scraperSearchFileHashMaxSize->setValue( scraperSearchFileHashMaxSize->setValue(
static_cast<float>(Settings::getInstance()->getInt("ScraperSearchFileHashMaxSize"))); static_cast<float>(Settings::getInstance()->getInt("ScraperSearchFileHashMaxSize")));
s->addWithLabel("HASH SEARCHES MAX FILE SIZE", scraperSearchFileHashMaxSize); s->addWithLabel(_("HASH SEARCHES MAX FILE SIZE"), scraperSearchFileHashMaxSize);
s->addSaveFunc([scraperSearchFileHashMaxSize, s] { s->addSaveFunc([scraperSearchFileHashMaxSize, s] {
if (scraperSearchFileHashMaxSize->getValue() != if (scraperSearchFileHashMaxSize->getValue() !=
static_cast<float>(Settings::getInstance()->getInt("ScraperSearchFileHashMaxSize"))) { static_cast<float>(Settings::getInstance()->getInt("ScraperSearchFileHashMaxSize"))) {
@ -940,7 +953,7 @@ void GuiScraperMenu::openOtherOptions()
// Overwrite files and data. // Overwrite files and data.
auto scraperOverwriteData = std::make_shared<SwitchComponent>(); auto scraperOverwriteData = std::make_shared<SwitchComponent>();
scraperOverwriteData->setState(Settings::getInstance()->getBool("ScraperOverwriteData")); scraperOverwriteData->setState(Settings::getInstance()->getBool("ScraperOverwriteData"));
s->addWithLabel("OVERWRITE FILES AND DATA", scraperOverwriteData); s->addWithLabel(_("OVERWRITE FILES AND DATA"), scraperOverwriteData);
s->addSaveFunc([scraperOverwriteData, s] { s->addSaveFunc([scraperOverwriteData, s] {
if (scraperOverwriteData->getState() != if (scraperOverwriteData->getState() !=
Settings::getInstance()->getBool("ScraperOverwriteData")) { Settings::getInstance()->getBool("ScraperOverwriteData")) {
@ -953,7 +966,7 @@ void GuiScraperMenu::openOtherOptions()
// Search using file hashes for non-interactive mode. // Search using file hashes for non-interactive mode.
auto scraperSearchFileHash = std::make_shared<SwitchComponent>(); auto scraperSearchFileHash = std::make_shared<SwitchComponent>();
scraperSearchFileHash->setState(Settings::getInstance()->getBool("ScraperSearchFileHash")); scraperSearchFileHash->setState(Settings::getInstance()->getBool("ScraperSearchFileHash"));
s->addWithLabel("SEARCH USING FILE HASHES (NON-INTERACTIVE MODE)", scraperSearchFileHash); s->addWithLabel(_("SEARCH USING FILE HASHES (NON-INTERACTIVE MODE)"), scraperSearchFileHash);
s->addSaveFunc([scraperSearchFileHash, s] { s->addSaveFunc([scraperSearchFileHash, s] {
if (scraperSearchFileHash->getState() != if (scraperSearchFileHash->getState() !=
Settings::getInstance()->getBool("ScraperSearchFileHash")) { Settings::getInstance()->getBool("ScraperSearchFileHash")) {
@ -977,7 +990,7 @@ void GuiScraperMenu::openOtherOptions()
auto scraperSearchMetadataName = std::make_shared<SwitchComponent>(); auto scraperSearchMetadataName = std::make_shared<SwitchComponent>();
scraperSearchMetadataName->setState( scraperSearchMetadataName->setState(
Settings::getInstance()->getBool("ScraperSearchMetadataName")); Settings::getInstance()->getBool("ScraperSearchMetadataName"));
s->addWithLabel("SEARCH USING METADATA NAMES", scraperSearchMetadataName); s->addWithLabel(_("SEARCH USING METADATA NAMES"), scraperSearchMetadataName);
s->addSaveFunc([scraperSearchMetadataName, s] { s->addSaveFunc([scraperSearchMetadataName, s] {
if (scraperSearchMetadataName->getState() != if (scraperSearchMetadataName->getState() !=
Settings::getInstance()->getBool("ScraperSearchMetadataName")) { Settings::getInstance()->getBool("ScraperSearchMetadataName")) {
@ -990,7 +1003,7 @@ void GuiScraperMenu::openOtherOptions()
// Include actual folders when scraping. // Include actual folders when scraping.
auto scraperIncludeFolders = std::make_shared<SwitchComponent>(); auto scraperIncludeFolders = std::make_shared<SwitchComponent>();
scraperIncludeFolders->setState(Settings::getInstance()->getBool("ScraperIncludeFolders")); scraperIncludeFolders->setState(Settings::getInstance()->getBool("ScraperIncludeFolders"));
s->addWithLabel("SCRAPE ACTUAL FOLDERS", scraperIncludeFolders); s->addWithLabel(_("SCRAPE ACTUAL FOLDERS"), scraperIncludeFolders);
s->addSaveFunc([scraperIncludeFolders, s] { s->addSaveFunc([scraperIncludeFolders, s] {
if (scraperIncludeFolders->getState() != if (scraperIncludeFolders->getState() !=
Settings::getInstance()->getBool("ScraperIncludeFolders")) { Settings::getInstance()->getBool("ScraperIncludeFolders")) {
@ -1003,7 +1016,7 @@ void GuiScraperMenu::openOtherOptions()
// Interactive scraping. // Interactive scraping.
auto scraperInteractive = std::make_shared<SwitchComponent>(); auto scraperInteractive = std::make_shared<SwitchComponent>();
scraperInteractive->setState(Settings::getInstance()->getBool("ScraperInteractive")); scraperInteractive->setState(Settings::getInstance()->getBool("ScraperInteractive"));
s->addWithLabel("INTERACTIVE MODE", scraperInteractive); s->addWithLabel(_("INTERACTIVE MODE"), scraperInteractive);
s->addSaveFunc([scraperInteractive, s] { s->addSaveFunc([scraperInteractive, s] {
if (scraperInteractive->getState() != if (scraperInteractive->getState() !=
Settings::getInstance()->getBool("ScraperInteractive")) { Settings::getInstance()->getBool("ScraperInteractive")) {
@ -1015,7 +1028,7 @@ void GuiScraperMenu::openOtherOptions()
// Semi-automatic scraping. // Semi-automatic scraping.
auto scraperSemiautomatic = std::make_shared<SwitchComponent>(); auto scraperSemiautomatic = std::make_shared<SwitchComponent>();
scraperSemiautomatic->setState(Settings::getInstance()->getBool("ScraperSemiautomatic")); scraperSemiautomatic->setState(Settings::getInstance()->getBool("ScraperSemiautomatic"));
s->addWithLabel("AUTO-ACCEPT SINGLE GAME MATCHES", scraperSemiautomatic); s->addWithLabel(_("AUTO-ACCEPT SINGLE GAME MATCHES"), scraperSemiautomatic);
s->addSaveFunc([scraperSemiautomatic, s] { s->addSaveFunc([scraperSemiautomatic, s] {
if (scraperSemiautomatic->getState() != if (scraperSemiautomatic->getState() !=
Settings::getInstance()->getBool("ScraperSemiautomatic")) { Settings::getInstance()->getBool("ScraperSemiautomatic")) {
@ -1038,7 +1051,7 @@ void GuiScraperMenu::openOtherOptions()
auto scraperRespectExclusions = std::make_shared<SwitchComponent>(); auto scraperRespectExclusions = std::make_shared<SwitchComponent>();
scraperRespectExclusions->setState( scraperRespectExclusions->setState(
Settings::getInstance()->getBool("ScraperRespectExclusions")); Settings::getInstance()->getBool("ScraperRespectExclusions"));
s->addWithLabel("RESPECT PER-FILE SCRAPER EXCLUSIONS", scraperRespectExclusions); s->addWithLabel(_("RESPECT PER-FILE SCRAPER EXCLUSIONS"), scraperRespectExclusions);
s->addSaveFunc([scraperRespectExclusions, s] { s->addSaveFunc([scraperRespectExclusions, s] {
if (scraperRespectExclusions->getState() != if (scraperRespectExclusions->getState() !=
Settings::getInstance()->getBool("ScraperRespectExclusions")) { Settings::getInstance()->getBool("ScraperRespectExclusions")) {
@ -1052,7 +1065,7 @@ void GuiScraperMenu::openOtherOptions()
auto scraperExcludeRecursively = std::make_shared<SwitchComponent>(); auto scraperExcludeRecursively = std::make_shared<SwitchComponent>();
scraperExcludeRecursively->setState( scraperExcludeRecursively->setState(
Settings::getInstance()->getBool("ScraperExcludeRecursively")); Settings::getInstance()->getBool("ScraperExcludeRecursively"));
s->addWithLabel("EXCLUDE FOLDERS RECURSIVELY", scraperExcludeRecursively); s->addWithLabel(_("EXCLUDE FOLDERS RECURSIVELY"), scraperExcludeRecursively);
s->addSaveFunc([scraperExcludeRecursively, s] { s->addSaveFunc([scraperExcludeRecursively, s] {
if (scraperExcludeRecursively->getState() != if (scraperExcludeRecursively->getState() !=
Settings::getInstance()->getBool("ScraperExcludeRecursively")) { Settings::getInstance()->getBool("ScraperExcludeRecursively")) {
@ -1075,7 +1088,7 @@ void GuiScraperMenu::openOtherOptions()
auto scraperConvertUnderscores = std::make_shared<SwitchComponent>(); auto scraperConvertUnderscores = std::make_shared<SwitchComponent>();
scraperConvertUnderscores->setState( scraperConvertUnderscores->setState(
Settings::getInstance()->getBool("ScraperConvertUnderscores")); Settings::getInstance()->getBool("ScraperConvertUnderscores"));
s->addWithLabel("CONVERT UNDERSCORES TO SPACES WHEN SEARCHING", scraperConvertUnderscores); s->addWithLabel(_("CONVERT UNDERSCORES TO SPACES WHEN SEARCHING"), scraperConvertUnderscores);
s->addSaveFunc([scraperConvertUnderscores, s] { s->addSaveFunc([scraperConvertUnderscores, s] {
if (scraperConvertUnderscores->getState() != if (scraperConvertUnderscores->getState() !=
Settings::getInstance()->getBool("ScraperConvertUnderscores")) { Settings::getInstance()->getBool("ScraperConvertUnderscores")) {
@ -1089,7 +1102,7 @@ void GuiScraperMenu::openOtherOptions()
auto scraperAutomaticRemoveDots = std::make_shared<SwitchComponent>(); auto scraperAutomaticRemoveDots = std::make_shared<SwitchComponent>();
scraperAutomaticRemoveDots->setState( scraperAutomaticRemoveDots->setState(
Settings::getInstance()->getBool("ScraperAutomaticRemoveDots")); Settings::getInstance()->getBool("ScraperAutomaticRemoveDots"));
s->addWithLabel("REMOVE DOTS FROM SEARCHES WHEN AUTO-SCRAPING", scraperAutomaticRemoveDots); s->addWithLabel(_("REMOVE DOTS FROM SEARCHES WHEN AUTO-SCRAPING"), scraperAutomaticRemoveDots);
s->addSaveFunc([scraperAutomaticRemoveDots, s] { s->addSaveFunc([scraperAutomaticRemoveDots, s] {
if (scraperAutomaticRemoveDots->getState() != if (scraperAutomaticRemoveDots->getState() !=
Settings::getInstance()->getBool("ScraperAutomaticRemoveDots")) { Settings::getInstance()->getBool("ScraperAutomaticRemoveDots")) {
@ -1111,7 +1124,7 @@ void GuiScraperMenu::openOtherOptions()
// Whether to fallback to additional regions. // Whether to fallback to additional regions.
auto scraperRegionFallback = std::make_shared<SwitchComponent>(mWindow); auto scraperRegionFallback = std::make_shared<SwitchComponent>(mWindow);
scraperRegionFallback->setState(Settings::getInstance()->getBool("ScraperRegionFallback")); scraperRegionFallback->setState(Settings::getInstance()->getBool("ScraperRegionFallback"));
s->addWithLabel("ENABLE FALLBACK TO ADDITIONAL REGIONS", scraperRegionFallback); s->addWithLabel(_("ENABLE FALLBACK TO ADDITIONAL REGIONS"), scraperRegionFallback);
s->addSaveFunc([scraperRegionFallback, s] { s->addSaveFunc([scraperRegionFallback, s] {
if (scraperRegionFallback->getState() != if (scraperRegionFallback->getState() !=
Settings::getInstance()->getBool("ScraperRegionFallback")) { Settings::getInstance()->getBool("ScraperRegionFallback")) {
@ -1220,16 +1233,17 @@ void GuiScraperMenu::pressedStart()
if ((*it)->getPlatformIds().empty()) { if ((*it)->getPlatformIds().empty()) {
std::string warningString; std::string warningString;
if (sys.size() == 1) { if (sys.size() == 1) {
warningString = "THE SELECTED SYSTEM DOES NOT HAVE A PLATFORM SET, RESULTS MAY BE " warningString =
"INACCURATE"; _("THE SELECTED SYSTEM DOES NOT HAVE A PLATFORM SET, RESULTS MAY BE "
"INACCURATE");
} }
else { else {
warningString = "AT LEAST ONE OF YOUR SELECTED SYSTEMS DOES NOT HAVE A PLATFORM " warningString = _("AT LEAST ONE OF YOUR SELECTED SYSTEMS DOES NOT HAVE A PLATFORM "
"SET, RESULTS MAY BE INACCURATE"; "SET, RESULTS MAY BE INACCURATE");
} }
mWindow->pushGui( mWindow->pushGui(
new GuiMsgBox(getHelpStyle(), Utils::String::toUpper(warningString), "PROCEED", new GuiMsgBox(getHelpStyle(), Utils::String::toUpper(warningString), _("PROCEED"),
std::bind(&GuiScraperMenu::start, this), "CANCEL", nullptr, "", std::bind(&GuiScraperMenu::start, this), _("CANCEL"), nullptr, "",
nullptr, nullptr, false, true, nullptr, nullptr, false, true,
(mRenderer->getIsVerticalOrientation() ? (mRenderer->getIsVerticalOrientation() ?
0.80f : 0.80f :
@ -1244,7 +1258,7 @@ void GuiScraperMenu::start()
{ {
if (mSystems->getSelectedObjects().empty()) { if (mSystems->getSelectedObjects().empty()) {
mWindow->pushGui( mWindow->pushGui(
new GuiMsgBox(getHelpStyle(), "PLEASE SELECT AT LEAST ONE SYSTEM TO SCRAPE")); new GuiMsgBox(getHelpStyle(), _("PLEASE SELECT AT LEAST ONE SYSTEM TO SCRAPE")));
return; return;
} }
@ -1319,7 +1333,7 @@ void GuiScraperMenu::start()
if (!contentToScrape) { if (!contentToScrape) {
mWindow->pushGui( mWindow->pushGui(
new GuiMsgBox(getHelpStyle(), "PLEASE SELECT AT LEAST ONE CONTENT TYPE TO SCRAPE")); new GuiMsgBox(getHelpStyle(), _("PLEASE SELECT AT LEAST ONE CONTENT TYPE TO SCRAPE")));
return; return;
} }
@ -1327,7 +1341,7 @@ void GuiScraperMenu::start()
if (searches.first.empty()) { if (searches.first.empty()) {
mWindow->pushGui( mWindow->pushGui(
new GuiMsgBox(getHelpStyle(), "ALL GAMES WERE FILTERED, NOTHING TO SCRAPE")); new GuiMsgBox(getHelpStyle(), _("ALL GAMES WERE FILTERED, NOTHING TO SCRAPE")));
} }
else { else {
GuiScraperMulti* gsm { GuiScraperMulti* gsm {
@ -1402,7 +1416,7 @@ bool GuiScraperMenu::input(InputConfig* config, Input input)
std::vector<HelpPrompt> GuiScraperMenu::getHelpPrompts() std::vector<HelpPrompt> GuiScraperMenu::getHelpPrompts()
{ {
std::vector<HelpPrompt> prompts {mMenu.getHelpPrompts()}; std::vector<HelpPrompt> prompts {mMenu.getHelpPrompts()};
prompts.push_back(HelpPrompt("b", "back")); prompts.push_back(HelpPrompt("b", _("back")));
prompts.push_back(HelpPrompt("y", "start scraper")); prompts.push_back(HelpPrompt("y", _("start scraper")));
return prompts; return prompts;
} }

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// //
// ES-DE // ES-DE Frontend
// GuiScraperMenu.h // GuiScraperMenu.h
// //
// Game media scraper, including settings as well as the scraping start button. // Game media scraper, including settings as well as the scraping start button.

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// //
// ES-DE // ES-DE Frontend
// GuiScraperMulti.cpp // GuiScraperMulti.cpp
// //
// Multiple game scraping user interface. // Multiple game scraping user interface.
@ -22,6 +22,7 @@
#include "components/TextComponent.h" #include "components/TextComponent.h"
#include "guis/GuiMsgBox.h" #include "guis/GuiMsgBox.h"
#include "guis/GuiScraperSearch.h" #include "guis/GuiScraperSearch.h"
#include "utils/LocalizationUtil.h"
GuiScraperMulti::GuiScraperMulti( GuiScraperMulti::GuiScraperMulti(
const std::pair<std::queue<ScraperSearchParams>, std::map<SystemData*, int>>& searches, const std::pair<std::queue<ScraperSearchParams>, std::map<SystemData*, int>>& searches,
@ -48,11 +49,13 @@ GuiScraperMulti::GuiScraperMulti(
mQueueCountPerSystem[(*it).first] = std::make_pair(0, (*it).second); mQueueCountPerSystem[(*it).first] = std::make_pair(0, (*it).second);
// Set up grid. // Set up grid.
mTitle = std::make_shared<TextComponent>("SCRAPING IN PROGRESS", Font::get(FONT_SIZE_LARGE), mTitle = std::make_shared<TextComponent>(
mMenuColorTitle, ALIGN_CENTER); _("SCRAPING IN PROGRESS"),
Font::get(FONT_SIZE_LARGE * Utils::Localization::sMenuTitleScaleFactor), mMenuColorTitle,
ALIGN_CENTER);
mGrid.setEntry(mTitle, glm::ivec2 {0, 0}, false, true, glm::ivec2 {2, 2}); mGrid.setEntry(mTitle, glm::ivec2 {0, 0}, false, true, glm::ivec2 {2, 2});
mSystem = std::make_shared<TextComponent>("SYSTEM", Font::get(FONT_SIZE_MEDIUM), mSystem = std::make_shared<TextComponent>(_("SYSTEM"), Font::get(FONT_SIZE_MEDIUM),
mMenuColorPrimary, ALIGN_CENTER); mMenuColorPrimary, ALIGN_CENTER);
mGrid.setEntry(mSystem, glm::ivec2 {0, 2}, false, true, glm::ivec2 {2, 1}); mGrid.setEntry(mSystem, glm::ivec2 {0, 2}, false, true, glm::ivec2 {2, 1});
@ -105,38 +108,39 @@ GuiScraperMulti::GuiScraperMulti(
std::vector<std::shared_ptr<ButtonComponent>> buttons; std::vector<std::shared_ptr<ButtonComponent>> buttons;
if (mApproveResults) { if (mApproveResults) {
buttons.push_back(std::make_shared<ButtonComponent>("REFINE SEARCH", "refine search", [&] { buttons.push_back(
// Check whether we should allow a refine of the game name. std::make_shared<ButtonComponent>(_("REFINE SEARCH"), _("refine search"), [&] {
if (!mSearchComp->getAcceptedResult()) { // Check whether we should allow a refine of the game name.
bool allowRefine = false; if (!mSearchComp->getAcceptedResult()) {
bool allowRefine = false;
// Previously refined. // Previously refined.
if (mSearchComp->getRefinedSearch()) if (mSearchComp->getRefinedSearch())
allowRefine = true; allowRefine = true;
// Interactive mode and "Auto-accept single game matches" not enabled. // Interactive mode and "Auto-accept single game matches" not enabled.
else if (mSearchComp->getSearchType() != GuiScraperSearch::SEMIAUTOMATIC_MODE) else if (mSearchComp->getSearchType() != GuiScraperSearch::SEMIAUTOMATIC_MODE)
allowRefine = true; allowRefine = true;
// Interactive mode with "Auto-accept single game matches" enabled and more // Interactive mode with "Auto-accept single game matches" enabled and more
// than one result. // than one result.
else if (mSearchComp->getSearchType() == GuiScraperSearch::SEMIAUTOMATIC_MODE && else if (mSearchComp->getSearchType() == GuiScraperSearch::SEMIAUTOMATIC_MODE &&
mSearchComp->getScraperResultsSize() > 1) mSearchComp->getScraperResultsSize() > 1)
allowRefine = true; allowRefine = true;
// Dito but there were no games found, or the search has not been completed. // Dito but there were no games found, or the search has not been completed.
else if (mSearchComp->getSearchType() == GuiScraperSearch::SEMIAUTOMATIC_MODE && else if (mSearchComp->getSearchType() == GuiScraperSearch::SEMIAUTOMATIC_MODE &&
!mSearchComp->getFoundGame()) !mSearchComp->getFoundGame())
allowRefine = true; allowRefine = true;
if (allowRefine) { if (allowRefine) {
// Copy any search refine that may have been previously entered by opening // Copy any search refine that may have been previously entered by opening
// the input screen using the "Y" button shortcut. // the input screen using the "Y" button shortcut.
mSearchQueue.front().nameOverride = mSearchComp->getNameOverride(); mSearchQueue.front().nameOverride = mSearchComp->getNameOverride();
mSearchComp->openInputScreen(mSearchQueue.front()); mSearchComp->openInputScreen(mSearchQueue.front());
mGrid.resetCursor(); mGrid.resetCursor();
}
} }
} }));
}));
buttons.push_back(std::make_shared<ButtonComponent>("SKIP", "skip game", [&] { buttons.push_back(std::make_shared<ButtonComponent>(_("SKIP"), _("skip game"), [&] {
// Skip game, unless the result has already been accepted. // Skip game, unless the result has already been accepted.
if (!mSearchComp->getAcceptedResult()) { if (!mSearchComp->getAcceptedResult()) {
skip(); skip();
@ -145,7 +149,7 @@ GuiScraperMulti::GuiScraperMulti(
})); }));
} }
buttons.push_back(std::make_shared<ButtonComponent>("STOP", "stop", buttons.push_back(std::make_shared<ButtonComponent>(_("STOP"), _("stop"),
std::bind(&GuiScraperMulti::finish, this))); std::bind(&GuiScraperMulti::finish, this)));
mButtonGrid = MenuComponent::makeButtonGrid(buttons); mButtonGrid = MenuComponent::makeButtonGrid(buttons);
@ -221,11 +225,10 @@ void GuiScraperMulti::doNextSearch()
std::stringstream ss; std::stringstream ss;
if (mQueueCountPerSystem.size() > 1) { if (mQueueCountPerSystem.size() > 1) {
// const int gameCount {++mQueueCountPerSystem[mSearchQueue.front().system].first};
const int totalGameCount {mQueueCountPerSystem[mSearchQueue.front().system].second}; const int totalGameCount {mQueueCountPerSystem[mSearchQueue.front().system].second};
const std::string gameCountText {_n("GAME", "GAMES", totalGameCount)};
mSystem->setText(Utils::String::toUpper(mSearchQueue.front().system->getFullName()) + " [" + mSystem->setText(Utils::String::toUpper(mSearchQueue.front().system->getFullName()) + " [" +
std::to_string(totalGameCount) + " GAME" + std::to_string(totalGameCount) + " " + gameCountText + "]");
(totalGameCount == 1 ? "]" : "S]"));
} }
else { else {
mSystem->setText(Utils::String::toUpper(mSearchQueue.front().system->getFullName())); mSystem->setText(Utils::String::toUpper(mSearchQueue.front().system->getFullName()));
@ -267,8 +270,9 @@ void GuiScraperMulti::doNextSearch()
// Update subtitle. // Update subtitle.
ss.str(""); ss.str("");
ss << "GAME " << (mCurrentGame + 1) << " OF " << mTotalGames << " - " << folderPath const std::string gameCounterText {
<< scrapeName Utils::String::format(_("GAME %i OF %i"), mCurrentGame + 1, mTotalGames)};
ss << gameCounterText << " - " << folderPath << scrapeName
<< ((mSearchQueue.front().game->getType() == FOLDER) ? " " + ViewController::FOLDER_CHAR : << ((mSearchQueue.front().game->getType() == FOLDER) ? " " + ViewController::FOLDER_CHAR :
""); "");
mSubtitle->setText(ss.str()); mSubtitle->setText(ss.str());
@ -308,20 +312,22 @@ void GuiScraperMulti::finish()
{ {
std::stringstream ss; std::stringstream ss;
if (mTotalSuccessful == 0) { if (mTotalSuccessful == 0) {
ss << "NO GAMES WERE SCRAPED"; ss << _("NO GAMES WERE SCRAPED");
} }
else { else {
ss << mTotalSuccessful << " GAME" << ((mTotalSuccessful > 1) ? "S" : "") ss << Utils::String::format(
<< " SUCCESSFULLY SCRAPED"; _n("%i GAME SUCCESSFULLY SCRAPED", "%i GAMES SUCCESSFULLY SCRAPED", mTotalSuccessful),
mTotalSuccessful);
if (mTotalSkipped > 0) if (mTotalSkipped > 0)
ss << "\n" ss << "\n"
<< mTotalSkipped << " GAME" << ((mTotalSkipped > 1) ? "S" : "") << " SKIPPED"; << Utils::String::format(_n("%i GAME SKIPPED", "%i GAMES SKIPPED", mTotalSkipped),
mTotalSkipped);
} }
// Pressing either OK or using the back button should delete us. // Pressing either OK or using the back button should delete us.
mWindow->pushGui(new GuiMsgBox( mWindow->pushGui(new GuiMsgBox(
getHelpStyle(), ss.str(), "OK", getHelpStyle(), ss.str(), _("OK"),
[&] { [&] {
mIsProcessing = false; mIsProcessing = false;
delete this; delete this;

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// //
// ES-DE // ES-DE Frontend
// GuiScraperMulti.h // GuiScraperMulti.h
// //
// Multiple game scraping user interface. // Multiple game scraping user interface.

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// //
// ES-DE // ES-DE Frontend
// GuiScraperSearch.cpp // GuiScraperSearch.cpp
// //
// User interface for the scraper where the user is able to see an overview // User interface for the scraper where the user is able to see an overview
@ -32,6 +32,7 @@
#include "guis/GuiTextEditKeyboardPopup.h" #include "guis/GuiTextEditKeyboardPopup.h"
#include "guis/GuiTextEditPopup.h" #include "guis/GuiTextEditPopup.h"
#include "resources/Font.h" #include "resources/Font.h"
#include "utils/LocalizationUtil.h"
#include "utils/StringUtil.h" #include "utils/StringUtil.h"
GuiScraperSearch::GuiScraperSearch(SearchType type, unsigned int scrapeCount, int rowCount) GuiScraperSearch::GuiScraperSearch(SearchType type, unsigned int scrapeCount, int rowCount)
@ -80,7 +81,8 @@ GuiScraperSearch::GuiScraperSearch(SearchType type, unsigned int scrapeCount, in
mDescContainer->setScrollParameters(6000.0f, 3000.0f, 0.8f); mDescContainer->setScrollParameters(6000.0f, 3000.0f, 0.8f);
mResultDesc = std::make_shared<TextComponent>("Result desc", Font::get(FONT_SIZE_SMALL), mResultDesc = std::make_shared<TextComponent>("Result desc", Font::get(FONT_SIZE_SMALL),
mMenuColorPrimary); mMenuColorPrimary, ALIGN_LEFT, ALIGN_CENTER,
glm::ivec2 {0, 1});
mDescContainer->addChild(mResultDesc.get()); mDescContainer->addChild(mResultDesc.get());
mDescContainer->setAutoScroll(true); mDescContainer->setAutoScroll(true);
@ -103,18 +105,18 @@ GuiScraperSearch::GuiScraperSearch(SearchType type, unsigned int scrapeCount, in
if (mScrapeRatings) if (mScrapeRatings)
mMD_Pairs.push_back(MetaDataPair( mMD_Pairs.push_back(MetaDataPair(
std::make_shared<TextComponent>("RATING:", font, mdLblColor), mMD_Rating, false)); std::make_shared<TextComponent>(_("RATING:"), font, mdLblColor), mMD_Rating, false));
mMD_Pairs.push_back(MetaDataPair(std::make_shared<TextComponent>("RELEASED:", font, mdLblColor),
mMD_ReleaseDate));
mMD_Pairs.push_back(MetaDataPair( mMD_Pairs.push_back(MetaDataPair(
std::make_shared<TextComponent>("DEVELOPER:", font, mdLblColor), mMD_Developer)); std::make_shared<TextComponent>(_("RELEASED:"), font, mdLblColor), mMD_ReleaseDate));
mMD_Pairs.push_back(MetaDataPair( mMD_Pairs.push_back(MetaDataPair(
std::make_shared<TextComponent>("PUBLISHER:", font, mdLblColor), mMD_Publisher)); std::make_shared<TextComponent>(_("DEVELOPER:"), font, mdLblColor), mMD_Developer));
mMD_Pairs.push_back(MetaDataPair(
std::make_shared<TextComponent>(_("PUBLISHER:"), font, mdLblColor), mMD_Publisher));
mMD_Pairs.push_back( mMD_Pairs.push_back(
MetaDataPair(std::make_shared<TextComponent>("GENRE:", font, mdLblColor), mMD_Genre)); MetaDataPair(std::make_shared<TextComponent>(_("GENRE:"), font, mdLblColor), mMD_Genre));
mMD_Pairs.push_back( mMD_Pairs.push_back(MetaDataPair(
MetaDataPair(std::make_shared<TextComponent>("PLAYERS:", font, mdLblColor), mMD_Players)); std::make_shared<TextComponent>(_("PLAYERS:"), font, mdLblColor), mMD_Players));
// If no rating is being scraped, add a filler to make sure that the fonts keep the same // If no rating is being scraped, add a filler to make sure that the fonts keep the same
// size so the GUI looks consistent. // size so the GUI looks consistent.
@ -256,12 +258,12 @@ void GuiScraperSearch::resizeMetadata()
float maxLblWidth {0.0f}; float maxLblWidth {0.0f};
for (auto it = mMD_Pairs.cbegin(); it != mMD_Pairs.cend(); ++it) { for (auto it = mMD_Pairs.cbegin(); it != mMD_Pairs.cend(); ++it) {
it->first->setFont(fontLbl); it->first->setFont(fontLbl);
it->first->setSize(0, 0); if (it->first->getTextCache() != nullptr &&
if (it->first->getSize().x > maxLblWidth) it->first->getTextCache()->metrics.size.x > maxLblWidth)
maxLblWidth = maxLblWidth = it->first->getTextCache()->metrics.size.x +
it->first->getSize().x + (16.0f * (mRenderer->getIsVerticalOrientation() ? (16.0f * (mRenderer->getIsVerticalOrientation() ?
mRenderer->getScreenHeightModifier() : mRenderer->getScreenHeightModifier() :
mRenderer->getScreenWidthModifier())); mRenderer->getScreenWidthModifier()));
} }
for (unsigned int i {0}; i < mMD_Pairs.size(); ++i) for (unsigned int i {0}; i < mMD_Pairs.size(); ++i)
@ -428,7 +430,7 @@ void GuiScraperSearch::onSearchDone(std::vector<ScraperSearchResult>& results)
mFoundGame = false; mFoundGame = false;
ComponentListRow row; ComponentListRow row;
row.addElement(std::make_shared<TextComponent>("NO GAMES FOUND", font, color), true); row.addElement(std::make_shared<TextComponent>(_("NO GAMES FOUND"), font, color), true);
if (mSkipCallback) if (mSkipCallback)
row.makeAcceptInputHandler(mSkipCallback); row.makeAcceptInputHandler(mSkipCallback);
@ -513,7 +515,7 @@ void GuiScraperSearch::onSearchDone(std::vector<ScraperSearchResult>& results)
auto gameEntry = auto gameEntry =
std::make_shared<TextComponent>(Utils::String::toUpper(gameName), font, color); std::make_shared<TextComponent>(Utils::String::toUpper(gameName), font, color);
gameEntry->setHorizontalScrolling(true); gameEntry->setHorizontalScrolling(true);
row.addElement(gameEntry, true); row.addElement(gameEntry, true, true, glm::ivec2 {1, 0});
row.makeAcceptInputHandler([this, i] { returnResult(mScraperResults.at(i)); }); row.makeAcceptInputHandler([this, i] { returnResult(mScraperResults.at(i)); });
mResultList->addRow(row); mResultList->addRow(row);
} }
@ -552,8 +554,16 @@ void GuiScraperSearch::onSearchDone(std::vector<ScraperSearchResult>& results)
void GuiScraperSearch::onSearchError(const std::string& error, void GuiScraperSearch::onSearchError(const std::string& error,
const bool retry, const bool retry,
const bool fatalError,
HttpReq::Status status) HttpReq::Status status)
{ {
if (fatalError) {
LOG(LogWarning) << "GuiScraperSearch: " << Utils::String::replace(error, "\n", "");
mWindow->pushGui(new GuiMsgBox(getHelpStyle(), Utils::String::toUpper(error), _("OK"),
mCancelCallback, "", nullptr, "", nullptr, nullptr, true));
return;
}
const int retries { const int retries {
glm::clamp(Settings::getInstance()->getInt("ScraperRetryOnErrorCount"), 0, 10)}; glm::clamp(Settings::getInstance()->getInt("ScraperRetryOnErrorCount"), 0, 10)};
if (retry && mSearchType != MANUAL_MODE && retries > 0 && mRetryCount < retries) { if (retry && mSearchType != MANUAL_MODE && retries > 0 && mRetryCount < retries) {
@ -570,16 +580,16 @@ void GuiScraperSearch::onSearchError(const std::string& error,
if (mScrapeCount > 1) { if (mScrapeCount > 1) {
LOG(LogError) << "GuiScraperSearch: " << Utils::String::replace(error, "\n", ""); LOG(LogError) << "GuiScraperSearch: " << Utils::String::replace(error, "\n", "");
mWindow->pushGui(new GuiMsgBox(getHelpStyle(), Utils::String::toUpper(error), "RETRY", mWindow->pushGui(new GuiMsgBox(getHelpStyle(), Utils::String::toUpper(error), _("RETRY"),
std::bind(&GuiScraperSearch::search, this, mLastSearch), std::bind(&GuiScraperSearch::search, this, mLastSearch),
"SKIP", mSkipCallback, "CANCEL", mCancelCallback, nullptr, _("SKIP"), mSkipCallback, _("CANCEL"), mCancelCallback,
true)); nullptr, true));
} }
else { else {
LOG(LogError) << "GuiScraperSearch: " << Utils::String::replace(error, "\n", ""); LOG(LogError) << "GuiScraperSearch: " << Utils::String::replace(error, "\n", "");
mWindow->pushGui(new GuiMsgBox(getHelpStyle(), Utils::String::toUpper(error), "RETRY", mWindow->pushGui(new GuiMsgBox(getHelpStyle(), Utils::String::toUpper(error), _("RETRY"),
std::bind(&GuiScraperSearch::search, this, mLastSearch), std::bind(&GuiScraperSearch::search, this, mLastSearch),
"CANCEL", mCancelCallback, "", nullptr, nullptr, true)); _("CANCEL"), mCancelCallback, "", nullptr, nullptr, true));
} }
} }
@ -639,10 +649,27 @@ void GuiScraperSearch::updateInfoPane()
mMD_Rating->setOpacity(1.0f); mMD_Rating->setOpacity(1.0f);
} }
mMD_ReleaseDate->setValue(Utils::String::toUpper(res.mdl.get("releasedate"))); mMD_ReleaseDate->setValue(Utils::String::toUpper(res.mdl.get("releasedate")));
mMD_Developer->setText(Utils::String::toUpper(res.mdl.get("developer")));
mMD_Publisher->setText(Utils::String::toUpper(res.mdl.get("publisher"))); if (res.mdl.get("developer") == "unknown")
mMD_Genre->setText(Utils::String::toUpper(res.mdl.get("genre"))); mMD_Developer->setText(Utils::String::toUpper(_(res.mdl.get("developer").c_str())));
mMD_Players->setText(Utils::String::toUpper(res.mdl.get("players"))); else
mMD_Developer->setText(Utils::String::toUpper(res.mdl.get("developer")));
if (res.mdl.get("publisher") == "unknown")
mMD_Publisher->setText(Utils::String::toUpper(_(res.mdl.get("publisher").c_str())));
else
mMD_Publisher->setText(Utils::String::toUpper(res.mdl.get("publisher")));
if (res.mdl.get("genre") == "unknown")
mMD_Genre->setText(Utils::String::toUpper(_(res.mdl.get("genre").c_str())));
else
mMD_Genre->setText(Utils::String::toUpper(res.mdl.get("genre")));
if (res.mdl.get("players") == "unknown")
mMD_Players->setText(Utils::String::toUpper(_(res.mdl.get("players").c_str())));
else
mMD_Players->setText(Utils::String::toUpper(res.mdl.get("players")));
mGrid.onSizeChanged(); mGrid.onSizeChanged();
} }
else { else {
@ -798,6 +825,7 @@ void GuiScraperSearch::update(int deltaTime)
mScraperResults = mSearchHandle->getResults(); mScraperResults = mSearchHandle->getResults();
const std::string statusString {mSearchHandle->getStatusString()}; const std::string statusString {mSearchHandle->getStatusString()};
const bool retryFlag {mSearchHandle->getRetry()}; const bool retryFlag {mSearchHandle->getRetry()};
const bool fatalErrorFlag {mSearchHandle->getFatalError()};
// We reset here because onSearchDone in auto mode can call mSkipCallback() which // We reset here because onSearchDone in auto mode can call mSkipCallback() which
// can call another search() which will set our mSearchHandle to something important. // can call another search() which will set our mSearchHandle to something important.
@ -821,7 +849,7 @@ void GuiScraperSearch::update(int deltaTime)
} }
} }
else if (status == ASYNC_ERROR) { else if (status == ASYNC_ERROR) {
onSearchError(statusString, retryFlag); onSearchError(statusString, retryFlag, fatalErrorFlag);
} }
} }
@ -855,7 +883,8 @@ void GuiScraperSearch::update(int deltaTime)
} }
else if (mMDRetrieveURLsHandle->status() == ASYNC_ERROR) { else if (mMDRetrieveURLsHandle->status() == ASYNC_ERROR) {
onSearchError(mMDRetrieveURLsHandle->getStatusString(), onSearchError(mMDRetrieveURLsHandle->getStatusString(),
mMDRetrieveURLsHandle->getRetry()); mMDRetrieveURLsHandle->getRetry(),
(mSearchHandle != nullptr ? mSearchHandle->getFatalError() : false));
mMDRetrieveURLsHandle.reset(); mMDRetrieveURLsHandle.reset();
} }
} }
@ -912,7 +941,8 @@ void GuiScraperSearch::update(int deltaTime)
} }
} }
else if (mMDResolveHandle->status() == ASYNC_ERROR) { else if (mMDResolveHandle->status() == ASYNC_ERROR) {
onSearchError(mMDResolveHandle->getStatusString(), mMDResolveHandle->getRetry()); onSearchError(mMDResolveHandle->getStatusString(), mMDResolveHandle->getRetry(),
(mSearchHandle != nullptr ? mSearchHandle->getFatalError() : false));
mMDResolveHandle.reset(); mMDResolveHandle.reset();
} }
} }
@ -940,7 +970,8 @@ void GuiScraperSearch::updateThumbnail()
} }
else { else {
mResultThumbnail->setImage(""); mResultThumbnail->setImage("");
onSearchError("Error downloading thumbnail:\n " + it->second->getErrorMsg(), true, onSearchError(_("Error downloading thumbnail:") + " \n" + it->second->getErrorMsg(), true,
(mSearchHandle != nullptr ? mSearchHandle->getFatalError() : false),
it->second->status()); it->second->status());
} }
@ -1018,14 +1049,14 @@ void GuiScraperSearch::openInputScreen(ScraperSearchParams& params)
searchString = Utils::String::replace(searchString, "_", " "); searchString = Utils::String::replace(searchString, "_", " ");
if (Settings::getInstance()->getBool("VirtualKeyboard")) { if (Settings::getInstance()->getBool("VirtualKeyboard")) {
mWindow->pushGui(new GuiTextEditKeyboardPopup(getHelpStyle(), 0.0f, "REFINE SEARCH", mWindow->pushGui(new GuiTextEditKeyboardPopup(
searchString, searchForFunc, false, "SEARCH", getHelpStyle(), 0.0f, _("REFINE SEARCH"), searchString, searchForFunc, false,
"SEARCH USING REFINED NAME?")); _("SEARCH"), _("SEARCH USING REFINED NAME?")));
} }
else { else {
mWindow->pushGui(new GuiTextEditPopup(getHelpStyle(), "REFINE SEARCH", searchString, mWindow->pushGui(new GuiTextEditPopup(getHelpStyle(), _("REFINE SEARCH"), searchString,
searchForFunc, false, "SEARCH", searchForFunc, false, _("SEARCH"),
"SEARCH USING REFINED NAME?")); _("SEARCH USING REFINED NAME?")));
} }
} }
@ -1116,15 +1147,15 @@ std::vector<HelpPrompt> GuiScraperSearch::getHelpPrompts()
{ {
std::vector<HelpPrompt> prompts; std::vector<HelpPrompt> prompts;
prompts.push_back(HelpPrompt("y", "refine search")); prompts.push_back(HelpPrompt("y", _("refine search")));
// Only show the skip prompt during multi-scraping. // Only show the skip prompt during multi-scraping.
if (mSkipCallback != nullptr) if (mSkipCallback != nullptr)
prompts.push_back(HelpPrompt("x", "skip")); prompts.push_back(HelpPrompt("x", _("skip")));
if (mFoundGame && (mRefinedSearch || mSearchType != SEMIAUTOMATIC_MODE || if (mFoundGame && (mRefinedSearch || mSearchType != SEMIAUTOMATIC_MODE ||
(mSearchType == SEMIAUTOMATIC_MODE && mScraperResults.size() > 1))) (mSearchType == SEMIAUTOMATIC_MODE && mScraperResults.size() > 1)))
prompts.push_back(HelpPrompt("a", "accept result")); prompts.push_back(HelpPrompt("a", _("accept result")));
return prompts; return prompts;
} }

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// //
// ES-DE // ES-DE Frontend
// GuiScraperSearch.h // GuiScraperSearch.h
// //
// User interface for the scraper where the user is able to see an overview // User interface for the scraper where the user is able to see an overview
@ -109,6 +109,7 @@ private:
void onSearchError(const std::string& error, void onSearchError(const std::string& error,
const bool retry, const bool retry,
const bool fatalError,
HttpReq::Status status = HttpReq::REQ_UNDEFINED_ERROR); HttpReq::Status status = HttpReq::REQ_UNDEFINED_ERROR);
void onSearchDone(std::vector<ScraperSearchResult>& results); void onSearchDone(std::vector<ScraperSearchResult>& results);

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// //
// ES-DE // ES-DE Frontend
// GuiScraperSingle.cpp // GuiScraperSingle.cpp
// //
// Single game scraping user interface. // Single game scraping user interface.
@ -16,6 +16,7 @@
#include "components/ButtonComponent.h" #include "components/ButtonComponent.h"
#include "components/MenuComponent.h" #include "components/MenuComponent.h"
#include "components/TextComponent.h" #include "components/TextComponent.h"
#include "utils/LocalizationUtil.h"
GuiScraperSingle::GuiScraperSingle(ScraperSearchParams& params, GuiScraperSingle::GuiScraperSingle(ScraperSearchParams& params,
std::function<void(const ScraperSearchResult&)> doneFunc, std::function<void(const ScraperSearchResult&)> doneFunc,
@ -48,7 +49,8 @@ GuiScraperSingle::GuiScraperSingle(ScraperSearchParams& params,
mGameName = std::make_shared<TextComponent>( mGameName = std::make_shared<TextComponent>(
scrapeName + scrapeName +
((mSearchParams.game->getType() == FOLDER) ? " " + ViewController::FOLDER_CHAR : ""), ((mSearchParams.game->getType() == FOLDER) ? " " + ViewController::FOLDER_CHAR : ""),
Font::get(FONT_SIZE_LARGE), mMenuColorPrimary, ALIGN_CENTER); Font::get(FONT_SIZE_LARGE * Utils::Localization::sMenuTitleScaleFactor), mMenuColorPrimary,
ALIGN_CENTER);
mGameName->setColor(mMenuColorTitle); mGameName->setColor(mMenuColorTitle);
mGrid.setEntry(mGameName, glm::ivec2 {0, 0}, false, true, glm::ivec2 {2, 2}); mGrid.setEntry(mGameName, glm::ivec2 {0, 0}, false, true, glm::ivec2 {2, 2});
@ -84,17 +86,18 @@ GuiScraperSingle::GuiScraperSingle(ScraperSearchParams& params,
// Buttons // Buttons
std::vector<std::shared_ptr<ButtonComponent>> buttons; std::vector<std::shared_ptr<ButtonComponent>> buttons;
buttons.push_back(std::make_shared<ButtonComponent>("REFINE SEARCH", "refine search", [&] { buttons.push_back(
// Refine the search, unless the result has already been accepted. std::make_shared<ButtonComponent>(_("REFINE SEARCH"), _("refine search"), [&] {
if (!mSearch->getAcceptedResult()) { // Refine the search, unless the result has already been accepted.
// Copy any search refine that may have been previously entered by opening if (!mSearch->getAcceptedResult()) {
// the input screen using the "Y" button shortcut. // Copy any search refine that may have been previously entered by opening
mSearchParams.nameOverride = mSearch->getNameOverride(); // the input screen using the "Y" button shortcut.
mSearch->openInputScreen(mSearchParams); mSearchParams.nameOverride = mSearch->getNameOverride();
mGrid.resetCursor(); mSearch->openInputScreen(mSearchParams);
} mGrid.resetCursor();
})); }
buttons.push_back(std::make_shared<ButtonComponent>("CANCEL", "cancel", [&] { }));
buttons.push_back(std::make_shared<ButtonComponent>(_("CANCEL"), _("cancel"), [&] {
if (mSearch->getSavedNewMedia()) { if (mSearch->getSavedNewMedia()) {
// If the user aborted the scraping but there was still some media downloaded, // If the user aborted the scraping but there was still some media downloaded,
// then flag to GuiMetaDataEd that the image and marquee textures need to be // then flag to GuiMetaDataEd that the image and marquee textures need to be
@ -194,7 +197,7 @@ void GuiScraperSingle::update(int deltaTime)
std::vector<HelpPrompt> GuiScraperSingle::getHelpPrompts() std::vector<HelpPrompt> GuiScraperSingle::getHelpPrompts()
{ {
std::vector<HelpPrompt> prompts {mGrid.getHelpPrompts()}; std::vector<HelpPrompt> prompts {mGrid.getHelpPrompts()};
prompts.push_back(HelpPrompt("b", "back (cancel)")); prompts.push_back(HelpPrompt("b", _("back (cancel)")));
return prompts; return prompts;
} }

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// //
// ES-DE // ES-DE Frontend
// GuiScraperSingle.h // GuiScraperSingle.h
// //
// Single game scraping user interface. // Single game scraping user interface.

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// //
// ES-DE // ES-DE Frontend
// GuiScreensaverOptions.cpp // GuiScreensaverOptions.cpp
// //
// User interface for the screensaver options. // User interface for the screensaver options.
@ -17,6 +17,7 @@
#include "guis/GuiMsgBox.h" #include "guis/GuiMsgBox.h"
#include "guis/GuiTextEditKeyboardPopup.h" #include "guis/GuiTextEditKeyboardPopup.h"
#include "guis/GuiTextEditPopup.h" #include "guis/GuiTextEditPopup.h"
#include "utils/LocalizationUtil.h"
GuiScreensaverOptions::GuiScreensaverOptions(const std::string& title) GuiScreensaverOptions::GuiScreensaverOptions(const std::string& title)
: GuiSettings {title} : GuiSettings {title}
@ -25,7 +26,7 @@ GuiScreensaverOptions::GuiScreensaverOptions(const std::string& title)
auto screensaverTimer = std::make_shared<SliderComponent>(0.0f, 30.0f, 1.0f, "m"); auto screensaverTimer = std::make_shared<SliderComponent>(0.0f, 30.0f, 1.0f, "m");
screensaverTimer->setValue( screensaverTimer->setValue(
static_cast<float>(Settings::getInstance()->getInt("ScreensaverTimer") / (1000 * 60))); static_cast<float>(Settings::getInstance()->getInt("ScreensaverTimer") / (1000 * 60)));
addWithLabel("START SCREENSAVER AFTER (MINUTES)", screensaverTimer); addWithLabel(_("START SCREENSAVER AFTER (MINUTES)"), screensaverTimer);
addSaveFunc([screensaverTimer, this] { addSaveFunc([screensaverTimer, this] {
if (static_cast<int>(std::round(screensaverTimer->getValue()) * (1000 * 60)) != if (static_cast<int>(std::round(screensaverTimer->getValue()) * (1000 * 60)) !=
Settings::getInstance()->getInt("ScreensaverTimer")) { Settings::getInstance()->getInt("ScreensaverTimer")) {
@ -38,17 +39,17 @@ GuiScreensaverOptions::GuiScreensaverOptions(const std::string& title)
// Screensaver type. // Screensaver type.
auto screensaverType = std::make_shared<OptionListComponent<std::string>>( auto screensaverType = std::make_shared<OptionListComponent<std::string>>(
getHelpStyle(), "SCREENSAVER TYPE", false); getHelpStyle(), _("SCREENSAVER TYPE"), false);
std::string selectedScreensaver {Settings::getInstance()->getString("ScreensaverType")}; std::string selectedScreensaver {Settings::getInstance()->getString("ScreensaverType")};
screensaverType->add("DIM", "dim", selectedScreensaver == "dim"); screensaverType->add(_("DIM"), "dim", selectedScreensaver == "dim");
screensaverType->add("BLACK", "black", selectedScreensaver == "black"); screensaverType->add(_("BLACK"), "black", selectedScreensaver == "black");
screensaverType->add("SLIDESHOW", "slideshow", selectedScreensaver == "slideshow"); screensaverType->add(_("SLIDESHOW"), "slideshow", selectedScreensaver == "slideshow");
screensaverType->add("VIDEO", "video", selectedScreensaver == "video"); screensaverType->add(_("VIDEO"), "video", selectedScreensaver == "video");
// If there are no objects returned, then there must be a manually modified entry in the // If there are no objects returned, then there must be a manually modified entry in the
// configuration file. Simply set the screensaver type to "dim" in this case. // configuration file. Simply set the screensaver type to "dim" in this case.
if (screensaverType->getSelectedObjects().size() == 0) if (screensaverType->getSelectedObjects().size() == 0)
screensaverType->selectEntry(0); screensaverType->selectEntry(0);
addWithLabel("SCREENSAVER TYPE", screensaverType); addWithLabel(_("SCREENSAVER TYPE"), screensaverType);
addSaveFunc([screensaverType, this] { addSaveFunc([screensaverType, this] {
if (screensaverType->getSelected() != if (screensaverType->getSelected() !=
Settings::getInstance()->getString("ScreensaverType")) { Settings::getInstance()->getString("ScreensaverType")) {
@ -60,7 +61,7 @@ GuiScreensaverOptions::GuiScreensaverOptions(const std::string& title)
// Whether to enable screensaver controls. // Whether to enable screensaver controls.
auto screensaverControls = std::make_shared<SwitchComponent>(); auto screensaverControls = std::make_shared<SwitchComponent>();
screensaverControls->setState(Settings::getInstance()->getBool("ScreensaverControls")); screensaverControls->setState(Settings::getInstance()->getBool("ScreensaverControls"));
addWithLabel("ENABLE SCREENSAVER CONTROLS", screensaverControls); addWithLabel(_("ENABLE SCREENSAVER CONTROLS"), screensaverControls);
addSaveFunc([screensaverControls, this] { addSaveFunc([screensaverControls, this] {
if (screensaverControls->getState() != if (screensaverControls->getState() !=
Settings::getInstance()->getBool("ScreensaverControls")) { Settings::getInstance()->getBool("ScreensaverControls")) {
@ -73,7 +74,7 @@ GuiScreensaverOptions::GuiScreensaverOptions(const std::string& title)
// Show filtered menu. // Show filtered menu.
ComponentListRow row; ComponentListRow row;
row.elements.clear(); row.elements.clear();
row.addElement(std::make_shared<TextComponent>("SLIDESHOW SCREENSAVER SETTINGS", row.addElement(std::make_shared<TextComponent>(_("SLIDESHOW SCREENSAVER SETTINGS"),
Font::get(FONT_SIZE_MEDIUM), mMenuColorPrimary), Font::get(FONT_SIZE_MEDIUM), mMenuColorPrimary),
true); true);
row.addElement(getMenu().makeArrow(), false); row.addElement(getMenu().makeArrow(), false);
@ -82,7 +83,7 @@ GuiScreensaverOptions::GuiScreensaverOptions(const std::string& title)
addRow(row); addRow(row);
row.elements.clear(); row.elements.clear();
row.addElement(std::make_shared<TextComponent>("VIDEO SCREENSAVER SETTINGS", row.addElement(std::make_shared<TextComponent>(_("VIDEO SCREENSAVER SETTINGS"),
Font::get(FONT_SIZE_MEDIUM), mMenuColorPrimary), Font::get(FONT_SIZE_MEDIUM), mMenuColorPrimary),
true); true);
row.addElement(getMenu().makeArrow(), false); row.addElement(getMenu().makeArrow(), false);
@ -95,13 +96,13 @@ GuiScreensaverOptions::GuiScreensaverOptions(const std::string& title)
void GuiScreensaverOptions::openSlideshowScreensaverOptions() void GuiScreensaverOptions::openSlideshowScreensaverOptions()
{ {
auto s = new GuiSettings("SLIDESHOW SCREENSAVER"); auto s = new GuiSettings(_("SLIDESHOW SCREENSAVER"));
// Timer for swapping images (in seconds). // Timer for swapping images (in seconds).
auto screensaverSwapImageTimeout = std::make_shared<SliderComponent>(2.0f, 120.0f, 2.0f, "s"); auto screensaverSwapImageTimeout = std::make_shared<SliderComponent>(2.0f, 120.0f, 2.0f, "s");
screensaverSwapImageTimeout->setValue(static_cast<float>( screensaverSwapImageTimeout->setValue(static_cast<float>(
Settings::getInstance()->getInt("ScreensaverSwapImageTimeout") / (1000))); Settings::getInstance()->getInt("ScreensaverSwapImageTimeout") / (1000)));
s->addWithLabel("SWAP IMAGES AFTER (SECONDS)", screensaverSwapImageTimeout); s->addWithLabel(_("SWAP IMAGES AFTER (SECONDS)"), screensaverSwapImageTimeout);
s->addSaveFunc([screensaverSwapImageTimeout, s] { s->addSaveFunc([screensaverSwapImageTimeout, s] {
if (screensaverSwapImageTimeout->getValue() != if (screensaverSwapImageTimeout->getValue() !=
static_cast<float>(Settings::getInstance()->getInt("ScreensaverSwapImageTimeout") / static_cast<float>(Settings::getInstance()->getInt("ScreensaverSwapImageTimeout") /
@ -117,7 +118,7 @@ void GuiScreensaverOptions::openSlideshowScreensaverOptions()
auto screensaverSlideshowOnlyFavorites = std::make_shared<SwitchComponent>(); auto screensaverSlideshowOnlyFavorites = std::make_shared<SwitchComponent>();
screensaverSlideshowOnlyFavorites->setState( screensaverSlideshowOnlyFavorites->setState(
Settings::getInstance()->getBool("ScreensaverSlideshowOnlyFavorites")); Settings::getInstance()->getBool("ScreensaverSlideshowOnlyFavorites"));
s->addWithLabel("ONLY INCLUDE FAVORITE GAMES", screensaverSlideshowOnlyFavorites); s->addWithLabel(_("ONLY INCLUDE FAVORITE GAMES"), screensaverSlideshowOnlyFavorites);
s->addSaveFunc([screensaverSlideshowOnlyFavorites, s] { s->addSaveFunc([screensaverSlideshowOnlyFavorites, s] {
if (screensaverSlideshowOnlyFavorites->getState() != if (screensaverSlideshowOnlyFavorites->getState() !=
Settings::getInstance()->getBool("ScreensaverSlideshowOnlyFavorites")) { Settings::getInstance()->getBool("ScreensaverSlideshowOnlyFavorites")) {
@ -131,7 +132,7 @@ void GuiScreensaverOptions::openSlideshowScreensaverOptions()
auto screensaverStretchImages = std::make_shared<SwitchComponent>(); auto screensaverStretchImages = std::make_shared<SwitchComponent>();
screensaverStretchImages->setState( screensaverStretchImages->setState(
Settings::getInstance()->getBool("ScreensaverStretchImages")); Settings::getInstance()->getBool("ScreensaverStretchImages"));
s->addWithLabel("STRETCH IMAGES TO SCREEN RESOLUTION", screensaverStretchImages); s->addWithLabel(_("STRETCH IMAGES TO SCREEN RESOLUTION"), screensaverStretchImages);
s->addSaveFunc([screensaverStretchImages, s] { s->addSaveFunc([screensaverStretchImages, s] {
if (screensaverStretchImages->getState() != if (screensaverStretchImages->getState() !=
Settings::getInstance()->getBool("ScreensaverStretchImages")) { Settings::getInstance()->getBool("ScreensaverStretchImages")) {
@ -145,7 +146,7 @@ void GuiScreensaverOptions::openSlideshowScreensaverOptions()
auto screensaverSlideshowGameInfo = std::make_shared<SwitchComponent>(); auto screensaverSlideshowGameInfo = std::make_shared<SwitchComponent>();
screensaverSlideshowGameInfo->setState( screensaverSlideshowGameInfo->setState(
Settings::getInstance()->getBool("ScreensaverSlideshowGameInfo")); Settings::getInstance()->getBool("ScreensaverSlideshowGameInfo"));
s->addWithLabel("DISPLAY GAME INFO OVERLAY", screensaverSlideshowGameInfo); s->addWithLabel(_("DISPLAY GAME INFO OVERLAY"), screensaverSlideshowGameInfo);
s->addSaveFunc([screensaverSlideshowGameInfo, s] { s->addSaveFunc([screensaverSlideshowGameInfo, s] {
if (screensaverSlideshowGameInfo->getState() != if (screensaverSlideshowGameInfo->getState() !=
Settings::getInstance()->getBool("ScreensaverSlideshowGameInfo")) { Settings::getInstance()->getBool("ScreensaverSlideshowGameInfo")) {
@ -159,7 +160,7 @@ void GuiScreensaverOptions::openSlideshowScreensaverOptions()
auto screensaverSlideshowScanlines = std::make_shared<SwitchComponent>(); auto screensaverSlideshowScanlines = std::make_shared<SwitchComponent>();
screensaverSlideshowScanlines->setState( screensaverSlideshowScanlines->setState(
Settings::getInstance()->getBool("ScreensaverSlideshowScanlines")); Settings::getInstance()->getBool("ScreensaverSlideshowScanlines"));
s->addWithLabel("RENDER SCANLINES", screensaverSlideshowScanlines); s->addWithLabel(_("RENDER SCANLINES"), screensaverSlideshowScanlines);
s->addSaveFunc([screensaverSlideshowScanlines, s] { s->addSaveFunc([screensaverSlideshowScanlines, s] {
if (screensaverSlideshowScanlines->getState() != if (screensaverSlideshowScanlines->getState() !=
Settings::getInstance()->getBool("ScreensaverSlideshowScanlines")) { Settings::getInstance()->getBool("ScreensaverSlideshowScanlines")) {
@ -173,7 +174,7 @@ void GuiScreensaverOptions::openSlideshowScreensaverOptions()
auto screensaverSlideshowCustomImages = std::make_shared<SwitchComponent>(); auto screensaverSlideshowCustomImages = std::make_shared<SwitchComponent>();
screensaverSlideshowCustomImages->setState( screensaverSlideshowCustomImages->setState(
Settings::getInstance()->getBool("ScreensaverSlideshowCustomImages")); Settings::getInstance()->getBool("ScreensaverSlideshowCustomImages"));
s->addWithLabel("USE CUSTOM IMAGES", screensaverSlideshowCustomImages); s->addWithLabel(_("USE CUSTOM IMAGES"), screensaverSlideshowCustomImages);
s->addSaveFunc([screensaverSlideshowCustomImages, s] { s->addSaveFunc([screensaverSlideshowCustomImages, s] {
if (screensaverSlideshowCustomImages->getState() != if (screensaverSlideshowCustomImages->getState() !=
Settings::getInstance()->getBool("ScreensaverSlideshowCustomImages")) { Settings::getInstance()->getBool("ScreensaverSlideshowCustomImages")) {
@ -187,7 +188,7 @@ void GuiScreensaverOptions::openSlideshowScreensaverOptions()
auto screensaverSlideshowRecurse = std::make_shared<SwitchComponent>(); auto screensaverSlideshowRecurse = std::make_shared<SwitchComponent>();
screensaverSlideshowRecurse->setState( screensaverSlideshowRecurse->setState(
Settings::getInstance()->getBool("ScreensaverSlideshowRecurse")); Settings::getInstance()->getBool("ScreensaverSlideshowRecurse"));
s->addWithLabel("CUSTOM IMAGE DIRECTORY RECURSIVE SEARCH", screensaverSlideshowRecurse); s->addWithLabel(_("CUSTOM IMAGE DIRECTORY RECURSIVE SEARCH"), screensaverSlideshowRecurse);
s->addSaveFunc([screensaverSlideshowRecurse, s] { s->addSaveFunc([screensaverSlideshowRecurse, s] {
if (screensaverSlideshowRecurse->getState() != if (screensaverSlideshowRecurse->getState() !=
Settings::getInstance()->getBool("ScreensaverSlideshowRecurse")) { Settings::getInstance()->getBool("ScreensaverSlideshowRecurse")) {
@ -200,7 +201,7 @@ void GuiScreensaverOptions::openSlideshowScreensaverOptions()
// Custom image directory. // Custom image directory.
ComponentListRow rowCustomImageDir; ComponentListRow rowCustomImageDir;
auto ScreensaverSlideshowCustomDir = std::make_shared<TextComponent>( auto ScreensaverSlideshowCustomDir = std::make_shared<TextComponent>(
"CUSTOM IMAGE DIRECTORY", Font::get(FONT_SIZE_MEDIUM), mMenuColorPrimary); _("CUSTOM IMAGE DIRECTORY"), Font::get(FONT_SIZE_MEDIUM), mMenuColorPrimary);
auto bracketCustomImageDir = std::make_shared<ImageComponent>(); auto bracketCustomImageDir = std::make_shared<ImageComponent>();
bracketCustomImageDir->setResize( bracketCustomImageDir->setResize(
glm::vec2 {0.0f, Font::get(FONT_SIZE_MEDIUM)->getLetterHeight()}); glm::vec2 {0.0f, Font::get(FONT_SIZE_MEDIUM)->getLetterHeight()});
@ -208,8 +209,8 @@ void GuiScreensaverOptions::openSlideshowScreensaverOptions()
bracketCustomImageDir->setColorShift(mMenuColorPrimary); bracketCustomImageDir->setColorShift(mMenuColorPrimary);
rowCustomImageDir.addElement(ScreensaverSlideshowCustomDir, true); rowCustomImageDir.addElement(ScreensaverSlideshowCustomDir, true);
rowCustomImageDir.addElement(bracketCustomImageDir, false); rowCustomImageDir.addElement(bracketCustomImageDir, false);
const std::string titleCustomImageDir {"CUSTOM IMAGE DIRECTORY"}; const std::string titleCustomImageDir {_("CUSTOM IMAGE DIRECTORY")};
const std::string defaultImageDirStaticText {"Default directory:"}; const std::string defaultImageDirStaticText {_("Default directory:")};
const std::string defaultImageDirText {Utils::FileSystem::getAppDataDirectory() + const std::string defaultImageDirText {Utils::FileSystem::getAppDataDirectory() +
"/screensavers/custom_slideshow"}; "/screensavers/custom_slideshow"};
const std::string initValueMediaDir { const std::string initValueMediaDir {
@ -225,15 +226,15 @@ void GuiScreensaverOptions::openSlideshowScreensaverOptions()
mWindow->pushGui(new GuiTextEditKeyboardPopup( mWindow->pushGui(new GuiTextEditKeyboardPopup(
getHelpStyle(), s->getMenu().getPosition().y, titleCustomImageDir, getHelpStyle(), s->getMenu().getPosition().y, titleCustomImageDir,
Settings::getInstance()->getString("ScreensaverSlideshowCustomDir"), Settings::getInstance()->getString("ScreensaverSlideshowCustomDir"),
updateValMediaDir, false, "SAVE", "SAVE CHANGES?", defaultImageDirStaticText, updateValMediaDir, false, _("SAVE"), _("SAVE CHANGES?"), defaultImageDirStaticText,
defaultImageDirText, "load default directory")); defaultImageDirText, _("load default directory")));
} }
else { else {
mWindow->pushGui(new GuiTextEditPopup( mWindow->pushGui(new GuiTextEditPopup(
getHelpStyle(), titleCustomImageDir, getHelpStyle(), titleCustomImageDir,
Settings::getInstance()->getString("ScreensaverSlideshowCustomDir"), Settings::getInstance()->getString("ScreensaverSlideshowCustomDir"),
updateValMediaDir, false, "SAVE", "SAVE CHANGES?", defaultImageDirStaticText, updateValMediaDir, false, _("SAVE"), _("SAVE CHANGES?"), defaultImageDirStaticText,
defaultImageDirText, "load default directory")); defaultImageDirText, _("load default directory")));
} }
}); });
s->addRow(rowCustomImageDir); s->addRow(rowCustomImageDir);
@ -244,13 +245,13 @@ void GuiScreensaverOptions::openSlideshowScreensaverOptions()
void GuiScreensaverOptions::openVideoScreensaverOptions() void GuiScreensaverOptions::openVideoScreensaverOptions()
{ {
auto s = new GuiSettings("VIDEO SCREENSAVER"); auto s = new GuiSettings(_("VIDEO SCREENSAVER"));
// Timer for swapping videos (in seconds). // Timer for swapping videos (in seconds).
auto screensaverSwapVideoTimeout = std::make_shared<SliderComponent>(0.0f, 120.0f, 2.0f, "s"); auto screensaverSwapVideoTimeout = std::make_shared<SliderComponent>(0.0f, 120.0f, 2.0f, "s");
screensaverSwapVideoTimeout->setValue(static_cast<float>( screensaverSwapVideoTimeout->setValue(static_cast<float>(
Settings::getInstance()->getInt("ScreensaverSwapVideoTimeout") / (1000))); Settings::getInstance()->getInt("ScreensaverSwapVideoTimeout") / (1000)));
s->addWithLabel("SWAP VIDEOS AFTER (SECONDS)", screensaverSwapVideoTimeout); s->addWithLabel(_("SWAP VIDEOS AFTER (SECONDS)"), screensaverSwapVideoTimeout);
s->addSaveFunc([screensaverSwapVideoTimeout, s] { s->addSaveFunc([screensaverSwapVideoTimeout, s] {
if (screensaverSwapVideoTimeout->getValue() != if (screensaverSwapVideoTimeout->getValue() !=
static_cast<float>(Settings::getInstance()->getInt("ScreensaverSwapVideoTimeout") / static_cast<float>(Settings::getInstance()->getInt("ScreensaverSwapVideoTimeout") /
@ -266,7 +267,7 @@ void GuiScreensaverOptions::openVideoScreensaverOptions()
auto screensaverVideoOnlyFavorites = std::make_shared<SwitchComponent>(); auto screensaverVideoOnlyFavorites = std::make_shared<SwitchComponent>();
screensaverVideoOnlyFavorites->setState( screensaverVideoOnlyFavorites->setState(
Settings::getInstance()->getBool("ScreensaverVideoOnlyFavorites")); Settings::getInstance()->getBool("ScreensaverVideoOnlyFavorites"));
s->addWithLabel("ONLY INCLUDE FAVORITE GAMES", screensaverVideoOnlyFavorites); s->addWithLabel(_("ONLY INCLUDE FAVORITE GAMES"), screensaverVideoOnlyFavorites);
s->addSaveFunc([screensaverVideoOnlyFavorites, s] { s->addSaveFunc([screensaverVideoOnlyFavorites, s] {
if (screensaverVideoOnlyFavorites->getState() != if (screensaverVideoOnlyFavorites->getState() !=
Settings::getInstance()->getBool("ScreensaverVideoOnlyFavorites")) { Settings::getInstance()->getBool("ScreensaverVideoOnlyFavorites")) {
@ -280,7 +281,7 @@ void GuiScreensaverOptions::openVideoScreensaverOptions()
auto screensaverStretchVideos = std::make_shared<SwitchComponent>(); auto screensaverStretchVideos = std::make_shared<SwitchComponent>();
screensaverStretchVideos->setState( screensaverStretchVideos->setState(
Settings::getInstance()->getBool("ScreensaverStretchVideos")); Settings::getInstance()->getBool("ScreensaverStretchVideos"));
s->addWithLabel("STRETCH VIDEOS TO SCREEN RESOLUTION", screensaverStretchVideos); s->addWithLabel(_("STRETCH VIDEOS TO SCREEN RESOLUTION"), screensaverStretchVideos);
s->addSaveFunc([screensaverStretchVideos, s] { s->addSaveFunc([screensaverStretchVideos, s] {
if (screensaverStretchVideos->getState() != if (screensaverStretchVideos->getState() !=
Settings::getInstance()->getBool("ScreensaverStretchVideos")) { Settings::getInstance()->getBool("ScreensaverStretchVideos")) {
@ -294,7 +295,7 @@ void GuiScreensaverOptions::openVideoScreensaverOptions()
auto screensaverVideoGameInfo = std::make_shared<SwitchComponent>(); auto screensaverVideoGameInfo = std::make_shared<SwitchComponent>();
screensaverVideoGameInfo->setState( screensaverVideoGameInfo->setState(
Settings::getInstance()->getBool("ScreensaverVideoGameInfo")); Settings::getInstance()->getBool("ScreensaverVideoGameInfo"));
s->addWithLabel("DISPLAY GAME INFO OVERLAY", screensaverVideoGameInfo); s->addWithLabel(_("DISPLAY GAME INFO OVERLAY"), screensaverVideoGameInfo);
s->addSaveFunc([screensaverVideoGameInfo, s] { s->addSaveFunc([screensaverVideoGameInfo, s] {
if (screensaverVideoGameInfo->getState() != if (screensaverVideoGameInfo->getState() !=
Settings::getInstance()->getBool("ScreensaverVideoGameInfo")) { Settings::getInstance()->getBool("ScreensaverVideoGameInfo")) {
@ -308,7 +309,7 @@ void GuiScreensaverOptions::openVideoScreensaverOptions()
auto screensaverVideoScanlines = std::make_shared<SwitchComponent>(); auto screensaverVideoScanlines = std::make_shared<SwitchComponent>();
screensaverVideoScanlines->setState( screensaverVideoScanlines->setState(
Settings::getInstance()->getBool("ScreensaverVideoScanlines")); Settings::getInstance()->getBool("ScreensaverVideoScanlines"));
s->addWithLabel("RENDER SCANLINES", screensaverVideoScanlines); s->addWithLabel(_("RENDER SCANLINES"), screensaverVideoScanlines);
s->addSaveFunc([screensaverVideoScanlines, s] { s->addSaveFunc([screensaverVideoScanlines, s] {
if (screensaverVideoScanlines->getState() != if (screensaverVideoScanlines->getState() !=
Settings::getInstance()->getBool("ScreensaverVideoScanlines")) { Settings::getInstance()->getBool("ScreensaverVideoScanlines")) {
@ -321,7 +322,7 @@ void GuiScreensaverOptions::openVideoScreensaverOptions()
// Render blur using a shader. // Render blur using a shader.
auto screensaverVideoBlur = std::make_shared<SwitchComponent>(); auto screensaverVideoBlur = std::make_shared<SwitchComponent>();
screensaverVideoBlur->setState(Settings::getInstance()->getBool("ScreensaverVideoBlur")); screensaverVideoBlur->setState(Settings::getInstance()->getBool("ScreensaverVideoBlur"));
s->addWithLabel("RENDER BLUR", screensaverVideoBlur); s->addWithLabel(_("RENDER BLUR"), screensaverVideoBlur);
s->addSaveFunc([screensaverVideoBlur, s] { s->addSaveFunc([screensaverVideoBlur, s] {
if (screensaverVideoBlur->getState() != if (screensaverVideoBlur->getState() !=
Settings::getInstance()->getBool("ScreensaverVideoBlur")) { Settings::getInstance()->getBool("ScreensaverVideoBlur")) {

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// //
// ES-DE // ES-DE Frontend
// GuiScreensaverOptions.h // GuiScreensaverOptions.h
// //
// User interface for the screensaver options. // User interface for the screensaver options.

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// //
// ES-DE // ES-DE Frontend
// GuiSettings.cpp // GuiSettings.cpp
// //
// User interface template for a settings GUI. // User interface template for a settings GUI.
@ -39,7 +39,7 @@ GuiSettings::GuiSettings(std::string title)
, mInvalidateCachedBackground {false} , mInvalidateCachedBackground {false}
{ {
addChild(&mMenu); addChild(&mMenu);
mMenu.addButton("BACK", "back", [this] { delete this; }); mMenu.addButton(_("BACK"), _("back"), [this] { delete this; });
setSize(Renderer::getScreenWidth(), Renderer::getScreenHeight()); setSize(Renderer::getScreenWidth(), Renderer::getScreenHeight());
mMenu.setPosition((mSize.x - mMenu.getSize().x) / 2.0f, Renderer::getScreenHeight() * 0.13f); mMenu.setPosition((mSize.x - mMenu.getSize().x) / 2.0f, Renderer::getScreenHeight() * 0.13f);
@ -77,7 +77,6 @@ void GuiSettings::save()
mCloseMenuFunction = nullptr; mCloseMenuFunction = nullptr;
} }
ViewController::getInstance()->rescanROMDirectory(); ViewController::getInstance()->rescanROMDirectory();
return;
} }
if (mNeedsCollectionsUpdate) { if (mNeedsCollectionsUpdate) {
@ -199,7 +198,8 @@ void GuiSettings::addEditableTextComponent(const std::string label,
row.elements.clear(); row.elements.clear();
auto lbl = std::make_shared<TextComponent>(Utils::String::toUpper(label), auto lbl = std::make_shared<TextComponent>(Utils::String::toUpper(label),
Font::get(FONT_SIZE_MEDIUM), mMenuColorPrimary); Font::get(FONT_SIZE_MEDIUM), mMenuColorPrimary,
ALIGN_LEFT, ALIGN_CENTER, glm::ivec2 {0, 0});
row.addElement(lbl, true); row.addElement(lbl, true);
row.addElement(ed, true); row.addElement(ed, true);
@ -237,23 +237,24 @@ void GuiSettings::addEditableTextComponent(const std::string label,
row.makeAcceptInputHandler([this, label, ed, updateVal, isPassword] { row.makeAcceptInputHandler([this, label, ed, updateVal, isPassword] {
// Never display the value if it's a password, instead set it to blank. // Never display the value if it's a password, instead set it to blank.
if (isPassword) if (isPassword)
mWindow->pushGui( mWindow->pushGui(new GuiTextEditKeyboardPopup(
new GuiTextEditKeyboardPopup(getHelpStyle(), getMenu().getPosition().y, label, getHelpStyle(), getMenu().getPosition().y, label, "", updateVal, false,
"", updateVal, false, "SAVE", "SAVE CHANGES?")); _("SAVE"), _("SAVE CHANGES?")));
else else
mWindow->pushGui(new GuiTextEditKeyboardPopup( mWindow->pushGui(new GuiTextEditKeyboardPopup(
getHelpStyle(), getMenu().getPosition().y, label, ed->getValue(), updateVal, getHelpStyle(), getMenu().getPosition().y, label, ed->getValue(), updateVal,
false, "SAVE", "SAVE CHANGES?")); false, _("SAVE"), _("SAVE CHANGES?")));
}); });
} }
else { else {
row.makeAcceptInputHandler([this, label, ed, updateVal, isPassword] { row.makeAcceptInputHandler([this, label, ed, updateVal, isPassword] {
if (isPassword) if (isPassword)
mWindow->pushGui(new GuiTextEditPopup(getHelpStyle(), label, "", updateVal, false, mWindow->pushGui(new GuiTextEditPopup(getHelpStyle(), label, "", updateVal, false,
"SAVE", "SAVE CHANGES?")); _("SAVE"), _("SAVE CHANGES?")));
else else
mWindow->pushGui(new GuiTextEditPopup(getHelpStyle(), label, ed->getValue(), mWindow->pushGui(new GuiTextEditPopup(getHelpStyle(), label, ed->getValue(),
updateVal, false, "SAVE", "SAVE CHANGES?")); updateVal, false, _("SAVE"),
_("SAVE CHANGES?")));
}); });
} }
@ -276,6 +277,6 @@ bool GuiSettings::input(InputConfig* config, Input input)
std::vector<HelpPrompt> GuiSettings::getHelpPrompts() std::vector<HelpPrompt> GuiSettings::getHelpPrompts()
{ {
std::vector<HelpPrompt> prompts {mMenu.getHelpPrompts()}; std::vector<HelpPrompt> prompts {mMenu.getHelpPrompts()};
prompts.push_back(HelpPrompt("b", "back")); prompts.push_back(HelpPrompt("b", _("back")));
return prompts; return prompts;
} }

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// //
// ES-DE // ES-DE Frontend
// GuiSettings.h // GuiSettings.h
// //
// User interface template for a settings GUI. // User interface template for a settings GUI.

View file

@ -12,6 +12,7 @@
#include "ThemeData.h" #include "ThemeData.h"
#include "components/MenuComponent.h" #include "components/MenuComponent.h"
#include "resources/ResourceManager.h" #include "resources/ResourceManager.h"
#include "utils/LocalizationUtil.h"
#include "rapidjson/document.h" #include "rapidjson/document.h"
#include "rapidjson/error/en.h" #include "rapidjson/error/en.h"
@ -44,8 +45,10 @@ GuiThemeDownloader::GuiThemeDownloader(std::function<void()> updateCallback)
FONT_SIZE_SMALL}; FONT_SIZE_SMALL};
// Set up main grid. // Set up main grid.
mTitle = std::make_shared<TextComponent>("THEME DOWNLOADER", Font::get(FONT_SIZE_LARGE), mTitle = std::make_shared<TextComponent>(
mMenuColorTitle, ALIGN_CENTER); _("THEME DOWNLOADER"),
Font::get(FONT_SIZE_LARGE * Utils::Localization::sMenuTitleScaleFactor), mMenuColorTitle,
ALIGN_CENTER);
mGrid.setEntry(mTitle, glm::ivec2 {0, 0}, false, true, glm::ivec2 {2, 2}, mGrid.setEntry(mTitle, glm::ivec2 {0, 0}, false, true, glm::ivec2 {2, 2},
GridFlags::BORDER_BOTTOM); GridFlags::BORDER_BOTTOM);
@ -128,7 +131,8 @@ GuiThemeDownloader::GuiThemeDownloader(std::function<void()> updateCallback)
mGrid.setEntry(mScrollDown, glm::ivec2 {1, 1}, false, false, glm::ivec2 {1, 1}); mGrid.setEntry(mScrollDown, glm::ivec2 {1, 1}, false, false, glm::ivec2 {1, 1});
std::vector<std::shared_ptr<ButtonComponent>> buttons; std::vector<std::shared_ptr<ButtonComponent>> buttons;
buttons.push_back(std::make_shared<ButtonComponent>("CLOSE", "CLOSE", [&] { delete this; })); buttons.push_back(
std::make_shared<ButtonComponent>(_("CLOSE"), _("close"), [&] { delete this; }));
mButtons = MenuComponent::makeButtonGrid(buttons); mButtons = MenuComponent::makeButtonGrid(buttons);
mGrid.setEntry(mButtons, glm::ivec2 {0, 3}, true, false, glm::ivec2 {2, 1}, mGrid.setEntry(mButtons, glm::ivec2 {0, 3}, true, false, glm::ivec2 {2, 1},
GridFlags::BORDER_TOP); GridFlags::BORDER_TOP);
@ -144,7 +148,7 @@ GuiThemeDownloader::GuiThemeDownloader(std::function<void()> updateCallback)
(mRenderer->getScreenHeight() - mSize.y) / 2.0f); (mRenderer->getScreenHeight() - mSize.y) / 2.0f);
mBusyAnim.setSize(mSize); mBusyAnim.setSize(mSize);
mBusyAnim.setText("DOWNLOADING THEMES LIST 100%"); mBusyAnim.setText(_("DOWNLOADING THEMES LIST 100%"));
mBusyAnim.onSizeChanged(); mBusyAnim.onSizeChanged();
mList->setCursorChangedCallback([this](CursorState state) { mList->setCursorChangedCallback([this](CursorState state) {
@ -343,7 +347,7 @@ bool GuiThemeDownloader::fetchRepository(const std::string& repositoryName, bool
LOG(LogInfo) << "GuiThemeDownloader: Repository \"" << repositoryName LOG(LogInfo) << "GuiThemeDownloader: Repository \"" << repositoryName
<< "\" already up to date"; << "\" already up to date";
if (repositoryName != "themes-list") if (repositoryName != "themes-list")
mMessage = "THEME ALREADY UP TO DATE"; mMessage = _("THEME ALREADY UP TO DATE");
git_annotated_commit_free(annotated); git_annotated_commit_free(annotated);
git_object_free(object); git_object_free(object);
git_remote_free(gitRemote); git_remote_free(gitRemote);
@ -405,7 +409,7 @@ bool GuiThemeDownloader::fetchRepository(const std::string& repositoryName, bool
} }
if (repositoryName != "themes-list") { if (repositoryName != "themes-list") {
mMessage = "THEME HAS BEEN UPDATED"; mMessage = _("THEME HAS BEEN UPDATED");
mHasThemeUpdates = true; mHasThemeUpdates = true;
} }
@ -579,8 +583,10 @@ bool GuiThemeDownloader::renameDirectory(const std::string& path, const std::str
if (renameStatus) { if (renameStatus) {
mWindow->pushGui(new GuiMsgBox( mWindow->pushGui(new GuiMsgBox(
getHelpStyle(), "COULDN'T RENAME DIRECTORY \"" + path + "\", PERMISSION PROBLEMS?", getHelpStyle(),
"OK", [] { return; }, "", nullptr, "", nullptr, nullptr, true)); Utils::String::format(_("COULDN'T RENAME DIRECTORY \"%s\"\nPERMISSION PROBLEMS?"),
path.c_str()),
_("OK"), [] { return; }, "", nullptr, "", nullptr, nullptr, true));
return true; return true;
} }
else { else {
@ -601,7 +607,7 @@ void GuiThemeDownloader::parseThemesList()
if (!Utils::FileSystem::exists(themesFile)) { if (!Utils::FileSystem::exists(themesFile)) {
LOG(LogError) << "GuiThemeDownloader: No themes.json file found"; LOG(LogError) << "GuiThemeDownloader: No themes.json file found";
mWindow->pushGui(new GuiMsgBox( mWindow->pushGui(new GuiMsgBox(
getHelpStyle(), "COULDN'T FIND THE THEMES LIST CONFIGURATION FILE", "OK", getHelpStyle(), _("COULDN'T FIND THE THEMES LIST CONFIGURATION FILE"), _("OK"),
[] { return; }, "", nullptr, "", nullptr, nullptr, true)); [] { return; }, "", nullptr, "", nullptr, nullptr, true));
mGrid.removeEntry(mCenterGrid); mGrid.removeEntry(mCenterGrid);
mGrid.setCursorTo(mButtons); mGrid.setCursorTo(mButtons);
@ -616,9 +622,9 @@ void GuiThemeDownloader::parseThemesList()
LOG(LogError) << "GuiThemeDownloader: Couldn't parse the themes.json file"; LOG(LogError) << "GuiThemeDownloader: Couldn't parse the themes.json file";
mWindow->pushGui(new GuiMsgBox( mWindow->pushGui(new GuiMsgBox(
getHelpStyle(), getHelpStyle(),
"COULDN'T PARSE THE THEMES LIST CONFIGURATION FILE, MAYBE THE LOCAL REPOSITORY IS " _("COULDN'T PARSE THE THEMES LIST CONFIGURATION FILE, MAYBE THE LOCAL REPOSITORY IS "
"CORRUPT?", "CORRUPT?"),
"OK", [] { return; }, "", nullptr, "", nullptr, nullptr, true)); _("OK"), [] { return; }, "", nullptr, "", nullptr, nullptr, true));
mGrid.removeEntry(mCenterGrid); mGrid.removeEntry(mCenterGrid);
mGrid.setCursorTo(mButtons); mGrid.setCursorTo(mButtons);
return; return;
@ -631,9 +637,9 @@ void GuiThemeDownloader::parseThemesList()
"downloading is not recommended"; "downloading is not recommended";
mWindow->pushGui(new GuiMsgBox( mWindow->pushGui(new GuiMsgBox(
getHelpStyle(), getHelpStyle(),
"ES-DE THEME ENGINE WAS UPDATED UPSTREAM. THESE THEMES MAY NOT BE COMPATIBLE WITH THE CURRENT RETRODECK VERSION." _("ES-DE THEME ENGINE WAS UPDATED UPSTREAM. THESE THEMES MAY NOT BE COMPATIBLE WITH THE CURRENT RETRODECK VERSION."
"CHECK IF A NEW RETRODECK UPDATE IS AVAILABLE, ELSE PLEASE WAIT FOR IT OR PROCEED AT YOUR OWN RISK.", "CHECK IF A NEW RETRODECK UPDATE IS AVAILABLE, ELSE PLEASE WAIT FOR IT OR PROCEED AT YOUR OWN RISK."),
"OK", [] { return; }, "", nullptr, "", nullptr, nullptr, true)); _("OK"), [] { return; }, "", nullptr, "", nullptr, nullptr, true));
} }
} }
@ -659,8 +665,13 @@ void GuiThemeDownloader::parseThemesList()
if (theme.HasMember("url") && theme["url"].IsString()) if (theme.HasMember("url") && theme["url"].IsString())
themeEntry.url = theme["url"].GetString(); themeEntry.url = theme["url"].GetString();
if (theme.HasMember("author") && theme["author"].IsString()) if (theme.HasMember("author") && theme["author"].IsString()) {
themeEntry.author = theme["author"].GetString(); themeEntry.author = theme["author"].GetString();
if (themeEntry.author.find(" and ") != std::string::npos) {
themeEntry.author = Utils::String::replace(themeEntry.author, " and ",
" " + _("and") + " ");
}
}
if (theme.HasMember("newEntry") && theme["newEntry"].IsBool()) if (theme.HasMember("newEntry") && theme["newEntry"].IsBool())
themeEntry.newEntry = theme["newEntry"].GetBool(); themeEntry.newEntry = theme["newEntry"].GetBool();
@ -759,19 +770,20 @@ void GuiThemeDownloader::populateGUI()
ThemeGUIEntry guiEntry; ThemeGUIEntry guiEntry;
guiEntry.themeName = themeNameElement; guiEntry.themeName = themeNameElement;
mThemeGUIEntries.emplace_back(guiEntry); mThemeGUIEntries.emplace_back(guiEntry);
row.addElement(themeNameElement, false); row.addElement(themeNameElement, false, true, glm::ivec2 {1, 0});
row.makeAcceptInputHandler([this, &theme] { row.makeAcceptInputHandler([this, &theme] {
std::promise<bool>().swap(mPromise); std::promise<bool>().swap(mPromise);
if (theme.manuallyDownloaded || theme.invalidRepository) { if (theme.manuallyDownloaded || theme.invalidRepository) {
mWindow->pushGui(new GuiMsgBox( mWindow->pushGui(new GuiMsgBox(
getHelpStyle(), getHelpStyle(),
"IT SEEMS AS IF THIS THEME HAS BEEN MANUALLY DOWNLOADED INSTEAD OF VIA " Utils::String::format(
"THIS THEME DOWNLOADER. A FRESH DOWNLOAD IS REQUIRED AND THE OLD THEME " _("IT SEEMS AS IF THIS THEME HAS BEEN MANUALLY DOWNLOADED INSTEAD OF VIA "
"DIRECTORY \"" + "THIS THEME DOWNLOADER. A FRESH DOWNLOAD IS REQUIRED AND THE OLD THEME "
theme.reponame + theme.manualExtension + "\" WILL BE RENAMED TO \"" + "DIRECTORY \"%s\" WILL BE RENAMED TO \"%s_DISABLED\""),
theme.reponame + theme.manualExtension + "_DISABLED\"", std::string {theme.reponame + theme.manualExtension}.c_str(),
"PROCEED", std::string {theme.reponame + theme.manualExtension}.c_str()),
_("PROCEED"),
[this, theme] { [this, theme] {
if (renameDirectory(mThemeDirectory + theme.reponame + if (renameDirectory(mThemeDirectory + theme.reponame +
theme.manualExtension, theme.manualExtension,
@ -783,9 +795,9 @@ void GuiThemeDownloader::populateGUI()
mFetchThread = std::thread(&GuiThemeDownloader::cloneRepository, this, mFetchThread = std::thread(&GuiThemeDownloader::cloneRepository, this,
theme.reponame, theme.url); theme.reponame, theme.url);
mStatusType = StatusType::STATUS_DOWNLOADING; mStatusType = StatusType::STATUS_DOWNLOADING;
mStatusText = "DOWNLOADING THEME"; mStatusText = _("DOWNLOADING THEME");
}, },
"CANCEL", [] { return; }, "", nullptr, nullptr, false, true, _("CANCEL"), [] { return; }, "", nullptr, nullptr, false, true,
(mRenderer->getIsVerticalOrientation() ? (mRenderer->getIsVerticalOrientation() ?
0.75f : 0.75f :
0.46f * (1.778f / mRenderer->getScreenAspectRatio())))); 0.46f * (1.778f / mRenderer->getScreenAspectRatio()))));
@ -793,13 +805,14 @@ void GuiThemeDownloader::populateGUI()
else if (theme.corruptRepository) { else if (theme.corruptRepository) {
mWindow->pushGui(new GuiMsgBox( mWindow->pushGui(new GuiMsgBox(
getHelpStyle(), getHelpStyle(),
"IT SEEMS AS IF THIS THEME REPOSITORY IS CORRUPT, WHICH COULD HAVE BEEN CAUSED " Utils::String::format(
"BY AN INTERRUPTION OF A PREVIOUS DOWNLOAD OR UPDATE, FOR EXAMPLE IF THE ES-DE " _("IT SEEMS AS IF THIS THEME REPOSITORY IS CORRUPT, WHICH COULD HAVE BEEN "
"PROCESS WAS KILLED. A FRESH DOWNLOAD IS REQUIRED AND THE OLD THEME DIRECTORY " "CAUSED BY AN INTERRUPTION OF A PREVIOUS DOWNLOAD OR UPDATE, FOR EXAMPLE "
"\"" + "IF THE ES-DE PROCESS WAS KILLED. A FRESH DOWNLOAD IS REQUIRED AND THE "
theme.reponame + theme.manualExtension + "\" WILL BE RENAMED TO \"" + "OLD THEME DIRECTORY \"%s\" WILL BE RENAMED TO \"%s_CORRUPT_DISABLED\""),
theme.reponame + theme.manualExtension + "_CORRUPT_DISABLED\"", std::string {theme.reponame + theme.manualExtension}.c_str(),
"PROCEED", std::string {theme.reponame + theme.manualExtension}.c_str()),
_("PROCEED"),
[this, theme] { [this, theme] {
if (renameDirectory(mThemeDirectory + theme.reponame + if (renameDirectory(mThemeDirectory + theme.reponame +
theme.manualExtension, theme.manualExtension,
@ -811,9 +824,9 @@ void GuiThemeDownloader::populateGUI()
mFetchThread = std::thread(&GuiThemeDownloader::cloneRepository, this, mFetchThread = std::thread(&GuiThemeDownloader::cloneRepository, this,
theme.reponame, theme.url); theme.reponame, theme.url);
mStatusType = StatusType::STATUS_DOWNLOADING; mStatusType = StatusType::STATUS_DOWNLOADING;
mStatusText = "DOWNLOADING THEME"; mStatusText = _("DOWNLOADING THEME");
}, },
"CANCEL", [] { return; }, "", nullptr, nullptr, false, true, _("CANCEL"), [] { return; }, "", nullptr, nullptr, false, true,
(mRenderer->getIsVerticalOrientation() ? (mRenderer->getIsVerticalOrientation() ?
0.75f : 0.75f :
0.46f * (1.778f / mRenderer->getScreenAspectRatio())))); 0.46f * (1.778f / mRenderer->getScreenAspectRatio()))));
@ -821,12 +834,14 @@ void GuiThemeDownloader::populateGUI()
else if (theme.shallowRepository) { else if (theme.shallowRepository) {
mWindow->pushGui(new GuiMsgBox( mWindow->pushGui(new GuiMsgBox(
getHelpStyle(), getHelpStyle(),
"IT SEEMS AS IF THIS IS A SHALLOW REPOSITORY WHICH MEANS THAT IT HAS BEEN " Utils::String::format(
"DOWNLOADED USING SOME OTHER TOOL THAN THIS THEME DOWNLOADER. A FRESH DOWNLOAD " _("IT SEEMS AS IF THIS IS A SHALLOW REPOSITORY WHICH MEANS THAT IT HAS "
"IS REQUIRED AND THE OLD THEME DIRECTORY \"" + "BEEN DOWNLOADED USING SOME OTHER TOOL THAN THIS THEME DOWNLOADER. A "
theme.reponame + theme.manualExtension + "\" WILL BE RENAMED TO \"" + "FRESH DOWNLOAD IS REQUIRED AND THE OLD THEME DIRECTORY \"%s\" WILL BE "
theme.reponame + theme.manualExtension + "_DISABLED\"", "RENAMED TO \"%s_DISABLED\""),
"PROCEED", std::string {theme.reponame + theme.manualExtension}.c_str(),
std::string {theme.reponame + theme.manualExtension}.c_str()),
_("PROCEED"),
[this, theme] { [this, theme] {
if (renameDirectory(mThemeDirectory + theme.reponame + if (renameDirectory(mThemeDirectory + theme.reponame +
theme.manualExtension, theme.manualExtension,
@ -838,9 +853,9 @@ void GuiThemeDownloader::populateGUI()
mFetchThread = std::thread(&GuiThemeDownloader::cloneRepository, this, mFetchThread = std::thread(&GuiThemeDownloader::cloneRepository, this,
theme.reponame, theme.url); theme.reponame, theme.url);
mStatusType = StatusType::STATUS_DOWNLOADING; mStatusType = StatusType::STATUS_DOWNLOADING;
mStatusText = "DOWNLOADING THEME"; mStatusText = _("DOWNLOADING THEME");
}, },
"CANCEL", [] { return; }, "", nullptr, nullptr, false, true, _("CANCEL"), [] { return; }, "", nullptr, nullptr, false, true,
(mRenderer->getIsVerticalOrientation() ? (mRenderer->getIsVerticalOrientation() ?
0.75f : 0.75f :
0.46f * (1.778f / mRenderer->getScreenAspectRatio())))); 0.46f * (1.778f / mRenderer->getScreenAspectRatio()))));
@ -848,19 +863,20 @@ void GuiThemeDownloader::populateGUI()
else if (theme.hasLocalChanges) { else if (theme.hasLocalChanges) {
mWindow->pushGui(new GuiMsgBox( mWindow->pushGui(new GuiMsgBox(
getHelpStyle(), getHelpStyle(),
"THEME REPOSITORY \"" + theme.reponame + Utils::String::format(
"\" CONTAINS LOCAL CHANGES. PROCEED TO OVERWRITE YOUR CHANGES " _("THEME REPOSITORY \"%s\" CONTAINS LOCAL CHANGES. PROCEED TO OVERWRITE "
"OR CANCEL TO SKIP ALL UPDATES FOR THIS THEME", "YOUR CHANGES OR CANCEL TO SKIP ALL UPDATES FOR THIS THEME"),
"PROCEED", std::string {theme.reponame}.c_str()),
_("PROCEED"),
[this, theme] { [this, theme] {
std::promise<bool>().swap(mPromise); std::promise<bool>().swap(mPromise);
mFuture = mPromise.get_future(); mFuture = mPromise.get_future();
mFetchThread = std::thread(&GuiThemeDownloader::fetchRepository, this, mFetchThread = std::thread(&GuiThemeDownloader::fetchRepository, this,
theme.reponame, true); theme.reponame, true);
mStatusType = StatusType::STATUS_UPDATING; mStatusType = StatusType::STATUS_UPDATING;
mStatusText = "UPDATING THEME"; mStatusText = _("UPDATING THEME");
}, },
"CANCEL", [] { return; }, "", nullptr, nullptr, false, true, _("CANCEL"), [] { return; }, "", nullptr, nullptr, false, true,
(mRenderer->getIsVerticalOrientation() ? (mRenderer->getIsVerticalOrientation() ?
0.75f : 0.75f :
0.45f * (1.778f / mRenderer->getScreenAspectRatio())))); 0.45f * (1.778f / mRenderer->getScreenAspectRatio()))));
@ -870,24 +886,24 @@ void GuiThemeDownloader::populateGUI()
mFetchThread = mFetchThread =
std::thread(&GuiThemeDownloader::fetchRepository, this, theme.reponame, false); std::thread(&GuiThemeDownloader::fetchRepository, this, theme.reponame, false);
mStatusType = StatusType::STATUS_UPDATING; mStatusType = StatusType::STATUS_UPDATING;
mStatusText = "UPDATING THEME"; mStatusText = _("UPDATING THEME");
} }
else { else {
mFuture = mPromise.get_future(); mFuture = mPromise.get_future();
mFetchThread = std::thread(&GuiThemeDownloader::cloneRepository, this, mFetchThread = std::thread(&GuiThemeDownloader::cloneRepository, this,
theme.reponame, theme.url); theme.reponame, theme.url);
mStatusType = StatusType::STATUS_DOWNLOADING; mStatusType = StatusType::STATUS_DOWNLOADING;
mStatusText = "DOWNLOADING THEME"; mStatusText = _("DOWNLOADING THEME");
} }
mWindow->stopInfoPopup(); mWindow->stopInfoPopup();
}); });
mList->addRow(row); mList->addRow(row);
} }
mVariantsLabel->setText("VARIANTS:"); mVariantsLabel->setText(_("VARIANTS:"));
mColorSchemesLabel->setText("COLOR SCHEMES:"); mColorSchemesLabel->setText(_("COLOR SCHEMES:"));
mAspectRatiosLabel->setText("ASPECT RATIOS:"); mAspectRatiosLabel->setText(_("ASPECT RATIOS:"));
mFontSizesLabel->setText("FONT SIZES:"); mFontSizesLabel->setText(_("FONT SIZES:"));
updateInfoPane(); updateInfoPane();
updateHelpPrompts(); updateHelpPrompts();
@ -934,36 +950,36 @@ void GuiThemeDownloader::updateInfoPane()
} }
if (mThemes[mList->getCursorId()].isCloned) { if (mThemes[mList->getCursorId()].isCloned) {
mDownloadStatus->setText(ViewController::TICKMARK_CHAR + " INSTALLED"); mDownloadStatus->setText(ViewController::TICKMARK_CHAR + " " + _("INSTALLED"));
mDownloadStatus->setColor(mMenuColorGreen); mDownloadStatus->setColor(mMenuColorGreen);
mDownloadStatus->setOpacity(1.0f); mDownloadStatus->setOpacity(1.0f);
} }
else if (mThemes[mList->getCursorId()].invalidRepository || else if (mThemes[mList->getCursorId()].invalidRepository ||
mThemes[mList->getCursorId()].manuallyDownloaded) { mThemes[mList->getCursorId()].manuallyDownloaded) {
mDownloadStatus->setText(ViewController::CROSSEDCIRCLE_CHAR + " MANUAL DOWNLOAD"); mDownloadStatus->setText(ViewController::CROSSEDCIRCLE_CHAR + " " + _("MANUAL DOWNLOAD"));
mDownloadStatus->setColor(mMenuColorRed); mDownloadStatus->setColor(mMenuColorRed);
mDownloadStatus->setOpacity(1.0f); mDownloadStatus->setOpacity(1.0f);
} }
else if (mThemes[mList->getCursorId()].corruptRepository) { else if (mThemes[mList->getCursorId()].corruptRepository) {
mDownloadStatus->setText(ViewController::CROSSEDCIRCLE_CHAR + " CORRUPT"); mDownloadStatus->setText(ViewController::CROSSEDCIRCLE_CHAR + " " + _("CORRUPT"));
mDownloadStatus->setColor(mMenuColorRed); mDownloadStatus->setColor(mMenuColorRed);
mDownloadStatus->setOpacity(1.0f); mDownloadStatus->setOpacity(1.0f);
} }
else if (mThemes[mList->getCursorId()].shallowRepository) { else if (mThemes[mList->getCursorId()].shallowRepository) {
mDownloadStatus->setText(ViewController::CROSSEDCIRCLE_CHAR + " SHALLOW"); mDownloadStatus->setText(ViewController::CROSSEDCIRCLE_CHAR + " " + _("SHALLOW"));
mDownloadStatus->setColor(mMenuColorRed); mDownloadStatus->setColor(mMenuColorRed);
mDownloadStatus->setOpacity(1.0f); mDownloadStatus->setOpacity(1.0f);
} }
else { else {
if (mThemes[mList->getCursorId()].newEntry) if (mThemes[mList->getCursorId()].newEntry)
mDownloadStatus->setText("NOT INSTALLED (NEW)"); mDownloadStatus->setText(_("NOT INSTALLED (NEW)"));
else else
mDownloadStatus->setText("NOT INSTALLED"); mDownloadStatus->setText(_("NOT INSTALLED"));
mDownloadStatus->setColor(mMenuColorPrimary); mDownloadStatus->setColor(mMenuColorPrimary);
mDownloadStatus->setOpacity(0.7f); mDownloadStatus->setOpacity(0.7f);
} }
if (mThemes[mList->getCursorId()].hasLocalChanges) { if (mThemes[mList->getCursorId()].hasLocalChanges) {
mLocalChanges->setText(ViewController::EXCLAMATION_CHAR + " LOCAL CHANGES"); mLocalChanges->setText(ViewController::EXCLAMATION_CHAR + " " + _("LOCAL CHANGES"));
mLocalChanges->setColor(mMenuColorRed); mLocalChanges->setColor(mMenuColorRed);
} }
else { else {
@ -975,9 +991,9 @@ void GuiThemeDownloader::updateInfoPane()
mAspectRatiosCount->setText(std::to_string(mThemes[mList->getCursorId()].aspectRatios.size())); mAspectRatiosCount->setText(std::to_string(mThemes[mList->getCursorId()].aspectRatios.size()));
mFontSizesCount->setText(std::to_string(mThemes[mList->getCursorId()].fontSizes.size())); mFontSizesCount->setText(std::to_string(mThemes[mList->getCursorId()].fontSizes.size()));
if (mThemes[mList->getCursorId()].deprecated) if (mThemes[mList->getCursorId()].deprecated)
mAuthor->setText("THIS THEME ENTRY WILL BE REMOVED IN THE NEAR FUTURE"); mAuthor->setText(_("THIS THEME ENTRY WILL BE REMOVED IN THE NEAR FUTURE"));
else else
mAuthor->setText("CREATED BY " + mAuthor->setText(_("CREATED BY") + " " +
Utils::String::toUpper(mThemes[mList->getCursorId()].author)); Utils::String::toUpper(mThemes[mList->getCursorId()].author));
} }
@ -1046,15 +1062,16 @@ void GuiThemeDownloader::update(int deltaTime)
mFetchThread.join(); mFetchThread.join();
mFetching = false; mFetching = false;
if (mRepositoryError != RepositoryError::NO_REPO_ERROR) { if (mRepositoryError != RepositoryError::NO_REPO_ERROR) {
std::string errorMessage {"ERROR: "}; std::string errorMessage {_("ERROR:")};
if (mThemes.empty()) { if (mThemes.empty()) {
errorMessage.append("COULDN'T DOWNLOAD THEMES LIST, "); errorMessage.append(" ").append(
_("COULDN'T DOWNLOAD THEMES LIST").append(","));
mGrid.removeEntry(mCenterGrid); mGrid.removeEntry(mCenterGrid);
mGrid.setCursorTo(mButtons); mGrid.setCursorTo(mButtons);
} }
errorMessage.append(Utils::String::toUpper(mMessage)); errorMessage.append(" ").append(Utils::String::toUpper(mMessage));
mWindow->pushGui(new GuiMsgBox( mWindow->pushGui(new GuiMsgBox(
getHelpStyle(), errorMessage, "OK", [] { return; }, "", nullptr, "", getHelpStyle(), errorMessage, _("OK"), [] { return; }, "", nullptr, "",
nullptr, nullptr, true)); nullptr, nullptr, true));
mMessage = ""; mMessage = "";
getHelpPrompts(); getHelpPrompts();
@ -1085,7 +1102,12 @@ void GuiThemeDownloader::update(int deltaTime)
if (mReceivedObjectsProgress != 1.0f) { if (mReceivedObjectsProgress != 1.0f) {
progress = static_cast<int>( progress = static_cast<int>(
std::round(glm::mix(0.0f, 100.0f, static_cast<float>(mReceivedObjectsProgress)))); std::round(glm::mix(0.0f, 100.0f, static_cast<float>(mReceivedObjectsProgress))));
if (mStatusText.substr(0, 11) == "DOWNLOADING") if (mStatusText.substr(0, std::string {_("DOWNLOADING")}.length()) ==
_("DOWNLOADING") ||
mStatusText.substr(0, std::string {_("DOWNLOADING THEME")}.length()) ==
_("DOWNLOADING THEME") ||
mStatusText.substr(0, std::string {_("DOWNLOADING THEMES LIST")}.length()) ==
_("DOWNLOADING THEMES LIST"))
mBusyAnim.setText(mStatusText + " " + std::to_string(progress) + "%"); mBusyAnim.setText(mStatusText + " " + std::to_string(progress) + "%");
else else
mBusyAnim.setText(mStatusText); mBusyAnim.setText(mStatusText);
@ -1093,7 +1115,12 @@ void GuiThemeDownloader::update(int deltaTime)
else if (mReceivedObjectsProgress != 0.0f) { else if (mReceivedObjectsProgress != 0.0f) {
progress = static_cast<int>( progress = static_cast<int>(
std::round(glm::mix(0.0f, 100.0f, static_cast<float>(mResolveDeltaProgress)))); std::round(glm::mix(0.0f, 100.0f, static_cast<float>(mResolveDeltaProgress))));
if (mStatusText.substr(0, 11) == "DOWNLOADING") if (mStatusText.substr(0, std::string {_("DOWNLOADING")}.length()) ==
_("DOWNLOADING") ||
mStatusText.substr(0, std::string {_("DOWNLOADING THEME")}.length()) ==
_("DOWNLOADING THEME") ||
mStatusText.substr(0, std::string {_("DOWNLOADING THEMES LIST")}.length()) ==
_("DOWNLOADING THEMES LIST"))
mBusyAnim.setText(mStatusText + " " + std::to_string(progress) + "%"); mBusyAnim.setText(mStatusText + " " + std::to_string(progress) + "%");
else else
mBusyAnim.setText(mStatusText); mBusyAnim.setText(mStatusText);
@ -1158,10 +1185,10 @@ void GuiThemeDownloader::onSizeChanged()
mGrid.setColWidthPerc(1, 0.04f); mGrid.setColWidthPerc(1, 0.04f);
mCenterGrid->setColWidthPerc(0, 0.01f); mCenterGrid->setColWidthPerc(0, 0.01f);
mCenterGrid->setColWidthPerc(1, (mRenderer->getScreenAspectRatio() < 1.6f ? 0.21f : 0.18f)); mCenterGrid->setColWidthPerc(1, (mRenderer->getScreenAspectRatio() < 1.6f ? 0.22f : 0.18f));
mCenterGrid->setColWidthPerc(2, 0.05f); mCenterGrid->setColWidthPerc(2, 0.05f);
mCenterGrid->setColWidthPerc(3, 0.18f); mCenterGrid->setColWidthPerc(3, (mRenderer->getScreenAspectRatio() < 1.6f ? 0.215f : 0.18f));
mCenterGrid->setColWidthPerc(4, 0.04f); mCenterGrid->setColWidthPerc(4, 0.035f);
mCenterGrid->setColWidthPerc(5, 0.005f); mCenterGrid->setColWidthPerc(5, 0.005f);
mCenterGrid->setColWidthPerc(7, 0.04f); mCenterGrid->setColWidthPerc(7, 0.04f);
@ -1232,12 +1259,11 @@ bool GuiThemeDownloader::input(InputConfig* config, Input input)
mWindow->pushGui(new GuiMsgBox( mWindow->pushGui(new GuiMsgBox(
getHelpStyle(), getHelpStyle(),
#if defined(__ANDROID__) #if defined(__ANDROID__)
"THIS WILL COMPLETELY DELETE THE THEME", _("THIS WILL COMPLETELY DELETE THE THEME"),
#else #else
"THIS WILL COMPLETELY DELETE THE THEME INCLUDING ANY " _("THIS WILL COMPLETELY DELETE THE THEME INCLUDING ANY LOCAL CUSTOMIZATIONS"),
"LOCAL CUSTOMIZATIONS",
#endif #endif
"PROCEED", _("PROCEED"),
[this] { [this] {
#if defined(_WIN64) #if defined(_WIN64)
const std::string themeDirectory { const std::string themeDirectory {
@ -1250,17 +1276,17 @@ bool GuiThemeDownloader::input(InputConfig* config, Input input)
LOG(LogInfo) << "Deleting theme directory \"" << themeDirectory << "\""; LOG(LogInfo) << "Deleting theme directory \"" << themeDirectory << "\"";
if (!Utils::FileSystem::removeDirectory(themeDirectory, true)) { if (!Utils::FileSystem::removeDirectory(themeDirectory, true)) {
mWindow->pushGui(new GuiMsgBox( mWindow->pushGui(new GuiMsgBox(
getHelpStyle(), "COULDN'T DELETE THEME, PERMISSION PROBLEMS?", "OK", getHelpStyle(), _("COULDN'T DELETE THEME, PERMISSION PROBLEMS?"), _("OK"),
[] { return; }, "", nullptr, "", nullptr, nullptr, true)); [] { return; }, "", nullptr, "", nullptr, nullptr, true));
} }
else { else {
mMessage = "THEME WAS DELETED"; mMessage = _("THEME WAS DELETED");
} }
mHasThemeUpdates = true; mHasThemeUpdates = true;
makeInventory(); makeInventory();
updateGUI(); updateGUI();
}, },
"CANCEL", nullptr, "", nullptr, nullptr, false, true, _("CANCEL"), nullptr, "", nullptr, nullptr, false, true,
(mRenderer->getIsVerticalOrientation() ? (mRenderer->getIsVerticalOrientation() ?
0.70f : 0.70f :
0.44f * (1.778f / mRenderer->getScreenAspectRatio())))); 0.44f * (1.778f / mRenderer->getScreenAspectRatio()))));
@ -1276,22 +1302,22 @@ std::vector<HelpPrompt> GuiThemeDownloader::getHelpPrompts()
if (mList->size() > 0) { if (mList->size() > 0) {
prompts = mGrid.getHelpPrompts(); prompts = mGrid.getHelpPrompts();
prompts.push_back(HelpPrompt("b", "close")); prompts.push_back(HelpPrompt("b", _("close")));
if (mGrid.getSelectedComponent() == mCenterGrid) if (mGrid.getSelectedComponent() == mCenterGrid)
prompts.push_back(HelpPrompt("x", "view screenshots")); prompts.push_back(HelpPrompt("x", _("view screenshots")));
if (mThemes[mList->getCursorId()].isCloned) { if (mThemes[mList->getCursorId()].isCloned) {
prompts.push_back(HelpPrompt("a", "fetch updates")); prompts.push_back(HelpPrompt("a", _("fetch updates")));
if (mGrid.getSelectedComponent() == mCenterGrid) if (mGrid.getSelectedComponent() == mCenterGrid)
prompts.push_back(HelpPrompt("y", "delete")); prompts.push_back(HelpPrompt("y", _("delete")));
} }
else { else {
prompts.push_back(HelpPrompt("a", "download")); prompts.push_back(HelpPrompt("a", _("download")));
} }
} }
else { else {
prompts.push_back(HelpPrompt("b", "close")); prompts.push_back(HelpPrompt("b", _("close")));
} }
return prompts; return prompts;
@ -1310,11 +1336,11 @@ bool GuiThemeDownloader::fetchThemesList()
if (errorCode != 0 || checkCorruptRepository(repository)) { if (errorCode != 0 || checkCorruptRepository(repository)) {
mWindow->pushGui(new GuiMsgBox( mWindow->pushGui(new GuiMsgBox(
getHelpStyle(), getHelpStyle(),
"IT SEEMS AS IF THE THEMES LIST REPOSITORY IS CORRUPT, WHICH COULD HAVE BEEN " _("IT SEEMS AS IF THE THEMES LIST REPOSITORY IS CORRUPT, WHICH COULD HAVE BEEN "
"CAUSED BY AN INTERRUPTION OF A PREVIOUS DOWNLOAD OR UPDATE, FOR EXAMPLE IF THE " "CAUSED BY AN INTERRUPTION OF A PREVIOUS DOWNLOAD OR UPDATE, FOR EXAMPLE IF THE "
"ES-DE PROCESS WAS KILLED. A FRESH DOWNLOAD IS REQUIRED AND THE OLD DIRECTORY " "ES-DE PROCESS WAS KILLED. A FRESH DOWNLOAD IS REQUIRED AND THE OLD DIRECTORY "
"\"themes-list\" WILL BE RENAMED TO \"themes-list_CORRUPT_DISABLED\"", "\"themes-list\" WILL BE RENAMED TO \"themes-list_CORRUPT_DISABLED\""),
"PROCEED", _("PROCEED"),
[this, repositoryName, url] { [this, repositoryName, url] {
if (renameDirectory(mThemeDirectory + "themes-list", "_CORRUPT_DISABLED")) { if (renameDirectory(mThemeDirectory + "themes-list", "_CORRUPT_DISABLED")) {
mGrid.removeEntry(mCenterGrid); mGrid.removeEntry(mCenterGrid);
@ -1326,10 +1352,10 @@ bool GuiThemeDownloader::fetchThemesList()
mFetchThread = std::thread(&GuiThemeDownloader::cloneRepository, this, mFetchThread = std::thread(&GuiThemeDownloader::cloneRepository, this,
repositoryName, url); repositoryName, url);
mStatusType = StatusType::STATUS_DOWNLOADING; mStatusType = StatusType::STATUS_DOWNLOADING;
mStatusText = "DOWNLOADING THEMES LIST"; mStatusText = _("DOWNLOADING THEMES LIST");
return false; return false;
}, },
"CANCEL", _("CANCEL"),
[&] { [&] {
delete this; delete this;
return false; return false;
@ -1346,28 +1372,28 @@ bool GuiThemeDownloader::fetchThemesList()
mFetchThread = mFetchThread =
std::thread(&GuiThemeDownloader::fetchRepository, this, repositoryName, false); std::thread(&GuiThemeDownloader::fetchRepository, this, repositoryName, false);
mStatusType = StatusType::STATUS_UPDATING; mStatusType = StatusType::STATUS_UPDATING;
mStatusText = "UPDATING THEMES LIST"; mStatusText = _("UPDATING THEMES LIST");
} }
git_repository_free(repository); git_repository_free(repository);
} }
else { else {
mWindow->pushGui(new GuiMsgBox( mWindow->pushGui(new GuiMsgBox(
getHelpStyle(), getHelpStyle(),
"IT SEEMS AS IF YOU'RE USING THE THEME DOWNLOADER FOR THE FIRST TIME. " _("IT SEEMS AS IF YOU'RE USING THE THEME DOWNLOADER FOR THE FIRST TIME. "
"AS SUCH THE THEMES LIST REPOSITORY WILL BE DOWNLOADED WHICH WILL TAKE A LITTLE " "AS SUCH THE THEMES LIST REPOSITORY WILL BE DOWNLOADED WHICH WILL TAKE A LITTLE "
"WHILE. SUBSEQUENT RUNS WILL HOWEVER BE MUCH FASTER AS ONLY NEW OR MODIFIED FILES " "WHILE. SUBSEQUENT RUNS WILL HOWEVER BE MUCH FASTER AS ONLY NEW OR MODIFIED FILES "
"WILL BE FETCHED. THE SAME IS TRUE FOR ANY THEMES YOU DOWNLOAD. NOTE THAT YOU CAN'T " "WILL BE FETCHED. THE SAME IS TRUE FOR ANY THEMES YOU DOWNLOAD. NOTE THAT YOU CAN'T "
"ABORT AN ONGOING DOWNLOAD AS THAT COULD LEAD TO DATA CORRUPTION.", "ABORT AN ONGOING DOWNLOAD AS THAT COULD LEAD TO DATA CORRUPTION."),
"PROCEED", _("PROCEED"),
[this, repositoryName, url] { [this, repositoryName, url] {
LOG(LogInfo) << "GuiThemeDownloader: Creating initial themes list repository clone"; LOG(LogInfo) << "GuiThemeDownloader: Creating initial themes list repository clone";
mFetchThread = mFetchThread =
std::thread(&GuiThemeDownloader::cloneRepository, this, repositoryName, url); std::thread(&GuiThemeDownloader::cloneRepository, this, repositoryName, url);
mStatusType = StatusType::STATUS_DOWNLOADING; mStatusType = StatusType::STATUS_DOWNLOADING;
mStatusText = "DOWNLOADING THEMES LIST"; mStatusText = _("DOWNLOADING THEMES LIST");
return false; return false;
}, },
"CANCEL", _("CANCEL"),
[&] { [&] {
delete this; delete this;
return false; return false;

View file

@ -29,6 +29,7 @@
#include "guis/GuiDetectDevice.h" #include "guis/GuiDetectDevice.h"
#include "guis/GuiLaunchScreen.h" #include "guis/GuiLaunchScreen.h"
#include "utils/FileSystemUtil.h" #include "utils/FileSystemUtil.h"
#include "utils/LocalizationUtil.h"
#include "utils/PlatformUtil.h" #include "utils/PlatformUtil.h"
#include "utils/StringUtil.h" #include "utils/StringUtil.h"
#include "views/ViewController.h" #include "views/ViewController.h"
@ -1012,6 +1013,25 @@ int main(int argc, char* argv[])
} }
} }
#if defined(__ANDROID__)
// We need to copy the font and locale files before starting the renderer as HarfBuzz
// and libintl need them before that point.
std::string buildIdentifier {PROGRAM_VERSION_STRING};
buildIdentifier.append(" (r")
.append(std::to_string(PROGRAM_RELEASE_NUMBER))
.append("), built ")
.append(PROGRAM_BUILT_STRING);
const bool needResourceCopy {Utils::Platform::Android::checkNeedResourceCopy(buildIdentifier)};
if (needResourceCopy) {
LOG(LogInfo) << "Application has been updated or it's a new installation, copying "
"bundled fonts and locales to internal storage...";
Utils::Platform::Android::setupFontFiles();
Utils::Platform::Android::setupLocalizationFiles();
}
#endif
Utils::Localization::setLocale();
renderer = Renderer::getInstance(); renderer = Renderer::getInstance();
window = Window::getInstance(); window = Window::getInstance();
@ -1045,21 +1065,14 @@ int main(int argc, char* argv[])
LOG(LogDebug) << "Android internal directory: " << AndroidVariables::sInternalDataDirectory; LOG(LogDebug) << "Android internal directory: " << AndroidVariables::sInternalDataDirectory;
LOG(LogDebug) << "Android external directory: " << AndroidVariables::sExternalDataDirectory; LOG(LogDebug) << "Android external directory: " << AndroidVariables::sExternalDataDirectory;
{ if (needResourceCopy) {
std::string buildIdentifier {PROGRAM_VERSION_STRING}; LOG(LogInfo) << "Application has been updated or it's a new installation, copying "
buildIdentifier.append(" (r") "bundled resources and theme to internal storage...";
.append(std::to_string(PROGRAM_RELEASE_NUMBER)) if (Settings::getInstance()->getBool("SplashScreen"))
.append("), built ") window->renderSplashScreen(Window::SplashScreenState::RESOURCE_COPY, 0.0f);
.append(PROGRAM_BUILT_STRING); if (Utils::Platform::Android::setupResources(buildIdentifier)) {
if (Utils::Platform::Android::checkNeedResourceCopy(buildIdentifier)) { LOG(LogError) << "Copying of resources and themes failed";
LOG(LogInfo) << "Application has been updated or it's a new installation, copying " return -1;
"bundled resources and theme to internal storage...";
if (Settings::getInstance()->getBool("SplashScreen"))
window->renderSplashScreen(Window::SplashScreenState::RESOURCE_COPY, 0.0f);
if (Utils::Platform::Android::setupResources(buildIdentifier)) {
LOG(LogError) << "Copying of resources and themes failed";
return -1;
}
} }
} }

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// //
// ES-DE // ES-DE Frontend
// GamesDBJSONScraper.cpp // GamesDBJSONScraper.cpp
// //
// Functions specifically for scraping from thegamesdb.net // Functions specifically for scraping from thegamesdb.net

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// //
// ES-DE // ES-DE Frontend
// GamesDBJSONScraper.h // GamesDBJSONScraper.h
// //
// Functions specifically for scraping from thegamesdb.net // Functions specifically for scraping from thegamesdb.net

Some files were not shown because too many files have changed in this diff Show more