From 59416d33ae7b93991d2d8eca7ebb50e5f7496e55 Mon Sep 17 00:00:00 2001 From: a Date: Mon, 12 Aug 2024 05:05:55 -0500 Subject: [PATCH] main.lua --- .../private_mpv/scripts/osc/main.lua | 965 ++++++++++++++++++ 1 file changed, 965 insertions(+) create mode 100644 private_dot_config/private_mpv/scripts/osc/main.lua diff --git a/private_dot_config/private_mpv/scripts/osc/main.lua b/private_dot_config/private_mpv/scripts/osc/main.lua new file mode 100644 index 0000000..7b3f759 --- /dev/null +++ b/private_dot_config/private_mpv/scripts/osc/main.lua @@ -0,0 +1,965 @@ +-- mpv oscf modern +-- by maoiscat +-- github.com/maoiscat/ +package.path = package.path .. ";".. os.getenv("HOME") .. "/.config/mpv/lib/oscf/?.lua" +require("expansion") +local assdraw = require 'mp.assdraw' + +-- user options +opts = { + scale = 1, -- osc render scale + fixedHeight = false, -- true to allow osc scale with window + hideTimeout = 1, -- seconds untile osc hides, negative means never + fadeDuration = 0.5, -- seconds during fade out, negative means never + } + +-- logo and message works out of box +addToIdleLayout('logo') + +-- define styles +local styles = { + background = { + color = {'0', '0', '0', '0'}, + alpha = {255, 255, 0, 0}, + border = 140, + blur = 140, + }, + tooltip = { + color = {'FFFFFF', 'FFFFFF', '0', '0'}, + border = 0.5, + blur = 1, + fontsize = 18, + wrap = 2, + }, + button1 = { + color1 = {'FFFFFF', 'FFFFFF', 'FFFFFF', 'FFFFFF'}, + color2 = {'999999', '999999', '999999', '999999'}, + fontsize = 36, + border = 0, + blur = 0, + font = 'material-design-iconic-font', + wrap = 2, + }, + button2 = { + color1 = {'FFFFFF', 'FFFFFF', 'FFFFFF', 'FFFFFF'}, + color2 = {'999999', '999999', '999999', '999999'}, + border = 0, + blur = 0, + fontsize = 24, + font = 'material-design-iconic-font', + wrap = 2, + }, + seekbarFg = { + color1 = {'E39C42', 'E39C42', '0', '0'}, + color2 = {'999999', '999999', '0', '0'}, + border = 0.5, + blur = 1, + }, + seekbarBg = { + color = {'eeeeee', 'eeeeee', '0', '0'}, + border = 0, + blur = 0, + }, + volumeSlider = { + color = {'ffffff', '0', '0', '0'}, + border = 0, + blur = 0, + }, + time = { + color1 = {'ffffff', 'ffffff', '0', '0'}, + color2 = {'eeeeee', 'eeeeee', '0', '0'}, + border = 0, + blur = 0, + fontsize = 17, + }, + title = { + color = {'ffffff', '0', '0', '0'}, + border = 0.5, + blur = 1, + fontsize = 48, + wrap = 2, + }, + winControl = { + color1 = {'ffffff', 'ffffff', '0', '0'}, + color2 = {'eeeeee', 'eeeeee', '0', '0'}, + border = 0.5, + blur = 1, + font = 'mpv-osd-symbols', + fontsize = 20, + }, + } + +-- enviroment updater +-- this element updates shared vairables, sets active areas and starts event generators +local env +env = newElement('env') +env.layer = 1000 +env.visible = false +env.updateTime = function() + dispatchEvent('time') +end +env.init = function(self) + self.slowTimer = mp.add_periodic_timer(0.25, self.updateTime) --use a slower timer to update playtime + -- event generators + mp.observe_property('track-list/count', 'native', + function(name, val) + if val==0 then return end + player.tracks = getTrackList() + player.playlist = getPlaylist() + player.chapters = getChapterList() + player.playlistPos = getPlaylistPos() + player.duration = mp.get_property_number('duration') + showOsc() + dispatchEvent('file-loaded') + end) + mp.observe_property('pause', 'bool', + function(name, val) + player.paused = val + dispatchEvent('pause') + end) + mp.observe_property('fullscreen', 'bool', + function(name, val) + player.fullscreen = val + dispatchEvent('fullscreen') + end) + mp.observe_property('window-maximized', 'bool', + function(name, val) + player.maximized = val + dispatchEvent('window-maximized') + end) + mp.observe_property('current-tracks/audio/id', 'number', + function(name, val) + if val then player.audioTrack = val + else player.audioTrack = 0 + end + dispatchEvent('audio-changed') + end) + mp.observe_property('current-tracks/sub/id', 'number', + function(name, val) + if val then player.subTrack = val + else player.subTrack = 0 + end + dispatchEvent('sub-changed') + end) + mp.observe_property('mute', 'bool', + function(name, val) + player.muted = val + dispatchEvent('mute') + end) + mp.observe_property('volume', 'number', + function(name, val) + player.volume = val + dispatchEvent('volume') + end) + end + +env.tick = function(self) + player.percentPos = mp.get_property_number('percent-pos') + player.timePos = mp.get_property_number('time-pos') + player.timeRem = mp.get_property_number('time-remaining') + return '' + end +env.responder['resize'] = function(self) + player.geo.refX = player.geo.width / 2 + player.geo.refY = player.geo.height - 40 + setPlayActiveArea('bg1', 0, player.geo.height - 120, player.geo.width, player.geo.height) + if player.fullscreen then + setPlayActiveArea('wc1', player.geo.width - 200, 0, player.geo.width, 48) + else + setPlayActiveArea('wc1', -1, -1, -1, -1) + end + return false + end +env.responder['pause'] = function(self) + if player.idle then return end + if player.paused then + setVisibility('always') + else + setVisibility('normal') + end + end +env.responder['idle'] = function(self) + if player.idle then + setVisibility('always') + else + setVisibility('normal') + end + return false + end +env:init() +addToPlayLayout('env') + +-- background +local ne +ne = newElement('background', 'box') +ne.geo.h = 1 +ne.geo.an = 8 +ne.layer = 5 +-- DO NOT directly assign a shared style tabe!! +ne.style = clone(styles.background) +ne.responder['resize'] = function(self) + self.geo.x = player.geo.refX + self.geo.y = player.geo.height + self.geo.w = player.geo.width + self.setPos(self) + self.render(self) + return false + end +ne:init() +addToPlayLayout('background') + +-- a shared tooltip +ne = newElement('tip', 'tooltip') +ne.layer = 20 +ne.style = clone(styles.tooltip) +ne:init() +addToPlayLayout('tip') +local tooltip = ne + +-- playpause button +ne = newElement('btnPlay', 'button') +ne.layer = 10 +ne.style = clone(styles.button1) +ne.geo.w = 45 +ne.geo.h = 45 +ne.geo.an = 5 +ne.responder['resize'] = function(self) + self.geo.x = player.geo.refX + self.geo.y = player.geo.refY + self:setPos() + self:setHitBox() + return false + end +ne.responder['mbtn_left_up'] = function(self, pos) + if self.enabled and self:isInside(pos) then + mp.commandv('cycle', 'pause') + return true + end + return false + end +ne.responder['pause'] = function(self) + if player.paused then + self.text = '\xEF\x8E\xAA' + else + self.text = '\xEF\x8E\xA7' + end + self:render() + return false + end +ne:init() +addToPlayLayout('btnPlay') + + +-- skip back button +ne = newElement('btnBack', 'button') +ne.layer = 10 +ne.style = clone(styles.button2) +ne.geo.w = 30 +ne.geo.h = 24 +ne.geo.an = 5 +ne.text = '\xEF\x8E\xA0' +ne.responder['resize'] = function(self) + self.geo.x = player.geo.refX - 60 + self.geo.y = player.geo.refY + self:setPos() + self:setHitBox() + return false + end +ne.responder['mbtn_left_up'] = function(self, pos) + if self.enabled and self:isInside(pos) then + mp.commandv('seek', -5, 'relative', 'keyframes') + return true + end + return false + end +ne:init() +addToPlayLayout('btnBack') + + +-- skip forward button +ne = newElement('btnForward', 'btnBack') +ne.text = '\xEF\x8E\x9F' +ne.responder['mbtn_left_up'] = function(self, pos) + if self.enabled and self:isInside(pos) then + mp.commandv('seek', 5, 'relative', 'keyframes') + return true + end + return false + end +ne.responder['resize'] = function(self) + self.geo.x = player.geo.refX + 60 + self.geo.y = player.geo.refY + self:setPos() + self:setHitBox() + return false + end +ne:init() +addToPlayLayout('btnForward') + + +-- play previous file button +ne = newElement('btnPrev', 'button') +ne.layer = 10 +ne.style = clone(styles.button2) +ne.geo.w = 30 +ne.geo.h = 24 +ne.geo.an = 5 +ne.text = '\xEF\x8E\xB5' +ne.responder['mbtn_left_up'] = function(self, pos) + if self.enabled and self:isInside(pos) then + mp.commandv('playlist-prev', 'weak') + return true + end + return false + end +ne.responder['resize'] = function(self) + self.geo.x = player.geo.refX - 120 + self.geo.y = player.geo.refY + self:setPos() + self:setHitBox() + return false + end +ne.responder['file-loaded'] = function(self) + if player.playlistPos <= 1 and player.loopPlaylist == 'no' then + self:disable() + else + self:enable() + end + return false + end +ne:init() +addToPlayLayout('btnPrev') + + +-- play next file button +ne = newElement('btnNext', 'btnPrev') +ne.text = '\xEF\x8E\xB4' +ne.responder['mbtn_left_up'] = function(self, pos) + if self.enabled and self:isInside(pos) then + mp.commandv('playlist-next', 'weak') + return true + end + return false + end +ne.responder['resize'] = function(self) + self.geo.x = player.geo.refX + 120 + self.geo.y = player.geo.refY + self:setPos() + self:setHitBox() + return false + end +ne.responder['file-loaded'] = function(self) + if player.playlistPos >= #player.playlist + and player.loopPlaylist == 'no' then + self:disable() + else + self:enable() + end + return false + end +ne:init() +addToPlayLayout('btnNext') + +-- cycle audio button +ne = newElement('cycleAudio', 'button') +ne.layer = 10 +ne.style = clone(styles.button2) +ne.geo.w = 30 +ne.geo.h = 24 +ne.geo.an = 5 +ne.text = '\xEF\x8E\xB7' +ne.tipText = '' +ne.responder['resize'] = function(self) + self.geo.x = 37 + self.geo.y = player.geo.refY + self.visible = player.geo.width >= 540 + self:setPos() + self:setHitBox() + return false + end +ne.responder['mouse_move'] = function(self, pos) + if self.enabled and self:isInside(pos) then + tooltip:show(self.tipText, {self.geo.x, self.geo.y+30}, self) + return true + else + tooltip:hide(self) + return false + end + end +ne.responder['file-loaded'] = function(self) + if #player.tracks.audio > 0 then + self:enable() + else + self:disable() + end + end +ne.responder['audio-changed'] = function(self) + if player.tracks then + local lang + if player.audioTrack == 0 then + lang = 'OFF' + else + lang = player.tracks.audio[player.audioTrack].lang + end + if not lang then lang = 'unknown' end + self.tipText = string.format('[%s/%s][%s]', + player.audioTrack, #player.tracks.audio, lang) + tooltip:update(self.tipText, self) + end + return false + end +ne.responder['mbtn_left_up'] = function(self, pos) + if self.enabled and self:isInside(pos) then + cycleTrack('audio') + return true + end + return false + end +ne.responder['mbtn_right_up'] = function(self, pos) + if self.enabled and self:isInside(pos) then + cycleTrack('audio', 'prev') + return true + end + return false + end +ne:init() +addToPlayLayout('cycleAudio') + + +-- cycle sub button +ne = newElement('cycleSub', 'cycleAudio') +ne.text = '\xEF\x8F\x93' +ne.responder['resize'] = function(self) + self.geo.x = 87 + self.geo.y = player.geo.refY + self.visible = player.geo.width >= 600 + self:setPos() + self:setHitBox() + return false + end +ne.responder['file-loaded'] = function(self) + if #player.tracks.sub > 0 then + self:enable() + else + self:disable() + end + end +ne.responder['audio-changed'] = nil +ne.responder['sub-changed'] = function(self) + if player.tracks then + local title + if player.subTrack == 0 then + title = 'OFF' + else + title = player.tracks.sub[player.subTrack].title + end + if not title then title = 'unknown' end + self.tipText = string.format('[%s/%s][%s]', + player.subTrack, #player.tracks.sub, title) + tooltip:update(self.tipText, self) + end + return false + end +ne.responder['mbtn_left_up'] = function(self, pos) + if self.enabled and self:isInside(pos) then + cycleTrack('sub') + return true + end + return false + end +ne.responder['mbtn_right_up'] = function(self, pos) + if self.enabled and self:isInside(pos) then + cycleTrack('sub', 'prev') + return true + end + return false + end +ne:init() +addToPlayLayout('cycleSub') + +-- toggle mute +ne = newElement('togMute', 'button') +ne.layer = 10 +ne.style = clone(styles.button2) +ne.geo.x = 137 +ne.geo.w = 30 +ne.geo.h = 24 +ne.geo.an = 5 +ne.responder['resize'] = function(self) + self.geo.y = player.geo.refY + self.visible = player.geo.width >= 700 + self:setPos() + self:setHitBox() + return false + end +ne.responder['mbtn_left_up'] = function(self, pos) + if self.enabled and self:isInside(pos) then + mp.commandv('cycle', 'mute') + return true + end + return false + end +ne.responder['mute'] = function(self) + if player.muted then + self.text = '\xEF\x8E\xBB' + else + self.text = '\xEF\x8E\xBC' + end + self:render() + return false + end +ne:init() +addToPlayLayout('togMute', 'button') + +-- volume slider +-- background +ne = newElement('volumeSliderBg', 'box') +ne.layer = 9 +ne.style = clone(styles.volumeSlider) +ne.geo.r = 0 +ne.geo.h = 1 +ne.geo.an = 4 +ne.responder['resize'] = function(self) + self.visible = player.geo.width > 740 + self.geo.x = 156 + self.geo.y = player.geo.refY + self.geo.w = 80 + self:init() + end +ne:init() +addToPlayLayout('volumeSliderBg') + +-- seekbar +ne = newElement('volumeSlider', 'slider') +ne.layer = 10 +ne.style = clone(styles.volumeSlider) +ne.geo.an = 4 +ne.geo.h = 14 +ne.barHeight = 2 +ne.barRadius = 0 +ne.nobRadius = 4 +ne.allowDrag = false +ne.lastSeek = nil +ne.responder['resize'] = function(self) + self.visible = player.geo.width > 740 + self.geo.an = 4 + self.geo.x = 152 + self.geo.y = player.geo.refY + self.geo.w = 88 + self:setParam() -- setParam may change geo settings + self:setPos() + self:render() + end +ne.responder['volume'] = function(self) + local val = player.volume + if val then + if val > 140 then val = 140 + elseif val < 0 then val = 0 end + self.value = val/1.4 + self.xValue = val/140 * self.xLength + self:render() + end + return false + end +ne.responder['idle'] = ne.responder['volume'] +ne.responder['mouse_move'] = function(self, pos) + if not self.enabled then return false end + local vol = self:getValueAt(pos) + if self.allowDrag then + if vol then + mp.commandv('set', 'volume', vol*1.4) + env.updateTime() + end + end + if self:isInside(pos) then + local tipText + if vol then + tipText = string.format('%d', vol*1.4) + else + tipText = 'N/A' + end + tooltip:show(tipText, {pos[1], self.geo.y}, self) + return true + else + tooltip:hide(self) + return false + end + end +ne.responder['mbtn_left_down'] = function(self, pos) + if not self.enabled then return false end + if self:isInside(pos) then + self.allowDrag = true + local vol = self:getValueAt(pos) + if vol then + mp.commandv('set', 'volume', vol*1.4) + return true + end + end + return false + end +ne.responder['mbtn_left_up'] = function(self, pos) + self.allowDrag = false + self.lastSeek = nil + end +ne:init() +addToPlayLayout('volumeSlider') + + +-- toggle info +ne = newElement('togInfo', 'button') +ne.layer = 10 +ne.style = clone(styles.button2) +ne.geo.w = 30 +ne.geo.h = 24 +ne.geo.an = 5 +ne.text = '\xEF\x87\xB7' +ne.responder['resize'] = function(self) + self.geo.x = player.geo.width - 87 + self.geo.y = player.geo.refY + self.visible = player.geo.width >= 640 + self:setPos() + self:setHitBox() + return false + end +ne.responder['mbtn_left_up'] = function(self, pos) + if self.enabled and self:isInside(pos) then + mp.commandv('script-binding', 'stats/display-stats-toggle') + return true + end + return false + end +ne:init() +addToPlayLayout('togInfo') + + +-- toggle fullscreen +ne = newElement('togFs', 'togInfo') +ne.text = '\xEF\x85\xAD' +ne.responder['resize'] = function(self) + self.geo.x = player.geo.width - 37 + self.geo.y = player.geo.refY + self.visible = player.geo.width >= 600 + if (player.fullscreen) then + self.text = '\xEF\x85\xAC' + else + self.text = '\xEF\x85\xAD' + end + self:render() + self:setPos() + self:setHitBox() + return false + end +ne.responder['mbtn_left_up'] = function(self, pos) + if self.enabled and self:isInside(pos) then + mp.commandv('cycle', 'fullscreen') + return true + end + return false + end +ne:init() +addToPlayLayout('togFs') + +-- seekbar background +ne = newElement('seekbarBg', 'box') +ne.layer = 9 +ne.style = clone(styles.seekbarBg) +ne.geo.r = 0 +ne.geo.h = 2 +ne.geo.an = 5 +ne.responder['resize'] = function(self) + self.geo.x = player.geo.refX + self.geo.y = player.geo.refY - 56 + self.geo.w = player.geo.width - 50 + self:init() + end +ne:init() +addToPlayLayout('seekbarBg') + +-- seekbar +ne = newElement('seekbar', 'slider') +ne.layer = 10 +ne.style = clone(styles.seekbarFg) +ne.geo.an = 5 +ne.geo.h = 20 +ne.barHeight = 2 +ne.barRadius = 0 +ne.nobRadius = 8 +ne.allowDrag = false +ne.lastSeek = nil +ne.responder['resize'] = function(self) + self.geo.an = 5 + self.geo.x = player.geo.refX + self.geo.y = player.geo.refY - 56 + self.geo.w = player.geo.width - 34 + self:setParam() -- setParam may change geo settings + self:setPos() + self:render() + end +ne.responder['time'] = function(self) + local val = player.percentPos + if val and not self.enabled then + self:enable() + elseif not val and self.enabled then + tooltip:hide(self) + self:disable() + end + if val then + self.value = val + self.xValue = val/100 * self.xLength + self:render() + end + return false + end +ne.responder['mouse_move'] = function(self, pos) + if not self.enabled then return false end + if self.allowDrag then + local seekTo = self:getValueAt(pos) + if seekTo then + mp.commandv('seek', seekTo, 'absolute-percent') + env.updateTime() + end + end + if self:isInside(pos) then + local tipText + if player.duration then + local seconds = self:getValueAt(pos)/100 * player.duration + if #player.chapters > 0 then + local ch = #player.chapters + for i, v in ipairs(player.chapters) do + if seconds < v.time then + ch = i - 1 + break + end + end + if ch == 0 then + tipText = string.format('[0/%d][unknown]\\N%s', + #player.chapters, mp.format_time(seconds)) + else + local title = player.chapters[ch].title + if not title then title = 'unknown' end + tipText = string.format('[%d/%d][%s]\\N%s', + ch, #player.chapters, title, + mp.format_time(seconds)) + end + else + tipText = mp.format_time(seconds) + end + else + tipText = '--:--:--' + end + tooltip:show(tipText, {pos[1], self.geo.y}, self) + return true + else + tooltip:hide(self) + return false + end + end +ne.responder['mbtn_left_down'] = function(self, pos) + if not self.enabled then return false end + if self:isInside(pos) then + self.allowDrag = true + local seekTo = self:getValueAt(pos) + if seekTo then + mp.commandv('seek', seekTo, 'absolute-percent') + env.updateTime() + return true + end + end + return false + end +ne.responder['mbtn_left_up'] = function(self, pos) + self.allowDrag = false + self.lastSeek = nil + end +ne.responder['file-loaded'] = function(self) + -- update chapter markers + env.updateTime() + self.markers = {} + if player.duration then + for i, v in ipairs(player.chapters) do + self.markers[i] = (v.time*100 / player.duration) + end + self:render() + end + return false + end +ne:init() +addToPlayLayout('seekbar') + +-- time display +ne = newElement('time1', 'button') +ne.layer = 10 +ne.style = clone(styles.time) +ne.geo.w = 64 +ne.geo.h = 20 +ne.geo.an = 7 +ne.enabled = true +ne.responder['resize'] = function(self) + self.geo.x = 25 + self.geo.y = player.geo.refY - 44 + self:setPos() + end +ne.responder['time'] = function(self) + if player.timePos then + self.pack[4] = mp.format_time(player.timePos) + else + self.pack[4] = '--:--:--' + end + end +ne:init() +addToPlayLayout('time1') + +-- time duration +ne = newElement('time2', 'time1') +ne.geo.an = 9 +ne.isDuration = true +ne.responder['resize'] = function(self) + self.geo.x = player.geo.width - 25 + self.geo.y = player.geo.refY - 44 + self:setPos() + self:setHitBox() + end +ne.responder['time'] = function(self) + if self.isDuration then + val = player.duration + else + val = -player.timeRem + end + if val then + self.pack[4] = mp.format_time(val) + else + self.pack[4] = '--:--:--' + end + end +ne.responder['mbtn_left_up'] = function(self, pos) + if self:isInside(pos) then + self.isDuration = not self.isDuration + return true + end + return false + end +ne:init() +addToPlayLayout('time2') + +-- title +ne = newElement('title') +ne.layer = 10 +ne.style = clone(styles.title) +ne.geo.x = 20 +ne.geo.an = 1 +ne.visible = false +ne.title = '' +ne.render = function(self) + local maxchars = player.geo.width / 23 + local text = self.title + -- 估计1个中文字符约等于1.5个英文字符 + local charcount = (text:len() + select(2, text:gsub('[^\128-\193]', ''))*2) / 3 + if not (maxchars == nil) and (charcount > maxchars) then + local limit = math.max(0, maxchars - 3) + if (charcount > limit) then + while (charcount > limit) do + text = text:gsub('.[\128-\191]*$', '') + charcount = (text:len() + select(2, text:gsub('[^\128-\193]', ''))*2) / 3 + end + text = text .. '...' + end + end + self.pack[4] = text + end +ne.tick = function(self) + if not self.visible then return '' end + if self.trans >= 0.9 then + self.visible = false + end + return table.concat(self.pack) + end +ne.responder['resize'] = function(self) + self.geo.y = player.geo.refY - 92 + self:setPos() + self:render() + self.visible = self.visible and (player.geo.height >= 320) + end +ne.responder['pause'] = function(self) + self.visible = (self.visible or player.paused) and (player.geo.height >= 320) + end +ne.responder['file-loaded'] = function(self) + local title = mp.command_native({'expand-text', '${media-title}'}) + title = title:gsub('\\n', ' '):gsub('\\$', ''):gsub('{','\\{') + self.title = title + self:render() + self.visible = true + end +ne.responder['idle'] = function(self) + self.visible = not player.idle + return false + end +ne:init() +addToPlayLayout('title') + +-- window controllers +ne = newElement('winClose', 'button') +ne.layer = 10 +ne.style = clone(styles.winControl) +ne.geo.y = 16 +ne.geo.w = 40 +ne.geo.h = 32 +ne.geo.an = 5 +ne.text = '\238\132\149' +ne.responder['resize'] = function(self) + self.geo.x = player.geo.width - 20 + self:init() + end +ne.responder['mbtn_left_up'] = function(self, pos) + if self.visible and self:isInside(pos) then + mp.commandv('quit') + return true + end + return false + end +ne.responder['fullscreen'] = function(self) + self.visible = player.fullscreen + end +ne:init() +addToPlayLayout('winClose') + + +ne = newElement('winMax', 'winClose') +ne.text = '' +ne.responder['resize'] = function(self) + self.geo.x = player.geo.width - 60 + if player.maximized or player.fullscreen then + self.text = '\238\132\148' + else + self.text = '\238\132\147' + end + self:init() + end +ne.responder['mbtn_left_up'] = function(self, pos) + if self.visible and self:isInside(pos) then + if player.fullscreen then + mp.commandv('cycle', 'fullscreen') + else + mp.commandv('cycle', 'window-maximized') + end + return true + end + return false + end +ne:init() +addToPlayLayout('winMax') + +ne = newElement('winMin', 'winClose') +ne.text = '\238\132\146' +ne.responder['resize'] = function(self) + self.geo.x = player.geo.width - 100 + self:init() + end +ne.responder['mbtn_left_up'] = function(self, pos) + if self.visible and self:isInside(pos) then + mp.commandv('cycle', 'window-minimized') + return true + end + return false + end +ne:init() +addToPlayLayout('winMin')