From 88e24f2c0c9a343bbc293dffa6c518f938b2dc24 Mon Sep 17 00:00:00 2001 From: Shane McGovern Date: Wed, 26 Aug 2026 13:26:14 +0100 Subject: [PATCH] feat(core): add global Start+Select 5s emergency force-quit watchdog --- main.lua | 75 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/main.lua b/main.lua index eda842d2..9409e181 100644 --- a/main.lua +++ b/main.lua @@ -22,6 +22,55 @@ local PlatformHooks = require("src.core.PlatformHooks") local HostDisplay = require("src.core.HostDisplay") local GameViewport = require("src.render.GameViewport") +-- Global emergency quit: holding Start + Select for 5 seconds forcefully terminates LOVE. +local emergencyQuitTimer = 0 + +local function checkEmergencyQuit(dt) + local held = false + if love.joystick and love.joystick.getJoysticks then + local joysticks = love.joystick.getJoysticks() + for _, j in ipairs(joysticks) do + if j:isGamepad() then + local start = j:isGamepadDown("start") + local selectBtn = j:isGamepadDown("back") or j:isGamepadDown("guide") + if start and selectBtn then + held = true + break + end + else + local bCount = j:getButtonCount() + local s1 = (bCount >= 7 and j:isDown(7)) or (bCount >= 9 and j:isDown(9)) + local s2 = (bCount >= 8 and j:isDown(8)) or (bCount >= 10 and j:isDown(10)) + if s1 and s2 then + held = true + break + end + end + end + end + + if love.keyboard and love.keyboard.isDown then + if (love.keyboard.isDown("escape") and love.keyboard.isDown("return")) + or (love.keyboard.isDown("lalt") and love.keyboard.isDown("f4")) then + held = true + end + end + + if held then + emergencyQuitTimer = emergencyQuitTimer + (dt or 0.016) + if emergencyQuitTimer >= 5.0 then + print("[FORCE QUIT] Start + Select held for 5 seconds. Exiting forcefully.") + pcall(function() + if love.audio and love.audio.stop then love.audio.stop() end + if love.window and love.window.close then love.window.close() end + end) + os.exit(0) + end + else + emergencyQuitTimer = 0 + end +end + -- Lua errors: persist a redacted trace in the save dir and surface a hint. do local defaultErrorHandler = love.errorhandler or love.errhand @@ -30,10 +79,33 @@ do if ok and hint and type(msg) == "string" then msg = msg .. "\n\n" .. hint end + + if love.window and love.window.isOpen and love.window.isOpen() and love.graphics and love.graphics.isActive() then + local fullMsg = tostring(msg) .. "\n\n" .. tostring(debug.traceback()) .. "\n\n[Hold START + SELECT for 5s to Force Quit]" + return function() + love.event.pump() + for e, a in love.event.poll() do + if e == "quit" or (e == "keypressed" and a == "escape") then + return 1 + elseif e == "gamepadpressed" and (a == "start" or a == "back") then + return 1 + end + end + checkEmergencyQuit(0.016) + love.graphics.origin() + love.graphics.clear(0.10, 0.10, 0.12) + love.graphics.setColor(1, 0.4, 0.4, 1) + love.graphics.printf(fullMsg, 20, 20, love.graphics.getWidth() - 40) + love.graphics.present() + love.timer.sleep(0.016) + end + end + if defaultErrorHandler then return defaultErrorHandler(msg) end end + love.errhand = love.errorhandler end local Game, EditorApp, Importer, TouchEditor, Studio @@ -559,6 +631,7 @@ function love.load(args) end function love.update(dt) + checkEmergencyQuit(dt) HostDisplay.update(dt) SwitchDiagnostics.maybeFlush(false) -- NX only (no-op elsewhere): follow dock/undock without waiting for SDL. @@ -1229,6 +1302,8 @@ function love.run() if love.timer then dt = love.timer.step() end idleFor = idleFor + dt + checkEmergencyQuit(dt) + -- call update and draw if love.update then love.update(dt) end