diff options
author | Philip Langdale <philipl@overt.org> | 2019-11-26 08:50:14 +0800 |
---|---|---|
committer | Philip Langdale <github.philipl@overt.org> | 2019-11-29 16:56:20 +0800 |
commit | a220f086486563bf27110013fc3f2322bbb198c7 (patch) | |
tree | 44b0211f13718c7a274f1283a157115462beab7b /player/lua | |
parent | 901b3dddb0c03d20ec634197b17a9d6b15e8e641 (diff) | |
download | mpv-a220f086486563bf27110013fc3f2322bbb198c7.tar.bz2 mpv-a220f086486563bf27110013fc3f2322bbb198c7.tar.xz |
osc: implement pseudo client side decorations via OSC
Today, if window decorations are not present, either because they were
disabled, or because the platform doesn't support them
(eg: gnome-shell on wayland), there are no window controls, meaning it
is not possible to minimize/maximize/close a window without knowing
keyboard shortcuts.
While you can imagine various ways of offering client side decorations,
it is attractive to consider using OSC because that is functionality
that we already have.
The main work here is defining a separate input area from the main
OSC box with its own buttons, etc.
While we could probably handle auto-detection based on whether
decorations are present or not, it's manually controlled for now.
The window control logic is mostly disconnected from the OSC itself,
except in the case of the `topbar` layout, where there has to be
coordination so that the controls don't get drawn on top of each other.
I had to do fine-positioning of the buttons based on the font on
my system, so don't be surprised if it looks wrong elsewhere.
You could also argue that window controls should be unscaled, even
if the main OSC box is scaled, but I've not tried to do this.
Diffstat (limited to 'player/lua')
-rw-r--r-- | player/lua/osc.lua | 152 |
1 files changed, 145 insertions, 7 deletions
diff --git a/player/lua/osc.lua b/player/lua/osc.lua index 35abf20ae2..46819858a5 100644 --- a/player/lua/osc.lua +++ b/player/lua/osc.lua @@ -45,6 +45,7 @@ local user_opts = { visibility = "auto", -- only used at init to set visibility_mode(...) boxmaxchars = 80, -- title crop threshold for box layout boxvideo = false, -- apply osc_param.video_margins to video + windowcontrols = "no", -- where to show window controls (or not at all) } -- read_options may modify hidetimeout, so save the original default value in @@ -85,6 +86,9 @@ local osc_styles = { timecodesBar = "{\\blur0\\bord0\\1c&HFFFFFF\\3c&HFFFFFF\\fs27}", timePosBar = "{\\blur0\\bord".. user_opts.tooltipborder .."\\1c&HFFFFFF\\3c&H000000\\fs30}", vidtitleBar = "{\\blur0\\bord0\\1c&HFFFFFF\\3c&HFFFFFF\\fs18\\q2}", + + wcButtons = "{\\1c&HFFFFFF\\fs24\\fnmpv-osd-symbols}", + wcBar = "{\\1c&H000000}", } -- internal states, do not touch @@ -115,7 +119,7 @@ local state = { using_video_margins = false, } - +local window_control_box_width = 80 -- @@ -961,6 +965,104 @@ function add_layout(name) end end +-- Window Controls +function window_controls(alignment, topbar) + local wc_geo = { + x = 0, + y = 30, + an = 1, + w = osc_param.playresx, + h = 30, + } + + local controlbox_w = window_control_box_width + local titlebox_w = wc_geo.w - controlbox_w + + -- Default alignment is "right" + local controlbox_left = wc_geo.w - controlbox_w + local titlebox_left = wc_geo.x + 5 + + if alignment == "left" then + controlbox_left = wc_geo.x + titlebox_left = wc_geo.x + controlbox_w + 5 + elseif alignment == "right" then + -- Already default + else + msg.error("Invalid setting \""..alignment.."\" for windowcontrols") + -- Falls back to "right" + end + + add_area("window-controls", + get_hitbox_coords(controlbox_left, wc_geo.y, wc_geo.an, + controlbox_w, wc_geo.h)) + + local lo + + -- Background Bar + new_element("wcbar", "box") + lo = add_layout("wcbar") + lo.geometry = wc_geo + lo.layer = 10 + lo.style = osc_styles.wcBar + lo.alpha[1] = user_opts.boxalpha + + local first_geo = + {x = controlbox_left + 5, y = 15, an = 4, w = 25, h = 25} + local second_geo = + {x = controlbox_left + 30, y = 15, an = 4, w = 25, h = 25} + local third_geo = + {x = controlbox_left + 55, y = 15, an = 4, w = 25, h = 25} + + -- Close + ne = new_element("close", "button") + ne.content = "\226\152\146" + ne.eventresponder["mbtn_left_up"] = + function () mp.commandv("quit") end + lo = add_layout("close") + lo.geometry = alignment == "left" and first_geo or third_geo + lo.style = osc_styles.wcButtons + + -- Minimize + ne = new_element("minimize", "button") + ne.content = "\226\154\128" + ne.eventresponder["mbtn_left_up"] = + function () mp.commandv("cycle", "window-minimized") end + lo = add_layout("minimize") + lo.geometry = alignment == "left" and second_geo or first_geo + lo.style = osc_styles.wcButtons + + -- Maximize + ne = new_element("maximize", "button") + ne.content = "\226\150\163" + ne.eventresponder["mbtn_left_up"] = + function () mp.commandv("cycle", "window-maximized") end + lo = add_layout("maximize") + lo.geometry = alignment == "left" and third_geo or second_geo + -- At least with default Ubuntu fonts, this symbol is differently aligned + lo.geometry.y = 13 + lo.style = osc_styles.wcButtons + + if topbar then + -- The title is already there as part of the top bar + return + end + + -- Window Title + ne = new_element("wctitle", "button") + ne.content = function () + local title = mp.command_native({"expand-text", user_opts.title}) + -- escape ASS, and strip newlines and trailing slashes + title = title:gsub("\\n", " "):gsub("\\$", ""):gsub("{","\\{") + return not (title == "") and title or "mpv" + end + lo = add_layout("wctitle") + lo.geometry = + { x = titlebox_left, y = wc_geo.y - 3, an = 1, w = titlebox_w, h = wc_geo.h } + lo.style = string.format("%s{\\clip(%f,%f,%f,%f)}", + osc_styles.wcButtons, + titlebox_left, wc_geo.y - wc_geo.h, titlebox_w, wc_geo.y + wc_geo.h) +end + -- -- Layouts -- @@ -1252,7 +1354,7 @@ layouts["slimbox"] = function () end -function bar_layout(direction) +function bar_layout(direction, windowcontrols) local osc_geo = { x = -2, y, @@ -1268,6 +1370,20 @@ function bar_layout(direction) local tsW = 90 local minW = (buttonW + padX)*5 + (tcW + padX)*4 + (tsW + padX)*2 + -- Special topbar handling when window controls are present + local padwc_l + local padwc_r + if direction < 0 or windowcontrols == "no" then + padwc_l = 0 + padwc_r = 0 + elseif windowcontrols == "left" then + padwc_l = window_control_box_width + padwc_r = 0 + else + padwc_l = 0 + padwc_r = window_control_box_width + end + if ((osc_param.display_aspect > 0) and (osc_param.playresx < minW)) then osc_param.playresy = minW / osc_param.display_aspect osc_param.playresx = osc_param.playresy * osc_param.display_aspect @@ -1348,7 +1464,7 @@ function bar_layout(direction) -- Playback control buttons - geo = { x = osc_geo.x + padX, y = line2, an = 4, + geo = { x = osc_geo.x + padX + padwc_l, y = line2, an = 4, w = buttonW, h = 36 - padY*2} lo = add_layout("playpause") lo.geometry = geo @@ -1374,7 +1490,7 @@ function bar_layout(direction) local sb_l = geo.x + padX -- Fullscreen button - geo = { x = osc_geo.x + osc_geo.w - buttonW - padX, y = geo.y, an = 4, + geo = { x = osc_geo.x + osc_geo.w - buttonW - padX - padwc_r, y = geo.y, an = 4, w = buttonW, h = geo.h } lo = add_layout("tog_fs") lo.geometry = geo @@ -1442,11 +1558,11 @@ function bar_layout(direction) end layouts["bottombar"] = function() - bar_layout(-1) + bar_layout(-1, false) end layouts["topbar"] = function() - bar_layout(1) + bar_layout(1, user_opts.windowcontrols) end -- Validate string type user options @@ -1704,7 +1820,6 @@ function osc_init() ne.eventresponder["mbtn_left_up"] = function () mp.commandv("cycle", "fullscreen") end - --seekbar ne = new_element("seekbar", "slider") @@ -1863,6 +1978,12 @@ function osc_init() -- load layout layouts[user_opts.layout]() + -- load window controls + if user_opts.windowcontrols ~= "no" then + window_controls(user_opts.windowcontrols, + user_opts.layout == "topbar") + end + --do something with the elements prepare_elements() @@ -2103,6 +2224,18 @@ function render() end end + if user_opts.windowcontrols ~= "no" then + for _,cords in ipairs(osc_param.areas["window-controls"]) do + if state.osc_visible then -- activate only when OSC is actually visible + set_virt_mouse_area(cords.x1, cords.y1, cords.x2, cords.y2, "window-controls") + end + + if (mouse_hit_coords(cords.x1, cords.y1, cords.x2, cords.y2)) then + mouse_over_osc = true + end + end + end + -- autohide if not (state.showtime == nil) and (user_opts.hidetimeout >= 0) and (state.showtime + (user_opts.hidetimeout/1000) < now) @@ -2353,6 +2486,11 @@ mp.set_key_bindings({ }, "input", "force") mp.enable_key_bindings("input") +mp.set_key_bindings({ + {"mbtn_left", function(e) process_event("mbtn_left", "up") end, + function(e) process_event("mbtn_left", "down") end}, +}, "window-controls", "force") +mp.enable_key_bindings("window-controls") user_opts.hidetimeout_orig = user_opts.hidetimeout |