mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-17 11:11:10 +02:00
CLOSES #223, CLOSES #233, CLOSES #236, CLOSES #240, CLOSES #241, CLOSES #249, CLOSES #252, CLOSES #255, CLOSES #257, CLOSES #258, CLOSES #263, CLOSES #265, CLOSES #274, CLOSES #275, CLOSES #276, CLOSES #279, CLOSES #280, CLOSES #282, CLOSES #283, CLOSES #287, CLOSES #291, CLOSES #292, CLOSES #293, CLOSES #301, CLOSES #304, CLOSES #315, CLOSES #316, CLOSES #317, CLOSES #321, CLOSES #322, CLOSES #330
This commit is contained in:
@@ -75,6 +75,32 @@ local function resolveMkdir()
|
||||
return mkdirFn
|
||||
end
|
||||
|
||||
-- Lazily-resolved windowless rmdir, the mirror of resolveMkdir above:
|
||||
-- function(absolutePath) or false when FFI is unavailable. Both syscalls
|
||||
-- refuse a non-empty directory, so a caller has to delete the files first.
|
||||
local rmdirFn = nil
|
||||
|
||||
local function resolveRmdir()
|
||||
if rmdirFn ~= nil then return rmdirFn end
|
||||
rmdirFn = false
|
||||
local ok, ffi = pcall(require, "ffi")
|
||||
if not ok then return rmdirFn end
|
||||
if ffi.os == "Windows" then
|
||||
pcall(ffi.cdef, "int RemoveDirectoryA(const char *lpPathName);")
|
||||
local resolved = pcall(function() return ffi.C.RemoveDirectoryA end)
|
||||
if resolved then
|
||||
rmdirFn = function(path) pcall(ffi.C.RemoveDirectoryA, path) end
|
||||
end
|
||||
else
|
||||
pcall(ffi.cdef, "int rmdir(const char *pathname);")
|
||||
local resolved = pcall(function() return ffi.C.rmdir end)
|
||||
if resolved then
|
||||
rmdirFn = function(path) pcall(ffi.C.rmdir, path) end
|
||||
end
|
||||
end
|
||||
return rmdirFn
|
||||
end
|
||||
|
||||
-- Mount an external directory onto the physfs read path (appended, so the
|
||||
-- game's own source always wins a name clash). Returns true on success.
|
||||
--
|
||||
@@ -229,6 +255,23 @@ function CacheFs.remove(rel)
|
||||
love.filesystem.remove(rel)
|
||||
end
|
||||
|
||||
-- Remove a single cache-relative directory once its files are gone. Needed
|
||||
-- because os.remove cannot delete a directory on Windows and
|
||||
-- love.filesystem.remove never reaches outside the save directory, so the
|
||||
-- portable game folder gets the same FFI-syscall treatment as its mkdir
|
||||
-- (issue #74: os.execute would flash a console window per call). Used by the
|
||||
-- mod installer so an uninstall leaves nothing behind (#330).
|
||||
function CacheFs.removeDir(rel)
|
||||
rel = withPrefix(rel)
|
||||
local root = CacheFs.root()
|
||||
if root then
|
||||
local rmdir = resolveRmdir()
|
||||
if rmdir then rmdir(realPath(root, rel)) end
|
||||
return
|
||||
end
|
||||
love.filesystem.remove(rel)
|
||||
end
|
||||
|
||||
-- Remove the game-folder copy of a cache subtree before a fresh import, so a
|
||||
-- cache-format bump does not leave orphaned files behind. No-op when the
|
||||
-- portable cache is inactive (the save-directory copy is cleared by
|
||||
|
||||
@@ -255,7 +255,36 @@ local function fileUrl(path)
|
||||
return "file://" .. encoded
|
||||
end
|
||||
|
||||
-- The native pickers below block the whole loop inside io.popen, and they are
|
||||
-- opened straight out of mousepressed -- with the button still physically
|
||||
-- down. SDL auto-captures the pointer for the length of a press (on X11 an
|
||||
-- XGrabPointer with owner_events) and only drops that capture when it
|
||||
-- processes the matching button-up, which it cannot do while we sit in popen
|
||||
-- and never pump. The grab then outlives the click and every pointer event
|
||||
-- over the file chooser is still routed to our window: the dialog draws and
|
||||
-- keyboard-navigates (keyboard focus is a separate grab) but ignores the
|
||||
-- mouse entirely -- issue #254 on Linux. Whether it bites is a race with how
|
||||
-- long the click was held, which is why the same build picks one ROM fine and
|
||||
-- then hangs the mouse on the next. So pump until no button is held, letting
|
||||
-- SDL see the release and let go first; bounded, so a stuck button costs a
|
||||
-- moment and never the launcher. pump() only drains OS events into LOVE's
|
||||
-- queue -- it dispatches nothing -- so there is no reentry into mousepressed
|
||||
-- and the release is still delivered normally on the next frame.
|
||||
local function releasePointerGrab()
|
||||
if not (love.mouse and love.mouse.isDown and love.event and love.event.pump
|
||||
and love.timer) then
|
||||
return
|
||||
end
|
||||
local deadline = love.timer.getTime() + 1
|
||||
while love.mouse.isDown(1, 2, 3) do
|
||||
love.event.pump()
|
||||
if love.timer.getTime() > deadline then break end
|
||||
love.timer.sleep(0.005)
|
||||
end
|
||||
end
|
||||
|
||||
local function commandOutput(command)
|
||||
releasePointerGrab()
|
||||
local pipe = io.popen(command, "r")
|
||||
if not pipe then return nil end
|
||||
local result = pipe:read("*a")
|
||||
@@ -2476,7 +2505,7 @@ function RomImporter:_drawModsPanel(x, y, w, h)
|
||||
love.graphics.printf(m.description, nx, ny + nameH + 6 * s, L.leftW, "left")
|
||||
end
|
||||
|
||||
-- right cluster: status chip, toggle, Delete — vertically centred
|
||||
-- right cluster: status chip, toggle, Delete -- vertically centred
|
||||
local clusterX = x + w - padH - L.clusterW
|
||||
local clusterY = cy + (cardH - clusterH) / 2
|
||||
local _, chipColor = modStatusChip(m.status)
|
||||
|
||||
Reference in New Issue
Block a user