Compare commits

...

5 Commits

Author SHA1 Message Date
Luke Street a85f8c203d Better config saving handling (#1846) 2026-05-26 20:42:26 -06:00
Luke Street dc0c868bc1 Crash handler: Properly map stack frames from modules; fix build IDs (#1848) 2026-05-26 20:42:11 -06:00
Irastris 2afc52772c Suppress Textinput line break events on BaseStringButton (#1833)
* Suppress Textinput line break events on BaseStringButton

* Oops, removed a comment
2026-05-26 20:39:07 -06:00
doop 0dc475108b Ensure dMdl_c packet is in drawlist for interp (#1818)
Fixes #1010.
2026-05-26 11:39:40 -04:00
Pieter-Jan Briers a7790d7323 Change "verify your dump" instructions in readme (#1809)
* Change "verify your dump" instructions in readme

No need to do this manually as the game already does it. Instead let's link to a guide for dumping the game legitimately.

* Gotta shill
2026-05-25 08:25:21 -06:00
11 changed files with 343 additions and 61 deletions
+3 -8
View File
@@ -22,16 +22,11 @@ It aims to be as accurate as possible to the original while also providing new o
> [!IMPORTANT]
> At a minimum, Dusklight requires a GPU with support for either D3D12, Vulkan, or Metal. Your experience with specific hardware, operating systems, and drivers may vary. In particular, older Intel iGPUs have a high likelihood of incompatibility. We are also aware of a number of issues on devices with Adreno GPUs and are working to resolve them.
### 1. Verify your dump
### 1. Dump your game
First, make sure your dump of the game is clean and supported by Dusklight. You can do this by checking the SHA-1 hash of your dump against this list of supported versions:
You must dump your own copy of the game, please see [this article](https://wiki.dolphin-emu.org/index.php?title=Ripping_Games) for instructions. After dumping, you can use a program like [Dolphin](https://dolphin-emu.org/) or [nodtool](https://github.com/encounter/nod/releases) to convert the `.iso` to a `.rvz` to save space.
| Version | SHA-1 hash |
|--------------| ------------------------------------------ |
| GameCube USA | `75edd3ddff41f125d1b4ce1a40378f1b565519e7` |
| GameCube EUR | `2601822a488eeb86fb89db16ca8f29c2c953e1ca` |
*Support for other versions of the game is planned in the future.
Currently, only the GameCube USA and EUR releases are supported. Support for other versions of the game is planned in the future.
### 2. Download [Dusklight](https://github.com/TwilitRealm/dusklight/releases)
+5
View File
@@ -30,6 +30,11 @@ public:
~FileStream();
/**
* \brief Flush buffered writes and throw if the flush fails.
*/
void Flush();
/**
* \brief Open a file for reading at the given path.
*/
+14
View File
@@ -45,6 +45,20 @@ void dMdl_c::create(J3DModelData* i_modelData, u16 i_materialId, dKy_tevstr_c* i
}
void dMdl_c::entryObj(dMdl_obj_c* i_obj) {
#ifdef TARGET_PC
// if field_0x1a is false, this dMdl_c is not in the drawlist
// if true, we need to make sure with interp enabled
if (dusk::frame_interp::is_enabled() && field_0x1a) {
auto pkt = dComIfGd_getListPacket()->mpBuffer[0];
while (pkt && pkt != this) {
pkt = pkt->getNextPacket();
}
if (!pkt) {
field_0x1a = false;
}
}
#endif
if (!field_0x1a) {
dComIfGd_getListPacket()->entryImm(this, 0);
field_0x1a = true;
+35 -5
View File
@@ -8,6 +8,8 @@
#include "dusk/settings.h"
#include <limits>
#include <filesystem>
#include <system_error>
#include <string>
#include "dusk/main.h"
@@ -24,8 +26,24 @@ aurora::Module DuskConfigLog("dusk::config");
static absl::flat_hash_map<std::string_view, ConfigVarBase*> RegisteredConfigVars;
static bool RegistrationDone = false;
static std::u8string GetConfigJsonPath() {
return (dusk::ConfigPath / ConfigFileName).u8string();
static std::filesystem::path GetConfigJsonPath() {
return dusk::ConfigPath / ConfigFileName;
}
static std::filesystem::path GetTempConfigJsonPath(const std::filesystem::path& configJsonPath) {
auto tempPath = configJsonPath;
tempPath.replace_filename(fmt::format(".{}.tmp", configJsonPath.filename().string()));
return tempPath;
}
static void ReplaceFile(const std::filesystem::path& source, const std::filesystem::path& target) {
std::error_code ec;
std::filesystem::rename(source, target, ec);
if (ec) {
const auto renameError = ec;
std::filesystem::remove(source, ec);
throw std::system_error(renameError);
}
}
ConfigVarBase::ConfigVarBase(const char* name, const ConfigImplBase* impl) : name(name), registered(false), layer(ConfigVarLayer::Default), impl(impl) {
@@ -211,7 +229,8 @@ void dusk::config::LoadFromUserPreferences() {
if (configJsonPath.empty()) {
return;
}
LoadFromFileName(reinterpret_cast<const char*>(configJsonPath.c_str()));
const auto configPathString = io::fs_path_to_string(configJsonPath);
LoadFromFileName(configPathString.c_str());
}
static void LoadFromPath(const char* path) {
@@ -254,6 +273,10 @@ void dusk::config::LoadFromFileName(const char* path) {
} else {
DuskConfigLog.error("Failed to load from config! {}", e.what());
}
} catch (const nlohmann::json::parse_error& e) {
DuskConfigLog.error("Failed to parse config JSON, staying with defaults: {}", e.what());
} catch (const std::exception& e) {
DuskConfigLog.error("Failed to load from config, staying with defaults: {}", e.what());
}
}
@@ -262,10 +285,11 @@ void dusk::config::Save() {
if (configJsonPath.empty()) {
return;
}
const auto configPathString = io::fs_path_to_string(configJsonPath);
DuskConfigLog.info(
"Saving config to '{}'",
reinterpret_cast<const char*>(configJsonPath.c_str()));
configPathString);
json j;
@@ -276,7 +300,13 @@ void dusk::config::Save() {
}
}
io::FileStream::WriteAllText(reinterpret_cast<const char*>(configJsonPath.c_str()), j.dump(4));
try {
const auto tempConfigJsonPath = GetTempConfigJsonPath(configJsonPath);
io::FileStream::WriteAllText(tempConfigJsonPath, j.dump(4));
ReplaceFile(tempConfigJsonPath, configJsonPath);
} catch (const std::exception& e) {
DuskConfigLog.error("Failed to save config to '{}': {}", configPathString, e.what());
}
}
void dusk::config::ClearAllActionBindings(int port) {
+254 -44
View File
@@ -64,6 +64,15 @@ struct CrashContext {
};
CrashContext g_ctx;
struct ModuleInfo {
uintptr_t base = 0;
uintptr_t size = 0;
char path[1024] = {};
uint8_t buildId[64] = {};
unsigned buildIdLen = 0;
unsigned pdbAge = 0;
};
void rawWrite(int fd, const char* data, size_t len) {
if (fd < 0) {
return;
@@ -119,9 +128,29 @@ void writeHexBytes(int fd, const uint8_t* data, unsigned len) {
}
}
const char* moduleName() {
const char* name = g_ctx.modulePath;
for (const char* p = g_ctx.modulePath; *p != '\0'; ++p) {
void writeHexByte(int fd, uint8_t value) {
char buf[2];
buf[0] = kHexDigits[value >> 4];
buf[1] = kHexDigits[value & 0xF];
rawWrite(fd, buf, 2);
}
void writeQuoted(int fd, const char* s) {
writeStr(fd, "\"");
if (s != nullptr) {
for (const char* p = s; *p != '\0'; ++p) {
if (*p == '"' || *p == '\\') {
rawWrite(fd, "\\", 1);
}
rawWrite(fd, p, 1);
}
}
writeStr(fd, "\"");
}
const char* baseName(const char* path) {
const char* name = path;
for (const char* p = path; p != nullptr && *p != '\0'; ++p) {
if (*p == '/' || *p == '\\') {
name = p + 1;
}
@@ -129,6 +158,40 @@ const char* moduleName() {
return name[0] != '\0' ? name : "(unknown)";
}
void writeBuildId(int fd, const uint8_t* buildId, unsigned buildIdLen, unsigned pdbAge) {
if (buildIdLen == 0) {
writeStr(fd, "(unavailable)");
return;
}
#if defined(_WIN32)
if (buildIdLen == 16) {
writeHexByte(fd, buildId[3]);
writeHexByte(fd, buildId[2]);
writeHexByte(fd, buildId[1]);
writeHexByte(fd, buildId[0]);
writeStr(fd, "-");
writeHexByte(fd, buildId[5]);
writeHexByte(fd, buildId[4]);
writeStr(fd, "-");
writeHexByte(fd, buildId[7]);
writeHexByte(fd, buildId[6]);
writeStr(fd, "-");
writeHexByte(fd, buildId[8]);
writeHexByte(fd, buildId[9]);
writeStr(fd, "-");
writeHexBytes(fd, buildId + 10, 6);
if (pdbAge != 0) {
writeStr(fd, "-");
writeDec(fd, pdbAge);
}
return;
}
#else
(void)pdbAge;
#endif
writeHexBytes(fd, buildId, buildIdLen);
}
const char* symbolFor(uintptr_t pc, unsigned long long* disp) {
#if defined(_WIN32) && defined(DUSK_CRASH_DBGHELP)
alignas(SYMBOL_INFO) static char storage[sizeof(SYMBOL_INFO) + 512];
@@ -156,7 +219,47 @@ const char* symbolFor(uintptr_t pc, unsigned long long* disp) {
#endif
}
void fallbackModuleInfo(ModuleInfo& info) {
info = {};
info.base = g_ctx.moduleBase;
std::strncpy(info.path, g_ctx.modulePath, sizeof(info.path) - 1);
if (g_ctx.buildIdLen > sizeof(info.buildId)) {
info.buildIdLen = sizeof(info.buildId);
} else {
info.buildIdLen = g_ctx.buildIdLen;
}
if (info.buildIdLen != 0) {
std::memcpy(info.buildId, g_ctx.buildId, info.buildIdLen);
}
info.pdbAge = g_ctx.pdbAge;
}
bool findModuleInfo(uintptr_t pc, ModuleInfo& info);
void emitAddressDetail(int fd, uintptr_t pc) {
ModuleInfo info;
findModuleInfo(pc, info);
const uintptr_t rva = pc >= info.base ? pc - info.base : 0ull;
writeHex(fd, pc);
writeStr(fd, " module_base=");
writeHex(fd, info.base);
if (info.size != 0) {
writeStr(fd, " image_size=");
writeHex(fd, info.size);
}
writeStr(fd, " rva=");
writeHex(fd, rva);
writeStr(fd, " module=");
writeQuoted(fd, info.path[0] != '\0' ? info.path : baseName(g_ctx.modulePath));
writeStr(fd, " build_id=");
writeBuildId(fd, info.buildId, info.buildIdLen, info.pdbAge);
}
void emitFrame(int fd, int index, uintptr_t pc) {
ModuleInfo info;
findModuleInfo(pc, info);
const uintptr_t rva = pc >= info.base ? pc - info.base : 0ull;
writeStr(fd, "#");
if (index < 10) {
writeStr(fd, "0");
@@ -164,10 +267,18 @@ void emitFrame(int fd, int index, uintptr_t pc) {
writeDec(fd, static_cast<unsigned int>(index));
writeStr(fd, " abs=");
writeHex(fd, pc);
writeStr(fd, " module_base=");
writeHex(fd, info.base);
if (info.size != 0) {
writeStr(fd, " image_size=");
writeHex(fd, info.size);
}
writeStr(fd, " rva=");
writeHex(fd, pc >= g_ctx.moduleBase ? pc - g_ctx.moduleBase : 0ull);
writeStr(fd, " ");
writeStr(fd, moduleName());
writeHex(fd, rva);
writeStr(fd, " module=");
writeQuoted(fd, info.path[0] != '\0' ? info.path : baseName(g_ctx.modulePath));
writeStr(fd, " build_id=");
writeBuildId(fd, info.buildId, info.buildIdLen, info.pdbAge);
unsigned long long disp = 0;
const char* sym = symbolFor(pc, &disp);
if (sym != nullptr && sym[0] != '\0') {
@@ -191,18 +302,7 @@ void emitHeader(int fd, const char* reason, unsigned long long code, bool hasCod
writeStr(fd, "\nModule base: ");
writeHex(fd, g_ctx.moduleBase);
writeStr(fd, "\nBuild-ID: ");
if (g_ctx.buildIdLen != 0) {
writeHexBytes(fd, g_ctx.buildId, g_ctx.buildIdLen);
#if defined(_WIN32)
if (g_ctx.pdbAge != 0) {
writeStr(fd, " (Age=");
writeDec(fd, g_ctx.pdbAge);
writeStr(fd, ")");
}
#endif
} else {
writeStr(fd, "(unavailable)");
}
writeBuildId(fd, g_ctx.buildId, g_ctx.buildIdLen, g_ctx.pdbAge);
writeStr(fd, "\nReason: ");
writeStr(fd, reason);
if (hasCode) {
@@ -214,9 +314,7 @@ void emitHeader(int fd, const char* reason, unsigned long long code, bool hasCod
writeHex(fd, faultAddr);
writeStr(fd, "\nCrash PC: ");
if (crashPcKnown) {
writeHex(fd, crashPc);
writeStr(fd, " rva=");
writeHex(fd, crashPc >= g_ctx.moduleBase ? crashPc - g_ctx.moduleBase : 0ull);
emitAddressDetail(fd, crashPc);
} else {
writeStr(fd, "(unavailable on this platform)");
}
@@ -233,23 +331,25 @@ void emitFooter(int fd) {
LONG g_inHandler = 0;
LPTOP_LEVEL_EXCEPTION_FILTER g_prevFilter = nullptr;
void captureBuildId() {
const auto* base = reinterpret_cast<const uint8_t*>(g_ctx.moduleBase);
bool readPeModuleInfo(uintptr_t moduleBase, ModuleInfo& info) {
const auto* base = reinterpret_cast<const uint8_t*>(moduleBase);
if (base == nullptr) {
return;
return false;
}
const auto* dos = reinterpret_cast<const IMAGE_DOS_HEADER*>(base);
if (dos->e_magic != IMAGE_DOS_SIGNATURE) {
return;
return false;
}
const auto* nt = reinterpret_cast<const IMAGE_NT_HEADERS*>(base + dos->e_lfanew);
if (nt->Signature != IMAGE_NT_SIGNATURE) {
return;
return false;
}
info.base = moduleBase;
info.size = nt->OptionalHeader.SizeOfImage;
const IMAGE_DATA_DIRECTORY& dir =
nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG];
if (dir.VirtualAddress == 0 || dir.Size == 0) {
return;
return true;
}
const auto* dbg = reinterpret_cast<const IMAGE_DEBUG_DIRECTORY*>(base + dir.VirtualAddress);
const unsigned count = dir.Size / sizeof(IMAGE_DEBUG_DIRECTORY);
@@ -261,11 +361,40 @@ void captureBuildId() {
if (std::memcmp(cv, "RSDS", 4) != 0) {
continue;
}
std::memcpy(g_ctx.buildId, cv + 4, sizeof(GUID));
g_ctx.buildIdLen = sizeof(GUID);
std::memcpy(&g_ctx.pdbAge, cv + 4 + sizeof(GUID), sizeof(g_ctx.pdbAge));
std::memcpy(info.buildId, cv + 4, sizeof(GUID));
info.buildIdLen = sizeof(GUID);
std::memcpy(&info.pdbAge, cv + 4 + sizeof(GUID), sizeof(info.pdbAge));
break;
}
return true;
}
void captureBuildId() {
ModuleInfo info;
if (!readPeModuleInfo(g_ctx.moduleBase, info)) {
return;
}
g_ctx.buildIdLen = info.buildIdLen;
if (g_ctx.buildIdLen != 0) {
std::memcpy(g_ctx.buildId, info.buildId, g_ctx.buildIdLen);
}
g_ctx.pdbAge = info.pdbAge;
}
bool findModuleInfo(uintptr_t pc, ModuleInfo& info) {
fallbackModuleInfo(info);
MEMORY_BASIC_INFORMATION mbi;
if (VirtualQuery(reinterpret_cast<LPCVOID>(pc), &mbi, sizeof(mbi)) == 0 ||
mbi.AllocationBase == nullptr) {
return false;
}
const auto moduleBase = reinterpret_cast<uintptr_t>(mbi.AllocationBase);
info = {};
info.base = moduleBase;
GetModuleFileNameA(reinterpret_cast<HMODULE>(moduleBase), info.path,
static_cast<DWORD>(sizeof(info.path) - 1));
readPeModuleInfo(moduleBase, info);
return true;
}
const char* exceptionName(DWORD code) {
@@ -512,23 +641,35 @@ void prewarmUnwinder() {
#if defined(__APPLE__)
void captureBuildId() {
const auto* header = reinterpret_cast<const struct mach_header_64*>(g_ctx.moduleBase);
bool readMachBuildId(uintptr_t moduleBase, ModuleInfo& info) {
const auto* header = reinterpret_cast<const struct mach_header_64*>(moduleBase);
if (header == nullptr || header->magic != MH_MAGIC_64) {
return;
return false;
}
const auto* lc = reinterpret_cast<const struct load_command*>(
reinterpret_cast<const char*>(header) + sizeof(struct mach_header_64));
for (uint32_t i = 0; i < header->ncmds; ++i) {
if (lc->cmd == LC_UUID) {
const auto* uuid = reinterpret_cast<const struct uuid_command*>(lc);
std::memcpy(g_ctx.buildId, uuid->uuid, sizeof(uuid->uuid));
g_ctx.buildIdLen = sizeof(uuid->uuid);
return;
std::memcpy(info.buildId, uuid->uuid, sizeof(uuid->uuid));
info.buildIdLen = sizeof(uuid->uuid);
return true;
}
lc = reinterpret_cast<const struct load_command*>(
reinterpret_cast<const char*>(lc) + lc->cmdsize);
}
return true;
}
void captureBuildId() {
ModuleInfo info;
if (!readMachBuildId(g_ctx.moduleBase, info)) {
return;
}
g_ctx.buildIdLen = info.buildIdLen;
if (g_ctx.buildIdLen != 0) {
std::memcpy(g_ctx.buildId, info.buildId, g_ctx.buildIdLen);
}
}
#else
@@ -547,7 +688,28 @@ bool segmentContains(const dl_phdr_info* info, uintptr_t addr) {
return false;
}
bool readGnuBuildId(const dl_phdr_info* info) {
void readElfModuleInfo(const dl_phdr_info* info, ModuleInfo& module) {
uintptr_t minAddr = ~static_cast<uintptr_t>(0);
uintptr_t maxAddr = 0;
for (int i = 0; i < info->dlpi_phnum; ++i) {
const ElfW(Phdr)& ph = info->dlpi_phdr[i];
if (ph.p_type != PT_LOAD) {
continue;
}
const uintptr_t start = info->dlpi_addr + ph.p_vaddr;
const uintptr_t end = start + ph.p_memsz;
if (start < minAddr) {
minAddr = start;
}
if (end > maxAddr) {
maxAddr = end;
}
}
if (minAddr <= maxAddr && maxAddr != 0) {
module.base = minAddr;
module.size = maxAddr - minAddr;
}
for (int i = 0; i < info->dlpi_phnum; ++i) {
const ElfW(Phdr)& ph = info->dlpi_phdr[i];
if (ph.p_type != PT_NOTE) {
@@ -563,17 +725,16 @@ bool readGnuBuildId(const dl_phdr_info* info) {
if (nh->n_type == NT_GNU_BUILD_ID && nh->n_namesz == 4 &&
std::memcmp(name, "GNU", 4) == 0) {
unsigned n = nh->n_descsz;
if (n > sizeof(g_ctx.buildId)) {
n = sizeof(g_ctx.buildId);
if (n > sizeof(module.buildId)) {
n = sizeof(module.buildId);
}
std::memcpy(g_ctx.buildId, desc, n);
g_ctx.buildIdLen = n;
return true;
std::memcpy(module.buildId, desc, n);
module.buildIdLen = n;
return;
}
p = desc + ((nh->n_descsz + 3) & ~3u);
}
}
return false;
}
int elfBuildIdCallback(dl_phdr_info* info, size_t, void* arg) {
@@ -581,7 +742,12 @@ int elfBuildIdCallback(dl_phdr_info* info, size_t, void* arg) {
if (!segmentContains(info, self)) {
return 0;
}
readGnuBuildId(info);
ModuleInfo module;
readElfModuleInfo(info, module);
g_ctx.buildIdLen = module.buildIdLen;
if (g_ctx.buildIdLen != 0) {
std::memcpy(g_ctx.buildId, module.buildId, g_ctx.buildIdLen);
}
return 1;
}
@@ -592,6 +758,50 @@ void captureBuildId() {
#endif
#if !defined(__APPLE__)
struct ElfModuleSearch {
uintptr_t pc;
ModuleInfo* module;
};
int elfModuleInfoCallback(dl_phdr_info* info, size_t, void* arg) {
auto* search = static_cast<ElfModuleSearch*>(arg);
if (!segmentContains(info, search->pc)) {
return 0;
}
if (info->dlpi_name != nullptr && info->dlpi_name[0] != '\0') {
std::strncpy(search->module->path, info->dlpi_name,
sizeof(search->module->path) - 1);
}
readElfModuleInfo(info, *search->module);
return 1;
}
#endif
bool findModuleInfo(uintptr_t pc, ModuleInfo& info) {
fallbackModuleInfo(info);
Dl_info moduleInfo;
if (dladdr(reinterpret_cast<void*>(pc), &moduleInfo) == 0) {
return false;
}
if (moduleInfo.dli_fbase != nullptr) {
info.base = reinterpret_cast<uintptr_t>(moduleInfo.dli_fbase);
}
if (moduleInfo.dli_fname != nullptr && moduleInfo.dli_fname[0] != '\0') {
info.path[0] = '\0';
std::strncpy(info.path, moduleInfo.dli_fname, sizeof(info.path) - 1);
}
info.buildIdLen = 0;
info.pdbAge = 0;
#if defined(__APPLE__)
readMachBuildId(info.base, info);
#else
ElfModuleSearch search{pc, &info};
dl_iterate_phdr(&elfModuleInfoCallback, &search);
#endif
return true;
}
const char* signalName(int sig) {
switch (sig) {
case SIGSEGV:
+15 -1
View File
@@ -1,5 +1,7 @@
#include <cerrno>
#include <cstdio>
#include <filesystem>
#include <system_error>
#include "dusk/io.hpp"
@@ -30,6 +32,9 @@ static FILE* ThrowIfNotOpen(const FileStream& file) {
}
[[noreturn]] static void ThrowForError(int code) {
if (code == 0) {
throw std::system_error(std::make_error_code(std::errc::io_error));
}
throw std::system_error(std::make_error_code(static_cast<std::errc>(code)));
}
@@ -77,6 +82,14 @@ FileStream::~FileStream() {
fclose(static_cast<FILE*>(file));
}
void FileStream::Flush() {
FILE* fileHandle = ThrowIfNotOpen(*this);
if (fflush(fileHandle) != 0) {
ThrowForError(errno);
}
}
FileStream FileStream::OpenRead(const char* utf8Path) {
return FileStream(OpenCore(utf8Path, MODE("rb"), _SH_DENYWR));
}
@@ -163,10 +176,11 @@ void FileStream::WriteAllText(const char* utf8Path, const std::string_view text)
void FileStream::WriteAllText(const std::filesystem::path& path, const std::string_view text) {
auto handle = Create(path);
handle.Write(text.data(), text.size());
handle.Flush();
}
FILE* FileStream::ToInner() {
auto handle = file;
file = nullptr;
return handle;
}
}
+1 -1
View File
@@ -95,7 +95,6 @@ void set_value(GraphicsOption option, int value) {
getSettings().game.bloomMultiplier.setValue(std::clamp(value, 0, 100) / 100.0f);
break;
}
config::Save();
}
Rml::Element* create_stepped_carousel_root(Rml::Element* parent) {
@@ -292,6 +291,7 @@ void GraphicsTuner::show() {
}
void GraphicsTuner::hide(bool close) {
config::Save();
mRoot->RemoveAttribute("open");
if (close) {
mPendingClose = true;
+5
View File
@@ -1501,4 +1501,9 @@ void SettingsWindow::update() {
Window::update();
}
void SettingsWindow::hide(bool close) {
config::Save();
Window::hide(close);
}
} // namespace dusk::ui
+1
View File
@@ -8,6 +8,7 @@ public:
SettingsWindow(bool prelaunch = false);
void update() override;
void hide(bool close) override;
protected:
bool mPrelaunch;
+10 -1
View File
@@ -7,7 +7,7 @@ namespace dusk::ui {
BaseStringButton::BaseStringButton(Rml::Element* parent, Props props)
: BaseControlledSelectButton(parent, {std::move(props.key)}), mType(std::move(props.type)),
mMaxLength(props.maxLength) {
mInputListeners.reserve(3);
mInputListeners.reserve(4);
}
void BaseStringButton::update() {
@@ -54,6 +54,15 @@ void BaseStringButton::start_editing() {
mRoot->DispatchEvent(Rml::EventId::Submit, {{"handled", Rml::Variant{true}}});
// Register input listeners
mInputListeners.emplace_back(std::make_unique<ScopedEventListener>(
mInputElem, Rml::EventId::Textinput, [this](Rml::Event& event) {
if (event.GetTargetElement() == mInputElem) {
const Rml::String text = event.GetParameter("text", Rml::String{});
if (!text.empty() && std::ranges::all_of(text, [](const char c) { return c == '\r' || c == '\n' || c == '\t'; })) {
event.StopImmediatePropagation();
}
}
}));
mInputListeners.emplace_back(std::make_unique<ScopedEventListener>(
mInputElem, Rml::EventId::Keydown, [this](Rml::Event& event) {
const auto cmd = map_nav_event(event);
-1
View File
@@ -61,7 +61,6 @@ bool initialize() noexcept {
}
void shutdown() noexcept {
config::Save();
sDocumentStack.clear();
sPassiveDocuments.clear();
sConnectedGamepads.clear();