From a5f0945a9f797187f2a3425687d227a8e5717355 Mon Sep 17 00:00:00 2001 From: bubio Date: Thu, 11 Jun 2026 15:33:25 +0900 Subject: [PATCH] fix: show FDD/HDD disk labels on Windows (backslash paths) dirname() only recognized '/', so Windows paths from the native file dialog (e.g. C:\games\disk.fdi) split to an empty directory and scanFolder failed with ExtractFailed. The drive slot was then cleared, leaving the FDD/HDD menu blank instead of showing the source and disk names. macOS/Linux use '/' and were unaffected. Also reconstruct sibling paths using the input's own separator so they round-trip with the original path and indexOfPath can identify the currently-mounted disk (previously the '/'-joined path never matched a backslash original, so the disk name fell back to "(no disk)"). - dirname: recognize both '/' and '\' via new lastSep helper - scanFolder: join with the input separator, not a hardcoded '/' - add dirname tests for Windows-style paths Co-Authored-By: Claude Opus 4.8 (1M context) --- src/archive.zig | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/src/archive.zig b/src/archive.zig index 03737ba..d3dc2f3 100644 --- a/src/archive.zig +++ b/src/archive.zig @@ -114,8 +114,13 @@ pub fn scanFolder(allocator: std.mem.Allocator, file_path: [:0]const u8, kind: c defer threaded.deinit(); const io = threaded.io(); + const sep_idx = lastSep(file_path) orelse return Error.ExtractFailed; const dir_path = dirname(file_path); if (dir_path.len == 0) return Error.ExtractFailed; + // Rebuild sibling paths with the SAME separator the input used (backslash or + // forward slash) so they round-trip with the original path — `indexOfPath` + // compares bytes exactly to find which sibling is the mounted disk. + const sep = file_path[sep_idx]; var arena = std.heap.ArenaAllocator.init(allocator); errdefer arena.deinit(); @@ -130,7 +135,7 @@ pub fn scanFolder(allocator: std.mem.Allocator, file_path: [:0]const u8, kind: c while (it.next(io) catch return Error.ExtractFailed) |entry| { if (entry.kind != .file) continue; if (cli.classifyExt(entry.name) != kind) continue; - const full = std.fmt.allocPrintSentinel(a, "{s}/{s}", .{ dir_path, entry.name }, 0) catch + const full = std.fmt.allocPrintSentinel(a, "{s}{c}{s}", .{ dir_path, sep, entry.name }, 0) catch return Error.OutOfMemory; try list.append(a, .{ .path = full, .name = "", .kind = kind }); } @@ -379,9 +384,19 @@ fn collectImages(io: std.Io, allocator: std.mem.Allocator, cache_dir: []const u8 /// The parent-directory portion of `path` (everything before the last '/'), /// or "" if there is none. POSIX-only; the app runs on macOS/Linux. +// Find the last path separator, recognizing both '/' and '\\' so Windows paths +// (which arrive with backslashes from the native file dialog) split correctly. +fn lastSep(path: []const u8) ?usize { + var idx: ?usize = null; + for (path, 0..) |ch, i| { + if (ch == '/' or ch == '\\') idx = i; + } + return idx; +} + fn dirname(path: []const u8) []const u8 { - const i = std.mem.lastIndexOfScalar(u8, path, '/') orelse return ""; - return if (i == 0) "/" else path[0..i]; + const i = lastSep(path) orelse return ""; + return if (i == 0) path[0..1] else path[0..i]; } /// Derive a renderable disk label from a basename (a real on-disk path's @@ -477,6 +492,9 @@ test "dirname — parent directory of a path" { try testing.expectEqualStrings("/a/b", dirname("/a/b/c.fdi")); try testing.expectEqualStrings("/", dirname("/c.fdi")); try testing.expectEqualStrings("", dirname("c.fdi")); + // Windows paths arrive with backslashes from the native file dialog. + try testing.expectEqualStrings("C:\\games", dirname("C:\\games\\disk.fdi")); + try testing.expectEqualStrings("\\", dirname("\\disk.fdi")); } test "displayName — UTF-8 kept, blank stem falls back to Disk N" {