Compare commits

...

10 commits

Author SHA1 Message Date
7c19195e49 [compat] improve thread naming logic
All checks were successful
eden-license / license-header (pull_request) Successful in 35s
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2025-08-31 05:19:26 +02:00
4b5a8e0621
[cmake] changed app id from org.eden_emu.eden to dev.eden_emu.eden (#237)
it is better to match app id with website domain

Reviewed-on: #237
Reviewed-by: crueter <crueter@eden-emu.dev>
Co-authored-by: Guo Yunhe <i@guoyunhe.me>
Co-committed-by: Guo Yunhe <i@guoyunhe.me>
2025-08-31 04:56:23 +02:00
39e27bc954
[android] fix intent-auto-driver-install (#369)
Resolving drivers based on the artifact name was too buggy and inconsistent, this PR improves it. Well, I like to think it does

Reviewed-on: #369
Reviewed-by: crueter <crueter@eden-emu.dev>
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Co-authored-by: Producdevity <y.gherbi.dev@gmail.com>
Co-committed-by: Producdevity <y.gherbi.dev@gmail.com>
2025-08-31 03:33:54 +02:00
21c77bdcac
[cmake] fix ffmpeg libdrm on macos (#367)
Signed-off-by: crueter <crueter@eden-emu.dev>
Reviewed-on: #367
Reviewed-by: Shinmegumi <shinmegumi@eden-emu.dev>
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
2025-08-31 03:10:34 +02:00
1c3ca17cfb
[dynarmic] fix annoying gcc/clang error (#365)
caused qt creator to crash somehow geg

Signed-off-by: crueter <crueter@eden-emu.dev>

Reviewed-on: #365
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Reviewed-by: MaranBr <maranbr@outlook.com>
2025-08-31 00:12:06 +02:00
7ca197d900
[qt, compat] fix freedesktop stuffs on Solaris/OpenBSD (#360)
Signed-off-by: lizzie <lizzie@eden-emu.dev>
Reviewed-on: #360
Reviewed-by: crueter <crueter@eden-emu.dev>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2025-08-30 23:08:04 +02:00
3b4c1beb0c
[desktop] only warn on firmware for qlaunch/games (#363)
- only warns about too new/missing for home menu
- only warns about missing for games that need it (mk8dx)

Signed-off-by: crueter <crueter@eden-emu.dev>

Reviewed-on: #363
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: MaranBr <maranbr@outlook.com>
2025-08-30 20:32:28 +02:00
76de9d6c8c
[cmake, compat] fix solaris boost build once and for all (#364)
Signed-off-by: lizzie <lizzie@eden-emu.dev>
Reviewed-on: #364
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: crueter <crueter@eden-emu.dev>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2025-08-30 20:32:21 +02:00
ab015bc730
[VK] Fix asserts with incorrect memory allocations (#357)
This fixes many assertions with incorrect memory allocations. Regression introduced in PR 334.

Co-authored-by: JPikachu <jpikachu.eden@gmail.com>
Co-authored-by: MaranBr <maranbr@outlook.com>
Reviewed-on: #357
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Reviewed-by: crueter <crueter@eden-emu.dev>
Co-authored-by: JPikachu <jpikachu@eden-emu.dev>
Co-committed-by: JPikachu <jpikachu@eden-emu.dev>
2025-08-30 19:35:53 +02:00
f005f6a3ab
[compat] fix freebsd mmap virtual base (#354)
Signed-off-by: lizzie <lizzie@eden-emu.dev>
Reviewed-on: #354
Reviewed-by: Shinmegumi <shinmegumi@eden-emu.dev>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2025-08-30 17:03:56 +02:00
19 changed files with 255 additions and 135 deletions

View file

@ -1,6 +1,6 @@
AppRun
eden.desktop
org.eden_emu.eden.desktop
dev.eden_emu.eden.desktop
shared/bin/eden
shared/lib/lib.path
shared/lib/ld-linux-x86-64.so.2

View file

@ -59,15 +59,15 @@ VERSION="$(echo "$EDEN_TAG")"
mkdir -p ./AppDir
cd ./AppDir
cp ../dist/org.eden_emu.eden.desktop .
cp ../dist/org.eden_emu.eden.svg .
cp ../dist/dev.eden_emu.eden.desktop .
cp ../dist/dev.eden_emu.eden.svg .
ln -sf ./org.eden_emu.eden.svg ./.DirIcon
ln -sf ./dev.eden_emu.eden.svg ./.DirIcon
UPINFO='gh-releases-zsync|eden-emulator|Releases|latest|*.AppImage.zsync'
if [ "$DEVEL" = 'true' ]; then
sed -i 's|Name=Eden|Name=Eden Nightly|' ./org.eden_emu.eden.desktop
sed -i 's|Name=Eden|Name=Eden Nightly|' ./dev.eden_emu.eden.desktop
UPINFO="$(echo "$UPINFO" | sed 's|Releases|nightly|')"
fi

View file

@ -6,7 +6,7 @@
which png2icns || [ which yay && yay libicns ] || exit
which magick || exit
export EDEN_SVG_ICO="dist/org.eden_emu.eden.svg"
export EDEN_SVG_ICO="dist/dev.eden_emu.eden.svg"
svgo --multipass $EDEN_SVG_ICO
magick -density 256x256 -background transparent $EDEN_SVG_ICO \

View file

@ -406,8 +406,10 @@ if (YUZU_USE_CPM)
if (NOT MSVC)
# boost sucks
if (NOT PLATFORM_LINUX AND NOT ANDROID)
target_compile_definitions(boost_container INTERFACE BOOST_HAS_PTHREADS)
# Solaris (and probably other NIXes) need explicit pthread definition
if (PLATFORM_SUN)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthreads")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pthreads")
endif()
target_compile_options(boost_heap INTERFACE -Wno-shadow)
@ -856,14 +858,14 @@ endif()
# https://specifications.freedesktop.org/shared-mime-info-spec/shared-mime-info-spec-latest.html
# https://www.freedesktop.org/software/appstream/docs/
if(ENABLE_QT AND UNIX AND NOT APPLE)
install(FILES "dist/org.eden_emu.eden.desktop"
install(FILES "dist/dev.eden_emu.eden.desktop"
DESTINATION "share/applications")
install(FILES "dist/org.eden_emu.eden.svg"
install(FILES "dist/dev.eden_emu.eden.svg"
DESTINATION "share/icons/hicolor/scalable/apps")
# TODO: these files need to be updated.
install(FILES "dist/org.eden_emu.eden.xml"
install(FILES "dist/dev.eden_emu.eden.xml"
DESTINATION "share/mime/packages")
install(FILES "dist/org.eden_emu.eden.metainfo.xml"
install(FILES "dist/dev.eden_emu.eden.metainfo.xml"
DESTINATION "share/metainfo")
endif()

View file

@ -10,7 +10,7 @@ Type=Application
Name=Eden
GenericName=Switch Emulator
Comment=Nintendo Switch video game console emulator
Icon=org.eden_emu.eden
Icon=dev.eden_emu.eden
TryExec=eden
Exec=eden %f
Categories=Game;Emulator;Qt;

View file

Before

Width:  |  Height:  |  Size: 9.2 KiB

After

Width:  |  Height:  |  Size: 9.2 KiB

Before After
Before After

View file

@ -63,20 +63,22 @@ if (NOT WIN32 AND NOT ANDROID)
set(FFmpeg_HWACCEL_INCLUDE_DIRS)
set(FFmpeg_HWACCEL_LDFLAGS)
# In Solaris needs explicit linking for ffmpeg which links to /lib/amd64/libX11.so
if(PLATFORM_SUN)
list(APPEND FFmpeg_HWACCEL_LIBRARIES
X11
"/usr/lib/xorg/amd64/libdrm.so")
else()
pkg_check_modules(LIBDRM libdrm REQUIRED)
list(APPEND FFmpeg_HWACCEL_LIBRARIES
${LIBDRM_LIBRARIES})
list(APPEND FFmpeg_HWACCEL_INCLUDE_DIRS
${LIBDRM_INCLUDE_DIRS})
if (NOT APPLE)
# In Solaris needs explicit linking for ffmpeg which links to /lib/amd64/libX11.so
if(PLATFORM_SUN)
list(APPEND FFmpeg_HWACCEL_LIBRARIES
X11
"/usr/lib/xorg/amd64/libdrm.so")
else()
pkg_check_modules(LIBDRM libdrm REQUIRED)
list(APPEND FFmpeg_HWACCEL_LIBRARIES
${LIBDRM_LIBRARIES})
list(APPEND FFmpeg_HWACCEL_INCLUDE_DIRS
${LIBDRM_INCLUDE_DIRS})
endif()
list(APPEND FFmpeg_HWACCEL_FLAGS
--enable-libdrm)
endif()
list(APPEND FFmpeg_HWACCEL_FLAGS
--enable-libdrm)
if(LIBVA_FOUND)
find_package(X11 REQUIRED)

View file

@ -124,11 +124,16 @@ object CustomSettingsHandler {
// Check for driver requirements if activity and driverViewModel are provided
if (activity != null && driverViewModel != null) {
val driverPath = extractDriverPath(customSettings)
if (driverPath != null) {
Log.info("[CustomSettingsHandler] Custom settings specify driver: $driverPath")
val rawDriverPath = extractDriverPath(customSettings)
if (rawDriverPath != null) {
// Normalize to local storage path (we only store drivers under driverStoragePath)
val driverFilename = rawDriverPath.substringAfterLast('/')
.substringAfterLast('\\')
val localDriverPath = "${GpuDriverHelper.driverStoragePath}$driverFilename"
Log.info("[CustomSettingsHandler] Custom settings specify driver: $rawDriverPath (normalized: $localDriverPath)")
// Check if driver exists in the driver storage
val driverFile = File(driverPath)
val driverFile = File(localDriverPath)
if (!driverFile.exists()) {
Log.info("[CustomSettingsHandler] Driver not found locally: ${driverFile.name}")
@ -182,7 +187,7 @@ object CustomSettingsHandler {
}
// Attempt to download and install the driver
val driverUri = DriverResolver.ensureDriverAvailable(driverPath, activity) { progress ->
val driverUri = DriverResolver.ensureDriverAvailable(driverFilename, activity) { progress ->
progressChannel.trySend(progress.toInt())
}
@ -209,12 +214,12 @@ object CustomSettingsHandler {
return null
}
// Verify the downloaded driver
val installedFile = File(driverPath)
// Verify the downloaded driver (from normalized local path)
val installedFile = File(localDriverPath)
val metadata = GpuDriverHelper.getMetadataFromZip(installedFile)
if (metadata.name == null) {
Log.error(
"[CustomSettingsHandler] Downloaded driver is invalid: $driverPath"
"[CustomSettingsHandler] Downloaded driver is invalid: $localDriverPath"
)
Toast.makeText(
activity,
@ -232,7 +237,7 @@ object CustomSettingsHandler {
}
// Add to driver list
driverViewModel.onDriverAdded(Pair(driverPath, metadata))
driverViewModel.onDriverAdded(Pair(localDriverPath, metadata))
Log.info(
"[CustomSettingsHandler] Successfully downloaded and installed driver: ${metadata.name}"
)
@ -268,7 +273,7 @@ object CustomSettingsHandler {
// Driver exists, verify it's valid
val metadata = GpuDriverHelper.getMetadataFromZip(driverFile)
if (metadata.name == null) {
Log.error("[CustomSettingsHandler] Invalid driver file: $driverPath")
Log.error("[CustomSettingsHandler] Invalid driver file: $localDriverPath")
Toast.makeText(
activity,
activity.getString(
@ -459,6 +464,8 @@ object CustomSettingsHandler {
if (inGpuDriverSection && trimmed.startsWith("driver_path=")) {
return trimmed.substringAfter("driver_path=")
.trim()
.removeSurrounding("\"", "\"")
}
}

View file

@ -68,6 +68,48 @@ object DriverResolver {
val filename: String
)
// Matching helpers
private val KNOWN_SUFFIXES = listOf(
".adpkg.zip",
".zip",
".7z",
".tar.gz",
".tar.xz",
".rar"
)
private fun stripKnownSuffixes(name: String): String {
var result = name
var changed: Boolean
do {
changed = false
for (s in KNOWN_SUFFIXES) {
if (result.endsWith(s, ignoreCase = true)) {
result = result.dropLast(s.length)
changed = true
}
}
} while (changed)
return result
}
private fun normalizeName(name: String): String {
val base = stripKnownSuffixes(name.lowercase())
// Remove non-alphanumerics to make substring checks resilient
return base.replace(Regex("[^a-z0-9]+"), " ").trim()
}
private fun tokenize(name: String): Set<String> =
normalizeName(name).split(Regex("\\s+")).filter { it.isNotBlank() }.toSet()
// Jaccard similarity between two sets
private fun jaccard(a: Set<String>, b: Set<String>): Double {
if (a.isEmpty() || b.isEmpty()) return 0.0
val inter = a.intersect(b).size.toDouble()
val uni = a.union(b).size.toDouble()
return if (uni == 0.0) 0.0 else inter / uni
}
/**
* Resolve a driver download URL from its filename
* @param filename The driver filename (e.g., "turnip_mrpurple-T19-toasted.adpkg.zip")
@ -98,7 +140,7 @@ object DriverResolver {
async {
searchRepository(repoPath, filename)
}
}.mapNotNull { it.await() }.firstOrNull().also { resolved ->
}.firstNotNullOfOrNull { it.await() }.also { resolved ->
// Cache the result if found
resolved?.let {
urlCache[filename] = it
@ -119,22 +161,56 @@ object DriverResolver {
releaseCache[repoPath] = it
}
// Search through all releases and artifacts
// First pass: exact name (case-insensitive) against asset filenames
val target = filename.lowercase()
for (release in releases) {
for (artifact in release.artifacts) {
if (artifact.name == filename) {
Log.info(
"[DriverResolver] Found $filename in $repoPath/${release.tagName}"
)
if (artifact.name.equals(filename, ignoreCase = true) || artifact.name.lowercase() == target) {
Log.info("[DriverResolver] Found $filename in $repoPath/${release.tagName}")
return@withContext ResolvedDriver(
downloadUrl = artifact.url.toString(),
repoPath = repoPath,
releaseTag = release.tagName,
filename = filename
filename = artifact.name
)
}
}
}
// Second pass: fuzzy match by asset filenames only
val reqNorm = normalizeName(filename)
val reqTokens = tokenize(filename)
var best: ResolvedDriver? = null
var bestScore = 0.0
for (release in releases) {
for (artifact in release.artifacts) {
val artNorm = normalizeName(artifact.name)
val artTokens = tokenize(artifact.name)
var score = jaccard(reqTokens, artTokens)
// Boost if one normalized name contains the other
if (artNorm.contains(reqNorm) || reqNorm.contains(artNorm)) {
score = maxOf(score, 0.92)
}
if (score > bestScore) {
bestScore = score
best = ResolvedDriver(
downloadUrl = artifact.url.toString(),
repoPath = repoPath,
releaseTag = release.tagName,
filename = artifact.name
)
}
}
}
// Threshold to avoid bad guesses, this worked fine in testing but might need tuning
if (best != null && bestScore >= 0.6) {
Log.info("[DriverResolver] Fuzzy matched $filename -> ${best.filename} in ${best.repoPath} (score=%.2f)".format(bestScore))
return@withContext best
}
null
} catch (e: Exception) {
Log.error("[DriverResolver] Failed to search $repoPath: ${e.message}")
@ -296,8 +372,8 @@ object DriverResolver {
context: Context,
onProgress: ((Float) -> Unit)? = null
): Uri? {
// Extract filename from path
val filename = driverPath.substringAfterLast('/')
// Extract filename from path (support both separators)
val filename = driverPath.substringAfterLast('/').substringAfterLast('\\')
// Check if driver already exists locally
val localPath = "${GpuDriverHelper.driverStoragePath}$filename"

View file

@ -417,14 +417,11 @@ static void* ChooseVirtualBase(size_t virtual_size) {
#else
static void* ChooseVirtualBase(size_t virtual_size) {
#if defined(__OpenBSD__) || defined(__sun__) || defined(__HAIKU__) || defined(__managarm__)
#if defined(__FreeBSD__) || defined(__DragonFly__) || defined(__OpenBSD__) || defined(__sun__) || defined(__HAIKU__) || defined(__managarm__) || defined(__AIX__)
void* virtual_base = mmap(nullptr, virtual_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE | MAP_ALIGNED_SUPER, -1, 0);
if (virtual_base != MAP_FAILED) {
if (virtual_base != MAP_FAILED)
return virtual_base;
}
#endif
return mmap(nullptr, virtual_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0);
}

View file

@ -1,3 +1,5 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@ -15,9 +17,8 @@
#else
#if defined(__Bitrig__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__OpenBSD__)
#include <pthread_np.h>
#else
#include <pthread.h>
#endif
#include <pthread.h>
#include <sched.h>
#endif
#ifndef _WIN32
@ -90,33 +91,35 @@ void SetCurrentThreadName(const char* name) {
#else // !MSVC_VER, so must be POSIX threads
// MinGW with the POSIX threading model does not support pthread_setname_np
#if !defined(_WIN32) || defined(_MSC_VER)
void SetCurrentThreadName(const char* name) {
// See for reference
// https://gitlab.freedesktop.org/mesa/mesa/-/blame/main/src/util/u_thread.c?ref_type=heads#L75
#ifdef __APPLE__
pthread_setname_np(name);
#elif defined(__HAIKU__)
rename_thread(find_thread(NULL), name);
#elif defined(__Bitrig__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__OpenBSD__)
pthread_set_name_np(pthread_self(), name);
#elif defined(__NetBSD__)
pthread_setname_np(pthread_self(), "%s", (void*)name);
#elif defined(__linux__)
// Linux limits thread names to 15 characters and will outright reject any
// attempt to set a longer name with ERANGE.
std::string truncated(name, std::min(strlen(name), static_cast<size_t>(15)));
if (int e = pthread_setname_np(pthread_self(), truncated.c_str())) {
errno = e;
LOG_ERROR(Common, "Failed to set thread name to '{}': {}", truncated, GetLastErrorMsg());
#elif defined(__linux__) || defined(__CYGWIN__) || defined(__sun__) || defined(__glibc__) || defined(__managarm__)
int ret = pthread_setname_np(pthread_self(), name);
if (ret == ERANGE) {
// Linux limits thread names to 15 characters and will outright reject any
// attempt to set a longer name with ERANGE.
char buf[16];
size_t const len = std::min<size_t>(std::strlen(name), sizeof(buf) - 1);
std::memcpy(buf, name, len);
buf[len] = '\0';
pthread_setname_np(pthread_self(), buf);
}
#elif !defined(_WIN32) || defined(_MSC_VER)
// mingw stub
(void)name;
#else
pthread_setname_np(pthread_self(), name);
#endif
}
#endif
#if defined(_WIN32)
void SetCurrentThreadName(const char* name) {
// Do Nothing on MingW
}
#endif
#endif

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
/* This file is part of the dynarmic project.
* Copyright (c) 2018 MerryMage
* SPDX-License-Identifier: 0BSD
@ -19,6 +22,16 @@
namespace Dynarmic::Common {
// prevents this function from printing 56,000 character warning messages
#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wno-stack-usage"
#endif
#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wno-stack-usage"
#endif
template<typename Function, typename... Values>
inline auto GenerateLookupTableFromList(Function f, mcl::mp::list<Values...>) {
#ifdef _MSC_VER
@ -34,4 +47,11 @@ inline auto GenerateLookupTableFromList(Function f, mcl::mp::list<Values...>) {
return MapT(pair_array.begin(), pair_array.end());
}
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif
#ifdef __clang__
#pragma clang diagnostic pop
#endif
} // namespace Dynarmic::Common

View file

@ -1378,13 +1378,13 @@ void Device::CollectPhysicalMemoryInfo() {
device_access_memory += mem_properties.memoryHeaps[element].size;
}
if (!is_integrated) {
const u64 reserve_memory = std::min<u64>(device_access_memory / 4, 2_GiB);
const u64 reserve_memory = std::min<u64>(device_access_memory / 8, 1_GiB);
device_access_memory -= reserve_memory;
if (Settings::values.vram_usage_mode.GetValue() != Settings::VramUsageMode::Aggressive) {
// Account for resolution scaling in memory limits
const size_t normal_memory = 8_GiB;
const size_t scaler_memory = 2_GiB * Settings::values.resolution_info.ScaleUp(1);
const size_t normal_memory = 6_GiB;
const size_t scaler_memory = 1_GiB * Settings::values.resolution_info.ScaleUp(1);
device_access_memory =
std::min<u64>(device_access_memory, normal_memory + scaler_memory);
}
@ -1393,7 +1393,7 @@ void Device::CollectPhysicalMemoryInfo() {
}
const s64 available_memory = static_cast<s64>(device_access_memory - device_initial_usage);
device_access_memory = static_cast<u64>(std::max<s64>(
std::min<s64>(available_memory - 4_GiB, 6_GiB), std::min<s64>(local_memory, 6_GiB)));
std::min<s64>(available_memory - 8_GiB, 6_GiB), std::min<s64>(local_memory, 6_GiB)));
}
void Device::CollectToolingInfo() {

View file

@ -553,9 +553,6 @@ GMainWindow::GMainWindow(bool has_broken_vulkan)
// Gen keys if necessary
OnCheckFirmwareDecryption();
// Check firmware
OnCheckFirmware();
game_list->LoadCompatibilityList();
// force reload on first load to ensure add-ons get updated
game_list->PopulateAsync(UISettings::values.game_dirs, false);
@ -3094,34 +3091,7 @@ bool GMainWindow::CreateShortcutLink(const std::filesystem::path& shortcut_path,
const std::filesystem::path& command,
const std::string& arguments, const std::string& categories,
const std::string& keywords, const std::string& name) try {
#if defined(__linux__) || defined(__FreeBSD__) // Linux and FreeBSD
std::filesystem::path shortcut_path_full = shortcut_path / (name + ".desktop");
std::ofstream shortcut_stream(shortcut_path_full, std::ios::binary | std::ios::trunc);
if (!shortcut_stream.is_open()) {
LOG_ERROR(Frontend, "Failed to create shortcut");
return false;
}
// TODO: Migrate fmt::print to std::print in futures STD C++ 23.
fmt::print(shortcut_stream, "[Desktop Entry]\n");
fmt::print(shortcut_stream, "Type=Application\n");
fmt::print(shortcut_stream, "Version=1.0\n");
fmt::print(shortcut_stream, "Name={}\n", name);
if (!comment.empty()) {
fmt::print(shortcut_stream, "Comment={}\n", comment);
}
if (std::filesystem::is_regular_file(icon_path)) {
fmt::print(shortcut_stream, "Icon={}\n", icon_path.string());
}
fmt::print(shortcut_stream, "TryExec={}\n", command.string());
fmt::print(shortcut_stream, "Exec={} {}\n", command.string(), arguments);
if (!categories.empty()) {
fmt::print(shortcut_stream, "Categories={}\n", categories);
}
if (!keywords.empty()) {
fmt::print(shortcut_stream, "Keywords={}\n", keywords);
}
return true;
#elif defined(_WIN32) // Windows
#ifdef _WIN32 // Windows
HRESULT hr = CoInitialize(nullptr);
if (FAILED(hr)) {
LOG_ERROR(Frontend, "CoInitialize failed");
@ -3183,7 +3153,34 @@ bool GMainWindow::CreateShortcutLink(const std::filesystem::path& shortcut_path,
return false;
}
return true;
#else // Unsupported platform
#elif defined(__unix__) && !defined(__APPLE__) && !defined(__ANDROID__) // Any desktop NIX
std::filesystem::path shortcut_path_full = shortcut_path / (name + ".desktop");
std::ofstream shortcut_stream(shortcut_path_full, std::ios::binary | std::ios::trunc);
if (!shortcut_stream.is_open()) {
LOG_ERROR(Frontend, "Failed to create shortcut");
return false;
}
// TODO: Migrate fmt::print to std::print in futures STD C++ 23.
fmt::print(shortcut_stream, "[Desktop Entry]\n");
fmt::print(shortcut_stream, "Type=Application\n");
fmt::print(shortcut_stream, "Version=1.0\n");
fmt::print(shortcut_stream, "Name={}\n", name);
if (!comment.empty()) {
fmt::print(shortcut_stream, "Comment={}\n", comment);
}
if (std::filesystem::is_regular_file(icon_path)) {
fmt::print(shortcut_stream, "Icon={}\n", icon_path.string());
}
fmt::print(shortcut_stream, "TryExec={}\n", command.string());
fmt::print(shortcut_stream, "Exec={} {}\n", command.string(), arguments);
if (!categories.empty()) {
fmt::print(shortcut_stream, "Categories={}\n", categories);
}
if (!keywords.empty()) {
fmt::print(shortcut_stream, "Keywords={}\n", keywords);
}
return true;
#else // Unsupported platform
return false;
#endif
} catch (const std::exception& e) {
@ -3228,7 +3225,7 @@ bool GMainWindow::MakeShortcutIcoPath(const u64 program_id, const std::string_vi
#if defined(_WIN32)
out_icon_path = Common::FS::GetEdenPath(Common::FS::EdenPath::IconsDir);
ico_extension = "ico";
#elif defined(__linux__) || defined(__FreeBSD__)
#elif defined(__unix__) && !defined(__APPLE__) && !defined(__ANDROID__)
out_icon_path = Common::FS::GetDataDirectory("XDG_DATA_HOME") / "icons/hicolor/256x256";
#endif
// Create icons directory if it doesn't exist
@ -4459,7 +4456,6 @@ void GMainWindow::InstallFirmware(const QString& location, bool recursive) {
progress.close();
OnCheckFirmwareDecryption();
OnCheckFirmware();
}
void GMainWindow::OnInstallFirmware() {
@ -4580,7 +4576,6 @@ void GMainWindow::OnInstallDecryptionKeys() {
}
OnCheckFirmwareDecryption();
OnCheckFirmware();
}
void GMainWindow::OnAbout() {
@ -4609,6 +4604,7 @@ void GMainWindow::OnToggleStatusBar() {
void GMainWindow::OnGameListRefresh() {
// force reload add-ons etc
game_list->ForceRefreshGameDirectory();
SetFirmwareVersion();
}
void GMainWindow::OnAlbum() {
@ -4707,13 +4703,42 @@ void GMainWindow::OnOpenControllerMenu() {
}
void GMainWindow::OnHomeMenu() {
auto result = FirmwareManager::VerifyFirmware(*system.get());
switch (result) {
case FirmwareManager::ErrorFirmwareMissing:
QMessageBox::warning(this, tr("No firmware available"),
tr("Please install firmware to use the Home Menu."));
return;
case FirmwareManager::ErrorFirmwareCorrupted:
QMessageBox::warning(this, tr("Firmware Corrupted"),
tr(FirmwareManager::GetFirmwareCheckString(result)));
return;
case FirmwareManager::ErrorFirmwareTooNew: {
if (!UISettings::values.show_fw_warning.GetValue()) break;
QMessageBox box(QMessageBox::Warning,
tr("Firmware Too New"),
tr(FirmwareManager::GetFirmwareCheckString(result)) + tr("\nContinue anyways?"),
QMessageBox::Yes | QMessageBox::No,
this);
QCheckBox *checkbox = new QCheckBox(tr("Don't show again"));
box.setCheckBox(checkbox);
int button = box.exec();
if (checkbox->isChecked()) {
UISettings::values.show_fw_warning.SetValue(false);
}
if (button == static_cast<int>(QMessageBox::No)) return;
break;
} default:
break;
}
constexpr u64 QLaunchId = static_cast<u64>(Service::AM::AppletProgramId::QLaunch);
auto bis_system = system->GetFileSystemController().GetSystemNANDContents();
if (!bis_system) {
QMessageBox::warning(this, tr("No firmware available"),
tr("Please install the firmware to use the Home Menu."));
return;
}
auto qlaunch_applet_nca = bis_system->GetEntry(QLaunchId, FileSys::ContentRecordType::Program);
if (!qlaunch_applet_nca) {
@ -4853,7 +4878,7 @@ void GMainWindow::CreateShortcut(const std::string& game_path, const u64 program
}
}
#if defined(__linux__)
#if defined(__unix__) && !defined(__APPLE__) && !defined(__ANDROID__)
// Special case for AppImages
// Warn once if we are making a shortcut to a volatile AppImage
if (command.string().ends_with(".AppImage") && !UISettings::values.shortcut_already_warned) {
@ -4863,7 +4888,7 @@ void GMainWindow::CreateShortcut(const std::string& game_path, const u64 program
}
UISettings::values.shortcut_already_warned = true;
}
#endif // __linux__
#endif
// Create shortcut
std::string arguments{arguments_};
@ -5240,19 +5265,6 @@ void GMainWindow::OnCheckFirmwareDecryption() {
UpdateMenuState();
}
void GMainWindow::OnCheckFirmware() {
auto result = FirmwareManager::VerifyFirmware(*system.get());
switch (result) {
case FirmwareManager::FirmwareGood:
break;
default:
QMessageBox::warning(this, tr("Firmware Read Error"),
tr(FirmwareManager::GetFirmwareCheckString(result)));
break;
}
}
bool GMainWindow::CheckFirmwarePresence() {
return FirmwareManager::CheckFirmwarePresence(*system.get());
}
@ -5730,17 +5742,13 @@ int main(int argc, char* argv[]) {
#ifdef _WIN32
// Increases the maximum open file limit to 8192
_setmaxstdio(8192);
#endif
#ifdef __APPLE__
#elif defined(__APPLE__)
// If you start a bundle (binary) on OSX without the Terminal, the working directory is "/".
// But since we require the working directory to be the executable path for the location of
// the user folder in the Qt Frontend, we need to cd into that working directory
const auto bin_path = Common::FS::GetBundleDirectory() / "..";
chdir(Common::FS::PathToUTF8String(bin_path).c_str());
#endif
#ifdef __linux__
#elif defined(__unix__) && !defined(__ANDROID__)
// Set the DISPLAY variable in order to open web browsers
// TODO (lat9nq): Find a better solution for AppImages to start external applications
if (QString::fromLocal8Bit(qgetenv("DISPLAY")).isEmpty()) {
@ -5749,7 +5757,7 @@ int main(int argc, char* argv[]) {
// Fix the Wayland appId. This needs to match the name of the .desktop file without the .desktop
// suffix.
QGuiApplication::setDesktopFileName(QStringLiteral("org.eden_emu.eden"));
QGuiApplication::setDesktopFileName(QStringLiteral("dev.eden_emu.eden"));
#endif
SetHighDPIAttributes();

View file

@ -424,7 +424,6 @@ private slots:
void OnCreateHomeMenuShortcut(GameListShortcutTarget target);
void OnCaptureScreenshot();
void OnCheckFirmwareDecryption();
void OnCheckFirmware();
void OnLanguageChanged(const QString& locale);
void OnMouseActivity();
bool OnShutdownBegin();

View file

@ -212,6 +212,9 @@ struct Values {
// Play time
Setting<bool> show_play_time{linkage, true, "show_play_time", Category::UiGameList};
// misc
Setting<bool> show_fw_warning{linkage, true, "show_fw_warning", Category::Miscellaneous};
bool configuration_applied;
bool reset_to_defaults;
bool shortcut_already_warned{false};

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2015 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@ -138,7 +141,7 @@ bool SaveIconToFile(const std::filesystem::path& icon_path, const QImage& image)
icon_file.Close();
return true;
#elif defined(__linux__) || defined(__FreeBSD__)
#elif defined(__unix__) && !defined(__APPLE__) && !defined(__ANDROID__)
// Convert and write the icon as a PNG
if (!image.save(QString::fromStdString(icon_path.string()))) {
LOG_ERROR(Frontend, "Could not write icon as PNG to file");