mirror of
https://github.com/zhenyan121/dotfiles.git
synced 2026-04-10 06:24:08 +08:00
feat: 上传我的配置
This commit is contained in:
105
.config/waybar/colors.css
Executable file
105
.config/waybar/colors.css
Executable file
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Css Colors
|
||||
* Generated with Matugen
|
||||
*/
|
||||
|
||||
@define-color background #15130b;
|
||||
|
||||
@define-color error #ffb4ab;
|
||||
|
||||
@define-color error_container #93000a;
|
||||
|
||||
@define-color inverse_on_surface #333027;
|
||||
|
||||
@define-color inverse_primary #6e5e0e;
|
||||
|
||||
@define-color inverse_surface #e9e2d4;
|
||||
|
||||
@define-color on_background #e9e2d4;
|
||||
|
||||
@define-color on_error #690005;
|
||||
|
||||
@define-color on_error_container #ffdad6;
|
||||
|
||||
@define-color on_primary #3a3000;
|
||||
|
||||
@define-color on_primary_container #f9e287;
|
||||
|
||||
@define-color on_primary_fixed #221b00;
|
||||
|
||||
@define-color on_primary_fixed_variant #534600;
|
||||
|
||||
@define-color on_secondary #373016;
|
||||
|
||||
@define-color on_secondary_container #eee2bc;
|
||||
|
||||
@define-color on_secondary_fixed #211b04;
|
||||
|
||||
@define-color on_secondary_fixed_variant #4e472a;
|
||||
|
||||
@define-color on_surface #e9e2d4;
|
||||
|
||||
@define-color on_surface_variant #cdc6b4;
|
||||
|
||||
@define-color on_tertiary #153722;
|
||||
|
||||
@define-color on_tertiary_container #c5ecce;
|
||||
|
||||
@define-color on_tertiary_fixed #00210f;
|
||||
|
||||
@define-color on_tertiary_fixed_variant #2c4e38;
|
||||
|
||||
@define-color outline #969080;
|
||||
|
||||
@define-color outline_variant #4b4739;
|
||||
|
||||
@define-color primary #dcc66e;
|
||||
|
||||
@define-color primary_container #534600;
|
||||
|
||||
@define-color primary_fixed #f9e287;
|
||||
|
||||
@define-color primary_fixed_dim #dcc66e;
|
||||
|
||||
@define-color scrim #000000;
|
||||
|
||||
@define-color secondary #d1c6a1;
|
||||
|
||||
@define-color secondary_container #4e472a;
|
||||
|
||||
@define-color secondary_fixed #eee2bc;
|
||||
|
||||
@define-color secondary_fixed_dim #d1c6a1;
|
||||
|
||||
@define-color shadow #000000;
|
||||
|
||||
@define-color source_color #dabc2c;
|
||||
|
||||
@define-color surface #15130b;
|
||||
|
||||
@define-color surface_bright #3c3930;
|
||||
|
||||
@define-color surface_container #221f17;
|
||||
|
||||
@define-color surface_container_high #2d2a21;
|
||||
|
||||
@define-color surface_container_highest #38352b;
|
||||
|
||||
@define-color surface_container_low #1e1b13;
|
||||
|
||||
@define-color surface_container_lowest #100e07;
|
||||
|
||||
@define-color surface_dim #15130b;
|
||||
|
||||
@define-color surface_tint #dcc66e;
|
||||
|
||||
@define-color surface_variant #4b4739;
|
||||
|
||||
@define-color tertiary #aad0b3;
|
||||
|
||||
@define-color tertiary_container #2c4e38;
|
||||
|
||||
@define-color tertiary_fixed #c5ecce;
|
||||
|
||||
@define-color tertiary_fixed_dim #aad0b3;
|
||||
|
||||
65
.config/waybar/config.jsonc
Executable file
65
.config/waybar/config.jsonc
Executable file
@@ -0,0 +1,65 @@
|
||||
{
|
||||
//这个waybar因为大改过一次布局,所以箭头的位置和名字已经完全对不上了,从左到右看吧。
|
||||
"include": [
|
||||
"modules.jsonc",
|
||||
"modules-dividers.jsonc"
|
||||
],
|
||||
//这一行layer top设置会让waybar显示在最上方
|
||||
"layer": "top",
|
||||
//"output": "DP-2",
|
||||
"position": "top",
|
||||
"fixed-center": true,
|
||||
// "height": 30,
|
||||
"reload_style_on_change": true,
|
||||
"modules-left": [
|
||||
// "ext/workspaces",
|
||||
"niri/workspaces",
|
||||
"custom/right_div#5",
|
||||
"cffi/niri-taskbar",
|
||||
"niri/window",
|
||||
//"dwl/window",
|
||||
// "hyprland/window",
|
||||
"custom/right_div#6"
|
||||
],
|
||||
"modules-center": [
|
||||
"custom/left_div#3",
|
||||
"bluetooth",
|
||||
"network",
|
||||
"custom/wfrec",
|
||||
// "custom/screenshot",
|
||||
"custom/left_div#2",
|
||||
// "idle_inhibitor",
|
||||
"custom/colorpicker",
|
||||
"power-profiles-daemon",
|
||||
"custom/left_div#11",
|
||||
"custom/left_div#1",
|
||||
"custom/applauncher",
|
||||
"custom/right_div#1",
|
||||
"custom/right_div#11",
|
||||
"clock",
|
||||
"custom/right_div#2",
|
||||
//"clock#date",
|
||||
"custom/cava",
|
||||
// "mpris",
|
||||
"custom/right_div#3"
|
||||
//"custom/right_div#4",
|
||||
],
|
||||
"modules-right": [
|
||||
// "group/backlight",
|
||||
"custom/left_div#7",
|
||||
"custom/updates",
|
||||
"tray",
|
||||
// "custom/mako",
|
||||
"custom/left_div#4",
|
||||
//"backlight/slider",
|
||||
//"backlight",
|
||||
// "group/ddcutil",
|
||||
"group/screenlight",
|
||||
"privacy",
|
||||
"group/audio",
|
||||
"custom/left_div#8",
|
||||
"battery",
|
||||
"custom/left_div#5",
|
||||
"group/powermenu"
|
||||
]
|
||||
}
|
||||
BIN
.config/waybar/logo/bluetooth.png
Executable file
BIN
.config/waybar/logo/bluetooth.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 40 KiB |
88
.config/waybar/modules-dividers.jsonc
Executable file
88
.config/waybar/modules-dividers.jsonc
Executable file
@@ -0,0 +1,88 @@
|
||||
{
|
||||
/*-------------------
|
||||
left dividers
|
||||
-------------------*/
|
||||
"custom/left_div#1": {
|
||||
"format": "",
|
||||
"tooltip": false
|
||||
},
|
||||
"custom/left_div#11": {
|
||||
"format": "",
|
||||
"tooltip": false
|
||||
},
|
||||
"custom/left_div#2": {
|
||||
"format": "",
|
||||
"tooltip": false
|
||||
},
|
||||
"custom/left_div#3": {
|
||||
"format": "",
|
||||
"tooltip": false
|
||||
},
|
||||
"custom/left_div#4": {
|
||||
"format": "",
|
||||
"tooltip": false
|
||||
},
|
||||
"custom/left_div#5": {
|
||||
"format": "",
|
||||
"tooltip": false
|
||||
},
|
||||
"custom/left_div#6": {
|
||||
"format": "",
|
||||
"tooltip": false
|
||||
},
|
||||
"custom/left_div#7": {
|
||||
"format": "",
|
||||
"tooltip": false
|
||||
},
|
||||
"custom/left_div#8": {
|
||||
"format": "",
|
||||
"tooltip": false
|
||||
},
|
||||
"custom/left_div#9": {
|
||||
"format": "",
|
||||
"tooltip": false
|
||||
},
|
||||
"custom/left_inv#1": {
|
||||
"format": "",
|
||||
"tooltip": false
|
||||
},
|
||||
"custom/left_inv#2": {
|
||||
"format": "",
|
||||
"tooltip": false
|
||||
},
|
||||
/*--------------------
|
||||
right dividers
|
||||
--------------------*/
|
||||
"custom/right_div#1": {
|
||||
"format": "",
|
||||
"tooltip": false
|
||||
},
|
||||
"custom/right_div#11": {
|
||||
"format": "",
|
||||
"tooltip": false
|
||||
},
|
||||
"custom/right_div#2": {
|
||||
"format": "",
|
||||
"tooltip": false
|
||||
},
|
||||
"custom/right_div#3": {
|
||||
"format": "",
|
||||
"tooltip": false
|
||||
},
|
||||
"custom/right_div#4": {
|
||||
"format": "",
|
||||
"tooltip": false
|
||||
},
|
||||
"custom/right_div#5": {
|
||||
"format": "",
|
||||
"tooltip": false
|
||||
},
|
||||
"custom/right_div#6": {
|
||||
"format": "",
|
||||
"tooltip": false
|
||||
},
|
||||
"custom/right_inv#1": {
|
||||
"format": "",
|
||||
"tooltip": false
|
||||
}
|
||||
}
|
||||
422
.config/waybar/modules.jsonc
Executable file
422
.config/waybar/modules.jsonc
Executable file
@@ -0,0 +1,422 @@
|
||||
{
|
||||
//电池电量
|
||||
"battery": {
|
||||
"interval": 60,
|
||||
"states": {
|
||||
"critical": 20,
|
||||
},
|
||||
"format": "{icon} {capacity}%",
|
||||
"format-charging": " {capacity}% ",
|
||||
"format-icons": [
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
""
|
||||
],
|
||||
"tooltip-format": "剩余电量:{capacity}%\n充 放 电 :{power}\n可用时间:{timeTo}\n---\n电池健康度:{health}%",
|
||||
},
|
||||
//时钟
|
||||
"clock": {
|
||||
"interval": 60,
|
||||
"format": " {:%H:%M}",
|
||||
"tooltip-format": "{:%Y/%m/%d %A}\n---\n左键:打开时钟\n右键:打开日历",
|
||||
"max-length": 25,
|
||||
"on-click": "gnome-clocks",
|
||||
"on-click-right": "gnome-calendar",
|
||||
},
|
||||
"clock#date": {
|
||||
// "interval":
|
||||
"format": " {:%m-%d}",
|
||||
// "timezone":
|
||||
// "timezones":
|
||||
// "locale":
|
||||
// "rotate":
|
||||
// "on-click":
|
||||
// "on-click-middle":
|
||||
// "on-click-right":
|
||||
// "on-scroll-up":
|
||||
// "on-scroll-down":
|
||||
// "smooth-scrolling-threshold":
|
||||
// "tooltip":
|
||||
"on-click": "gnome-calendar",
|
||||
"tooltip-format": "{:%Y/%m/%d %A}",
|
||||
},
|
||||
//正在运行的软件
|
||||
"tray": {
|
||||
"icon-size": 22,
|
||||
"spacing": 7,
|
||||
// 自定义图标
|
||||
//"icons": {
|
||||
// "blueman": "/home/shorin/.config/waybar/logo/bluetooth.png",
|
||||
//},
|
||||
},
|
||||
//工作区和窗口名字
|
||||
"niri/workspaces": {
|
||||
"format": "{icon}",
|
||||
"format-icons": {
|
||||
"active": "",
|
||||
"default": "",
|
||||
},
|
||||
},
|
||||
"niri/window": {
|
||||
"format": "{}",
|
||||
"rewrite": {
|
||||
"(.*) - Mozilla Firefox": "🌎 $1",
|
||||
"(.*) - zsh": "> [$1]",
|
||||
},
|
||||
},
|
||||
//网络和蓝牙
|
||||
"group/network-bluetooth": {
|
||||
"orientation": "inherit",
|
||||
"drawer": {
|
||||
"transition-left-to-right": false,
|
||||
},
|
||||
"modules": [
|
||||
"network",
|
||||
"bluetooth"
|
||||
],
|
||||
},
|
||||
"network": {
|
||||
"format-disconnected": "{icon}",
|
||||
"format-wifi": "{icon}",
|
||||
"format-ethernet": "{icon}",
|
||||
"format-icons": {
|
||||
"disconnected": "",
|
||||
"wifi": [
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
""
|
||||
],
|
||||
"ethernet": "",
|
||||
},
|
||||
//鼠标悬浮在wifi图标上会显示wifi名称、信号强度、ip地址、设备名
|
||||
"tooltip-format-wifi": "{essid} ({signalStrength}%)\n{ifname} : {ipaddr}\n---\n左键:打开面板\n右键:打开高级网络配置工具",
|
||||
"tooltip-format-ethernet": "{ifname} : {ipaddr}\n---\n左键:打开面板\n右键:打开高级网络配置工具",
|
||||
"tooltip-format-disconnected": "网络未连接\n---\n左键:打开面板\n右键:打开高级网络配置工具",
|
||||
"on-click": "kitty --class nmtui -e nmtui",
|
||||
"on-click-right": "nm-connection-editor",
|
||||
},
|
||||
"bluetooth": {
|
||||
//蓝牙关闭状态下的图标和鼠标悬浮时的提示
|
||||
"format-disabled": "",
|
||||
"tooltip-format-disabled": "左键:启用蓝牙",
|
||||
"format": "",
|
||||
"tooltip-format-on": "左键:禁用蓝牙\n右键:打开面板",
|
||||
//连接状态下的图标和提示
|
||||
"format-connected": "",
|
||||
"tooltip-format-connected": " {:device_alias}\n---\n右键:打开面板",
|
||||
//点击功能
|
||||
"on-click": "~/.config/waybar/scripts/toggle-bluetooth.sh",
|
||||
"on-click-right": "kitty --class bluetui -e bluetui",
|
||||
},
|
||||
//左键循环切换性能模式
|
||||
"power-profiles-daemon": {
|
||||
"format": "{icon}",
|
||||
"tooltip-format": "当前性能模式:{profile}\n---\n左键:切换性能模式",
|
||||
"tooltip": true,
|
||||
"format-icons": {
|
||||
"performance": "",
|
||||
"balanced": "",
|
||||
"power-saver": "",
|
||||
},
|
||||
},
|
||||
//截图
|
||||
"custom/screenshot": {
|
||||
"format": "",
|
||||
"tooltip-format": "左键:快速截图(仅保存到剪贴板)\n右键:打开截图菜单\n中键:打开长截图菜单",
|
||||
"on-click": "~/.config/waybar/scripts/screenshot.sh",
|
||||
"on-click-right": "~/.config/waybar/scripts/power-screenshot.sh",
|
||||
"on-click-middle": "~/.config/waybar/scripts/longshot-sh/longshot.sh"
|
||||
},
|
||||
//wf-recorder
|
||||
"custom/wfrec": {
|
||||
"exec": "~/.config/waybar/scripts/wf-recorder.sh status-json",
|
||||
"return-type": "json",
|
||||
//"interval": 1, // 每秒刷新一次
|
||||
"signal": 9, // 与脚本里的 WAYBAR_SIG 对应
|
||||
"tooltip": true,
|
||||
"on-click": "~/.config/waybar/scripts/wf-recorder.sh toggle", // 左键:开始/停止
|
||||
"on-click-right": "~/.config/waybar/scripts/wf-recorder.sh stop"
|
||||
},
|
||||
//声音模块
|
||||
"group/audio": {
|
||||
"orientation": "inherit",
|
||||
"drawer": {
|
||||
"transition-left-to-right": false,
|
||||
},
|
||||
"modules": [
|
||||
"pulseaudio",
|
||||
"pulseaudio/slider"
|
||||
],
|
||||
},
|
||||
"pulseaudio": {
|
||||
"tooltip-format": "左键:静音\n右键:关闭麦克风\n中键:打开面板",
|
||||
"format": "{icon} {format_source} ",
|
||||
"format-bluetooth": " {format_source} ",
|
||||
"format-source": "{volume}%",
|
||||
"format-source-muted": "",
|
||||
"format-muted": " {format_source} ",
|
||||
"format-icons": {
|
||||
"headphone": "",
|
||||
"bluetooth": "",
|
||||
// "speaker":""
|
||||
"speaker": [
|
||||
"",
|
||||
"",
|
||||
""
|
||||
],
|
||||
},
|
||||
"on-click": "wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle",
|
||||
"on-click-right": "wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle",
|
||||
"on-click-middle": "pavucontrol --tab=3",
|
||||
"on-scroll-up": "wpctl set-volume @DEFAULT_AUDIO_SOURCE@ 5%+",
|
||||
"on-scroll-down": "wpctl set-volume @DEFAULT_AUDIO_SOURCE@ 5%-",
|
||||
},
|
||||
"pulseaudio/slider": {
|
||||
"min": 0,
|
||||
"max": 100,
|
||||
"orientation": "horizontal",
|
||||
},
|
||||
//多媒体
|
||||
"mpris": {
|
||||
"format": "{player_icon}",
|
||||
"format-paused": "{status_icon}",
|
||||
"player-icons": {
|
||||
"default": "▶",
|
||||
},
|
||||
"status-icons": {
|
||||
"paused": "⏸",
|
||||
},
|
||||
"toooltip": "{dynamic}",
|
||||
"enable-tooltip-len-limits": true,
|
||||
},
|
||||
//logo+程序启动器
|
||||
"custom/applauncher": {
|
||||
"tooltip-format": "左键:软件启动菜单\n右键:切换壁纸",
|
||||
"format": "",
|
||||
"on-click": "fuzzel",
|
||||
"on-click-right": "waypaper",
|
||||
// "on-click-middle": "~/.config/scripts/niri_auto_blur_bg.sh || pkill -f niri_auto_blur_bg.sh"
|
||||
},
|
||||
//电源菜单
|
||||
"group/powermenu": {
|
||||
"orientation": "inherit",
|
||||
"drawer": {
|
||||
"transition-left-to-right": false,
|
||||
},
|
||||
"modules": [
|
||||
"custom/wlogout",
|
||||
"custom/reboot",
|
||||
"custom/logout",
|
||||
"custom/lockscreen",
|
||||
],
|
||||
},
|
||||
"custom/wlogout": {
|
||||
"tooltip": false,
|
||||
"format": "",
|
||||
"on-click": "systemctl poweroff",
|
||||
"on-scroll-up": "true",
|
||||
"on-scroll-down": "true"
|
||||
},
|
||||
"custom/lockscreen": {
|
||||
"tooltip": false,
|
||||
"format": "",
|
||||
"on-click": "swaylock || hyprlock",
|
||||
"on-scroll-up": "true",
|
||||
"on-scroll-down": "true"
|
||||
},
|
||||
"custom/reboot": {
|
||||
"tooltip": false,
|
||||
"format": "",
|
||||
"on-click": "systemctl reboot",
|
||||
"on-scroll-up": "true",
|
||||
"on-scroll-down": "true"
|
||||
},
|
||||
"custom/logout": {
|
||||
"format": "",
|
||||
"on-click": "niri msg action quit",
|
||||
"tooltip": false,
|
||||
"on-scroll-up": "true",
|
||||
"on-scroll-down": "true"
|
||||
},
|
||||
"group/screenlight": {
|
||||
"orientation": "inherit",
|
||||
"drawer": {
|
||||
"transition-left-to-right": false,
|
||||
},
|
||||
"modules": [
|
||||
"backlight",
|
||||
"backlight/slider",
|
||||
|
||||
],
|
||||
},
|
||||
//内屏亮度
|
||||
"backlight/slider": {
|
||||
"min": 5,
|
||||
"max": 100,
|
||||
"orientation": "horizontal",
|
||||
},
|
||||
"backlight": {
|
||||
"tooltip": true,
|
||||
"tooltip-format":"调节内屏亮度",
|
||||
"format": ""
|
||||
},
|
||||
//调节外接屏幕亮度
|
||||
"group/ddcutil": {
|
||||
"orientation": "inherit",
|
||||
"drawer": {
|
||||
"transition-left-to-right": false,
|
||||
},
|
||||
"modules": [
|
||||
"custom/ddcutil-day",
|
||||
"backlight/slider",
|
||||
"backlight",
|
||||
"custom/separator#1",
|
||||
"custom/ddcutil-sleep",
|
||||
"custom/ddcutil-night",
|
||||
],
|
||||
},
|
||||
"custom/ddcutil-day": {
|
||||
"tooltip-format": "左键:100%外接屏幕亮度\n右键:切换护眼模式",
|
||||
"format": "",
|
||||
"on-click": "ddcutil --display 1 setvcp 10 100 ",
|
||||
"on-click-right": "~/.local/bin/toggle-wlsunset",
|
||||
"on-scroll-up": "true",
|
||||
"on-scroll-down": "true"
|
||||
},
|
||||
"custom/ddcutil-night": {
|
||||
"tooltip-format": "左键:65%外接屏幕亮度",
|
||||
"format": "",
|
||||
"on-click": "ddcutil --display 1 setvcp 10 65 ",
|
||||
"on-scroll-up": "true",
|
||||
"on-scroll-down": "true"
|
||||
},
|
||||
"custom/ddcutil-sleep": {
|
||||
"tooltip-format": "左键:5%外接屏幕亮度",
|
||||
"format": "",
|
||||
"on-click": "ddcutil --display 1 setvcp 10 5 ",
|
||||
"on-scroll-up": "true",
|
||||
"on-scroll-down": "true"
|
||||
},
|
||||
//swaync通知模块
|
||||
"custom/swaync": {
|
||||
"tooltip": false,
|
||||
"format": "{icon}",
|
||||
"format-icons": {
|
||||
"notification": "<span foreground='red'><sup></sup></span>",
|
||||
"none": "",
|
||||
"dnd-notification": "<span foreground='red'><sup></sup></span>",
|
||||
"dnd-none": "",
|
||||
"inhibited-notification": "<span foreground='red'><sup></sup></span>",
|
||||
"inhibited-none": "",
|
||||
"dnd-inhibited-notification": "<span foreground='red'><sup></sup></span>",
|
||||
"dnd-inhibited-none": "",
|
||||
},
|
||||
"return-type": "json",
|
||||
"exec-if": "which swaync-client",
|
||||
"exec": "swaync-client -swb",
|
||||
"on-click": "swaync-client -t -sw",
|
||||
"on-click-right": "swaync-client -d -sw",
|
||||
"escape": true,
|
||||
},
|
||||
"custom/mako": {
|
||||
"format": "",
|
||||
"on-click": "makoctl restore",
|
||||
},
|
||||
//gnome-control-center
|
||||
"custom/settings": {
|
||||
"format": "",
|
||||
"on-click": "env XDG_CURRENT_DESKTOP=GNOME gnome-control-center",
|
||||
"tooltip-format": "Open control center",
|
||||
},
|
||||
//获取屏幕颜色colorpicker
|
||||
"custom/colorpicker": {
|
||||
"tooltip": true,
|
||||
"format": "",
|
||||
"on-click": "hyprpicker | wl-copy",
|
||||
"tooltip-format":"左键:提取颜色",
|
||||
},
|
||||
//音频可视化
|
||||
"custom/cava": {
|
||||
"tooltip": false,
|
||||
"format": "{}",
|
||||
"exec": "~/.config/waybar/scripts/cava.sh",
|
||||
},
|
||||
//禁止熄屏
|
||||
"idle_inhibitor": {
|
||||
"format": "{icon}",
|
||||
"format-icons": {
|
||||
"activated": "",
|
||||
"deactivated": "",
|
||||
},
|
||||
"tooltip-format-activated": "自动熄屏已禁止",
|
||||
"tooltip-format-deactivated": "自动熄屏已开启",
|
||||
"on-click-right": "~/.config/scripts/matugen-select-type.sh"
|
||||
},
|
||||
//分隔符
|
||||
"custom/separator#1": {
|
||||
"format": "",
|
||||
"tooltip": false,
|
||||
},
|
||||
//archupdater
|
||||
"custom/updates": {
|
||||
"format": "{}{icon}",
|
||||
"return-type": "json",
|
||||
"format-icons": {
|
||||
"has-updates": "",
|
||||
"updated": ""
|
||||
},
|
||||
"exec": "~/.config/waybar/scripts/check-updates.sh",
|
||||
"interval": 3600,
|
||||
"on-click": "kitty -e paru",
|
||||
},
|
||||
//隐私
|
||||
"privacy": {
|
||||
"icon-spacing": 10,
|
||||
"icon-size": 16,
|
||||
"transition-duration": 250,
|
||||
"modules": [
|
||||
{
|
||||
"type": "screenshare",
|
||||
"tooltip": true,
|
||||
"tooltip-icon-size": 24,
|
||||
},
|
||||
],
|
||||
},
|
||||
"cffi/niri-taskbar": {
|
||||
// module_path
|
||||
"module_path": "/usr/lib/waybar/libniri_taskbar.so",
|
||||
"apps": {
|
||||
"signal": [
|
||||
{
|
||||
"match": "\\([0-9]+\\)$",
|
||||
"class": "unread",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
// dwl and mangowc
|
||||
"ext/workspaces": {
|
||||
"format": "{icon}",
|
||||
//"format-icons": {
|
||||
// "active": "",
|
||||
// "default": ""
|
||||
// },
|
||||
"ignore-hidden": true,
|
||||
"on-click": "activate",
|
||||
"on-click-right": "deactivate",
|
||||
"sort-by-id": true,
|
||||
},
|
||||
"dwl/window": {
|
||||
"format": "[{layout}]{title}",
|
||||
}
|
||||
|
||||
}
|
||||
356
.config/waybar/modules.jsonc.bak
Executable file
356
.config/waybar/modules.jsonc.bak
Executable file
@@ -0,0 +1,356 @@
|
||||
{
|
||||
// 电池电量
|
||||
"battery": {
|
||||
"interval": 60,
|
||||
"states": {
|
||||
"critical": 20
|
||||
},
|
||||
"format": "{icon} {capacity}%",
|
||||
"format-charging": " {capacity}% ",
|
||||
"format-icons": [
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
""
|
||||
],
|
||||
"tooltip-format": "剩余电量:{capacity}%\n充 放 电 :{power}\n可用时间:{timeTo}\n---\n电池健康度:{health}%"
|
||||
},
|
||||
|
||||
// 时钟
|
||||
"clock": {
|
||||
"interval": 60,
|
||||
"format": " {:%H:%M}",
|
||||
"tooltip-format": "{:%Y/%m/%d %A}\n---\n左键:打开时钟\n右键:打开日历",
|
||||
"max-length": 25,
|
||||
"on-click": "gnome-clocks",
|
||||
"on-click-right": "gnome-calendar"
|
||||
},
|
||||
|
||||
// 日期小部件(点击打开日历)
|
||||
"clock#date": {
|
||||
"format": " {:%m-%d}",
|
||||
"on-click": "gnome-calendar",
|
||||
"tooltip-format": "{:%Y/%m/%d %A}"
|
||||
},
|
||||
|
||||
// 系统托盘(蓝牙、输入法等图标)
|
||||
"tray": {
|
||||
"icon-size": 22,
|
||||
"spacing": 7
|
||||
},
|
||||
|
||||
// niri 工作区指示器
|
||||
"niri/workspaces": {
|
||||
"format": "{icon}",
|
||||
"format-icons": {
|
||||
"active": "",
|
||||
"default": ""
|
||||
}
|
||||
},
|
||||
|
||||
// 当前窗口标题美化
|
||||
"niri/window": {
|
||||
"format": "{}",
|
||||
"rewrite": {
|
||||
"(.*) - Mozilla Firefox": "🌎 $1",
|
||||
"(.*) - zsh": "> [$1]"
|
||||
}
|
||||
},
|
||||
|
||||
// 网络 & 蓝牙组合抽屉
|
||||
"group/network-bluetooth": {
|
||||
"orientation": "inherit",
|
||||
"drawer": {
|
||||
"transition-left-to-right": false
|
||||
},
|
||||
"modules": [
|
||||
"network",
|
||||
"bluetooth"
|
||||
]
|
||||
},
|
||||
|
||||
// 网络状态
|
||||
"network": {
|
||||
"format-disconnected": "{icon}",
|
||||
"format-wifi": "{icon}",
|
||||
"format-ethernet": "{icon}",
|
||||
"format-icons": {
|
||||
"disconnected": "",
|
||||
"wifi": [
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
""
|
||||
],
|
||||
"ethernet": ""
|
||||
},
|
||||
"tooltip-format-wifi": "{essid} ({signalStrength}%)\n{ifname} : {ipaddr}\n---\n左键:打开面板\n右键:打开高级网络配置工具",
|
||||
"tooltip-format-ethernet": "{ifname} : {ipaddr}\n---\n左键:打开面板\n右键:打开高级网络配置工具",
|
||||
"tooltip-format-disconnected": "网络未连接\n---\n左键:打开面板\n右键:打开高级网络配置工具",
|
||||
"on-click": "kitty --class nmtui -e nmtui",
|
||||
"on-click-right": "nm-connection-editor"
|
||||
},
|
||||
|
||||
// 蓝牙控制
|
||||
"bluetooth": {
|
||||
"format-disabled": "",
|
||||
"tooltip-format-disabled": "左键:启用蓝牙",
|
||||
"format": "",
|
||||
"tooltip-format-on": "左键:禁用蓝牙\n右键:打开面板",
|
||||
"format-connected": "",
|
||||
"tooltip-format-connected": " {:device_alias}\n---\n右键:打开面板",
|
||||
"on-click": "~/.config/waybar/scripts/toggle-bluetooth.sh",
|
||||
"on-click-right": "kitty --class bluetui -e bluetui"
|
||||
},
|
||||
|
||||
// 性能模式切换(左键循环)
|
||||
"power-profiles-daemon": {
|
||||
"format": "{icon}",
|
||||
"tooltip-format": "当前性能模式:{profile}\n---\n左键:切换性能模式",
|
||||
"tooltip": true,
|
||||
"format-icons": {
|
||||
"performance": "",
|
||||
"balanced": "",
|
||||
"power-saver": ""
|
||||
}
|
||||
},
|
||||
|
||||
// 音频组(图标 + 滑块)
|
||||
"group/audio": {
|
||||
"orientation": "inherit",
|
||||
"drawer": {
|
||||
"transition-left-to-right": false
|
||||
},
|
||||
"modules": [
|
||||
"pulseaudio",
|
||||
"pulseaudio/slider"
|
||||
]
|
||||
},
|
||||
|
||||
// 音频控制(支持滚动调节麦克风音量)
|
||||
"pulseaudio": {
|
||||
"tooltip-format": "左键:静音\n右键:关闭麦克风\n中键:打开面板",
|
||||
"format": "{icon} {format_source} ",
|
||||
"format-bluetooth": " {format_source} ",
|
||||
"format-source": "{volume}%",
|
||||
"format-source-muted": "",
|
||||
"format-muted": " {format_source} ",
|
||||
"format-icons": {
|
||||
"headphone": "",
|
||||
"bluetooth": "",
|
||||
"speaker": [
|
||||
"",
|
||||
"",
|
||||
""
|
||||
]
|
||||
},
|
||||
"on-click": "wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle",
|
||||
"on-click-right": "wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle",
|
||||
"on-click-middle": "pavucontrol --tab=3",
|
||||
"on-scroll-up": "wpctl set-volume @DEFAULT_AUDIO_SOURCE@ 5%+",
|
||||
"on-scroll-down": "wpctl set-volume @DEFAULT_AUDIO_SOURCE@ 5%-"
|
||||
},
|
||||
|
||||
// 音量滑块(横向)
|
||||
"pulseaudio/slider": {
|
||||
"min": 0,
|
||||
"max": 100,
|
||||
"orientation": "horizontal"
|
||||
},
|
||||
|
||||
// 多媒体播放器控制(mpris)
|
||||
"mpris": {
|
||||
"format": "{player_icon}",
|
||||
"format-paused": "{status_icon}",
|
||||
"player-icons": {
|
||||
"default": "▶"
|
||||
},
|
||||
"status-icons": {
|
||||
"paused": "⏸"
|
||||
},
|
||||
"tooltip": "{dynamic}",
|
||||
"enable-tooltip-len-limits": true
|
||||
},
|
||||
|
||||
// 应用启动器 + 壁纸切换
|
||||
"custom/applauncher": {
|
||||
"tooltip-format": "左键:软件启动菜单\n右键:切换壁纸",
|
||||
"format": "",
|
||||
"on-click": "fuzzel",
|
||||
"on-click-right": "waypaper"
|
||||
},
|
||||
|
||||
// 电源菜单组(登出/重启/锁屏)
|
||||
"group/powermenu": {
|
||||
"orientation": "inherit",
|
||||
"drawer": {
|
||||
"transition-left-to-right": false
|
||||
},
|
||||
"modules": [
|
||||
"custom/wlogout",
|
||||
"custom/reboot",
|
||||
"custom/logout",
|
||||
"custom/lockscreen"
|
||||
]
|
||||
},
|
||||
|
||||
"custom/wlogout": {
|
||||
"tooltip": false,
|
||||
"format": "",
|
||||
"on-click": "wlogout",
|
||||
"on-scroll-up": "true",
|
||||
"on-scroll-down": "true"
|
||||
},
|
||||
|
||||
"custom/lockscreen": {
|
||||
"tooltip": false,
|
||||
"format": "",
|
||||
"on-click": "swaylock || hyprlock",
|
||||
"on-scroll-up": "true",
|
||||
"on-scroll-down": "true"
|
||||
},
|
||||
|
||||
"custom/reboot": {
|
||||
"tooltip": false,
|
||||
"format": "",
|
||||
"on-click": "systemctl reboot",
|
||||
"on-scroll-up": "true",
|
||||
"on-scroll-down": "true"
|
||||
},
|
||||
|
||||
"custom/logout": {
|
||||
"format": "",
|
||||
"on-click": "niri msg action quit",
|
||||
"tooltip": false,
|
||||
"on-scroll-up": "true",
|
||||
"on-scroll-down": "true"
|
||||
},
|
||||
|
||||
// ✅ 内屏亮度控制模块(已优化,不依赖外屏)
|
||||
// 使用 backlight + slider 组合,适配大多数笔记本/内置显示器
|
||||
"backlight": {
|
||||
"tooltip": true,
|
||||
"tooltip-format": "调节内屏亮度",
|
||||
"format": "{icon}",
|
||||
"format-icons": [
|
||||
"", // 0-10%
|
||||
"", // 10-20%
|
||||
"", // 20-30%
|
||||
"", // 30-40%
|
||||
"", // 40-50%
|
||||
"", // 50-60%
|
||||
"", // 60-70%
|
||||
"", // 70-80%
|
||||
"" // 80-100%
|
||||
]
|
||||
},
|
||||
|
||||
"backlight/slider": {
|
||||
"min": 5, // 最低亮度设为5避免完全黑屏
|
||||
"max": 100,
|
||||
"orientation": "horizontal"
|
||||
},
|
||||
|
||||
// 通知中心(swaync)
|
||||
"custom/swaync": {
|
||||
"tooltip": false,
|
||||
"format": "{icon}",
|
||||
"format-icons": {
|
||||
"notification": "<span foreground='red'><sup></sup></span>",
|
||||
"none": "",
|
||||
"dnd-notification": "<span foreground='red'><sup></sup></span>",
|
||||
"dnd-none": "",
|
||||
"inhibited-notification": "<span foreground='red'><sup></sup></span>",
|
||||
"inhibited-none": "",
|
||||
"dnd-inhibited-notification": "<span foreground='red'><sup></sup></span>",
|
||||
"dnd-inhibited-none": ""
|
||||
},
|
||||
"return-type": "json",
|
||||
"exec-if": "which swaync-client",
|
||||
"exec": "swaync-client -swb",
|
||||
"on-click": "swaync-client -t -sw",
|
||||
"on-click-right": "swaync-client -d -sw",
|
||||
"escape": true
|
||||
},
|
||||
|
||||
// 备用通知方案(mako)
|
||||
"custom/mako": {
|
||||
"format": "",
|
||||
"on-click": "makoctl restore"
|
||||
},
|
||||
|
||||
// 系统设置面板
|
||||
"custom/settings": {
|
||||
"format": "",
|
||||
"on-click": "env XDG_CURRENT_DESKTOP=GNOME gnome-control-center",
|
||||
"tooltip-format": "打开系统设置"
|
||||
},
|
||||
|
||||
// 自动更新检查器(Arch Linux)
|
||||
"custom/updates": {
|
||||
"format": "{}{icon}",
|
||||
"return-type": "json",
|
||||
"format-icons": {
|
||||
"has-updates": "",
|
||||
"updated": ""
|
||||
},
|
||||
"exec": "~/.config/waybar/scripts/check-updates.sh",
|
||||
"interval": 3600,
|
||||
"on-click": "kitty -e sysup"
|
||||
},
|
||||
|
||||
// 隐私指示器(如屏幕共享)
|
||||
"privacy": {
|
||||
"icon-spacing": 10,
|
||||
"icon-size": 16,
|
||||
"transition-duration": 250,
|
||||
"modules": [
|
||||
{
|
||||
"type": "screenshare",
|
||||
"tooltip": true,
|
||||
"tooltip-icon-size": 24
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
// niri 任务栏扩展
|
||||
"cffi/niri-taskbar": {
|
||||
"module_path": "/usr/lib/waybar/libniri_taskbar.so",
|
||||
"apps": {
|
||||
"signal": [
|
||||
{
|
||||
"match": "\\([0-9]+\\)$",
|
||||
"class": "unread"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
// 兼容 dwl / mangowc 的工作区模块(备用)
|
||||
"ext/workspaces": {
|
||||
"format": "{icon}",
|
||||
"ignore-hidden": true,
|
||||
"on-click": "activate",
|
||||
"on-click-right": "deactivate",
|
||||
"sort-by-id": true
|
||||
},
|
||||
|
||||
// dwl 窗口标题显示
|
||||
"dwl/window": {
|
||||
"format": "[{layout}]{title}"
|
||||
},
|
||||
|
||||
// 分隔符
|
||||
"custom/separator#1": {
|
||||
"format": "",
|
||||
"tooltip": false
|
||||
}
|
||||
}
|
||||
|
||||
65
.config/waybar/scripts/cava.sh
Executable file
65
.config/waybar/scripts/cava.sh
Executable file
@@ -0,0 +1,65 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 配置
|
||||
CHARS="▁▂▃▄▅▆▇█"
|
||||
BARS=10
|
||||
CONF="/tmp/waybar_cava_config"
|
||||
|
||||
# 初始化
|
||||
len=$((${#CHARS}-1))
|
||||
idle_char="${CHARS:0:1}"
|
||||
idle_output=$(printf "%0.s$idle_char" $(seq 1 $BARS))
|
||||
|
||||
# 生成 Cava 配置
|
||||
cat > "$CONF" <<EOF
|
||||
[general]
|
||||
bars = $BARS
|
||||
[input]
|
||||
method = pulse
|
||||
source = auto
|
||||
[output]
|
||||
method = raw
|
||||
raw_target = /dev/stdout
|
||||
data_format = ascii
|
||||
ascii_max_range = $len
|
||||
EOF
|
||||
|
||||
cleanup() {
|
||||
trap - EXIT INT TERM
|
||||
pkill -P $$ 2>/dev/null
|
||||
echo "$idle_output"
|
||||
exit 0
|
||||
}
|
||||
trap cleanup EXIT INT TERM
|
||||
|
||||
# 核心检测:是否存在未暂停的音频流
|
||||
is_audio_active() {
|
||||
pactl list sink-inputs 2>/dev/null | grep -q "Corked: no"
|
||||
}
|
||||
|
||||
# 初始状态
|
||||
echo "$idle_output"
|
||||
|
||||
while true; do
|
||||
# 如果存在未静音的音频
|
||||
if is_audio_active; then
|
||||
if ! pgrep -P $$ -x cava >/dev/null; then
|
||||
# 这里的 sed 字典是根据你的 CHARS 动态生成的
|
||||
sed_dict="s/;//g;"
|
||||
for ((i=0; i<=${len}; i++)); do
|
||||
sed_dict="${sed_dict}s/$i/${CHARS:$i:1}/g;"
|
||||
done
|
||||
cava -p "$CONF" 2>/dev/null | sed -u "$sed_dict" &
|
||||
fi
|
||||
# 正在播放时,稍微降低检查频率减少 CPU 占用
|
||||
sleep 1
|
||||
else
|
||||
if pgrep -P $$ -x cava >/dev/null; then
|
||||
pkill -P $$ -x cava 2>/dev/null
|
||||
wait 2>/dev/null
|
||||
echo "$idle_output"
|
||||
fi
|
||||
# 没声音时,使用 subscribe 等待事件,被动唤醒,不产生任何循环开销
|
||||
timeout 5s pactl subscribe 2>/dev/null | grep --line-buffered "sink-input" | head -n 1 >/dev/null
|
||||
fi
|
||||
done
|
||||
60
.config/waybar/scripts/check-updates.sh
Executable file
60
.config/waybar/scripts/check-updates.sh
Executable file
@@ -0,0 +1,60 @@
|
||||
#!/bin/bash
|
||||
|
||||
# === 配置部分 ===
|
||||
CACHE_FILE="$HOME/.cache/waybar-updates.json"
|
||||
|
||||
# === 函数定义 ===
|
||||
generate_json() {
|
||||
local updates=$1
|
||||
local count
|
||||
|
||||
if [ -z "$updates" ]; then
|
||||
count=0
|
||||
else
|
||||
count=$(echo "$updates" | wc -l)
|
||||
fi
|
||||
|
||||
if [ "$count" -gt 0 ]; then
|
||||
local tooltip=$(echo "$updates" | awk '{printf "%s\\n", $0}' | sed 's/"/\\"/g' | head -c -2)
|
||||
printf '{"text": "%s", "alt": "has-updates", "tooltip": "%s"}\n' "$count" "$tooltip"
|
||||
else
|
||||
printf '{"text": "", "alt": "updated", "tooltip": "System is up to date"}\n'
|
||||
fi
|
||||
}
|
||||
|
||||
# === 主逻辑 ===
|
||||
|
||||
# 尝试获取更新
|
||||
# 捕获输出
|
||||
NEW_UPDATES=$(checkupdates 2>/dev/null)
|
||||
STATUS=$?
|
||||
|
||||
# === 关键修正 ===
|
||||
# checkupdates 退出代码说明:
|
||||
# 0 = 有更新
|
||||
# 2 = 无更新 (这是正常情况,不是错误!)
|
||||
# 1 = 发生错误 (如网络断开、锁被占用)
|
||||
|
||||
if [ $STATUS -eq 0 ]; then
|
||||
# --- 情况A:发现更新 ---
|
||||
OUTPUT=$(generate_json "$NEW_UPDATES")
|
||||
echo "$OUTPUT" > "$CACHE_FILE"
|
||||
echo "$OUTPUT"
|
||||
|
||||
elif [ $STATUS -eq 2 ]; then
|
||||
# --- 情况B:正常运行,但没有更新 ---
|
||||
# 必须清空缓存或者写入 0 状态,而不是读取旧缓存
|
||||
OUTPUT=$(generate_json "")
|
||||
echo "$OUTPUT" > "$CACHE_FILE"
|
||||
echo "$OUTPUT"
|
||||
|
||||
else
|
||||
# --- 情况C:真的出错了 (Exit 1) ---
|
||||
# 比如没网,或者 pacman 锁死
|
||||
# 只有这种时候才读取旧缓存来保底
|
||||
if [ -f "$CACHE_FILE" ]; then
|
||||
cat "$CACHE_FILE"
|
||||
else
|
||||
printf '{"text": "?", "alt": "updated", "tooltip": "Check failed"}\n'
|
||||
fi
|
||||
fi
|
||||
134
.config/waybar/scripts/longshot-sh/longshot-grim.sh
Executable file
134
.config/waybar/scripts/longshot-sh/longshot-grim.sh
Executable file
@@ -0,0 +1,134 @@
|
||||
#!/bin/bash
|
||||
|
||||
# =================语言=================
|
||||
if env | grep -q "zh_CN"; then
|
||||
STR_NEXT="📸 截取下一张 (仅需定高度)"
|
||||
STR_FINISH="💾 完成并处理"
|
||||
STR_ABORT="❌ 放弃"
|
||||
STR_ERR="错误"
|
||||
STR_SAVED="已保存"
|
||||
else
|
||||
STR_NEXT="📸 Capture Next (Height only)"
|
||||
STR_FINISH="💾 Finish"
|
||||
STR_ABORT="❌ Abort"
|
||||
STR_ERR="Error"
|
||||
STR_SAVED="Saved"
|
||||
fi
|
||||
|
||||
# =================配置=================
|
||||
CONFIG_DIR="$HOME/.cache/longshot-sh"
|
||||
CONFIG_FILE="$CONFIG_DIR/mode"
|
||||
SAVE_DIR="$HOME/Pictures/Screenshots/longshots"
|
||||
|
||||
TMP_DIR="/tmp/longshot_grim_$(date +%s)"
|
||||
FILENAME="longshot_$(date +%Y%m%d_%H%M%S).png"
|
||||
RESULT_PATH="$SAVE_DIR/$FILENAME"
|
||||
TMP_STITCHED="$TMP_DIR/stitched.png"
|
||||
|
||||
mkdir -p "$SAVE_DIR" "$TMP_DIR"
|
||||
cleanup() { rm -rf "$TMP_DIR"; }
|
||||
trap cleanup EXIT SIGINT SIGTERM
|
||||
|
||||
# 菜单工具
|
||||
# [修改]: 将 fuzzel 宽度从 45 调整为 30,使其更紧凑
|
||||
CMD_FUZZEL="fuzzel -d --anchor=top --y-margin=20 --lines=3 --width=30"
|
||||
CMD_WOFI="wofi --dmenu --lines 3"
|
||||
CMD_ROFI="rofi -dmenu -l 3"
|
||||
|
||||
if command -v fuzzel &> /dev/null; then MENU_CMD="$CMD_FUZZEL"
|
||||
elif command -v wofi &> /dev/null; then MENU_CMD="$CMD_WOFI"
|
||||
elif command -v rofi &> /dev/null; then MENU_CMD="$CMD_ROFI"
|
||||
else exit 1; fi
|
||||
|
||||
# [新增]: 动态计算宽度函数 (主要针对 wofi)
|
||||
function get_dynamic_width() {
|
||||
local text="$1"
|
||||
# 获取最长行的长度
|
||||
local max_len=$(echo -e "$text" | wc -L)
|
||||
# 计算: 字符数 * 28px + 60px 边距 (可根据屏幕分辨率微调)
|
||||
echo $(( max_len * 28 + 60 ))
|
||||
}
|
||||
|
||||
# [修改]: 增加对 wofi 的动态宽度支持
|
||||
function show_menu() {
|
||||
local content="$1"
|
||||
|
||||
if [[ "$MENU_CMD" == *"wofi"* ]]; then
|
||||
# 如果是 wofi,计算宽度并附加参数
|
||||
local width=$(get_dynamic_width "$content")
|
||||
echo -e "$content" | $MENU_CMD --width "$width"
|
||||
else
|
||||
# 其他工具 (fuzzel/rofi) 保持原样
|
||||
echo -e "$content" | $MENU_CMD
|
||||
fi
|
||||
}
|
||||
|
||||
# ======================================
|
||||
# Step 1: 第一张截图 (直接开始,不询问)
|
||||
# ======================================
|
||||
# 用户在主菜单点击了 "选择区域",所以这里直接 Slurp
|
||||
GEO_1=$(slurp)
|
||||
if [ -z "$GEO_1" ]; then exit 0; fi
|
||||
|
||||
IFS=', x' read -r FIX_X FIX_Y FIX_W FIX_H <<< "$GEO_1"
|
||||
grim -g "$GEO_1" "$TMP_DIR/001.png"
|
||||
|
||||
# ======================================
|
||||
# Step 2: 循环截图
|
||||
# ======================================
|
||||
INDEX=2
|
||||
DO_SAVE=false
|
||||
|
||||
while true; do
|
||||
# 菜单提示下一张 (show_menu 会自动处理宽度)
|
||||
ACTION=$(show_menu "$STR_NEXT\n$STR_FINISH\n$STR_ABORT")
|
||||
|
||||
case "$ACTION" in
|
||||
*"📸"*)
|
||||
sleep 0.2
|
||||
GEO_NEXT=$(slurp)
|
||||
if [ -z "$GEO_NEXT" ]; then continue; fi
|
||||
|
||||
# 锁定宽度和X轴,只取新高度
|
||||
IFS=', x' read -r _TX NEW_Y _TW NEW_H <<< "$GEO_NEXT"
|
||||
FINAL_GEO="${FIX_X},${NEW_Y} ${FIX_W}x${NEW_H}"
|
||||
|
||||
IMG_NAME="$(printf "%03d" $INDEX).png"
|
||||
grim -g "$FINAL_GEO" "$TMP_DIR/$IMG_NAME"
|
||||
((INDEX++))
|
||||
;;
|
||||
*"💾"*) DO_SAVE=true; break ;;
|
||||
*"❌"*) exit 0 ;;
|
||||
*) break ;; # 意外退出
|
||||
esac
|
||||
done
|
||||
|
||||
# ======================================
|
||||
# Step 3: 处理与自动动作
|
||||
# ======================================
|
||||
COUNT=$(ls "$TMP_DIR"/*.png 2>/dev/null | wc -l)
|
||||
|
||||
if [ "$COUNT" -gt 0 ] && [ "$DO_SAVE" = true ]; then
|
||||
# 拼接
|
||||
magick "$TMP_DIR"/*.png -append "$TMP_STITCHED"
|
||||
mv "$TMP_STITCHED" "$RESULT_PATH"
|
||||
|
||||
# 复制到剪贴板
|
||||
if command -v wl-copy &> /dev/null; then wl-copy < "$RESULT_PATH"; fi
|
||||
|
||||
# 读取配置执行动作
|
||||
FINAL_MODE=$(cat "$CONFIG_FILE" 2>/dev/null || echo "PREVIEW")
|
||||
|
||||
case "$FINAL_MODE" in
|
||||
"PREVIEW")
|
||||
imv "$RESULT_PATH"
|
||||
;;
|
||||
"EDIT")
|
||||
if command -v satty &> /dev/null; then satty -f "$RESULT_PATH"
|
||||
else imv "$RESULT_PATH"; fi
|
||||
;;
|
||||
"SAVE")
|
||||
notify-send -i "$RESULT_PATH" "Longshot" "$STR_SAVED: $(basename "$RESULT_PATH")"
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
80
.config/waybar/scripts/longshot-sh/longshot-wf-recorder.sh
Executable file
80
.config/waybar/scripts/longshot-sh/longshot-wf-recorder.sh
Executable file
@@ -0,0 +1,80 @@
|
||||
#!/bin/bash
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
VENV_PYTHON="$SCRIPT_DIR/venv/bin/python"
|
||||
PY_STITCH="$SCRIPT_DIR/stitch.py"
|
||||
|
||||
CONFIG_DIR="$HOME/.cache/longshot-sh"
|
||||
CONFIG_FILE="$CONFIG_DIR/mode"
|
||||
SAVE_DIR="$HOME/Pictures/Screenshots/longshots"
|
||||
mkdir -p "$SAVE_DIR"
|
||||
|
||||
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
||||
TMP_VIDEO="/tmp/longshot_${TIMESTAMP}.mp4"
|
||||
OUTPUT_IMG="${SAVE_DIR}/longshot_${TIMESTAMP}.png"
|
||||
|
||||
# 语言
|
||||
if env | grep -q "zh_CN"; then
|
||||
TXT_REC="录制中"
|
||||
TXT_MSG="正在拼接..."
|
||||
TXT_SAVED="已保存并复制"
|
||||
WIDTH=6
|
||||
else
|
||||
TXT_REC="Recording"
|
||||
TXT_MSG="Stitching..."
|
||||
TXT_SAVED="Saved"
|
||||
WIDTH=10
|
||||
fi
|
||||
|
||||
# 录制菜单
|
||||
if command -v fuzzel &> /dev/null; then
|
||||
MENU_REC_CMD="fuzzel -d --anchor top --y-margin 20 --width $WIDTH --lines 0"
|
||||
elif command -v wofi &> /dev/null; then
|
||||
MENU_REC_CMD="wofi -d -i -p Rec"
|
||||
else
|
||||
MENU_REC_CMD="rofi -dmenu -p Rec"
|
||||
fi
|
||||
|
||||
# Step 1: 选区
|
||||
GEOMETRY=$(slurp)
|
||||
if [ -z "$GEOMETRY" ]; then exit 1; fi
|
||||
|
||||
# Step 2: 录制
|
||||
wf-recorder -g "$GEOMETRY" -f "$TMP_VIDEO" \
|
||||
-c libx264 -p crf=0 -p preset=ultrafast -p pixel_format=yuv420p \
|
||||
&> /dev/null &
|
||||
REC_PID=$!
|
||||
|
||||
sleep 0.5
|
||||
if ! kill -0 $REC_PID 2>/dev/null; then
|
||||
notify-send "Error" "wf-recorder failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Step 3: 停止菜单
|
||||
echo "Stop" | $MENU_REC_CMD -p "$TXT_REC" > /dev/null
|
||||
|
||||
kill -SIGINT $REC_PID
|
||||
wait $REC_PID 2>/dev/null
|
||||
|
||||
# Step 4: 处理
|
||||
if [ -f "$TMP_VIDEO" ]; then
|
||||
notify-send -t 2000 "Longshot" "$TXT_MSG"
|
||||
|
||||
"$VENV_PYTHON" "$PY_STITCH" "$TMP_VIDEO" "$OUTPUT_IMG"
|
||||
rm -f "$TMP_VIDEO"
|
||||
|
||||
if [ -f "$OUTPUT_IMG" ]; then
|
||||
if command -v wl-copy &> /dev/null; then wl-copy < "$OUTPUT_IMG"; fi
|
||||
|
||||
# 自动执行动作
|
||||
FINAL_MODE=$(cat "$CONFIG_FILE" 2>/dev/null || echo "PREVIEW")
|
||||
case "$FINAL_MODE" in
|
||||
"PREVIEW") imv "$OUTPUT_IMG" ;;
|
||||
"EDIT") if command -v satty &> /dev/null; then satty -f "$OUTPUT_IMG"; else imv "$OUTPUT_IMG"; fi ;;
|
||||
"SAVE") notify-send "Longshot" "$TXT_SAVED: $(basename "$OUTPUT_IMG")" ;;
|
||||
esac
|
||||
fi
|
||||
else
|
||||
notify-send "Error" "No video"
|
||||
fi
|
||||
224
.config/waybar/scripts/longshot-sh/longshot.sh
Executable file
224
.config/waybar/scripts/longshot-sh/longshot.sh
Executable file
@@ -0,0 +1,224 @@
|
||||
#!/bin/bash
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
VENV_PYTHON="$SCRIPT_DIR/venv/bin/python"
|
||||
|
||||
# =================配置区=================
|
||||
CONFIG_DIR="$HOME/.cache/longshot-sh"
|
||||
mkdir -p "$CONFIG_DIR"
|
||||
|
||||
FILE_MODE="$CONFIG_DIR/mode" # PREVIEW / EDIT / SAVE
|
||||
FILE_BACKEND="$CONFIG_DIR/backend" # WF / GRIM
|
||||
|
||||
# 初始化默认值
|
||||
[ ! -f "$FILE_MODE" ] && echo "PREVIEW" > "$FILE_MODE"
|
||||
[ ! -f "$FILE_BACKEND" ] && echo "WF" > "$FILE_BACKEND"
|
||||
|
||||
# =================语言资源=================
|
||||
if env | grep -q "zh_CN"; then
|
||||
TXT_TITLE_WF="缓慢滚动,回车停止"
|
||||
TXT_TITLE_GRIM="记住截图末尾位置"
|
||||
|
||||
TXT_START="📷 选择截图区域"
|
||||
TXT_SETTING="⚙️ 设置"
|
||||
TXT_EXIT="❌ 退出"
|
||||
|
||||
TXT_BACK="🔙 返回主菜单"
|
||||
TXT_SW_BACKEND="📹 切换后端"
|
||||
TXT_SW_ACTION="🛠 切换动作"
|
||||
TXT_PROMPT_ACTION="请选择截图后的动作:"
|
||||
|
||||
TXT_ST_WF="流式录制 (wf-recorder)"
|
||||
TXT_ST_GRIM="分段截图 (grim)"
|
||||
|
||||
TXT_ST_PRE="预览 (imv)"
|
||||
TXT_ST_EDIT="编辑 (satty)"
|
||||
TXT_ST_SAVE="仅保存"
|
||||
|
||||
# 初始化提示
|
||||
TXT_MSG_INIT="首次运行,正在初始化环境..."
|
||||
TXT_MSG_SETUP_DONE="环境初始化完成!"
|
||||
TXT_ERR_SETUP="环境安装失败,请查看 /tmp/longshot_setup.log"
|
||||
TXT_ERR_NO_SETUP="未找到 setup.sh 文件"
|
||||
|
||||
# 新增:依赖缺失提示
|
||||
TXT_ERR_DEP_TITLE="缺少系统依赖"
|
||||
TXT_ERR_DEP_MSG="请安装以下包:"
|
||||
else
|
||||
TXT_TITLE_WF="Scroll Slowly, Enter to Stop"
|
||||
TXT_TITLE_GRIM="Remember End Position"
|
||||
|
||||
TXT_START="📷 Select Area"
|
||||
TXT_SETTING="⚙️ Settings"
|
||||
TXT_EXIT="❌ Exit"
|
||||
|
||||
TXT_BACK="🔙 Back"
|
||||
TXT_SW_BACKEND="📹 Switch Backend"
|
||||
TXT_SW_ACTION="🛠 Switch Action"
|
||||
TXT_PROMPT_ACTION="Select action after capture:"
|
||||
|
||||
TXT_ST_WF="Stream (wf-recorder)"
|
||||
TXT_ST_GRIM="Manual (grim)"
|
||||
|
||||
TXT_ST_PRE="Preview"
|
||||
TXT_ST_EDIT="Edit"
|
||||
TXT_ST_SAVE="Save Only"
|
||||
|
||||
# Init messages
|
||||
TXT_MSG_INIT="First run, initializing environment..."
|
||||
TXT_MSG_SETUP_DONE="Environment initialized!"
|
||||
TXT_ERR_SETUP="Setup failed, check /tmp/longshot_setup.log"
|
||||
TXT_ERR_NO_SETUP="setup.sh not found"
|
||||
|
||||
# New: Dependency error
|
||||
TXT_ERR_DEP_TITLE="Missing Dependencies"
|
||||
TXT_ERR_DEP_MSG="Please install:"
|
||||
fi
|
||||
|
||||
# ================= 1. 系统依赖检测 (新增) =================
|
||||
# 检测核心工具: wf-recorder, grim, slurp, imagemagick (magick)
|
||||
REQUIRED_TOOLS=("wf-recorder" "grim" "slurp" "magick" "wl-copy")
|
||||
MISSING_TOOLS=()
|
||||
|
||||
for tool in "${REQUIRED_TOOLS[@]}"; do
|
||||
if ! command -v "$tool" &> /dev/null; then
|
||||
MISSING_TOOLS+=("$tool")
|
||||
fi
|
||||
done
|
||||
|
||||
if [ ${#MISSING_TOOLS[@]} -ne 0 ]; then
|
||||
# 构建错误信息
|
||||
MSG="${TXT_ERR_DEP_MSG} ${MISSING_TOOLS[*]}"
|
||||
|
||||
# 尝试发送通知
|
||||
if command -v notify-send &> /dev/null; then
|
||||
notify-send -u critical "$TXT_ERR_DEP_TITLE" "$MSG"
|
||||
else
|
||||
# 如果连 notify-send 都没有,这就很尴尬了,尝试用 echo 输出到 stderr
|
||||
echo "❌ $TXT_ERR_DEP_TITLE: $MSG" >&2
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ================= 2. Python 环境自动检测与修复 =================
|
||||
if [ ! -f "$VENV_PYTHON" ]; then
|
||||
notify-send -t 5000 "Longshot" "$TXT_MSG_INIT"
|
||||
|
||||
if [ -f "$SCRIPT_DIR/setup.sh" ]; then
|
||||
chmod +x "$SCRIPT_DIR/setup.sh"
|
||||
"$SCRIPT_DIR/setup.sh" > /tmp/longshot_setup.log 2>&1
|
||||
|
||||
if [ ! -f "$VENV_PYTHON" ]; then
|
||||
notify-send -u critical "Error" "$TXT_ERR_SETUP"
|
||||
exit 1
|
||||
else
|
||||
notify-send -t 3000 "Longshot" "$TXT_MSG_SETUP_DONE"
|
||||
fi
|
||||
else
|
||||
notify-send -u critical "Error" "$TXT_ERR_NO_SETUP"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# =================菜单工具=================
|
||||
if command -v fuzzel &> /dev/null; then
|
||||
MENU_CMD="fuzzel -d --anchor top --y-margin 20 --width 35 --lines 4"
|
||||
elif command -v wofi &> /dev/null; then
|
||||
MENU_CMD="wofi -d -i -p Longshot"
|
||||
else
|
||||
MENU_CMD="rofi -dmenu"
|
||||
fi
|
||||
|
||||
# =================主循环=================
|
||||
while true; do
|
||||
# 1. 读取当前配置
|
||||
CUR_MODE=$(cat "$FILE_MODE")
|
||||
CUR_BACKEND=$(cat "$FILE_BACKEND")
|
||||
|
||||
# 2. 动态生成 UI 文本
|
||||
CURRENT_TITLE=""
|
||||
if [ "$CUR_BACKEND" == "WF" ]; then
|
||||
CURRENT_TITLE="$TXT_TITLE_WF"
|
||||
else
|
||||
CURRENT_TITLE="$TXT_TITLE_GRIM"
|
||||
fi
|
||||
|
||||
LBL_MODE=""
|
||||
case "$CUR_MODE" in
|
||||
"PREVIEW") LBL_MODE="$TXT_ST_PRE" ;;
|
||||
"EDIT") LBL_MODE="$TXT_ST_EDIT" ;;
|
||||
"SAVE") LBL_MODE="$TXT_ST_SAVE" ;;
|
||||
esac
|
||||
|
||||
LBL_BACKEND=""
|
||||
case "$CUR_BACKEND" in
|
||||
"WF") LBL_BACKEND="$TXT_ST_WF" ;;
|
||||
"GRIM") LBL_BACKEND="$TXT_ST_GRIM" ;;
|
||||
esac
|
||||
|
||||
# 3. 显示主菜单
|
||||
OPTION_START="$TXT_START"
|
||||
OPTION_SETTING="$TXT_SETTING [$LBL_BACKEND | $LBL_MODE]"
|
||||
OPTION_EXIT="$TXT_EXIT"
|
||||
|
||||
if [[ "$MENU_CMD" == *"fuzzel"* ]] || [[ "$MENU_CMD" == *"rofi"* ]]; then
|
||||
CHOICE=$(echo -e "$OPTION_START\n$OPTION_SETTING\n$OPTION_EXIT" | $MENU_CMD -p "$CURRENT_TITLE")
|
||||
else
|
||||
CHOICE=$(echo -e "$OPTION_START\n$OPTION_SETTING\n$OPTION_EXIT" | $MENU_CMD)
|
||||
fi
|
||||
|
||||
# 4. 处理选择
|
||||
if [[ "$CHOICE" == *"$TXT_START"* ]]; then
|
||||
# === 启动后端 ===
|
||||
if [ "$CUR_BACKEND" == "WF" ]; then
|
||||
exec "$SCRIPT_DIR/longshot-wf-recorder.sh"
|
||||
else
|
||||
exec "$SCRIPT_DIR/longshot-grim.sh"
|
||||
fi
|
||||
break
|
||||
|
||||
elif [[ "$CHOICE" == *"$TXT_SETTING"* ]]; then
|
||||
# === 设置菜单循环 ===
|
||||
while true; do
|
||||
S_MODE=$(cat "$FILE_MODE")
|
||||
S_BACK=$(cat "$FILE_BACKEND")
|
||||
|
||||
D_BACK=""; [ "$S_BACK" == "WF" ] && D_BACK="$TXT_ST_WF" || D_BACK="$TXT_ST_GRIM"
|
||||
D_MODE="";
|
||||
case "$S_MODE" in
|
||||
"PREVIEW") D_MODE="$TXT_ST_PRE" ;;
|
||||
"EDIT") D_MODE="$TXT_ST_EDIT" ;;
|
||||
"SAVE") D_MODE="$TXT_ST_SAVE" ;;
|
||||
esac
|
||||
|
||||
ITEM_BACKEND="$TXT_SW_BACKEND [$D_BACK]"
|
||||
ITEM_ACTION="$TXT_SW_ACTION [$D_MODE]"
|
||||
|
||||
if [[ "$MENU_CMD" == *"fuzzel"* ]] || [[ "$MENU_CMD" == *"rofi"* ]]; then
|
||||
S_CHOICE=$(echo -e "$TXT_BACK\n$ITEM_BACKEND\n$ITEM_ACTION" | $MENU_CMD -p "$TXT_SETTING")
|
||||
else
|
||||
S_CHOICE=$(echo -e "$TXT_BACK\n$ITEM_BACKEND\n$ITEM_ACTION" | $MENU_CMD)
|
||||
fi
|
||||
|
||||
if [[ "$S_CHOICE" == *"$TXT_BACK"* ]]; then
|
||||
break
|
||||
elif [[ "$S_CHOICE" == *"$TXT_SW_BACKEND"* ]]; then
|
||||
if [ "$S_BACK" == "WF" ]; then echo "GRIM" > "$FILE_BACKEND"; else echo "WF" > "$FILE_BACKEND"; fi
|
||||
elif [[ "$S_CHOICE" == *"$TXT_SW_ACTION"* ]]; then
|
||||
if [[ "$MENU_CMD" == *"fuzzel"* ]] || [[ "$MENU_CMD" == *"rofi"* ]]; then
|
||||
A_CHOICE=$(echo -e "$TXT_ST_PRE\n$TXT_ST_EDIT\n$TXT_ST_SAVE" | $MENU_CMD -p "$TXT_PROMPT_ACTION")
|
||||
else
|
||||
A_CHOICE=$(echo -e "$TXT_ST_PRE\n$TXT_ST_EDIT\n$TXT_ST_SAVE" | $MENU_CMD)
|
||||
fi
|
||||
|
||||
if [[ "$A_CHOICE" == *"$TXT_ST_PRE"* ]]; then echo "PREVIEW" > "$FILE_MODE"; fi
|
||||
if [[ "$A_CHOICE" == *"$TXT_ST_EDIT"* ]]; then echo "EDIT" > "$FILE_MODE"; fi
|
||||
if [[ "$A_CHOICE" == *"$TXT_ST_SAVE"* ]]; then echo "SAVE" > "$FILE_MODE"; fi
|
||||
else
|
||||
exit 0
|
||||
fi
|
||||
done
|
||||
else
|
||||
exit 0
|
||||
fi
|
||||
done
|
||||
42
.config/waybar/scripts/longshot-sh/setup.sh
Executable file
42
.config/waybar/scripts/longshot-sh/setup.sh
Executable file
@@ -0,0 +1,42 @@
|
||||
#!/bin/bash
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
VENV_DIR="$SCRIPT_DIR/venv"
|
||||
|
||||
echo "🔧 初始化环境..."
|
||||
|
||||
# 1. 检查系统工具
|
||||
# 涵盖了两个方案所需的所有工具
|
||||
REQUIRED_TOOLS=("wf-recorder" "grim" "slurp" "wl-copy" "magick" "python3")
|
||||
MISSING_TOOLS=()
|
||||
|
||||
for tool in "${REQUIRED_TOOLS[@]}"; do
|
||||
if ! command -v "$tool" &> /dev/null; then
|
||||
MISSING_TOOLS+=("$tool")
|
||||
fi
|
||||
done
|
||||
|
||||
if [ ${#MISSING_TOOLS[@]} -ne 0 ]; then
|
||||
echo "⚠️ 警告: 缺少以下系统工具,请使用包管理器安装:"
|
||||
echo " ${MISSING_TOOLS[*]}"
|
||||
echo " (例如 Arch: sudo pacman -S wf-recorder grim slurp wl-clipboard imagemagick python)"
|
||||
echo " (例如 Debian/Ubuntu: sudo apt install wf-recorder grim slurp wl-clipboard imagemagick python3-venv)"
|
||||
fi
|
||||
|
||||
# 2. Python 虚拟环境
|
||||
if [ ! -d "$VENV_DIR" ]; then
|
||||
echo "📦 创建 Python 虚拟环境..."
|
||||
python3 -m venv "$VENV_DIR"
|
||||
fi
|
||||
|
||||
# 3. 安装 Python 依赖
|
||||
echo "⬇️ 安装 Python 库..."
|
||||
"$VENV_DIR/bin/pip" install --upgrade pip > /dev/null
|
||||
"$VENV_DIR/bin/pip" install opencv-python numpy > /dev/null
|
||||
|
||||
# 4. 赋予执行权限
|
||||
chmod +x "$SCRIPT_DIR/longshot.sh"
|
||||
chmod +x "$SCRIPT_DIR/longshot-wf-recorder.sh"
|
||||
chmod +x "$SCRIPT_DIR/longshot-grim.sh"
|
||||
chmod +x "$SCRIPT_DIR/stitch.py"
|
||||
|
||||
echo "✅ 完成!请运行 ./longshot.sh"
|
||||
105
.config/waybar/scripts/longshot-sh/stitch.py
Executable file
105
.config/waybar/scripts/longshot-sh/stitch.py
Executable file
@@ -0,0 +1,105 @@
|
||||
#!/usr/bin/env python3
|
||||
import cv2
|
||||
import numpy as np
|
||||
import sys
|
||||
import os
|
||||
|
||||
def stitch_video(video_path, output_path):
|
||||
if not os.path.exists(video_path):
|
||||
return
|
||||
|
||||
cap = cv2.VideoCapture(video_path)
|
||||
if not cap.isOpened():
|
||||
print("❌ 无法打开视频")
|
||||
return
|
||||
|
||||
frames = []
|
||||
ret, prev_frame = cap.read()
|
||||
if not ret: return
|
||||
|
||||
frames.append(prev_frame)
|
||||
anchor_frame = prev_frame.copy()
|
||||
|
||||
# ==========================
|
||||
# 核心参数 (手动滚动优化)
|
||||
# ==========================
|
||||
MIN_SCROLL = 2
|
||||
MATCH_CONFIDENCE = 0.5
|
||||
|
||||
# 忽略上下边缘 (防止浏览器地址栏/状态栏干扰)
|
||||
IGNORE_Y_TOP = 0.15
|
||||
IGNORE_Y_BOTTOM = 0.15
|
||||
IGNORE_X = 0.15
|
||||
|
||||
h, w, _ = anchor_frame.shape
|
||||
|
||||
# 有效特征区
|
||||
x1 = int(w * IGNORE_X)
|
||||
x2 = int(w * (1 - IGNORE_X))
|
||||
y1 = int(h * IGNORE_Y_TOP)
|
||||
template_h = int(h * 0.2)
|
||||
|
||||
print(f"⚡ 正在分析 (梯度匹配模式)...")
|
||||
|
||||
last_shift = 0
|
||||
SEARCH_WINDOW = 50
|
||||
|
||||
while True:
|
||||
ret, curr_frame = cap.read()
|
||||
if not ret: break
|
||||
|
||||
# 1. 梯度处理 (解决白底黑字问题)
|
||||
curr_gray = cv2.cvtColor(curr_frame, cv2.COLOR_BGR2GRAY)
|
||||
anchor_gray = cv2.cvtColor(anchor_frame, cv2.COLOR_BGR2GRAY)
|
||||
|
||||
curr_grad = cv2.Sobel(curr_gray, cv2.CV_8U, 0, 1, ksize=3)
|
||||
anchor_grad = cv2.Sobel(anchor_gray, cv2.CV_8U, 0, 1, ksize=3)
|
||||
|
||||
# 2. 提取模板
|
||||
template = curr_grad[y1 : y1 + template_h, x1:x2]
|
||||
roi = anchor_grad[y1:, x1:x2]
|
||||
|
||||
# 3. 匹配
|
||||
res = cv2.matchTemplate(roi, template, cv2.TM_CCOEFF_NORMED)
|
||||
|
||||
# 4. 惯性约束
|
||||
if last_shift > 0:
|
||||
mask = np.zeros_like(res)
|
||||
target_y = last_shift
|
||||
y_min = max(0, target_y - SEARCH_WINDOW)
|
||||
y_max = min(res.shape[0], target_y + SEARCH_WINDOW)
|
||||
mask[y_min:y_max, :] = 1
|
||||
res = np.multiply(res, mask)
|
||||
|
||||
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
|
||||
shift = max_loc[1]
|
||||
|
||||
# 5. 判定
|
||||
if max_val > MATCH_CONFIDENCE and shift > MIN_SCROLL and shift < (roi.shape[0] - 5):
|
||||
new_content_start_y = h - shift
|
||||
if new_content_start_y < h:
|
||||
new_part = curr_frame[new_content_start_y:, :, :]
|
||||
frames.append(new_part)
|
||||
anchor_frame = curr_frame.copy()
|
||||
|
||||
if last_shift == 0: last_shift = shift
|
||||
else: last_shift = int(last_shift * 0.6 + shift * 0.4)
|
||||
|
||||
cap.release()
|
||||
|
||||
if len(frames) > 1:
|
||||
try:
|
||||
full_image = np.vstack(frames)
|
||||
cv2.imwrite(output_path, full_image, [cv2.IMWRITE_PNG_COMPRESSION, 3])
|
||||
print(f"🎉 处理完成")
|
||||
except Exception as e:
|
||||
print(f"❌ 保存失败: {e}")
|
||||
else:
|
||||
print("⚠️ 未检测到滚动,保存第一帧")
|
||||
cv2.imwrite(output_path, frames[0])
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 3:
|
||||
print("Usage: python stitch.py <input_video> <output_image>")
|
||||
else:
|
||||
stitch_video(sys.argv[1], sys.argv[2])
|
||||
195
.config/waybar/scripts/old-longshot.sh
Executable file
195
.config/waybar/scripts/old-longshot.sh
Executable file
@@ -0,0 +1,195 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ==============================================================================
|
||||
# 1. 本地化与文案配置
|
||||
# ==============================================================================
|
||||
# 默认英文
|
||||
STR_PROMPT="Longshot> "
|
||||
STR_START="⛶ Start Selection (Width as baseline)"
|
||||
STR_CANCEL="❌ Cancel"
|
||||
STR_NEXT="📸 Capture Next (Height only)"
|
||||
STR_SAVE="💾 Save & Finish"
|
||||
STR_EDIT="🎨 Edit & Finish"
|
||||
STR_ABORT="❌ Abort"
|
||||
STR_NOTIFY_TITLE="Longshot"
|
||||
STR_NOTIFY_SAVED="Saved to"
|
||||
STR_NOTIFY_COPIED="Copied to clipboard"
|
||||
STR_ERR_DEP="Missing dependency"
|
||||
STR_ERR_MENU="Menu tool not found"
|
||||
STR_ERR_TITLE="Error"
|
||||
|
||||
# 中文检测
|
||||
if env | grep -q "zh_CN"; then
|
||||
STR_PROMPT="长截图> "
|
||||
STR_START="⛶ 开始框选(该图宽视为基准)"
|
||||
STR_CANCEL="❌ 取消"
|
||||
STR_NEXT="📸 截取下一张(只需确定高度)"
|
||||
STR_SAVE="💾 完成并保存"
|
||||
STR_EDIT="🎨 完成并编辑"
|
||||
STR_ABORT="❌ 放弃并退出"
|
||||
STR_NOTIFY_TITLE="长截图完成"
|
||||
STR_NOTIFY_SAVED="已保存至"
|
||||
STR_NOTIFY_COPIED="并已复制到剪贴板"
|
||||
STR_ERR_DEP="缺少核心依赖"
|
||||
STR_ERR_MENU="未找到菜单工具 (fuzzel/rofi/wofi)"
|
||||
STR_ERR_TITLE="错误"
|
||||
fi
|
||||
|
||||
# ==============================================================================
|
||||
# 2. 用户配置与安全初始化
|
||||
# ==============================================================================
|
||||
SAVE_DIR="$HOME/Pictures/Screenshots/longshots"
|
||||
TMP_BASE_NAME="niri_longshot"
|
||||
TMP_DIR="/tmp/${TMP_BASE_NAME}_$(date +%s)"
|
||||
FILENAME="longshot_$(date +%Y%m%d_%H%M%S).png"
|
||||
RESULT_PATH="$SAVE_DIR/$FILENAME"
|
||||
TMP_STITCHED="$TMP_DIR/stitched_temp.png"
|
||||
|
||||
# --- [保险措施 1] 启动时清理陈旧垃圾 ---
|
||||
# 查找 /tmp 下名字包含 niri_longshot 且修改时间超过 10 分钟的目录并删除
|
||||
# 这可以防止因断电或 kill -9 导致的垃圾堆积,同时不影响刚启动的其他实例
|
||||
find /tmp -maxdepth 1 -type d -name "${TMP_BASE_NAME}_*" -mmin +10 -exec rm -rf {} + 2>/dev/null
|
||||
|
||||
# 创建目录
|
||||
mkdir -p "$SAVE_DIR"
|
||||
mkdir -p "$TMP_DIR"
|
||||
|
||||
# --- [保险措施 2] 增强型 Trap ---
|
||||
# 无论脚本是正常退出 (EXIT)、被 Ctrl+C (SIGINT)、还是被 kill (SIGTERM),都执行清理
|
||||
# 这里的逻辑是:只要脚本进程结束,就删掉本次生成的 TMP_DIR
|
||||
cleanup() {
|
||||
rm -rf "$TMP_DIR"
|
||||
}
|
||||
trap cleanup EXIT SIGINT SIGTERM SIGHUP
|
||||
|
||||
# ==============================================================================
|
||||
# 3. 依赖与工具探测
|
||||
# ==============================================================================
|
||||
CMD_FUZZEL="fuzzel -d --anchor=top --y-margin=10 --lines=5 --width=45 --prompt=$STR_PROMPT"
|
||||
CMD_ROFI="rofi -dmenu -i -p $STR_PROMPT -l 5"
|
||||
CMD_WOFI="wofi --dmenu --lines 5 --prompt $STR_PROMPT"
|
||||
|
||||
REQUIRED_CMDS=("grim" "slurp" "magick" "notify-send")
|
||||
for cmd in "${REQUIRED_CMDS[@]}"; do
|
||||
if ! command -v "$cmd" &> /dev/null; then
|
||||
PKG_NAME="$cmd"
|
||||
[[ "$cmd" == "magick" ]] && PKG_NAME="imagemagick"
|
||||
notify-send -u critical "$STR_ERR_TITLE" "$STR_ERR_DEP: $cmd\nInstall: sudo pacman -S $PKG_NAME"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
EDITOR_CMD=""
|
||||
if command -v satty &> /dev/null; then EDITOR_CMD="satty --filename";
|
||||
elif command -v swappy &> /dev/null; then EDITOR_CMD="swappy -f"; fi
|
||||
|
||||
MENU_CMD=""
|
||||
if command -v fuzzel &> /dev/null; then MENU_CMD="$CMD_FUZZEL"
|
||||
elif command -v rofi &> /dev/null; then MENU_CMD="$CMD_ROFI"
|
||||
elif command -v wofi &> /dev/null; then MENU_CMD="$CMD_WOFI"
|
||||
else
|
||||
notify-send -u critical "$STR_ERR_TITLE" "$STR_ERR_MENU"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
function show_menu() { echo -e "$1" | $MENU_CMD; }
|
||||
|
||||
# ==============================================================================
|
||||
# 步骤 1: 第一张截图 (基准)
|
||||
# ==============================================================================
|
||||
|
||||
SELECTION=$(show_menu "$STR_START\n$STR_CANCEL")
|
||||
if [[ "$SELECTION" != "$STR_START" ]]; then exit 0; fi
|
||||
|
||||
sleep 0.2
|
||||
GEO_1=$(slurp)
|
||||
# 如果第一步被 Super+Q 杀掉 slurp,GEO_1 为空,脚本会在此退出并触发 cleanup
|
||||
if [ -z "$GEO_1" ]; then exit 0; fi
|
||||
|
||||
IFS=', x' read -r FIX_X FIX_Y FIX_W FIX_H <<< "$GEO_1"
|
||||
grim -g "$GEO_1" "$TMP_DIR/001.png"
|
||||
|
||||
# ==============================================================================
|
||||
# 步骤 2: 循环截图
|
||||
# ==============================================================================
|
||||
INDEX=2
|
||||
SAVE_MODE=""
|
||||
|
||||
while true; do
|
||||
MENU_OPTIONS="$STR_NEXT\n$STR_SAVE"
|
||||
if [[ -n "$EDITOR_CMD" ]]; then MENU_OPTIONS="$MENU_OPTIONS\n$STR_EDIT"; fi
|
||||
MENU_OPTIONS="$MENU_OPTIONS\n$STR_ABORT"
|
||||
|
||||
# 如果此时 Super+Q 杀掉了 Fuzzel,ACTION 为空
|
||||
ACTION=$(show_menu "$MENU_OPTIONS")
|
||||
|
||||
case "$ACTION" in
|
||||
*"📸"*)
|
||||
sleep 0.2
|
||||
GEO_NEXT=$(slurp)
|
||||
|
||||
# 如果此时 Super+Q 杀掉 Slurp,GEO_NEXT 为空,回到菜单
|
||||
if [ -z "$GEO_NEXT" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
IFS=', x' read -r _TEMP_X NEW_Y _TEMP_W NEW_H <<< "$GEO_NEXT"
|
||||
FINAL_GEO="${FIX_X},${NEW_Y} ${FIX_W}x${NEW_H}"
|
||||
|
||||
IMG_NAME="$(printf "%03d" $INDEX).png"
|
||||
grim -g "$FINAL_GEO" "$TMP_DIR/$IMG_NAME"
|
||||
((INDEX++))
|
||||
;;
|
||||
|
||||
*"💾"*)
|
||||
SAVE_MODE="save"
|
||||
break
|
||||
;;
|
||||
|
||||
*"🎨"*)
|
||||
SAVE_MODE="edit"
|
||||
break
|
||||
;;
|
||||
|
||||
*"❌"*)
|
||||
exit 0
|
||||
;;
|
||||
|
||||
*)
|
||||
# Fuzzel 被 Super+Q 关闭,ACTION 为空,进入这里
|
||||
# 直接 Break 跳出循环,进入保存/拼接流程 (防止误操作导致丢失)
|
||||
# 或者如果你想放弃,这里改成 exit 0
|
||||
break
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# ==============================================================================
|
||||
# 步骤 3: 拼接与后续处理
|
||||
# ==============================================================================
|
||||
COUNT=$(ls "$TMP_DIR"/*.png 2>/dev/null | wc -l)
|
||||
|
||||
if [ "$COUNT" -gt 0 ]; then
|
||||
magick "$TMP_DIR"/*.png -append "$TMP_STITCHED"
|
||||
|
||||
if [[ "$SAVE_MODE" == "edit" ]]; then
|
||||
$EDITOR_CMD "$TMP_STITCHED"
|
||||
fi
|
||||
|
||||
# 只要有保存意向 (SAVE_MODE不为空),或者是因为意外退出且至少有图
|
||||
# 如果你是"意外退出菜单",默认是不保存的 (SAVE_MODE为空)
|
||||
# 这里我们只在显式选择保存/编辑时才保存
|
||||
if [[ -n "$SAVE_MODE" ]]; then
|
||||
mv "$TMP_STITCHED" "$RESULT_PATH"
|
||||
|
||||
COPY_MSG=""
|
||||
if command -v wl-copy &> /dev/null; then
|
||||
wl-copy < "$RESULT_PATH"
|
||||
COPY_MSG="$STR_NOTIFY_COPIED"
|
||||
fi
|
||||
|
||||
notify-send -i "$RESULT_PATH" "$STR_NOTIFY_TITLE" "$STR_NOTIFY_SAVED $FILENAME\n$COPY_MSG"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 脚本结束,触发 Trap 清理 TMP_DIR
|
||||
653
.config/waybar/scripts/power-screenshot.sh
Executable file
653
.config/waybar/scripts/power-screenshot.sh
Executable file
@@ -0,0 +1,653 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
########################
|
||||
# 配置区域(直接改这里)
|
||||
########################
|
||||
|
||||
NIRI_CONFIG="$HOME/.config/niri/config.kdl" # niri 配置文件
|
||||
|
||||
SHOTEDITOR_DEFAULT="satty" # 默认截图编辑器:swappy 或 satty
|
||||
COPY_CMD="wl-copy" # 复制到剪贴板的命令
|
||||
|
||||
# 菜单程序,按你实际使用的启动器改
|
||||
# wofi 示例: MENU_CMD='wofi -d'
|
||||
# rofi 示例: MENU_CMD='rofi -dmenu'
|
||||
MENU_CMD='fuzzel --dmenu'
|
||||
|
||||
# 图片目录
|
||||
PICTURES_DIR="$(xdg-user-dir PICTURES 2>/dev/null || echo "$HOME/Pictures")"
|
||||
SCREEN_DIR="$PICTURES_DIR/Screenshots"
|
||||
|
||||
########################
|
||||
# 本地化(中/英)
|
||||
########################
|
||||
|
||||
LOCALE="${LC_MESSAGES:-${LANG:-en}}"
|
||||
if [[ "$LOCALE" == zh* ]]; then
|
||||
# 通用
|
||||
LABEL_CANCEL="取消"
|
||||
LABEL_SETTINGS="设置"
|
||||
LABEL_EDIT_YES="编辑"
|
||||
LABEL_EDIT_NO="不编辑"
|
||||
|
||||
# Niri 模式
|
||||
LABEL_NIRI_FULL="全屏"
|
||||
LABEL_NIRI_WINDOW="窗口"
|
||||
LABEL_NIRI_REGION="选取区域"
|
||||
|
||||
# Grim 模式
|
||||
LABEL_GRIM_FULL="全屏"
|
||||
LABEL_GRIM_REGION="选取区域"
|
||||
|
||||
# 设置菜单
|
||||
LABEL_SETTINGS_EDITOR="截图工具"
|
||||
LABEL_SETTINGS_BACKEND="后端模式"
|
||||
LABEL_BACKEND_AUTO="自动(检测 Niri)"
|
||||
LABEL_BACKEND_GRIM="仅 Grim+slurp"
|
||||
LABEL_BACK="返回"
|
||||
|
||||
# 编辑开关显示
|
||||
LABEL_EDIT_STATE_ON="编辑:开启"
|
||||
LABEL_EDIT_STATE_OFF="编辑:关闭"
|
||||
|
||||
# 提示文字
|
||||
PROMPT_MAIN="请选择截图模式"
|
||||
PROMPT_SETTINGS="设置 / 更改选项"
|
||||
PROMPT_EDITOR="请选择截图编辑工具"
|
||||
PROMPT_BACKEND="请选择后端模式"
|
||||
else
|
||||
LABEL_CANCEL="Cancel"
|
||||
LABEL_SETTINGS="Settings"
|
||||
LABEL_EDIT_YES="Edit"
|
||||
LABEL_EDIT_NO="No edit"
|
||||
|
||||
LABEL_NIRI_FULL="Fullscreen"
|
||||
LABEL_NIRI_WINDOW="Window"
|
||||
LABEL_NIRI_REGION="Region"
|
||||
|
||||
LABEL_GRIM_FULL="Fullscreen"
|
||||
LABEL_GRIM_REGION="Select area"
|
||||
|
||||
LABEL_SETTINGS_EDITOR="Screenshot tool"
|
||||
LABEL_SETTINGS_BACKEND="Backend mode"
|
||||
LABEL_BACKEND_AUTO="Auto (detect Niri)"
|
||||
LABEL_BACKEND_GRIM="Grim+slurp only"
|
||||
LABEL_BACK="Back"
|
||||
|
||||
LABEL_EDIT_STATE_ON="Edit: ON"
|
||||
LABEL_EDIT_STATE_OFF="Edit: OFF"
|
||||
|
||||
PROMPT_MAIN="Choose screenshot mode"
|
||||
PROMPT_SETTINGS="Settings / Options"
|
||||
PROMPT_EDITOR="Choose screenshot editor"
|
||||
PROMPT_BACKEND="Choose backend mode"
|
||||
fi
|
||||
|
||||
########################
|
||||
# 持久化配置路径 (已修改为 .cache 目录)
|
||||
########################
|
||||
|
||||
XDG_CACHE_HOME="${XDG_CACHE_HOME:-$HOME/.cache}"
|
||||
# 旧路径: CONFIG_DIR="$HOME/.config/waybar/waybar-shot"
|
||||
CONFIG_DIR="$XDG_CACHE_HOME/waybar-power-screenshot-sh"
|
||||
|
||||
BACKEND_FILE="$CONFIG_DIR/backend"
|
||||
EDITOR_FILE="$CONFIG_DIR/editor"
|
||||
EDIT_MODE_FILE="$CONFIG_DIR/edit_mode" # yes / no
|
||||
|
||||
# 确保新的缓存目录存在
|
||||
mkdir -p "$CONFIG_DIR"
|
||||
|
||||
########################
|
||||
# 通用工具函数
|
||||
########################
|
||||
|
||||
menu() {
|
||||
printf '%s\n' "$@" | eval "$MENU_CMD" 2>/dev/null || true
|
||||
}
|
||||
|
||||
menu_prompt() {
|
||||
local prompt="$1"
|
||||
shift
|
||||
local esc_prompt="${prompt//\"/\\\"}"
|
||||
printf '%s\n' "$@" | eval "$MENU_CMD --prompt \"$esc_prompt\"" 2>/dev/null || true
|
||||
}
|
||||
|
||||
load_backend_mode() {
|
||||
local mode
|
||||
if [[ -n "${SHOT_BACKEND:-}" ]]; then
|
||||
mode="$SHOT_BACKEND"
|
||||
elif [[ -f "$BACKEND_FILE" ]]; then
|
||||
mode="$(<"$BACKEND_FILE")"
|
||||
else
|
||||
mode="auto"
|
||||
fi
|
||||
case "$mode" in
|
||||
auto|grim|niri) ;;
|
||||
*) mode="auto" ;;
|
||||
esac
|
||||
printf '%s\n' "$mode"
|
||||
}
|
||||
|
||||
save_backend_mode() {
|
||||
local mode="$1"
|
||||
# 由于脚本开始时已创建,这里只需要保证文件写入成功
|
||||
printf '%s\n' "$mode" >"$BACKEND_FILE"
|
||||
}
|
||||
|
||||
load_editor() {
|
||||
local ed
|
||||
if [[ -n "${SHOTEDITOR:-}" ]]; then
|
||||
ed="$SHOTEDITOR"
|
||||
elif [[ -f "$EDITOR_FILE" ]]; then
|
||||
ed="$(<"$EDITOR_FILE")"
|
||||
else
|
||||
ed="$SHOTEDITOR_DEFAULT"
|
||||
fi
|
||||
|
||||
ed="${ed,,}"
|
||||
case "$ed" in
|
||||
swappy|satty) ;;
|
||||
*) ed="$SHOTEDITOR_DEFAULT" ;;
|
||||
esac
|
||||
printf '%s\n' "$ed"
|
||||
}
|
||||
|
||||
save_editor() {
|
||||
local ed="$1"
|
||||
# 由于脚本开始时已创建,这里只需要保证文件写入成功
|
||||
printf '%s\n' "$ed" >"$EDITOR_FILE"
|
||||
}
|
||||
|
||||
load_edit_mode() {
|
||||
local v="yes"
|
||||
if [[ -f "$EDIT_MODE_FILE" ]]; then
|
||||
v="$(<"$EDIT_MODE_FILE")"
|
||||
fi
|
||||
case "$v" in
|
||||
yes|no) ;;
|
||||
*) v="yes" ;; # 默认:编辑开启
|
||||
esac
|
||||
printf '%s\n' "$v"
|
||||
}
|
||||
|
||||
save_edit_mode() {
|
||||
local v="$1"
|
||||
# 由于脚本开始时已创建,这里只需要保证文件写入成功
|
||||
printf '%s\n' "$v" >"$EDIT_MODE_FILE"
|
||||
}
|
||||
|
||||
detect_backend() {
|
||||
case "$BACKEND_MODE" in
|
||||
niri) echo "niri" ;;
|
||||
grim) echo "grim" ;;
|
||||
auto|*)
|
||||
if command -v niri >/dev/null 2>&1 && pgrep -x niri >/dev/null 2>&1; then
|
||||
echo "niri"
|
||||
else
|
||||
echo "grim"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
choose_editor() {
|
||||
local choice
|
||||
choice="$(menu_prompt "$PROMPT_EDITOR" "swappy" "satty" "$LABEL_BACK")"
|
||||
case "$choice" in
|
||||
swappy|Swappy)
|
||||
SHOTEDITOR="swappy"
|
||||
save_editor "$SHOTEDITOR"
|
||||
;;
|
||||
satty|Satty)
|
||||
SHOTEDITOR="satty"
|
||||
save_editor "$SHOTEDITOR"
|
||||
;;
|
||||
*) : ;;
|
||||
esac
|
||||
}
|
||||
|
||||
choose_backend_mode() {
|
||||
local choice
|
||||
choice="$(menu_prompt "$PROMPT_BACKEND" "$LABEL_BACKEND_AUTO" "$LABEL_BACKEND_GRIM" "$LABEL_BACK")"
|
||||
case "$choice" in
|
||||
"$LABEL_BACKEND_AUTO")
|
||||
BACKEND_MODE="auto"
|
||||
save_backend_mode "$BACKEND_MODE"
|
||||
;;
|
||||
"$LABEL_BACKEND_GRIM")
|
||||
BACKEND_MODE="grim"
|
||||
save_backend_mode "$BACKEND_MODE"
|
||||
;;
|
||||
*) : ;;
|
||||
esac
|
||||
}
|
||||
|
||||
settings_menu() {
|
||||
while :; do
|
||||
local backend_desc editor_line backend_line choice
|
||||
|
||||
if [[ -n "${SHOT_BACKEND:-}" ]]; then
|
||||
backend_desc="${BACKEND_MODE} (env)"
|
||||
else
|
||||
if [[ "$BACKEND_MODE" == "grim" ]]; then
|
||||
backend_desc="$LABEL_BACKEND_GRIM"
|
||||
elif [[ "$BACKEND_MODE" == "niri" ]]; then
|
||||
backend_desc="niri"
|
||||
else
|
||||
backend_desc="$LABEL_BACKEND_AUTO"
|
||||
fi
|
||||
fi
|
||||
|
||||
editor_line="$LABEL_SETTINGS_EDITOR: $SHOTEDITOR"
|
||||
backend_line="$LABEL_SETTINGS_BACKEND: $backend_desc"
|
||||
|
||||
choice="$(menu_prompt "$PROMPT_SETTINGS" "$editor_line" "$backend_line" "$LABEL_BACK")"
|
||||
case "$choice" in
|
||||
"$editor_line") choose_editor ;;
|
||||
"$backend_line")
|
||||
if [[ -n "${SHOT_BACKEND:-}" ]]; then
|
||||
: # 环境变量强制时不改持久化
|
||||
else
|
||||
choose_backend_mode
|
||||
fi
|
||||
;;
|
||||
*) return ;; # 返回上一层
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
latest_in_dir() {
|
||||
local dir="$1"
|
||||
find "$dir" -maxdepth 1 -type f -printf '%T@ %p\n' 2>/dev/null \
|
||||
| sort -n | tail -1 | cut -d' ' -f2-
|
||||
}
|
||||
|
||||
########################
|
||||
# 剪贴板相关(Niri 编辑用)
|
||||
########################
|
||||
|
||||
clip_hash() {
|
||||
wl-paste -t image/png 2>/dev/null \
|
||||
| sha1sum 2>/dev/null \
|
||||
| cut -d' ' -f1 2>/dev/null \
|
||||
|| echo ""
|
||||
}
|
||||
|
||||
wait_clipboard_change() {
|
||||
local old new i
|
||||
old="$(clip_hash)"
|
||||
for i in {1..200}; do # 最多等 ~10 秒
|
||||
new="$(clip_hash)"
|
||||
if [[ -n "$new" && "$new" != "$old" ]]; then
|
||||
return 0
|
||||
fi
|
||||
sleep 0.05
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
########################
|
||||
# Niri 相关
|
||||
########################
|
||||
|
||||
get_niri_shot_dir() {
|
||||
[[ -f "$NIRI_CONFIG" ]] || { echo "Config not found: $NIRI_CONFIG" >&2; return 1; }
|
||||
|
||||
local line tpl dir
|
||||
line="$(
|
||||
grep -E '^[[:space:]]*screenshot-path[[:space:]]' "$NIRI_CONFIG" \
|
||||
| grep -v '^[[:space:]]*//' \
|
||||
| tail -n 1 || true
|
||||
)"
|
||||
[[ -n "$line" ]] || { echo "No screenshot-path in config" >&2; return 1; }
|
||||
|
||||
tpl="$(sed -E 's/.*screenshot-path[[:space:]]+"([^"]+)".*/\1/' <<<"$line")"
|
||||
[[ -n "$tpl" ]] || { echo "Failed to parse screenshot-path: $line" >&2; return 1; }
|
||||
|
||||
tpl="${tpl/#\~/$HOME}"
|
||||
dir="${tpl%/*}"
|
||||
|
||||
printf '%s\n' "$dir"
|
||||
}
|
||||
|
||||
# Grim 用:从文件编辑
|
||||
edit_file_image() {
|
||||
local src="$1"
|
||||
local backend="$2" # "niri" 或 "grim"
|
||||
|
||||
local dir ts dst
|
||||
|
||||
if [[ "$backend" == "niri" ]]; then
|
||||
dir="$NIRI_EDIT_DIR"
|
||||
else
|
||||
dir="$SCREEN_DIR"
|
||||
fi
|
||||
|
||||
mkdir -p "$dir"
|
||||
ts="$(date +'%Y-%m-%d_%H-%M-%S')"
|
||||
dst="$dir/$SHOTEDITOR-$ts.png"
|
||||
|
||||
case "$SHOTEDITOR" in
|
||||
satty)
|
||||
satty --filename "$src" --output-filename "$dst"
|
||||
;;
|
||||
swappy)
|
||||
swappy -f "$src" -o "$dst"
|
||||
;;
|
||||
*)
|
||||
echo "Unknown SHOTEDITOR: $SHOTEDITOR (use satty or swappy)" >&2
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ -f "$dst" ]]; then
|
||||
if [[ "$backend" == "grim" && "$src" == /tmp/* ]]; then
|
||||
rm -f "$src"
|
||||
fi
|
||||
"$COPY_CMD" < "$dst"
|
||||
fi
|
||||
}
|
||||
|
||||
# Niri 用:从剪贴板编辑
|
||||
edit_from_clipboard() {
|
||||
local backend="$1" # 目前只会传 "niri"
|
||||
|
||||
local dir ts dst
|
||||
if [[ "$backend" == "niri" ]]; then
|
||||
dir="$NIRI_EDIT_DIR"
|
||||
else
|
||||
dir="$SCREEN_DIR"
|
||||
fi
|
||||
|
||||
mkdir -p "$dir"
|
||||
ts="$(date +'%Y-%m-%d_%H-%M-%S')"
|
||||
dst="$dir/$SHOTEDITOR-$ts.png"
|
||||
|
||||
case "$SHOTEDITOR" in
|
||||
satty)
|
||||
wl-paste -t image/png 2>/dev/null | satty -f - --output-filename "$dst"
|
||||
;;
|
||||
swappy)
|
||||
wl-paste -t image/png 2>/dev/null | swappy -f - -o "$dst"
|
||||
;;
|
||||
*)
|
||||
echo "Unknown SHOTEDITOR: $SHOTEDITOR (use satty or swappy)" >&2
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ -f "$dst" ]]; then
|
||||
"$COPY_CMD" < "$dst"
|
||||
fi
|
||||
}
|
||||
|
||||
niri_capture_and_maybe_edit() {
|
||||
local mode="$1" # fullscreen / window / region
|
||||
local need_edit="$2" # yes / no
|
||||
|
||||
local action
|
||||
case "$mode" in
|
||||
fullscreen) action="screenshot-screen" ;;
|
||||
window) action="screenshot-window" ;;
|
||||
region) action="screenshot" ;;
|
||||
*) return 0 ;;
|
||||
esac
|
||||
|
||||
# 不编辑:用目录里的最新文件判断 screenshot 完成
|
||||
if [[ "$need_edit" != "yes" ]]; then
|
||||
local before shot
|
||||
before="$(latest_in_dir "$NIRI_SHOT_DIR" || true)"
|
||||
|
||||
niri msg action "$action"
|
||||
|
||||
while :; do
|
||||
shot="$(latest_in_dir "$NIRI_SHOT_DIR" || true)"
|
||||
if [[ -z "$before" && -n "$shot" ]] || \
|
||||
[[ -n "$before" && -n "$shot" && "$shot" != "$before" ]]; then
|
||||
break
|
||||
fi
|
||||
sleep 0.05
|
||||
done
|
||||
return 0
|
||||
fi
|
||||
|
||||
# 编辑:基于剪贴板
|
||||
niri msg action "$action"
|
||||
|
||||
if ! wait_clipboard_change; then
|
||||
echo "等待剪贴板中的截图超时" >&2
|
||||
return 0
|
||||
fi
|
||||
|
||||
edit_from_clipboard "niri"
|
||||
return 0
|
||||
}
|
||||
|
||||
run_niri_flow() {
|
||||
NIRI_SHOT_DIR="$(get_niri_shot_dir)" || return 0
|
||||
NIRI_EDIT_DIR="$NIRI_SHOT_DIR/Edited"
|
||||
mkdir -p "$NIRI_SHOT_DIR" "$NIRI_EDIT_DIR"
|
||||
|
||||
while :; do
|
||||
local choice mode edit_mode edit_label
|
||||
|
||||
edit_mode="$(load_edit_mode)"
|
||||
if [[ "$edit_mode" == "yes" ]]; then
|
||||
edit_label="$LABEL_EDIT_STATE_ON"
|
||||
else
|
||||
edit_label="$LABEL_EDIT_STATE_OFF"
|
||||
fi
|
||||
|
||||
choice="$(menu_prompt "$PROMPT_MAIN" \
|
||||
"$LABEL_NIRI_FULL" \
|
||||
"$LABEL_NIRI_WINDOW" \
|
||||
"$LABEL_NIRI_REGION" \
|
||||
"$edit_label" \
|
||||
"$LABEL_SETTINGS" \
|
||||
"$LABEL_CANCEL"
|
||||
)"
|
||||
|
||||
[[ -z "$choice" || "$choice" == "$LABEL_CANCEL" ]] && return 2
|
||||
|
||||
case "$choice" in
|
||||
"$LABEL_NIRI_FULL") mode="fullscreen" ;;
|
||||
"$LABEL_NIRI_WINDOW") mode="window" ;;
|
||||
"$LABEL_NIRI_REGION") mode="region" ;;
|
||||
"$edit_label")
|
||||
if [[ "$edit_mode" == "yes" ]]; then
|
||||
save_edit_mode "no"
|
||||
else
|
||||
save_edit_mode "yes"
|
||||
fi
|
||||
continue
|
||||
;;
|
||||
"$LABEL_SETTINGS")
|
||||
# 进入设置菜单(可以在里面改 editor / backend / edit-mode)
|
||||
settings_menu
|
||||
|
||||
# 立即重新加载持久化配置,使修改即时生效
|
||||
SHOTEDITOR="$(load_editor)"
|
||||
BACKEND_MODE="$(load_backend_mode)"
|
||||
|
||||
# 检查后端是否被改动;若改动则通知上层切换后端
|
||||
NEW_BACKEND="$(detect_backend)"
|
||||
if [[ "$NEW_BACKEND" != "niri" ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
continue
|
||||
;;
|
||||
*)
|
||||
return 2
|
||||
;;
|
||||
esac
|
||||
|
||||
edit_mode="$(load_edit_mode)"
|
||||
if [[ "$edit_mode" == "yes" ]]; then
|
||||
niri_capture_and_maybe_edit "$mode" "yes"
|
||||
else
|
||||
niri_capture_and_maybe_edit "$mode" "no"
|
||||
fi
|
||||
|
||||
# 截图完成后退出(主循环会根据返回码决定是否结束脚本)
|
||||
return 0
|
||||
done
|
||||
}
|
||||
|
||||
########################
|
||||
# Grim + slurp 相关
|
||||
########################
|
||||
|
||||
grim_capture_and_maybe_edit() {
|
||||
local mode="$1" # fullscreen / region
|
||||
local need_edit="$2" # yes / no
|
||||
|
||||
mkdir -p "$SCREEN_DIR"
|
||||
|
||||
local ts shot geo
|
||||
ts="$(date +'%Y-%m-%d_%H-%M-%S')"
|
||||
|
||||
if [[ "$need_edit" == "yes" ]]; then
|
||||
# 编辑模式:原图在 /tmp,用完删,只保留编辑后的图
|
||||
shot="/tmp/waybar-shot-$ts.png"
|
||||
|
||||
case "$mode" in
|
||||
fullscreen)
|
||||
grim "$shot"
|
||||
;;
|
||||
region)
|
||||
geo="$(slurp 2>/dev/null)" || return 0
|
||||
grim -g "$geo" "$shot"
|
||||
;;
|
||||
*)
|
||||
return 0 ;;
|
||||
esac
|
||||
|
||||
edit_file_image "$shot" "grim"
|
||||
return 0
|
||||
else
|
||||
# 不编辑:原图保存到 Screenshots
|
||||
shot="$SCREEN_DIR/Screenshot_$ts.png"
|
||||
|
||||
case "$mode" in
|
||||
fullscreen)
|
||||
grim "$shot"
|
||||
;;
|
||||
region)
|
||||
geo="$(slurp 2>/dev/null)" || return 0
|
||||
grim -g "$geo" "$shot"
|
||||
;;
|
||||
*)
|
||||
return 0 ;;
|
||||
esac
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
run_grim_flow() {
|
||||
mkdir -p "$SCREEN_DIR"
|
||||
|
||||
while :; do
|
||||
local choice mode edit_mode edit_label
|
||||
|
||||
edit_mode="$(load_edit_mode)"
|
||||
if [[ "$edit_mode" == "yes" ]]; then
|
||||
edit_label="$LABEL_EDIT_STATE_ON"
|
||||
else
|
||||
edit_label="$LABEL_EDIT_STATE_OFF"
|
||||
fi
|
||||
|
||||
choice="$(menu_prompt "$PROMPT_MAIN" \
|
||||
"$LABEL_GRIM_FULL" \
|
||||
"$LABEL_GRIM_REGION" \
|
||||
"$edit_label" \
|
||||
"$LABEL_SETTINGS" \
|
||||
"$LABEL_CANCEL"
|
||||
)"
|
||||
|
||||
[[ -z "$choice" || "$choice" == "$LABEL_CANCEL" ]] && return 2
|
||||
|
||||
case "$choice" in
|
||||
"$LABEL_GRIM_FULL") mode="fullscreen" ;;
|
||||
"$LABEL_GRIM_REGION") mode="region" ;;
|
||||
"$edit_label")
|
||||
if [[ "$edit_mode" == "yes" ]]; then
|
||||
save_edit_mode "no"
|
||||
else
|
||||
save_edit_mode "yes"
|
||||
fi
|
||||
continue
|
||||
;;
|
||||
"$LABEL_SETTINGS")
|
||||
settings_menu
|
||||
|
||||
# 立即重新加载持久化配置,使修改即时生效
|
||||
SHOTEDITOR="$(load_editor)"
|
||||
BACKEND_MODE="$(load_backend_mode)"
|
||||
|
||||
NEW_BACKEND="$(detect_backend)"
|
||||
if [[ "$NEW_BACKEND" != "grim" ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
continue
|
||||
;;
|
||||
*)
|
||||
return 2
|
||||
;;
|
||||
esac
|
||||
|
||||
edit_mode="$(load_edit_mode)"
|
||||
if [[ "$edit_mode" == "yes" ]]; then
|
||||
grim_capture_and_maybe_edit "$mode" "yes"
|
||||
else
|
||||
grim_capture_and_maybe_edit "$mode" "no"
|
||||
fi
|
||||
|
||||
return 0
|
||||
done
|
||||
}
|
||||
|
||||
########################
|
||||
# 入口(主循环)
|
||||
########################
|
||||
|
||||
while :; do
|
||||
BACKEND_MODE="$(load_backend_mode)"
|
||||
SHOTEDITOR="$(load_editor)"
|
||||
|
||||
BACKEND="$(detect_backend)"
|
||||
|
||||
case "$BACKEND" in
|
||||
niri)
|
||||
rc=0
|
||||
run_niri_flow || rc=$?
|
||||
if [[ "$rc" -eq 0 ]]; then
|
||||
exit 0
|
||||
elif [[ "$rc" -eq 1 ]]; then
|
||||
# 后端切换:继续主循环以根据新后端重试
|
||||
continue
|
||||
else
|
||||
# 取消或其他:退出
|
||||
exit 0
|
||||
fi
|
||||
;;
|
||||
grim)
|
||||
rc=0
|
||||
run_grim_flow || rc=$?
|
||||
if [[ "$rc" -eq 0 ]]; then
|
||||
exit 0
|
||||
elif [[ "$rc" -eq 1 ]]; then
|
||||
continue
|
||||
else
|
||||
exit 0
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
run_grim_flow
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
done
|
||||
3
.config/waybar/scripts/screenshot.sh
Executable file
3
.config/waybar/scripts/screenshot.sh
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
grim -g "$(slurp)" - | wl-copy
|
||||
16
.config/waybar/scripts/toggle-bluetooth.sh
Executable file
16
.config/waybar/scripts/toggle-bluetooth.sh
Executable file
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env bash
|
||||
# 检查蓝牙的 rfkill 状态
|
||||
# 'rfkill list bluetooth' 会输出蓝牙设备的信息
|
||||
# 'grep -q "Soft blocked: yes"' 在输出中安静地 (-q) 查找 "Soft blocked: yes" 字符串
|
||||
|
||||
if rfkill list bluetooth | grep -q "Soft blocked: yes"; then
|
||||
# 如果找到了 "Soft blocked: yes" (说明蓝牙被软屏蔽了)
|
||||
# 则执行 unblock 命令来解锁
|
||||
rfkill unblock bluetooth
|
||||
# (可选) 发送一个通知,提供操作反馈
|
||||
else
|
||||
# 如果没有找到 "Soft blocked: yes" (说明蓝牙是开启的)
|
||||
# 则执行 block 命令来屏蔽
|
||||
rfkill block bluetooth
|
||||
# (可选) 发送通知
|
||||
fi
|
||||
791
.config/waybar/scripts/wf-recorder.sh
Executable file
791
.config/waybar/scripts/wf-recorder.sh
Executable file
@@ -0,0 +1,791 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
# ================== Runtime state & Persistent Config ==================
|
||||
APP="wf-recorder"
|
||||
|
||||
# --- 运行时状态 (应在每次会话结束时消失, 遵循 XDG_RUNTIME_DIR) ---
|
||||
RUNTIME_DIR="${XDG_RUNTIME_DIR:-/run/user/$UID}"
|
||||
STATE_DIR="$RUNTIME_DIR/wfrec"
|
||||
|
||||
PIDFILE="$STATE_DIR/pid"
|
||||
STARTFILE="$STATE_DIR/start"
|
||||
SAVEPATH_FILE="$STATE_DIR/save_path"
|
||||
MODEFILE="$STATE_DIR/mode" # full/region -> tooltip
|
||||
GIF_MARKER="$STATE_DIR/is_gif" # [NEW] 标记当前录制是否为 GIF 模式
|
||||
TICKPIDFILE="$STATE_DIR/tickpid"
|
||||
WAYBAR_PIDS_CACHE="$STATE_DIR/waybar.pids"
|
||||
|
||||
|
||||
# --- 持久性配置 (缓存/设置, 存放在 .cache/ 下, 遵循 XDG_CACHE_HOME) ---
|
||||
XDG_CACHE_HOME="${XDG_CACHE_HOME:-$HOME/.cache}"
|
||||
CONFIG_DIR="$XDG_CACHE_HOME/wf-recorder-sh" # 目标目录 .cache/wf-recorder-sh
|
||||
|
||||
CFG_CODEC="$CONFIG_DIR/codec"
|
||||
CFG_FPS="$CONFIG_DIR/framerate"
|
||||
CFG_AUDIO="$CONFIG_DIR/audio"
|
||||
CFG_DRM="$CONFIG_DIR/drm_device"
|
||||
CFG_EXT="$CONFIG_DIR/container_ext" # persisted file format (auto/mp4/mkv/webm)
|
||||
|
||||
# 创建所需的目录
|
||||
mkdir -p "$STATE_DIR"
|
||||
mkdir -p "$CONFIG_DIR"
|
||||
|
||||
# Hold chosen mode
|
||||
MODE_DECIDED=""
|
||||
IS_GIF_MODE="false" # [NEW] 临时变量
|
||||
|
||||
# ================== Tunables (ENV overridable) ==================
|
||||
# defaults — 默认使用 CPU 编码 (libx264)
|
||||
_DEFAULT_CODEC="libx264"
|
||||
_DEFAULT_FRAMERATE=""
|
||||
_DEFAULT_AUDIO="on"
|
||||
_DEFAULT_SAVE_EXT="auto" # auto/mp4/mkv/webm
|
||||
|
||||
# --- [NEW] GIF 配置区域 ---
|
||||
GIF_WIDTH=720
|
||||
GIF_FPS=30
|
||||
GIF_DITHER_MODE="bayer:bayer_scale=5"
|
||||
GIF_STATS_MODE="diff"
|
||||
# -------------------------
|
||||
|
||||
# load persisted settings if exist
|
||||
codec_from_file=$(cat "$CFG_CODEC" 2>/dev/null || true)
|
||||
fps_from_file=$(cat "$CFG_FPS" 2>/dev/null || true)
|
||||
audio_from_file=$(cat "$CFG_AUDIO" 2>/dev/null || true)
|
||||
drm_from_file=$(cat "$CFG_DRM" 2>/dev/null || true)
|
||||
ext_from_file=$(cat "$CFG_EXT" 2>/dev/null || true)
|
||||
|
||||
# priority: ENV > persisted > default
|
||||
CODEC="${CODEC:-${codec_from_file:-$_DEFAULT_CODEC}}"
|
||||
FRAMERATE="${FRAMERATE:-${fps_from_file:-$_DEFAULT_FRAMERATE}}"
|
||||
AUDIO="${AUDIO:-${audio_from_file:-$_DEFAULT_AUDIO}}"
|
||||
DRM_DEVICE="${DRM_DEVICE:-${drm_from_file:-}}"
|
||||
SAVE_EXT="${SAVE_EXT:-${ext_from_file:-$_DEFAULT_SAVE_EXT}}"
|
||||
|
||||
TITLE="${TITLE:-}"
|
||||
SAVE_DIR_ENV="${SAVE_DIR:-}"
|
||||
SAVE_SUBDIR_FS="${SAVE_SUBDIR_FS:-fullscreen}"
|
||||
|
||||
OUTPUT="${OUTPUT:-}" # e.g. eDP-1 / DP-2
|
||||
OUTPUT_SELECT="${OUTPUT_SELECT:-auto}" # off|auto|menu
|
||||
MENU_TITLE_OUTPUT="${MENU_TITLE_OUTPUT:-}"
|
||||
MENU_BACKEND="${MENU_BACKEND:-auto}" # auto|fuzzel|wofi|rofi|bemenu|fzf|term
|
||||
|
||||
RECORD_MODE="${RECORD_MODE:-ask}" # ask|full|region
|
||||
MODE_MENU_TITLE="${MODE_MENU_TITLE:-Select recording mode}"
|
||||
REC_AREA="${REC_AREA:-}" # "x,y WIDTHxHEIGHT" (optional)
|
||||
GEOM_IN_NAME="${GEOM_IN_NAME:-off}"
|
||||
|
||||
WAYBAR_POKE="${WAYBAR_POKE:-on}"
|
||||
WAYBAR_SIG="${WAYBAR_SIG:-9}"
|
||||
ICON_REC="${ICON_REC:-⏺}"
|
||||
ICON_IDLE="${ICON_IDLE:-}"
|
||||
|
||||
PKILL_AFTER_STOP="${PKILL_AFTER_STOP:-on}"
|
||||
|
||||
# DEBUG: 若设为 on,则在前台运行 wf-recorder,并把输出直接显示到终端(仅终端,不写文件)
|
||||
DEBUG="${DEBUG:-off}"
|
||||
|
||||
# ================== Utils ==================
|
||||
has() { command -v "$1" >/dev/null 2>&1; }
|
||||
|
||||
lang_code() {
|
||||
local l="${LC_MESSAGES:-${LANG:-en}}"
|
||||
l="${l,,}"; l="${l%%.*}"; l="${l%%-*}"; l="${l%%_*}"
|
||||
case "$l" in zh|zh-cn|zh-tw|zh-hk) echo zh ;; ja|jp) echo ja ;; *) echo en ;; esac
|
||||
}
|
||||
|
||||
msg() {
|
||||
local id="$1"; shift
|
||||
case "$(lang_code)" in
|
||||
zh)
|
||||
case "$id" in
|
||||
err_wf_not_found) printf "未找到 wf-recorder" ;;
|
||||
err_need_slurp) printf "需要 slurp 以进行区域选择" ;;
|
||||
err_need_ffmpeg) printf "GIF 转换需要 ffmpeg,但未找到。" ;;
|
||||
warn_drm_ignored) printf "警告:DRM_DEVICE=%s 不存在或不可读,将忽略。" "$@" ;;
|
||||
warn_invalid_fps) printf "警告:FRAMERATE=\"%s\" 非法,已忽略。" "$@" ;;
|
||||
warn_render_unreadable) printf "警告:无效的 render 节点:%s" "$@" ;;
|
||||
cancel_no_mode) printf "已取消:未选择录制模式。" ;;
|
||||
cancel_no_output) printf "已取消:未选择输出。" ;;
|
||||
cancel_no_region) printf "已取消:未选择区域。" ;;
|
||||
warn_multi_outputs_cancel) printf "检测到多个输出但未选择,已取消。" ;;
|
||||
notif_started_full) printf "开始录制(全屏:%s)→ %s" "$@" ;;
|
||||
notif_started_region) printf "开始录制(区域)→ %s" "$@" ;;
|
||||
notif_device_suffix) printf "(设备 %s)" "$@" ;;
|
||||
notif_saved) printf "已保存:%s" "$@" ;;
|
||||
notif_stopped) printf "已停止录制。" ;;
|
||||
notif_processing_gif) printf "正在转换为 GIF,请稍候..." ;;
|
||||
notif_gif_failed) printf "GIF 转换失败,保留原视频。" ;;
|
||||
notif_copied) printf "文件已复制" ;;
|
||||
already_running) printf "already running" ;;
|
||||
not_running) printf "not running" ;;
|
||||
title_mode) printf "选择录制模式" ;;
|
||||
title_output) printf "选择输出" ;;
|
||||
menu_fullscreen) printf "全屏" ;;
|
||||
menu_region) printf "选择区域" ;;
|
||||
menu_gif_region) printf "录制 GIF (区域)" ;;
|
||||
# settings labels -> "标签:值"
|
||||
title_settings) printf "设置..." ;;
|
||||
menu_settings) printf "设置..." ;;
|
||||
menu_set_codec) printf "编码格式:%s" "$@" ;;
|
||||
menu_set_fps) printf "帧率:%s" "$@" ;;
|
||||
menu_set_filefmt) printf "文件格式:%s" "$@" ;;
|
||||
menu_toggle_audio) printf "音频:%s" "$@" ;;
|
||||
menu_set_render) printf "渲染设备:%s" "$@" ;;
|
||||
menu_back) printf "返回" ;;
|
||||
fps_unlimited) printf "不限制" ;;
|
||||
render_auto) printf "自动" ;;
|
||||
ext_auto) printf "自动" ;;
|
||||
title_select_codec) printf "选择编码格式" ;;
|
||||
title_select_fps) printf "选择帧率" ;;
|
||||
title_select_filefmt) printf "选择文件格式" ;;
|
||||
title_select_render) printf "选择渲染设备(/dev/dri/renderD*)" ;;
|
||||
mode_full) printf "全屏" ;;
|
||||
mode_region) printf "区域" ;;
|
||||
prompt_enter_number) printf "输入编号:" ;;
|
||||
menu_exit) printf "退出" ;;
|
||||
*) printf "%s" "$id" ;;
|
||||
esac
|
||||
;;
|
||||
ja)
|
||||
case "$id" in
|
||||
err_wf_not_found) printf "wf-recorder が見つかりません" ;;
|
||||
err_need_slurp) printf "領域選択には slurp が必要です" ;;
|
||||
err_need_ffmpeg) printf "GIF変換には ffmpeg が必要ですが、見つかりません。" ;;
|
||||
warn_drm_ignored) printf "警告:DRM_DEVICE=%s は無視されます。" "$@" ;;
|
||||
warn_invalid_fps) printf "警告:FRAMERATE=\"%s\" は不正です。" "$@" ;;
|
||||
warn_render_unreadable) printf "警告:無効なレンダー ノード:%s" "$@" ;;
|
||||
cancel_no_mode) printf "キャンセル:録画モード未選択。" ;;
|
||||
cancel_no_output) printf "キャンセル:出力未選択。" ;;
|
||||
cancel_no_region) printf "キャンセル:領域未選択。" ;;
|
||||
warn_multi_outputs_cancel) printf "出力が複数ですが未選択のため中止。" ;;
|
||||
notif_started_full) printf "録画開始(全画面:%s)→ %s" "$@" ;;
|
||||
notif_started_region) printf "録画開始(領域)→ %s" "$@" ;;
|
||||
notif_device_suffix) printf "(デバイス %s)" "$@" ;;
|
||||
notif_saved) printf "保存しました:%s" "$@" ;;
|
||||
notif_stopped) printf "録画を停止しました。" ;;
|
||||
notif_processing_gif) printf "GIF に変換中、お待ちください..." ;;
|
||||
notif_gif_failed) printf "GIF 変換に失敗しました。元の動画を保持します。" ;;
|
||||
notif_copied) printf "ファイルをコピーしました" ;;
|
||||
already_running) printf "already running" ;;
|
||||
not_running) printf "not running" ;;
|
||||
title_mode) printf "録画モードを選択" ;;
|
||||
title_output) printf "出力を選択" ;;
|
||||
menu_fullscreen) printf "全画面" ;;
|
||||
menu_region) printf "領域選択" ;;
|
||||
menu_gif_region) printf "GIF録画 (領域)" ;;
|
||||
# settings labels -> "ラベル:値"(全角コロン)
|
||||
title_settings) printf "設定..." ;;
|
||||
menu_settings) printf "設定..." ;;
|
||||
menu_set_codec) printf "コーデック:%s" "$@" ;;
|
||||
menu_set_fps) printf "フレームレート:%s" "$@" ;;
|
||||
menu_set_filefmt) printf "ファイル形式:%s" "$@" ;;
|
||||
menu_toggle_audio) printf "音声:%s" "$@" ;;
|
||||
menu_set_render) printf "レンダーデバイス:%s" "$@" ;;
|
||||
menu_back) printf "戻る" ;;
|
||||
fps_unlimited) printf "無制限" ;;
|
||||
render_auto) printf "自動" ;;
|
||||
ext_auto) printf "自動" ;;
|
||||
title_select_codec) printf "コーデックを選択" ;;
|
||||
title_select_fps) printf "フレームレートを選択" ;;
|
||||
title_select_filefmt) printf "ファイル形式を選択" ;;
|
||||
title_select_render) printf "レンダーデバイスを選択(/dev/dri/renderD*)" ;;
|
||||
mode_full) printf "全画面" ;;
|
||||
mode_region) printf "領域" ;;
|
||||
prompt_enter_number) printf "番号を入力:" ;;
|
||||
menu_exit) printf "終了" ;;
|
||||
*) printf "%s" "$id" ;;
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
case "$id" in
|
||||
err_wf_not_found) printf "wf-recorder not found" ;;
|
||||
err_need_slurp) printf "slurp required for region selection" ;;
|
||||
err_need_ffmpeg) printf "ffmpeg is required for GIF conversion but not found." ;;
|
||||
warn_drm_ignored) printf "Warning: DRM_DEVICE=%s ignored." "$@" ;;
|
||||
warn_invalid_fps) printf "Warning: invalid FRAMERATE=\"%s\"." "$@" ;;
|
||||
warn_render_unreadable) printf "Warning: invalid render node: %s" "$@" ;;
|
||||
cancel_no_mode) printf "Canceled: no recording mode selected." ;;
|
||||
cancel_no_output) printf "Canceled: no output selected." ;;
|
||||
cancel_no_region) printf "Canceled: no region selected." ;;
|
||||
warn_multi_outputs_cancel) printf "Multiple outputs but none selected; canceled." ;;
|
||||
notif_started_full) printf "Recording started (fullscreen: %s) → %s" "$@" ;;
|
||||
notif_started_region) printf "Recording started (region) → %s" "$@" ;;
|
||||
notif_device_suffix) printf " (device %s)" "$@" ;;
|
||||
notif_saved) printf "Saved: %s" "$@" ;;
|
||||
notif_stopped) printf "Recording stopped." ;;
|
||||
notif_processing_gif) printf "Converting to GIF, please wait..." ;;
|
||||
notif_gif_failed) printf "GIF conversion failed. Original video kept." ;;
|
||||
notif_copied) printf "File copied" ;;
|
||||
already_running) printf "already running" ;;
|
||||
not_running) printf "not running" ;;
|
||||
title_mode) printf "Select recording mode" ;;
|
||||
title_output) printf "Select output" ;;
|
||||
menu_fullscreen) printf "Fullscreen" ;;
|
||||
menu_region) printf "Region" ;;
|
||||
menu_gif_region) printf "Record GIF (Region)" ;;
|
||||
# settings labels -> "Label: Value"
|
||||
title_settings) printf "Settings..." ;;
|
||||
menu_settings) printf "Settings..." ;;
|
||||
menu_set_codec) printf "Codec: %s" "$@" ;;
|
||||
menu_set_fps) printf "Framerate: %s" "$@" ;;
|
||||
menu_set_filefmt) printf "File Format: %s" "$@" ;;
|
||||
menu_toggle_audio) printf "Audio: %s" "$@" ;;
|
||||
menu_set_render) printf "Render Device: %s" "$@" ;;
|
||||
menu_back) printf "Back" ;;
|
||||
fps_unlimited) printf "unlimited" ;;
|
||||
render_auto) printf "Auto" ;;
|
||||
ext_auto) printf "Auto" ;;
|
||||
title_select_codec) printf "Select Codec" ;;
|
||||
title_select_fps) printf "Select Framerate" ;;
|
||||
title_select_filefmt) printf "Select File Format" ;;
|
||||
title_select_render) printf "Select Render Device (/dev/dri/renderD*)" ;;
|
||||
mode_full) printf "Fullscreen" ;;
|
||||
mode_region) printf "Region" ;;
|
||||
prompt_enter_number) printf "Enter number: " ;;
|
||||
menu_exit) printf "Exit" ;;
|
||||
*) printf "%s" "$id" ;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
is_running() {
|
||||
[[ -r "$PIDFILE" ]] || return 1
|
||||
local pid; read -r pid <"$PIDFILE" 2>/dev/null || return 1
|
||||
[[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null
|
||||
}
|
||||
notify() { has notify-send && notify-send "wf-recorder" "$1" || true; }
|
||||
|
||||
signal_waybar() {
|
||||
local pids
|
||||
if [[ -r "$WAYBAR_PIDS_CACHE" ]]; then
|
||||
pids="$(tr '\n' ' ' <"$WAYBAR_PIDS_CACHE")"
|
||||
if [[ -n "$pids" ]]; then kill -RTMIN+"$WAYBAR_SIG" $pids 2>/dev/null && return 0; fi
|
||||
fi
|
||||
pids="$(pgrep -x -u "$UID" waybar 2>/dev/null | tr '\n' ' ')"
|
||||
[[ -n "$pids" ]] && printf '%s\n' $pids >"$WAYBAR_PIDS_CACHE"
|
||||
[[ -n "$pids" ]] && kill -RTMIN+"$WAYBAR_SIG" $pids 2>/dev/null || true
|
||||
}
|
||||
emit_waybar_signal() { [[ "${WAYBAR_POKE,,}" == "off" ]] && return 0; signal_waybar; }
|
||||
|
||||
start_tick() {
|
||||
if [[ -f "$TICKPIDFILE" ]]; then
|
||||
local tpid; read -r tpid <"$TICKPIDFILE" 2>/dev/null || true
|
||||
[[ -n "$tpid" ]] && kill -TERM "$tpid" 2>/dev/null || true
|
||||
rm -f "$TICKPIDFILE"
|
||||
fi
|
||||
(
|
||||
while :; do
|
||||
[[ -r "$PIDFILE" ]] || break
|
||||
local p; read -r p <"$PIDFILE" 2>/dev/null || p=""
|
||||
[[ -n "$p" ]] && kill -0 "$p" 2>/dev/null || break
|
||||
signal_waybar
|
||||
sleep 1
|
||||
done
|
||||
) & echo $! >"$TICKPIDFILE"
|
||||
}
|
||||
stop_tick() {
|
||||
if [[ -f "$TICKPIDFILE" ]]; then
|
||||
local tpid; read -r tpid <"$TICKPIDFILE" 2>/dev/null || true
|
||||
[[ -n "$tpid" ]] && kill -TERM "$tpid" 2>/dev/null || true
|
||||
rm -f "$TICKPIDFILE"
|
||||
fi
|
||||
}
|
||||
|
||||
get_save_dir() {
|
||||
local videos
|
||||
if has xdg-user-dir; then videos="$(xdg-user-dir VIDEOS 2>/dev/null || true)"; fi
|
||||
videos="${videos:-"$HOME/Videos"}"
|
||||
echo "${SAVE_DIR_ENV:-"$videos/wf-recorder"}"
|
||||
}
|
||||
|
||||
# --- render device helpers ---
|
||||
list_render_nodes() {
|
||||
local d
|
||||
for d in /dev/dri/renderD*; do
|
||||
[[ -r "$d" ]] && printf '%s\n' "$d"
|
||||
done 2>/dev/null || true
|
||||
}
|
||||
render_display() {
|
||||
local cur="${1:-}"
|
||||
if [[ -z "$cur" ]]; then
|
||||
msg render_auto
|
||||
else
|
||||
printf "%s" "$cur"
|
||||
fi
|
||||
}
|
||||
pick_render_device() {
|
||||
local dev="${DRM_DEVICE:-}"
|
||||
if [[ -n "$dev" && ! -r "$dev" ]]; then
|
||||
printf '%s\n' "$(msg warn_render_unreadable "$dev")" >&2
|
||||
dev=""
|
||||
fi
|
||||
echo -n "$dev"
|
||||
}
|
||||
|
||||
# --- file format helpers ---
|
||||
ext_for_codec(){ case "${1,,}" in
|
||||
*h264*|*hevc*) echo mp4 ;;
|
||||
*vp9*) echo webm ;;
|
||||
*av1*) echo mkv ;;
|
||||
*) echo mp4 ;;
|
||||
esac; }
|
||||
choose_ext(){
|
||||
local e="${SAVE_EXT,,}"
|
||||
if [[ -z "$e" || "$e" == "auto" ]]; then
|
||||
ext_for_codec "$CODEC"
|
||||
else
|
||||
case "$e" in mp4|mkv|webm) echo "$e" ;; *) echo mp4 ;; esac
|
||||
fi
|
||||
}
|
||||
|
||||
# ================== Menus ==================
|
||||
__norm() { printf '%s' "$1" | tr -d '\r' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//'; }
|
||||
|
||||
_pick_menu_backend() {
|
||||
local pref="${MENU_BACKEND,,}"
|
||||
case "$pref" in fuzzel|wofi|rofi|bemenu|fzf|term) : ;; auto|"") pref="auto" ;; *) pref="auto" ;; esac
|
||||
if [[ "$pref" != "auto" ]]; then
|
||||
if has "$pref"; then echo "$pref"; else [[ -t 0 ]] && echo "term" || echo "none"; fi
|
||||
return
|
||||
fi
|
||||
for b in fuzzel wofi rofi bemenu fzf; do has "$b" && { echo "$b"; return; }; done
|
||||
[[ -t 0 ]] && echo "term" || echo "none"
|
||||
}
|
||||
|
||||
menu_pick() { # $1:title; items...
|
||||
local title="${1:-Select}"; shift
|
||||
local items=("$@")
|
||||
((${#items[@]})) || return 130
|
||||
|
||||
local backend; backend="$(_pick_menu_backend)"
|
||||
local sel rc=130
|
||||
case "$backend" in
|
||||
fuzzel) set +e; sel="$(printf '%s\n' "${items[@]}" | fuzzel --dmenu -p "$title")"; rc=$?; set -e ;;
|
||||
wofi) set +e; sel="$(printf '%s\n' "${items[@]}" | wofi --dmenu --prompt "$title")"; rc=$?; set -e ;;
|
||||
rofi) set +e; sel="$(printf '%s\n' "${items[@]}" | rofi -dmenu -p "$title")"; rc=$?; set -e ;;
|
||||
bemenu) set +e; sel="$(printf '%s\n' "${items[@]}" | bemenu -p "$title")"; rc=$?; set -e ;;
|
||||
fzf) set +e; sel="$(printf '%s\n' "${items[@]}" | fzf --prompt "$title> ")"; rc=$?; set -e ;;
|
||||
term)
|
||||
echo "$title"
|
||||
local i=1; for it in "${items[@]}"; do printf ' %d) %s\n' "$i" "$it"; ((i++)); done
|
||||
printf "%s" "$(msg prompt_enter_number)"
|
||||
local idx; set +e; read -r idx; rc=$?; set -e
|
||||
if [[ $rc -eq 0 && -n "$idx" && "$idx" =~ ^[0-9]+$ ]]; then
|
||||
if (( idx>=1 && idx<=${#items[@]} )); then sel="${items[$((idx-1))]}"; rc=0; fi
|
||||
fi
|
||||
;;
|
||||
none) return 130 ;;
|
||||
esac
|
||||
[[ $rc -ne 0 || -z "${sel:-}" ]] && return 130
|
||||
printf '%s' "$(__norm "$sel")"
|
||||
}
|
||||
|
||||
# ---------- Outputs ----------
|
||||
list_outputs() {
|
||||
local raw
|
||||
if raw="$(wf-recorder -L 2>/dev/null)"; then :; elif has wlr-randr; then raw="$(wlr-randr 2>/dev/null | awk '/^[^ ]/{print $1}')"; else raw=""; fi
|
||||
awk 'BEGIN{RS="[ \t\r\n,]+"} /^[A-Za-z0-9_.:-]+$/ { if ($0 ~ /^(e?DP|HDMI|DVI|VGA|LVDS|Virtual|XWAYLAND)/) seen[$0]=1 } END{for(k in seen) print k}' <<<"$raw" | sort -u
|
||||
}
|
||||
decide_output() {
|
||||
if [[ -n "$OUTPUT" ]]; then printf '%s' "$OUTPUT"; return 0; fi
|
||||
local -a outs; mapfile -t outs < <(list_outputs || true)
|
||||
local out_title; out_title="${MENU_TITLE_OUTPUT:-$(msg title_output)}"
|
||||
if [[ "${OUTPUT_SELECT}" == "menu" ]] || { [[ "${OUTPUT_SELECT}" == "auto" ]] && ((${#outs[@]} > 1)); }; then
|
||||
local pick; pick="$(menu_pick "$out_title" "${outs[@]}")" || return 130
|
||||
printf '%s' "$pick"; return 0
|
||||
fi
|
||||
if ((${#outs[@]} == 1)); then printf '%s' "${outs[0]}"; else printf '%s\n' "$(msg warn_multi_outputs_cancel)" >&2; return 130; fi
|
||||
}
|
||||
|
||||
# ---------- Settings ----------
|
||||
choose_render_menu() {
|
||||
local -a nodes
|
||||
mapfile -t nodes < <(list_render_nodes | sort -V || true)
|
||||
local auto_item; auto_item="$(msg render_auto)"
|
||||
local pick
|
||||
if ! pick="$(menu_pick "$(msg title_select_render)" "$auto_item" "${nodes[@]}")"; then
|
||||
return 0
|
||||
fi
|
||||
if [[ "$pick" == "$auto_item" ]]; then
|
||||
DRM_DEVICE=""
|
||||
rm -f "$CFG_DRM"
|
||||
return 0
|
||||
fi
|
||||
local sel="$pick"
|
||||
if [[ -n "$sel" && -r "$sel" ]]; then
|
||||
DRM_DEVICE="$sel"
|
||||
printf '%s' "$DRM_DEVICE" >"$CFG_DRM"
|
||||
else
|
||||
printf '%s\n' "$(msg warn_render_unreadable "$sel")" >&2
|
||||
fi
|
||||
}
|
||||
|
||||
choose_filefmt_menu() {
|
||||
local auto_item; auto_item="$(msg ext_auto)"
|
||||
local pick
|
||||
if ! pick="$(menu_pick "$(msg title_select_filefmt)" "$auto_item" "mp4" "mkv" "webm")"; then
|
||||
return 0
|
||||
fi
|
||||
if [[ "$pick" == "$auto_item" ]]; then
|
||||
SAVE_EXT="auto"
|
||||
rm -f "$CFG_EXT"
|
||||
else
|
||||
case "$pick" in
|
||||
mp4|mkv|webm) SAVE_EXT="$pick"; printf '%s' "$SAVE_EXT" >"$CFG_EXT" ;;
|
||||
*) : ;;
|
||||
esac
|
||||
fi
|
||||
}
|
||||
|
||||
show_settings_menu() {
|
||||
while :; do
|
||||
local fps_display="${FRAMERATE:-$(msg fps_unlimited)}"
|
||||
local audio_display="${AUDIO}"
|
||||
local render_display_now; render_display_now="$(render_display "$DRM_DEVICE")"
|
||||
local ff_display; if [[ -z "$SAVE_EXT" || "${SAVE_EXT,,}" == "auto" ]]; then ff_display="$(msg ext_auto)"; else ff_display="$SAVE_EXT"; fi
|
||||
|
||||
# ORDER: Framerate → Audio → Codec → File Format → Render → Back
|
||||
local pick; pick="$(menu_pick "$(msg title_settings)" \
|
||||
"$(msg menu_set_fps "$fps_display")" \
|
||||
"$(msg menu_toggle_audio "$audio_display")" \
|
||||
"$(msg menu_set_codec "$CODEC")" \
|
||||
"$(msg menu_set_filefmt "$ff_display")" \
|
||||
"$(msg menu_set_render "$render_display_now")" \
|
||||
"$(msg menu_back)")" || return 0
|
||||
|
||||
if [[ "$pick" == "$(msg menu_set_fps "$fps_display")" ]]; then
|
||||
local newf; newf="$(menu_pick "$(msg title_select_fps)" "60" "30" "120" "144" "165" "240" "$(msg fps_unlimited)")" || continue
|
||||
if [[ "$newf" == "$(msg fps_unlimited)" ]]; then
|
||||
FRAMERATE=""; rm -f "$CFG_FPS"
|
||||
else
|
||||
if [[ "$newf" =~ ^[0-9]+$ && "$newf" -gt 0 ]]; then FRAMERATE="$newf"; printf '%s' "$FRAMERATE" >"$CFG_FPS"; fi
|
||||
fi
|
||||
|
||||
elif [[ "$pick" == "$(msg menu_toggle_audio "$audio_display")" ]]; then
|
||||
if [[ "$AUDIO" == "on" ]]; then AUDIO="off"; else AUDIO="on"; fi
|
||||
printf '%s' "$AUDIO" >"$CFG_AUDIO"
|
||||
|
||||
elif [[ "$pick" == "$(msg menu_set_codec "$CODEC")" ]]; then
|
||||
# 仅保留 CPU (libx264) 与所有常见 VAAPI 编码选项,CPU 放在首位
|
||||
local newc; newc="$(menu_pick "$(msg title_select_codec)" \
|
||||
"libx264" "h264_vaapi" "hevc_vaapi" "av1_vaapi" "vp9_vaapi")" || continue
|
||||
CODEC="$newc"; printf '%s' "$CODEC" >"$CFG_CODEC"
|
||||
|
||||
elif [[ "$pick" == "$(msg menu_set_filefmt "$ff_display")" ]]; then
|
||||
choose_filefmt_menu
|
||||
|
||||
elif [[ "$pick" == "$(msg menu_set_render "$render_display_now")" ]]; then
|
||||
choose_render_menu
|
||||
|
||||
elif [[ "$pick" == "$(msg menu_back)" ]]; then
|
||||
return 0
|
||||
fi
|
||||
# loop to refresh values instantly
|
||||
done
|
||||
}
|
||||
|
||||
# ---------- Mode selection ----------
|
||||
decide_mode() {
|
||||
case "${RECORD_MODE,,}" in
|
||||
full|fullscreen) MODE_DECIDED="full"; return 0 ;;
|
||||
region|area) MODE_DECIDED="region"; return 0 ;;
|
||||
*) ;;
|
||||
esac
|
||||
local L_FULL L_REGION L_GIF L_SETTINGS L_EXIT
|
||||
case "$(lang_code)" in
|
||||
zh) L_FULL="$(msg menu_fullscreen)"; L_REGION="$(msg menu_region)"; L_GIF="$(msg menu_gif_region)"; L_SETTINGS="$(msg menu_settings)"; L_EXIT="$(msg menu_exit)";;
|
||||
ja) L_FULL="$(msg menu_fullscreen)"; L_REGION="$(msg menu_region)"; L_GIF="$(msg menu_gif_region)"; L_SETTINGS="$(msg menu_settings)"; L_EXIT="$(msg menu_exit)";;
|
||||
*) L_FULL="Fullscreen"; L_REGION="Region"; L_GIF="$(msg menu_gif_region)"; L_SETTINGS="$(msg menu_settings)"; L_EXIT="$(msg menu_exit)";;
|
||||
esac
|
||||
local title; title="$(msg title_mode)"
|
||||
while :; do
|
||||
# ORDER: Fullscreen -> Region -> GIF -> Settings -> Exit
|
||||
# [FIXED] 调整菜单顺序以匹配图片要求:全屏在最前,GIF在区域之后
|
||||
local pick; pick="$(menu_pick "$title" "$L_FULL" "$L_REGION" "$L_GIF" "$L_SETTINGS" "$L_EXIT")" || return 130
|
||||
if [[ "$pick" == "$L_FULL" ]]; then MODE_DECIDED="full"; return 0
|
||||
elif [[ "$pick" == "$L_REGION" ]]; then MODE_DECIDED="region"; return 0
|
||||
elif [[ "$pick" == "$L_GIF" ]]; then MODE_DECIDED="region"; IS_GIF_MODE="true"; return 0
|
||||
elif [[ "$pick" == "$L_SETTINGS" ]]; then show_settings_menu; continue
|
||||
elif [[ "$pick" == "$L_EXIT" ]]; then return 130
|
||||
else return 130; fi
|
||||
done
|
||||
}
|
||||
|
||||
# ---------- Helpers ----------
|
||||
geom_token() {
|
||||
local g="$1"
|
||||
awk 'NF==2{split($1,a,","); split($2,b,"x");
|
||||
if(a[1]!=""){printf "%sx%s@%s,%s",b[1],b[2],a[1],a[2]}}' <<<"$g"
|
||||
}
|
||||
pretty_dur() {
|
||||
local dur="${1:-0}"
|
||||
[[ "$dur" =~ ^[0-9]+$ ]] || dur=0
|
||||
if ((dur>=3600)); then printf "%d:%02d:%02d" $((dur/3600)) $(((dur%3600)/60)) $((dur%60))
|
||||
else printf "%02d:%02d" $((dur/60)) $((dur%60)); fi
|
||||
}
|
||||
json_escape() { sed ':a;N;$!ba;s/\\/\\\\/g;s/"/\\"/g;s/\n/\\n/g'; }
|
||||
|
||||
# ================== Start / Stop ==================
|
||||
start_rec() {
|
||||
if is_running; then echo "$(msg already_running)"; exit 0; fi
|
||||
has wf-recorder || { echo "$(msg err_wf_not_found)"; exit 1; }
|
||||
|
||||
MODE_DECIDED=""
|
||||
IS_GIF_MODE="false"
|
||||
if ! decide_mode; then
|
||||
echo "$(msg cancel_no_mode)"; emit_waybar_signal; exit 130
|
||||
fi
|
||||
local mode="$MODE_DECIDED"
|
||||
|
||||
# [NEW] GIF 模式检查
|
||||
if [[ "$IS_GIF_MODE" == "true" ]]; then
|
||||
if ! has ffmpeg; then echo "$(msg err_need_ffmpeg)"; emit_waybar_signal; exit 1; fi
|
||||
# GIF 模式强制使用 mp4 作为中间格式,因为 mp4 兼容性好且编码速度快
|
||||
SAVE_EXT="mp4"
|
||||
touch "$GIF_MARKER"
|
||||
else
|
||||
rm -f "$GIF_MARKER"
|
||||
fi
|
||||
|
||||
local marker="" output="" GEOM="" gtok=""
|
||||
local -a args
|
||||
args=( -c "$CODEC" )
|
||||
|
||||
local ROOT_DIR TARGET_DIR
|
||||
ROOT_DIR="$(get_save_dir)"
|
||||
if [[ "$mode" == "full" ]]; then TARGET_DIR="$ROOT_DIR/${SAVE_SUBDIR_FS}"; else TARGET_DIR="$ROOT_DIR"; fi
|
||||
mkdir -p "$TARGET_DIR"
|
||||
|
||||
if [[ "$mode" == "full" ]]; then
|
||||
output="$(decide_output)" || { echo "$(msg cancel_no_output)"; emit_waybar_signal; exit 130; }
|
||||
[[ -n "$output" ]] && args+=( -o "$output" )
|
||||
marker="FS${output:+-$output}"
|
||||
else
|
||||
if [[ -n "$REC_AREA" ]]; then
|
||||
GEOM="$REC_AREA"
|
||||
else
|
||||
has slurp || { echo "$(msg err_need_slurp)"; emit_waybar_signal; exit 1; }
|
||||
set +e; GEOM="$(slurp)"; local rc=$?; set -e
|
||||
if [[ $rc -ne 0 || -z "${GEOM// /}" ]]; then echo "$(msg cancel_no_region)"; emit_waybar_signal; exit 130; fi
|
||||
fi
|
||||
GEOM="$(echo -n "$GEOM" | tr -s '[:space:]' ' ')"
|
||||
args+=( -g "$GEOM" )
|
||||
if [[ "${GEOM_IN_NAME,,}" == "on" ]]; then gtok="$(geom_token "$GEOM")"; marker="REGION${gtok:+-$gtok}"; else marker="REGION"; fi
|
||||
fi
|
||||
|
||||
local ts safe_title base SAVE_PATH ext
|
||||
ts="$(date +'%Y-%m-%d-%H%M%S')"; safe_title="${TITLE// /_}"
|
||||
base="$ts${safe_title:+-$safe_title}-${marker}"
|
||||
ext="$(choose_ext)"
|
||||
SAVE_PATH="$TARGET_DIR/$base.$ext"
|
||||
|
||||
args=( --file "$SAVE_PATH" "${args[@]}" )
|
||||
|
||||
# Render device
|
||||
local dev; dev="$(pick_render_device)"; [[ -n "$dev" ]] && args+=( -d "$dev" )
|
||||
|
||||
# Audio
|
||||
case "$AUDIO" in off|OFF|0|false) ;; on|ON|1|true|"") args+=( --audio ) ;; *) args+=( --audio="$AUDIO" ) ;; esac
|
||||
|
||||
# Framerate
|
||||
if [[ -n "$FRAMERATE" ]]; then
|
||||
if [[ "$FRAMERATE" =~ ^[0-9]+$ && "$FRAMERATE" -gt 0 ]]; then args+=( --framerate "$FRAMERATE" )
|
||||
else printf '%s\n' "$(msg warn_invalid_fps "$FRAMERATE")" >&2; fi
|
||||
fi
|
||||
|
||||
# Pixel format
|
||||
if [[ "$CODEC" == *"_vaapi" ]]; then args+=( -F "scale_vaapi=format=nv12:out_range=full:out_color_primaries=bt709" )
|
||||
else args+=( -F "format=yuv420p" ); fi
|
||||
|
||||
# === 不保存日志:仅在 DEBUG=on 时将 wf-recorder 输出到终端 ===
|
||||
if [[ "${DEBUG,,}" == "on" ]]; then
|
||||
echo "DEBUG=on: running wf-recorder in foreground"
|
||||
echo "Command: wf-recorder ${args[*]}"
|
||||
wf-recorder "${args[@]}" 2>&1 &
|
||||
local pid=$!
|
||||
echo "$pid" >"$PIDFILE"
|
||||
date +%s >"$STARTFILE"
|
||||
echo "$SAVE_PATH" >"$SAVEPATH_FILE"
|
||||
echo "$mode" >"$MODEFILE"
|
||||
local note; if [[ "$mode" == "full" ]]; then note="$(msg notif_started_full "$output" "$SAVE_PATH")"; else note="$(msg notif_started_region "$SAVE_PATH")"; fi
|
||||
[[ -n "$dev" ]] && note+="$(msg notif_device_suffix "$dev")"
|
||||
echo "$note";
|
||||
emit_waybar_signal
|
||||
start_tick
|
||||
return 0
|
||||
fi
|
||||
|
||||
# 非 DEBUG:后台运行,且不保存任何日志(与原脚本行为相近)
|
||||
setsid nohup wf-recorder "${args[@]}" >/dev/null 2>&1 &
|
||||
local pid=$!
|
||||
echo "$pid" >"$PIDFILE"
|
||||
date +%s >"$STARTFILE"
|
||||
echo "$SAVE_PATH" >"$SAVEPATH_FILE"
|
||||
echo "$mode" >"$MODEFILE"
|
||||
|
||||
local note; if [[ "$mode" == "full" ]]; then note="$(msg notif_started_full "$output" "$SAVE_PATH")"; else note="$(msg notif_started_region "$SAVE_PATH")"; fi
|
||||
[[ -n "$dev" ]] && note+="$(msg notif_device_suffix "$dev")"
|
||||
echo "$note";
|
||||
emit_waybar_signal
|
||||
start_tick
|
||||
}
|
||||
|
||||
stop_rec() {
|
||||
if ! is_running; then echo "$(msg not_running)"; emit_waybar_signal; exit 0; fi
|
||||
local pid; read -r pid <"$PIDFILE"
|
||||
|
||||
kill -INT "$pid" 2>/dev/null || true
|
||||
for _ in {1..40}; do sleep 0.1; is_running || break; done
|
||||
is_running && kill -TERM "$pid" 2>/dev/null || true
|
||||
sleep 0.2
|
||||
is_running && kill -KILL "$pid" 2>/dev/null || true
|
||||
|
||||
# 停止后清理运行时状态文件
|
||||
rm -f "$PIDFILE" "$MODEFILE"
|
||||
stop_tick
|
||||
|
||||
local save_path=""; [[ -r "$SAVEPATH_FILE" ]] && read -r save_path <"$SAVEPATH_FILE"
|
||||
|
||||
# --- [NEW] GIF Conversion Logic ---
|
||||
if [[ -f "$GIF_MARKER" ]]; then
|
||||
rm -f "$GIF_MARKER"
|
||||
if [[ -n "$save_path" && -f "$save_path" ]]; then
|
||||
notify "$(msg notif_processing_gif)"
|
||||
|
||||
# [FIXED] 确保 GIF 目录存在: .../wf-recorder/gif/
|
||||
local gif_dir="$(get_save_dir)/gif"
|
||||
mkdir -p "$gif_dir"
|
||||
|
||||
local filename=$(basename "$save_path")
|
||||
local gif_out="$gif_dir/${filename%.*}.gif"
|
||||
|
||||
# 使用您提供的滤镜字符串
|
||||
local filters="fps=$GIF_FPS,scale=$GIF_WIDTH:-1:flags=lanczos,split[s0][s1];[s0]palettegen=stats_mode=$GIF_STATS_MODE[p];[s1][p]paletteuse=dither=$GIF_DITHER_MODE"
|
||||
|
||||
# 运行转换,如果成功则删除原文件
|
||||
if ffmpeg -y -v error -i "$save_path" -vf "$filters" "$gif_out"; then
|
||||
rm "$save_path"
|
||||
save_path="$gif_out"
|
||||
# 更新保存路径以便后续通知使用
|
||||
echo "$save_path" > "$SAVEPATH_FILE"
|
||||
else
|
||||
notify "$(msg notif_gif_failed)"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
# -----------------------------------
|
||||
|
||||
if [[ -n "$save_path" && -f "$save_path" ]]; then
|
||||
# 生成不带后缀的 latest(例如:.../latest)
|
||||
ln -sf "$(basename "$save_path")" "$(dirname "$save_path")/latest" || true
|
||||
|
||||
# --- [NEW] Auto Copy to Clipboard (as File Object) ---
|
||||
local cp_note=""
|
||||
if command -v wl-copy >/dev/null; then
|
||||
# [CRITICAL FIX] 使用 text/uri-list MIME 类型,并添加 file:// 前缀
|
||||
# 这会让剪贴板将其视为一个“文件”,允许在文件管理器或聊天软件中直接粘贴
|
||||
echo "file://${save_path}" | wl-copy --type text/uri-list
|
||||
cp_note=" $(msg notif_copied)"
|
||||
fi
|
||||
# ------------------------------------
|
||||
|
||||
local s; s="$(msg notif_saved "$save_path")${cp_note}"; echo "$s"; notify "$s"
|
||||
else
|
||||
local s; s="$(msg notif_stopped)"; echo "$s"; notify "$s"
|
||||
fi
|
||||
|
||||
if [[ "${PKILL_AFTER_STOP,,}" != "off" ]]; then
|
||||
for sig in INT TERM KILL; do
|
||||
pgrep -x -u "$UID" "$APP" >/dev/null || break
|
||||
pkill -"$sig" -x -u "$UID" "$APP" 2>/dev/null || true
|
||||
sleep 0.1
|
||||
done
|
||||
fi
|
||||
emit_waybar_signal
|
||||
}
|
||||
|
||||
# ================== Waybar JSON/status ==================
|
||||
tooltip_idle_text() {
|
||||
case "$(lang_code)" in
|
||||
zh) cat <<'EOF'
|
||||
屏幕录制(wf-recorder)
|
||||
左键:打开录制菜单
|
||||
右键:强制关闭
|
||||
EOF
|
||||
;;
|
||||
ja) cat <<'EOF'
|
||||
画面録画(wf-recorder)
|
||||
左クリック:録画メニューを開く
|
||||
右クリック:強制停止
|
||||
EOF
|
||||
;;
|
||||
*) cat <<'EOF'
|
||||
Screen recording (wf-recorder)
|
||||
Left click: open recording menu
|
||||
Right click: force stop
|
||||
EOF
|
||||
;;
|
||||
esac
|
||||
}
|
||||
tooltip_recording_text() { # $1 elapsed, $2 filepath, $3 mode: full|region
|
||||
local t="$1" p="${2:-}" m="${3:-}"
|
||||
local mode_label
|
||||
case "$m" in full|fullscreen) mode_label="$(msg mode_full)";; region|area) mode_label="$(msg mode_region)";; *) mode_label="";; esac
|
||||
case "$(lang_code)" in
|
||||
zh) [[ -n "$p" ]] && { [[ -n "$mode_label" ]] && printf "录制中(%s)\n已用时:%s\n文件:%s\n" "$mode_label" "$t" "$p" || printf "录制中\n已用时:%s\n文件:%s\n" "$t" "$p"; } || { [[ -n "$mode_label" ]] && printf "录制中(%s)\n已用时:%s\n" "$mode_label" "$t" || printf "录制中\n已用时:%s\n" "$t"; } ;;
|
||||
ja) [[ -n "$p" ]] && { [[ -n "$mode_label" ]] && printf "録画中(%s)\n経過時間:%s\nファイル:%s\n" "$mode_label" "$t" "$p" || printf "録画中\n経過時間:%s\nファイル:%s\n" "$t" "$p"; } || { [[ -n "$mode_label" ]] && printf "録画中(%s)\n経過時間:%s\n" "$mode_label" "$t" || printf "録画中\n経過時間:%s\n" "$t"; } ;;
|
||||
*) [[ -n "$p" ]] && { [[ -n "$mode_label" ]] && printf "Recording (%s)\nElapsed: %s\nFile: %s\n" "$mode_label" "$t" "$p" || printf "Recording\nElapsed: %s\nFile: %s\n" "$t" "$p"; } || { [[ -n "$mode_label" ]] && printf "Recording (%s)\nElapsed: %s\n" "$mode_label" "$t" || printf "Recording\nElapsed: %s\n" "$t"; } ;;
|
||||
esac
|
||||
}
|
||||
pretty_status_json() {
|
||||
local text tooltip class alt
|
||||
if is_running; then
|
||||
local start=0; [[ -r "$STARTFILE" ]] && read -r start <"$STARTFILE" || true
|
||||
[[ "$start" =~ ^[0-9]+$ ]] || start=0
|
||||
local now dur; now="$(date +%s)"; dur=$((now - start)); (( dur < 0 )) && dur=0
|
||||
local t; t="$(pretty_dur "$dur")"
|
||||
local save_path=""; [[ -r "$SAVEPATH_FILE" ]] && read -r save_path <"$SAVEPATH_FILE" || true
|
||||
local mode=""; [[ -r "$MODEFILE" ]] && read -r mode <"$MODEFILE" || true
|
||||
text="$ICON_REC$t"
|
||||
tooltip="$(tooltip_recording_text "$t" "$save_path" "$mode")"
|
||||
class="recording"; alt="rec"
|
||||
else
|
||||
text="$ICON_IDLE"; tooltip="$(tooltip_idle_text)"; class="idle"; alt="idle"
|
||||
fi
|
||||
printf '{"text":"%s","tooltip":"%s","class":"%s","alt":"%s"}\n' \
|
||||
"$(printf '%s' "$text" | json_escape)" \
|
||||
"$(printf '%s' "$tooltip" | json_escape)" \
|
||||
"$class" "$alt"
|
||||
}
|
||||
status_rec() {
|
||||
local json="${1:-}"
|
||||
if [[ "$json" == "--json" ]]; then
|
||||
pretty_status_json
|
||||
else
|
||||
if is_running; then
|
||||
local start=0; [[ -r "$STARTFILE" ]] && read -r start <"$STARTFILE" || true
|
||||
[[ "$start" =~ ^[0-9]+$ ]] || start=0
|
||||
local now dur; now="$(date +%s)"; dur=$((now - start)); (( dur < 0 )) && dur=0
|
||||
printf "%s%s\n" "$ICON_REC" "$(pretty_dur "$dur")"
|
||||
else
|
||||
echo "$ICON_IDLE"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# ================== Main ==================
|
||||
case "${1:-toggle}" in
|
||||
start) start_rec ;;
|
||||
stop) stop_rec ;;
|
||||
status) status_rec ;;
|
||||
status-json) status_rec --json ;;
|
||||
waybar) status_rec --json ;;
|
||||
is-active) if is_running; then exit 0; else exit 1; fi ;;
|
||||
toggle) is_running && stop_rec || start_rec ;;
|
||||
settings) show_settings_menu ;;
|
||||
*) echo "Usage: $0 {start|stop|toggle|status|status-json|waybar|is-active|settings}"; exit 2 ;;
|
||||
esac
|
||||
494
.config/waybar/style.css
Executable file
494
.config/waybar/style.css
Executable file
@@ -0,0 +1,494 @@
|
||||
@import "colors.css";
|
||||
/* @import "/home/shorin/.cache/wal/colors-waybar.css"; */
|
||||
|
||||
/* 核心逻辑:全相对单位 (em) 改造
|
||||
基准字体大小:18px
|
||||
如果需要整体放大缩小,只需修改下方的 18px 即可
|
||||
*/
|
||||
* {
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
font-family: "JetBrainsMono Nerd Font Propo","LXGW WenKai Screen","JetBrains Maple Mono";
|
||||
/* font-family:"0xProto Nerd Font Propo"; */
|
||||
font-size: 16.6px; /* 基准大小 */
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
window#waybar {
|
||||
background: transparent;
|
||||
color: @on_surface;
|
||||
}
|
||||
|
||||
/* 鼠标悬浮信息提示 */
|
||||
tooltip {
|
||||
background: @secondary_container;
|
||||
border: 0.17em solid @outline; /* 3px */
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
tooltip label {
|
||||
color: @on_surface;
|
||||
font-size: 0.89em; /* 16px */
|
||||
}
|
||||
|
||||
|
||||
/* 工作区 */
|
||||
#workspaces button {
|
||||
padding: 0px 0.56em; /* 10px */
|
||||
background: @surface;
|
||||
color: @tertiary_container;
|
||||
}
|
||||
|
||||
/* #workspaces label {
|
||||
font-size: 1.22em;
|
||||
} */
|
||||
|
||||
#workspaces button:hover {
|
||||
background: @on_tertiary;
|
||||
}
|
||||
|
||||
#workspaces button.active{
|
||||
color:@tertiary;
|
||||
}
|
||||
|
||||
#custom-right_div.5 {
|
||||
background: @surface_container_high;
|
||||
color: @surface;
|
||||
font-size: 1.39em; /* 25px */
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
/* waybar niri taskbar */
|
||||
.niri-taskbar {
|
||||
background: @surface_container_high;
|
||||
padding: 0 0 0 0.28em; /* 5px */
|
||||
}
|
||||
|
||||
.niri-taskbar button:hover {
|
||||
background: @surface_container;
|
||||
}
|
||||
|
||||
.niri-taskbar button.focused {
|
||||
background: @surface;
|
||||
}
|
||||
|
||||
.niri-taskbar button.urgent {
|
||||
background-color: @tertiary;
|
||||
animation-name: blink;
|
||||
animation-duration: 0.5s;
|
||||
animation-timing-function: steps(12);
|
||||
animation-iteration-count: infinite;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
@keyframes blink {
|
||||
to {
|
||||
background-color: @primary;
|
||||
color: @error;
|
||||
}
|
||||
}
|
||||
|
||||
/* 窗口名 */
|
||||
#window {
|
||||
padding: 0px 0.56em; /* 10px */
|
||||
background-color: @surface_container_high;
|
||||
color: @on_surface;
|
||||
}
|
||||
|
||||
#window label {
|
||||
font-size: 0.89em; /* 16px */
|
||||
}
|
||||
|
||||
window#waybar.empty #window {
|
||||
background-color: @surface_container_high;
|
||||
}
|
||||
|
||||
#custom-right_div.6 {
|
||||
color: @surface_container_high;
|
||||
font-size: 1.39em; /* 25px */
|
||||
padding: 0px;
|
||||
}
|
||||
/* —————————————————————————————————————————————————————————————————————————— */
|
||||
/* —————————————————————————————————————————————————————————————————————————— */
|
||||
/* —————————————————————————————————————————————————————————————————————————— */
|
||||
/* —————————————————————————————————————————————————————————————————————————— */
|
||||
/* —————————————————————————————————————————————————————————————————————————— */
|
||||
|
||||
/* 中间 */
|
||||
|
||||
#custom-left_div.3 {
|
||||
color: @surface_container_high;
|
||||
padding: 0px;
|
||||
font-size: 1.39em; /* 25px */
|
||||
}
|
||||
|
||||
|
||||
#bluetooth {
|
||||
padding: 0px 0.3em 0px 0.5em; /* 7px */
|
||||
font-size: 1.25em; /* 20px */
|
||||
}
|
||||
#bluetooth.disabled{
|
||||
padding: 0px 0.22em 0px 0.46em ;
|
||||
}
|
||||
|
||||
#network {
|
||||
padding: 0px 0.34em; /* 7px */
|
||||
font-size: 1.2em; /* 22px */
|
||||
}
|
||||
|
||||
#custom-settings {
|
||||
padding: 0px 0.39em; /* 7px */
|
||||
font-size: 1.06em; /* 19px */
|
||||
}
|
||||
|
||||
#custom-screenshot {
|
||||
padding: 0 0.4em; /* 7px */
|
||||
font-size: 1.28em; /* 22px */
|
||||
}
|
||||
|
||||
/* wf-recoder脚本 */
|
||||
#custom-wfrec {
|
||||
padding: 0 0.3em; /* 7px */
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
#custom-wfrec.recording,
|
||||
#custom-wfrec,
|
||||
#bluetooth,
|
||||
#network,
|
||||
#custom-settings,
|
||||
#custom-screenshot {
|
||||
background-color: @surface_container_high;
|
||||
color: @secondary;
|
||||
}
|
||||
|
||||
/* 录制中 */
|
||||
#custom-wfrec.recording {
|
||||
color: @error;
|
||||
}
|
||||
/* #custom-wfrec,
|
||||
#custom-screenshot{
|
||||
color: @secondary;
|
||||
} */
|
||||
|
||||
#custom-left_div.2 {
|
||||
background-color: @surface_container_high;
|
||||
color: @tertiary;
|
||||
padding: 0px 0px;
|
||||
font-size: 1.39em; /* 25px */
|
||||
}
|
||||
|
||||
#power-profiles-daemon,
|
||||
#custom-colorpicker,
|
||||
#idle_inhibitor {
|
||||
background-color: @tertiary;
|
||||
color: @on_tertiary;
|
||||
padding: 0px 0.36em; /* 6px */
|
||||
}
|
||||
#idle_inhibitor.activated{
|
||||
padding: 0px 0.4em 0px 0.4em;
|
||||
}
|
||||
#power-profiles-daemon{
|
||||
padding: 0px 0.4em 0em 0.36em;
|
||||
}
|
||||
#power-profiles-daemon.performance {
|
||||
color: @on_error;
|
||||
font-size: 1.28em; /* 23px */
|
||||
padding: 0px 0.32em 0px 0.45em; /* 9px 8px */
|
||||
}
|
||||
|
||||
#power-profiles-daemon.balanced {
|
||||
color: @on_tertiary;
|
||||
}
|
||||
|
||||
#power-profiles-daemon.power-saver {
|
||||
color: #1aa052;
|
||||
|
||||
}
|
||||
|
||||
/* 菜单 */
|
||||
#custom-left_div.11 {
|
||||
background-color: @tertiary;
|
||||
color: @surface_container
|
||||
}
|
||||
#custom-applauncher {
|
||||
font-size: 1.39em; /* 25px */
|
||||
padding: 0px 0.39em; /* 7px */
|
||||
margin: 0px;
|
||||
background-color: @primary;
|
||||
color: @on_primary;
|
||||
|
||||
}
|
||||
#custom-right_div.1,
|
||||
#custom-left_div.1 {
|
||||
background-color: @surface_container;
|
||||
color: @primary;
|
||||
}
|
||||
#custom-left_div.1,
|
||||
#custom-right_div.1 {
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
font-size: 1.39em; /* 25px */
|
||||
}
|
||||
#custom-left_div.11,
|
||||
#custom-right_div.11 {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
font-size: 1.39em; /* 25px */
|
||||
}
|
||||
#custom-right_div.11 {
|
||||
|
||||
background-color: @secondary;
|
||||
color: @surface_container;
|
||||
}
|
||||
|
||||
/*中间右边第二级*/
|
||||
|
||||
#clock {
|
||||
padding: 0px 0.45em 0em 0.45em; /* 8px */
|
||||
}
|
||||
|
||||
#clock {
|
||||
background-color: @secondary;
|
||||
color: @on_secondary;
|
||||
}
|
||||
|
||||
#custom-right_div.2 {
|
||||
background-color: @surface_container_high;
|
||||
color: @secondary;
|
||||
padding: 0px;
|
||||
font-size: 1.39em; /* 25px */
|
||||
}
|
||||
|
||||
#custom-cava {
|
||||
background-color: @surface_container_high;
|
||||
color: @primary;
|
||||
padding: 0px 0.35em; /* 7px */
|
||||
}
|
||||
|
||||
#mpris {
|
||||
background-color: @surface_container_high;
|
||||
color: @primary;
|
||||
padding: 0px 0.39em 0px 0px; /* 7px 0 0 */
|
||||
}
|
||||
|
||||
#custom-right_div.3 {
|
||||
|
||||
color: @surface_container_high;
|
||||
padding: 0px 0.1em 0px 0px ;
|
||||
font-size: 1.39em; /* 25px */
|
||||
}
|
||||
#custom-right_div.4 {
|
||||
/* background-color: @surface_container_high; */
|
||||
color: @on_secondary;
|
||||
padding: 0px;
|
||||
font-size: 1.39em; /* 25px */
|
||||
}
|
||||
/* —————————————————————————————————————————————————————————————————————————— */
|
||||
/* —————————————————————————————————————————————————————————————————————————— */
|
||||
/* —————————————————————————————————————————————————————————————————————————— */
|
||||
/* —————————————————————————————————————————————————————————————————————————— */
|
||||
/* —————————————————————————————————————————————————————————————————————————— */
|
||||
|
||||
/* 右侧 */
|
||||
|
||||
|
||||
#custom-left_div.7 {
|
||||
/* background-color: @surface_container_high; */
|
||||
color: @surface_bright;
|
||||
font-size: 1.39em; /* 25px */
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
|
||||
#custom-updates {
|
||||
border-radius: 0px;
|
||||
padding: 0px 0.17em 0px 0.39em; /* 3px 7px */
|
||||
background-color: @surface_bright;
|
||||
color: @error;
|
||||
|
||||
}
|
||||
|
||||
#tray {
|
||||
padding: 0px 0.39em 0px 0.39em; /* 7px */
|
||||
font-size: 1.11em; /* 20px */
|
||||
background-color: @surface_bright;
|
||||
}
|
||||
|
||||
|
||||
#custom-left_div.4 {
|
||||
background-color: @surface_bright;
|
||||
color: @surface_container_high;
|
||||
padding: 0px;
|
||||
font-size: 1.39em; /* 25px */
|
||||
}
|
||||
/* 亮度 */
|
||||
#custom-ddcutil-day,
|
||||
#custom-ddcutil-night,
|
||||
#custom-ddcutil-sleep,
|
||||
#custom-separator.1 {
|
||||
background-color: @surface_container_high;
|
||||
color: @tertiary;
|
||||
padding: 0px 0.33em; /* 5px */
|
||||
}
|
||||
|
||||
#backlight {
|
||||
background-color: @surface_container_high;
|
||||
color: @tertiary;
|
||||
padding: 0px 0.28em 0px 0px; /* 5px */
|
||||
}
|
||||
|
||||
#backlight-slider {
|
||||
background-color: @surface_container_high;
|
||||
padding: 0px 0.28em 0px 0px; /* 5px */
|
||||
}
|
||||
|
||||
#backlight-slider slider {
|
||||
min-height: 0px;
|
||||
min-width: 0px;
|
||||
opacity: 0;
|
||||
background-image: none;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
background: none;
|
||||
}
|
||||
|
||||
#backlight-slider trough {
|
||||
min-height: 0.56em; /* 10px */
|
||||
min-width: 4.44em; /* 80px */
|
||||
border-radius: 0.28em; /* 5px */
|
||||
opacity: 0;
|
||||
background-color: @background;
|
||||
}
|
||||
|
||||
#backlight-slider highlight {
|
||||
min-width: 0.56em; /* 10px */
|
||||
border-radius: 0.28em; /* 5px */
|
||||
background-color: @tertiary;
|
||||
}
|
||||
|
||||
/* 音视频 */
|
||||
#privacy {
|
||||
padding: 0px 0.39em; /* 7px */
|
||||
}
|
||||
|
||||
#privacy {
|
||||
background-color: @surface_container_high;
|
||||
color: @error;
|
||||
}
|
||||
|
||||
#pulseaudio {
|
||||
padding: 0px 0px 0px 0.28em; /* 5px */
|
||||
}
|
||||
|
||||
#pulseaudio-slider {
|
||||
padding: 0px 0px 0px 0.56em; /* 10px */
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
#pulseaudio-slider,
|
||||
#pulseaudio {
|
||||
background-color: @surface_container_high;
|
||||
color: @tertiary;
|
||||
}
|
||||
|
||||
#pulseaudio-slider slider {
|
||||
min-height: 0px;
|
||||
min-width: 0px;
|
||||
opacity: 0;
|
||||
background-image: none;
|
||||
box-shadow: none;
|
||||
background: none;
|
||||
}
|
||||
|
||||
#pulseaudio-slider trough {
|
||||
min-height: 0.56em; /* 10px */
|
||||
min-width: 4.44em; /* 80px */
|
||||
border-radius: 0.28em; /* 5px */
|
||||
background-color: @surface;
|
||||
}
|
||||
|
||||
#pulseaudio-slider highlight {
|
||||
min-width: 0px;
|
||||
border-radius: 0.28em; /* 5px */
|
||||
background-color: @tertiary;
|
||||
}
|
||||
|
||||
#custom-left_div.8 {
|
||||
background-color: @surface_container_high;
|
||||
color: @surface_container;
|
||||
font-size: 1.39em; /* 25px */
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
|
||||
/* 电池 */
|
||||
|
||||
#battery {
|
||||
background-color: @surface_container;
|
||||
color: @secondary;
|
||||
padding: 0px 0.39em; /* 7px */
|
||||
}
|
||||
|
||||
#battery.critical:not(.charging) {
|
||||
background-color: @surface_container;
|
||||
color: @error;
|
||||
animation-name: blink;
|
||||
animation-duration: 0.5s;
|
||||
animation-timing-function: steps(12);
|
||||
animation-iteration-count: infinite;
|
||||
animation-direction: alternate;
|
||||
padding: 0px 0.39em; /* 7px */
|
||||
}
|
||||
|
||||
/* powermenu电源菜单 */
|
||||
#custom-left_div.5 {
|
||||
background-color: @surface_container;
|
||||
color: @surface;
|
||||
padding: 0px;
|
||||
font-size: 1.39em; /* 25px */
|
||||
}
|
||||
#custom-wlogout {
|
||||
padding: 0px 0.83em 0px 0.56em; /* 15px 10px */
|
||||
font-size: 1.39em; /* 25px */
|
||||
}
|
||||
|
||||
#custom-wlogout,
|
||||
#custom-reboot,
|
||||
#custom-lockscreen,
|
||||
#custom-logout {
|
||||
background-color: @surface;
|
||||
color: @error;
|
||||
padding: 0px 0.56em; /* 10px */
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#clock.date {
|
||||
padding: 0px 0.39em; /* 7px */
|
||||
}
|
||||
#custom-datelogo,
|
||||
#clock.date {
|
||||
background-color: @secondary_container;
|
||||
color: @on_secondary_container;
|
||||
}
|
||||
|
||||
#custom-swaync {
|
||||
background-color: @surface_container_high;
|
||||
color: @on_surface_container;
|
||||
padding: 0px 0.83em; /* 15px */
|
||||
}
|
||||
|
||||
#custom-mako {
|
||||
background-color: @surface_container_high;
|
||||
color: @on_surface_container;
|
||||
padding: 0px 0.83em; /* 15px */
|
||||
}
|
||||
|
||||
#custom-left_div.6 {
|
||||
background-color: @surface;
|
||||
color: @surface_container_high;
|
||||
padding: 0px;
|
||||
font-size: 1.39em; /* 25px */
|
||||
}
|
||||
Reference in New Issue
Block a user