Files
dotfiles/mpv/scripts/file-browser/addons/windir.lua
2025-06-14 20:26:14 +02:00

98 lines
3.2 KiB
Lua

--[[
An addon for mpv-file-browser which uses the Windows dir command to parse native directories
This behaves near identically to the native parser, but IO is done asynchronously.
Available at: https://github.com/CogentRedTester/mpv-file-browser/tree/master/addons
]]--
local mp = require "mp"
local msg = require "mp.msg"
local fb = require "file-browser"
--this is a LuaJit module this addon will not load if not using LuaJit
local ffi = require 'ffi'
ffi.cdef([[
int __stdcall WideCharToMultiByte(unsigned int CodePage, unsigned int dwFlags, const wchar_t *lpWideCharStr, int cchWideChar, char *lpMultiByteStr, int cbMultiByte, const char *lpDefaultChar, bool *lpUsedDefaultChar);
]])
--converts a UTF16 string to a UTF8 string
--this function was adapted from https://github.com/mpv-player/mpv/issues/10139#issuecomment-1117954648
local function utf8(WideCharStr)
WideCharStr = ffi.cast("wchar_t*", WideCharStr)
if not WideCharStr then return nil end
local utf8_size = ffi.C.WideCharToMultiByte(65001, 0, WideCharStr, -1, nil, 0, nil, nil) --CP_UTF8
if utf8_size > 0 then
local utf8_path = ffi.new("char[?]", utf8_size)
local utf8_size = ffi.C.WideCharToMultiByte(65001, 0, WideCharStr, -1, utf8_path, utf8_size, nil, nil)
if utf8_size > 0 then
--removes the trailing `\0` character which can break things
return ffi.string(utf8_path, utf8_size):sub(1, -2)
end
end
end
local dir = {
priority = 109,
api_version = "1.1.0",
name = "cmd-dir",
keybind_name = "file"
}
local function command(args, parse_state)
local _, cmd = parse_state:yield(
mp.command_native_async({
name = "subprocess",
playback_only = false,
capture_stdout = true,
capture_stderr = true,
args = args,
}, fb.coroutine.callback() )
)
cmd.stdout = utf8(cmd.stdout)
cmd.stderr = utf8(cmd.stderr)
--dir returns this exact error message if the directory is empty
if cmd.status == 1 and cmd.stderr == "File Not Found\r\n" then cmd.status = 0 end
return cmd.status == 0 and cmd.stdout or nil, cmd.stderr
end
function dir:can_parse(directory)
if directory == "" then return end
return not fb.get_protocol(directory)
end
function dir:parse(directory, parse_state)
local list = {}
local files, dirs, err
-- the dir command expects backslashes for our paths
directory = directory:gsub("/", "\\")
dirs, err = command({ "cmd", "/U", "/c", "dir", "/b", "/ad", directory }, parse_state)
if not dirs then return msg.error(err) end
files, err = command({ "cmd", "/U", "/c", "dir", "/b", "/a-d", directory }, parse_state)
if not files then return msg.error(err) end
for name in dirs:gmatch("[^\n\r]+") do
name = name.."/"
if fb.valid_dir(name) then
table.insert(list, { name = name, type = "dir" })
msg.trace(name)
end
end
for name in files:gmatch("[^\n\r]+") do
if fb.valid_file(name) then
table.insert(list, { name = name, type = "file" })
msg.trace(name)
end
end
return list, { filtered = true }
end
return dir