From f8230bab9cb08f9defd13a43b29e9153f1585421 Mon Sep 17 00:00:00 2001 From: Nose Date: Sun, 19 Apr 2026 18:13:44 -0700 Subject: [PATCH] updated mpv code to try and fix invisible cursor --- MPV/LUA/main.lua | 256 +++++++++++++----- .../scripts/uosc/scripts/uosc/main.lua | 8 +- 2 files changed, 188 insertions(+), 76 deletions(-) diff --git a/MPV/LUA/main.lua b/MPV/LUA/main.lua index 340f580..16feb65 100644 --- a/MPV/LUA/main.lua +++ b/MPV/LUA/main.lua @@ -388,31 +388,52 @@ M._disable_input_section = function(name, reason) end end +function M._sync_uosc_cursor(reason) + local why = tostring(reason or 'unknown') + + local function do_sync(suffix) + pcall(mp.set_property, 'cursor-autohide', '1000') + if ensure_uosc_loaded() then + pcall(mp.commandv, 'script-message-to', 'uosc', 'sync-cursor') + end + M._disable_input_section('input_console', why .. suffix) + M._disable_input_section('input_forced_console', why .. suffix) + M._disable_input_section('image', why .. suffix) + end + + do_sync('@immediate') + mp.add_timeout(0.05, function() + do_sync('@sync') + end) + mp.add_timeout(0.20, function() + do_sync('@sync2') + end) +end + +function M._close_uosc_menu_and_sync(menu_type, reason) + local why = tostring(reason or 'unknown') + if ensure_uosc_loaded() then + if menu_type and menu_type ~= '' then + pcall(mp.commandv, 'script-message-to', 'uosc', 'close-menu', menu_type) + else + pcall(mp.commandv, 'script-message-to', 'uosc', 'close-menu') + end + end + M._sync_uosc_cursor(why) + return true +end + M._reset_uosc_input_state = function(reason) local why = tostring(reason or 'unknown') M._disable_input_section('input_console', why) M._disable_input_section('input_forced_console', why) + M._disable_input_section('image', why) + pcall(mp.set_property, 'cursor-autohide', '1000') if not ensure_uosc_loaded() then return false end pcall(mp.commandv, 'script-message-to', 'uosc', 'close-menu') - pcall(mp.commandv, 'script-message-to', 'uosc', 'sync-cursor') - M._disable_input_section('input_console', why .. '@immediate') - M._disable_input_section('input_forced_console', why .. '@immediate') - mp.add_timeout(0.05, function() - if ensure_uosc_loaded() then - pcall(mp.commandv, 'script-message-to', 'uosc', 'sync-cursor') - end - M._disable_input_section('input_console', why .. '@sync') - M._disable_input_section('input_forced_console', why .. '@sync') - end) - mp.add_timeout(0.20, function() - if ensure_uosc_loaded() then - pcall(mp.commandv, 'script-message-to', 'uosc', 'sync-cursor') - end - M._disable_input_section('input_console', why .. '@sync2') - M._disable_input_section('input_forced_console', why .. '@sync2') - end) + M._sync_uosc_cursor(why) return true end @@ -429,6 +450,12 @@ M._open_uosc_menu = function(menu_data, reason) _lua_log('menu: open-menu failed reason=' .. why .. ' err=' .. tostring(err)) return false end + mp.add_timeout(0.03, function() + pcall(mp.set_property, 'cursor-autohide', '1000') + if ensure_uosc_loaded() then + pcall(mp.commandv, 'script-message-to', 'uosc', 'sync-cursor') + end + end) return true end @@ -927,6 +954,25 @@ local function _normalize_store_name(store) return trim(store) end +function M._store_name_is_visible_in_mpv(store) + store = _normalize_store_name(store) + return store ~= '' and store:lower() ~= 'local' +end + +function M._visible_store_names() + local out = {} + if type(_cached_store_names) ~= 'table' then + return out + end + for _, name in ipairs(_cached_store_names) do + name = _normalize_store_name(name) + if M._store_name_is_visible_in_mpv(name) then + out[#out + 1] = name + end + end + return out +end + local function _is_cached_store_name(store) local needle = _normalize_store_name(store) if needle == '' then @@ -1174,17 +1220,25 @@ local HELPER_START_DEBOUNCE = 2.0 -- Track ready-heartbeat freshness so stale or non-timestamp values don't mask a stopped helper local _helper_ready_last_value = '' local _helper_ready_last_seen_ts = 0 -local HELPER_READY_STALE_SECONDS = 10.0 +local HELPER_READY_STALE_SECONDS = 120.0 M._lyric_helper_state = M._lyric_helper_state or { last_start_ts = -1000, debounce = 3.0 } M._subtitle_autoselect_state = M._subtitle_autoselect_state or { serial = 0, deadline = 0 } +function M._normalize_mpv_user_data_text(value) + local text = trim(tostring(value or '')) + if text:sub(1, 1) == '"' and text:sub(-1) == '"' and #text >= 2 then + text = text:sub(2, -2) + end + return trim(text) +end + local function _is_pipeline_helper_ready() local helper_version = mp.get_property('user-data/medeia-pipeline-helper-version') if helper_version == nil or helper_version == '' then helper_version = mp.get_property_native('user-data/medeia-pipeline-helper-version') end - helper_version = tostring(helper_version or '') - if helper_version ~= '2026-03-23.1' then + helper_version = M._normalize_mpv_user_data_text(helper_version) + if helper_version ~= '' and helper_version ~= '2026-03-23.1' then return false end @@ -1197,7 +1251,7 @@ local function _is_pipeline_helper_ready() _helper_ready_last_seen_ts = 0 return false end - local s = tostring(ready) + local s = M._normalize_mpv_user_data_text(ready) if s == '' or s == '0' then _helper_ready_last_value = s _helper_ready_last_seen_ts = 0 @@ -1228,6 +1282,9 @@ local function _is_pipeline_helper_ready() -- Fall back only for non-timestamp values so stale helper timestamps from a -- previous session do not look fresh right after Lua reload. + if helper_version ~= '2026-03-23.1' then + return false + end if _helper_ready_last_seen_ts > 0 and (now - _helper_ready_last_seen_ts) <= HELPER_READY_STALE_SECONDS then return true end @@ -1236,22 +1293,33 @@ local function _is_pipeline_helper_ready() end local function _helper_ready_diagnostics() - local ready = mp.get_property(PIPELINE_READY_PROP) - if ready == nil or ready == '' then - ready = mp.get_property_native(PIPELINE_READY_PROP) + local raw_ready = mp.get_property(PIPELINE_READY_PROP) + if raw_ready == nil or raw_ready == '' then + raw_ready = mp.get_property_native(PIPELINE_READY_PROP) end - local helper_version = mp.get_property('user-data/medeia-pipeline-helper-version') - if helper_version == nil or helper_version == '' then - helper_version = mp.get_property_native('user-data/medeia-pipeline-helper-version') + local raw_helper_version = mp.get_property('user-data/medeia-pipeline-helper-version') + if raw_helper_version == nil or raw_helper_version == '' then + raw_helper_version = mp.get_property_native('user-data/medeia-pipeline-helper-version') end + local ready = M._normalize_mpv_user_data_text(raw_ready) + local helper_version = M._normalize_mpv_user_data_text(raw_helper_version) local now = mp.get_time() or 0 local age = 'n/a' if _helper_ready_last_seen_ts > 0 then age = string.format('%.2fs', math.max(0, now - _helper_ready_last_seen_ts)) end + local heartbeat_age = 'n/a' + local ready_epoch = tonumber(ready) + local os_now = (os and os.time) and os.time() or nil + if ready_epoch and ready_epoch > 1000000000 and os_now then + heartbeat_age = string.format('%.2fs', math.max(0, os_now - ready_epoch)) + end return 'ready=' .. tostring(ready or '') + .. ' raw_ready=' .. tostring(raw_ready or '') .. ' helper_version=' .. tostring(helper_version or '') + .. ' raw_helper_version=' .. tostring(raw_helper_version or '') .. ' required_version=2026-03-23.1' + .. ' heartbeat_age=' .. tostring(heartbeat_age) .. ' last_value=' .. tostring(_helper_ready_last_value or '') .. ' last_seen_age=' .. tostring(age) end @@ -2327,6 +2395,29 @@ local function _get_current_item_is_image() return false end +function M._has_visual_screenshot_source() + if _get_current_item_is_image() then + return true, 'image' + end + + local width = tonumber(mp.get_property_number('width') or 0) or 0 + local height = tonumber(mp.get_property_number('height') or 0) or 0 + if width > 0 and height > 0 then + return true, 'video' + end + + local video_params = mp.get_property_native('video-params') + if type(video_params) == 'table' then + local params_width = tonumber(video_params.w or video_params.dw or video_params.width or 0) or 0 + local params_height = tonumber(video_params.h or video_params.dh or video_params.height or 0) or 0 + if params_width > 0 and params_height > 0 then + return true, 'video' + end + end + + return false, '' +end + local function _set_image_property(value) pcall(mp.set_property_native, 'user-data/mpv/image', value and true or false) @@ -2776,6 +2867,7 @@ local function _open_store_picker_for_pending_screenshot() local function build_items() local selected = _get_selected_store() + local visible_names = M._visible_store_names() local items = {} items[#items + 1] = { @@ -2784,8 +2876,8 @@ local function _open_store_picker_for_pending_screenshot() value = { 'script-message-to', mp.get_script_name(), 'medeia-image-screenshot-pick-path', '{}' }, } - if type(_cached_store_names) == 'table' and #_cached_store_names > 0 then - for _, name in ipairs(_cached_store_names) do + if #visible_names > 0 then + for _, name in ipairs(visible_names) do name = trim(tostring(name or '')) if name ~= '' then items[#items + 1] = { @@ -2822,6 +2914,16 @@ local function _open_store_picker_for_pending_screenshot() end local function _capture_screenshot() + local screenshot_available = false + local screenshot_kind = '' + screenshot_available, screenshot_kind = M._has_visual_screenshot_source() + if not screenshot_available then + _lua_log('screenshot: blocked reason=no visual source') + M._sync_uosc_cursor('screenshot-unavailable') + mp.osd_message('Screenshot unavailable (no video/image frame)', 2) + return + end + local function _format_time_label(seconds) local total = math.max(0, math.floor(tonumber(seconds or 0) or 0)) local hours = math.floor(total / 3600) @@ -2856,6 +2958,11 @@ local function _capture_screenshot() end local out_path = utils.join_path(temp_dir, filename) out_path = _normalize_fs_path(out_path) + local was_paused = mp.get_property_native('pause') and true or false + + if not was_paused then + pcall(mp.set_property_native, 'pause', true) + end local function do_screenshot(mode) mode = mode or 'video' @@ -2869,22 +2976,31 @@ local function _capture_screenshot() -- try 'window' as fallback. local ok = do_screenshot('video') if not ok then - _lua_log('screenshot: video-mode failed; trying window-mode') + _lua_log('screenshot: video-mode failed; trying window-mode kind=' .. tostring(screenshot_kind)) ok = do_screenshot('window') end if not ok then _lua_log('screenshot: BOTH video and window modes FAILED') + if not was_paused then + pcall(mp.set_property_native, 'pause', false) + end + M._sync_uosc_cursor('screenshot-failed') mp.osd_message('Screenshot failed (no frames)', 2) return end + if not was_paused then + mp.osd_message('Screenshot captured. Playback paused for review.', 2) + end + _ensure_selected_store_loaded() local function dispatch_screenshot_save() - local store_count = (type(_cached_store_names) == 'table') and #_cached_store_names or 0 + local visible_store_names = M._visible_store_names() + local store_count = #visible_store_names local selected_store = _normalize_store_name(_get_selected_store()) - if not _is_cached_store_name(selected_store) then + if not M._store_name_is_visible_in_mpv(selected_store) then selected_store = '' end @@ -2895,7 +3011,7 @@ local function _capture_screenshot() end if selected_store == '' and store_count == 1 then - selected_store = _normalize_store_name(_cached_store_names[1]) + selected_store = _normalize_store_name(visible_store_names[1]) end if selected_store == '' then @@ -3616,16 +3732,11 @@ _refresh_store_cache = function(timeout_seconds, on_complete) end local cached_json = mp.get_property('user-data/medeia-store-choices-cached') - _lua_log('stores: cache_read cached_json=' .. tostring(cached_json) .. ' len=' .. tostring(cached_json and #cached_json or 0)) - if cached_json and cached_json ~= '' then local ok, cached_resp = pcall(utils.parse_json, cached_json) - _lua_log('stores: cache_parse ok=' .. tostring(ok) .. ' resp_type=' .. tostring(type(cached_resp))) if ok then if type(cached_resp) == 'string' then - _lua_log('stores: cache_parse returned string, trying again...') ok, cached_resp = pcall(utils.parse_json, cached_resp) - _lua_log('stores: cache_parse retry ok=' .. tostring(ok) .. ' resp_type=' .. tostring(type(cached_resp))) end if ok then if apply_store_choices(cached_resp, 'cache') then @@ -3633,10 +3744,8 @@ _refresh_store_cache = function(timeout_seconds, on_complete) end end else - _lua_log('stores: cache_parse failed ok=' .. tostring(ok) .. ' resp=' .. tostring(cached_resp)) + _lua_log('stores: cached property parse failed; falling back to direct config') end - else - _lua_log('stores: cache_empty cached_json=' .. tostring(cached_json)) end if not _store_cache_retry_pending then @@ -3678,10 +3787,11 @@ local function _open_store_picker() _ensure_selected_store_loaded() local selected = _get_selected_store() - local cached_count = (type(_cached_store_names) == 'table') and #_cached_store_names or 0 + local visible_store_names = M._visible_store_names() + local cached_count = #visible_store_names local cached_preview = '' - if type(_cached_store_names) == 'table' and #_cached_store_names > 0 then - cached_preview = table.concat(_cached_store_names, ', ') + if cached_count > 0 then + cached_preview = table.concat(visible_store_names, ', ') end _lua_log( 'stores: open picker selected=' @@ -3695,10 +3805,11 @@ local function _open_store_picker() local function build_items() local selected = _get_selected_store() local current_url = trim(tostring(_current_url_for_web_actions() or _current_target() or '')) + local visible_names = M._visible_store_names() local items = {} - if type(_cached_store_names) == 'table' and #_cached_store_names > 0 then - for _, name in ipairs(_cached_store_names) do + if #visible_names > 0 then + for _, name in ipairs(visible_names) do name = trim(tostring(name or '')) if name ~= '' then local payload = { store = name } @@ -3728,25 +3839,13 @@ local function _open_store_picker() -- Open immediately with whatever cache we have. _uosc_open_list_picker(STORE_PICKER_MENU_TYPE, 'Store', build_items()) - -- Best-effort refresh; retry briefly to avoid races where the helper isn't - -- ready/observing yet at the exact moment the menu opens. - local function attempt_refresh(tries_left) + mp.add_timeout(0.05, function() _refresh_store_cache(1.2, function(success, changed) if success and changed then _lua_log('stores: reopening menu (store list changed)') _uosc_open_list_picker(STORE_PICKER_MENU_TYPE, 'Store', build_items()) end end) - - if tries_left > 0 then - mp.add_timeout(0.25, function() - attempt_refresh(tries_left - 1) - end) - end - end - - mp.add_timeout(0.05, function() - attempt_refresh(6) end) end @@ -4225,6 +4324,9 @@ end _store_status_hint_for_url = function(store, url, fallback) store = trim(tostring(store or '')) url = trim(tostring(url or '')) + if not M._store_name_is_visible_in_mpv(store) then + return fallback + end if store == '' or url == '' or not _current_store_url_status_matches(store, url) then return fallback end @@ -4257,6 +4359,11 @@ _refresh_current_store_url_status = function(reason) local url = trim(tostring(target or '')) local normalized_url = _normalize_url_for_store_lookup(url) + if not M._store_name_is_visible_in_mpv(store) then + _begin_current_store_url_status(store, url, 'idle') + return + end + if url == '' or not _is_http_url(url) or _extract_store_hash(url) then _begin_current_store_url_status(store, url, 'idle') return @@ -5167,6 +5274,7 @@ local function _open_save_location_picker_for_pending_download() local function build_items() local selected = _get_selected_store() + local visible_names = M._visible_store_names() local items = { { title = 'Pick folder…', @@ -5175,8 +5283,8 @@ local function _open_save_location_picker_for_pending_download() }, } - if type(_cached_store_names) == 'table' and #_cached_store_names > 0 then - for _, name in ipairs(_cached_store_names) do + if #visible_names > 0 then + for _, name in ipairs(visible_names) do name = trim(tostring(name or '')) if name ~= '' then local payload = { store = name } @@ -5898,10 +6006,13 @@ end -- Open the command submenu with tailored cmdlets (screenshot, clip, trim prompt) function M.open_cmd_menu() + local screenshot_available, screenshot_kind = M._has_visual_screenshot_source() + local screenshot_hint = screenshot_available and ((screenshot_kind == 'image') and 'Capture current image' or 'Capture current frame') or 'Disabled: no video or image frame' local items = { { title = 'Screenshot', - hint = 'Capture a screenshot', + hint = screenshot_hint, + selectable = screenshot_available, value = { 'script-message-to', mp.get_script_name(), 'medios-cmd-exec', utils.format_json({ cmd = 'screenshot' }) }, }, { @@ -6195,19 +6306,15 @@ mp.register_script_message('medios-load-url-event', function(json) if not ok or type(event) ~= 'table' then _lua_log('[LOAD-URL] Failed to parse JSON: ' .. tostring(json)) mp.osd_message('Failed to parse URL', 2) - if ensure_uosc_loaded() then - _lua_log('[LOAD-URL] Closing menu due to parse error') - mp.commandv('script-message-to', 'uosc', 'close-menu', LOAD_URL_MENU_TYPE) - end + _lua_log('[LOAD-URL] Closing menu due to parse error') + M._close_uosc_menu_and_sync(LOAD_URL_MENU_TYPE, 'load-url-parse-error') return end _lua_log('[LOAD-URL] Parsed event: type=' .. tostring(event.type) .. ', query=' .. tostring(event.query)) if event.type ~= 'search' then _lua_log('[LOAD-URL] Event type is not search: ' .. tostring(event.type)) - if ensure_uosc_loaded() then - _lua_log('[LOAD-URL] Closing menu due to type mismatch') - mp.commandv('script-message-to', 'uosc', 'close-menu', LOAD_URL_MENU_TYPE) - end + _lua_log('[LOAD-URL] Closing menu due to type mismatch') + M._close_uosc_menu_and_sync(LOAD_URL_MENU_TYPE, 'load-url-close') return end @@ -6217,10 +6324,8 @@ mp.register_script_message('medios-load-url-event', function(json) _lua_log('[LOAD-URL] URL is empty') _log_all('ERROR', 'Load URL failed: URL is empty') mp.osd_message('URL is empty', 2) - if ensure_uosc_loaded() then - _lua_log('[LOAD-URL] Closing menu due to empty URL') - mp.commandv('script-message-to', 'uosc', 'close-menu', LOAD_URL_MENU_TYPE) - end + _lua_log('[LOAD-URL] Closing menu due to empty URL') + M._close_uosc_menu_and_sync(LOAD_URL_MENU_TYPE, 'load-url-empty') return end @@ -6341,6 +6446,9 @@ function M.show_menu() local target = _current_target() local selected_store = trim(tostring(_get_selected_store() or '')) + if not M._store_name_is_visible_in_mpv(selected_store) then + selected_store = '' + end local download_hint = nil if selected_store ~= '' and target and not _extract_store_hash(tostring(target)) then download_hint = _store_status_hint_for_url(selected_store, tostring(target), 'save to ' .. selected_store) diff --git a/MPV/portable_config/scripts/uosc/scripts/uosc/main.lua b/MPV/portable_config/scripts/uosc/scripts/uosc/main.lua index df6e5a0..da37775 100644 --- a/MPV/portable_config/scripts/uosc/scripts/uosc/main.lua +++ b/MPV/portable_config/scripts/uosc/scripts/uosc/main.lua @@ -1123,8 +1123,12 @@ mp.register_script_message('sync-cursor', function() local mouse = mp.get_property_native('mouse-pos') if type(mouse) == 'table' and mouse.hover and mouse.x and mouse.y then cursor:move(mouse.x, mouse.y) - else - cursor:leave() + elseif cursor.x < math.huge and cursor.y < math.huge then + if cursor.hidden then + cursor.hidden = false + Elements:trigger('global_mouse_enter') + end + Elements:update_proximities() end cursor:queue_autohide() request_render()