From: Ian Kelling Date: Wed, 30 Jul 2025 04:19:08 +0000 (-0400) Subject: various improvements. move mpv into priv repo due to media specific stuff X-Git-Url: https://iankelling.org/git/?a=commitdiff_plain;h=3fb41ea446b4c1c1a82d93ae2e1569465d7c7929;p=distro-setup various improvements. move mpv into priv repo due to media specific stuff --- diff --git a/brc2 b/brc2 index df9e17e..a9c3a6d 100644 --- a/brc2 +++ b/brc2 @@ -2976,8 +2976,8 @@ myprof() { if [[ -s office_at_conference.fsf.org/$log_base ]]; then day_logs+=(office_at_conference.fsf.org/$log_base) fi - if [[ -s sys_at_conference.fsf.org/$log_base ]]; then - day_logs+=(sys_at_conference.fsf.org/$log_base) + if [[ -s sys-private_at_conference.fsf.org/$log_base ]]; then + day_logs+=(sys-private_at_conference.fsf.org/$log_base) fi if (( ${#day_logs[@]} == 0 )); then continue @@ -2999,6 +2999,7 @@ myprof() { profr() { source /p/c/domain-info if [[ $HOSTNAME == $d_host ]]; then + : # todo: fill this in? else ssh b8.nz profr-local fi @@ -4863,6 +4864,44 @@ mdread() { o $tmpf } +# Note: after swapping monitor, I had to run cmd to get improved refresh rate. I suspect this will be setup automatically on the next X restart: +# xrandr --output DP-0 --mode 3840x2160 --rate 120 + +# Note: to get started with ddcutil: +# +# ddcutil detect +# ddcutil --display 1 capabilities +mon-brightness() { + local percent + if [[ $1 ]]; then + percent="$1" + if [[ ! $percent =~ $uint_regex ]] || (( percent < 1 || percent > 100 )) ; then + e "$0: error: arg must be 1-100" + return 1 + fi + ddcutil --display 1 setvcp 10 $percent + else + e "$0: getting current brightness. To set brightness, add percentage arg of 1-100." + ddcutil --display 1 getvcp 10 + fi + + # note, for setting color preset, quote from capabilities: + + # Feature: 14 (Select color preset) + # Values: + # 01: sRGB + # 04: 5000 K + # 05: 6500 K + # 06: 7500 K + # 08: 9300 K + # 09: 10000 K + # 0b: User 1 + # 0c: User 2 + + # However, options 08+ do not work. Perhaps the user settings need setup first. +} + + export BASEFILE_DIR=/a/bin/fai-basefiles #export ANDROID_HOME=/a/opt/android-home diff --git a/distro-end b/distro-end index 037b66e..1bdec04 100755 --- a/distro-end +++ b/distro-end @@ -1699,6 +1699,9 @@ DEVICESCAN -a -o on -S on -n standby,q $sched \ ########### misc stuff +# group used by ddcutil +sudo usermod -a -G i2c iank + # according to # /a/c/machine_specific/frodo/.config/pipewire/pipewire.conf pipewire # uses RTKit to prioritize pipewire process by default, but if you are @@ -2171,6 +2174,7 @@ case $HOSTNAME in sudo /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh -y noble pi postgresql-17 sudo -u postgres createuser -d iank + sudo usermod -a -G postgres iank fi ;; esac diff --git a/filesystem/usr/local/bin/i3-event-hook b/filesystem/usr/local/bin/i3-event-hook index 87487c6..6a1dbb7 100755 --- a/filesystem/usr/local/bin/i3-event-hook +++ b/filesystem/usr/local/bin/i3-event-hook @@ -112,7 +112,8 @@ def focus_hook(i3, e): # print("pnodes: ", pnode.nodes) for node in pnode.nodes: - kill_single_win_containers(i3, e, node, pnode) + for node_l2 in node.nodes: + kill_single_win_containers(i3, e, node_l2, node) def main(): i3 = Connection() diff --git a/filesystem/usr/local/bin/mon-brightness-cycle b/filesystem/usr/local/bin/mon-brightness-cycle new file mode 100755 index 0000000..32ac218 --- /dev/null +++ b/filesystem/usr/local/bin/mon-brightness-cycle @@ -0,0 +1,45 @@ +#!/bin/bash +# I, Ian Kelling, follow the GNU license recommendations at +# https://www.gnu.org/licenses/license-recommendations.en.html. They +# recommend that small programs, < 300 lines, be licensed under the +# Apache License 2.0. This file contains or is part of one or more small +# programs. If a small program grows beyond 300 lines, I plan to change +# to a recommended GPL license. + +# Copyright 2025 Ian Kelling + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -e; . /usr/local/lib/bash-bear; set +e + +state_f=/tmp/user/1000/tmp_mon_bright.KROSZousbE + +low_level=70 + +set-bright() { + ddcutil --display 1 setvcp 10 $1 + echo $1 >$state_f +} + +if [[ -s $state_f ]]; then + cur_bright=$(cat $state_f) + # echo "state_f: $cur_bright" # debug +else + cur_bright=$(ddcutil -t --display 1 getvcp 10 | awk '{print $4}') +fi + +if [[ $cur_bright == "$low_level" ]]; then + set-bright 100 +else + set-bright $low_level +fi diff --git a/i3-sway/common.conf b/i3-sway/common.conf index 695c5cd..f1cb675 100644 --- a/i3-sway/common.conf +++ b/i3-sway/common.conf @@ -23,8 +23,7 @@ set $ex exec --no-startup-id bindsym $mod+2 $ex "i3-split-maybe"; exec "pavucontrol" # calling without --new-instance makes this to be the instance that links # will open in from other applications. -# unused. todo: consider binding this to some key on the right side of keyboard. -#bindsym $mod+3 $ex "i3-split-maybe"; exec "abrowser" +bindsym $mod+3 $ex "mon-brightness-cycle" # calling just abrowser mysteriously stopped working, # so I figured out this is how to get output, but then # it suddenly started working again. @@ -230,3 +229,5 @@ for_window [class="focus" instance="focus"] floating disable client.focused #4c7899 #285577 #ffffff #2e9ef4 #ff4400 client.focused_inactive #333333 #5f676a #ffffff #484e50 #B8C8CD client.unfocused #333333 #222222 #888888 #292d2e #B8C8CD + +assign [instance="mpv-i3-ws6"] 6 diff --git a/i3-sway/i3.conf b/i3-sway/i3.conf index a29f08f..631a966 100644 --- a/i3-sway/i3.conf +++ b/i3-sway/i3.conf @@ -4,11 +4,12 @@ bindsym $mod+Shift+o exec "i3-nagbar -t warning -m 'You pressed the exit shortcu bindsym $mod+Shift+i $ex /b/ds/i3-sway/gen bindsym $mod+Shift+p restart - +$ex /a/bin/ds/icc-load $ex copyq $ex dunst # haven't been using it enough to justify automatically running it.] #$ex /usr/lib/x86_64-linux-gnu/libexec/kdeconnectd -$ex /usr/local/bin/awatch + +#$ex /usr/local/bin/awatch # this dies when we restart i3. exec_always --no-startup-id i3-event-hook diff --git a/icc-load b/icc-load new file mode 100755 index 0000000..4c72051 --- /dev/null +++ b/icc-load @@ -0,0 +1,67 @@ +#!/bin/bash +# I, Ian Kelling, follow the GNU license recommendations at +# https://www.gnu.org/licenses/license-recommendations.en.html. They +# recommend that small programs, < 300 lines, be licensed under the +# Apache License 2.0. This file contains or is part of one or more small +# programs. If a small program grows beyond 300 lines, I plan to change +# to a recommended GPL license. + +# Copyright 2025 Ian Kelling + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +# I generally followed this guide: +# https://phototacopodcast.com/photographers-guide-to-screen-calibration-with-displaycal/ +# plus some displaycal forum searching. I ignored the part about +# brightness because I prefer to view images extra bright. +# +# This was also a useful intro: https://web.archive.org/web/20221226110150/https://encrypted.pcode.nl/blog/index.html%3Fp=853.html +# +# It wasn't clear to me whether I should use the builtin correction +# profile (as recommended on the forum), or the correction profile in +# the user database. Ultimately, I decided to the one in the user +# database, as theoretically, it is the result of a measurement with a +# Spectrometer, and is likely more accurate. However, I also know that I +# did measurements with brightness 100%, more than most people use, +# which could change things. +# +# When doing the initial monitor calibration, the builtin correction was +# best with 98 red, 100 gree, 98 blue. The user db one was roughly 97 +# red, 100 green, 97 blue. I stuck with the 98 one, and figured that way +# I'd be slightly more conservative and maybe keep 1% more brightness. +# +# U3225QE #1 2025-07-29 01-57 D6500 2.2 F-S XYZLUT+MTX.icc +# This uses the user-db correction, i1 DisplayPro, ColorMunki Display & Dell, DELL U3225QE (i1 Pro 2) by 4k-monitor.ccmx, +# and was taken with my typical ambiant light, including me sitting in front of the computer. +# +# 'U3225QE #1 2025-07-29 01-16 D6500 2.2 F-S XYZLUT+MTX.icc' +# This uses the builtin correction with my typical ambiant light, except I was not in front of the monitor. +# +# +# The installation process done by displaycal didn't work, but looking +# at the console output, I figured out a simple way to make it work (as +# below). Not sure if colord could have worked, but +# https://wiki.archlinux.org/title/ICC_profiles said it was less +# compatible, so I didn't bother. + +case $HOSTNAME in + frodo) + + /usr/bin/dispwin -v -d1 -c -I '/a/bin/ds/icc/U3225QE #1 2025-07-29 01-57 D6500 2.2 F-S XYZLUT+MTX.icc' + # test that it worked with: + # dispwin -v -d1 -V '/etc/xdgcolor/icc/devices/display/U3225QE #1 2025-07-29 01-57 D6500 2.2 F-S XYZLUT+MTX.icc' + # + # You can also watch the grey color of konsole change slightly when it loads. + ;; +esac diff --git a/icc/U3225QE #1 2025-07-28 23-51 D6500 2.2 F-S XYZLUT+MTX.icc b/icc/U3225QE #1 2025-07-28 23-51 D6500 2.2 F-S XYZLUT+MTX.icc new file mode 100644 index 0000000..e68d7fa Binary files /dev/null and b/icc/U3225QE #1 2025-07-28 23-51 D6500 2.2 F-S XYZLUT+MTX.icc differ diff --git a/icc/U3225QE #1 2025-07-29 01-16 D6500 2.2 F-S XYZLUT+MTX.icc b/icc/U3225QE #1 2025-07-29 01-16 D6500 2.2 F-S XYZLUT+MTX.icc new file mode 100644 index 0000000..b4a2909 Binary files /dev/null and b/icc/U3225QE #1 2025-07-29 01-16 D6500 2.2 F-S XYZLUT+MTX.icc differ diff --git a/icc/U3225QE #1 2025-07-29 01-57 D6500 2.2 F-S XYZLUT+MTX.icc b/icc/U3225QE #1 2025-07-29 01-57 D6500 2.2 F-S XYZLUT+MTX.icc new file mode 100644 index 0000000..ed763c4 Binary files /dev/null and b/icc/U3225QE #1 2025-07-29 01-57 D6500 2.2 F-S XYZLUT+MTX.icc differ diff --git a/machine_specific/frodo/filesystem/etc/systemd/system/btrbkr.service b/machine_specific/frodo/filesystem/etc/systemd/system/btrbkr.service index 8938e9c..234a624 100644 --- a/machine_specific/frodo/filesystem/etc/systemd/system/btrbkr.service +++ b/machine_specific/frodo/filesystem/etc/systemd/system/btrbkr.service @@ -4,4 +4,4 @@ After=multi-user.target [Service] Type=oneshot -ExecStart=/usr/local/bin/sysd-mail-once btrbkrust btrbk -c /etc/btrbk/r.conf run +ExecStart=/usr/local/bin/sysd-mail-once btrbkr btrbk -c /etc/btrbk/r.conf run diff --git a/machine_specific/frodo/subdir_files/.config/pipewire/pipewire-pulse.conf b/machine_specific/frodo/subdir_files/.config/pipewire/pipewire-pulse.conf deleted file mode 100644 index 77110b9..0000000 --- a/machine_specific/frodo/subdir_files/.config/pipewire/pipewire-pulse.conf +++ /dev/null @@ -1,173 +0,0 @@ -# PulseAudio config file for PipeWire version "1.0.5" # -# -# Copy and edit this file in /etc/pipewire for system-wide changes -# or in ~/.config/pipewire for local changes. -# -# It is also possible to place a file with an updated section in -# /etc/pipewire/pipewire-pulse.conf.d/ for system-wide changes or in -# ~/.config/pipewire/pipewire-pulse.conf.d/ for local changes. -# - -context.properties = { - ## Configure properties in the system. - #mem.warn-mlock = false - #mem.allow-mlock = true - #mem.mlock-all = false - #log.level = 2 - - #default.clock.quantum-limit = 8192 -} - -context.spa-libs = { - audio.convert.* = audioconvert/libspa-audioconvert - support.* = support/libspa-support -} - -context.modules = [ - { name = libpipewire-module-rt - args = { - nice.level = -11 - #rt.prio = 83 - #rt.time.soft = -1 - #rt.time.hard = -1 - #uclamp.min = 0 - #uclamp.max = 1024 - } - flags = [ ifexists nofail ] - } - { name = libpipewire-module-protocol-native } - { name = libpipewire-module-client-node } - { name = libpipewire-module-adapter } - { name = libpipewire-module-metadata } - - { name = libpipewire-module-protocol-pulse - args = { - # contents of pulse.properties can also be placed here - # to have config per server. - } - } -] - -# Extra scripts can be started here. Setup in default.pa can be moved in -# a script or in pulse.cmd below -context.exec = [ - #{ path = "pactl" args = "load-module module-always-sink" } - #{ path = "pactl" args = "upload-sample my-sample.wav my-sample" } - #{ path = "/usr/bin/sh" args = "~/.config/pipewire/default.pw" } -] - -# Extra commands can be executed here. -# load-module : loads a module with args and flags -# args = " " -# ( flags = [ nofail ] ) -pulse.cmd = [ - { cmd = "load-module" args = "module-always-sink" flags = [ ] } - #{ cmd = "load-module" args = "module-switch-on-connect" } - #{ cmd = "load-module" args = "module-gsettings" flags = [ nofail ] } -] - -stream.properties = { - #node.latency = 1024/48000 - #node.autoconnect = true - #resample.quality = 4 - #channelmix.normalize = false - #channelmix.mix-lfe = true - #channelmix.upmix = true - #channelmix.upmix-method = psd # none, simple - #channelmix.lfe-cutoff = 150 - #channelmix.fc-cutoff = 12000 - #channelmix.rear-delay = 12.0 - #channelmix.stereo-widen = 0.0 - #channelmix.hilbert-taps = 0 - #dither.noise = 0 -} - -pulse.properties = { - # the addresses this server listens on - server.address = [ - "unix:native" - #"unix:/tmp/something" # absolute paths may be used - #"tcp:4713" # IPv4 and IPv6 on all addresses - #"tcp:[::]:9999" # IPv6 on all addresses - #"tcp:127.0.0.1:8888" # IPv4 on a single address - # - #{ address = "tcp:4713" # address - # max-clients = 64 # maximum number of clients - # listen-backlog = 32 # backlog in the server listen queue - # client.access = "restricted" # permissions for clients - #} - ] - #server.dbus-name = "org.pulseaudio.Server" - #pulse.min.req = 128/48000 # 2.7ms - #pulse.default.req = 960/48000 # 20 milliseconds - #pulse.min.frag = 128/48000 # 2.7ms - #pulse.default.frag = 96000/48000 # 2 seconds - #pulse.default.tlength = 96000/48000 # 2 seconds - # iank. ran out of time to test this to prevent io stuttering - # I set this in the pipewire config too. I don't understand quite how they interact. - #pulse.min.quantum = 1024/44100 - - #pulse.min.quantum = 128/48000 # 2.7ms - #pulse.idle.timeout = 0 # don't pause after underruns - #pulse.default.format = F32 - #pulse.default.position = [ FL FR ] - # These overrides are only applied when running in a vm. - vm.overrides = { - pulse.min.quantum = 1024/48000 # 22ms - } -} - -# client/stream specific properties -pulse.rules = [ - { - matches = [ - { - # all keys must match the value. ! negates. ~ starts regex. - #client.name = "Firefox" - #application.process.binary = "teams" - #application.name = "~speech-dispatcher.*" - } - ] - actions = { - update-props = { - #node.latency = 512/48000 - } - # Possible quirks:" - # force-s16-info forces sink and source info as S16 format - # remove-capture-dont-move removes the capture DONT_MOVE flag - # block-source-volume blocks updates to source volume - # block-sink-volume blocks updates to sink volume - #quirks = [ ] - } - } - { - # skype does not want to use devices that don't have an S16 sample format. - matches = [ - { application.process.binary = "teams" } - { application.process.binary = "teams-insiders" } - { application.process.binary = "skypeforlinux" } - ] - actions = { quirks = [ force-s16-info ] } - } - { - # firefox marks the capture streams as don't move and then they - # can't be moved with pavucontrol or other tools. - matches = [ { application.process.binary = "firefox" } ] - actions = { quirks = [ remove-capture-dont-move ] } - } - { - # speech dispatcher asks for too small latency and then underruns. - matches = [ { application.name = "~speech-dispatcher.*" } ] - actions = { - update-props = { - pulse.min.req = 512/48000 # 10.6ms - pulse.min.quantum = 512/48000 # 10.6ms - pulse.idle.timeout = 5 # pause after 5 seconds of underrun - } - } - } - #{ - # matches = [ { application.process.binary = "Discord" } ] - # actions = { quirks = [ block-source-volume ] } - #} -] diff --git a/machine_specific/frodo/subdir_files/.config/pipewire/pipewire.conf b/machine_specific/frodo/subdir_files/.config/pipewire/pipewire.conf index c66bc2b..2ef7e85 100644 --- a/machine_specific/frodo/subdir_files/.config/pipewire/pipewire.conf +++ b/machine_specific/frodo/subdir_files/.config/pipewire/pipewire.conf @@ -9,24 +9,6 @@ # context.properties = { - ## Configure properties in the system. - #library.name.system = support/libspa-support - #context.data-loop.library.name.system = support/libspa-support - #support.dbus = true - #link.max-buffers = 64 - link.max-buffers = 16 # version < 3 clients can't handle more - #mem.warn-mlock = false - #mem.allow-mlock = true - #mem.mlock-all = false - #clock.power-of-two-quantum = true - #log.level = 2 - #cpu.zero.denormals = false - - core.daemon = true # listening for socket connections - core.name = pipewire-0 # core name and socket name - - ## Properties for the DSP configuration. - #default.clock.rate = 48000 # iank: adding this made us output at the same sample rate as the audio we were playing, which seems like a good thing. from arch wiki: "PipeWire can also change dynamically the output sample rates supported by your DAC", find them with: # grep -E 'Codec|Audio Output|rates' /proc/asound/card*/codec#* @@ -41,293 +23,12 @@ context.properties = { #default.clock.quantum = 1024 # iank. ran out of time to test this to prevent io stuttering #default.clock.min-quantum = 1024 - - #default.clock.min-quantum = 32 - #default.clock.max-quantum = 2048 - #default.clock.quantum-limit = 8192 - #default.clock.quantum-floor = 4 - #default.video.width = 640 - #default.video.height = 480 - #default.video.rate.num = 25 - #default.video.rate.denom = 1 - # - #settings.check-quantum = false - #settings.check-rate = false - # - # These overrides are only applied when running in a vm. - vm.overrides = { - default.clock.min-quantum = 1024 - } - - # keys checked below to disable module loading - module.x11.bell = true - # enables autoloading of access module, when disabled an alternative - # access module needs to be loaded. - module.access = true - # enables autoloading of module-jackdbus-detect - module.jackdbus-detect = true -} - -context.spa-libs = { - # = - # - # Used to find spa factory names. It maps an spa factory name - # regular expression to a library name that should contain - # that factory. - # - audio.convert.* = audioconvert/libspa-audioconvert - avb.* = avb/libspa-avb - api.alsa.* = alsa/libspa-alsa - api.v4l2.* = v4l2/libspa-v4l2 - api.libcamera.* = libcamera/libspa-libcamera - api.bluez5.* = bluez5/libspa-bluez5 - api.vulkan.* = vulkan/libspa-vulkan - api.jack.* = jack/libspa-jack - support.* = support/libspa-support - #videotestsrc = videotestsrc/libspa-videotestsrc - #audiotestsrc = audiotestsrc/libspa-audiotestsrc } -context.modules = [ - #{ name = - # ( args = { = ... } ) - # ( flags = [ ( ifexists ) ( nofail ) ] ) - # ( condition = [ { = ... } ... ] ) - #} - # - # Loads a module with the given parameters. - # If ifexists is given, the module is ignored when it is not found. - # If nofail is given, module initialization failures are ignored. - # If condition is given, the module is loaded only when the context - # properties all match the match rules. - # - - # Uses realtime scheduling to boost the audio thread priorities. This uses - # RTKit if the user doesn't have permission to use regular realtime - # scheduling. You can also clamp utilisation values to improve scheduling - # on embedded and heterogeneous systems, e.g. Arm big.LITTLE devices. - { name = libpipewire-module-rt - args = { - nice.level = -11 - rt.prio = 88 - #rt.time.soft = -1 - #rt.time.hard = -1 - #uclamp.min = 0 - #uclamp.max = 1024 - } - flags = [ ifexists nofail ] - } - - # The native communication protocol. - { name = libpipewire-module-protocol-native - args = { - # List of server Unix sockets, and optionally permissions - #sockets = [ { name = "pipewire-0" }, { name = "pipewire-0-manager" } ] - } - } - - # The profile module. Allows application to access profiler - # and performance data. It provides an interface that is used - # by pw-top and pw-profiler. - { name = libpipewire-module-profiler } - - # Allows applications to create metadata objects. It creates - # a factory for Metadata objects. - { name = libpipewire-module-metadata } - - # Creates a factory for making devices that run in the - # context of the PipeWire server. - { name = libpipewire-module-spa-device-factory } - - # Creates a factory for making nodes that run in the - # context of the PipeWire server. - { name = libpipewire-module-spa-node-factory } - - # Allows creating nodes that run in the context of the - # client. Is used by all clients that want to provide - # data to PipeWire. - { name = libpipewire-module-client-node } - - # Allows creating devices that run in the context of the - # client. Is used by the session manager. - { name = libpipewire-module-client-device } - - # The portal module monitors the PID of the portal process - # and tags connections with the same PID as portal - # connections. - { name = libpipewire-module-portal - flags = [ ifexists nofail ] - } - - # The access module can perform access checks and block - # new clients. - { name = libpipewire-module-access - args = { - # Socket-specific access permissions - #access.socket = { pipewire-0 = "default", pipewire-0-manager = "unrestricted" } - - # Deprecated legacy mode (not socket-based), - # for now enabled by default if access.socket is not specified - #access.legacy = true - } - condition = [ { module.access = true } ] - } - - # Makes a factory for wrapping nodes in an adapter with a - # converter and resampler. - { name = libpipewire-module-adapter } - - # Makes a factory for creating links between ports. - { name = libpipewire-module-link-factory } - - # Provides factories to make session manager objects. - { name = libpipewire-module-session-manager } - - # Use libcanberra to play X11 Bell - { name = libpipewire-module-x11-bell - args = { - #sink.name = "@DEFAULT_SINK@" - #sample.name = "bell-window-system" - #x11.display = null - #x11.xauthority = null - } - flags = [ ifexists nofail ] - condition = [ { module.x11.bell = true } ] - } - { name = libpipewire-module-jackdbus-detect - args = { - #jack.library = libjack.so.0 - #jack.server = null - #jack.client-name = PipeWire - #jack.connect = true - #tunnel.mode = duplex # source|sink|duplex - source.props = { - #audio.channels = 2 - #midi.ports = 1 - #audio.position = [ FL FR ] - # extra sink properties - } - sink.props = { - #audio.channels = 2 - #midi.ports = 1 - #audio.position = [ FL FR ] - # extra sink properties - } - } - flags = [ ifexists nofail ] - condition = [ { module.jackdbus-detect = true } ] - } -] - -context.objects = [ - #{ factory = - # ( args = { = ... } ) - # ( flags = [ ( nofail ) ] ) - # ( condition = [ { = ... } ... ] ) - #} - # - # Creates an object from a PipeWire factory with the given parameters. - # If nofail is given, errors are ignored (and no object is created). - # If condition is given, the object is created only when the context properties - # all match the match rules. - # - #{ factory = spa-node-factory args = { factory.name = videotestsrc node.name = videotestsrc node.description = videotestsrc "Spa:Pod:Object:Param:Props:patternType" = 1 } } - #{ factory = spa-device-factory args = { factory.name = api.jack.device foo=bar } flags = [ nofail ] } - #{ factory = spa-device-factory args = { factory.name = api.alsa.enum.udev } } - #{ factory = spa-node-factory args = { factory.name = api.alsa.seq.bridge node.name = Internal-MIDI-Bridge } } - #{ factory = adapter args = { factory.name = audiotestsrc node.name = my-test node.description = audiotestsrc } } - #{ factory = spa-node-factory args = { factory.name = api.vulkan.compute.source node.name = my-compute-source } } - - # A default dummy driver. This handles nodes marked with the "node.always-process" - # property when no other driver is currently active. JACK clients need this. - { factory = spa-node-factory - args = { - factory.name = support.node.driver - node.name = Dummy-Driver - node.group = pipewire.dummy - priority.driver = 20000 - #clock.id = monotonic # realtime | tai | monotonic-raw | boottime - #clock.name = "clock.system.monotonic" - } - } - { factory = spa-node-factory - args = { - factory.name = support.node.driver - node.name = Freewheel-Driver - priority.driver = 19000 - node.group = pipewire.freewheel - node.freewheel = true - } - } - - # This creates a new Source node. It will have input ports - # that you can link, to provide audio for this source. - #{ factory = adapter - # args = { - # factory.name = support.null-audio-sink - # node.name = "my-mic" - # node.description = "Microphone" - # media.class = "Audio/Source/Virtual" - # audio.position = "FL,FR" - # monitor.passthrough = true - # } - #} - - # This creates a single PCM source device for the given - # alsa device path hw:0. You can change source to sink - # to make a sink in the same way. - #{ factory = adapter - # args = { - # factory.name = api.alsa.pcm.source - # node.name = "alsa-source" - # node.description = "PCM Source" - # media.class = "Audio/Source" - # api.alsa.path = "hw:0" - # api.alsa.period-size = 1024 - # api.alsa.headroom = 0 - # api.alsa.disable-mmap = false - # api.alsa.disable-batch = false - # audio.format = "S16LE" - # audio.rate = 48000 - # audio.channels = 2 - # audio.position = "FL,FR" - # } - #} - - # Use the metadata factory to create metadata and some default values. - #{ factory = metadata - # args = { - # metadata.name = my-metadata - # metadata.values = [ - # { key = default.audio.sink value = { name = somesink } } - # { key = default.audio.source value = { name = somesource } } - # ] - # } - #} -] - -context.exec = [ - #{ path = - # ( args = "" ) - # ( condition = [ { = ... } ... ] ) - #} - # - # Execute the given program with arguments. - # If condition is given, the program is executed only when the context - # properties all match the match rules. - # - # You can optionally start the session manager here, - # but it is better to start it as a systemd service. - # Run the session manager with -h for options. - # - #{ path = "/usr/bin/pipewire-media-session" args = "" - # condition = [ { exec.session-manager = null } { exec.session-manager = true } ] } - # - # You can optionally start the pulseaudio-server here as well - # but it is better to start it as a systemd service. - # It can be interesting to start another daemon here that listens - # on another address with the -a option (eg. -a tcp:4713). - # - #{ path = "/usr/bin/pipewire" args = "-c pipewire-pulse.conf" - # condition = [ { exec.pipewire-pulse = null } { exec.pipewire-pulse = true } ] } -] +# found a rancom config suggestion of a file which is just: +# context.properties = { +# default.clock.rate = 48000 +# default.clock.quantum = 1024 +# default.clock.min-quantum = 32 +# default.clock.max-quantum = 2048 +# } diff --git a/pkgs b/pkgs index 782b00f..e6a1821 100644 --- a/pkgs +++ b/pkgs @@ -124,12 +124,14 @@ p3=( debootstrap debconf-doc devscripts + ddcutil dillo # using self-compiled version #digikam # used by digikam for icons breeze-icon-theme dirmngr + displaycal dos2unix dosfstools dnsutils @@ -212,6 +214,7 @@ p3=( info inotify-tools ipcalc + iputils-arping iputils-tracepath iperf3 iproute2-doc @@ -221,11 +224,12 @@ p3=( kid3-cli konsole knot-dnsutils + # for skdump + libatasmart-bin libterm-readkey-perl libreoffice libreoffice-help-en-us linphone-desktop - linux-doc lshw make-doc manpages @@ -307,6 +311,8 @@ p3=( software-properties-common speex sox + # used by emacs package sqlformat with the same name. + sqlfluff sqlite3-doc squashfs-tools strace diff --git a/primary-setup b/primary-setup index 5df4149..c5f528a 100755 --- a/primary-setup +++ b/primary-setup @@ -80,6 +80,9 @@ fi if [[ $HOSTNAME == "$MAIL_HOST" || $HOSTNAME == kd ]]; then m systemctl --now enable btrbk.timer else + # ssh might have been disabled by mailtest-check. + m systemctl --now enable ssh.socket + m systemctl --no-block start ssh serm systemctl --now disable btrbk.timer fi diff --git a/subdir_files/.config/mpv/input.conf b/subdir_files/.config/mpv/input.conf deleted file mode 100644 index 9ce157d..0000000 --- a/subdir_files/.config/mpv/input.conf +++ /dev/null @@ -1,13 +0,0 @@ -a cycle_values video-rotate "90" "180" "270" "0" -Alt+h add video-zoom 0.25 -Alt+g add video-zoom -0.25 - -# Skip to the next file -b playlist-next - -# Skip to the previous file -c playlist-prev - -# default is 5 seconds and non-exact, often much longer jump. -RIGHT seek 3 exact -LEFT seek -4 exact diff --git a/subdir_files/.config/mpv/mpv.conf b/subdir_files/.config/mpv/mpv.conf deleted file mode 100644 index fdb32f3..0000000 --- a/subdir_files/.config/mpv/mpv.conf +++ /dev/null @@ -1,76 +0,0 @@ -#loop=inf -#pause -#save-position-on-quit -#loop-file=inf -loop-file=no -volume=50 -player-operation-mode=pseudo-gui -replaygain=track - -# by default mpv does not hardware decode, but the distro package has -# /etc/mpv/mpv.conf : hwdec=vaapi that makes android videos black and -# white on one of my computers. vlc uses this library, which works on -# that computer, so going with that for now. This config file overrides -# the /etc one. -hwdec=vdpau - -# like [l] but shuffled -[k] -loop-file=inf -save-position-on-quit -resume-playback -shuffle - -# loop. saves playlist position. good for looking at gifs -[l] -loop-file=inf -save-position-on-quit -resume-playback -#no-resume-playback -#no-save-position-on-quit - - - -# use --profile d -[d] -loop-file=inf -shuffle -#vo=gpu -no-resume-playback -no-save-position-on-quit - -[s] -shuffle - -# audio, especially with beetag -[a] -volume=100 -player-operation-mode=cplayer -audio-display=no -# dont display any tags -display-tags= -# this gets rid of lines like: -# [ipc_398] Write error (Broken pipe) -# -# which I suspect are happening because we do 2 socats too fast. But it -# never seems to cause any other problem, so whatever, just hide it. -really-quiet - -# note, useful cli option: -# --script-opts=osc-visibility=always -# -# gets rid of lines like: -# (+) Audio --aid=1 (flac 2ch 44100Hz) -# AO: [pulse] 44100Hz stereo 2ch s16 -# and -# ffmpeg/demuxer] mp3: Estimating duration from bitrate, this may be inaccurate -# -# Found via man mpv and its suggestion to use -# --msg-level=all=trace -# -# for supressing -# "Audio device underrun detected." it requires all=error. - -msg-level=cplayer=warn,ffmpeg/demuxer=error - -input-ipc-server=/tmp/mpvsock diff --git a/subdir_files/.config/mpv/scripts/playlistmanager.lua b/subdir_files/.config/mpv/scripts/playlistmanager.lua deleted file mode 100644 index 3a3027c..0000000 --- a/subdir_files/.config/mpv/scripts/playlistmanager.lua +++ /dev/null @@ -1,1706 +0,0 @@ --- iank wget https://raw.githubusercontent.com/jonniek/mpv-playlistmanager/16e18949e3d604c2ffe43e95391f420227881139/playlistmanager.lua -local settings = { - --navigation keybindings force override only while playlist is visible - --if "no" then you can display the playlist by any of the navigation keys - dynamic_binds = true, - - -- to bind multiple keys separate them by a space - - -- main key to show playlist - key_showplaylist = "SHIFT+ENTER", - - -- display playlist while key is held down - key_peek_at_playlist = "", - - -- dynamic keys - key_moveup = "UP", - key_movedown = "DOWN", - key_movepageup = "PGUP", - key_movepagedown = "PGDWN", - key_movebegin = "HOME", - key_moveend = "END", - key_selectfile = "RIGHT LEFT", - key_unselectfile = "", - key_playfile = "ENTER", - key_removefile = "BS", - key_closeplaylist = "ESC SHIFT+ENTER", - - -- extra functionality keys - key_sortplaylist = "", - key_shuffleplaylist = "r", - key_reverseplaylist = "", - key_loadfiles = "", - key_saveplaylist = "", - - --replaces matches on filenames based on extension, put as empty string to not replace anything - --replace rules are executed in provided order - --replace rule key is the pattern and value is the replace value - --uses :gsub('pattern', 'replace'), read more http://lua-users.org/wiki/StringLibraryTutorial - --'all' will match any extension or protocol if it has one - --uses json and parses it into a lua table to be able to support .conf file - - filename_replace = [[ - [ - { - "protocol": { "all": true }, - "rules": [ - { "%%(%x%x)": "hex_to_char" } - ] - } - ] - ]], - ---[=====[ START OF SAMPLE REPLACE - Remove this line to use it - --Sample replace: replaces underscore to space on all files - --for mp4 and webm; remove extension, remove brackets and surrounding whitespace, change dot between alphanumeric to space - filename_replace = [[ - [ - { - "ext": { "all": true}, - "rules": [ - { "_" : " " } - ] - },{ - "ext": { "mp4": true, "mkv": true }, - "rules": [ - { "^(.+)%..+$": "%1" }, - { "%s*[%[%(].-[%]%)]%s*": "" }, - { "(%w)%.(%w)": "%1 %2" } - ] - },{ - "protocol": { "http": true, "https": true }, - "rules": [ - { "^%a+://w*%.?": "" } - ] - } - ] - ]], ---END OF SAMPLE REPLACE ]=====] - - --json array of filetypes to search from directory - loadfiles_filetypes = [[ - [ - "jpg", "jpeg", "png", "tif", "tiff", "gif", "webp", "svg", "bmp", - "mp3", "wav", "ogm", "flac", "m4a", "wma", "ogg", "opus", - "mkv", "avi", "mp4", "ogv", "webm", "rmvb", "flv", "wmv", "mpeg", "mpg", "m4v", "3gp" - ] - ]], - - --loadfiles at startup if 1 or more items in playlist - loadfiles_on_start = false, - -- loadfiles from working directory on idle startup - loadfiles_on_idle_start = false, - --always put loaded files after currently playing file - loadfiles_always_append = false, - - --sort playlist when files are added to playlist - sortplaylist_on_file_add = false, - - --default sorting method, must be one of: "name-asc", "name-desc", "date-asc", "date-desc", "size-asc", "size-desc". - default_sort = "name-asc", - - --"linux | windows | auto" - system = "auto", - - --Use ~ for home directory. Leave as empty to use mpv/playlists - playlist_savepath = "", - - -- constant filename to save playlist as. Note that it will override existing playlist. Leave empty for generated name. - playlist_save_filename = "", - - --save playlist automatically after current file was unloaded - save_playlist_on_file_end = false, - - - --show file title every time a new file is loaded - show_title_on_file_load = false, - --show playlist every time a new file is loaded - show_playlist_on_file_load = false, - --close playlist when selecting file to play - close_playlist_on_playfile = false, - - --sync cursor when file is loaded from outside reasons(file-ending, playlist-next shortcut etc.) - --has the sideeffect of moving cursor if file happens to change when navigating - --good side is cursor always following current file when going back and forth files with playlist-next/prev - sync_cursor_on_load = true, - - --allow the playlist cursor to loop from end to start and vice versa - loop_cursor = true, - - - -- allow playlistmanager to write watch later config when navigating between files - allow_write_watch_later_config = true, - - -- reset cursor navigation when closing or opening playlist - reset_cursor_on_close = true, - reset_cursor_on_open = true, - - --prefer to display titles for following files: "all", "url", "none". Sorting still uses filename. - prefer_titles = "url", - - --youtube-dl executable for title resolving if enabled, probably "youtube-dl" or "yt-dlp", can be absolute path - youtube_dl_executable = "yt-dlp", - - --call youtube-dl to resolve the titles of urls in the playlist - resolve_url_titles = false, - - --call ffprobe to resolve the titles of local files in the playlist (if they exist in the metadata) - resolve_local_titles = false, - - -- timeout in seconds for url title resolving - resolve_title_timeout = 15, - - -- how many url titles can be resolved at a time. Higher number might lead to stutters. - concurrent_title_resolve_limit = 10, - - --osd timeout on inactivity in seconds, use 0 for no timeout - playlist_display_timeout = 0, - - -- when peeking at playlist, show playlist at the very least for display timeout - peek_respect_display_timeout = false, - - -- the maximum amount of lines playlist will render. -1 will automatically calculate lines. - showamount = -1, - - --playlist ass style overrides inside curly brackets, \keyvalue is one field, extra \ for escape in lua - --example {\\q2\\an7\\fnUbuntu\\fs10\\b0\\bord1} equals: line-wrap=no, align=top left, 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 - --\\q2 style is recommended since filename wrapping may lead to unexpected rendering - --\\an7 style is recommended to align to top left otherwise, osd-align-x/y is respected - style_ass_tags = "{\\q2\\an7}", - --paddings for left right and top bottom - text_padding_x = 30, - text_padding_y = 60, - - --screen dim when menu is open 0.0 - 1.0 (0 is no dim, 1 is black) - curtain_opacity=0.0, - - --set title of window with stripped name - set_title_stripped = false, - title_prefix = "", - title_suffix = " - mpv", - - --slice long filenames, and how many chars to show - slice_longfilenames = false, - slice_longfilenames_amount = 70, - - --Playlist header template - --%mediatitle or %filename = title or name of playing file - --%pos = position of playing file - --%cursor = position of navigation - --%plen = playlist length - --%N = newline - playlist_header = "[%cursor/%plen]", - - --Playlist file templates - --%pos = position of file with leading zeros - --%name = title or name of file - --%N = newline - --you can also use the ass tags mentioned above. For example: - -- selected_file="{\\c&HFF00FF&}➔ %name" | to add a color for selected file. However, if you - -- use ass tags you need to reset them for every line (see https://github.com/jonniek/mpv-playlistmanager/issues/20) - normal_file = "○ %name", - hovered_file = "● %name", - selected_file = "➔ %name", - playing_file = "▷ %name", - playing_hovered_file = "▶ %name", - playing_selected_file = "➤ %name", - - - -- what to show when playlist is truncated - playlist_sliced_prefix = "...", - playlist_sliced_suffix = "...", - - --output visual feedback to OSD for tasks - display_osd_feedback = true, -} -local opts = require("mp.options") -opts.read_options(settings, "playlistmanager", function(list) update_opts(list) end) - -local utils = require("mp.utils") -local msg = require("mp.msg") -local assdraw = require("mp.assdraw") - -local alignment_table = { - [1] = { ["x"] = "left", ["y"] = "bottom" }, - [2] = { ["x"] = "center", ["y"] = "bottom" }, - [3] = { ["x"] = "right", ["y"] = "bottom" }, - [4] = { ["x"] = "left", ["y"] = "center" }, - [5] = { ["x"] = "center", ["y"] = "center" }, - [6] = { ["x"] = "right", ["y"] = "center" }, - [7] = { ["x"] = "left", ["y"] = "top" }, - [8] = { ["x"] = "center", ["y"] = "top" }, - [9] = { ["x"] = "right", ["y"] = "top" }, -} - ---check os -if settings.system=="auto" then - local o = {} - if mp.get_property_native('options/vo-mmcss-profile', o) ~= o then - settings.system = "windows" - else - settings.system = "linux" - end -end - --- auto calculate showamount -if settings.showamount == -1 then - -- same as draw_playlist() height - local h = 720 - - local playlist_h = h - -- both top and bottom with same padding - playlist_h = playlist_h - settings.text_padding_y * 2 - - -- osd-font-size is based on 720p height - -- see https://mpv.io/manual/stable/#options-osd-font-size - -- details in https://mpv.io/manual/stable/#options-sub-font-size - -- draw_playlist() is based on 720p, need some conversion - local fs = mp.get_property_native('osd-font-size') * h / 720 - -- get the ass font size - if settings.style_ass_tags ~= nil then - local ass_fs_tag = settings.style_ass_tags:match('\\fs%d+') - if ass_fs_tag ~= nil then - fs = tonumber(ass_fs_tag:match('%d+')) - end - end - - settings.showamount = math.floor(playlist_h / fs) - - -- exclude the header line - if settings.playlist_header ~= "" then - settings.showamount = settings.showamount - 1 - -- probably some newlines (%N or \N) in the header - for _ in settings.playlist_header:gmatch('%%N') do - settings.showamount = settings.showamount - 1 - end - for _ in settings.playlist_header:gmatch('\\N') do - settings.showamount = settings.showamount - 1 - end - end - - msg.info('auto showamount: ' .. settings.showamount) -end - ---global variables -local playlist_overlay = mp.create_osd_overlay("ass-events") -local playlist_visible = false -local strippedname = nil -local path = nil -local directory = nil -local filename = nil -local pos = 0 -local plen = 0 -local cursor = 0 ---table for saved media titles for later if we prefer them -local title_table = {} --- table for urls and local file paths that we have requested to be resolved to titles -local requested_titles = {} - -local filetype_lookup = {} - -function refresh_UI() - if not playlist_visible then return end - refresh_globals() - if plen == 0 then return end - draw_playlist() -end - -function update_opts(changelog) - msg.verbose('updating options') - - --parse filename json - if changelog.filename_replace then - if(settings.filename_replace~="") then - settings.filename_replace = utils.parse_json(settings.filename_replace) - else - settings.filename_replace = false - end - end - - --parse loadfiles json - if changelog.loadfiles_filetypes then - settings.loadfiles_filetypes = utils.parse_json(settings.loadfiles_filetypes) - - filetype_lookup = {} - --create loadfiles set - for _, ext in ipairs(settings.loadfiles_filetypes) do - filetype_lookup[ext] = true - end - end - - if changelog.resolve_url_titles then - resolve_titles() - end - - if changelog.resolve_local_titles then - resolve_titles() - end - - if changelog.playlist_display_timeout then - keybindstimer = mp.add_periodic_timer(settings.playlist_display_timeout, remove_keybinds) - keybindstimer:kill() - end - - refresh_UI() -end - -update_opts({filename_replace = true, loadfiles_filetypes = true}) - ------ winapi start ----- --- in windows system, we can use the sorting function provided by the win32 API --- see https://learn.microsoft.com/en-us/windows/win32/api/shlwapi/nf-shlwapi-strcmplogicalw -local winapisort = nil -if settings.system == "windows" then - -- ffiok is false usually means the mpv builds without luajit - local ffiok, ffi = pcall(require, "ffi") - if ffiok then - ffi.cdef[[ - int MultiByteToWideChar(unsigned int CodePage, unsigned long dwFlags, const char *lpMultiByteStr, int cbMultiByte, wchar_t *lpWideCharStr, int cchWideChar); - int StrCmpLogicalW(const wchar_t * psz1, const wchar_t * psz2); - ]] - - local shlwapi = ffi.load("shlwapi.dll") - - function MultiByteToWideChar(MultiByteStr) - local UTF8_CODEPAGE = 65001 - if MultiByteStr then - local utf16_len = ffi.C.MultiByteToWideChar(UTF8_CODEPAGE, 0, MultiByteStr, -1, nil, 0) - if utf16_len > 0 then - local utf16_str = ffi.new("wchar_t[?]", utf16_len) - if ffi.C.MultiByteToWideChar(UTF8_CODEPAGE, 0, MultiByteStr, -1, utf16_str, utf16_len) > 0 then - return utf16_str - end - end - end - return "" - end - - winapisort = function (a, b) - return shlwapi.StrCmpLogicalW(MultiByteToWideChar(a), MultiByteToWideChar(b)) < 0 - end - - end -end ------ winapi end ----- - -local sort_modes = { - { - id="name-asc", - title="name ascending", - sort_fn=function (a, b, playlist) - if winapisort ~= nil then - return winapisort(playlist[a].string, playlist[b].string) - end - return alphanumsort(playlist[a].string, playlist[b].string) - end, - }, - { - id="name-desc", - title="name descending", - sort_fn=function (a, b, playlist) - if winapisort ~= nil then - return winapisort(playlist[b].string, playlist[a].string) - end - return alphanumsort(playlist[b].string, playlist[a].string) - end, - }, - { - id="date-asc", - title="date ascending", - sort_fn=function (a, b) - return (get_file_info(a).mtime or 0) < (get_file_info(b).mtime or 0) - end, - }, - { - id="date-desc", - title="date descending", - sort_fn=function (a, b) - return (get_file_info(a).mtime or 0) > (get_file_info(b).mtime or 0) - end, - }, - { - id="size-asc", - title="size ascending", - sort_fn=function (a, b) - return (get_file_info(a).size or 0) < (get_file_info(b).size or 0) - end, - }, - { - id="size-desc", - title="size descending", - sort_fn=function (a, b) - return (get_file_info(a).size or 0) > (get_file_info(b).size or 0) - end, - }, -} - -local sort_mode = 1 -for mode, sort_data in pairs(sort_modes) do - if sort_data.id == settings.default_sort then - sort_mode = mode - end -end - -function is_protocol(path) - return type(path) == 'string' and path:match('^%a[%a%d-_]+://') ~= nil -end - -function on_file_loaded() - refresh_globals() - if settings.sync_cursor_on_load then cursor=pos end - refresh_UI() -- refresh only after moving cursor - - filename = mp.get_property("filename") - path = mp.get_property('path') - local media_title = mp.get_property("media-title") - if is_protocol(path) and not title_table[path] and path ~= media_title then - title_table[path] = media_title - end - - strippedname = stripfilename(mp.get_property('media-title')) - if settings.show_title_on_file_load then - mp.commandv('show-text', strippedname) - end - if settings.show_playlist_on_file_load then - showplaylist() - end - if settings.set_title_stripped then - mp.set_property("title", settings.title_prefix..strippedname..settings.title_suffix) - end -end - -function on_start_file() - refresh_globals() - filename = mp.get_property("filename") - path = mp.get_property('path') - --if not a url then join path with working directory - if not is_protocol(path) then - path = utils.join_path(mp.get_property('working-directory'), path) - directory = utils.split_path(path) - else - directory = nil - end - - if settings.loadfiles_on_start and plen == 1 then - local ext = filename:match("%.([^%.]+)$") - -- a directory or playlist has been loaded, let's not do anything as mpv will expand it into files - if ext and filetype_lookup[ext:lower()] then - msg.info("Loading files from playing files directory") - playlist() - end - end -end - -function on_end_file() - if settings.save_playlist_on_file_end then save_playlist() end - strippedname = nil - path = nil - directory = nil - filename = nil -end - -function refresh_globals() - pos = mp.get_property_number('playlist-pos', 0) - plen = mp.get_property_number('playlist-count', 0) -end - -function escapepath(dir, escapechar) - return string.gsub(dir, escapechar, '\\'..escapechar) -end - -function replace_table_has_value(value, valid_values) - if value == nil or valid_values == nil then - return false - end - return valid_values['all'] or valid_values[value] -end - -local filename_replace_functions = { - --decode special characters in url - hex_to_char = function(x) return string.char(tonumber(x, 16)) end -} - --- from http://lua-users.org/wiki/LuaUnicode -local UTF8_PATTERN = '[%z\1-\127\194-\244][\128-\191]*' - --- return a substring based on utf8 characters --- like string.sub, but negative index is not supported -local function utf8_sub(s, i, j) - if i > j then - return s - end - - local t = {} - local idx = 1 - for char in s:gmatch(UTF8_PATTERN) do - if i <= idx and idx <= j then - local width = #char > 2 and 2 or 1 - idx = idx + width - t[#t + 1] = char - end - end - return table.concat(t) -end - ---strip a filename based on its extension or protocol according to rules in settings -function stripfilename(pathfile, media_title) - if pathfile == nil then return '' end - local ext = pathfile:match("%.([^%.]+)$") - local protocol = pathfile:match("^(%a%a+)://") - if not ext then ext = "" end - local tmp = pathfile - if settings.filename_replace and not media_title then - for k,v in ipairs(settings.filename_replace) do - if replace_table_has_value(ext, v['ext']) or replace_table_has_value(protocol, v['protocol']) then - for ruleindex, indexrules in ipairs(v['rules']) do - for rule, override in pairs(indexrules) do - override = filename_replace_functions[override] or override - tmp = tmp:gsub(rule, override) - end - end - end - end - end - local tmp_clip = utf8_sub(tmp, 1, settings.slice_longfilenames_amount) - if settings.slice_longfilenames and tmp ~= tmp_clip then - tmp = tmp_clip .. "..." - end - return tmp -end - ---gets the file info of an item -function get_file_info(item) - local path = mp.get_property('playlist/' .. item - 1 .. '/filename') - if is_protocol(path) then return {} end - local file_info = utils.file_info(path) - if not file_info then - msg.warn('failed to read file info for', path) - return {} - end - - return file_info -end - ---gets a nicename of playlist entry at 0-based position i -function get_name_from_index(i, notitle) - refresh_globals() - if plen <= i then msg.error("no index in playlist", i, "length", plen); return nil end - local _, name = nil - local title = mp.get_property('playlist/'..i..'/title') - local name = mp.get_property('playlist/'..i..'/filename') - - local should_use_title = settings.prefer_titles == 'all' or is_protocol(name) and settings.prefer_titles == 'url' - - --check if file has a media title stored - if not title and should_use_title and title_table[name] then - title = title_table[name] - end - - --if we have media title use a more conservative strip - if title and not notitle and should_use_title then - -- Escape a string for verbatim display on the OSD - -- Ref: https://github.com/mpv-player/mpv/blob/94677723624fb84756e65c8f1377956667244bc9/player/lua/stats.lua#L145 - return stripfilename(title, true):gsub("\\", '\\\239\187\191'):gsub("{", "\\{"):gsub("^ ", "\\h") - end - - --remove paths if they exist, keeping protocols for stripping - if string.sub(name, 1, 1) == '/' or name:match("^%a:[/\\]") then - _, name = utils.split_path(name) - end - return stripfilename(name):gsub("\\", '\\\239\187\191'):gsub("{", "\\{"):gsub("^ ", "\\h") -end - -function parse_header(string) - local esc_title = stripfilename(mp.get_property("media-title"), true):gsub("%%", "%%%%") - local esc_file = stripfilename(mp.get_property("filename")):gsub("%%", "%%%%") - return string:gsub("%%N", "\\N") - -- add a blank character at the end of each '\N' to ensure that the height of the empty line is the same as the non empty line - :gsub("\\N", "\\N ") - :gsub("%%pos", mp.get_property_number("playlist-pos",0)+1) - :gsub("%%plen", mp.get_property("playlist-count")) - :gsub("%%cursor", cursor+1) - :gsub("%%mediatitle", esc_title) - :gsub("%%filename", esc_file) - -- undo name escape - :gsub("%%%%", "%%") -end - -function parse_filename(string, name, index) - local base = tostring(plen):len() - local esc_name = stripfilename(name):gsub("%%", "%%%%") - return string:gsub("%%N", "\\N") - :gsub("%%pos", string.format("%0"..base.."d", index+1)) - :gsub("%%name", esc_name) - -- undo name escape - :gsub("%%%%", "%%") -end - -function parse_filename_by_index(index) - local template = settings.normal_file - - local is_idle = mp.get_property_native('idle-active') - local position = is_idle and -1 or pos - - if index == position then - if index == cursor then - if selection then - template = settings.playing_selected_file - else - template = settings.playing_hovered_file - end - else - template = settings.playing_file - end - elseif index == cursor then - if selection then - template = settings.selected_file - else - template = settings.hovered_file - end - end - - return parse_filename(template, get_name_from_index(index), index) -end - -function is_terminal_mode() - local width, height, aspect_ratio = mp.get_osd_size() - return width == 0 and height == 0 and aspect_ratio == 0 -end - -function draw_playlist() - refresh_globals() - - -- if there is no playing file, then cursor can be -1. That would break rendering of playlist. - if cursor == -1 then - cursor = 0 - end - - local ass = assdraw.ass_new() - local terminaloutput = "" - - local _, _, a = mp.get_osd_size() - local h = 720 - local w = math.ceil(h * a) - - if settings.curtain_opacity ~= nil and settings.curtain_opacity ~= 0 and settings.curtain_opacity <= 1.0 then - -- curtain dim from https://github.com/christoph-heinrich/mpv-quality-menu/blob/501794bfbef468ee6a61e54fc8821fe5cd72c4ed/quality-menu.lua#L699-L707 - local alpha = 255 - math.ceil(255 * settings.curtain_opacity) - ass.text = string.format('{\\pos(0,0)\\rDefault\\an7\\1c&H000000&\\alpha&H%X&}', alpha) - ass:draw_start() - ass:rect_cw(0, 0, w, h) - ass:draw_stop() - ass:new_event() - end - - ass:append(settings.style_ass_tags) - - -- add \clip style - -- make both left and right follow text_padding_x - -- both top and bottom follow text_padding_y - local border_size = mp.get_property_number('osd-border-size') - if settings.style_ass_tags ~= nil then - local bord = tonumber(settings.style_ass_tags:match('\\bord(%d+%.?%d*)')) - if bord ~= nil then border_size = bord end - end - ass:append(string.format('{\\clip(%f,%f,%f,%f)}', - settings.text_padding_x - border_size, settings.text_padding_y - border_size, - w - 1 - settings.text_padding_x + border_size, h - 1 - settings.text_padding_y + border_size)) - - -- align from mpv.conf - local align_x = mp.get_property("osd-align-x") - local align_y = mp.get_property("osd-align-y") - -- align from style_ass_tags - if settings.style_ass_tags ~= nil then - local an = tonumber(settings.style_ass_tags:match('\\an(%d)')) - if an ~= nil and alignment_table[an] ~= nil then - align_x = alignment_table[an]["x"] - align_y = alignment_table[an]["y"] - end - end - -- range of x [0, w-1] - local pos_x - if align_x == 'left' then - pos_x = settings.text_padding_x - elseif align_x == 'right' then - pos_x = w - 1 - settings.text_padding_x - else - pos_x = math.floor((w - 1) / 2) - end - -- range of y [0, h-1] - local pos_y - if align_y == 'top' then - pos_y = settings.text_padding_y - elseif align_y == 'bottom' then - pos_y = h - 1 - settings.text_padding_y - else - pos_y = math.floor((h - 1) / 2) - end - ass:pos(pos_x, pos_y) - - if settings.playlist_header ~= "" then - local header = parse_header(settings.playlist_header) - ass:append(header.."\\N") - terminaloutput = terminaloutput..header.."\n" - end - - -- (visible index, playlist index) pairs of playlist entries that should be rendered - local visible_indices = {} - - local one_based_cursor = cursor + 1 - table.insert(visible_indices, one_based_cursor) - - local offset = 1; - local visible_indices_length = 1; - while visible_indices_length < settings.showamount and visible_indices_length < plen do - -- add entry for offset steps below the cursor - local below = one_based_cursor + offset - if below <= plen then - table.insert(visible_indices, below) - visible_indices_length = visible_indices_length + 1; - end - - -- add entry for offset steps above the cursor - -- also need to double check that there is still space, this happens if we have even numbered limit - local above = one_based_cursor - offset - if above >= 1 and visible_indices_length < settings.showamount and visible_indices_length < plen then - table.insert(visible_indices, 1, above) - visible_indices_length = visible_indices_length + 1; - end - - offset = offset + 1 - end - - -- both indices are 1 based - for display_index, playlist_index in pairs(visible_indices) do - if display_index == 1 and playlist_index ~= 1 then - ass:append(settings.playlist_sliced_prefix.."\\N") - terminaloutput = terminaloutput..settings.playlist_sliced_prefix.."\n" - elseif display_index == settings.showamount and playlist_index ~= plen then - ass:append(settings.playlist_sliced_suffix) - terminaloutput = terminaloutput..settings.playlist_sliced_suffix.."\n" - else - -- parse_filename_by_index expects 0 based index - local fname = parse_filename_by_index(playlist_index - 1) - ass:append(fname.."\\N") - terminaloutput = terminaloutput..fname.."\n" - end - end - - if is_terminal_mode() then - local timeout_setting = settings.playlist_display_timeout - local timeout = timeout_setting == 0 and 2147483 or timeout_setting - -- TODO: probably have to strip ass tags from terminal output - -- would maybe be possible to use terminal color output instead - mp.osd_message(terminaloutput, timeout) - else - playlist_overlay.data = ass.text - playlist_overlay:update() - end -end - -local peek_display_timer = nil -local peek_button_pressed = false - -function peek_timeout() - peek_display_timer:kill() - if not peek_button_pressed and not playlist_visible then - remove_keybinds() - end -end - -function handle_complex_playlist_toggle(table) - local event = table["event"] - if event == "press" then - msg.error("Complex key event not supported. Falling back to normal playlist display.") - showplaylist() - elseif event == "down" then - showplaylist(1000000) - if settings.peek_respect_display_timeout then - peek_button_pressed = true - peek_display_timer = mp.add_periodic_timer(settings.playlist_display_timeout, peek_timeout) - end - elseif event == "up" then - -- set playlist state to not visible, doesn't actually hide playlist yet - -- this will allow us to check if other functionality has rendered playlist before removing binds - playlist_visible = false - - function remove_keybinds_after_timeout() - -- if playlist is still not visible then lets actually hide it - -- this lets other keys that interupt the peek to render playlist without peek up event closing it - if not playlist_visible then - remove_keybinds() - end - end - - if settings.peek_respect_display_timeout then - peek_button_pressed = false - if not peek_display_timer:is_enabled() then - mp.add_timeout(0.01, remove_keybinds_after_timeout) - end - else - -- use small delay to let dynamic binds run before keys are potentially unbound - mp.add_timeout(0.01, remove_keybinds_after_timeout) - end - end -end - -function toggle_playlist(show_function) - local show = show_function or showplaylist - if playlist_visible then - remove_keybinds() - else - -- toggle always shows without timeout - show(0) - end -end - -function showplaylist(duration) - refresh_globals() - if plen == 0 then return end - if not playlist_visible and settings.reset_cursor_on_open then - resetcursor() - end - - playlist_visible = true - add_keybinds() - - draw_playlist() - keybindstimer:kill() - - local dur = tonumber(duration) or settings.playlist_display_timeout - if dur > 0 then - keybindstimer = mp.add_periodic_timer(dur, remove_keybinds) - end -end - -function showplaylist_non_interactive(duration) - refresh_globals() - if plen == 0 then return end - if not playlist_visible and settings.reset_cursor_on_open then - resetcursor() - end - playlist_visible = true - draw_playlist() - keybindstimer:kill() - - local dur = tonumber(duration) or settings.playlist_display_timeout - if dur > 0 then - keybindstimer = mp.add_periodic_timer(dur, remove_keybinds) - end -end - -selection=nil -function selectfile() - refresh_globals() - if plen == 0 then return end - if not selection then - selection=cursor - else - selection=nil - end - showplaylist() -end - -function unselectfile() - selection=nil - showplaylist() -end - -function resetcursor() - selection = nil - cursor = mp.get_property_number('playlist-pos', 1) -end - -function removefile() - refresh_globals() - if plen == 0 then return end - selection = nil - if cursor==pos then mp.command("script-message unseenplaylist mark true \"playlistmanager avoid conflict when removing file\"") end - mp.commandv("playlist-remove", cursor) - if cursor==plen-1 then cursor = cursor - 1 end - if plen == 1 then - remove_keybinds() - else - showplaylist() - end -end - -function moveup() - refresh_globals() - if plen == 0 then return end - if cursor~=0 then - if selection then mp.commandv("playlist-move", cursor,cursor-1) end - cursor = cursor-1 - elseif settings.loop_cursor then - if selection then mp.commandv("playlist-move", cursor,plen) end - cursor = plen-1 - end - showplaylist() -end - -function movedown() - refresh_globals() - if plen == 0 then return end - if cursor ~= plen-1 then - if selection then mp.commandv("playlist-move", cursor,cursor+2) end - cursor = cursor + 1 - elseif settings.loop_cursor then - if selection then mp.commandv("playlist-move", cursor,0) end - cursor = 0 - end - showplaylist() -end - - -function movepageup() - refresh_globals() - if plen == 0 or cursor == 0 then return end - local offset = settings.showamount % 2 == 0 and 1 or 0 - local last_file_that_doesnt_scroll = math.ceil(settings.showamount / 2) - local reverse_cursor = plen - cursor - local files_to_jump = math.max(last_file_that_doesnt_scroll + offset - reverse_cursor, 0) + settings.showamount - 2 - local prev_cursor = cursor - cursor = cursor - files_to_jump - if cursor < last_file_that_doesnt_scroll then - cursor = 0 - end - if selection then - mp.commandv("playlist-move", prev_cursor, cursor) - end - showplaylist() -end - -function movepagedown() - refresh_globals() - if plen == 0 or cursor == plen - 1 then return end - local last_file_that_doesnt_scroll = math.ceil(settings.showamount / 2) - 1 - local files_to_jump = math.max(last_file_that_doesnt_scroll - cursor, 0) + settings.showamount - 2 - local prev_cursor = cursor - cursor = cursor + files_to_jump - - local cursor_on_last_page = plen - (settings.showamount - 3) - if cursor > cursor_on_last_page then - cursor = plen - 1 - end - if selection then - mp.commandv("playlist-move", prev_cursor, cursor + 1) - end - showplaylist() -end - - -function movebegin() - refresh_globals() - if plen == 0 or cursor == 0 then return end - local prev_cursor = cursor - cursor = 0 - if selection then mp.commandv("playlist-move", prev_cursor, cursor) end - showplaylist() -end - -function moveend() - refresh_globals() - if plen == 0 or cursor == plen-1 then return end - local prev_cursor = cursor - cursor = plen-1 - if selection then mp.commandv("playlist-move", prev_cursor, cursor+1) end - showplaylist() -end - -function write_watch_later(force_write) - if settings.allow_write_watch_later_config then - if mp.get_property_bool("save-position-on-quit") or force_write then - mp.command("write-watch-later-config") - end - end -end - -function playlist_next() - write_watch_later(true) - mp.commandv("playlist-next", "weak") - if settings.close_playlist_on_playfile then - remove_keybinds() - end - refresh_UI() -end - -function playlist_prev() - write_watch_later(true) - mp.commandv("playlist-prev", "weak") - if settings.close_playlist_on_playfile then - remove_keybinds() - end - refresh_UI() -end - -function playlist_random() - write_watch_later() - refresh_globals() - if plen < 2 then return end - math.randomseed(os.time()) - local random = pos - while random == pos do - random = math.random(0, plen-1) - end - mp.set_property("playlist-pos", random) - if settings.close_playlist_on_playfile then - remove_keybinds() - end -end - -function playfile() - refresh_globals() - if plen == 0 then return end - selection = nil - local is_idle = mp.get_property_native('idle-active') - if cursor ~= pos or is_idle then - write_watch_later() - mp.set_property("playlist-pos", cursor) - else - if cursor~=plen-1 then - cursor = cursor + 1 - end - write_watch_later() - mp.commandv("playlist-next", "weak") - end - if settings.close_playlist_on_playfile then - remove_keybinds() - elseif playlist_visible then - showplaylist() - end -end - -function file_filter(filenames) - local files = {} - for i = 1, #filenames do - local file = filenames[i] - local ext = file:match('%.([^%.]+)$') - if ext and filetype_lookup[ext:lower()] then - table.insert(files, file) - end - end - return files -end - -function get_playlist_filenames_set() - local filenames = {} - for n=0,plen-1,1 do - local filename = mp.get_property('playlist/'..n..'/filename') - local _, file = utils.split_path(filename) - filenames[file] = true - end - return filenames -end - ---Creates a playlist of all files in directory, will keep the order and position ---For exaple, Folder has 12 files, you open the 5th file and run this, the remaining 7 are added behind the 5th file and prior 4 files before it -function playlist(force_dir) - refresh_globals() - if not directory and plen > 0 then return end - local hasfile = true - if plen == 0 then - hasfile = false - dir = mp.get_property('working-directory') - else - dir = directory - end - - if dir == "." then dir = "" end - if force_dir then dir = force_dir end - - local files = file_filter(utils.readdir(dir, "files")) - if winapisort ~= nil then - table.sort(files, winapisort) - else - table.sort(files, alphanumsort) - end - - - if files == nil then - msg.verbose("no files in directory") - return - end - - local filenames = get_playlist_filenames_set() - local c, c2 = 0,0 - if files then - local cur = false - local filename = mp.get_property("filename") - for _, file in ipairs(files) do - if file == nil or file[1] == "." then - break - end - local appendstr = "append" - if not hasfile then - cur = true - appendstr = "append-play" - hasfile = true - end - if filename == file then - cur = true - elseif filenames[file] then - -- skip files already in playlist - elseif cur == true or settings.loadfiles_always_append then - mp.commandv("loadfile", utils.join_path(dir, file), appendstr) - msg.info("Appended to playlist: " .. file) - c2 = c2 + 1 - else - mp.commandv("loadfile", utils.join_path(dir, file), appendstr) - msg.info("Prepended to playlist: " .. file) - mp.commandv("playlist-move", mp.get_property_number("playlist-count", 1)-1, c) - c = c + 1 - end - end - if c2 > 0 or c>0 then - msg.info("Added "..c + c2.." files to playlist") - else - msg.info("No additional files found") - end - cursor = mp.get_property_number('playlist-pos', 1) - else - msg.error("Could not scan for files: "..(error or "")) - end - refresh_globals() - if playlist_visible then - showplaylist() - end - if settings.display_osd_feedback then - if c2 > 0 or c>0 then - mp.osd_message("Added "..c + c2.." files to playlist") - else - mp.osd_message("No additional files found") - end - end - return c + c2 -end - -function parse_home(path) - if not path:find("^~") then - return path - end - local home_dir = os.getenv("HOME") or os.getenv("USERPROFILE") - if not home_dir then - local drive = os.getenv("HOMEDRIVE") - local path = os.getenv("HOMEPATH") - if drive and path then - home_dir = utils.join_path(drive, path) - else - msg.error("Couldn't find home dir.") - return nil - end - end - local result = path:gsub("^~", home_dir) - return result -end - -local interactive_save = false -function activate_playlist_save() - if interactive_save then - remove_keybinds() - mp.command("script-message playlistmanager-save-interactive \"start interactive filenaming process\"") - else - save_playlist() - end -end - ---saves the current playlist into a m3u file -function save_playlist(filename) - local length = mp.get_property_number('playlist-count', 0) - if length == 0 then return end - - --get playlist save path - local savepath - if settings.playlist_savepath == nil or settings.playlist_savepath == "" then - savepath = mp.command_native({"expand-path", "~~home/"}).."/playlists" - else - savepath = parse_home(settings.playlist_savepath) - if savepath == nil then return end - end - - --create savepath if it doesn't exist - if utils.readdir(savepath) == nil then - local windows_args = {'powershell', '-NoProfile', '-Command', 'mkdir', savepath} - local unix_args = { 'mkdir', savepath } - local args = settings.system == 'windows' and windows_args or unix_args - local res = utils.subprocess({ args = args, cancellable = false }) - if res.status ~= 0 then - msg.error("Failed to create playlist save directory "..savepath..". Error: "..(res.error or "unknown")) - return - end - end - - local name = filename - if name == nil then - if settings.playlist_save_filename == nil or settings.playlist_save_filename == "" then - local date = os.date("*t") - local datestring = ("%02d-%02d-%02d_%02d-%02d-%02d"):format(date.year, date.month, date.day, date.hour, date.min, date.sec) - - name = datestring.."_playlist-size_"..length..".m3u" - else - name = settings.playlist_save_filename - end - end - - local savepath = utils.join_path(savepath, name) - local file, err = io.open(savepath, "w") - if not file then - msg.error("Error in creating playlist file, check permissions. Error: "..(err or "unknown")) - else - file:write("#EXTM3U\n") - local i=0 - while i < length do - local pwd = mp.get_property("working-directory") - local filename = mp.get_property('playlist/'..i..'/filename') - local fullpath = filename - if not is_protocol(filename) then - fullpath = utils.join_path(pwd, filename) - end - local title = mp.get_property('playlist/'..i..'/title') or title_table[filename] - if title then - file:write("#EXTINF:,"..title.."\n") - end - file:write(fullpath, "\n") - i=i+1 - end - local saved_msg = "Playlist written to: "..savepath - if settings.display_osd_feedback then mp.osd_message(saved_msg) end - msg.info(saved_msg) - file:close() - end -end - -function alphanumsort(a, b) - local function padnum(d) - local dec, n = string.match(d, "(%.?)0*(.+)") - return #dec > 0 and ("%.12f"):format(d) or ("%s%03d%s"):format(dec, #n, n) - end - return tostring(a):lower():gsub("%.?%d+",padnum)..("%3d"):format(#b) - < tostring(b):lower():gsub("%.?%d+",padnum)..("%3d"):format(#a) -end - --- fast sort algo from https://github.com/zsugabubus/dotfiles/blob/master/.config/mpv/scripts/playlist-filtersort.lua -function sortplaylist(startover) - local playlist = mp.get_property_native('playlist') - if #playlist < 2 then return end - - local order = {} - for i=1, #playlist do - order[i] = i - playlist[i].string = get_name_from_index(i - 1, true) - end - - table.sort(order, function(a, b) - return sort_modes[sort_mode].sort_fn(a, b, playlist) - end) - - for i=1, #playlist do - playlist[order[i]].new_pos = i - end - - for i=1, #playlist do - while true do - local j = playlist[i].new_pos - if i == j then - break - end - mp.commandv('playlist-move', (i) - 1, (j + 1) - 1) - mp.commandv('playlist-move', (j - 1) - 1, (i) - 1) - playlist[j], playlist[i] = playlist[i], playlist[j] - end - end - - for i = 1, #playlist do - local filename = mp.get_property('playlist/' .. i - 1 .. '/filename') - local ext = filename:match("%.([^%.]+)$") - if not ext or not filetype_lookup[ext:lower()] then - --move the directory to the end of the playlist - mp.commandv('playlist-move', i - 1, #playlist) - end - end - - cursor = mp.get_property_number('playlist-pos', 0) - if startover then - mp.set_property('playlist-pos', 0) - end - if playlist_visible then - showplaylist() - end - if settings.display_osd_feedback then - mp.osd_message("Playlist sorted with "..sort_modes[sort_mode].title) - end -end - -function reverseplaylist() - local length = mp.get_property_number('playlist-count', 0) - if length < 2 then return end - for outer=1, length-1, 1 do - mp.commandv('playlist-move', outer, 0) - end - if playlist_visible then - showplaylist() - end - if settings.display_osd_feedback then - mp.osd_message("Playlist reversed") - end -end - -function shuffleplaylist() - refresh_globals() - if plen < 2 then return end - mp.command("playlist-shuffle") - math.randomseed(os.time()) - mp.commandv("playlist-move", pos, math.random(0, plen-1)) - - local playlist = mp.get_property_native('playlist') - for i = 1, #playlist do - local filename = mp.get_property('playlist/' .. i - 1 .. '/filename') - local ext = filename:match("%.([^%.]+)$") - if not ext or not filetype_lookup[ext:lower()] then - --move the directory to the end of the playlist - mp.commandv('playlist-move', i - 1, #playlist) - end - end - - mp.set_property('playlist-pos', 0) - refresh_globals() - if playlist_visible then - showplaylist() - end - if settings.display_osd_feedback then - mp.osd_message("Playlist shuffled") - end -end - -function bind_keys(keys, name, func, opts) - if keys == nil or keys == "" then - mp.add_key_binding(keys, name, func, opts) - return - end - local i = 1 - for key in keys:gmatch("[^%s]+") do - local prefix = i == 1 and '' or i - mp.add_key_binding(key, name..prefix, func, opts) - i = i + 1 - end -end - -function bind_keys_forced(keys, name, func, opts) - if keys == nil or keys == "" then - mp.add_forced_key_binding(keys, name, func, opts) - return - end - local i = 1 - for key in keys:gmatch("[^%s]+") do - local prefix = i == 1 and '' or i - mp.add_forced_key_binding(key, name..prefix, func, opts) - i = i + 1 - end -end - -function unbind_keys(keys, name) - if keys == nil or keys == "" then - mp.remove_key_binding(name) - return - end - local i = 1 - for key in keys:gmatch("[^%s]+") do - local prefix = i == 1 and '' or i - mp.remove_key_binding(name..prefix) - i = i + 1 - end -end - -function add_keybinds() - bind_keys_forced(settings.key_moveup, 'moveup', moveup, "repeatable") - bind_keys_forced(settings.key_movedown, 'movedown', movedown, "repeatable") - bind_keys_forced(settings.key_movepageup, 'movepageup', movepageup, "repeatable") - bind_keys_forced(settings.key_movepagedown, 'movepagedown', movepagedown, "repeatable") - bind_keys_forced(settings.key_movebegin, 'movebegin', movebegin, "repeatable") - bind_keys_forced(settings.key_moveend, 'moveend', moveend, "repeatable") - bind_keys_forced(settings.key_selectfile, 'selectfile', selectfile) - bind_keys_forced(settings.key_unselectfile, 'unselectfile', unselectfile) - bind_keys_forced(settings.key_playfile, 'playfile', playfile) - bind_keys_forced(settings.key_removefile, 'removefile', removefile, "repeatable") - bind_keys_forced(settings.key_closeplaylist, 'closeplaylist', remove_keybinds) -end - -function remove_keybinds() - keybindstimer:kill() - keybindstimer = mp.add_periodic_timer(settings.playlist_display_timeout, remove_keybinds) - keybindstimer:kill() - playlist_overlay.data = "" - playlist_overlay:remove() - if is_terminal_mode() then - mp.osd_message("") - end - playlist_visible = false - if settings.reset_cursor_on_close then - resetcursor() - end - if settings.dynamic_binds then - unbind_keys(settings.key_moveup, 'moveup') - unbind_keys(settings.key_movedown, 'movedown') - unbind_keys(settings.key_movepageup, 'movepageup') - unbind_keys(settings.key_movepagedown, 'movepagedown') - unbind_keys(settings.key_movebegin, 'movebegin') - unbind_keys(settings.key_moveend, 'moveend') - unbind_keys(settings.key_selectfile, 'selectfile') - unbind_keys(settings.key_unselectfile, 'unselectfile') - unbind_keys(settings.key_playfile, 'playfile') - unbind_keys(settings.key_removefile, 'removefile') - unbind_keys(settings.key_closeplaylist, 'closeplaylist') - end -end - -keybindstimer = mp.add_periodic_timer(settings.playlist_display_timeout, remove_keybinds) -keybindstimer:kill() - -if not settings.dynamic_binds then - add_keybinds() -end - -if settings.loadfiles_on_idle_start and mp.get_property_number('playlist-count', 0) == 0 then - playlist() -end - -mp.observe_property('playlist-count', "number", function(_, plcount) - --if we promised to listen and sort on playlist size increase do it - if settings.sortplaylist_on_file_add and (plcount > plen) then - msg.info("Added files will be automatically sorted") - refresh_globals() - sortplaylist() - end - refresh_UI() - resolve_titles() -end) -mp.observe_property('osd-dimensions', 'native', refresh_UI) - - -url_request_queue = {} -function url_request_queue.push(item) table.insert(url_request_queue, item) end -function url_request_queue.pop() return table.remove(url_request_queue, 1) end -local url_titles_to_fetch = url_request_queue -local ongoing_url_requests = {} - -function url_fetching_throttler() - if #url_titles_to_fetch == 0 then - url_title_fetch_timer:kill() - end - - local ongoing_url_requests_count = 0 - for _, ongoing in pairs(ongoing_url_requests) do - if ongoing then - ongoing_url_requests_count = ongoing_url_requests_count + 1 - end - end - - -- start resolving some url titles if there is available slots - local amount_to_fetch = math.max(0, settings.concurrent_title_resolve_limit - ongoing_url_requests_count) - for index=1,amount_to_fetch,1 do - local file = url_titles_to_fetch.pop() - if file then - ongoing_url_requests[file] = true - resolve_ytdl_title(file) - end - end -end - -url_title_fetch_timer = mp.add_periodic_timer(0.1, url_fetching_throttler) -url_title_fetch_timer:kill() - -local_request_queue = {} -function local_request_queue.push(item) table.insert(local_request_queue, item) end -function local_request_queue.pop() return table.remove(local_request_queue, 1) end -local local_titles_to_fetch = local_request_queue -local ongoing_local_request = false - --- this will only allow 1 concurrent local title resolve process -function local_fetching_throttler() - if not ongoing_local_request then - local file = local_titles_to_fetch.pop() - if file then - ongoing_local_request = true - resolve_ffprobe_title(file) - end - end -end - -function resolve_titles() - if settings.prefer_titles == 'none' then return end - if not settings.resolve_url_titles and not settings.resolve_local_titles then return end - - local length = mp.get_property_number('playlist-count', 0) - if length < 2 then return end - -- loop all items in playlist because we can't predict how it has changed - local added_urls = false - local added_local = false - for i=0,length - 1,1 do - local filename = mp.get_property('playlist/'..i..'/filename') - local title = mp.get_property('playlist/'..i..'/title') - if i ~= pos - and filename - and not title - and not title_table[filename] - and not requested_titles[filename] - then - requested_titles[filename] = true - if filename:match('^https?://') and settings.resolve_url_titles then - url_titles_to_fetch.push(filename) - added_urls = true - elseif settings.prefer_titles == "all" and settings.resolve_local_titles then - local_titles_to_fetch.push(filename) - added_local = true - end - end - end - if added_urls then - url_title_fetch_timer:resume() - end - if added_local then - local_fetching_throttler() - end -end - -function resolve_ytdl_title(filename) - local args = { - settings.youtube_dl_executable, - '--no-playlist', - '--flat-playlist', - '-sJ', - '--no-config', - filename, - } - local req = mp.command_native_async( - { - name = "subprocess", - args = args, - playback_only = false, - capture_stdout = true - }, - function (success, res) - ongoing_url_requests[filename] = false - if res.killed_by_us then - msg.verbose('Request to resolve url title ' .. filename .. ' timed out') - return - end - if res.status == 0 then - local json, err = utils.parse_json(res.stdout) - if not err then - local is_playlist = json['_type'] and json['_type'] == 'playlist' - local title = (is_playlist and '[playlist]: ' or '') .. json['title'] - msg.verbose(filename .. " resolved to '" .. title .. "'") - title_table[filename] = title - mp.set_property_native('user-data/playlistmanager/titles', title_table) - refresh_UI() - else - msg.error("Failed parsing json, reason: "..(err or "unknown")) - end - else - msg.error("Failed to resolve url title "..filename.." Error: "..(res.error or "unknown")) - end - end - ) - - mp.add_timeout( - settings.resolve_title_timeout, - function() - mp.abort_async_command(req) - ongoing_url_requests[filename] = false - end - ) -end - -function resolve_ffprobe_title(filename) - local args = { "ffprobe", "-show_format", "-show_entries", "format=tags", "-loglevel", "quiet", filename } - local req = mp.command_native_async( - { - name = "subprocess", - args = args, - playback_only = false, - capture_stdout = true - }, - function (success, res) - ongoing_local_request = false - local_fetching_throttler() - if res.killed_by_us then - msg.verbose('Request to resolve local title ' .. filename .. ' timed out') - return - end - if res.status == 0 then - local title = string.match(res.stdout, "title=([^\n\r]+)") - if title then - msg.verbose(filename .. " resolved to '" .. title .. "'") - title_table[filename] = title - mp.set_property_native('user-data/playlistmanager/titles', title_table) - refresh_UI() - end - else - msg.error("Failed to resolve local title "..filename.." Error: "..(res.error or "unknown")) - end - end - ) -end - ---script message handler -function handlemessage(msg, value, value2) - if msg == "show" and value == "playlist" then - if value2 ~= "toggle" then - showplaylist(value2) - return - else - toggle_playlist(showplaylist) - return - end - end - if msg == "show" and value == "playlist-nokeys" then - if value2 ~= "toggle" then - showplaylist_non_interactive(value2) - return - else - toggle_playlist(showplaylist_non_interactive) - return - end - end - if msg == "show" and value == "filename" and strippedname and value2 then - mp.commandv('show-text', strippedname, tonumber(value2)*1000 ) ; return - end - if msg == "show" and value == "filename" and strippedname then - mp.commandv('show-text', strippedname ) ; return - end - if msg == "sort" then sortplaylist(value) ; return end - if msg == "shuffle" then shuffleplaylist() ; return end - if msg == "reverse" then reverseplaylist() ; return end - if msg == "loadfiles" then playlist(value) ; return end - if msg == "save" then save_playlist(value) ; return end - if msg == "playlist-next" then playlist_next() ; return end - if msg == "playlist-prev" then playlist_prev() ; return end - if msg == "playlist-next-random" then playlist_random() ; return end - if msg == "enable-interactive-save" then interactive_save = true end - if msg == "close" then remove_keybinds() end -end - -mp.register_script_message("playlistmanager", handlemessage) - -bind_keys(settings.key_sortplaylist, "sortplaylist", function() - sortplaylist() - sort_mode = sort_mode + 1 - if sort_mode > #sort_modes then sort_mode = 1 end -end) -bind_keys(settings.key_shuffleplaylist, "shuffleplaylist", shuffleplaylist) -bind_keys(settings.key_reverseplaylist, "reverseplaylist", reverseplaylist) -bind_keys(settings.key_loadfiles, "loadfiles", playlist) -bind_keys(settings.key_saveplaylist, "saveplaylist", activate_playlist_save) -bind_keys(settings.key_showplaylist, "showplaylist", showplaylist) -bind_keys( - settings.key_peek_at_playlist, - "peek_at_playlist", - handle_complex_playlist_toggle, - { complex=true } -) - -mp.register_event("start-file", on_start_file) -mp.register_event("file-loaded", on_file_loaded) -mp.register_event("end-file", on_end_file) diff --git a/subdir_files/.config/sqlfluff/.sqlfluff b/subdir_files/.config/sqlfluff/.sqlfluff new file mode 100644 index 0000000..a3c29ef --- /dev/null +++ b/subdir_files/.config/sqlfluff/.sqlfluff @@ -0,0 +1,14 @@ +[sqlfluff] +dialect = postgres + +## Here are things the man page suggested might get it to indent despite parsing errors, but nothing worked. +# fix_even_unparsable = True +# exclude_rules = LT02 +#ignore = lexing,linting,parsing,templating + + +[sqlfluff:indentation] +tab_space_size = 2 + +[sqlfluff:rules:capitalisation.keywords] +capitalisation_policy = lower diff --git a/subdir_files/.local/share/icc b/subdir_files/.local/share/icc new file mode 120000 index 0000000..e3a5ff6 --- /dev/null +++ b/subdir_files/.local/share/icc @@ -0,0 +1 @@ +../../../icc \ No newline at end of file