diff options
| author | ilotterytea <ilotterytea@proton.me> | 2024-05-26 20:10:26 +0500 |
|---|---|---|
| committer | ilotterytea <ilotterytea@proton.me> | 2024-05-26 20:10:26 +0500 |
| commit | 036c889c4a4f7f59d1e1a592586b54c5c5e93005 (patch) | |
| tree | aa76d678790abc79f24edf83c17a564eb2c6f65d | |
52 files changed, 6531 insertions, 0 deletions
diff --git a/alacritty/alacritty.toml b/alacritty/alacritty.toml new file mode 100644 index 0000000..8c65901 --- /dev/null +++ b/alacritty/alacritty.toml @@ -0,0 +1,41 @@ +[font] +size = 7.0 + +[font.bold] +family = "Cascadia Mono" + +[font.glyph_offset] +x = 0 +y = 0 + +[font.italic] +family = "Cascadia Mono" + +[font.normal] +family = "Cascadia Mono" + +[font.offset] +x = 0 +y = 0 + +[mouse] +hide_when_typing = false + +[scrolling] +history = 100000 +multiplier = 3 + +[window] +decorations = "none" +dynamic_padding = true +startup_mode = "Windowed" +title = "Alacritty" +opacity = 0.95 + +[window.dimensions] +columns = 100 +lines = 25 + +[window.padding] +x = 5 +y = 5 diff --git a/alacritty/alacritty.yml b/alacritty/alacritty.yml new file mode 100644 index 0000000..a32f0b2 --- /dev/null +++ b/alacritty/alacritty.yml @@ -0,0 +1,110 @@ +# Alacritty Configuration +env: + TERM: xterm-256color + +# Windows +window: + dimensions: + columns: 100 + lines: 25 + padding: + x: 5 + y: 5 + dynamic_padding: true + decorations: none + startup_mode: Windowed + title: Alacritty + +# Scrolling +scrolling: + # Maximum number of lines in the scrollback buffer. + # Specifying '0' will disable scrolling. + history: 100000 + + # Number of lines the viewport will move for every line scrolled when + # scrollback is enabled (history > 0). + multiplier: 3 + +# Visual Bell +bell: + animation: EaseOutExpo + color: '0xffffff' + duration: 0 + +# Background opacity +background_opacity: 0.96 + +# Font Configuration +font: + normal: + family: Sarasa Term K + # style: Nerd + + bold: + family: Sarasa Term K + italic: + family: Sarasa Term K + # style: "Medium Italic" + + size: 14.0 + + offset: + x: 0 + y: 0 + + glyph_offset: + x: 0 + y: 0 + +draw_bold_text_with_bright_colors: true + +mouse: + # Click settings + # + # The `double_click` and `triple_click` settings control the time + # alacritty should wait for accepting multiple clicks as one double + # or triple click. + double_click: { threshold: 300 } + triple_click: { threshold: 300 } + hide_when_typing: false + +#cursor: + style: + # Cursor shape + # + # Values for `shape`: + # - ▇ Block + # - _ Underline + # - | Beam + shape: Beam + blinking: On + blink_interval: 750 + +# Selection colors +colors: + # Default colors + primary: + background: '0x2a2f33' + foreground: '0xb3cfa7' + + # Normal colors + normal: + black: '0x212529' + red: '0xea5252' + green: '0xacb765' + yellow: '0xc1bf89' + blue: '0x82abbc' + magenta: '0xb18a97' + cyan: '0x88b482' + white: '0xb3cfa7' + + # Bright colors + bright: + black: '0x31363b' + red: '0xea5252' + green: '0xacb765' + yellow: '0xc1bf89' + blue: '0x82abbc' + magenta: '0xb18a97' + cyan: '0x89b482' + white: '0xb3cfa7' diff --git a/bspwm/autostart b/bspwm/autostart new file mode 100755 index 0000000..e3fb541 --- /dev/null +++ b/bspwm/autostart @@ -0,0 +1,17 @@ +#!/bin/sh +# Applications +pgrep conky || conky & + +# pidof clight || clight --lat=26.83 --lon=80.92 & +pidof redshift || redshift & +xrdb ~/.Xresources +firefox & +# syncthing serve --no-browser --logfile=default & + +setxkbmap -layout us,ru -option grp:alt_shift_toggle & + +~/.local/scripts/mpd_start & + +xautolock -detectsleep -time 5 -locker lock.sh -notify 30 -notifier "notify-send -u critical -t 10000 -- 'LOCKING screen in 30 seconds'" & +# mailsync & +~/.config/polybar/launch.sh & diff --git a/bspwm/bspwmrc b/bspwm/bspwmrc new file mode 100755 index 0000000..570b688 --- /dev/null +++ b/bspwm/bspwmrc @@ -0,0 +1,85 @@ +#!/bin/bash + +#### MONITORS #### +bspc monitor -d 1 2 3 4 5 6 7 8 9 + +external_monitor=$(xrandr --query | grep 'DP-1') + +if [[ $external_monitor = DP-1\ connected* ]]; then + xrandr --output HDMI-1 --primary --mode 1920x1080 --output DP-1 --mode 1920x1080 --left-of HDMI-1 + bspc monitor HDMI-1 -d 1 2 3 4 5 6 + bspc monitor DP-1 -d 7 8 9 +else + xrandr --output HDMI-1 --auto + bspc monitor HDMI-1 -d 1 2 3 4 5 6 7 8 9 +fi + +# Truncate a couple of common commands that are used herein. +_bc() { + bspc config "$@" +} + + +#### BSPWM configuration #### +_bc window_gap 8 +_bc top_padding 36 +_bc top_monocle_padding 0 +_bc border_width 1 +_bc bottom_padding 0 +_bc left_padding 0 +_bc right_padding 0 +_bc single_monocle false +_bc click_to_focus false +_bc split_ratio 0.50 +_bc borderless_monocle true +_bc gapless_monocle true +_bc focus_by_distance true +_bc paddingless_monocle true +_bc focus_follows_pointer true +_bc ignore_ewmh_focus true +_bc history_aware_focus true +_bc remove_disabled_monitors true +_bc merge_overlapping_monitors true +_bc pointer_follows_monitor true +_bc pointer_modifier mod1 +_bc pointer_action1 move +_bc pointer_action2 resize_side +_bc pointer_action3 resize_corner + +# Colors +bspc config active_border_color "#474f54" +bspc config focused_border_color "#8bdfff" +bspc config normal_border_color "#373d41" +bspc config presel_feedback_color "#373d41" + +# Rules +bspc rule -a firefox -o desktop=2 +bspc rule -a discord desktop='^8' +bspc rule -a feh state='floating' +bspc rule -a mpv state='floating' +bspc rule -a openssh-askpass state='floating' +bspc rule -a "Iwgtk" state='floating' +bspc rule -a Zathura state='tiled' +bspc rule -a Pavucontrol state='floating' +bspc rule -a flameshot state='floating' +bspc rule -a *:SPLASH state=floating +bspc rule -a logsplease state='floating' +bspc rule -a maxoning state='floating' +bspc rule -a roguelike state='floating' +bspc desktop -f 1 + +# INIT-DAEMONS +xsetroot -cursor_name left_ptr & +sxhkd & +# xcompmgr -cCfF -t -2 -l -2 -r 2.8 -o 0.55 -D 5 & +picom --experimental-backends & +dunst & + +# Replace caps lock with Esc +# setxkbmap -option caps:escape +# setxkbmap -option caps:backspace + +# AUTOSTART +# set wallpaper using hsetroot +hsetroot -fill ~/.wallpapers/urban.jpg & +~/.config/bspwm/autostart & diff --git a/bspwm/noswallow b/bspwm/noswallow new file mode 100644 index 0000000..dbfb8f9 --- /dev/null +++ b/bspwm/noswallow @@ -0,0 +1 @@ +firefox diff --git a/bspwm/terminals b/bspwm/terminals new file mode 100644 index 0000000..4fe8cd5 --- /dev/null +++ b/bspwm/terminals @@ -0,0 +1,2 @@ +kitty +Alacritty diff --git a/btop/btop.conf b/btop/btop.conf new file mode 100644 index 0000000..26400f6 --- /dev/null +++ b/btop/btop.conf @@ -0,0 +1,248 @@ +#? Config file for btop v. 1.3.2 + +#* Name of a btop++/bpytop/bashtop formatted ".theme" file, "Default" and "TTY" for builtin themes. +#* Themes should be placed in "../share/btop/themes" relative to binary or "$HOME/.config/btop/themes" +color_theme = "Default" + +#* If the theme set background should be shown, set to False if you want terminal background transparency. +theme_background = False + +#* Sets if 24-bit truecolor should be used, will convert 24-bit colors to 256 color (6x6x6 color cube) if false. +truecolor = True + +#* Set to true to force tty mode regardless if a real tty has been detected or not. +#* Will force 16-color mode and TTY theme, set all graph symbols to "tty" and swap out other non tty friendly symbols. +force_tty = False + +#* Define presets for the layout of the boxes. Preset 0 is always all boxes shown with default settings. Max 9 presets. +#* Format: "box_name:P:G,box_name:P:G" P=(0 or 1) for alternate positions, G=graph symbol to use for box. +#* Use whitespace " " as separator between different presets. +#* Example: "cpu:0:default,mem:0:tty,proc:1:default cpu:0:braille,proc:0:tty" +presets = "cpu:1:default,proc:0:default cpu:0:default,mem:0:default,net:0:default cpu:0:block,net:0:tty" + +#* Set to True to enable "h,j,k,l,g,G" keys for directional control in lists. +#* Conflicting keys for h:"help" and k:"kill" is accessible while holding shift. +vim_keys = False + +#* Rounded corners on boxes, is ignored if TTY mode is ON. +rounded_corners = True + +#* Default symbols to use for graph creation, "braille", "block" or "tty". +#* "braille" offers the highest resolution but might not be included in all fonts. +#* "block" has half the resolution of braille but uses more common characters. +#* "tty" uses only 3 different symbols but will work with most fonts and should work in a real TTY. +#* Note that "tty" only has half the horizontal resolution of the other two, so will show a shorter historical view. +graph_symbol = "braille" + +# Graph symbol to use for graphs in cpu box, "default", "braille", "block" or "tty". +graph_symbol_cpu = "default" + +# Graph symbol to use for graphs in gpu box, "default", "braille", "block" or "tty". +graph_symbol_gpu = "braille" + +# Graph symbol to use for graphs in cpu box, "default", "braille", "block" or "tty". +graph_symbol_mem = "default" + +# Graph symbol to use for graphs in cpu box, "default", "braille", "block" or "tty". +graph_symbol_net = "default" + +# Graph symbol to use for graphs in cpu box, "default", "braille", "block" or "tty". +graph_symbol_proc = "default" + +#* Manually set which boxes to show. Available values are "cpu mem net proc" and "gpu0" through "gpu5", separate values with whitespace. +shown_boxes = "proc mem net cpu" + +#* Update time in milliseconds, recommended 2000 ms or above for better sample times for graphs. +update_ms = 500 + +#* Processes sorting, "pid" "program" "arguments" "threads" "user" "memory" "cpu lazy" "cpu direct", +#* "cpu lazy" sorts top process over time (easier to follow), "cpu direct" updates top process directly. +proc_sorting = "pid" + +#* Reverse sorting order, True or False. +proc_reversed = False + +#* Show processes as a tree. +proc_tree = False + +#* Use the cpu graph colors in the process list. +proc_colors = True + +#* Use a darkening gradient in the process list. +proc_gradient = True + +#* If process cpu usage should be of the core it's running on or usage of the total available cpu power. +proc_per_core = False + +#* Show process memory as bytes instead of percent. +proc_mem_bytes = True + +#* Show cpu graph for each process. +proc_cpu_graphs = True + +#* Use /proc/[pid]/smaps for memory information in the process info box (very slow but more accurate) +proc_info_smaps = False + +#* Show proc box on left side of screen instead of right. +proc_left = False + +#* (Linux) Filter processes tied to the Linux kernel(similar behavior to htop). +proc_filter_kernel = False + +#* In tree-view, always accumulate child process resources in the parent process. +proc_aggregate = False + +#* Sets the CPU stat shown in upper half of the CPU graph, "total" is always available. +#* Select from a list of detected attributes from the options menu. +cpu_graph_upper = "Auto" + +#* Sets the CPU stat shown in lower half of the CPU graph, "total" is always available. +#* Select from a list of detected attributes from the options menu. +cpu_graph_lower = "Auto" + +#* If gpu info should be shown in the cpu box. Available values = "Auto", "On" and "Off". +show_gpu_info = "Auto" + +#* Toggles if the lower CPU graph should be inverted. +cpu_invert_lower = True + +#* Set to True to completely disable the lower CPU graph. +cpu_single_graph = False + +#* Show cpu box at bottom of screen instead of top. +cpu_bottom = False + +#* Shows the system uptime in the CPU box. +show_uptime = True + +#* Show cpu temperature. +check_temp = True + +#* Which sensor to use for cpu temperature, use options menu to select from list of available sensors. +cpu_sensor = "Auto" + +#* Show temperatures for cpu cores also if check_temp is True and sensors has been found. +show_coretemp = True + +#* Set a custom mapping between core and coretemp, can be needed on certain cpus to get correct temperature for correct core. +#* Use lm-sensors or similar to see which cores are reporting temperatures on your machine. +#* Format "x:y" x=core with wrong temp, y=core with correct temp, use space as separator between multiple entries. +#* Example: "4:0 5:1 6:3" +cpu_core_map = "" + +#* Which temperature scale to use, available values: "celsius", "fahrenheit", "kelvin" and "rankine". +temp_scale = "celsius" + +#* Use base 10 for bits/bytes sizes, KB = 1000 instead of KiB = 1024. +base_10_sizes = False + +#* Show CPU frequency. +show_cpu_freq = True + +#* Draw a clock at top of screen, formatting according to strftime, empty string to disable. +#* Special formatting: /host = hostname | /user = username | /uptime = system uptime +clock_format = "%X" + +#* Update main ui in background when menus are showing, set this to false if the menus is flickering too much for comfort. +background_update = True + +#* Custom cpu model name, empty string to disable. +custom_cpu_name = "" + +#* Optional filter for shown disks, should be full path of a mountpoint, separate multiple values with whitespace " ". +#* Begin line with "exclude=" to change to exclude filter, otherwise defaults to "most include" filter. Example: disks_filter="exclude=/boot /home/user". +disks_filter = "" + +#* Show graphs instead of meters for memory values. +mem_graphs = True + +#* Show mem box below net box instead of above. +mem_below_net = False + +#* Count ZFS ARC in cached and available memory. +zfs_arc_cached = True + +#* If swap memory should be shown in memory box. +show_swap = True + +#* Show swap as a disk, ignores show_swap value above, inserts itself after first disk. +swap_disk = True + +#* If mem box should be split to also show disks info. +show_disks = True + +#* Filter out non physical disks. Set this to False to include network disks, RAM disks and similar. +only_physical = True + +#* Read disks list from /etc/fstab. This also disables only_physical. +use_fstab = True + +#* Setting this to True will hide all datasets, and only show ZFS pools. (IO stats will be calculated per-pool) +zfs_hide_datasets = False + +#* Set to true to show available disk space for privileged users. +disk_free_priv = False + +#* Toggles if io activity % (disk busy time) should be shown in regular disk usage view. +show_io_stat = True + +#* Toggles io mode for disks, showing big graphs for disk read/write speeds. +io_mode = False + +#* Set to True to show combined read/write io graphs in io mode. +io_graph_combined = False + +#* Set the top speed for the io graphs in MiB/s (100 by default), use format "mountpoint:speed" separate disks with whitespace " ". +#* Example: "/mnt/media:100 /:20 /boot:1". +io_graph_speeds = "" + +#* Set fixed values for network graphs in Mebibits. Is only used if net_auto is also set to False. +net_download = 100 + +net_upload = 100 + +#* Use network graphs auto rescaling mode, ignores any values set above and rescales down to 10 Kibibytes at the lowest. +net_auto = True + +#* Sync the auto scaling for download and upload to whichever currently has the highest scale. +net_sync = True + +#* Starts with the Network Interface specified here. +net_iface = "" + +#* Show battery stats in top right if battery is present. +show_battery = True + +#* Which battery to use if multiple are present. "Auto" for auto detection. +selected_battery = "Auto" + +#* Show power stats of battery next to charge indicator. +show_battery_watts = True + +#* Set loglevel for "~/.config/btop/btop.log" levels are: "ERROR" "WARNING" "INFO" "DEBUG". +#* The level set includes all lower levels, i.e. "DEBUG" will show all logging info. +log_level = "WARNING" + +#* Measure PCIe throughput on NVIDIA cards, may impact performance on certain cards. +nvml_measure_pcie_speeds = True + +#* Horizontally mirror the GPU graph. +gpu_mirror_graph = True + +#* Custom gpu0 model name, empty string to disable. +custom_gpu_name0 = "" + +#* Custom gpu1 model name, empty string to disable. +custom_gpu_name1 = "" + +#* Custom gpu2 model name, empty string to disable. +custom_gpu_name2 = "" + +#* Custom gpu3 model name, empty string to disable. +custom_gpu_name3 = "" + +#* Custom gpu4 model name, empty string to disable. +custom_gpu_name4 = "" + +#* Custom gpu5 model name, empty string to disable. +custom_gpu_name5 = "" diff --git a/btop/btop.log b/btop/btop.log new file mode 100644 index 0000000..f8d6211 --- /dev/null +++ b/btop/btop.log @@ -0,0 +1,24 @@ + +2024/02/12 (17:24:38) | ===> btop++ v.1.3.1 +2024/02/12 (17:24:38) | WARNING: ROCm SMI: Dynamic loading only supported for version 5 and 6 + +2024/02/12 (19:23:36) | ===> btop++ v.1.3.1 +2024/02/12 (19:23:36) | WARNING: ROCm SMI: Dynamic loading only supported for version 5 and 6 + +2024/02/12 (22:13:50) | ===> btop++ v.1.3.1 +2024/02/12 (22:13:50) | WARNING: ROCm SMI: Dynamic loading only supported for version 5 and 6 + +2024/02/13 (00:27:44) | ===> btop++ v.1.3.1 +2024/02/13 (00:27:44) | WARNING: ROCm SMI: Dynamic loading only supported for version 5 and 6 + +2024/02/13 (00:42:32) | ===> btop++ v.1.3.1 +2024/02/13 (00:42:32) | WARNING: ROCm SMI: Dynamic loading only supported for version 5 and 6 + +2024/02/15 (01:45:13) | ===> btop++ v.1.3.1 +2024/02/15 (01:45:13) | WARNING: ROCm SMI: Dynamic loading only supported for version 5 and 6 + +2024/02/15 (01:46:16) | ===> btop++ v.1.3.1 +2024/02/15 (01:46:16) | WARNING: ROCm SMI: Dynamic loading only supported for version 5 and 6 + +2024/02/15 (02:11:35) | ===> btop++ v.1.3.1 +2024/02/15 (02:11:35) | WARNING: ROCm SMI: Dynamic loading only supported for version 5 and 6 diff --git a/conky/conky-center.conf b/conky/conky-center.conf new file mode 100644 index 0000000..ca38b57 --- /dev/null +++ b/conky/conky-center.conf @@ -0,0 +1,51 @@ +# Conky Configuration + +conky.config = { + alignment = 'middle_middle', + background = false, + border_width = 0, + cpu_avg_samples = 2, + default_color = 'BFDDB2', + default_outline_color = 'white', + default_shade_color = 'white', + double_buffer = true, + draw_borders = false, + draw_graph_borders = true, + draw_outline = false, + draw_shades = false, + extra_newline = false, + font = 'Sarasa Mono K:size=12', + gap_x = 0, + gap_y = 80, + minimum_height = 5, + minimum_width = 5, + net_avg_samples = 2, + no_buffers = true, + out_to_console = false, + out_to_ncurses = false, + out_to_stderr = false, + out_to_x = true, + own_window = true, + own_window_class = 'Conky', + own_window_type = 'desktop', + own_window = true, + own_window_transparent = false, + own_window_colour = '#2a2f33', + own_window_argb_visual = false, + own_window_argb_value = 200, + own_window_type = 'desktop', + show_graph_range = false, + show_graph_scale = false, + stippled_borders = 0, + update_interval = 3.0, + uppercase = false, + use_spacer = 'none', + use_xft = true, +} + +conky.text = [[ +${font Sarasa Mono K:weight=Regular:size=64}${time %H:%M}${font} ${voffset -50}${alignc}${font MesloLGS NF:size=12} ${font} ${font Sarasa Mono K:weight=Regular:size=14}${time %A, %d %b}${font} +${offset 215} ${voffset 2}${font MesloLGS NF:size=12} ${font}${mem} ${font MesloLGS NF:size=12} ${font}${cpu cpu0}% +${offset 215} ${voffset 2}${font MesloLGS NF:size=12} ${font}${font Sarasa Mono K:size=12}${exec vnstat --short | grep "today" | awk '{print $2, $3}'}${font} ${font MesloLGS NF:size=12} ${font}${font Sarasa Mono K:size=12}${texeci 60 exec vnstat --short | grep "today" | awk '{print $5, $6}'}${font} +${voffset 20}${alignc}${texeci 360 exec curl "https://api.quotable.io/random?maxLength=66" 2> /dev/null | jq '.content'} +]] diff --git a/conky/conky-old.conf b/conky/conky-old.conf new file mode 100644 index 0000000..fc4a997 --- /dev/null +++ b/conky/conky-old.conf @@ -0,0 +1,96 @@ +conky.config = { + + --Various settings + + background = true, -- forked to background + cpu_avg_samples = 2, + diskio_avg_samples = 10, + double_buffer = true, + if_up_strictness = 'address', + net_avg_samples = 2, + no_buffers = true, + temperature_unit = 'celsius', + text_buffer_size = 2048, + update_interval = 1, + imlib_cache_size = 0, --spotify cove + + --Placement + + alignment = 'bottom_left', + gap_x = 15, + gap_y = 15, + minimum_height = 280, + minimum_width = 600, + maximum_width = 700, + + --Graphical + + border_inner_margin = 10, -- margin between border and text + border_outer_margin = 0, -- margin between border and edge of window + border_width = 0, -- border width in pixels + default_bar_width = 280, + default_bar_height = 10, + default_gauge_height = 25, + default_gauge_width =40, + default_graph_height = 40, + default_graph_width = 153, + default_shade_color = '#000000', + default_outline_color = '#000000', + draw_borders = false, --draw borders around text + draw_graph_borders = true, + draw_shades = false, + draw_outline = false, + stippled_borders = 0, + + --Textual + + extra_newline = false, + format_human_readable = true, + font = 'Feena Casual', + max_text_width = 0, + max_user_text = 16384, + override_utf8_locale = true, + short_units = true, + top_name_width = 21, + top_name_verbose = false, + uppercase = false, + use_spacer = 'none', + use_xft = true, + xftalpha = 1, + + --Windows + + own_window = true, + own_window_argb_value = 00, + own_window_argb_visual = true, + own_window_class = 'Conky', + own_window_colour = '#000000', + own_window_hints = 'undecorated,below,sticky,skip_taskbar,skip_pager', + own_window_transparent = no, + own_window_title = 'system_conky', + own_window_type = 'desktop',-- # options are: normal/override/dock/desktop/panel + + + --Colours + + default_color = '#ECEFF4', -- default color and border color + color1 = '#d4be98', + color2 = '#ea6962', + color3 = '#888888', + color4 = '#BDBDBD', + color5 = '#CCCCCC', + color6 = '#FFFFFF', + + --Signal Colours + color7 = '#1F7411', --green + color8 = '#FFA726', --orange + color9 = '#F1544B', --firebrick +}; + + +conky.text = [[ +${goto 35}${color1}${font Feena Casual:size=100}${time %I }${font Feena Casual:size=45}${color1}${time %A}${color2}${goto 35}${voffset 55}${color2}${font Feena Casual:size=22}${time %d} ${font Feena Casual:size=65}${voffset -15}${color2}${time %B} ${font Feena Casual:size=22}${goto 65}${voffset 34}${color2}${time %Y}${font Feena Casual:size=100}${goto 155}${voffset -54}${color1}${time %M}${font Feena Casual:size=25}${color2} ${time %P} + + +#${goto 60}${voffset -65}${font Feena Casual:size=23} ${time %A} | #${time %B %d %Y} +]] diff --git a/conky/conky.conf b/conky/conky.conf new file mode 100644 index 0000000..76bad48 --- /dev/null +++ b/conky/conky.conf @@ -0,0 +1,53 @@ +# Conky Configuration + +conky.config = { + alignment = 'top_left', + background = false, + border_width = 0, + cpu_avg_samples = 2, + default_color = 'aadae0', + default_outline_color = 'white', + default_shade_color = 'white', + double_buffer = true, + draw_borders = false, + draw_graph_borders = true, + draw_outline = false, + draw_shades = false, + extra_newline = false, + font = 'FiraSans:size=12', + gap_x = 30, + gap_y = 60, + minimum_height = 256, + minimum_width = 256, + net_avg_samples = 2, + no_buffers = true, + out_to_console = false, + out_to_ncurses = false, + out_to_stderr = false, + out_to_x = true, + own_window = true, + own_window_class = 'Conky', + own_window_type = 'desktop', + own_window = true, + own_window_transparent = true, + own_window_argb_visual = true, + own_window_type = 'desktop', + show_graph_range = false, + show_graph_scale = false, + stippled_borders = 0, + update_interval = 3.0, + uppercase = false, + use_spacer = 'none', + use_xft = true, + xinerama_head = 1; +} + +conky.text = [[ +${image ~/.face -p 0,0 -s 128x128 -n} +${voffset 90} +${font FiraSans:weight=Semibold:size=24}Hi, ${exec whoami}${font} + +${font FiraSans:weight=Semibold:size=28}It's ${time %H:%M}${font} +${font FiraSans:weight=Medium:size=20}${time %A}${font} +${font FiraSans:weight=Regular:size=20}${time %d %b}${font} +]] diff --git a/conky/conky_minimal_left.conf b/conky/conky_minimal_left.conf new file mode 100644 index 0000000..3400c73 --- /dev/null +++ b/conky/conky_minimal_left.conf @@ -0,0 +1,49 @@ +# Conky Configuration + +conky.config = { + alignment = 'bottom_left', + background = false, + border_width = 0, + cpu_avg_samples = 2, + default_color = 'BFDDB2', + default_outline_color = 'white', + default_shade_color = 'white', + double_buffer = true, + draw_borders = false, + draw_graph_borders = true, + draw_outline = false, + draw_shades = false, + extra_newline = false, + font = 'Sarasa Mono K:size=12', + gap_x = 30, + gap_y = 30, + minimum_height = 5, + minimum_width = 5, + net_avg_samples = 2, + no_buffers = true, + out_to_console = false, + out_to_ncurses = false, + out_to_stderr = false, + out_to_x = true, + own_window = true, + own_window_class = 'Conky', + own_window_type = 'desktop', + own_window = true, + own_window_transparent = true, + own_window_argb_visual = true, + own_window_type = 'desktop', + show_graph_range = false, + show_graph_scale = false, + stippled_borders = 0, + update_interval = 3.0, + uppercase = false, + use_spacer = 'none', + use_xft = true, +} + +conky.text = [[ +${font Sarasa Mono K:weight=Regular:size=24}${time %H:%M}${font} +${voffset 2}${font Sarasa Mono K:weight=Regular:size=14}${time %A, %d %b}${font} +${voffset 2}${font MesloLGS NF:size=12} ${font}${mem} ${font MesloLGS NF:size=12} ${font}${cpu cpu0}% +${voffset 2}${font MesloLGS NF:size=12} ${font}${font Sarasa Mono K:size=12}${exec vnstat --short | grep "today" | awk '{print $2, $3}'}${font} ${font MesloLGS NF:size=12} ${font}${font Sarasa Mono K:size=12}${texeci 60 exec vnstat --short | grep "today" | awk '{print $5, $6}'}${font} +]] diff --git a/conky/conky_notes.conf b/conky/conky_notes.conf new file mode 100644 index 0000000..c30ff90 --- /dev/null +++ b/conky/conky_notes.conf @@ -0,0 +1,90 @@ +conky.config = { + + --Various settings + + background = true, -- forked to background + cpu_avg_samples = 2, + diskio_avg_samples = 10, + double_buffer = true, + if_up_strictness = 'address', + net_avg_samples = 2, + no_buffers = true, + temperature_unit = 'celsius', + text_buffer_size = 2048, + update_interval = 2, + imlib_cache_size = 0, + + --Placement + + alignment = 'top_right', + gap_x = 30, + gap_y = 50, + minimum_height = 200, + minimum_width = 200, + maximum_width = 550, + + --Graphical + + border_inner_margin = 10, + + -- margin between border and text + border_outer_margin = 0, + -- margin between border and edge of window + border_width = 0, + -- border width in pixels + default_bar_width = 280, + default_bar_height = 2, + default_gauge_height = 25, + default_gauge_width =40, + default_graph_height = 40, + default_graph_width = 153, + default_shade_color = '#000000', + default_outline_color = '#828282', + draw_borders = false, + --draw borders around text + draw_graph_borders = true, + draw_shades = false, + draw_outline = false, + stippled_borders = 0, + + --Textual + + extra_newline = false, + format_human_readable = true, + font = 'Open Sans', + max_text_width = 0, + max_user_text = 16384, + override_utf8_locale = false, + short_units = true, + top_name_width = 21, + top_name_verbose = false, + uppercase = false, + use_spacer = 'none', + use_xft = true, + xftalpha = 1, + + --Windows + + own_window = true, + own_window_argb_value = 0, + own_window_argb_visual = true, + own_window_class = 'Conky', + own_window_colour = '#000000', + own_window_hints = 'undecorated,below,sticky,skip_taskbar,skip_pager', + own_window_transparent = yes, + own_window_title = 'Conky', + own_window_type = 'desktop', + + + --Colors + + default_color = '#BFDDB2', + color1 = '#BFDDB2', +}; + + +conky.text = [[ +${alignc}${font Sarasa Mono K:weight=Regular:size=24}Todo${color0}${font} +${voffset 15}${font Sarasa Mono K:weight=Light:size=18}${color1}\ +${exec awk '{printf "%d. %s\n", NR, $0}' < "$HOME/todo.md" | fmt --width=30 -s}${color1}${font} +]] diff --git a/conky/conky_stats.conf b/conky/conky_stats.conf new file mode 100644 index 0000000..bf46c59 --- /dev/null +++ b/conky/conky_stats.conf @@ -0,0 +1,50 @@ +# Conky Configuration + +conky.config = { + alignment = 'bottom_right', + background = false, + border_width = 0, + cpu_avg_samples = 2, + default_color = 'aadae0', + default_outline_color = 'white', + default_shade_color = 'white', + double_buffer = true, + draw_borders = false, + draw_graph_borders = true, + draw_outline = false, + draw_shades = false, + extra_newline = false, + font = 'FiraSans:size=12', + gap_x = 30, + gap_y = 30, + minimum_height = 5, + minimum_width = 5, + net_avg_samples = 2, + no_buffers = true, + out_to_console = false, + out_to_ncurses = false, + out_to_stderr = false, + out_to_x = true, + own_window = true, + own_window_class = 'Conky', + own_window_type = 'desktop', + own_window = true, + own_window_transparent = true, + own_window_argb_visual = true, + own_window_type = 'desktop', + show_graph_range = false, + show_graph_scale = false, + stippled_borders = 0, + update_interval = 3.0, + uppercase = false, + use_spacer = 'none', + use_xft = true, + xinerama_head = 1; +} + +conky.text = [[ +${font Symbols Nerd Font:size=12}${font} ${mem} +${font Symbols Nerd Font:size=12}${font} ${cpu cpu0}% +${font Symbols Nerd Font:size=12}${font} ${exec vnstat --short | grep "today" | awk '{print $2, $3}'} ${font Symbols Nerd Font:size=12}${font} ${texeci 60 exec vnstat --short | grep "today" | awk '{print $5, $6}'} +]] + diff --git a/conky/start-conky.desktop b/conky/start-conky.desktop new file mode 100755 index 0000000..46638e7 --- /dev/null +++ b/conky/start-conky.desktop @@ -0,0 +1,13 @@ +#!/usr/bin/env xdg-open + +[Desktop Entry] +Type=Application +Exec=conky +Icon=conky +X-GNOME-Autostart-enabled=true +X-MATE-Autostart-enabled=true +NoDisplay=false +Hidden=false +Name[en_US]=start-conky +Comment[en_US]= +X-GNOME-Autostart-Delay=0 diff --git a/conky/start-conky.sh b/conky/start-conky.sh new file mode 100755 index 0000000..f78d51d --- /dev/null +++ b/conky/start-conky.sh @@ -0,0 +1,3 @@ +#!/bin/bash +conky -c $HOME/.config/conky/conky.conf & +conky -c $HOME/.config/conky/conky_stats.conf & diff --git a/conky/system-all.conf b/conky/system-all.conf new file mode 100644 index 0000000..6d2b791 --- /dev/null +++ b/conky/system-all.conf @@ -0,0 +1,141 @@ +use_xft yes +xftfont DejaVu Sans:size=12 +xftalpha 0.8 +text_buffer_size 2048 + +# Update interval in seconds +update_interval 1 + +# This is the number of times Conky will update before quitting. +total_run_times 0 + +own_window yes +own_window_transparent yes +#own_window_type override +#own_window_type desktop +own_window_type desktop #use this if you want a nice shadow to appear around conky + +# If own_window is yes, these window manager hints may be used +own_window_hints undecorated,below,sticky,skip_taskbar,skip_pager + +# Use double buffering (reduces flicker, may not work for everyone) +double_buffer yes + +# Minimum size of text area +minimum_size 220 0 +#maximum_width 200 + +# Draw shades? +draw_shades no + +# Draw outlines? +draw_outline no + +# Draw borders around text +draw_borders no + +# Stippled borders? +stippled_borders 0 + +# border margins +border_margin 5 + +# border width +border_width 1 + +# Default colors and also border colors +default_color #5d5861 +#default_shade_color black +#default_outline_color grey +own_window_colour 000000 + +# Text alignment, other possible values are commented +#alignment top_left +alignment top_left +#alignment bottom_left +#alignment bottom_right +#alignment middle_right + +# Gap between borders of screen and text +# same thing as passing -x at command line +gap_x 20 +gap_y 50 + +# Subtract file system buffers from used memory? +no_buffers yes + +# set to yes if you want all text to be in uppercase +uppercase no + +# number of cpu samples to average +# set to 1 to disable averaging +cpu_avg_samples 2 + +# number of net samples to average +# set to 1 to disable averaging +net_avg_samples 2 + +# Force UTF8? note that UTF8 support required XFT +override_utf8_locale yes + +# Add spaces to keep things from moving about? This only affects certain objects. +use_spacer none + +own_window_argb_value 0 +own_window_argb_visual yes +TEXT +${font Zekton:Regular:size=11}SYSTEM ${hr 2} +#Command exec cut .. know the distribution +${font Zekton:Regular:size=12}${alignc 0}${exec cut -d '\' -f 1 /etc/issue} +${voffset -10}${font OpenLogos:size=40} +#Hostname +${voffset -85}${alignc =10}${font Capture it:size=16}${nodename} +#PC +${font Zekton:Regular:size=12}${alignc 0}ASUS +${font Zekton:Regular:size=12}${alignc 0} +${font DejaVu Sans:Regular:size=13}K${font} Kernel: ${alignr}${kernel} + +${font StyleBats:Regular:size=16}A${font} CPU 1: ${cpu cpu1}% ${alignr}${cpubar cpu1 8,60} +${font StyleBats:Regular:size=16}A${font} CPU 2: ${cpu cpu2}% ${alignr}${cpubar cpu2 8,60} + +${font StyleBats:Regular:size=16}g${font} RAM: $mem $memperc% ${alignr}${membar 8,60} +${font StyleBats:Regular:size=16}j${font} SWAP: $swap $swapperc% ${alignr}${swapbar 8,60} + +${font Webdings:Regular:size=16}~${font}Battery: ${battery_percent BAT0}% ${alignr}${battery_bar 8,60 BAT0} +${font StyleBats:Regular:size=16}q${font} Uptime: ${alignr}${uptime} + +${font Zekton:Regular:size=11}DATE ${hr 2} + +${alignc 0}${font Capture it:size=32}${time %H:%M}${font Zekton:size=10} +${voffset 2}${alignc}${time %A, %d %B %Y} + +${font Zekton:Regular:size=11}HD ${hr 2} + +${voffset 4}${font Pie charts for maps:Regular:size=14}7${font} ${voffset -5}Root: +${voffset 4}${fs_used /}/${fs_size /} ${alignr}${fs_bar 8,60 /} + +${font Pie charts for maps:Regular:size=14}m${font} ${voffset -5}Home: +${voffset 4}${fs_free /home}/${fs_size /home} ${alignr}${fs_bar 8,60 /home} + +${font Zekton:Regular:size=11}NETWORK ${hr 2} +#Aquí tengo que aclarar algo mis interfaces de red son +#Wired = enp2s0 and WiFi = wlp3s0 + +${if_existing /proc/net/route enp2s0} + +${voffset -15}${alignc 0}${font Capture it:size=12}W i r e d +${font PizzaDude Bullets:size=14}O${font} Up: ${upspeed enp2s0}${alignr}${upspeedgraph enp2s0 8,60 black black} +${voffset 4}${font PizzaDude Bullets:size=14}U${font} Down: ${downspeed enp2s0}${alignr}${downspeedgraph enp2s0 8,60 black black} +${voffset 4}${font PizzaDude Bullets:size=14}N${font} Upload: ${alignr}${totalup enp2s0} +${voffset 4}${font PizzaDude Bullets:size=14}T${font} Dowload: ${alignr}${totaldown enp2s0} +${else}${if_existing /proc/net/route wlp3s0}${alignc 0}${font Capture it:size=12}W i F i +${font}${alignc}SSID: ${wireless_essid wlp3s0} + +Signal: ${wireless_link_qual_perc wlp3s0}% ${alignr}${wireless_link_bar 8,60 wlp3s0} + +${font PizzaDude Bullets:size=14}O${font} Up: ${upspeed wlp3s0}${alignr}${upspeedgraph wlp3s0 8,60 black black} +${voffset 4}${font PizzaDude Bullets:size=14}U${font} Down: ${downspeed wlp3s0}${alignr}${downspeedgraph wlp3s0 8,60 black black} + +${voffset 4}${font PizzaDude Bullets:size=14}N${font} Upload: ${alignr}${totalup wlp3s0} +${voffset 4}${font PizzaDude Bullets:size=14}T${font} Download: ${alignr}${totaldown wlp3s0} +${endif} diff --git a/conky/test.conf b/conky/test.conf new file mode 100644 index 0000000..27152dd --- /dev/null +++ b/conky/test.conf @@ -0,0 +1,42 @@ +conky.config = { + alignment = 'top_right', + background = false, + border_width = 0, + cpu_avg_samples = 2, + default_color = 'BFDDB2', + default_outline_color = 'white', + default_shade_color = 'white', + double_buffer = true, + draw_borders = false, + draw_graph_borders = true, + draw_outline = false, + draw_shades = false, + extra_newline = false, + font = 'Sarasa Fixed K :size=18', + gap_x = 50, + gap_y = 50, + minimum_height = 5, + minimum_width = 5, + net_avg_samples = 2, + no_buffers = true, + out_to_console = false, + out_to_ncurses = false, + out_to_stderr = false, + out_to_x = true, + own_window = true, + own_window_class = 'Conky', + own_window_type = 'desktop', + own_window = true, + own_window_transparent = true, + own_window_argb_visual = true, + own_window_type = 'desktop', + show_graph_range = false, + show_graph_scale = false, + stippled_borders = 0, + update_interval = 1.0, + uppercase = false, + use_spacer = 'none', + use_xft = true +} +conky.text=[[ +]] diff --git a/dunst/dunstrc b/dunst/dunstrc new file mode 100644 index 0000000..39296fd --- /dev/null +++ b/dunst/dunstrc @@ -0,0 +1,63 @@ +[global] + monitor = 0 + follow = keyboard + geometry = "300x10-8+34" + indicate_hidden = yes + shrink = true + transparency = 6 + notification_height = 0 + separator_height = 20 + padding = 20 + horizontal_padding = 10 + frame_width = 1 + frame_color = "#ffffff" + separator_color = auto + font = FiraSans + markup = full + format = "<b>%s</b> %p\n<i>%b</i>" + alignment = left + show_age_threshold = 60 + word_wrap = yes + ellipsize = middle + ignore_newline = no + stack_duplicates = true + hide_duplicate_count = false + show_indicators = yes + icon_position = left + max_icon_size = 48 + icon_path = /usr/share/icons/Papirus/16x16/status/:/usr/share/icons/Papirus/16x16/devices/:/usr/share/icons/Papirus/16x16/apps/ + sticky_history = yes + history_length = 20 + always_run_script = true + startup_notification = false + browser = /usr/bin/firefox -new-tab + verbosity = mesg + corner_radius = 0 + force_xinerama = false + mouse_left_click = close_current + mouse_middle_click = do_action + mouse_right_click = close_all + +[shortcuts] + close = ctrl+space + close_all = ctrl+shift+space + history = ctrl+grave + context = ctrl+shift+period + +[urgency_low] +timeout = 4 +background = "#2a2f33" +foreground = "#aadae0" +frame_color = "#aadae0" + +[urgency_normal] +timeout = 8 +background = "#2a2f33" +foreground = "#baeff6" +frame_color = "#baeff6" + +[urgency_critical] +timeout = 0 +background = "#2a2f33" +foreground = "#ffbaba" +frame_color = "#ffbaba" @@ -0,0 +1,187 @@ +# Basic Settings +set hidden true +# set drawbox true +# set icons false +set ignorecase true + +# Custom Functions +cmd open ${{ + case $(file --mime-type "$f" -bL) in + text/*|application/json) $EDITOR "$f";; + *) xdg-open "$f" ;; + esac +}} + +cmd mkdir ${{ + printf "Directory Name: " + read ans + mkdir $ans +}} + +cmd mkfile ${{ + printf "File Name: " + read ans + $EDITOR $ans +}} + +cmd chmod ${{ + printf "Mode Bits: " + read ans + + for file in "$fx" + do + chmod $ans $file + done + + lf -remote 'send reload' +}} + +cmd sudomkfile ${{ + printf "File Name: " + read ans + sudo $EDITOR $ans +}} + +cmd setwallpaper %cp "$f" ~/.config/wallpaper.png && xwallpaper --zoom "$f" + +cmd fzf_jump ${{ + res="$(find . -maxdepth 3 | fzf --reverse --header='Jump to location')" + if [ -f "$res" ]; then + cmd="select" + elif [ -d "$res" ]; then + cmd="cd" + fi + lf -remote "send $id $cmd \"$res\"" +}} + +cmd broot_jump ${{ + f=$(mktemp) + res="$(broot --outcmd $f && cat $f | sed 's/cd //')" + rm -f "$f" + if [ -f "$res" ]; then + cmd="select" + elif [ -d "$res" ]; then + cmd="cd" + fi + lf -remote "send $id $cmd \"$res\"" +}} + +cmd dragon %dragon -a -x $fx +cmd dragon-stay %dragon -a $fx +cmd dragon-individual %dragon $fx +cmd cpdragon %cpdragon +cmd mvdragon %mvdragon +cmd dlfile %dlfile + +# Archive bindings +cmd unarchive ${{ + case "$f" in + *.zip) unzip "$f" ;; + *.tar.gz) tar -xzvf "$f" ;; + *.tar.bz2) tar -xjvf "$f" ;; + *.tar) tar -xvf "$f" ;; + *) echo "Unsupported format" ;; + esac +}} + +cmd zip %zip -r "$f" "$f" +cmd tar %tar cvf "$f.tar" "$f" +cmd targz %tar cvzf "$f.tar.gz" "$f" +cmd tarbz2 %tar cjvf "$f.tar.bz2" "$f" + +# Trash cli bindings +cmd trash ${{ + files=$(printf "$fx" | tr '\n' ';') + while [ "$files" ]; do + # extract the substring from start of string up to delimiter. + # this is the first "element" of the string. + file=${files%%;*} + + trash-put "$(basename "$file")" + # if there's only one element left, set `files` to an empty string. + # this causes us to exit this `while` loop. + # else, we delete the first "element" of the string from files, and move onto the next. + if [ "$files" = "$file" ]; then + files='' + else + files="${files#*;}" + fi + done +}} + +cmd clear_trash %trash-empty + +cmd restore_trash ${{ + trash-restore +}} + +cmd stripspace %stripspace "$f" + +# Bindings +# Remove some defaults +map m +map o +map n +map "'" +map '"' +map d +map c +map e +map f + +# File Openers +map ee $$EDITOR "$f" +map u $view "$f" + +# Archive Mappings +map az zip +map at tar +map ag targz +map ab targz +map au unarchive + +# Trash Mappings +map dd trash +map tc clear_trash +map tr restore_trash + +# Broot Mapping +map f broot_jump + +# Dragon Mapping +map dr dragon +map ds dragon-stay +map di dragon-individual +map dm mvdragon +map dc cpdragon +map dl dlfile + +map ss stripspace + +# Basic Functions +map . set hidden! +map DD delete +map p paste +map x cut +map y copy +map <enter> open +map mf mkfile +map mr sudomkfile +map md mkdir +map ms $mkscript +map ch chmod +map bg setwallpaper +map o open_config +map br $vimv $fx +map r rename +map H top +map L bottom +map R reload +map C clear +map U unselect + + +map gD cd ~/Downloads +map ge cd ~/Desktop + +map \;j cd ~ diff --git a/mpd/mpd.conf b/mpd/mpd.conf new file mode 100644 index 0000000..884699d --- /dev/null +++ b/mpd/mpd.conf @@ -0,0 +1,31 @@ +# Recommended location for database +db_file "~/.local/share/mpd/database" + +# Logs to systemd journal +log_file "syslog" + +# The music directory is by default the XDG directory, uncomment to amend and choose a different directory +music_directory "/data/Music/" + +# Uncomment to refresh the database whenever files in the music_directory are changed +auto_update "yes" +auto_update_depth "1" +restore_paused "yes" + +# Uncomment to enable the functionalities +playlist_directory "~/.local/share/mpd/playlists" +pid_file "~/.local/share/mpd/pid" +state_file "~/.local/share/mpd/state" +sticker_file "~/.local/share/mpd/sticker.sql" + +audio_output { + type "fifo" + name "my_fifo" + path "/tmp/mpd.fifo" + format "44100:16:2" +} + +audio_output { + type "pulse" + name "pulse audio" +} diff --git a/mpv/input.conf b/mpv/input.conf new file mode 100644 index 0000000..a475f10 --- /dev/null +++ b/mpv/input.conf @@ -0,0 +1,132 @@ +# vim: syntax=config + + +# Mouse + +MOUSE_BTN2 cycle pause +MOUSE_BTN3 ignore +MOUSE_BTN4 ignore +MOUSE_BTN5 ignore +MOUSE_BTN6 ignore + + + +# Trackpad + +AXIS_UP ignore +AXIS_DOWN ignore +AXIS_LEFT ignore +AXIS_RIGHT ignore + + + +# Arrow/navigation keys + +RIGHT osd-msg-bar seek +5 relative+keyframes +LEFT osd-msg-bar seek -5 relative+keyframes +SHIFT+RIGHT osd-msg-bar seek +1 relative+exact +SHIFT+LEFT osd-msg-bar seek -1 relative+exact +CTRL+RIGHT frame-step ; show-text "Frame: ${estimated-frame-number} / ${estimated-frame-count}" +CTRL+LEFT frame-back-step ; show-text "Frame: ${estimated-frame-number} / ${estimated-frame-count}" + +UP add volume +2 ; show-text "Volume: ${volume}" +DOWN add volume -2 ; show-text "Volume: ${volume}" +SHIFT+UP osd-msg-bar seek +120 relative+keyframes +SHIFT+DOWN osd-msg-bar seek -120 relative+keyframes + +PGUP osd-msg-bar seek +600 relative+keyframes +PGDWN osd-msg-bar seek -600 relative+keyframes + +ALT+RIGHT sub-seek +1 ; show-text "Sub Seek +1" +ALT+LEFT sub-seek -1 ; show-text "Sub Seek -1" + +#ALT+RIGHT add video-pan-x -0.01 +#ALT+LEFT add video-pan-x +0.01 +#ALT+UP add video-pan-y +0.01 +#ALT+DOWN add video-pan-y -0.01 + +#META+RIGHT add video-zoom +0.05 +#META+LEFT add video-zoom -0.05 +#META+UP add video-zoom +0.05 +#META+DOWN add video-zoom -0.05 + + + +# ` [1] [2] [3] [4] [5] [6] [7] [8] [9] [0] - = +# ~ [!] @ # $ % ^ & * ( ) _ + + +1 add contrast -1 ; show-text "Contrast: ${contrast}" +2 add contrast +1 ; show-text "Contrast: ${contrast}" +3 add brightness -1 ; show-text "Brightness: ${brightness}" +4 add brightness +1 ; show-text "Brightness: ${brightness}" +5 add gamma -1 ; show-text "Gamma: ${gamma}" +6 add gamma +1 ; show-text "Gamma: ${gamma}" +7 add saturation -1 ; show-text "Saturation: ${saturation}" +8 add saturation +1 ; show-text "Saturation: ${saturation}" + +9 add volume -2 ; show-text "Volume: ${volume}" +0 add volume +2 ; show-text "Volume: ${volume}" + +! cycle ontop + +# CTRL+1 show-text "Shaders: ${glsl-shaders}" +# CTRL+2 change-list glsl-shaders append "~/.mpv/shaders/ravu-r3-rgb.hook" +# CTRL+3 change-list glsl-shaders append "~/.mpv/shaders/FSRCNNX_x2_8-0-4-1.glsl" +# CTRL+4 change-list glsl-shaders toggle "~/.mpv/shaders/KrigBilateral.glsl" +# CTRL+5 change-list glsl-shaders toggle "~/.mpv/shaders/SSimSuperRes.glsl" +# CTRL+6 change-list glsl-shaders toggle "~/.mpv/shaders/SSimDownscaler.glsl" +# CTRL+7 change-list glsl-shaders toggle "~/.mpv/shaders/FineSharp.glsl" +# CTRL+8 change-list glsl-shaders toggle "~/.mpv/shaders/adaptive-sharpen.glsl" +# CTRL+0 set glsl-shaders "" + + +Q quit +q script-binding auto_save_state/quit-watch-later-conditional + + + + +# [esc] [space] [backspace] +# [tab] [enter] + + +ESC cycle fullscreen +SPACE cycle pause +IDEOGRAPHIC_SPACE cycle pause +TAB cycle mute +ENTER show-progress + +# Numpad + +KP0 ignore +KP1 ignore +KP2 ignore +KP3 ignore +KP4 ignore +KP5 ignore +KP6 ignore +KP7 ignore +KP8 ignore +KP9 ignore +KP_DEC ignore +KP_ENTER ignore + + + +# Media Keys + +POWER script-binding auto_save_state/quit-watch-later-conditional +MENU show-progress +PLAY cycle pause +PAUSE cycle pause +PLAYPAUSE cycle pause +STOP script-binding auto_save_state/quit-watch-later-conditional +FORWARD osd-msg-bar seek +5 relative keyframes +REWIND osd-msg-bar seek -5 relative keyframes +NEXT script-binding betterchapters/chapterplaylist-next #; show-text "${?chapter:Chapter: ${chapter}}" +PREV script-binding betterchapters/chapterplaylist-prev #; show-text "${?chapter:Chapter: ${chapter}}" +VOLUME_UP add volume +2 ; show-text "Volume: ${volume}" +VOLUME_DOWN add volume -2 ; show-text "Volume: ${volume}" +MUTE cycle mute +CLOSE_WIN quit +a script-message cycle-profiles "low-quality;mid-quality;high-quality" diff --git a/mpv/lua-modules/auto-profiles-functions.lua b/mpv/lua-modules/auto-profiles-functions.lua new file mode 100644 index 0000000..539a84d --- /dev/null +++ b/mpv/lua-modules/auto-profiles-functions.lua @@ -0,0 +1,122 @@ +local utils = require 'mp.utils' +local msg = require 'mp.msg' + +-- Quality levels +local HIGH = "High Quality" +local MID = "Mid Quality" +local LOW = "Low Quality" +-- Platform +local is_linux, is_osx, is_windows = false, false, false +local exec_cache = {} + + +local function exec(process, force_exec) + local key = table.concat(process, " ") + if force_exec or exec_cache[key] == nil or exec_cache[key].error then + local p_ret = utils.subprocess({args = process, playback_only = false}) + exec_cache[key] = p_ret + if p_ret.error and p_ret.error == "init" then + msg.error("executable not found: " .. key) + end + return p_ret + else + return exec_cache[key] + end +end + + +local function get_platform() + local is_linux = false + local is_osx = false + local is_windows = type(package) == 'table' and type(package.config) == 'string' and string.sub(package.config, 1, 1) == '\\' + if not is_windows then + uname = exec({"uname"}) + is_linux = uname.stdout == "Linux\n" + is_osx = uname.stdout == "Darwin\n" + end + return is_linux, is_osx, is_windows +end + + +function on_battery() + if is_osx then + local bat = exec({"/usr/bin/pmset", "-g", "batt"}, true) + return string.match(bat.stdout, "Now drawing from 'Battery Power'") + elseif is_linux then + local res = exec({"/bin/cat", "/sys/class/power_supply/AC0/online"}, true) + return res.stdout == "0\n" + elseif is_windows then + msg.warn("on_battery() not implemented on windows. PRs welcome") + end + msg.warn("assuming AC power") + return false +end + + +-- This is a crude attempt to figure out if a (beefy) dedicated GPU is present. +-- Can't identify the actually used GPU but works when we assume that an existing +-- dedicated GPU can/will be used in case we are drawing power from AC. + -- local r = exec({"lshw", "-C", 'display'}) + -- local r = exec({"nvidia-smi"}) + -- r.stdout = string.lower(r.stdout) + -- return string.find(r.stdout, "amd") ~= nil or string.find(r.stdout, "nvidia") ~= nil + -- return string.find(r.stdout, "mpv") ~= nil +function dedicated_gpu() + if is_osx then + local r = exec({"system_profiler", "SPDisplaysDataType"}) + return string.find(r.stdout, "Chipset Model: Radeon") ~= nil or string.find(r.stdout, "Chipset Model: NVIDIA GeForce") ~= nil + -- Untested + elseif is_linux then + local nv = os.getenv("__NV_PRIME_RENDER_OFFLOAD") + return nv ~= nil + elseif is_windows then + msg.warn("dedicated_gpu() not implemented on windows. PRs welcome") + end + msg.warn("assuming dedicated GPU") + return true +end + + +local function determine_level(width, height, fps) + + if on_battery() then + return LOW + end + + if dedicated_gpu() then + if width > 4096 then + return MID + end + if width > 1920 and fps > 61 then + return MID + end + return HIGH + else + if width > 1919 then + return LOW + end + if fps > 58 then + return LOW + end + return MID + end + + msg.error("could not determine profile") + msg.warn("assuming HIGH") + return HIGH +end + + +local function is_level(level) + return function(width, height, fps) + local l = determine_level(width, height, fps) + return l == level + end +end + + +is_high = is_level(HIGH) +is_mid = is_level(MID) +is_low = is_level(LOW) + +is_linux, is_osx, is_windows = get_platform() diff --git a/mpv/lua-modules/scroll-list.lua b/mpv/lua-modules/scroll-list.lua new file mode 100644 index 0000000..0666f5a --- /dev/null +++ b/mpv/lua-modules/scroll-list.lua @@ -0,0 +1,236 @@ +local mp = require 'mp' +local scroll_list = { + global_style = [[]], + header_style = [[{\q2\fs35\c&00ccff&}]], + list_style = [[{\q2\fs25\c&Hffffff&}]], + wrapper_style = [[{\c&00ccff&\fs16}]], + cursor_style = [[{\c&00ccff&}]], + selected_style = [[{\c&Hfce788&}]], + + cursor = [[➤\h]], + indent = [[\h\h\h\h]], + + num_entries = 16, + wrap = false, + empty_text = "no entries" +} + +--formats strings for ass handling +--this function is taken from https://github.com/mpv-player/mpv/blob/master/player/lua/console.lua#L110 +function scroll_list.ass_escape(str) + str = str:gsub('\\', '\\\239\187\191') + str = str:gsub('{', '\\{') + str = str:gsub('}', '\\}') + -- Precede newlines with a ZWNBSP to prevent ASS's weird collapsing of + -- consecutive newlines + str = str:gsub('\n', '\239\187\191\\N') + -- Turn leading spaces into hard spaces to prevent ASS from stripping them + str = str:gsub('\\N ', '\\N\\h') + str = str:gsub('^ ', '\\h') + return str +end +--appends the entered text to the overlay +function scroll_list:append(text) + if text == nil then return end + self.ass.data = self.ass.data .. text + end + +--appends a newline character to the osd +function scroll_list:newline() + self.ass.data = self.ass.data .. '\\N' +end + +--re-parses the list into an ass string +--if the list is closed then it flags an update on the next open +function scroll_list:update() + if self.hidden then self.flag_update = true + else self:update_ass() end +end + +--prints the header to the overlay +function scroll_list:format_header() + self:append(self.header_style) + self:append(self.header) + self:newline() +end + +--formats each line of the list and prints it to the overlay +function scroll_list:format_line(index, item) + self:append(self.list_style) + + if index == self.selected then self:append(self.cursor_style..self.cursor..self.selected_style) + else self:append(self.indent) end + + self:append(item.style) + self:append(item.ass) + self:newline() +end + +--refreshes the ass text using the contents of the list +function scroll_list:update_ass() + self.ass.data = self.global_style + self:format_header() + + if #self.list < 1 then + self:append(self.empty_text) + self.ass:update() + return + end + + local start = 1 + local finish = start+self.num_entries-1 + + --handling cursor positioning + local mid = math.ceil(self.num_entries/2)+1 + if self.selected+mid > finish then + local offset = self.selected - finish + mid + + --if we've overshot the end of the list then undo some of the offset + if finish + offset > #self.list then + offset = offset - ((finish+offset) - #self.list) + end + + start = start + offset + finish = finish + offset + end + + --making sure that we don't overstep the boundaries + if start < 1 then start = 1 end + local overflow = finish < #self.list + --this is necessary when the number of items in the dir is less than the max + if not overflow then finish = #self.list end + + --adding a header to show there are items above in the list + if start > 1 then self:append(self.wrapper_style..(start-1)..' item(s) above\\N\\N') end + + for i=start, finish do + self:format_line(i, self.list[i]) + end + + if overflow then self:append('\\N'..self.wrapper_style..#self.list-finish..' item(s) remaining') end + self.ass:update() +end + +--moves the selector down the list +function scroll_list:scroll_down() + if self.selected < #self.list then + self.selected = self.selected + 1 + self:update_ass() + elseif self.wrap then + self.selected = 1 + self:update_ass() + end +end + +--moves the selector up the list +function scroll_list:scroll_up() + if self.selected > 1 then + self.selected = self.selected - 1 + self:update_ass() + elseif self.wrap then + self.selected = #self.list + self:update_ass() + end +end + +--adds the forced keybinds +function scroll_list:add_keybinds() + for _,v in ipairs(self.keybinds) do + mp.add_forced_key_binding(v[1], 'dynamic/'..self.ass.id..'/'..v[2], v[3], v[4]) + end +end + +--removes the forced keybinds +function scroll_list:remove_keybinds() + for _,v in ipairs(self.keybinds) do + mp.remove_key_binding('dynamic/'..self.ass.id..'/'..v[2]) + end +end + +--opens the list and sets the hidden flag +function scroll_list:open_list() + self.hidden = false + if not self.flag_update then self.ass:update() + else self.flag_update = false ; self:update_ass() end +end + +--closes the list and sets the hidden flag +function scroll_list:close_list() + self.hidden = true + self.ass:remove() +end + +--modifiable function that opens the list +function scroll_list:open() + self:open_list() + self:add_keybinds() +end + +--modifiable function that closes the list +function scroll_list:close () + self:remove_keybinds() + self:close_list() +end + +--toggles the list +function scroll_list:toggle() + if self.hidden then self:open() + else self:close() end +end + +--clears the list in-place +function scroll_list:clear() + local i = 1 + while self.list[i] do + self.list[i] = nil + i = i + 1 + end +end + +--added alias for ipairs(list.list) for lua 5.1 +function scroll_list:ipairs() + return ipairs(self.list) +end + +--append item to the end of the list +function scroll_list:insert(item) + self.list[#self.list + 1] = item +end + +local metatable = { + __index = function(t, key) + if scroll_list[key] ~= nil then return scroll_list[key] + elseif key == "__current" then return t.list[t.selected] + elseif type(key) == "number" then return t.list[key] end + end, + __newindex = function(t, key, value) + if type(key) == "number" then rawset(t.list, key, value) + else rawset(t, key, value) end + end, + __scroll_list = scroll_list, + __len = function(t) return #t.list end, + __ipairs = function(t) return ipairs(t.list) end +} + +--creates a new list object +function scroll_list:new() + local vars + vars = { + ass = mp.create_osd_overlay('ass-events'), + hidden = true, + flag_update = true, + + header = "header \\N ----------------------------------------------", + list = {}, + selected = 1, + + keybinds = { + {'DOWN', 'scroll_down', function() vars:scroll_down() end, {repeatable = true}}, + {'UP', 'scroll_up', function() vars:scroll_up() end, {repeatable = true}}, + {'ESC', 'close_browser', function() vars:close() end, {}} + } + } + return setmetatable(vars, metatable) +end + +return scroll_list:new() diff --git a/mpv/lua-modules/user-input-module.lua b/mpv/lua-modules/user-input-module.lua new file mode 100644 index 0000000..2c25d42 --- /dev/null +++ b/mpv/lua-modules/user-input-module.lua @@ -0,0 +1,49 @@ +--[[ + This is a module designed to interface with mpv-user-input + https://github.com/CogentRedTester/mpv-user-input + + Loading this script as a module will return a table with two functions to format + requests to get and cancel user-input requests. See the README for details. + + Alternatively, developers can just paste these functions directly into their script, + however this is not recommended as there is no guarantee that the formatting of + these requests will remain the same for future versions of user-input. +]] + +local mp = require 'mp' +local mod = {} + +local name = mp.get_script_name() +local counter = 1 + +-- sends a request to ask the user for input using formatted options provided +-- creates a script message to recieve the response and call fn +function mod.get_user_input(fn, options) + options = options or {} + local response_string = name.."/__user_input_request/"..counter + counter = counter + 1 + + -- create a callback for user-input to respond to + mp.register_script_message(response_string, function(input, err) + mp.unregister_script_message(response_string) + fn(err == "" and input or nil, err) + end) + + -- send the input command + mp.commandv("script-message-to", "user_input", "request-user-input", + response_string, + name .. '/' .. (options.id or ""), -- id code for the request + options.request_text or options.text or (name.." is requesting user input:"), + options.default_input or "", + options.queueable and "1" or "", + options.replace and "1" or "" + ) +end + +-- sends a request to cancel all input requests with the given id +function mod.cancel_user_input(id) + id = name .. '/' .. (id or "") + mp.commandv("script-message-to", "user_input", "cancel-user-input", id) +end + +return mod diff --git a/mpv/mpv.conf b/mpv/mpv.conf new file mode 100644 index 0000000..0ced36d --- /dev/null +++ b/mpv/mpv.conf @@ -0,0 +1,177 @@ +# vim: syntax=config + + +########### +# General # +########### + +input-ipc-server=/tmp/mpvsocket # listen for IPC on this socket +#load-stats-overlay=no # use local stats.lua +#save-position-on-quit # handled by a script + +msg-module # prepend module name to log messages +msg-color # color log messages on terminal +term-osd-bar # display a progress bar on the terminal +use-filedir-conf # look for additional config files in the directory of the opened file +keep-open # keep the player open when a file's end is reached +autofit-larger=100%x95% # resize window in case it's larger than W%xH% of the screen +cursor-autohide-fs-only # don't autohide the cursor in window mode, only fullscreen +input-media-keys=no # enable/disable OSX media keys +cursor-autohide=1000 # autohide the curser after 1s +prefetch-playlist=yes +force-seekable=yes + +screenshot-format=png +screenshot-png-compression=8 +screenshot-template='~/Desktop/%F (%P) %n' + +hls-bitrate=max # use max quality for HLS streams + +[default] + + +######### +# Cache # +######### + +cache=yes +demuxer-max-bytes=400MiB +demuxer-max-back-bytes=150MiB + + +############# +# OSD / OSC # +############# + +osd-level=1 # enable osd and display --osd-status-msg on interaction +osd-duration=2500 # hide the osd after x ms +osd-status-msg='${time-pos} / ${duration}${?percent-pos: (${percent-pos}%)}${?frame-drop-count:${!frame-drop-count==0: Dropped: ${frame-drop-count}}}\n${?chapter:Chapter: ${chapter}}' + +osd-font='Sarasa Mono K' +osd-font-size=36 +osd-color='#FFFFFFFF' # ARGB format +osd-border-color='#FF000000' # ARGB format +#osd-shadow-offset=1 # pixel width for osd text and progress bar +# osd-bar-align-y=-1 # progress bar y alignment (-1 top, 0 centered, 1 bottom) +osd-border-size=2 # size for osd text and progress bar +# osd-bar-h=2 # height of osd bar as a fractional percentage of your screen height +# osd-bar-w=100 # width of " " " + + +# Subtitles # + +demuxer-mkv-subtitle-preroll=yes # try to show embedded subs when seeking even when no index information is present +demuxer-mkv-subtitle-preroll-secs=2 + +sub-auto=fuzzy # external subs don't have to match the file name exactly to autoload +sub-file-paths-append=ass # search for external subs in these relative subdirectories +sub-file-paths-append=srt +sub-file-paths-append=sub +sub-file-paths-append=subs +sub-file-paths-append=subtitles + +embeddedfonts=yes # use embedded fonts for SSA/ASS subs +sub-fix-timing=no # do not try to fix gaps (which might make it worse in some cases) +sub-ass-force-style=Kerning=yes # allows you to override style parameters of ASS scripts +sub-use-margins +sub-ass-force-margins + +# the following options only apply to subtitles without own styling (i.e. not ASS but e.g. SRT) +sub-font="Sarasa UI K" +sub-font-size=40 +sub-color="#FFFFFFFF" +sub-border-color="#FF000000" +sub-border-size=2.0 +sub-shadow-offset=1 +sub-shadow-color="#33000000" +sub-spacing=0.5 + + +# Languages # + +slang=en,eng # automatically select these subtitles (decreasing priority) +alang=ja,jp,jpn,en,eng # automatically select these audio tracks (decreasing priority) + + +# Audio # + +audio-file-auto=fuzzy # external audio doesn't has to match the file name exactly to autoload +audio-pitch-correction=yes # automatically insert scaletempo when playing with higher speed +volume-max=115 # maximum volume in %, everything above 100 results in amplification +volume=90 # default volume, 100 = unchanged + + +# Video Output # + +# Defaults for all profiles +tscale=oversample # [sharp] oversample <-> linear (triangle) <-> catmull_rom <-> mitchell <-> gaussian <-> bicubic [smooth] +opengl-early-flush=no +opengl-pbo=no # "yes" is currently bugged: https://github.com/mpv-player/mpv/issues/4988 +hwdec=no + +ytdl-format=bestvideo[height<=?720][fps<=?30][vcodec!=?vp9]+bestaudio/best + +[high-quality] +profile-desc=cond:is_high(get('width', 0), get('height', 0), get('estimated-vf-fps', 0)) +#scale=ewa_hanning +#scale-radius=3.2383154841662362 +scale=ewa_lanczossharp +cscale=ewa_lanczossoft +dscale=mitchell +scale-antiring=0 +cscale-antiring=0 +dither-depth=auto +correct-downscaling=yes +sigmoid-upscaling=yes +deband=yes + +[mid-quality] +profile-desc=cond:is_mid(get('width', 0), get('height', 0), get('estimated-vf-fps', 0)) +scale=spline36 +cscale=bicubic +dscale=mitchell +scale-antiring=1.0 +cscale-antiring=1.0 +dither-depth=auto +correct-downscaling=yes +sigmoid-upscaling=yes +deband=yes +glsl-shaders-set="" + +[low-quality] +profile-desc=cond:is_low(get('width', 0), get('height', 0), get('estimated-vf-fps', 0)) +scale=bilinear +cscale=bilinear +dscale=bilinear +scale-antiring=0 +cscale-antiring=0 +dither-depth=no +correct-downscaling=no +sigmoid-upscaling=no +deband=no +glsl-shaders-set="" + +[4K-lavc-threads] +profile-desc=cond:get('width', -math.huge) >= 3840 +vd-lavc-threads=32 + +[4K-lavc-threads-inverted] +profile-desc=cond:get('width', math.huge) < 3840 +vd-lavc-threads=0 + + +# Protocol Specific Configuration # + +[protocol.https] +cache=yes +user-agent='Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:72.0) Gecko/20100101 Firefox/72.0' + +[protocol.http] +cache=yes +user-agent='Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:72.0) Gecko/20100101 Firefox/72.0' + +[360p] +ytdl-format=bestvideo[height<=360][vcodec=vp9]+bestaudio/bestvideo[height<=360]+bestaudio/best[height<=360]/best + +[480p] +ytdl-format=bestvideo[height<=480][vcodec=vp9]+bestaudio/bestvideo[height<=480]+bestaudio/best[height<=480]/best diff --git a/mpv/scripts/auto-audio-device.lua b/mpv/scripts/auto-audio-device.lua new file mode 100644 index 0000000..92fcdf8 --- /dev/null +++ b/mpv/scripts/auto-audio-device.lua @@ -0,0 +1,36 @@ +local mp = require 'mp' + + +local auto_change = true +local av_map = { + ["default"] = "auto", + ["DELL U2312HM"] = "coreaudio/AppleUSBAudioEngine:FiiO:DigiHug USB Audio:1a160000:3", -- FiiO DAC + ["SONY TV *00"] = "coreaudio/AppleGFXHDAEngineOutputDP:0:{D94D-8204-01010101}", -- HDMI + ["Color LCD"] = "coreaudio/AppleHDAEngineOutput:1B,0,1,1:0", -- Built-in +} + +function set_audio_device(obs_display) + if obs_display ~= nil and not auto_change then + return + end + + local display = obs_display or mp.get_property_native("display-names") + if not display or not display[1] then + --print("Invalid display return value: " .. tostring(display)) + return + end + + local new_adev = av_map[display[1]] or av_map["default"] + local current_adev = mp.get_property("audio-device", av_map["default"]) + if new_adev ~= current_adev then + mp.osd_message("Audio device: " .. new_adev) + mp.set_property("audio-device", new_adev) + end +end + +mp.observe_property("display-names", "native", function(name, value) set_audio_device(value) end) +mp.add_key_binding("", "set-audio-device", function() set_audio_device(nil) end) +mp.add_key_binding("", "toggle-switching", function() + auto_change = not auto_change + mp.osd_message("Audio device switching: " .. tostring(auto_change)) +end) diff --git a/mpv/scripts/auto-profiles.lua b/mpv/scripts/auto-profiles.lua new file mode 100644 index 0000000..32e1809 --- /dev/null +++ b/mpv/scripts/auto-profiles.lua @@ -0,0 +1,198 @@ +--[[ + +Automatically apply profiles based on runtime conditions. +At least mpv 0.21.0 is required. + +This script queries the list of loaded config profiles, and checks the +"profile-desc" field of each profile. If it starts with "cond:", the script +parses the string as Lua expression, and evaluates it. If the expression +returns true, the profile is applied, if it returns false, it is ignored. + +Expressions can reference properties by accessing "p". For example, "p.pause" +would return the current pause status. If the variable name contains any "_" +characters, they are turned into "-". For example, "playback_time" would +return the property "playback-time". (Although you can also just write +p["playback-time"].) + +Note that if a property is not available, it will return nil, which can cause +errors if used in expressions. These are printed and ignored, and the +expression is considered to be false. You can also write e.g. +get("playback-time", 0) instead of p.playback_time to default to 0. + +Whenever a property referenced by a profile condition changes, the condition +is re-evaluated. If the return value of the condition changes from false or +error to true, the profile is applied. + +Note that profiles cannot be "unapplied", so you may have to define inverse +profiles with inverse conditions do undo a profile. + +Using profile-desc is just a hack - maybe it will be changed later. + +Supported --script-opts: + + auto-profiles: if set to "no", the script disables itself (but will still + listen to property notifications etc. - if you set it to + "yes" again, it will re-evaluate the current state) + +Example profiles: + +# the profile names aren't used (except for logging), but must not clash with +# other profiles +[test] +profile-desc=cond:p.playback_time>10 +video-zoom=2 + +# you would need this to actually "unapply" the "test" profile +[test-revert] +profile-desc=cond:p.playback_time<=10 +video-zoom=0 + +--]] + +local lua_modules = mp.find_config_file('lua-modules') +if lua_modules then + package.path = package.path .. ';' .. lua_modules .. '/?.lua' +end + +local f = require 'auto-profiles-functions' +local utils = require 'mp.utils' +local msg = require 'mp.msg' + +local profiles = {} +local watched_properties = {} -- indexed by property name (used as a set) +local cached_properties = {} -- property name -> last known raw value +local properties_to_profiles = {} -- property name -> set of profiles using it +local have_dirty_profiles = false -- at least one profile is marked dirty + +-- Used during evaluation of the profile condition, and should contain the +-- profile the condition is evaluated for. +local current_profile = nil + +local function evaluate(profile) + msg.verbose("Re-evaluate auto profile " .. profile.name) + + current_profile = profile + local status, res = pcall(profile.cond) + current_profile = nil + + if not status then + -- errors can be "normal", e.g. in case properties are unavailable + msg.info("Error evaluating: " .. res) + res = false + elseif type(res) ~= "boolean" then + msg.error("Profile '" .. profile.name .. "' did not return a boolean.") + res = false + end + if res ~= profile.status and res == true then + msg.info("Applying profile " .. profile.name) + mp.commandv("apply-profile", profile.name) + end + profile.status = res + profile.dirty = false +end + +local function on_property_change(name, val) + cached_properties[name] = val + -- Mark all profiles reading this property as dirty, so they get re-evaluated + -- the next time the script goes back to sleep. + local dependent_profiles = properties_to_profiles[name] + if dependent_profiles then + for profile, _ in pairs(dependent_profiles) do + assert(profile.cond) -- must be a profile table + profile.dirty = true + have_dirty_profiles = true + end + end +end + +local function on_idle() + if mp.get_opt("auto-profiles") == "no" then + return + end + + -- When events and property notifications stop, re-evaluate all dirty profiles. + if have_dirty_profiles then + for _, profile in ipairs(profiles) do + if profile.dirty then + evaluate(profile) + end + end + end + have_dirty_profiles = false +end + +mp.register_idle(on_idle) + +local evil_meta_magic = { + __index = function(table, key) + -- interpret everything as property, unless it already exists as + -- a non-nil global value + local v = _G[key] + if type(v) ~= "nil" then + return v + end + -- Lua identifiers can't contain "-", so in order to match with mpv + -- property conventions, replace "_" to "-" + key = string.gsub(key, "_", "-") + -- Normally, we use the cached value only (to reduce CPU usage I guess?) + if not watched_properties[key] then + watched_properties[key] = true + mp.observe_property(key, "native", on_property_change) + cached_properties[key] = mp.get_property_native(key) + end + -- The first time the property is read we need add it to the + -- properties_to_profiles table, which will be used to mark the profile + -- dirty if a property referenced by it changes. + if current_profile then + local map = properties_to_profiles[key] + if not map then + map = {} + properties_to_profiles[key] = map + end + map[current_profile] = true + end + return cached_properties[key] + end, +} + +local evil_magic = {} +setmetatable(evil_magic, evil_meta_magic) + +local function compile_cond(name, s) + chunk, err = loadstring("return " .. s, "profile " .. name .. " condition") + if not chunk then + msg.error("Profile '" .. name .. "' condition: " .. err) + return function() return false end + end + return chunk +end + +for i, v in ipairs(mp.get_property_native("profile-list")) do + local desc = v["profile-desc"] + if desc and desc:sub(1, 5) == "cond:" then + local profile = { + name = v.name, + cond = compile_cond(v.name, desc:sub(6)), + properties = {}, + status = nil, + dirty = true, -- need re-evaluate + } + profiles[#profiles + 1] = profile + have_dirty_profiles = true + end +end + +-- these definitions are for use by the condition expressions + +p = evil_magic + +function get(property_name, default) + local val = p[property_name] + if val == nil then + val = default + end + return val +end + +-- re-evaluate all profiles immediately +on_idle() diff --git a/mpv/scripts/auto-save-state.lua b/mpv/scripts/auto-save-state.lua new file mode 100644 index 0000000..a4ec3ec --- /dev/null +++ b/mpv/scripts/auto-save-state.lua @@ -0,0 +1,55 @@ +-- Save watch-later conditionally. +-- a) Always for playlists (so mpv remembers the position within this playlist) +-- b) Never for files shorter than `min_length` seconds +-- c) When the current playback position is > `thresh_start` and < `thresh_end` + + +local opts = require 'mp.options' +local o = { + min_length = 600, + thresh_end = 180, + thresh_start = 60, +} +opts.read_options(o) + + +-- Return true when multiple files are being played +function check_playlist() + local pcount, err = mp.get_property_number("playlist-count") + if not pcount then + print("error: " .. err) + pcount = 1 + end + + return pcount > 1 +end + + +-- Return true when the current playback time is not too close to the start or end +-- Always return false for short files, no matter the playback time +function check_time() + local duration = mp.get_property_number("duration", 9999) + if duration < o.min_length then + return false + end + + local remaining, err = mp.get_property_number("time-remaining") + if not remaining then + print("error: " .. err) + remaining = -math.huge + end + local pos, err = mp.get_property_number("time-pos") + if not pos then + print("error: " .. err) + pos = -math.huge + end + + return pos > o.thresh_start and remaining > o.thresh_end +end + + +mp.add_key_binding("q", "quit-watch-later-conditional", + function() + mp.set_property_bool("options/save-position-on-quit", check_playlist() or check_time()) + mp.command("quit") + end) diff --git a/mpv/scripts/autodeint.lua b/mpv/scripts/autodeint.lua new file mode 100644 index 0000000..16f56ea --- /dev/null +++ b/mpv/scripts/autodeint.lua @@ -0,0 +1,158 @@ +-- From: https://github.com/mpv-player/mpv/tree/master/TOOLS/lua +-- (with slight modifications) +-- +-- This script uses the lavfi idet filter to automatically insert the +-- appropriate deinterlacing filter based on a short section of the +-- currently playing video. +-- +-- It registers the key-binding ctrl+d, which when pressed, inserts the filters +-- ``vf=lavfi=idet,pullup,vf=lavfi=idet``. After 4 seconds, it removes these +-- filters and decides whether the content is progressive, interlaced, or +-- telecined and the interlacing field dominance. +-- +-- Based on this information, it may set mpv's ``deinterlace`` property (which +-- usually inserts the yadif filter), or insert the ``pullup`` filter if the +-- content is telecined. It also sets mpv's ``field-dominance`` property. +-- +-- OPTIONS: +-- The default detection time may be overridden by adding +-- +-- --script-opts=autodeint.detect_seconds=<number of seconds> +-- +-- to mpv's arguments. This may be desirable to allow idet more +-- time to collect data. +-- +-- To see counts of the various types of frames for each detection phase, +-- the verbosity can be increased with +-- +-- --msg-level autodeint=v +-- +-- This script requires a recent version of ffmpeg for which the idet +-- filter adds the required metadata. + +require "mp.msg" + +script_name = mp.get_script_name() +detect_label = string.format("%s-detect", script_name) +pullup_label = string.format("%s", script_name) +ivtc_detect_label = string.format("%s-ivtc-detect", script_name) + +-- number of seconds to gather cropdetect data +detect_seconds = tonumber(mp.get_opt(string.format("%s.detect_seconds", script_name))) +if not detect_seconds then + detect_seconds = 4 +end + +function del_filter_if_present(label) + -- necessary because mp.command('vf del @label:filter') raises an + -- error if the filter doesn't exist + local vfs = mp.get_property_native("vf") + + for i,vf in pairs(vfs) do + if vf["label"] == label then + table.remove(vfs, i) + mp.set_property_native("vf", vfs) + return true + end + end + return false +end + +function start_detect() + -- exit if detection is already in progress + if timer then + mp.msg.warn("already detecting!") + mp.osd_message("autodeint: already detecting!") + return + end + + mp.set_property("deinterlace","no") + del_filter_if_present(pullup_label) + + -- insert the detection filter + local cmd = string.format('vf add @%s:lavfi=graph="idet",@%s:pullup,@%s:lavfi=graph="idet"', + detect_label, pullup_label, ivtc_detect_label) + if not mp.command(cmd) then + mp.msg.error("failed to insert detection filters") + mp.osd_message("autodeint: failed to insert detection filters") + return + end + + -- wait to gather data + mp.osd_message("autodeint: starting detection") + timer = mp.add_timeout(detect_seconds, select_filter) +end + +function stop_detect() + del_filter_if_present(detect_label) + del_filter_if_present(ivtc_detect_label) + timer = nil +end + +progressive, interlaced_tff, interlaced_bff, interlaced = 0, 1, 2, 3, 4 + +function judge(label) + -- get the metadata + local result = mp.get_property_native(string.format("vf-metadata/%s", label)) + -- filter might have been removed by third party + if not result or next(result) == nil then + return progressive + end + num_tff = tonumber(result["lavfi.idet.multiple.tff"]) + num_bff = tonumber(result["lavfi.idet.multiple.bff"]) + num_progressive = tonumber(result["lavfi.idet.multiple.progressive"]) + num_undetermined = tonumber(result["lavfi.idet.multiple.undetermined"]) + num_interlaced = num_tff + num_bff + num_determined = num_interlaced + num_progressive + + mp.msg.verbose(label .. " progressive = "..num_progressive) + mp.msg.verbose(label .. " interlaced-tff = "..num_tff) + mp.msg.verbose(label .. " interlaced-bff = "..num_bff) + mp.msg.verbose(label .. " undetermined = "..num_undetermined) + + if num_determined < num_undetermined then + mp.msg.warn("majority undetermined frames") + end + if num_progressive > 20*num_interlaced then + return progressive + elseif num_tff > 10*num_bff then + return interlaced_tff + elseif num_bff > 10*num_tff then + return interlaced_bff + else + return interlaced + end +end + +function select_filter() + -- handle the first detection filter results + verdict = judge(detect_label) + if verdict == progressive then + mp.msg.info("progressive: doing nothing") + mp.osd_message("autodeint: no deinterlacing required") + stop_detect() + return + elseif verdict == interlaced_tff then + mp.set_property("field-dominance", "top") + elseif verdict == interlaced_bff then + mp.set_property("field-dominance", "bottom") + elseif verdict == interlaced then + mp.set_property("field-dominance", "auto") + end + + -- handle the ivtc detection filter results + verdict = judge(ivtc_detect_label) + if verdict == progressive then + mp.msg.info(string.format("telecinied with %s field dominance: using pullup", mp.get_property("field-dominance"))) + mp.osd_message("autodeint: using pullup") + stop_detect() + else + mp.msg.info(string.format("interlaced with %s field dominance: setting deinterlace property", mp.get_property("field-dominance"))) + del_filter_if_present(pullup_label) + mp.osd_message(string.format("autodeint: setting deinterlace property (%s)", mp.get_property("field-dominance"))) + mp.set_property("deinterlace", "yes") + stop_detect() + end +end + +mp.add_key_binding("ctrl+d", script_name, start_detect) diff --git a/mpv/scripts/betterchapters.lua b/mpv/scripts/betterchapters.lua new file mode 100644 index 0000000..4b871c8 --- /dev/null +++ b/mpv/scripts/betterchapters.lua @@ -0,0 +1,21 @@ +-- From: https://github.com/mpv-player/mpv/issues/4738#issuecomment-321298846 + +function chapter_seek(direction) + local chapters = mp.get_property_number("chapters") + if chapters == nil then chapters = 0 end + local chapter = mp.get_property_number("chapter") + if chapter == nil then chapter = 0 end + if chapter+direction < 0 then + mp.command("playlist_prev") + mp.commandv("script-message", "osc-playlist") + elseif chapter+direction >= chapters then + mp.command("playlist_next") + mp.commandv("script-message", "osc-playlist") + else + mp.commandv("add", "chapter", direction) + mp.commandv("script-message", "osc-chapterlist") + end +end + +mp.add_key_binding(nil, "chapterplaylist-next", function() chapter_seek(1) end) +mp.add_key_binding(nil, "chapterplaylist-prev", function() chapter_seek(-1) end)
\ No newline at end of file diff --git a/mpv/scripts/blacklist-extensions.lua b/mpv/scripts/blacklist-extensions.lua new file mode 100644 index 0000000..a8ec638 --- /dev/null +++ b/mpv/scripts/blacklist-extensions.lua @@ -0,0 +1,80 @@ +-- From: https://github.com/occivink/mpv-scripts + +opts = { + blacklist="", + whitelist="", + remove_files_without_extension = false, + oneshot = true, +} +(require 'mp.options').read_options(opts) +local msg = require 'mp.msg' + +function split(input) + local ret = {} + for str in string.gmatch(input, "([^,]+)") do + ret[#ret + 1] = str + end + return ret +end + +opts.blacklist = split(opts.blacklist) +opts.whitelist = split(opts.whitelist) + +local exclude +if #opts.whitelist > 0 then + exclude = function(extension) + for _, ext in pairs(opts.whitelist) do + if extension == ext then + return false + end + end + return true + end +elseif #opts.blacklist > 0 then + exclude = function(extension) + for _, ext in pairs(opts.blacklist) do + if extension == ext then + return true + end + end + return false + end +else + return +end + +function should_remove(filename) + if string.find(filename, "://") then + return false + end + local extension = string.match(filename, "%.([^./]+)$") + if not extension and opts.remove_file_without_extension then + return true + end + if extension and exclude(string.lower(extension)) then + return true + end + return false +end + +function process(playlist_count) + if playlist_count < 2 then return end + if opts.oneshot then + mp.unobserve_property(observe) + end + local playlist = mp.get_property_native("playlist") + local removed = 0 + for i = #playlist, 1, -1 do + if should_remove(playlist[i].filename) then + mp.commandv("playlist-remove", i-1) + removed = removed + 1 + end + end + if removed == #playlist then + msg.warn("Removed eveything from the playlist") + end +end + +function observe(k,v) process(v) end + +mp.observe_property("playlist-count", "number", observe) diff --git a/mpv/scripts/cycle-profiles.lua b/mpv/scripts/cycle-profiles.lua new file mode 100644 index 0000000..da846c5 --- /dev/null +++ b/mpv/scripts/cycle-profiles.lua @@ -0,0 +1,103 @@ +--[[ + script to cycle profiles with a keybind, accomplished through script messages + available at: https://github.com/CogentRedTester/mpv-scripts + syntax: + script-message cycle-profiles "profile1;profile2;profile3" + You must use semicolons to separate the profiles, do not include any spaces that are not part of the profile name. + The script will print the profile description to the screen when switching, if there is no profile description, then it just prints the name +]]-- + +--change this to change what character separates the profile names +seperator = ";" + +msg = require 'mp.msg' + +--splits the profiles string into an array of profile names +--function taken from: https://stackoverflow.com/questions/1426954/split-string-in-lua/7615129#7615129 +function mysplit (inputstr, sep) + if sep == nil then + sep = "%s" + end + local t={} + for str in string.gmatch(inputstr, "([^"..sep.."]+)") do + table.insert(t, str) + end + return t +end + +--table of all available profiles and options +profileList = mp.get_property_native('profile-list') + +--keeps track of current profile for every unique cycle +iterator = {} + +--stores descriptions for profiles +--once requested a description is stored here so it does not need to be found again +profilesDescs = {} + +--if trying to cycle to an unknown profile this function is run to find a description to print +function findDesc(profile) + msg.verbose('unknown profile ' .. profile .. ', searching for description') + + for i = 1, #profileList, 1 do + if profileList[i]['name'] == profile then + msg.verbose('profile found') + local desc = profileList[i]['profile-desc'] + + if desc ~= nil then + msg.verbose('description found') + profilesDescs[profile] = desc + else + msg.verbose('no description, will use name') + profilesDescs[profile] = profile + end + return + end + end + + msg.verbose('profile not found') + profilesDescs[profile] = "no profile '" .. profile .. "'" +end + +--prints the profile description to the OSD +--if the profile has not been requested before during the session then it runs findDesc() +function printProfileDesc(profile) + local desc = profilesDescs[profile] + if desc == nil then + findDesc(profile) + desc = profilesDescs[profile] + end + + msg.verbose('profile description: ' .. desc) + mp.osd_message(desc) +end + +function main(profileStr) + --if there is not already an iterator for this cycle then it creates one + if iterator[profileStr] == nil then + msg.verbose('unknown cycle, creating new iterator') + iterator[profileStr] = 1 + end + local i = iterator[profileStr] + + --converts the string into an array of profile names + local profiles = mysplit(profileStr, seperator) + msg.verbose('cycling ' .. tostring(profiles)) + msg.verbose("number of profiles: " .. tostring(#profiles)) + + --sends the command to apply the profile + msg.info("applying profile " .. profiles[i]) + mp.commandv('apply-profile', profiles[i]) + + --prints the profile description to the OSD + printProfileDesc(profiles[i]) + + --moves the iterator + iterator[profileStr] = iterator[profileStr] + 1 + if iterator[profileStr] > #profiles then + msg.verbose('reached end of profiles, wrapping back to start') + iterator[profileStr] = 1 + end +end + +mp.register_script_message('cycle-profiles', main) diff --git a/mpv/scripts/music-mode.lua b/mpv/scripts/music-mode.lua new file mode 100644 index 0000000..4c57456 --- /dev/null +++ b/mpv/scripts/music-mode.lua @@ -0,0 +1,192 @@ +local mp = require 'mp' +local msg = require 'mp.msg' +local opt = require 'mp.options' + +--script options, set these in script-opts +local o = { + --change to disable automatic mode switching + auto = true, + + --profile to call when valid extension is found + profile = "music", + + --runs this profile when in music mode and a non-audio file is loaded + --you should essentially put all your defaults that the music profile changed in here + undo_profile = "", + + --start playback in music mode. This setting is only applied when the player is initially started, + --changing this option during runtime does nothing. + --probably only useful if auto is disabled + enable = false, + + --the script will also enable the following input section when music mode is enabled + --see the mpv manual for details on sections + input_section = "music", + + --dispays the metadata of the track on the osd when music mode is on + --there is also a script message to enable this seperately + show_metadata = false +} + +opt.read_options(o, 'musicmode', function() msg.verbose('options updated') end) + +--a music file is one where mpv returns an audio stream or coverart as the first track +local function is_audio_file() + local track_list = mp.get_property_native("track-list") + + local has_audio = false + for _, track in ipairs(track_list) do + if track.type == "audio" then has_audio = true + elseif not track.albumart and (track["demux-fps"] or 2) > 1 then return false end + end + return has_audio +end + +local metadata = mp.create_osd_overlay('ass-events') +metadata.hidden = not o.show_metadata + +local function update_metadata() + metadata.data = mp.get_property_osd('filtered-metadata') + metadata:update() +end + +local function enable_metadata() + metadata.hidden = false + metadata:update() +end + +local function disable_metadata() + metadata.hidden = true + metadata:remove() +end + +--changes visibility of metadata +local function show_metadata(command) + if command == "on" or command == nil then + enable_metadata() + elseif command == "off" then + disable_metadata() + elseif command == "toggle" then + if metadata.hidden then + enable_metadata() + else + disable_metadata() + end + else + msg.warn('unknown command "' .. command .. '"') + end +end + +--to prevent superfluous loading of profiles the script keeps track of when music mode is enabled +local musicMode = false + +--enables music mode +local function activate() + mp.commandv('apply-profile', o.profile) + mp.commandv('enable-section', o.input_section, "allow-vo-dragging+allow-hide-cursor") + mp.osd_message('Music Mode enabled') + + if o.show_metadata then + show_metadata("on") + end + + musicMode = true +end + +--disables music mode +local function deactivate() + mp.commandv('apply-profile', o.undo_profile) + mp.commandv('disable-section', o.input_section) + mp.osd_message('Music Mode disabled') + + if o.show_metadata then + show_metadata('off') + end + + musicMode = false +end + +local function main() + --if the file is an audio file then the music profile is loaded + if is_audio_file() then + msg.verbose('audio file, applying profile "' .. o.profile .. '"') + if not musicMode then + activate() + end + elseif o.undo_profile ~= "" and musicMode then + msg.verbose('video file, applying undo profile "' .. o.undo_profile .. '"') + deactivate() + end +end + +--sets music mode from script-message +local function script_message(command) + if command == "on" or command == nil then + activate() + elseif command == "off" then + deactivate() + elseif command == "toggle" then + if musicMode then + deactivate() + else + activate() + end + else + msg.warn('unknown command "' .. command .. '"') + end +end + +local function lock() + o.auto = false + msg.info('Music Mode locked') + mp.osd_message('Music Mode locked') +end + +local function unLock() + o.auto = true + msg.info('Music Mode unlocked') + mp.osd_message('Music Mode unlocked') +end + +--toggles lock +local function lock_script_message(command) + if command == "on" or command == nil then + lock() + elseif command == "off" then + unLock() + elseif command == "toggle" then + if o.auto then + lock() + else + unLock() + end + else + msg.warn('unknown command "' .. command .. '"') + end +end + +--runs when the file is loaded, if script is locked it will do nothing +local function file_loaded() + if o.auto then + main() + end +end + +if o.enable then + activate() +end + +--sets music mode +--accepts arguments: 'on', 'off', 'toggle' +mp.register_script_message('music-mode', script_message) + +--stops the script from switching modes on file loads +----accepts arguments: 'on', 'off', 'toggle' +mp.register_script_message('music-mode-lock', lock_script_message) + +--shows file metadata on osc +--accepts arguments: 'on' 'off' 'toggle' +mp.register_script_message('show-metadata', show_metadata) + +mp.add_hook('on_preloaded', 40, file_loaded) +mp.observe_property('filtered-metadata', 'string', update_metadata) diff --git a/mpv/scripts/user-input.lua b/mpv/scripts/user-input.lua new file mode 100644 index 0000000..689e073 --- /dev/null +++ b/mpv/scripts/user-input.lua @@ -0,0 +1,664 @@ +local mp = require 'mp' +local msg = require 'mp.msg' +local utils = require 'mp.utils' +local options = require 'mp.options' + +-- Default options +local opts = { + -- All drawing is scaled by this value, including the text borders and the + -- cursor. Change it if you have a high-DPI display. + scale = 1, + -- Set the font used for the REPL and the console. This probably doesn't + -- have to be a monospaced font. + font = "", + -- Set the font size used for the REPL and the console. This will be + -- multiplied by "scale." + font_size = 16, +} + +options.read_options(opts, "user_input") + +local queue = { + queue = {}, + active_ids = {} +} +local histories = {} +local request = nil + +local line = '' + +--[[ + sends a response to the original script in the form of a json string + it is expected that all requests get a response, if the input is nil then err should say why + current error codes are: + exitted the user closed the input instead of pressing Enter + already_queued a request with the specified id was already in the queue + replaced the request was replaced with a newer request + cancelled a script cancelled the request +]]-- +local function send_response(send_line, err, override_response) + mp.commandv("script-message", override_response or request.response, send_line and line or "", err or "") +end + + +--[[ + The below code is a modified implementation of text input from mpv's console.lua: + https://github.com/mpv-player/mpv/blob/7ca14d646c7e405f3fb1e44600e2a67fc4607238/player/lua/console.lua + + Modifications: + removed support for log messages, sending commands, tab complete, help commands + removed update timer + Changed esc key to call handle_esc function + handle_esc and handle_enter now call the send_response() function + all functions that send responses now call queue:pop() + made history specific to request ids + localised all functions - reordered some to fit + keybindings use new names +]]-- + +------------------------------START ORIGINAL MPV CODE----------------------------------- +---------------------------------------------------------------------------------------- +---------------------------------------------------------------------------------------- +---------------------------------------------------------------------------------------- +---------------------------------------------------------------------------------------- + +-- Copyright (C) 2019 the mpv developers +-- +-- Permission to use, copy, modify, and/or distribute this software for any +-- purpose with or without fee is hereby granted, provided that the above +-- copyright notice and this permission notice appear in all copies. +-- +-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +-- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +-- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +-- SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +-- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +-- OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +local assdraw = require 'mp.assdraw' + +local function detect_platform() + local o = {} + -- Kind of a dumb way of detecting the platform but whatever + if mp.get_property_native('options/vo-mmcss-profile', o) ~= o then + return 'windows' + elseif mp.get_property_native('options/macos-force-dedicated-gpu', o) ~= o then + return 'macos' + elseif os.getenv('WAYLAND_DISPLAY') then + return 'wayland' + end + return 'x11' +end + +-- Pick a better default font for Windows and macOS +local platform = detect_platform() +if platform == 'windows' then + opts.font = 'Consolas' +elseif platform == 'macos' then + opts.font = 'Menlo' +else + opts.font = 'monospace' +end + +local repl_active = false +local insert_mode = false +local cursor = 1 +local key_bindings = {} +local global_margin_y = 0 + +-- Escape a string for verbatim display on the OSD +local function ass_escape(str) + -- There is no escape for '\' in ASS (I think?) but '\' is used verbatim if + -- it isn't followed by a recognised character, so add a zero-width + -- non-breaking space + str = str:gsub('\\', '\\\239\187\191') + str = str:gsub('{', '\\{') + str = str:gsub('}', '\\}') + -- Precede newlines with a ZWNBSP to prevent ASS's weird collapsing of + -- consecutive newlines + str = str:gsub('\n', '\239\187\191\\N') + -- Turn leading spaces into hard spaces to prevent ASS from stripping them + str = str:gsub('\\N ', '\\N\\h') + str = str:gsub('^ ', '\\h') + return str +end + +-- Render the REPL and console as an ASS OSD +local function update() + local dpi_scale = mp.get_property_native("display-hidpi-scale", 1.0) + + dpi_scale = dpi_scale * opts.scale + + local screenx, screeny, aspect = mp.get_osd_size() + screenx = screenx / dpi_scale + screeny = screeny / dpi_scale + + -- Clear the OSD if the REPL is not active + if not repl_active then + mp.set_osd_ass(screenx, screeny, '') + return + end + + local ass = assdraw.ass_new() + local style = '{\\r' .. + '\\1a&H00&\\3a&H00&\\4a&H99&' .. + '\\1c&Heeeeee&\\3c&H111111&\\4c&H000000&' .. + '\\fn' .. opts.font .. '\\fs' .. opts.font_size .. + '\\bord1\\xshad0\\yshad1\\fsp0\\q1}' + -- Create the cursor glyph as an ASS drawing. ASS will draw the cursor + -- inline with the surrounding text, but it sets the advance to the width + -- of the drawing. So the cursor doesn't affect layout too much, make it as + -- thin as possible and make it appear to be 1px wide by giving it 0.5px + -- horizontal borders. + local cheight = opts.font_size * 8 + local cglyph = '{\\r' .. + '\\1a&H44&\\3a&H44&\\4a&H99&' .. + '\\1c&Heeeeee&\\3c&Heeeeee&\\4c&H000000&' .. + '\\xbord0.5\\ybord0\\xshad0\\yshad1\\p4\\pbo24}' .. + 'm 0 0 l 1 0 l 1 ' .. cheight .. ' l 0 ' .. cheight .. + '{\\p0}' + local before_cur = ass_escape(line:sub(1, cursor - 1)) + local after_cur = ass_escape(line:sub(cursor)) + + ass:new_event() + ass:an(1) + ass:pos(2, screeny - 2 - global_margin_y * screeny) + ass:append(style .. request.text .. '\\N') + ass:append('> ' .. before_cur) + ass:append(cglyph) + ass:append(style .. after_cur) + + -- Redraw the cursor with the REPL text invisible. This will make the + -- cursor appear in front of the text. + ass:new_event() + ass:an(1) + ass:pos(2, screeny - 2) + ass:append(style .. '{\\alpha&HFF&}> ' .. before_cur) + ass:append(cglyph) + ass:append(style .. '{\\alpha&HFF&}' .. after_cur) + + mp.set_osd_ass(screenx, screeny, ass.text) +end + +-- Naive helper function to find the next UTF-8 character in 'str' after 'pos' +-- by skipping continuation bytes. Assumes 'str' contains valid UTF-8. +local function next_utf8(str, pos) + if pos > str:len() then return pos end + repeat + pos = pos + 1 + until pos > str:len() or str:byte(pos) < 0x80 or str:byte(pos) > 0xbf + return pos +end + +-- As above, but finds the previous UTF-8 charcter in 'str' before 'pos' +local function prev_utf8(str, pos) + if pos <= 1 then return pos end + repeat + pos = pos - 1 + until pos <= 1 or str:byte(pos) < 0x80 or str:byte(pos) > 0xbf + return pos +end + +-- Insert a character at the current cursor position (any_unicode, Shift+Enter) +local function handle_char_input(c) + if insert_mode then + line = line:sub(1, cursor - 1) .. c .. line:sub(next_utf8(line, cursor)) + else + line = line:sub(1, cursor - 1) .. c .. line:sub(cursor) + end + cursor = cursor + #c + update() +end + +-- Remove the character behind the cursor (Backspace) +local function handle_backspace() + if cursor <= 1 then return end + local prev = prev_utf8(line, cursor) + line = line:sub(1, prev - 1) .. line:sub(cursor) + cursor = prev + update() +end + +-- Remove the character in front of the cursor (Del) +local function handle_del() + if cursor > line:len() then return end + line = line:sub(1, cursor - 1) .. line:sub(next_utf8(line, cursor)) + update() +end + +-- Toggle insert mode (Ins) +local function handle_ins() + insert_mode = not insert_mode +end + +-- Move the cursor to the next character (Right) +local function next_char(amount) + cursor = next_utf8(line, cursor) + update() +end + +-- Move the cursor to the previous character (Left) +local function prev_char(amount) + cursor = prev_utf8(line, cursor) + update() +end + +-- Clear the current line (Ctrl+C) +local function clear() + line = '' + cursor = 1 + insert_mode = false + request.history.pos = #request.history.list + 1 + update() +end + +-- Close the REPL if the current line is empty, otherwise do nothing (Ctrl+D) +local function maybe_exit() + if line == '' then + send_response(false, "exitted") + queue:pop() + end +end + +local function handle_esc() + send_response(false, "exitted") + queue:pop() +end + +-- Run the current command and clear the line (Enter) +local function handle_enter() + if request.history.list[#request.history.list] ~= line and line ~= "" then + request.history.list[#request.history.list + 1] = line + end + send_response(true) + queue:pop() +end + +-- Go to the specified position in the command history +local function go_history(new_pos) + local old_pos = request.history.pos + request.history.pos = new_pos + + -- Restrict the position to a legal value + if request.history.pos > #request.history.list + 1 then + request.history.pos = #request.history.list + 1 + elseif request.history.pos < 1 then + request.history.pos = 1 + end + + -- Do nothing if the history position didn't actually change + if request.history.pos == old_pos then + return + end + + -- If the user was editing a non-history line, save it as the last history + -- entry. This makes it much less frustrating to accidentally hit Up/Down + -- while editing a line. + if old_pos == #request.history.list + 1 and line ~= '' and request.history.list[#request.history.list] ~= line then + request.history.list[#request.history.list + 1] = line + end + + -- Now show the history line (or a blank line for #history + 1) + if request.history.pos <= #request.history.list then + line = request.history.list[request.history.pos] + else + line = '' + end + cursor = line:len() + 1 + insert_mode = false + update() +end + +-- Go to the specified relative position in the command history (Up, Down) +local function move_history(amount) + go_history(request.history.pos + amount) +end + +-- Go to the first command in the command history (PgUp) +local function handle_pgup() + go_history(1) +end + +-- Stop browsing history and start editing a blank line (PgDown) +local function handle_pgdown() + go_history(#request.history.list + 1) +end + +-- Move to the start of the current word, or if already at the start, the start +-- of the previous word. (Ctrl+Left) +local function prev_word() + -- This is basically the same as next_word() but backwards, so reverse the + -- string in order to do a "backwards" find. This wouldn't be as annoying + -- to do if Lua didn't insist on 1-based indexing. + cursor = line:len() - select(2, line:reverse():find('%s*[^%s]*', line:len() - cursor + 2)) + 1 + update() +end + +-- Move to the end of the current word, or if already at the end, the end of +-- the next word. (Ctrl+Right) +local function next_word() + cursor = select(2, line:find('%s*[^%s]*', cursor)) + 1 + update() +end + +-- Move the cursor to the beginning of the line (HOME) +local function go_home() + cursor = 1 + update() +end + +-- Move the cursor to the end of the line (END) +local function go_end() + cursor = line:len() + 1 + update() +end + +-- Delete from the cursor to the end of the word (Ctrl+W) +local function del_word() + local before_cur = line:sub(1, cursor - 1) + local after_cur = line:sub(cursor) + + before_cur = before_cur:gsub('[^%s]+%s*$', '', 1) + line = before_cur .. after_cur + cursor = before_cur:len() + 1 + update() +end + +-- Delete from the cursor to the end of the line (Ctrl+K) +local function del_to_eol() + line = line:sub(1, cursor - 1) + update() +end + +-- Delete from the cursor back to the start of the line (Ctrl+U) +local function del_to_start() + line = line:sub(cursor) + cursor = 1 + update() +end + +-- Returns a string of UTF-8 text from the clipboard (or the primary selection) +local function get_clipboard(clip) + if platform == 'x11' then + local res = utils.subprocess({ + args = { 'xclip', '-selection', clip and 'clipboard' or 'primary', '-out' }, + playback_only = false, + }) + if not res.error then + return res.stdout + end + elseif platform == 'wayland' then + local res = utils.subprocess({ + args = { 'wl-paste', clip and '-n' or '-np' }, + playback_only = false, + }) + if not res.error then + return res.stdout + end + elseif platform == 'windows' then + local res = utils.subprocess({ + args = { 'powershell', '-NoProfile', '-Command', [[& { + Trap { + Write-Error -ErrorRecord $_ + Exit 1 + } + + $clip = "" + if (Get-Command "Get-Clipboard" -errorAction SilentlyContinue) { + $clip = Get-Clipboard -Raw -Format Text -TextFormatType UnicodeText + } else { + Add-Type -AssemblyName PresentationCore + $clip = [Windows.Clipboard]::GetText() + } + + $clip = $clip -Replace "`r","" + $u8clip = [System.Text.Encoding]::UTF8.GetBytes($clip) + [Console]::OpenStandardOutput().Write($u8clip, 0, $u8clip.Length) + }]] }, + playback_only = false, + }) + if not res.error then + return res.stdout + end + elseif platform == 'macos' then + local res = utils.subprocess({ + args = { 'pbpaste' }, + playback_only = false, + }) + if not res.error then + return res.stdout + end + end + return '' +end + +-- Paste text from the window-system's clipboard. 'clip' determines whether the +-- clipboard or the primary selection buffer is used (on X11 and Wayland only.) +local function paste(clip) + local text = get_clipboard(clip) + local before_cur = line:sub(1, cursor - 1) + local after_cur = line:sub(cursor) + line = before_cur .. text .. after_cur + cursor = cursor + text:len() + update() +end + +-- List of input bindings. This is a weird mashup between common GUI text-input +-- bindings and readline bindings. +local function get_bindings() + local bindings = { + { 'esc', handle_esc }, + { 'enter', handle_enter }, + { 'kp_enter', handle_enter }, + { 'shift+enter', function() handle_char_input('\n') end }, + { 'bs', handle_backspace }, + { 'shift+bs', handle_backspace }, + { 'del', handle_del }, + { 'shift+del', handle_del }, + { 'ins', handle_ins }, + { 'shift+ins', function() paste(false) end }, + { 'mbtn_mid', function() paste(false) end }, + { 'left', function() prev_char() end }, + { 'right', function() next_char() end }, + { 'up', function() move_history(-1) end }, + { 'wheel_up', function() move_history(-1) end }, + { 'down', function() move_history(1) end }, + { 'wheel_down', function() move_history(1) end }, + { 'wheel_left', function() end }, + { 'wheel_right', function() end }, + { 'ctrl+left', prev_word }, + { 'ctrl+right', next_word }, + { 'home', go_home }, + { 'end', go_end }, + { 'pgup', handle_pgup }, + { 'pgdwn', handle_pgdown }, + { 'ctrl+c', clear }, + { 'ctrl+d', maybe_exit }, + { 'ctrl+k', del_to_eol }, + { 'ctrl+u', del_to_start }, + { 'ctrl+v', function() paste(true) end }, + { 'meta+v', function() paste(true) end }, + { 'ctrl+w', del_word }, + { 'kp_dec', function() handle_char_input('.') end }, + } + + for i = 0, 9 do + bindings[#bindings + 1] = + {'kp' .. i, function() handle_char_input('' .. i) end} + end + + return bindings +end + +local function text_input(info) + if info.key_text and (info.event == "press" or info.event == "down" + or info.event == "repeat") + then + handle_char_input(info.key_text) + end +end + +local function define_key_bindings() + if #key_bindings > 0 then + return + end + for _, bind in ipairs(get_bindings()) do + -- Generate arbitrary name for removing the bindings later. + local name = "_userinput_" .. bind[1] + key_bindings[#key_bindings + 1] = name + mp.add_forced_key_binding(bind[1], name, bind[2], {repeatable = true}) + end + mp.add_forced_key_binding("any_unicode", "_userinput_text", text_input, + {repeatable = true, complex = true}) + key_bindings[#key_bindings + 1] = "_userinput_text" +end + +local function undefine_key_bindings() + for _, name in ipairs(key_bindings) do + mp.remove_key_binding(name) + end + key_bindings = {} +end + +-- Set the REPL visibility ("enable", Esc) +local function set_active(active) + if active == repl_active then return end + if active then + repl_active = true + insert_mode = false + define_key_bindings() + else + clear() + repl_active = false + undefine_key_bindings() + collectgarbage() + end + update() +end + + +utils.shared_script_property_observe("osc-margins", function(_, val) + if val then + -- formatted as "%f,%f,%f,%f" with left, right, top, bottom, each + -- value being the border size as ratio of the window size (0.0-1.0) + local vals = {} + for v in string.gmatch(val, "[^,]+") do + vals[#vals + 1] = tonumber(v) + end + global_margin_y = vals[4] -- bottom + else + global_margin_y = 0 + end + update() +end) + +-- Redraw the REPL when the OSD size changes. This is needed because the +-- PlayRes of the OSD will need to be adjusted. +mp.observe_property('osd-width', 'native', update) +mp.observe_property('osd-height', 'native', update) +mp.observe_property('display-hidpi-scale', 'native', update) + +---------------------------------------------------------------------------------------- +---------------------------------------------------------------------------------------- +---------------------------------------------------------------------------------------- +-------------------------------END ORIGINAL MPV CODE------------------------------------ + +-- push new request onto the queue +-- if a request with the same id already exists and the queueable flag is not enabled then +-- a nil result will be returned to the function +function queue:push(req) + if self.active_ids[req.id] then + + -- replace an existing request with the new one + if req.replace then + for i = 1, #self.queue do + if self.queue[i].id == req.id then + send_response(false, "replaced", self.queue[i].response) + self.queue[i] = req + if i == 1 then request = req ; update() end + return + end + end + end + + --cancel the new request if it is not queueable + if not req.queueable then send_response(false, "already_queued", req.response) ; return end + end + + table.insert(self.queue, req) + self.active_ids[req.id] = (self.active_ids[req.id] or 0) + 1 + if #self.queue == 1 then return self:start_queue() end +end + +-- removes the first item in the queue and either continues or stops the queue +function queue:pop() + self:remove(1) + clear() + + if #self.queue < 1 then return self:stop_queue() + else return self:continue_queue() end +end + +-- safely removes an item from the queue and updates the set of active requests +function queue:remove(index) + local req = table.remove(self.queue, index) + self.active_ids[req.id] = self.active_ids[req.id] ~= 1 and self.active_ids[req.id] - 1 or nil +end + +function queue:start_queue() + request = self.queue[1] + line = request.default_input + set_active(true) +end + +function queue:continue_queue() + request = self.queue[1] + line = request.default_input + update() +end + +function queue:stop_queue() + set_active(false) +end + +-- removes all requests with the specified id from the queue +mp.register_script_message("cancel-user-input", function(id) + local i = 2 + while i <= #queue.queue do + if queue.queue[i].id == id then + send_response(false, "cancelled", queue.queue[i].response) + queue:remove(i) + else + i = i + 1 + end + end + + if queue.queue[1] and queue.queue[1].id == id then + send_response(false, "cancelled") + queue:pop() + end +end) + +-- script message to recieve input requests, get-user-input.lua acts as an interface to call this script message +-- requests are recieved as json objects +mp.register_script_message("request-user-input", function(response, id, request_text, default_input, queueable, replace) + local req = {} + + if not response then msg.error("input requests require a response string") ; return end + if not id then msg.error("input requests require an id string") ; return end + req.response = response + req.text = ass_escape(request_text or "") + req.default_input = default_input + req.id = id or "mpv" + req.queueable = (queueable == "1") + req.replace = (replace == "1") + + if not histories[id] then histories[id] = {pos = 1, list = {}} end + req.history = histories[id] + + queue:push(req) +end) + +--temporary keybind for debugging purposes +mp.add_key_binding("Ctrl+i", "user-input", function() set_active(true) end) diff --git a/mpv/scripts/youtube-quality.lua b/mpv/scripts/youtube-quality.lua new file mode 100644 index 0000000..b587f37 --- /dev/null +++ b/mpv/scripts/youtube-quality.lua @@ -0,0 +1,275 @@ +-- youtube-quality.lua +-- +-- Change youtube video quality on the fly. +-- +-- Diplays a menu that lets you switch to different ytdl-format settings while +-- you're in the middle of a video (just like you were using the web player). +-- +-- Bound to ctrl-f by default. + +local mp = require 'mp' +local utils = require 'mp.utils' +local msg = require 'mp.msg' +local assdraw = require 'mp.assdraw' + +local opts = { + --key bindings + toggle_menu_binding = "ctrl+f", + up_binding = "UP", + down_binding = "DOWN", + select_binding = "ENTER", + + --formatting / cursors + selected_and_active = "▶ - ", + selected_and_inactive = "● - ", + unselected_and_active = "▷ - ", + unselected_and_inactive = "○ - ", + + --font size scales by window, if false requires larger font and padding sizes + scale_playlist_by_window=false, + + --playlist ass style overrides inside curly brackets, \keyvalue is one field, extra \ for escape in lua + --example {\\fnUbuntu\\fs10\\b0\\bord1} equals: font=Ubuntu, size=10, bold=no, border=1 + --read http://docs.aegisub.org/3.2/ASS_Tags/ for reference of tags + --undeclared tags will use default osd settings + --these styles will be used for the whole playlist. More specific styling will need to be hacked in + -- + --(a monospaced font is recommended but not required) + style_ass_tags = "{\\fnmonospace}", + + --paddings for top left corner + text_padding_x = 5, + text_padding_y = 5, + + --other + menu_timeout = 10, + + --use youtube-dl to fetch a list of available formats (overrides quality_strings) + fetch_formats = true, + + --default menu entries + quality_strings=[[ + [ + {"4320p" : "bestvideo[height<=?4320p]+bestaudio/best"}, + {"2160p" : "bestvideo[height<=?2160]+bestaudio/best"}, + {"1440p" : "bestvideo[height<=?1440]+bestaudio/best"}, + {"1080p" : "bestvideo[height<=?1080]+bestaudio/best"}, + {"720p" : "bestvideo[height<=?720]+bestaudio/best"}, + {"480p" : "bestvideo[height<=?480]+bestaudio/best"}, + {"360p" : "bestvideo[height<=?360]+bestaudio/best"}, + {"240p" : "bestvideo[height<=?240]+bestaudio/best"}, + {"144p" : "bestvideo[height<=?144]+bestaudio/best"} + ] + ]], +} +(require 'mp.options').read_options(opts, "youtube-quality") +opts.quality_strings = utils.parse_json(opts.quality_strings) + +local destroyer = nil + + +function show_menu() + local selected = 1 + local active = 0 + local current_ytdl_format = mp.get_property("ytdl-format") + msg.verbose("current ytdl-format: "..current_ytdl_format) + local num_options = 0 + local options = {} + + + if opts.fetch_formats then + options, num_options = download_formats() + end + + if next(options) == nil then + for i,v in ipairs(opts.quality_strings) do + num_options = num_options + 1 + for k,v2 in pairs(v) do + options[i] = {label = k, format=v2} + if v2 == current_ytdl_format then + active = i + selected = active + end + end + end + end + + --set the cursor to the currently format + for i,v in ipairs(options) do + if v.format == current_ytdl_format then + active = i + selected = active + break + end + end + + function selected_move(amt) + selected = selected + amt + if selected < 1 then selected = num_options + elseif selected > num_options then selected = 1 end + timeout:kill() + timeout:resume() + draw_menu() + end + function choose_prefix(i) + if i == selected and i == active then return opts.selected_and_active + elseif i == selected then return opts.selected_and_inactive end + + if i ~= selected and i == active then return opts.unselected_and_active + elseif i ~= selected then return opts.unselected_and_inactive end + return "> " --shouldn't get here. + end + + function draw_menu() + local ass = assdraw.ass_new() + + ass:pos(opts.text_padding_x, opts.text_padding_y) + ass:append(opts.style_ass_tags) + + for i,v in ipairs(options) do + ass:append(choose_prefix(i)..v.label.."\\N") + end + + local w, h = mp.get_osd_size() + if opts.scale_playlist_by_window then w,h = 0, 0 end + mp.set_osd_ass(w, h, ass.text) + end + + function destroy() + timeout:kill() + mp.set_osd_ass(0,0,"") + mp.remove_key_binding("move_up") + mp.remove_key_binding("move_down") + mp.remove_key_binding("select") + mp.remove_key_binding("escape") + destroyer = nil + end + timeout = mp.add_periodic_timer(opts.menu_timeout, destroy) + destroyer = destroy + + mp.add_forced_key_binding(opts.up_binding, "move_up", function() selected_move(-1) end, {repeatable=true}) + mp.add_forced_key_binding(opts.down_binding, "move_down", function() selected_move(1) end, {repeatable=true}) + mp.add_forced_key_binding(opts.select_binding, "select", function() + destroy() + mp.set_property("ytdl-format", options[selected].format) + reload_resume() + end) + mp.add_forced_key_binding(opts.toggle_menu_binding, "escape", destroy) + + draw_menu() + return +end + +local ytdl = { + path = "youtube-dl", + searched = false, + blacklisted = {} +} + +format_cache={} +function download_formats() + local function exec(args) + local ret = utils.subprocess({args = args}) + return ret.status, ret.stdout, ret + end + + local function table_size(t) + s = 0 + for i,v in ipairs(t) do + s = s+1 + end + return s + end + + local url = mp.get_property("path") + + url = string.gsub(url, "ytdl://", "") -- Strip possible ytdl:// prefix. + + -- don't fetch the format list if we already have it + if format_cache[url] ~= nil then + local res = format_cache[url] + return res, table_size(res) + end + mp.osd_message("fetching available formats with youtube-dl...", 60) + + if not (ytdl.searched) then + local ytdl_mcd = mp.find_config_file("youtube-dl") + if not (ytdl_mcd == nil) then + msg.verbose("found youtube-dl at: " .. ytdl_mcd) + ytdl.path = ytdl_mcd + end + ytdl.searched = true + end + + local command = {ytdl.path, "--no-warnings", "--no-playlist", "-J"} + table.insert(command, url) + local es, json, result = exec(command) + + if (es < 0) or (json == nil) or (json == "") then + mp.osd_message("fetching formats failed...", 1) + msg.error("failed to get format list: " .. err) + return {}, 0 + end + + local json, err = utils.parse_json(json) + + if (json == nil) then + mp.osd_message("fetching formats failed...", 1) + msg.error("failed to parse JSON data: " .. err) + return {}, 0 + end + + res = {} + msg.verbose("youtube-dl succeeded!") + for i,v in ipairs(json.formats) do + if v.vcodec ~= "none" then + local fps = v.fps and v.fps.."fps" or "" + local resolution = string.format("%sx%s", v.width, v.height) + local l = string.format("%-9s %-5s (%-4s / %s)", resolution, fps, v.ext, v.vcodec) + local f = string.format("%s+bestaudio/best", v.format_id) + table.insert(res, {label=l, format=f, width=v.width }) + end + end + + table.sort(res, function(a, b) return a.width > b.width end) + + mp.osd_message("", 0) + format_cache[url] = res + return res, table_size(res) +end + + +-- register script message to show menu +mp.register_script_message("toggle-quality-menu", +function() + if destroyer ~= nil then + destroyer() + else + show_menu() + end +end) + +-- keybind to launch menu +mp.add_key_binding(opts.toggle_menu_binding, "quality-menu", show_menu) + +-- special thanks to reload.lua (https://github.com/4e6/mpv-reload/) +function reload_resume() + local playlist_pos = mp.get_property_number("playlist-pos") + local reload_duration = mp.get_property_native("duration") + local time_pos = mp.get_property("time-pos") + + mp.set_property_number("playlist-pos", playlist_pos) + + -- Tries to determine live stream vs. pre-recordered VOD. VOD has non-zero + -- duration property. When reloading VOD, to keep the current time position + -- we should provide offset from the start. Stream doesn't have fixed start. + -- Decent choice would be to reload stream from it's current 'live' positon. + -- That's the reason we don't pass the offset when reloading streams. + if reload_duration and reload_duration > 0 then + local function seeker() + mp.commandv("seek", time_pos, "absolute") + mp.unregister_event(seeker) + end + mp.register_event("file-loaded", seeker) + end +end diff --git a/mpv/scripts/youtube-serarch.lua b/mpv/scripts/youtube-serarch.lua new file mode 100644 index 0000000..dfe04c6 --- /dev/null +++ b/mpv/scripts/youtube-serarch.lua @@ -0,0 +1,149 @@ +--[[ + This script allows users to search and open youtube results from within mpv. + Available at: https://github.com/CogentRedTester/mpv-scripts + Users can open the search page with Y, and use Y again to open a search. + Alternatively, Ctrl+y can be used at any time to open a search. + Esc can be used to close the page. + Enter will open the selected item, Shift+Enter will append the item to the playlist. + This script requires that my other scripts `scroll-list` and `user-input` be installed. + scroll-list.lua and user-input-module.lua must be in the ~~/script-modules/ directory, + while user-input.lua should be loaded by mpv normally. + https://github.com/CogentRedTester/mpv-scroll-list + https://github.com/CogentRedTester/mpv-user-input + This script also requires a youtube API key to be entered. + The API key must be passed to the `API_key` script-opt. + A personal API key is free and can be created from: + https://console.developers.google.com/apis/api/youtube.googleapis.com/ + The script also requires that curl be in the system path. +]]-- + +local mp = require "mp" +local msg = require "mp.msg" +local utils = require "mp.utils" +local opts = require "mp.options" + +package.path = mp.command_native({"expand-path", "~~/lua-modules/?.lua;"}) .. package.path +local ui = require "user-input-module" +local list = require "scroll-list" + +list.header = "Youtube Search Results\\N-----------------------------" +list.num_entries = 18 +list.list_style = [[{\fs10}\N{\q2\fs25\c&Hffffff&}]] + +local o = { + API_key = "AIzaSyB3IuyFp8C5SYEgJ0BSGgKZZjfUtuVGl44", + num_results = 40 +} + +opts.read_options(o) + +local ass_escape = list.ass_escape + +--taken from: https://gist.github.com/liukun/f9ce7d6d14fa45fe9b924a3eed5c3d99 +local function urlencode(url) + if type(url) ~= "string" then return url end + url = url:gsub("\n", "\r\n") + url = url:gsub("([^%w ])", function (c) string.format("%%%02X", string.byte(c)) end) + url = url:gsub(" ", "+") + return url +end + +--sends an API request +local function send_request(type, queries) + local url = "https://www.googleapis.com/youtube/v3/"..type + + url = url.."?key="..o.API_key + + for key, value in pairs(queries) do + url = url.."&"..key.."="..urlencode(value) + end + + + local request = mp.command_native({ + name = "subprocess", + capture_stdout = true, + capture_stderr = true, + playback_only = false, + args = {"curl", url} + }) + + local response = utils.parse_json(request.stdout) + if request.status ~= 0 then msg.error(request.stderr) ; return nil end + return response +end + +local function insert_video(item) + list:insert({ + ass = ("%s {\\c&aaaaaa&}%s"):format(ass_escape(item.snippet.title), ass_escape(item.snippet.channelTitle)), + url = "https://www.youtube.com/watch?v="..item.id.videoId + }) +end + +local function insert_playlist(item) + list:insert({ + ass = ("🖿 %s {\\c&aaaaaa&}%s"):format(ass_escape(item.snippet.title), ass_escape(item.snippet.channelTitle)), + url = "https://www.youtube.com/playlist?list="..item.id.playlistId + }) +end + +local function insert_channel(item) + list:insert({ + ass = ("👤 %s"):format(ass_escape(item.snippet.title)), + url = "https://www.youtube.com/channel/"..item.id.channelId + }) +end + +local function reset_list() + list.selected = 1 + list:clear() +end + +local function search(query) + local response = send_request("search", { + q = query, + part = "id,snippet", + maxResults = o.num_results + }) + + if not response then return end + reset_list() + + for _, item in ipairs(response.items) do + if item.id.kind == "youtube#video" then + insert_video(item) + elseif item.id.kind == "youtube#playlist" then + insert_playlist(item) + elseif item.id.kind == "youtube#channel" then + insert_channel(item) + end + end + list.header = "Youtube Search: "..ass_escape(query).."\\N-------------------------------------------------" + list:update() + list:open() +end + +local function play_result(flag) + if not list[list.selected] then return end + if flag == "new_window" then mp.commandv("run", "mpv", list[list.selected].url) ; return end + + mp.commandv("loadfile", list[list.selected].url, flag) + if flag == "replace" then list:close() end +end + +table.insert(list.keybinds, {"ENTER", "play", function() play_result("replace") end, {}}) +table.insert(list.keybinds, {"Shift+ENTER", "play_append", function() play_result("append-play") end, {}}) +table.insert(list.keybinds, {"Ctrl+ENTER", "play_new_window", function() play_result("new_window") end, {}}) + +local function open_search_input() + ui.get_user_input(function(input) + if not input then return end + search( input ) + end) +end + +mp.add_key_binding("Ctrl+y", "yt", open_search_input) + +mp.add_key_binding("Y", "youtube-search", function() + if not list.hidden then open_search_input() + else list:open() end +end) diff --git a/ncmpcpp/config b/ncmpcpp/config new file mode 100644 index 0000000..39ca059 --- /dev/null +++ b/ncmpcpp/config @@ -0,0 +1,101 @@ +ncmpcpp_directory = ~/.ncmpcpp +lyrics_directory = ~/.ncmpcpp/lyrics + +mpd_host = "127.0.0.1" +mpd_port = "6600" +mpd_connection_timeout = 5 +mpd_crossfade_time = 5 + +mpd_music_dir = "/data/Music/" + +audio_output { + type "my_fifo" + name "Visualizer feed" + path "/tmp/mpd.fifo" + format "44100:16:2" +} +visualizer_output_name = "my_fifo" +visualizer_type = "ellipse" +execute_on_song_change = nowplaying + +visualizer_look = "●┃" +visualizer_in_stereo = "yes" + +system_encoding = "utf-8" +playlist_disable_highlight_delay = "0" +message_delay_time = 5 + +now_playing_suffix = $/b +selected_item_prefix = "* " + +browser_sort_mode = name +browser_sort_format = {%a - }{%t}|{%f} {(%l)} +song_list_format = "{{%a - %t}|{%f}}{$R%l}" +song_columns_list_format = "(50)[green]{t|f:Title} (40)[cyan]{ar} (5f)[cyan]{l}" +# song_columns_list_format = "(50)[cyan]{ar}(50)[green]{t|f:Title}" +song_status_format = "{$2 $8%a $3: $8%t $3 $6%b}|{$b$2 $8%f}" + +progressbar_elapsed_color = cyan +statusbar_color = green +statusbar_time_color = cyan + playlist_display_mode = "columns" + + browser_display_mode = "columns" + + playlist_disable_highlight_delay = "0" + + playlist_show_remaining_time = "yes" + +discard_colors_if_item_is_selected = "no" + +incremental_seeking = "yes" +seek_time = "1" + +autocenter_mode = yes +centered_cursor = yes +progressbar_look = "━|━" +# user_interface = "alternative" +# +header_visibility = no +statusbar_visibility = yes +titles_visibility = no +header_text_scrolling = yes +# +#follow_now_playing_lyrics = no +fetch_lyrics_for_current_song_in_background = yes +ask_before_clearing_playlists = yes +# +#clock_display_seconds = no +# + +display_volume_level = "no" + +display_bitrate = "no" +search_engine_default_search_mode = 2 + +external_editor = "/usr/bin/nvim" +use_console_editor = yes + + follow_now_playing_lyrics = "yes" + + display_screens_numbers_on_start = "yes" + + lyrics_database = "1" + +default_place_to_search_in = "database" + +lyrics_fetchers = azlyrics, genius, sing365, metrolyrics, justsomelyrics, jahlyrics, plyrics, tekstowo, internet + +## Navigation ## +cyclic_scrolling = "yes" +jump_to_now_playing_song_at_start = "yes" +lines_scrolled = "2" +#fancy_scrolling = "yes" + +## Other ## +regular_expressions = "extended" + +# enable_window_title = "no" +follow_now_playing_lyrics = "yes" +ignore_leading_the = "yes" +empty_tag_marker = "" diff --git a/neofetch/config.conf b/neofetch/config.conf new file mode 100644 index 0000000..cdba4c6 --- /dev/null +++ b/neofetch/config.conf @@ -0,0 +1,864 @@ +# See this wiki page for more info: +# https://github.com/dylanaraps/neofetch/wiki/Customizing-Info +print_info() { + info title + info underline + + info "OS" distro + info "Host" model + info "Kernel" kernel + info "Uptime" uptime + info "Packages" packages + info "Shell" shell + info "Resolution" resolution + info "DE" de + info "WM" wm + info "WM Theme" wm_theme + info "Theme" theme + info "Icons" icons + info "Terminal" term + info "Terminal Font" term_font + info "CPU" cpu + info "GPU" gpu + info "Memory" memory + + # info "GPU Driver" gpu_driver # Linux/macOS only + # info "CPU Usage" cpu_usage + # info "Disk" disk + # info "Battery" battery + # info "Font" font + # info "Song" song + # [[ "$player" ]] && prin "Music Player" "$player" + # info "Local IP" local_ip + # info "Public IP" public_ip + # info "Users" users + # info "Locale" locale # This only works on glibc systems. + + info cols +} + +# Title + + +# Hide/Show Fully qualified domain name. +# +# Default: 'off' +# Values: 'on', 'off' +# Flag: --title_fqdn +title_fqdn="off" + + +# Kernel + + +# Shorten the output of the kernel function. +# +# Default: 'on' +# Values: 'on', 'off' +# Flag: --kernel_shorthand +# Supports: Everything except *BSDs (except PacBSD and PC-BSD) +# +# Example: +# on: '4.8.9-1-ARCH' +# off: 'Linux 4.8.9-1-ARCH' +kernel_shorthand="on" + + +# Distro + + +# Shorten the output of the distro function +# +# Default: 'off' +# Values: 'on', 'tiny', 'off' +# Flag: --distro_shorthand +# Supports: Everything except Windows and Haiku +distro_shorthand="off" + +# Show/Hide OS Architecture. +# Show 'x86_64', 'x86' and etc in 'Distro:' output. +# +# Default: 'on' +# Values: 'on', 'off' +# Flag: --os_arch +# +# Example: +# on: 'Arch Linux x86_64' +# off: 'Arch Linux' +os_arch="on" + + +# Uptime + + +# Shorten the output of the uptime function +# +# Default: 'on' +# Values: 'on', 'tiny', 'off' +# Flag: --uptime_shorthand +# +# Example: +# on: '2 days, 10 hours, 3 mins' +# tiny: '2d 10h 3m' +# off: '2 days, 10 hours, 3 minutes' +uptime_shorthand="on" + + +# Memory + + +# Show memory pecentage in output. +# +# Default: 'off' +# Values: 'on', 'off' +# Flag: --memory_percent +# +# Example: +# on: '1801MiB / 7881MiB (22%)' +# off: '1801MiB / 7881MiB' +memory_percent="off" + +# Change memory output unit. +# +# Default: 'mib' +# Values: 'kib', 'mib', 'gib' +# Flag: --memory_unit +# +# Example: +# kib '1020928KiB / 7117824KiB' +# mib '1042MiB / 6951MiB' +# gib: ' 0.98GiB / 6.79GiB' +memory_unit="mib" + + +# Packages + + +# Show/Hide Package Manager names. +# +# Default: 'tiny' +# Values: 'on', 'tiny' 'off' +# Flag: --package_managers +# +# Example: +# on: '998 (pacman), 8 (flatpak), 4 (snap)' +# tiny: '908 (pacman, flatpak, snap)' +# off: '908' +package_managers="on" + + +# Shell + + +# Show the path to $SHELL +# +# Default: 'off' +# Values: 'on', 'off' +# Flag: --shell_path +# +# Example: +# on: '/bin/bash' +# off: 'bash' +shell_path="off" + +# Show $SHELL version +# +# Default: 'on' +# Values: 'on', 'off' +# Flag: --shell_version +# +# Example: +# on: 'bash 4.4.5' +# off: 'bash' +shell_version="on" + + +# CPU + + +# CPU speed type +# +# Default: 'bios_limit' +# Values: 'scaling_cur_freq', 'scaling_min_freq', 'scaling_max_freq', 'bios_limit'. +# Flag: --speed_type +# Supports: Linux with 'cpufreq' +# NOTE: Any file in '/sys/devices/system/cpu/cpu0/cpufreq' can be used as a value. +speed_type="bios_limit" + +# CPU speed shorthand +# +# Default: 'off' +# Values: 'on', 'off'. +# Flag: --speed_shorthand +# NOTE: This flag is not supported in systems with CPU speed less than 1 GHz +# +# Example: +# on: 'i7-6500U (4) @ 3.1GHz' +# off: 'i7-6500U (4) @ 3.100GHz' +speed_shorthand="off" + +# Enable/Disable CPU brand in output. +# +# Default: 'on' +# Values: 'on', 'off' +# Flag: --cpu_brand +# +# Example: +# on: 'Intel i7-6500U' +# off: 'i7-6500U (4)' +cpu_brand="on" + +# CPU Speed +# Hide/Show CPU speed. +# +# Default: 'on' +# Values: 'on', 'off' +# Flag: --cpu_speed +# +# Example: +# on: 'Intel i7-6500U (4) @ 3.1GHz' +# off: 'Intel i7-6500U (4)' +cpu_speed="on" + +# CPU Cores +# Display CPU cores in output +# +# Default: 'logical' +# Values: 'logical', 'physical', 'off' +# Flag: --cpu_cores +# Support: 'physical' doesn't work on BSD. +# +# Example: +# logical: 'Intel i7-6500U (4) @ 3.1GHz' (All virtual cores) +# physical: 'Intel i7-6500U (2) @ 3.1GHz' (All physical cores) +# off: 'Intel i7-6500U @ 3.1GHz' +cpu_cores="logical" + +# CPU Temperature +# Hide/Show CPU temperature. +# Note the temperature is added to the regular CPU function. +# +# Default: 'off' +# Values: 'C', 'F', 'off' +# Flag: --cpu_temp +# Supports: Linux, BSD +# NOTE: For FreeBSD and NetBSD-based systems, you'll need to enable +# coretemp kernel module. This only supports newer Intel processors. +# +# Example: +# C: 'Intel i7-6500U (4) @ 3.1GHz [27.2°C]' +# F: 'Intel i7-6500U (4) @ 3.1GHz [82.0°F]' +# off: 'Intel i7-6500U (4) @ 3.1GHz' +cpu_temp="off" + + +# GPU + + +# Enable/Disable GPU Brand +# +# Default: 'on' +# Values: 'on', 'off' +# Flag: --gpu_brand +# +# Example: +# on: 'AMD HD 7950' +# off: 'HD 7950' +gpu_brand="on" + +# Which GPU to display +# +# Default: 'all' +# Values: 'all', 'dedicated', 'integrated' +# Flag: --gpu_type +# Supports: Linux +# +# Example: +# all: +# GPU1: AMD HD 7950 +# GPU2: Intel Integrated Graphics +# +# dedicated: +# GPU1: AMD HD 7950 +# +# integrated: +# GPU1: Intel Integrated Graphics +gpu_type="all" + + +# Resolution + + +# Display refresh rate next to each monitor +# Default: 'off' +# Values: 'on', 'off' +# Flag: --refresh_rate +# Supports: Doesn't work on Windows. +# +# Example: +# on: '1920x1080 @ 60Hz' +# off: '1920x1080' +refresh_rate="off" + + +# Gtk Theme / Icons / Font + + +# Shorten output of GTK Theme / Icons / Font +# +# Default: 'off' +# Values: 'on', 'off' +# Flag: --gtk_shorthand +# +# Example: +# on: 'Numix, Adwaita' +# off: 'Numix [GTK2], Adwaita [GTK3]' +gtk_shorthand="off" + + +# Enable/Disable gtk2 Theme / Icons / Font +# +# Default: 'on' +# Values: 'on', 'off' +# Flag: --gtk2 +# +# Example: +# on: 'Numix [GTK2], Adwaita [GTK3]' +# off: 'Adwaita [GTK3]' +gtk2="on" + +# Enable/Disable gtk3 Theme / Icons / Font +# +# Default: 'on' +# Values: 'on', 'off' +# Flag: --gtk3 +# +# Example: +# on: 'Numix [GTK2], Adwaita [GTK3]' +# off: 'Numix [GTK2]' +gtk3="on" + + +# IP Address + + +# Website to ping for the public IP +# +# Default: 'http://ident.me' +# Values: 'url' +# Flag: --ip_host +public_ip_host="http://ident.me" + +# Public IP timeout. +# +# Default: '2' +# Values: 'int' +# Flag: --ip_timeout +public_ip_timeout=2 + + +# Desktop Environment + + +# Show Desktop Environment version +# +# Default: 'on' +# Values: 'on', 'off' +# Flag: --de_version +de_version="on" + + +# Disk + + +# Which disks to display. +# The values can be any /dev/sdXX, mount point or directory. +# NOTE: By default we only show the disk info for '/'. +# +# Default: '/' +# Values: '/', '/dev/sdXX', '/path/to/drive'. +# Flag: --disk_show +# +# Example: +# disk_show=('/' '/dev/sdb1'): +# 'Disk (/): 74G / 118G (66%)' +# 'Disk (/mnt/Videos): 823G / 893G (93%)' +# +# disk_show=('/'): +# 'Disk (/): 74G / 118G (66%)' +# +disk_show=('/') + +# Disk subtitle. +# What to append to the Disk subtitle. +# +# Default: 'mount' +# Values: 'mount', 'name', 'dir', 'none' +# Flag: --disk_subtitle +# +# Example: +# name: 'Disk (/dev/sda1): 74G / 118G (66%)' +# 'Disk (/dev/sdb2): 74G / 118G (66%)' +# +# mount: 'Disk (/): 74G / 118G (66%)' +# 'Disk (/mnt/Local Disk): 74G / 118G (66%)' +# 'Disk (/mnt/Videos): 74G / 118G (66%)' +# +# dir: 'Disk (/): 74G / 118G (66%)' +# 'Disk (Local Disk): 74G / 118G (66%)' +# 'Disk (Videos): 74G / 118G (66%)' +# +# none: 'Disk: 74G / 118G (66%)' +# 'Disk: 74G / 118G (66%)' +# 'Disk: 74G / 118G (66%)' +disk_subtitle="mount" + +# Disk percent. +# Show/Hide disk percent. +# +# Default: 'on' +# Values: 'on', 'off' +# Flag: --disk_percent +# +# Example: +# on: 'Disk (/): 74G / 118G (66%)' +# off: 'Disk (/): 74G / 118G' +disk_percent="on" + + +# Song + + +# Manually specify a music player. +# +# Default: 'auto' +# Values: 'auto', 'player-name' +# Flag: --music_player +# +# Available values for 'player-name': +# +# amarok +# audacious +# banshee +# bluemindo +# clementine +# cmus +# deadbeef +# deepin-music +# dragon +# elisa +# exaile +# gnome-music +# gmusicbrowser +# gogglesmm +# guayadeque +# io.elementary.music +# iTunes +# juk +# lollypop +# mocp +# mopidy +# mpd +# muine +# netease-cloud-music +# olivia +# playerctl +# pogo +# pragha +# qmmp +# quodlibet +# rhythmbox +# sayonara +# smplayer +# spotify +# strawberry +# tauonmb +# tomahawk +# vlc +# xmms2d +# xnoise +# yarock +music_player="auto" + +# Format to display song information. +# +# Default: '%artist% - %album% - %title%' +# Values: '%artist%', '%album%', '%title%' +# Flag: --song_format +# +# Example: +# default: 'Song: Jet - Get Born - Sgt Major' +song_format="%artist% - %album% - %title%" + +# Print the Artist, Album and Title on separate lines +# +# Default: 'off' +# Values: 'on', 'off' +# Flag: --song_shorthand +# +# Example: +# on: 'Artist: The Fratellis' +# 'Album: Costello Music' +# 'Song: Chelsea Dagger' +# +# off: 'Song: The Fratellis - Costello Music - Chelsea Dagger' +song_shorthand="off" + +# 'mpc' arguments (specify a host, password etc). +# +# Default: '' +# Example: mpc_args=(-h HOST -P PASSWORD) +mpc_args=() + + +# Text Colors + + +# Text Colors +# +# Default: 'distro' +# Values: 'distro', 'num' 'num' 'num' 'num' 'num' 'num' +# Flag: --colors +# +# Each number represents a different part of the text in +# this order: 'title', '@', 'underline', 'subtitle', 'colon', 'info' +# +# Example: +# colors=(distro) - Text is colored based on Distro colors. +# colors=(4 6 1 8 8 6) - Text is colored in the order above. +colors=(distro) + + +# Text Options + + +# Toggle bold text +# +# Default: 'on' +# Values: 'on', 'off' +# Flag: --bold +bold="on" + +# Enable/Disable Underline +# +# Default: 'on' +# Values: 'on', 'off' +# Flag: --underline +underline_enabled="on" + +# Underline character +# +# Default: '-' +# Values: 'string' +# Flag: --underline_char +underline_char="-" + + +# Info Separator +# Replace the default separator with the specified string. +# +# Default: ':' +# Flag: --separator +# +# Example: +# separator="->": 'Shell-> bash' +# separator=" =": 'WM = dwm' +separator=":" + + +# Color Blocks + + +# Color block range +# The range of colors to print. +# +# Default: '0', '15' +# Values: 'num' +# Flag: --block_range +# +# Example: +# +# Display colors 0-7 in the blocks. (8 colors) +# neofetch --block_range 0 7 +# +# Display colors 0-15 in the blocks. (16 colors) +# neofetch --block_range 0 15 +block_range=(0 15) + +# Toggle color blocks +# +# Default: 'on' +# Values: 'on', 'off' +# Flag: --color_blocks +color_blocks="on" + +# Color block width in spaces +# +# Default: '3' +# Values: 'num' +# Flag: --block_width +block_width=3 + +# Color block height in lines +# +# Default: '1' +# Values: 'num' +# Flag: --block_height +block_height=1 + +# Color Alignment +# +# Default: 'auto' +# Values: 'auto', 'num' +# Flag: --col_offset +# +# Number specifies how far from the left side of the terminal (in spaces) to +# begin printing the columns, in case you want to e.g. center them under your +# text. +# Example: +# col_offset="auto" - Default behavior of neofetch +# col_offset=7 - Leave 7 spaces then print the colors +col_offset="auto" + +# Progress Bars + + +# Bar characters +# +# Default: '-', '=' +# Values: 'string', 'string' +# Flag: --bar_char +# +# Example: +# neofetch --bar_char 'elapsed' 'total' +# neofetch --bar_char '-' '=' +bar_char_elapsed="-" +bar_char_total="=" + +# Toggle Bar border +# +# Default: 'on' +# Values: 'on', 'off' +# Flag: --bar_border +bar_border="on" + +# Progress bar length in spaces +# Number of chars long to make the progress bars. +# +# Default: '15' +# Values: 'num' +# Flag: --bar_length +bar_length=15 + +# Progress bar colors +# When set to distro, uses your distro's logo colors. +# +# Default: 'distro', 'distro' +# Values: 'distro', 'num' +# Flag: --bar_colors +# +# Example: +# neofetch --bar_colors 3 4 +# neofetch --bar_colors distro 5 +bar_color_elapsed="distro" +bar_color_total="distro" + + +# Info display +# Display a bar with the info. +# +# Default: 'off' +# Values: 'bar', 'infobar', 'barinfo', 'off' +# Flags: --cpu_display +# --memory_display +# --battery_display +# --disk_display +# +# Example: +# bar: '[---=======]' +# infobar: 'info [---=======]' +# barinfo: '[---=======] info' +# off: 'info' +cpu_display="off" +memory_display="off" +battery_display="off" +disk_display="off" + + +# Backend Settings + + +# Image backend. +# +# Default: 'ascii' +# Values: 'ascii', 'caca', 'chafa', 'jp2a', 'iterm2', 'off', +# 'pot', 'termpix', 'pixterm', 'tycat', 'w3m', 'kitty' +# Flag: --backend +image_backend="ascii" + +# Image Source +# +# Which image or ascii file to display. +# +# Default: 'auto' +# Values: 'auto', 'ascii', 'wallpaper', '/path/to/img', '/path/to/ascii', '/path/to/dir/' +# 'command output (neofetch --ascii "$(fortune | cowsay -W 30)")' +# Flag: --source +# +# NOTE: 'auto' will pick the best image source for whatever image backend is used. +# In ascii mode, distro ascii art will be used and in an image mode, your +# wallpaper will be used. +image_source="auto" + + +# Ascii Options + + +# Ascii distro +# Which distro's ascii art to display. +# +# Default: 'auto' +# Values: 'auto', 'distro_name' +# Flag: --ascii_distro +# NOTE: AIX, Alpine, Anarchy, Android, Antergos, antiX, "AOSC OS", +# "AOSC OS/Retro", Apricity, ArcoLinux, ArchBox, ARCHlabs, +# ArchStrike, XFerience, ArchMerge, Arch, Artix, Arya, Bedrock, +# Bitrig, BlackArch, BLAG, BlankOn, BlueLight, bonsai, BSD, +# BunsenLabs, Calculate, Carbs, CentOS, Chakra, ChaletOS, +# Chapeau, Chrom*, Cleanjaro, ClearOS, Clear_Linux, Clover, +# Condres, Container_Linux, CRUX, Cucumber, Debian, Deepin, +# DesaOS, Devuan, DracOS, DarkOs, DragonFly, Drauger, Elementary, +# EndeavourOS, Endless, EuroLinux, Exherbo, Fedora, Feren, FreeBSD, +# FreeMiNT, Frugalware, Funtoo, GalliumOS, Garuda, Gentoo, Pentoo, +# gNewSense, GNOME, GNU, GoboLinux, Grombyang, Guix, Haiku, Huayra, +# Hyperbola, janus, Kali, KaOS, KDE_neon, Kibojoe, Kogaion, +# Korora, KSLinux, Kubuntu, LEDE, LFS, Linux_Lite, +# LMDE, Lubuntu, Lunar, macos, Mageia, MagpieOS, Mandriva, +# Manjaro, Maui, Mer, Minix, LinuxMint, MX_Linux, Namib, +# Neptune, NetBSD, Netrunner, Nitrux, NixOS, Nurunner, +# NuTyX, OBRevenge, OpenBSD, openEuler, OpenIndiana, openmamba, +# OpenMandriva, OpenStage, OpenWrt, osmc, Oracle, OS Elbrus, PacBSD, +# Parabola, Pardus, Parrot, Parsix, TrueOS, PCLinuxOS, Peppermint, +# popos, Porteus, PostMarketOS, Proxmox, Puppy, PureOS, Qubes, Radix, +# Raspbian, Reborn_OS, Redstar, Redcore, Redhat, Refracted_Devuan, +# Regata, Rosa, sabotage, Sabayon, Sailfish, SalentOS, Scientific, +# Septor, SereneLinux, SharkLinux, Siduction, Slackware, SliTaz, +# SmartOS, Solus, Source_Mage, Sparky, Star, SteamOS, SunOS, +# openSUSE_Leap, openSUSE_Tumbleweed, openSUSE, SwagArch, Tails, +# Trisquel, Ubuntu-Budgie, Ubuntu-GNOME, Ubuntu-MATE, Ubuntu-Studio, +# Ubuntu, Venom, Void, Obarun, windows10, Windows7, Xubuntu, Zorin, +# and IRIX have ascii logos +# NOTE: Arch, Ubuntu, Redhat, and Dragonfly have 'old' logo variants. +# Use '{distro name}_old' to use the old logos. +# NOTE: Ubuntu has flavor variants. +# Change this to Lubuntu, Kubuntu, Xubuntu, Ubuntu-GNOME, +# Ubuntu-Studio, Ubuntu-Mate or Ubuntu-Budgie to use the flavors. +# NOTE: Arcolinux, Dragonfly, Fedora, Alpine, Arch, Ubuntu, +# CRUX, Debian, Gentoo, FreeBSD, Mac, NixOS, OpenBSD, android, +# Antrix, CentOS, Cleanjaro, ElementaryOS, GUIX, Hyperbola, +# Manjaro, MXLinux, NetBSD, Parabola, POP_OS, PureOS, +# Slackware, SunOS, LinuxLite, OpenSUSE, Raspbian, +# postmarketOS, and Void have a smaller logo variant. +# Use '{distro name}_small' to use the small variants. +ascii_distro="auto" + +# Ascii Colors +# +# Default: 'distro' +# Values: 'distro', 'num' 'num' 'num' 'num' 'num' 'num' +# Flag: --ascii_colors +# +# Example: +# ascii_colors=(distro) - Ascii is colored based on Distro colors. +# ascii_colors=(4 6 1 8 8 6) - Ascii is colored using these colors. +ascii_colors=(distro) + +# Bold ascii logo +# Whether or not to bold the ascii logo. +# +# Default: 'on' +# Values: 'on', 'off' +# Flag: --ascii_bold +ascii_bold="on" + + +# Image Options + + +# Image loop +# Setting this to on will make neofetch redraw the image constantly until +# Ctrl+C is pressed. This fixes display issues in some terminal emulators. +# +# Default: 'off' +# Values: 'on', 'off' +# Flag: --loop +image_loop="off" + +# Thumbnail directory +# +# Default: '~/.cache/thumbnails/neofetch' +# Values: 'dir' +thumbnail_dir="${XDG_CACHE_HOME:-${HOME}/.cache}/thumbnails/neofetch" + +# Crop mode +# +# Default: 'normal' +# Values: 'normal', 'fit', 'fill' +# Flag: --crop_mode +# +# See this wiki page to learn about the fit and fill options. +# https://github.com/dylanaraps/neofetch/wiki/What-is-Waifu-Crop%3F +crop_mode="normal" + +# Crop offset +# Note: Only affects 'normal' crop mode. +# +# Default: 'center' +# Values: 'northwest', 'north', 'northeast', 'west', 'center' +# 'east', 'southwest', 'south', 'southeast' +# Flag: --crop_offset +crop_offset="center" + +# Image size +# The image is half the terminal width by default. +# +# Default: 'auto' +# Values: 'auto', '00px', '00%', 'none' +# Flags: --image_size +# --size +image_size="auto" + +# Gap between image and text +# +# Default: '3' +# Values: 'num', '-num' +# Flag: --gap +gap=3 + +# Image offsets +# Only works with the w3m backend. +# +# Default: '0' +# Values: 'px' +# Flags: --xoffset +# --yoffset +yoffset=0 +xoffset=0 + +# Image background color +# Only works with the w3m backend. +# +# Default: '' +# Values: 'color', 'blue' +# Flag: --bg_color +background_color= + + +# Misc Options + +# Stdout mode +# Turn off all colors and disables image backend (ASCII/Image). +# Useful for piping into another command. +# Default: 'off' +# Values: 'on', 'off' +stdout="off" diff --git a/picom/picom.conf b/picom/picom.conf new file mode 100644 index 0000000..6870142 --- /dev/null +++ b/picom/picom.conf @@ -0,0 +1,148 @@ +# Animations + +transition-length = 250; +transition-pow-x = 0.3; +transition-pow-y = 0.3; +transition-pow-w = 0.3; +transition-pow-h = 0.3; +size-transition = true; + +## Corners + +corner-radius = 5; +round-borders = 5; +rounded-corners-exclude = [ + "window_type = 'dock'", + "window_type = 'desktop'", + "class_g = 'Plank'" +]; + +## Shadows + +shadow = true; +shadow-radius = 10; +shadow-opacity = 0.25; +shadow-offset-x = -10; +shadow-offset-y = -10; +# shadow-red = 0 +# shadow-green = 0 +# shadow-blue = 0 +shadow-color = "#000000" +shadow-exclude = [ + #"name = 'Notification'", + "class_g = 'Conky'", + #"class_g ?= 'Notify-osd'", + "class_g = 'firefox'", + #"class_g = 'Rofi'", + "class_g = 'Plank'", + "_GTK_FRAME_EXTENTS@:c" +]; +# shadow-exclude-reg = "" + +## Fading + +fading = true; +fade-in-step = 0.03; +fade-out-step = 0.03; +fade-delta = 7 +fade-exclude = [ + "class_g *?= \"rofi\"" +] +no-fading-openclose = false +no-fading-destroyed-argb = false + +## Transparency / Opacity + +inactive-opacity = 1.0; +frame-opacity = 1.0; +inactive-opacity-override = false; +active-opacity = 1.0 +inactive-dim = 0.0 +focus-exclude = [ + "class_g *?= \"rofi\"", + "class_g = 'Plank'" +]; +inactive-dim-fixed = 1.0 +# opacity-rule = [] + + +# Background-Blurring + +blur: { + method = "dual_kawase"; + strength = 4.0; + # deviation = 1.0; + # kernel = "11x11gaussian"; +} + +# blur-background = true; +# blur-background-frame = true; +# blur-kern = "3x3box"; +# blur-kern = "5,5,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1"; +# blur-background-fixed = true; + +blur-background-exclude = [ + "window_type = 'desktop'", + "window_type = 'utility'", + #"window_type = 'notification'", + "class_g = 'slop'", + #"class_g = 'Polybar'", + "class_g = 'Firefox' && argb", + "class_g = 'Plank'", + "name = 'rofi - Search'", + "_GTK_FRAME_EXTENTS@:c" +]; +# blur-method = "dual_kawase" +# blur-size = 12 +# blur-deviation = false +# blur-strength = 5 +# blur-background = false +# blur-background-frame = false +# blur-background-fixed = false + +# blur-kern = "5,5,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1"; +blur-kern = "3x3box"; + +# General Settings + +backend = "glx"; #xrender, glx, xr_glx_hybrid +vsync = true; + +mark-wmwin-focused = true; +mark-ovredir-focused = true; +detect-rounded-corners = true; +detect-client-opacity = true; +refresh-rate = 0; +use-ewmh-active-win = true; +unredir-if-possible = false +# unredir-if-possible-delay = 0 +# unredir-if-possible-exclude = [] + +detect-transient = true; +detect-client-leader = true; +# resize-damage = 1 +# invert-color-include = [] +# glx-no-stencil = false +# glx-no-rebind-pixmap = false + +use-damage = true; +xrender-sync-fence = true; + +# no-ewmh-fullscreen = false +# max-brightness = 1.0 +# transparent-clipping = false +log-level = "warn"; +# log-file = "/path/to/your/log/file" + +# 'WINDOW_TYPE' is one of the 15 window types defined in EWMH standard: +# "unknown", "desktop", "dock", "toolbar", "menu", "utility", +# "splash", "dialog", "normal", "dropdown_menu", "popup_menu", +# "tooltip", "notification", "combo", and "dnd". +wintypes: +{ + tooltip = { fade = true; shadow = true; opacity = 0.75; focus = true; full-shadow = false; }; + dock = { shadow = false; } + dnd = { shadow = false; } + popup_menu = { opacity = 0.95; } + dropdown_menu = { opacity = 0.95; } +}; diff --git a/polybar/colors.ini b/polybar/colors.ini new file mode 100644 index 0000000..0be6d33 --- /dev/null +++ b/polybar/colors.ini @@ -0,0 +1,21 @@ +;; ___ _ +;; / __\___ | | ___ _ __ ___ +;; / / / _ \| |/ _ \| '__/ __| +;;/ /__| (_) | | (_) | | \__ \ +;;\____/\___/|_|\___/|_| |___/ + +[colors] +background = #55000000 +border = #55444444 +background2 = #ff000000 + +foreground = #ffffff +foreground2 = #000000 +foreground3 = #bbbbbb + +contrast = #8bdfff +;contrast = #000000 +contrast2 = #ffa676 +contrast3 = #76ffa1 + +;; _-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_- diff --git a/polybar/config.ini b/polybar/config.ini new file mode 100644 index 0000000..5aa12ac --- /dev/null +++ b/polybar/config.ini @@ -0,0 +1,78 @@ +;; ___ _ _ +;; / _ \___ | |_ _| |__ __ _ _ __ +;; / /_)/ _ \| | | | | '_ \ / _` | '__| +;; / ___/ (_) | | |_| | |_) | (_| | | +;; \/ \___/|_|\__, |_.__/ \__,_|_| +;; |___/ +;; https://github.com/b4skyx/dotfiles + + +;------------------------- +;; Imports +include-file = ~/.config/polybar/colors.ini +include-file = ~/.config/polybar/modules.ini + +;------------------------- +[global/wm] +; margin-top = 5 +; margin-bottom = 5 + +;------------------------- + +[settings] +screenchange-reload = true +pseudo-transparency = false + +;------------------------- + +[bar/main] +monitor = +width = 100% +height = 32 +fixed-center = true +override-redirect = true +wm-restack = bspwm +bottom = false + +background = ${colors.background} +foreground = ${colors.foreground} + +line-size = 2 +line-color = #00000000 + +border-size = 0 +border-bottom-size = 1 +border-color = ${colors.border} + +padding-left = 0 +padding-right = 1 + +font-0 = FiraSans:pixelsize=11;2 +font-1 = Symbols Nerd Font:pixelsize=12;1 + +modules-left = launcher bspwm xwindow +modules-center = date time +modules-right = xkeyboard pulseaudio network battery powercontrol + +tray-position = right +tray-detached = false +tray-maxsize = 16 +tray-offset-x = 0 +tray-offset-y = 0 +tray-padding = 3 +tray-scale = 1.0 + +enable-ipc = true + +;------------------------- + +[bar/secondary] + +# Show systray only on first monitor + +inherit = bar/main + +monitor = DP-1 +tray-position = right + +;------------------------- diff --git a/polybar/launch.sh b/polybar/launch.sh new file mode 100755 index 0000000..38cf198 --- /dev/null +++ b/polybar/launch.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +## Add this to your wm startup file. + +# Terminate already running bar instances +killall -q polybar + +# Wait until the processes have been shut down +while pgrep -u $UID -x polybar >/dev/null; do sleep 1; done + +# Get network Interface +export DEFAULT_NETWORK_INTERFACE=$(ip route | grep '^default' | awk '{print $5}' | head -n1) + +# Load on second monitor if connected +external_monitor=$(xrandr --query | grep 'DP-1') +if [[ $external_monitor = DP-1\ connected* ]]; then + polybar -c ~/.config/polybar/config.ini secondary & +fi + +## Load bar on primary monitor +polybar -c ~/.config/polybar/config.ini main & + diff --git a/polybar/modules.ini b/polybar/modules.ini new file mode 100644 index 0000000..0401a5e --- /dev/null +++ b/polybar/modules.ini @@ -0,0 +1,218 @@ +[module/bspwm] +type = internal/bspwm + +ws-icon-0 = 1;1 +ws-icon-1 = 2;2 +ws-icon-2 = 3;3 +ws-icon-3 = 4;4 +ws-icon-4 = 5;5 +ws-icon-5 = 6;6 +ws-icon-6 = 7;7 +ws-icon-7 = 8;8 +ws-icon-8 = 9;9 + +label-focused = %icon% +label-focused-foreground = ${colors.foreground2} +label-focused-background = ${colors.contrast} +label-focused-padding = 3 + +label-occupied = %icon% +label-occupied-foreground = ${colors.foreground3} +label-occupied-padding = 2 + +label-urgent = %icon% +label-urgent-foreground = ${colors.contrast2} +label-urgent-padding = 2 + +label-empty = +label-empty-foreground = ${colors.foreground} +label-empty-padding = 2 + +;------------------------- + +[module/xwindow] +type = internal/xwindow +label = %title:0:100:...% +label-padding = 4 +label-empty = ~ +label-empty-padding = 4 +label-empty-foreground = ${colors.contrast4} +format-foreground = ${colors.foreground} + +;------------------------- + +[module/mpris] +type = custom/script +icon = 契 +exec = ~/.config/polybar/scripts/mpris_tail.py --icon-stopped '' --icon-paused '' --icon-playing '' --truncate-text '..' -f '%{A1:playerctl previous:}{icon} %{A} {:artist:t18:{artist}:}{:artist: - :}{:t25:{title}:}' +tail = true +format-foreground = ${colors.contrast3} +format-padding = 2 +label = "%output%" +click-left = ~/.config/polybar/scripts/mpris_tail.py play-pause & +click-right = ~/.config/polybar/scripts/mpris_tail.py next & + +;------------------------- + +[module/date] +type = internal/date +interval = 1 + +time = %A, %b %d + +label-padding = 2 +label = %time% + +[module/time] +type = internal/date +internal = 1 + +time = %H:%M +label-padding = 2 +label = %time% + +;------------------------- + +[module/network] +type = internal/network +interface = ${env:DEFAULT_NETWORK_INTERFACE:wlan0} + +interval = 3.0 +accumulate-stats = true +unknown-as-up = true + +format-connected = +format-connected-padding = 2 + +format-disconnected = +format-disconnected-padding = 2 + +;label-connected = "Connected" +;label-connected-padding = 2 +;label-disconnected = "No signal" +;label-disconnected-padding = 2 + +;ramp-signal-0 = +;ramp-signal-1 = +;ramp-signal-2 = +;ramp-signal-3 = +;ramp-signal-4 = + +;------------------------- + +[module/pulseaudio] +type = internal/pulseaudio + +use-ui-max = false +interval = 5 + +format-volume = <label-volume> +format-volume-padding = 2 + +label-volume = %percentage%% +label-volume-padding = 1 + +format-muted = <label-muted> +label-muted = "Muted" +label-muted-padding = 1 +format-muted-padding = 2 + +click-right = tdrop -am -w 1000 -h 650 -x 23% -y 17% -n 2 kitty -e pulsemixer + +;------------------------- + +[module/battery] +type = internal/battery + +full-at = 99 + +battery = BAT0 +adapter = ACAD + +poll-interval = 2 +time-format = %H:%M + +format-charging = <animation-charging> +format-charging-background = ${colors.dark3} +format-charging-foreground = ${colors.contrast1} +format-charging-padding = 2 + +format-discharging = <ramp-capacity> +format-discharging-background = ${colors.dark3} +format-discharging-foreground = ${colors.foreground} +format-discharging-padding = 2 + +;label-charging = %percentage%% +;label-discharging = %percentage%% + +label-full = +label-full-background = ${colors.dark3} +label-full-foreground = ${colors.foreground} +label-full-padding = 2 + +ramp-capacity-0 = +ramp-capacity-1 = +ramp-capacity-2 = +ramp-capacity-3 = +ramp-capacity-4 = +ramp-capacity-5 = +ramp-capacity-6 = +ramp-capacity-7 = +ramp-capacity-8 = +ramp-capacity-9 = + +animation-charging-0 = +animation-charging-1 = +animation-charging-2 = +animation-charging-3 = +animation-charging-4 = +animation-charging-5 = +animation-charging-6 = +animation-charging-7 = +animation-charging-8 = +animation-charging-framerate = 750 + +;------------------------- + +[module/xkeyboard] +type = internal/xkeyboard + +; blacklist-0 = num lock +; blacklist-1 = scroll lock +; blacklist-2 = caps lock + +format = <label-layout> <label-indicator> + +format-prefix = " " + +label-layout = %layout% +label-layout-padding = 1 + +blacklist-0 = num lock +blacklist-1 = scroll lock + +format-padding = 2 +label-indicator = %name% +label-indicator-on-capslock = + +;------------------------ + +[module/powercontrol] +type = custom/text + +format = ⏻ +format-foreground = ${colors.contrast} +format-padding = 2 + +click-left = rofi -show p -modi p:rofi-power-menu -theme iconic -show-icons + +;------------------------ + +[module/launcher] +type = custom/text + +format = +format-foreground = ${colors.contrast} +format-padding = 4 + +click-left = rofi -show drun -theme iconic -show-icons diff --git a/polybar/scripts/micfn b/polybar/scripts/micfn new file mode 100755 index 0000000..bebe7d6 --- /dev/null +++ b/polybar/scripts/micfn @@ -0,0 +1,34 @@ +#!/bin/sh + +function helpfn() { + echo "micfn, a quick mic toggle script" +} + +active_port=$(pactl list | sed -n '/^Source/,/^$/p' | grep 'Active Port' | cut -d ' ' -f3) + +if amixer get Capture | grep "\[off\]" > /dev/null +then + status="OFF" +else + status="ON" +fi + +function toggle() { + amixer set Capture toggle > /dev/null + if amixer get Capture | grep "\[off\]" > /dev/null + then + notify-send -t 3000 "$active_port" "Microphone OFF" + else + notify-send -t 3000 "$active_port" "Microphone ON" + fi +} + +if [ "$1" == "-h" -o "$1" == "--help" ]; then + helpfn + exit 0 +elif [ "$1" == "-t" -o "$1" == "--toggle" ]; then + toggle + exit 0 +elif [ $# -eq 0 ]; then + echo $status +fi diff --git a/polybar/scripts/mpris_tail.py b/polybar/scripts/mpris_tail.py new file mode 100755 index 0000000..9cd4900 --- /dev/null +++ b/polybar/scripts/mpris_tail.py @@ -0,0 +1,535 @@ +#!/usr/bin/env python3 + +import sys +import dbus +import os +from operator import itemgetter +import argparse +import re +from urllib.parse import unquote +import time +from dbus.mainloop.glib import DBusGMainLoop +from gi.repository import GLib +DBusGMainLoop(set_as_default=True) + + +FORMAT_STRING = '{icon} {artist} - {title}' +FORMAT_REGEX = re.compile(r'(\{:(?P<tag>.*?)(:(?P<format>[wt])(?P<formatlen>\d+))?:(?P<text>.*?):\})', re.I) +FORMAT_TAG_REGEX = re.compile(r'(?P<format>[wt])(?P<formatlen>\d+)') +SAFE_TAG_REGEX = re.compile(r'[{}]') + +class PlayerManager: + def __init__(self, blacklist = [], connect = True): + self.blacklist = blacklist + self._connect = connect + self._session_bus = dbus.SessionBus() + self.players = {} + + self.print_queue = [] + self.connected = False + self.player_states = {} + + self.refreshPlayerList() + + if self._connect: + self.connect() + loop = GLib.MainLoop() + try: + loop.run() + except KeyboardInterrupt: + print("interrupt received, stopping…") + + def connect(self): + self._session_bus.add_signal_receiver(self.onOwnerChangedName, 'NameOwnerChanged') + self._session_bus.add_signal_receiver(self.onChangedProperties, 'PropertiesChanged', + path = '/org/mpris/MediaPlayer2', + sender_keyword='sender') + + def onChangedProperties(self, interface, properties, signature, sender = None): + if sender in self.players: + player = self.players[sender] + # If we know this player, but haven't been able to set up a signal handler + if 'properties_changed' not in player._signals: + # Then trigger the signal handler manually + player.onPropertiesChanged(interface, properties, signature) + else: + # If we don't know this player, get its name and add it + bus_name = self.getBusNameFromOwner(sender) + if bus_name is None: + return + self.addPlayer(bus_name, sender) + player = self.players[sender] + player.onPropertiesChanged(interface, properties, signature) + + def onOwnerChangedName(self, bus_name, old_owner, new_owner): + if self.busNameIsAPlayer(bus_name): + if new_owner and not old_owner: + self.addPlayer(bus_name, new_owner) + elif old_owner and not new_owner: + self.removePlayer(old_owner) + else: + self.changePlayerOwner(bus_name, old_owner, new_owner) + + def getBusNameFromOwner(self, owner): + player_bus_names = [ bus_name for bus_name in self._session_bus.list_names() if self.busNameIsAPlayer(bus_name) ] + for player_bus_name in player_bus_names: + player_bus_owner = self._session_bus.get_name_owner(player_bus_name) + if owner == player_bus_owner: + return player_bus_name + + def busNameIsAPlayer(self, bus_name): + return bus_name.startswith('org.mpris.MediaPlayer2') and bus_name.split('.')[3] not in self.blacklist + + def refreshPlayerList(self): + player_bus_names = [ bus_name for bus_name in self._session_bus.list_names() if self.busNameIsAPlayer(bus_name) ] + for player_bus_name in player_bus_names: + self.addPlayer(player_bus_name) + if self.connected != True: + self.connected = True + self.printQueue() + + def addPlayer(self, bus_name, owner = None): + player = Player(self._session_bus, bus_name, owner = owner, connect = self._connect, _print = self.print) + self.players[player.owner] = player + + def removePlayer(self, owner): + if owner in self.players: + self.players[owner].disconnect() + del self.players[owner] + # If there are no more players, clear the output + if len(self.players) == 0: + _printFlush(ICON_NONE) + # Else, print the output of the next active player + else: + players = self.getSortedPlayerOwnerList() + if len(players) > 0: + self.players[players[0]].printStatus() + + def changePlayerOwner(self, bus_name, old_owner, new_owner): + player = Player(self._session_bus, bus_name, owner = new_owner, connect = self._connect, _print = self.print) + self.players[new_owner] = player + del self.players[old_owner] + + # Get a list of player owners sorted by current status and age + def getSortedPlayerOwnerList(self): + players = [ + { + 'number': int(owner.split('.')[-1]), + 'status': 2 if player.status == 'playing' else 1 if player.status == 'paused' else 0, + 'owner': owner + } + for owner, player in self.players.items() + ] + return [ info['owner'] for info in reversed(sorted(players, key=itemgetter('status', 'number'))) ] + + # Get latest player that's currently playing + def getCurrentPlayer(self): + playing_players = [ + player_owner for player_owner in self.getSortedPlayerOwnerList() + if + self.players[player_owner].status == 'playing' or + self.players[player_owner].status == 'paused' + ] + return self.players[playing_players[0]] if playing_players else None + + def print(self, status, player): + self.player_states[player.bus_name] = status + + if self.connected: + current_player = self.getCurrentPlayer() + if current_player != None: + _printFlush(self.player_states[current_player.bus_name]) + else: + _printFlush(ICON_STOPPED) + else: + self.print_queue.append([status, player]) + + def printQueue(self): + for args in self.print_queue: + self.print(args[0], args[1]) + self.print_queue.clear() + + +class Player: + def __init__(self, session_bus, bus_name, owner = None, connect = True, _print = None): + self._session_bus = session_bus + self.bus_name = bus_name + self._disconnecting = False + self.__print = _print + + self.metadata = { + 'artist' : '', + 'album' : '', + 'title' : '', + 'track' : 0 + } + + self._rate = 1. + self._positionAtLastUpdate = 0. + self._timeAtLastUpdate = time.time() + self._positionTimerRunning = False + + self._metadata = None + self.status = 'stopped' + self.icon = ICON_NONE + self.icon_reversed = ICON_PLAYING + if owner is not None: + self.owner = owner + else: + self.owner = self._session_bus.get_name_owner(bus_name) + self._obj = self._session_bus.get_object(self.bus_name, '/org/mpris/MediaPlayer2') + self._properties_interface = dbus.Interface(self._obj, dbus_interface='org.freedesktop.DBus.Properties') + self._introspect_interface = dbus.Interface(self._obj, dbus_interface='org.freedesktop.DBus.Introspectable') + self._media_interface = dbus.Interface(self._obj, dbus_interface='org.mpris.MediaPlayer2') + self._player_interface = dbus.Interface(self._obj, dbus_interface='org.mpris.MediaPlayer2.Player') + self._introspect = self._introspect_interface.get_dbus_method('Introspect', dbus_interface=None) + self._getProperty = self._properties_interface.get_dbus_method('Get', dbus_interface=None) + self._playerPlay = self._player_interface.get_dbus_method('Play', dbus_interface=None) + self._playerPause = self._player_interface.get_dbus_method('Pause', dbus_interface=None) + self._playerPlayPause = self._player_interface.get_dbus_method('PlayPause', dbus_interface=None) + self._playerStop = self._player_interface.get_dbus_method('Stop', dbus_interface=None) + self._playerPrevious = self._player_interface.get_dbus_method('Previous', dbus_interface=None) + self._playerNext = self._player_interface.get_dbus_method('Next', dbus_interface=None) + self._playerRaise = self._media_interface.get_dbus_method('Raise', dbus_interface=None) + self._signals = {} + + self.refreshPosition() + self.refreshStatus() + self.refreshMetadata() + + if connect: + self.printStatus() + self.connect() + + def play(self): + self._playerPlay() + def pause(self): + self._playerPause() + def playpause(self): + self._playerPlayPause() + def stop(self): + self._playerStop() + def previous(self): + self._playerPrevious() + def next(self): + self._playerNext() + def raisePlayer(self): + self._playerRaise() + + def connect(self): + if self._disconnecting is not True: + introspect_xml = self._introspect(self.bus_name, '/') + if 'TrackMetadataChanged' in introspect_xml: + self._signals['track_metadata_changed'] = self._session_bus.add_signal_receiver(self.onMetadataChanged, 'TrackMetadataChanged', self.bus_name) + self._signals['seeked'] = self._player_interface.connect_to_signal('Seeked', self.onSeeked) + self._signals['properties_changed'] = self._properties_interface.connect_to_signal('PropertiesChanged', self.onPropertiesChanged) + + def disconnect(self): + self._disconnecting = True + for signal_name, signal_handler in list(self._signals.items()): + signal_handler.remove() + del self._signals[signal_name] + + def refreshStatus(self): + # Some clients (VLC) will momentarily create a new player before removing it again + # so we can't be sure the interface still exists + try: + self.status = str(self._getProperty('org.mpris.MediaPlayer2.Player', 'PlaybackStatus')).lower() + self.updateIcon() + self.checkPositionTimer() + except dbus.exceptions.DBusException: + self.disconnect() + + def refreshMetadata(self): + # Some clients (VLC) will momentarily create a new player before removing it again + # so we can't be sure the interface still exists + try: + self._metadata = self._getProperty('org.mpris.MediaPlayer2.Player', 'Metadata') + self._parseMetadata() + except dbus.exceptions.DBusException: + self.disconnect() + + def updateIcon(self): + self.icon = ( + ICON_PLAYING if self.status == 'playing' else + ICON_PAUSED if self.status == 'paused' else + ICON_STOPPED if self.status == 'stopped' else + ICON_NONE + ) + self.icon_reversed = ( + ICON_PAUSED if self.status == 'playing' else + ICON_PLAYING + ) + + def _print(self, status): + self.__print(status, self) + + def _parseMetadata(self): + if self._metadata != None: + # Obtain properties from _metadata + _artist = _getProperty(self._metadata, 'xesam:artist', ['']) + _album = _getProperty(self._metadata, 'xesam:album', '') + _title = _getProperty(self._metadata, 'xesam:title', '') + _track = _getProperty(self._metadata, 'xesam:trackNumber', '') + _genre = _getProperty(self._metadata, 'xesam:genre', ['']) + _disc = _getProperty(self._metadata, 'xesam:discNumber', '') + _length = _getProperty(self._metadata, 'xesam:length', 0) or _getProperty(self._metadata, 'mpris:length', 0) + _length_int = _length if type(_length) is int else int(float(_length)) + _date = _getProperty(self._metadata, 'xesam:contentCreated', '') + _year = _date[0:4] if len(_date) else '' + _url = _getProperty(self._metadata, 'xesam:url', '') + _cover = _getProperty(self._metadata, 'xesam:artUrl', '') or _getProperty(self._metadata, 'mpris:artUrl', '') + _duration = _getDuration(_length_int) + # Update metadata + self.metadata['artist'] = re.sub(SAFE_TAG_REGEX, """\1\1""", _metadataGetFirstItem(_artist)) + self.metadata['album'] = re.sub(SAFE_TAG_REGEX, """\1\1""", _metadataGetFirstItem(_album)) + self.metadata['title'] = re.sub(SAFE_TAG_REGEX, """\1\1""", _metadataGetFirstItem(_title)) + self.metadata['track'] = _track + self.metadata['genre'] = re.sub(SAFE_TAG_REGEX, """\1\1""", _metadataGetFirstItem(_genre)) + self.metadata['disc'] = _disc + self.metadata['date'] = re.sub(SAFE_TAG_REGEX, """\1\1""", _date) + self.metadata['year'] = re.sub(SAFE_TAG_REGEX, """\1\1""", _year) + self.metadata['url'] = _url + self.metadata['filename'] = os.path.basename(_url) + self.metadata['length'] = _length_int + self.metadata['cover'] = re.sub(SAFE_TAG_REGEX, """\1\1""", _metadataGetFirstItem(_cover)) + self.metadata['duration'] = _duration + + def onMetadataChanged(self, track_id, metadata): + self.refreshMetadata() + self.printStatus() + + def onPropertiesChanged(self, interface, properties, signature): + updated = False + if dbus.String('Metadata') in properties: + _metadata = properties[dbus.String('Metadata')] + if _metadata != self._metadata: + self._metadata = _metadata + self._parseMetadata() + updated = True + if dbus.String('PlaybackStatus') in properties: + status = str(properties[dbus.String('PlaybackStatus')]).lower() + if status != self.status: + self.status = status + self.checkPositionTimer() + self.updateIcon() + updated = True + if dbus.String('Rate') in properties and dbus.String('PlaybackStatus') not in properties: + self.refreshStatus() + if NEEDS_POSITION and dbus.String('Rate') in properties: + rate = properties[dbus.String('Rate')] + if rate != self._rate: + self._rate = rate + self.refreshPosition() + + if updated: + self.refreshPosition() + self.printStatus() + + def checkPositionTimer(self): + if NEEDS_POSITION and self.status == 'playing' and not self._positionTimerRunning: + self._positionTimerRunning = True + GLib.timeout_add_seconds(1, self._positionTimer) + + def onSeeked(self, position): + self.refreshPosition() + self.printStatus() + + def _positionTimer(self): + self.printStatus() + self._positionTimerRunning = self.status == 'playing' + return self._positionTimerRunning + + def refreshPosition(self): + try: + time_us = self._getProperty('org.mpris.MediaPlayer2.Player', 'Position') + except dbus.exceptions.DBusException: + time_us = 0 + + self._timeAtLastUpdate = time.time() + self._positionAtLastUpdate = time_us / 1000000 + + def _getPosition(self): + if self.status == 'playing': + return self._positionAtLastUpdate + self._rate * (time.time() - self._timeAtLastUpdate) + else: + return self._positionAtLastUpdate + + def _statusReplace(self, match, metadata): + tag = match.group('tag') + format = match.group('format') + formatlen = match.group('formatlen') + text = match.group('text') + tag_found = False + reversed_tag = False + + if tag.startswith('-'): + tag = tag[1:] + reversed_tag = True + + if format is None: + tag_is_format_match = re.match(FORMAT_TAG_REGEX, tag) + if tag_is_format_match: + format = tag_is_format_match.group('format') + formatlen = tag_is_format_match.group('formatlen') + tag_found = True + if format is not None: + text = text.format_map(CleanSafeDict(**metadata)) + if format == 'w': + formatlen = int(formatlen) + text = text[:formatlen] + elif format == 't': + formatlen = int(formatlen) + if len(text) > formatlen: + text = text[:max(formatlen - len(TRUNCATE_STRING), 0)] + TRUNCATE_STRING + if tag_found is False and tag in metadata and len(metadata[tag]): + tag_found = True + + if reversed_tag: + tag_found = not tag_found + + if tag_found: + return text + else: + return '' + + def printStatus(self): + if self.status in [ 'playing', 'paused' ]: + metadata = { **self.metadata, 'icon': self.icon, 'icon-reversed': self.icon_reversed } + if NEEDS_POSITION: + metadata['position'] = time.strftime("%M:%S", time.gmtime(self._getPosition())) + # replace metadata tags in text + text = re.sub(FORMAT_REGEX, lambda match: self._statusReplace(match, metadata), FORMAT_STRING) + # restore polybar tag formatting and replace any remaining metadata tags after that + try: + text = re.sub(r'p(.*?)p(.*?)p(.*?)p', r'%{\1}\2%{\3}', text.format_map(CleanSafeDict(**metadata))) + except: + print("Invalid format string") + self._print(text) + else: + self._print(ICON_STOPPED) + + +def _dbusValueToPython(value): + if isinstance(value, dbus.Dictionary): + return {_dbusValueToPython(key): _dbusValueToPython(value) for key, value in value.items()} + elif isinstance(value, dbus.Array): + return [ _dbusValueToPython(item) for item in value ] + elif isinstance(value, dbus.Boolean): + return int(value) == 1 + elif ( + isinstance(value, dbus.Byte) or + isinstance(value, dbus.Int16) or + isinstance(value, dbus.UInt16) or + isinstance(value, dbus.Int32) or + isinstance(value, dbus.UInt32) or + isinstance(value, dbus.Int64) or + isinstance(value, dbus.UInt64) + ): + return int(value) + elif isinstance(value, dbus.Double): + return float(value) + elif ( + isinstance(value, dbus.ObjectPath) or + isinstance(value, dbus.Signature) or + isinstance(value, dbus.String) + ): + return unquote(str(value)) + +def _getProperty(properties, property, default = None): + value = default + if not isinstance(property, dbus.String): + property = dbus.String(property) + if property in properties: + value = properties[property] + return _dbusValueToPython(value) + else: + return value + +def _getDuration(t: int): + seconds = t / 1000000 + return time.strftime("%M:%S", time.gmtime(seconds)) + +def _metadataGetFirstItem(_value): + if type(_value) is list: + # Returns the string representation of the first item on _value if it has at least one item. + # Returns an empty string if _value is empty. + return str(_value[0]) if len(_value) else '' + else: + # If _value isn't a list just return the string representation of _value. + return str(_value) + +class CleanSafeDict(dict): + def __missing__(self, key): + return '{{{}}}'.format(key) + + +""" +Seems to assure print() actually prints when no terminal is connected +""" + +_last_status = '' +def _printFlush(status, **kwargs): + global _last_status + if status != _last_status: + print(status, **kwargs) + sys.stdout.flush() + _last_status = status + + + +parser = argparse.ArgumentParser() +parser.add_argument('command', help="send the given command to the active player", + choices=[ 'play', 'pause', 'play-pause', 'stop', 'previous', 'next', 'status', 'list', 'current', 'metadata', 'raise' ], + default=None, + nargs='?') +parser.add_argument('-b', '--blacklist', help="ignore a player by it's bus name. Can be be given multiple times (e.g. -b vlc -b audacious)", + action='append', + metavar="BUS_NAME", + default=[]) +parser.add_argument('-f', '--format', default='{icon} {:artist:{artist} - :}{:title:{title}:}{:-title:{filename}:}') +parser.add_argument('--truncate-text', default='…') +parser.add_argument('--icon-playing', default='⏵') +parser.add_argument('--icon-paused', default='⏸') +parser.add_argument('--icon-stopped', default='⏹') +parser.add_argument('--icon-none', default='') +args = parser.parse_args() + +FORMAT_STRING = re.sub(r'%\{(.*?)\}(.*?)%\{(.*?)\}', r'p\1p\2p\3p', args.format) +NEEDS_POSITION = "{position}" in FORMAT_STRING + +TRUNCATE_STRING = args.truncate_text +ICON_PLAYING = args.icon_playing +ICON_PAUSED = args.icon_paused +ICON_STOPPED = args.icon_stopped +ICON_NONE = args.icon_none + +if args.command is None: + PlayerManager(blacklist = args.blacklist) +else: + player_manager = PlayerManager(blacklist = args.blacklist, connect = False) + current_player = player_manager.getCurrentPlayer() + if args.command == 'play' and current_player: + current_player.play() + elif args.command == 'pause' and current_player: + current_player.pause() + elif args.command == 'play-pause' and current_player: + current_player.playpause() + elif args.command == 'stop' and current_player: + current_player.stop() + elif args.command == 'previous' and current_player: + current_player.previous() + elif args.command == 'next' and current_player: + current_player.next() + elif args.command == 'status' and current_player: + current_player.printStatus() + elif args.command == 'list': + print("\n".join(sorted([ + "{} : {}".format(player.bus_name.split('.')[3], player.status) + for player in player_manager.players.values() ]))) + elif args.command == 'current' and current_player: + print("{} : {}".format(current_player.bus_name.split('.')[3], current_player.status)) + elif args.command == 'metadata' and current_player: + print(_dbusValueToPython(current_player._metadata)) + elif args.command == 'raise' and current_player: + current_player.raisePlayer() diff --git a/redshift.conf b/redshift.conf new file mode 100644 index 0000000..587d209 --- /dev/null +++ b/redshift.conf @@ -0,0 +1,14 @@ +[redshift] +; Set the day and night screen temperatures +temp-day=6500 +temp-night=3700 +; enabling smooth transition +transition=1 +adjustment-method=randr +; Now specify the location manually +location-provider=manual +[manual] +; use the Internet to get your latitudes and longitudes +; below is the latitude and longitude for Delhi +lat=28.38 +lon=77.12 diff --git a/rofi/colors.rasi b/rofi/colors.rasi new file mode 100644 index 0000000..48b4a9c --- /dev/null +++ b/rofi/colors.rasi @@ -0,0 +1,13 @@ +* { + /* Theme Colors */ + bg: #101010; + bg2: #4e4949; + + fg: #cccccc; + fg2: #ebdada; + + border: #202020; + border2: #615a5a; + + transparent: #00000000; +} diff --git a/rofi/config.rasi b/rofi/config.rasi new file mode 100644 index 0000000..c45acb9 --- /dev/null +++ b/rofi/config.rasi @@ -0,0 +1 @@ +@import "simple" diff --git a/rofi/iconic.rasi b/rofi/iconic.rasi new file mode 100644 index 0000000..a2c8388 --- /dev/null +++ b/rofi/iconic.rasi @@ -0,0 +1,119 @@ +configuration { + display-drun: ""; + display-run: ""; + display-window: ""; + drun-display-format: "{name}"; + icon-theme: "Papirus"; + fixed-num-lines: true; + font: "FiraSans Regular 14"; +} + +@import "colors" + +window { + width: 20em; + spacing: 0; + transparency: "real"; + background-color: @bg; + padding: 0em; + border: 0; + border-color: @transparent; + border-radius: 2; +} + +mainbox { + background-color: @transparent; +} + +inputbar { + background-color: @bg; + text-color: @fg; + border-color: @border; + children: [prompt, entry]; + padding: 0; + border: 0; + margin: 0 0 0em 0; + border-radius: 2; +} + +prompt { + background-color: inherit; + text-color: @fg; + padding: 1em 0.75em; + border-radius: 2; +} + +entry { + background-color: inherit; + text-color: @fg; + placeholder: "Search..."; + padding: 1em 0 1em 0; + width: 10%; + horizontal-align: 0.013; + vertical-align: 0; + border: 0; + border-radius: 2; +} + +listview { + background-color: @transparent; + text-color: @fg; + cycle: false; + dynamic: true; + border: 0; + border-radius: 2; + layout: vertical; + spacing: 0.1em; + lines: 6; + columns: 1; +} + +element { + background-color: @bg; + text-color: @fg; + border: 0; + border-radius: 0; + orientation: horizontal; +} + +element-icon { + background-color: @transparent; + border: 0; + border-radius: 0; + size: 28px; + padding: 0.25em 0.5em; + horizontal-align: 0.5; + vertical-align: 0.5; +} + +element-text { + background-color: @transparent; + text-color: @fg; + text-size: 6; + border: 0; + border-radius: 0; + padding: 0.2em 0.2em 0.2em 0; + horizontal-align: 0; + vertical-align: 0.5; +} + +element selected { + background-color: @bg2; + text-color: @fg2; + border-color: @bg2; + border: 1; + border-radius: 0; +} + +element-text selected { + background-color: @bg2; + text-color: @fg2; + border-color: @bg2; + border: 0; +} +element-icon selected { + background-color: @bg2; + text-color: @fg2; + border-color: @bg2; + border: 0; +} diff --git a/rofi/simple.rasi b/rofi/simple.rasi new file mode 100644 index 0000000..584d93f --- /dev/null +++ b/rofi/simple.rasi @@ -0,0 +1,90 @@ +configuration { + display-drun: ""; + display-run: ""; + display-window: "缾"; + icon-theme: "Flatery"; + fixed-num-lines: true; + font: "Sarasa UI K 12"; +} + +@import "colors" + +window { + width: 30em; + spacing: 0; + transparency: "real"; + background-color: @bg; + border: 3; + border-color: @bg2; + border-radius: 0; +} + +inputbar { + background-color: @bg; + text-color: @fg; + border-color: @bg2; + children: [prompt, entry]; + border: 0; +} + +prompt { + background-color: @bg; + text-color: @fg; + padding: 0.5em 0.5em 0.5em 0.5em; + border: 0; +} + +entry { + background-color: @bg; + text-color: @fg; + placeholder: ""; + padding: 0.5em 0 0.5em 0; + border: 0; +} + +listview { + background-color: @bg; + cycle: false; + dynamic: true; + padding: 0 0 0 0; + border: 0; + lines: 8; + columns: 1; + children: [element]; +} + +scrollbar { + handle-width: 0; +} + +mainbox { + background-color: @bg; + border: 0; +} + +element { + background-color: @bg; + text-color: @fg; + padding: 0px; + border: 0; +} + +element selected { + background-color: @bg2; + text-color: @fg; + border: 0; +} + +element-text { + background-color: @bg; + text-color: @fg; + border: 0; + padding: 0.2em 0.2em 0.2em 0.2em; +} + +element-text selected { + background-color: @bg2; + text-color: @fg; + border: 0; + padding: 0.2em 0.2em 0.2em 0.2em; +} diff --git a/sxhkd/sxhkdrc b/sxhkd/sxhkdrc new file mode 100644 index 0000000..f1f9a2a --- /dev/null +++ b/sxhkd/sxhkdrc @@ -0,0 +1,229 @@ +####################### +# Program Keybindings # +####################### + +# Spawn Terminal of a certain geometry +super + Return + alacritty + +# Rofi +super + d + rofi -show drun -theme iconic -show-icons + +# rofi-emoji script +super + j + rofi-emoji + +# Screenshot +Print + maim -s ~/Pictures/Screenshots/$(date +%Y-%m-%d_%H-%M-%S).png + +shift + Print + maim --window $(xdotool getactivewindow) ~/Pictures/Screenshots/$(date +%Y-%m-%d_%H-%M-%S).png + +##################### +# BSPWM Keybindings # +##################### + +#----~ Init ~----# + +# make sxhkd reload its configuration files: +super + Escape + pkill -USR1 -x sxhkd + +# Toggle compositor +super + shift + c + picom_toggle + +# Toggle glava +super + shift + m + pkill -USR1 -x glava || glava --desktop > /dev/null & + +# Reload Polybar +super + shift + Escape + ~/.config/polybar/launch.sh + +# quit/restart bspwm +super + alt + {q,r} + bspc {quit,wm -r} + +# Quit all ~ return to login +super + alt + x + kill -9 -1 + +# close and kill windows +super + {_,shift + }q + bspc node -{c,k} + +#----~ Workspace Switching ~----# + +# Switch Active Workspaces +alt + {Tab, shift + Tab} + bspc {desktop next.occupied -f, desktop prev.occupied -f} + +# focus the last node/desktop +super + {grave,Tab} + bspc {node,desktop} -f last + +#----~ Workspace Management ~----# + +# alternate between the tiled and monocle layout +super + m + bspc desktop -l next +super + f + bspc node -t \~fullscreen + +# swap the current node and the biggest local node +super + g + bspc node -s biggest.local + +# set the node flags +super + ctrl + {m,l,s,p} + bspc node -g {marked,locked,sticky,private} + +# Toggle between floating and tiling +super + u + bspc node focused.tiled -t floating || bspc node focused.floating -t tiled + +# Toggle between pseudo-tiling and tiling +super + i + bspc node focused.tiled -t pseudo_tiled || bspc node -t tiled + +# Mark node as hidden/unhide all +super + {_, shift} + h + {bspc node --flag hidden=on, bspunhide} + +# Hide all on current desktop +super + ctrl + h + bsphide + +# Rotate layout by 90 degrees +super + shift + r + bspc node @/ -R 90 + +#send node to next/previous window +super + {_, shift} + k + bspc node -d {next, prev} -f + +#----~ Focus Settings ~----# + +# focus the node in the given direction +super + {Left, Down, Up, Right} + bspc node -f {west,south,north,east} + +# focus the node for the given path jump +super + shift + {p,b} + bspc node -f @{parent,brother} + +# focus the next/previous node in the current desktop +super + {_,shift + }n + bspc node -f {next,prev}.local.!hidden.window + +# focus the next/previous desktop in the current monitor +super + bracket{left,right} + bspc desktop -f {prev,next}.local + +# focus or send to the given desktop +super + {_,shift + }{1-9} + bspc {desktop -f,node -d} '{1-9}' + +# Switch places with the direction window or create an empty node +super + shift + {Left, Down, Up, Right} + bspwm_move {west,south,north,east} + +# Show or hide marked nodes +super + semicolon + bspc_marked_hidden + +#----~ Preselection ~----# + +# preselect the direction +super + ctrl + {Left, Down, Up, Right} + bspc node -p {west,south,north,east} + +# preselect the ratio +super + ctrl + {1-9} + bspc node -o 0.{1-9} + +# cancel the preselection for the focused node +super + ctrl + space + bspc node -p cancel + +# cancel the preselection for the focused desktop +super + ctrl + shift + space + bspc query -N -d | xargs -I id -n 1 bspc node id -p cancel + +#----~ Movement/Resizing ~----# + +# Move current window to a pre-selected space or an empty node +super + y + bspwm_fill + +# Resize +super + alt + {Left, Down, Up, Right} + bspc node -z {left -20 0 || bspc node -z right -20 0, \ + bottom 0 20 || bspc node -z top 0 20, \ + top 0 -20 || bspc node -z bottom 0 -20, \ + right 20 0 || bspc node -z left 20 0} + +#----~ BSPWM tabs using Suckless tabbetd ~----# + +# At given direction: join two windows into a new tabbed or add window to an existing tabbed +super + t; {Left,Down,Up,Right} + tabc.sh $(bspc query -N -n {west,south,north,east}) add $(bspc query -N -n focused) + +# Remove current tab from tabbed +super + t; r + tabc.sh $(bspc query -N -n focused) remove + + + +################################### +########### System Keys ########### +################################### + +#Volume Control +XF86AudioLowerVolume + pamixer -u && pamixer -d 5 +XF86AudioRaiseVolume + pamixer -u && pamixer -i 5 +XF86AudioMute + pamixer -t + +#Brightness control +XF86MonBrightnessDown + brightness -dec 5 +XF86MonBrightnessUp + brightness -inc 5 + +## Lcok screen +super + shift + l + lock.sh + +## Personal password manager +super + p + dpass + +## Music Controls +super + {less, greater, question} + playerctl {previous, next, play-pause} + +## Mic Toggle +super + apostrophe + micfn --toggle + +## Open Applications + +## Tdrop, a drop down terminal to act like a scratchpad. Check scripts + +# Spawn a term with tmux +super + shift + Return + st -g 90x24 -e "tmux" + +# General app shortcuts +super + o; {w, p, r} + {firefox, pavucontrol, tdrop_c -n 3 -p 'bspc rule -a St -o state=floating' st -e lf} + +# Ncmpcpp +super + alt + m + tdrop_c -n 2 -p 'bspc rule -a St -o state=floating' st -e ncmpcpp |
