local Element = require('elements/Element') local dots = {'.', '..', '...'} local function cleanup_output(output) return tostring(output):gsub('%c*\n%c*', '\n'):match('^[%s%c]*(.-)[%s%c]*$') end ---@class Updater : Element local Updater = class(Element) function Updater:new() return Class.new(self) --[[@as Updater]] end function Updater:init() Element.init(self, 'updater', {render_order = 1000}) self.output = nil self.message = t('Updating uosc') self.state = 'pending' -- Matches icon name local config_dir = mp.command_native({'expand-path', '~~/'}) Elements:maybe('curtain', 'register', self.id) local function handle_result(success, result, error) if success and result and result.status == 0 then self.state = 'done' self.message = t('uosc has been installed. Restart mpv for it to take effect.') else self.state = 'error' self.message = t('An error has occurred.') .. ' ' .. t('See above for clues.') end local output = (result.stdout or '') .. '\n' .. (error or result.stderr or '') if state.platform == 'darwin' then output = 'Self-updater is known not to work on MacOS.\nIf you know about a solution, please make an issue and share it with us!.\n' .. output end self.output = ass_escape(cleanup_output(output)) request_render() end local function update(args) local env = utils.get_env_list() env[#env + 1] = 'MPV_CONFIG_DIR=' .. config_dir mp.command_native_async({ name = 'subprocess', capture_stderr = true, capture_stdout = true, playback_only = false, args = args, env = env, }, handle_result) end if state.platform == 'windows' then local url = 'https://raw.githubusercontent.com/tomasklaen/uosc/HEAD/installers/windows.ps1' update({'powershell', '-NoProfile', '-Command', 'irm ' .. url .. ' | iex'}) else -- Detect missing dependencies. We can't just let the process run and -- report an error, as on snap packages there's no error. Everything -- either exits with 0, or no helpful output/error message. local missing = {} for _, name in ipairs({'curl', 'unzip'}) do local result = mp.command_native({ name = 'subprocess', capture_stdout = true, playback_only = false, args = {'which', name}, }) local path = cleanup_output(result and result.stdout or '') if path == '' then missing[#missing + 1] = name end end if #missing > 0 then local stderr = 'Missing dependencies: ' .. table.concat(missing, ', ') if config_dir:match('/snap/') then stderr = stderr .. '\nThis is a known error for mpv snap packages.\nYou can still update uosc by entering the Linux install command from uosc\'s readme into your terminal, it just can\'t be done this way.\nIf you know about a solution, please make an issue and share it with us!' end handle_result(false, {stderr = stderr}) else local url = 'https://raw.githubusercontent.com/tomasklaen/uosc/HEAD/installers/unix.sh' update({'/bin/bash', '-c', 'source <(curl -fsSL ' .. url .. ')'}) end end end function Updater:destroy() Elements:maybe('curtain', 'unregister', self.id) Element.destroy(self) end function Updater:render() local ass = assdraw.ass_new() local text_size = math.min(20 * state.scale, display.height / 20) local icon_size = text_size * 2 local center_x = round(display.width / 2) local color = fg if self.state == 'done' then color = config.color.success elseif self.state == 'error' then color = config.color.error end -- Divider local divider_width = round(math.min(500 * state.scale, display.width * 0.8)) local divider_half, divider_border_half, divider_y = divider_width / 2, round(1 * state.scale), display.height * 0.65 local divider_ay, divider_by = round(divider_y - divider_border_half), round(divider_y + divider_border_half) ass:rect(center_x - divider_half, divider_ay, center_x - icon_size, divider_by, { color = color, border = options.text_border * state.scale, border_color = bg, opacity = 0.5, }) ass:rect(center_x + icon_size, divider_ay, center_x + divider_half, divider_by, { color = color, border = options.text_border * state.scale, border_color = bg, opacity = 0.5, }) if self.state == 'pending' then ass:spinner(center_x, divider_y, icon_size, { color = fg, border = options.text_border * state.scale, border_color = bg, }) else ass:icon(center_x, divider_y, icon_size * 0.8, self.state, { color = color, border = options.text_border * state.scale, border_color = bg, }) end -- Output local output = self.output or dots[math.ceil((mp.get_time() % 1) * #dots)] ass:txt(center_x, divider_y - icon_size, 2, output, { size = text_size, color = fg, border = options.text_border * state.scale, border_color = bg, }) -- Message ass:txt(center_x, divider_y + icon_size, 5, self.message, { size = text_size, bold = true, color = color, border = options.text_border * state.scale, border_color = bg, }) -- Button if self.state ~= 'pending' then -- Background local button_y = divider_y + icon_size * 1.75 local button_rect = { ax = round(center_x - icon_size / 2), ay = round(button_y), bx = round(center_x + icon_size / 2), by = round(button_y + icon_size), } local is_hovered = get_point_to_rectangle_proximity(cursor, button_rect) == 0 ass:rect(button_rect.ax, button_rect.ay, button_rect.bx, button_rect.by, { color = fg, radius = state.radius, opacity = is_hovered and 1 or 0.5, }) -- Icon local x = round(button_rect.ax + (button_rect.bx - button_rect.ax) / 2) local y = round(button_rect.ay + (button_rect.by - button_rect.ay) / 2) ass:icon(x, y, icon_size * 0.8, 'close', {color = bg}) cursor:zone('primary_click', button_rect, function() self:destroy() end) end return ass end return Updater