Updater.lua 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. local Element = require('elements/Element')
  2. local dots = {'.', '..', '...'}
  3. local function cleanup_output(output)
  4. return tostring(output):gsub('%c*\n%c*', '\n'):match('^[%s%c]*(.-)[%s%c]*$')
  5. end
  6. ---@class Updater : Element
  7. local Updater = class(Element)
  8. function Updater:new() return Class.new(self) --[[@as Updater]] end
  9. function Updater:init()
  10. Element.init(self, 'updater', {render_order = 1000})
  11. self.output = nil
  12. self.message = t('Updating uosc')
  13. self.state = 'pending' -- Matches icon name
  14. local config_dir = mp.command_native({'expand-path', '~~/'})
  15. Elements:maybe('curtain', 'register', self.id)
  16. local function handle_result(success, result, error)
  17. if success and result and result.status == 0 then
  18. self.state = 'done'
  19. self.message = t('uosc has been installed. Restart mpv for it to take effect.')
  20. else
  21. self.state = 'error'
  22. self.message = t('An error has occurred.') .. ' ' .. t('See above for clues.')
  23. end
  24. local output = (result.stdout or '') .. '\n' .. (error or result.stderr or '')
  25. if state.platform == 'darwin' then
  26. output =
  27. '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' ..
  28. output
  29. end
  30. self.output = ass_escape(cleanup_output(output))
  31. request_render()
  32. end
  33. local function update(args)
  34. local env = utils.get_env_list()
  35. env[#env + 1] = 'MPV_CONFIG_DIR=' .. config_dir
  36. mp.command_native_async({
  37. name = 'subprocess',
  38. capture_stderr = true,
  39. capture_stdout = true,
  40. playback_only = false,
  41. args = args,
  42. env = env,
  43. }, handle_result)
  44. end
  45. if state.platform == 'windows' then
  46. local url = 'https://raw.githubusercontent.com/tomasklaen/uosc/HEAD/installers/windows.ps1'
  47. update({'powershell', '-NoProfile', '-Command', 'irm ' .. url .. ' | iex'})
  48. else
  49. -- Detect missing dependencies. We can't just let the process run and
  50. -- report an error, as on snap packages there's no error. Everything
  51. -- either exits with 0, or no helpful output/error message.
  52. local missing = {}
  53. for _, name in ipairs({'curl', 'unzip'}) do
  54. local result = mp.command_native({
  55. name = 'subprocess',
  56. capture_stdout = true,
  57. playback_only = false,
  58. args = {'which', name},
  59. })
  60. local path = cleanup_output(result and result.stdout or '')
  61. if path == '' then
  62. missing[#missing + 1] = name
  63. end
  64. end
  65. if #missing > 0 then
  66. local stderr = 'Missing dependencies: ' .. table.concat(missing, ', ')
  67. if config_dir:match('/snap/') then
  68. stderr = stderr ..
  69. '\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!'
  70. end
  71. handle_result(false, {stderr = stderr})
  72. else
  73. local url = 'https://raw.githubusercontent.com/tomasklaen/uosc/HEAD/installers/unix.sh'
  74. update({'/bin/bash', '-c', 'source <(curl -fsSL ' .. url .. ')'})
  75. end
  76. end
  77. end
  78. function Updater:destroy()
  79. Elements:maybe('curtain', 'unregister', self.id)
  80. Element.destroy(self)
  81. end
  82. function Updater:render()
  83. local ass = assdraw.ass_new()
  84. local text_size = math.min(20 * state.scale, display.height / 20)
  85. local icon_size = text_size * 2
  86. local center_x = round(display.width / 2)
  87. local color = fg
  88. if self.state == 'done' then
  89. color = config.color.success
  90. elseif self.state == 'error' then
  91. color = config.color.error
  92. end
  93. -- Divider
  94. local divider_width = round(math.min(500 * state.scale, display.width * 0.8))
  95. local divider_half, divider_border_half, divider_y = divider_width / 2, round(1 * state.scale), display.height * 0.65
  96. local divider_ay, divider_by = round(divider_y - divider_border_half), round(divider_y + divider_border_half)
  97. ass:rect(center_x - divider_half, divider_ay, center_x - icon_size, divider_by, {
  98. color = color, border = options.text_border * state.scale, border_color = bg, opacity = 0.5,
  99. })
  100. ass:rect(center_x + icon_size, divider_ay, center_x + divider_half, divider_by, {
  101. color = color, border = options.text_border * state.scale, border_color = bg, opacity = 0.5,
  102. })
  103. if self.state == 'pending' then
  104. ass:spinner(center_x, divider_y, icon_size, {
  105. color = fg, border = options.text_border * state.scale, border_color = bg,
  106. })
  107. else
  108. ass:icon(center_x, divider_y, icon_size * 0.8, self.state, {
  109. color = color, border = options.text_border * state.scale, border_color = bg,
  110. })
  111. end
  112. -- Output
  113. local output = self.output or dots[math.ceil((mp.get_time() % 1) * #dots)]
  114. ass:txt(center_x, divider_y - icon_size, 2, output, {
  115. size = text_size, color = fg, border = options.text_border * state.scale, border_color = bg,
  116. })
  117. -- Message
  118. ass:txt(center_x, divider_y + icon_size, 5, self.message, {
  119. size = text_size, bold = true, color = color, border = options.text_border * state.scale, border_color = bg,
  120. })
  121. -- Button
  122. if self.state ~= 'pending' then
  123. -- Background
  124. local button_y = divider_y + icon_size * 1.75
  125. local button_rect = {
  126. ax = round(center_x - icon_size / 2),
  127. ay = round(button_y),
  128. bx = round(center_x + icon_size / 2),
  129. by = round(button_y + icon_size),
  130. }
  131. local is_hovered = get_point_to_rectangle_proximity(cursor, button_rect) == 0
  132. ass:rect(button_rect.ax, button_rect.ay, button_rect.bx, button_rect.by, {
  133. color = fg,
  134. radius = state.radius,
  135. opacity = is_hovered and 1 or 0.5,
  136. })
  137. -- Icon
  138. local x = round(button_rect.ax + (button_rect.bx - button_rect.ax) / 2)
  139. local y = round(button_rect.ay + (button_rect.by - button_rect.ay) / 2)
  140. ass:icon(x, y, icon_size * 0.8, 'close', {color = bg})
  141. cursor:zone('primary_click', button_rect, function() self:destroy() end)
  142. end
  143. return ass
  144. end
  145. return Updater