diff --git a/home-config/home-configuration.scm b/home-config/home-configuration.scm index a3454cd..35390ae 100644 --- a/home-config/home-configuration.scm +++ b/home-config/home-configuration.scm @@ -150,6 +150,7 @@ ("nvim/after/ftplugin/mail/custom.vim" ,(local-file "nvim/config/after/ftplugin/mail/custom.vim")) ("sway" ,(local-file "sway" #:recursive? #t)) ("hypr" ,(local-file "hypr" #:recursive? #t)) + ("mpv" ,(local-file "mpv" #:recursive? #t)) ("foot" ,(local-file "foot" #:recursive? #t)) ("pulse/client.conf" ,(local-file "pulseaudio/client.conf")) ("waybar" ,(local-file "waybar" #:recursive? #t)) diff --git a/home-config/mpv/scripts/easycrop.lua b/home-config/mpv/scripts/easycrop.lua new file mode 100644 index 0000000..415fce5 --- /dev/null +++ b/home-config/mpv/scripts/easycrop.lua @@ -0,0 +1,254 @@ +local msg = require('mp.msg') +local assdraw = require('mp.assdraw') + +local script_name = "easycrop" + +-- Number of crop points currently chosen (0 to 2) +local points = {} +-- True if in cropping selection mode +local cropping = false +-- Original value of osc property +local osc_prop = false + +-- Helper that converts two points to top-left and bottom-right +local swizzle_points = function (p1, p2) + if p1.x > p2.x then p1.x, p2.x = p2.x, p1.x end + if p1.y > p2.y then p1.y, p2.y = p2.y, p1.y end +end + +local clamp = function (val, min, max) + assert(min <= max) + if val < min then return min end + if val > max then return max end + return val +end + +local video_space_from_screen_space = function (ssp) + -- Video native dimensions and screen size + local vid_w = mp.get_property("width") + local vid_h = mp.get_property("height") + local osd_w = mp.get_property("osd-width") + local osd_h = mp.get_property("osd-height") + + -- Factor by which the video is scaled to fit the screen + local scale = math.min(osd_w/vid_w, osd_h/vid_h) + + -- Size video takes up in screen + local vid_sw, vid_sh = scale*vid_w, scale*vid_h + + -- Video offset within screen + local off_x = math.floor((osd_w - vid_sw)/2) + local off_y = math.floor((osd_h - vid_sh)/2) + + local vsp = {} + + -- Move the point to within the video + vsp.x = clamp(ssp.x, off_x, off_x + vid_sw) + vsp.y = clamp(ssp.y, off_y, off_y + vid_sh) + + -- Convert screen-space to video-space + vsp.x = math.floor((vsp.x - off_x) / scale) + vsp.y = math.floor((vsp.y - off_y) / scale) + + return vsp +end + +local screen_space_from_video_space = function (vsp) + -- Video native dimensions and screen size + local vid_w = mp.get_property("width") + local vid_h = mp.get_property("height") + local osd_w = mp.get_property("osd-width") + local osd_h = mp.get_property("osd-height") + + -- Factor by which the video is scaled to fit the screen + local scale = math.min(osd_w/vid_w, osd_h/vid_h) + + -- Size video takes up in screen + local vid_sw, vid_sh = scale*vid_w, scale*vid_h + + -- Video offset within screen + local off_x = math.floor((osd_w - vid_sw)/2) + local off_y = math.floor((osd_h - vid_sh)/2) + + local ssp = {} + ssp.x = vsp.x * scale + off_x + ssp.y = vsp.y * scale + off_y + return ssp +end + +-- Wrapper that converts RRGGBB / RRGGBBAA to ASS format +local ass_set_color = function (idx, color) + assert(color:len() == 8 or color:len() == 6) + local ass = "" + + -- Set alpha value (if present) + if color:len() == 8 then + local alpha = 0xff - tonumber(color:sub(7, 8), 16) + ass = ass .. string.format("\\%da&H%X&", idx, alpha) + end + + -- Swizzle RGB to BGR and build ASS string + color = color:sub(5, 6) .. color:sub(3, 4) .. color:sub(1, 2) + return "{" .. ass .. string.format("\\%dc&H%s&", idx, color) .. "}" +end + +local draw_rect = function (p1, p2) + local osd_w, osd_h = mp.get_property("osd-width"), mp.get_property("osd-height") + + ass = assdraw.ass_new() + + -- Draw overlay over surrounding unselected region + + ass:draw_start() + ass:pos(0, 0) + + ass:append(ass_set_color(1, "000000aa")) + ass:append(ass_set_color(3, "00000000")) + + local l = math.min(p1.x, p2.x) + local r = math.max(p1.x, p2.x) + local u = math.min(p1.y, p2.y) + local d = math.max(p1.y, p2.y) + + ass:rect_cw(0, 0, l, osd_h) + ass:rect_cw(r, 0, osd_w, osd_h) + ass:rect_cw(l, 0, r, u) + ass:rect_cw(l, d, r, osd_h) + + ass:draw_stop() + + -- Draw border around selected region + + ass:new_event() + ass:draw_start() + ass:pos(0, 0) + + ass:append(ass_set_color(1, "00000000")) + ass:append(ass_set_color(3, "000000ff")) + ass:append("{\\bord2}") + + ass:rect_cw(p1.x, p1.y, p2.x, p2.y) + + ass:draw_stop() + + mp.set_osd_ass(osd_w, osd_h, ass.text) +end + +local draw_fill = function () + local osd_w, osd_h = mp.get_property("osd-width"), mp.get_property("osd-height") + + ass = assdraw.ass_new() + ass:draw_start() + ass:pos(0, 0) + + ass:append(ass_set_color(1, "000000aa")) + ass:append(ass_set_color(3, "00000000")) + ass:rect_cw(0, 0, osd_w, osd_h) + + ass:draw_stop() + mp.set_osd_ass(osd_w, osd_h, ass.text) +end + +local draw_clear = function () + local osd_w, osd_h = mp.get_property("osd-width"), mp.get_property("osd-height") + mp.set_osd_ass(osd_w, osd_h, "") +end + +local draw_cropper = function () + if #points == 1 then + local p1 = screen_space_from_video_space(points[1]) + local p2 = {} + p2.x, p2.y = mp.get_mouse_pos() + draw_rect(p1, p2) + end +end + +local uncrop = function () + mp.command("no-osd vf del @" .. script_name .. ":crop") +end + +local crop = function(p1, p2) + swizzle_points(p1, p2) + + local w = p2.x - p1.x + local h = p2.y - p1.y + local ok, err = mp.command(string.format( + "no-osd vf add @%s:crop=%s:%s:%s:%s", script_name, w, h, p1.x, p1.y)) + + if not ok then + mp.osd_message("Cropping failed") + points = {} + end +end + +local easycrop_stop = function () + mp.set_property("osc", osc_prop) + cropping = false + mp.remove_key_binding("easycrop_mouse_btn0") + draw_clear() +end + +local mouse_btn0_cb = function () + if not cropping then + return + end + + local mx, my = mp.get_mouse_pos() + table.insert(points, video_space_from_screen_space({ x = mx, y = my })) + + if #points == 2 then + crop(points[1], points[2]) + easycrop_stop() + end +end + +local easycrop_start = function () + -- Cropping requires swdec or hwdec with copy-back + local hwdec = mp.get_property("hwdec-current") + if hwdec == nil then + return mp.msg.error("Cannot determine current hardware decoder mode") + end + -- Check whitelist of ok values + local valid_hwdec = { + ["no"] = true, -- software decoding + -- Taken from mpv manual + ["videotoolbox-co"] = true, + ["vaapi-copy"] = true, + ["dxva2-copy"] = true, + ["d3d11va-copy"] = true, + ["mediacodec"] = true + } + if not valid_hwdec[hwdec] then + return mp.osd_message("Cropping requires swdec or hwdec with copy-back (see mpv manual)") + end + + -- Just clear the current crop and return, if there is one + if #points ~= 0 then + uncrop() + points = {} + return + end + + -- Hide OSC + osc_prop = mp.get_property("osc") + mp.set_property("osc", "no") + + cropping = true + mp.add_forced_key_binding("mouse_btn0", "easycrop_mouse_btn0", mouse_btn0_cb) + draw_fill() +end + +local easycrop_activate = function () + if cropping then + easycrop_stop() + else + easycrop_start() + end +end + +mp.add_key_binding("mouse_move", draw_cropper) +mp.observe_property("osd-width", "native", draw_cropper) +mp.observe_property("osd-height", "native", draw_cropper) + +mp.add_key_binding("c", "easy_crop", easycrop_activate) + diff --git a/modules/ryan-config/base-system.scm b/modules/ryan-config/base-system.scm index 5034f4e..ac1ac79 100644 --- a/modules/ryan-config/base-system.scm +++ b/modules/ryan-config/base-system.scm @@ -159,7 +159,7 @@ "wireplumber" "wireshark" "zsh")) - (list my-ca-certs swaylock-effects-new xdg-desktop-portal-hyprland-ryan virt-manager-ovmf bluez-ryan blueman-ryan swayidle-new waybar-new) + (list my-ca-certs swaylock-effects-new xdg-desktop-portal-hyprland-ryan virt-manager-ovmf bluez-ryan blueman-ryan swayidle-new waybar-new hyprlock) %my-base-packages )) ;; Below is the list of system services. To search for available diff --git a/modules/ryan-packages/wm.scm b/modules/ryan-packages/wm.scm index 474596a..ada3adf 100644 --- a/modules/ryan-packages/wm.scm +++ b/modules/ryan-packages/wm.scm @@ -93,38 +93,42 @@ #~(list "-Dspectre=disabled"))) (outputs '("out")))) - (define-public hyprlock - (package - (name "hyprlock") - (version "0.3.0") - (source - (origin - (method git-fetch) - (uri (git-reference - (url "https://github.com/hyprwm/hyprlock") - (commit (string-append "v" version)))) - (file-name (git-file-name name version)) - (sha256 - (base32 "0w2a25hivn8xd8p05vc9xg57rd9siv12dwmr4skpqx4dcmxxbg5d")))) - (build-system cmake-build-system) - (native-inputs - (list gcc-13 - pkg-config - mesa-headers - wayland)) - (inputs - (list cairo - pango - libxkbcommon - libdrm - hyprlang - mesa - wayland-protocols - linux-pam)) - (home-page "https://github.com/hyprwm/hyprlock") - (license license:bsd-3) - (synopsis "Screen locker for Hyprland") - (description "Screen locker for hyprland."))) + (let ((commit "d9a6229434fba475ea42b634ee2f03919236798d") + (revision "1")) + (package + (name "hyprlock") + (version (git-version "0.3.0" revision commit)) + (source + (origin + (method git-fetch) + (uri (git-reference + (url "https://github.com/hyprwm/hyprlock") + (commit commit))) + (file-name (git-file-name name version)) + (sha256 + (base32 "19cg8vj4sgz5pxib9m08af1lilay9bckjhlr6h333s014l7y09sw")))) + (build-system cmake-build-system) + (arguments + `(#:tests? #f)) + (native-inputs + (list gcc-13 + pkg-config + mesa-headers + wayland)) + (inputs + (list cairo + pango + libxkbcommon + hyprlang + mesa + libdrm + libglvnd + wayland-protocols + linux-pam)) + (home-page "https://github.com/hyprwm/hyprlock") + (license license:bsd-3) + (synopsis "Screen locker for Hyprland") + (description "Screen locker for hyprland.")))) hyprlock