feat: relight keyboard on activity
This commit is contained in:
@@ -5,24 +5,25 @@
|
|||||||
* Listens to pause/playback/core-idle so lights only stay off when media is actively playing.
|
* Listens to pause/playback/core-idle so lights only stay off when media is actively playing.
|
||||||
* Captures the session's starting brightness, dims via `brightnessctl`, and restores exactly that value when playback stops.
|
* Captures the session's starting brightness, dims via `brightnessctl`, and restores exactly that value when playback stops.
|
||||||
* Debounces rapid state flips, auto-discovers an LED device (`brightnessctl --list`), and surfaces warnings when Linux, device, or permission prerequisites fail.
|
* Debounces rapid state flips, auto-discovers an LED device (`brightnessctl --list`), and surfaces warnings when Linux, device, or permission prerequisites fail.
|
||||||
* Optionally restores the keyboard backlight whenever mpv loses window focus so other apps retain your preferred lighting.
|
* Instantly relights on keyboard activity (while keeping mpv's bindings intact) and optionally restores the keyboard backlight whenever mpv loses window focus.
|
||||||
|
|
||||||
Requirements:
|
Requirements:
|
||||||
* Linux + mpv 0.36+ with the bundled Lua 5.2 runtime.
|
* Linux + mpv 0.36+ with the bundled Lua 5.2 runtime.
|
||||||
* `brightnessctl` accessible in PATH or via `brightnessctl_path`, with permission to control the target LED class device.
|
* `brightnessctl` accessible in PATH or via `brightnessctl_path`, with permission to control the target LED class device.
|
||||||
|
|
||||||
Configuration:
|
Configuration:
|
||||||
Edit the `config` table below to tune timeouts, restore behavior, minimum brightness, LED device detection, focus handling, and the `brightnessctl` binary path.
|
Edit the `config` table below to adjust the inactivity timeout, restore behavior, minimum brightness, LED device detection, focus handling, input-triggered relight, and the `brightnessctl` binary path.
|
||||||
]]
|
]]
|
||||||
|
|
||||||
local config = {
|
local config = {
|
||||||
timeout_ms = 3000,
|
timeout_ms = 10000,
|
||||||
restore_on_pause = true,
|
restore_on_pause = true,
|
||||||
restore_on_unfocus = true,
|
restore_on_unfocus = true,
|
||||||
minimum_brightness = 0,
|
minimum_brightness = 0,
|
||||||
led_path = "",
|
led_path = "",
|
||||||
debounce_ms = 250,
|
debounce_ms = 250,
|
||||||
brightnessctl_path = "brightnessctl",
|
brightnessctl_path = "brightnessctl",
|
||||||
|
relight_on_input = true,
|
||||||
}
|
}
|
||||||
|
|
||||||
-- No edits are required below unless you want to change script behavior.
|
-- No edits are required below unless you want to change script behavior.
|
||||||
@@ -48,11 +49,44 @@ local function coerce_boolean(value, default)
|
|||||||
return default
|
return default
|
||||||
end
|
end
|
||||||
|
|
||||||
config.timeout_ms = tonumber(config.timeout_ms) or 3000
|
config.timeout_ms = tonumber(config.timeout_ms) or 10000
|
||||||
config.minimum_brightness = math.max(0, math.floor(tonumber(config.minimum_brightness) or 0))
|
config.minimum_brightness = math.max(0, math.floor(tonumber(config.minimum_brightness) or 0))
|
||||||
config.debounce_ms = math.max(0, tonumber(config.debounce_ms) or 250)
|
config.debounce_ms = math.max(0, tonumber(config.debounce_ms) or 250)
|
||||||
config.restore_on_pause = coerce_boolean(config.restore_on_pause, true)
|
config.restore_on_pause = coerce_boolean(config.restore_on_pause, true)
|
||||||
config.restore_on_unfocus = coerce_boolean(config.restore_on_unfocus, true)
|
config.restore_on_unfocus = coerce_boolean(config.restore_on_unfocus, true)
|
||||||
|
config.relight_on_input = coerce_boolean(config.relight_on_input, true)
|
||||||
|
|
||||||
|
local INPUT_ACTIVITY_KEYS = {
|
||||||
|
"ANY_UNICODE",
|
||||||
|
"SPACE",
|
||||||
|
"ENTER",
|
||||||
|
"KP_ENTER",
|
||||||
|
"BS",
|
||||||
|
"TAB",
|
||||||
|
"ESC",
|
||||||
|
"UP",
|
||||||
|
"DOWN",
|
||||||
|
"LEFT",
|
||||||
|
"RIGHT",
|
||||||
|
"PGUP",
|
||||||
|
"PGDWN",
|
||||||
|
"HOME",
|
||||||
|
"END",
|
||||||
|
"INS",
|
||||||
|
"DEL",
|
||||||
|
}
|
||||||
|
|
||||||
|
for i = 1, 12 do
|
||||||
|
table.insert(INPUT_ACTIVITY_KEYS, "F" .. tostring(i))
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = 0, 9 do
|
||||||
|
table.insert(INPUT_ACTIVITY_KEYS, "KP" .. tostring(i))
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, extra in ipairs({ "KP_DIVIDE", "KP_MULTIPLY", "KP_SUBTRACT", "KP_ADD" }) do
|
||||||
|
table.insert(INPUT_ACTIVITY_KEYS, extra)
|
||||||
|
end
|
||||||
|
|
||||||
local function seconds(ms)
|
local function seconds(ms)
|
||||||
return (tonumber(ms) or 0) / 1000
|
return (tonumber(ms) or 0) / 1000
|
||||||
@@ -72,6 +106,8 @@ local state = {
|
|||||||
first_frame_seen = false,
|
first_frame_seen = false,
|
||||||
pending_timer = false,
|
pending_timer = false,
|
||||||
window_focused = true,
|
window_focused = true,
|
||||||
|
input_bindings_registered = false,
|
||||||
|
forwarding_key_event = false,
|
||||||
}
|
}
|
||||||
|
|
||||||
local function detect_keyboard_device()
|
local function detect_keyboard_device()
|
||||||
@@ -349,6 +385,96 @@ local function restore_backlight(reason)
|
|||||||
state.original_brightness = nil
|
state.original_brightness = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function event_key_identifier(event)
|
||||||
|
if not event then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
if event.key_name and event.key_name ~= "" then
|
||||||
|
return event.key_name
|
||||||
|
end
|
||||||
|
if event.key_text and event.key_text ~= "" then
|
||||||
|
return event.key_text
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local function forward_key_event(event)
|
||||||
|
if not event or state.forwarding_key_event then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local key_name = event_key_identifier(event)
|
||||||
|
if not key_name then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
state.forwarding_key_event = true
|
||||||
|
local ok, err
|
||||||
|
if event.event == "down" then
|
||||||
|
ok, err = pcall(mp.commandv, "keydown", key_name)
|
||||||
|
elseif event.event == "up" then
|
||||||
|
ok, err = pcall(mp.commandv, "keyup", key_name)
|
||||||
|
else
|
||||||
|
if event.scale and event.scale ~= 1 then
|
||||||
|
ok, err = pcall(mp.commandv, "keypress", key_name, tostring(event.scale))
|
||||||
|
else
|
||||||
|
ok, err = pcall(mp.commandv, "keypress", key_name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
state.forwarding_key_event = false
|
||||||
|
if not ok then
|
||||||
|
msg.error(("Failed to forward key '%s': %s"):format(key_name, err or "unknown"))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function handle_user_activity(source)
|
||||||
|
if state.control_blocked then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if state.is_dark then
|
||||||
|
restore_backlight(source or "input")
|
||||||
|
end
|
||||||
|
|
||||||
|
if not state.playback_active then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if mp.get_property_native("pause") then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if state.pending_timer and not state.first_frame_seen then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
schedule_timer()
|
||||||
|
end
|
||||||
|
|
||||||
|
local function on_key_activity(event)
|
||||||
|
if state.forwarding_key_event then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if config.relight_on_input and event and event.event ~= "up" then
|
||||||
|
handle_user_activity("key-input")
|
||||||
|
end
|
||||||
|
|
||||||
|
forward_key_event(event)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function register_input_activity_bindings()
|
||||||
|
if state.input_bindings_registered then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
state.input_bindings_registered = true
|
||||||
|
|
||||||
|
for index, key in ipairs(INPUT_ACTIVITY_KEYS) do
|
||||||
|
local name = string.format("kb-blackout-activity-%d", index)
|
||||||
|
mp.add_forced_key_binding(key, name, on_key_activity, { complex = true })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
local function handle_play_event(source)
|
local function handle_play_event(source)
|
||||||
if state.control_blocked or not probe_environment() then
|
if state.control_blocked or not probe_environment() then
|
||||||
return
|
return
|
||||||
@@ -460,6 +586,12 @@ local function on_focus(_, value)
|
|||||||
handle_focus_event("focus", value)
|
handle_focus_event("focus", value)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if config.relight_on_input then
|
||||||
|
register_input_activity_bindings()
|
||||||
|
else
|
||||||
|
msg.verbose("relight_on_input disabled; skipping key activity bindings.")
|
||||||
|
end
|
||||||
|
|
||||||
mp.observe_property("pause", "bool", on_pause)
|
mp.observe_property("pause", "bool", on_pause)
|
||||||
mp.observe_property("playback-time", "native", on_playback_time)
|
mp.observe_property("playback-time", "native", on_playback_time)
|
||||||
mp.observe_property("filename", "string", on_filename)
|
mp.observe_property("filename", "string", on_filename)
|
||||||
|
|||||||
Reference in New Issue
Block a user