2160 lines
71 KiB
Org Mode
2160 lines
71 KiB
Org Mode
#+TITLE: Custom Scripts
|
||
#+setupfile: headers
|
||
#+PROPERTY: header-args :exports code
|
||
#+PROPERTY: header-args:emacs-lisp :exports none :tangle no
|
||
|
||
* Custom Scripts
|
||
This file will present all the executable scripts I wrote. It is also their
|
||
original source code, all the following code snippets are exported and tangled
|
||
from this file to the actual executables.
|
||
|
||
** Autostart
|
||
:PROPERTIES:
|
||
:HEADER-ARGS: :shebang "#!/usr/bin/env bash" :mkdirp yes :tangle ~/.local/bin/autostart
|
||
:END:
|
||
Because I sometimes switch from window manager to window manager, creating a
|
||
script that handles by itself autostarting things for me is way easier than
|
||
rewriting every time the autostart part of my configuration. As you can every
|
||
instance will be launched asynchronously, and only if there is no other instance
|
||
of said command running.
|
||
#+NAME: autostart-table
|
||
| Command | Arguments | Run once? |
|
||
|----------------------+------------------------------------------+-----------|
|
||
| ~pactl~ | ~load-module module-switch-on-connect~ | |
|
||
| ~mpc~ | ~stop~ | no |
|
||
| ~xrdb~ | ~-merge "$XDG_CONFIG_HOME"/X11/Xresources~ | no |
|
||
| ~picom~ | | yes |
|
||
| ~numlockx~ | ~on~ | yes |
|
||
| ~xfce-polkit~ | | yes |
|
||
| ~nm-applet~ | | yes |
|
||
| ~xwallpaper~ | ~--zoom "$(cat "$HOME"/.cache/wallpaper)"~ | no |
|
||
| ~xss-lock~ | ~plock~ | yes |
|
||
| ~/usr/lib/kdeconnectd~ | | yes |
|
||
| ~dunst~ | | yes |
|
||
|
||
#+NAME: autostart-gen
|
||
#+header: :wrap "src bash :exports code"
|
||
#+BEGIN_SRC emacs-lisp :var table=autostart-table :cache yes
|
||
(mapconcat (lambda (start-command)
|
||
(let* ((clean-string (lambda (str) (replace-regexp-in-string "~" "" str)))
|
||
(command (funcall clean-string (nth 0 start-command)))
|
||
(arguments (funcall clean-string (nth 1 start-command)))
|
||
(oncep (string= "yes" (nth 2 start-command)))
|
||
(full-command (replace-regexp-in-string
|
||
" +"
|
||
" "
|
||
(format "%s %s &" command arguments))))
|
||
|
||
(concat (format "which %s && %s"
|
||
command
|
||
(if (not oncep)
|
||
full-command
|
||
(format (concat "if pgrep -x %s ; then\n"
|
||
" echo %s already running\n"
|
||
"else\n"
|
||
" %s\n"
|
||
" disown\n"
|
||
"fi")
|
||
command
|
||
command
|
||
full-command))))))
|
||
table
|
||
"\n")
|
||
#+END_SRC
|
||
|
||
#+RESULTS[8c42a43989020c61050b2930ae60c81248c2dd44]: autostart-gen
|
||
#+begin_src bash :exports code
|
||
which pactl && pactl load-module module-switch-on-connect &
|
||
which mpc && mpc stop &
|
||
which xrdb && xrdb -merge "$XDG_CONFIG_HOME"/X11/Xresources &
|
||
which picom && if pgrep -x picom ; then
|
||
echo picom already running
|
||
else
|
||
picom &
|
||
disown
|
||
fi
|
||
which numlockx && if pgrep -x numlockx ; then
|
||
echo numlockx already running
|
||
else
|
||
numlockx on &
|
||
disown
|
||
fi
|
||
which xfce-polkit && if pgrep -x xfce-polkit ; then
|
||
echo xfce-polkit already running
|
||
else
|
||
xfce-polkit &
|
||
disown
|
||
fi
|
||
which nm-applet && if pgrep -x nm-applet ; then
|
||
echo nm-applet already running
|
||
else
|
||
nm-applet &
|
||
disown
|
||
fi
|
||
which xwallpaper && xwallpaper --zoom "$(cat "$HOME"/.cache/wallpaper)" &
|
||
which xss-lock && if pgrep -x xss-lock ; then
|
||
echo xss-lock already running
|
||
else
|
||
xss-lock plock &
|
||
disown
|
||
fi
|
||
which /usr/lib/kdeconnectd && if pgrep -x /usr/lib/kdeconnectd ; then
|
||
echo /usr/lib/kdeconnectd already running
|
||
else
|
||
/usr/lib/kdeconnectd &
|
||
disown
|
||
fi
|
||
which dunst && if pgrep -x dunst ; then
|
||
echo dunst already running
|
||
else
|
||
dunst &
|
||
disown
|
||
fi
|
||
#+end_src
|
||
|
||
I also have an external sound card, a Scarlet 2i2 G3, that I would
|
||
like to use as my default audio output. However, it might not be
|
||
always connected, hence the following code:
|
||
#+NAME: default-soundcard
|
||
#+BEGIN_SRC bash
|
||
SOUNDCARD=$(pactl list short sinks | grep "Focusrite")
|
||
if [[ -n $SOUNDCARD ]]; then
|
||
pactl set-default-sink "$(echo "$SOUNDCARD" | awk '{print $2}')"
|
||
fi
|
||
#+END_SRC
|
||
|
||
** cli utilities
|
||
*** Backup
|
||
:PROPERTIES:
|
||
:HEADER-ARGS: :shebang "#!/usr/bin/env fish" :mkdirp yes :tangle ~/.local/bin/backup
|
||
:END:
|
||
~backup~ is a very simple, one-liner script that will create a local
|
||
copy of a file and add the date at which it was copied in the
|
||
filename. You can see its source code here:
|
||
#+BEGIN_SRC fish
|
||
cp -r $argv[1] $argv[1].bak.(date +"%Y%m%d%H%M%S")
|
||
#+END_SRC
|
||
|
||
*** CPU Scaling
|
||
:PROPERTIES:
|
||
:HEADER-ARGS: :shebang "#!/usr/bin/env bash" :mkdirp yes :tangle ~/.local/bin/cpu-scaling
|
||
:END:
|
||
As I am using a laptop, maximum performance isn’t always what I want.
|
||
Sometimes, it’s just better to have not so great but less
|
||
battery-demanding performance. It is possible to achieve this by
|
||
modifying the CPU governor with ~cpupower~. The [[https://wiki.archlinux.org/title/CPU_frequency_scaling#Scaling_governors][Arch Wiki]] has, as usual,
|
||
some superb documentation on this.
|
||
|
||
The following script asks the user through ~rofi~ which governor to
|
||
apply, and it relies on [[file:./scripts.md#askpass][askpass]] to retrieve the user’s password.
|
||
#+begin_src bash
|
||
governors=("performance" "powersave" "userspace" "ondemand" "conservative" "schedutil")
|
||
governor=$(printf "%s\n" "${governors[@]}" | rofi -dmenu)
|
||
sudo -A cpupower frequency-set -g "$governor"
|
||
#+end_src
|
||
|
||
*** docker-running
|
||
:PROPERTIES:
|
||
:HEADER-ARGS: :shebang "#!/usr/bin/env sh" :tangle ~/.local/bin/docker-running
|
||
:END:
|
||
As with =mu-unread= below, =docker-running= is a small and simple utility
|
||
for my StumpWM configuration which indicates how many Docker
|
||
containers are currently running.
|
||
|
||
#+begin_src sh
|
||
NB_CONTAINERS=$(docker ps -q | wc -l | tr -d '\n')
|
||
printf "^f3^f0 %d" $NB_CONTAINERS
|
||
#+end_src
|
||
|
||
*** Kamoji Generator
|
||
:PROPERTIES:
|
||
:HEADER-ARGS: :shebang "#!/usr/bin/env sh" :tangle ~/.local/bin/kamoji
|
||
:END:
|
||
This script comes straight from [[https://charm.sh/blog/kamoji-generator/][this blog post]] and generates kamoji. I
|
||
modified it a bit in order to work with either =xclipboard= or =wl-copy=
|
||
depending on whether I am in an X.org session or a Wayland session.
|
||
|
||
Note that it requires the =OPENAI_API_KEY= environment variable to be
|
||
set with a valid OpenAI key.
|
||
#+begin_src bash
|
||
# If the user passes '-h', '--help', or 'help' print out a little bit
|
||
# of help. text.
|
||
case "$1" in
|
||
"-h" | "--help" | "help")
|
||
printf 'Generate kaomojis on request.\n\n'
|
||
printf 'Usage: %s [kind]\n' "$(basename "$0")"
|
||
exit 1
|
||
;;
|
||
esac
|
||
|
||
# The user can pass an argument like "bear" or "angry" to specify the
|
||
# general kind of Kaomoji produced.
|
||
sentiment=""
|
||
if [[ $1 != "" ]]; then
|
||
sentiment=" $1"
|
||
fi
|
||
|
||
# Ask mods to generate Kaomojis. Save the output in a variable.
|
||
kaomoji="$(mods "generate 10${sentiment} kaomojis. Number them and put each one on its own line.")"
|
||
if [[ $kaomoji == "" ]]; then
|
||
exit 1
|
||
fi
|
||
|
||
# Pipe mods output to gum, so the user can choose the perfect kaomoji.
|
||
# Save that choice in a variable. Also note that we're using cut to
|
||
# drop the item number in front of the Kaomoji.
|
||
choice="$(echo "$kaomoji" | gum choose | cut -d ' ' -f 2)"
|
||
if [[ $choice == "" ]]; then
|
||
exit 1
|
||
fi
|
||
|
||
# If the current session is Wayland, copy with wl-copy, otherwise copy
|
||
# with xclipboard
|
||
if [ "$XDG_SESSION_TYPE" = "wayland" ]
|
||
then printf '%s' "$choice" | wl-copy # Wayland
|
||
else printf '%s' "$choice" | xclip -sel clip # X11
|
||
fi
|
||
|
||
# We're done!
|
||
printf 'Copied %s to the clipboard\n' "$choice"
|
||
#+end_src
|
||
|
||
*** mu-unread
|
||
:PROPERTIES:
|
||
:HEADER-ARGS: :shebang "#!/usr/bin/env sh" :tangle ~/.local/bin/mu-unread
|
||
:END:
|
||
~mu-unread~ is a very simple utility that simply returns the amount of
|
||
unread emails I have through the use of ~mu~.
|
||
|
||
As you can see, the output string contains two font switchers for
|
||
StumpWM so I can switch from the main font to Siji for the character
|
||
contained between them: U+E072 (an email icon).
|
||
#+begin_src sh
|
||
UNREAD=$(mu find "flag:unread AND (maildir:/Inbox OR maildir:/Junk)" | wc -l)
|
||
printf "^f2^f0%s" "$UNREAD"
|
||
#+end_src
|
||
|
||
*** Post scrot script
|
||
:PROPERTIES:
|
||
:HEADER-ARGS: :shebang "#!/usr/bin/env sh" :tangle ~/.local/bin/post-scrot
|
||
:END:
|
||
It is possible to call a script on the resulting image of a ~scrot~
|
||
command. Not only do I want to move them to a specific directory, I
|
||
also want to be able to see them in ~nsxiv~ (a replacement for ~sxiv~) in
|
||
case I want to edit the image, copy it or simply delete it (sometimes
|
||
I take screenshots by accident).
|
||
#+begin_src sh
|
||
mv "$@" ~/Pictures/Screenshots/
|
||
nsxiv -abfs f "$HOME/Pictures/Screenshots/$*"
|
||
#+end_src
|
||
|
||
*** screenshot
|
||
:PROPERTIES:
|
||
:HEADER-ARGS: :shebang "#!/usr/bin/env sh" :mkdirp yes :tangle ~/.local/bin/screenshot
|
||
:END:
|
||
This is a utility only useful with Wayland for now, using =grim=, =slurp=
|
||
(in order to select which area of the screen I wish to capture) and
|
||
=wl-copy= (from =wl-clipboard=). It saves the screenshot in my
|
||
=$HOME/Pictures/Screenshots= directory with a name formatted as
|
||
=Screenshot_20230425_134550.png= if the screenshot was taken on the 25th
|
||
of April 2023 at 1:45:50PM. If the file already exists, the script
|
||
will suffix the name with an underscore followed by an incremental
|
||
number like =Screenshot_20230425_134550_1.png= or
|
||
=Screenshot_20230425_134550_2.png=.
|
||
|
||
If the argument =select= is passed to =screenshot=, as in =screenshot
|
||
select=, then use =slurp= to select the area to capture.
|
||
#+begin_src sh
|
||
OUTFILE_BASE="$HOME/Pictures/Screenshots/Screenshot_$(date +%Y%m%d)_$(date +%H%M%S)"
|
||
OUTFILE="$OUTFILE_BASE.png"
|
||
SUFFIX=0
|
||
|
||
while getopts ':cd:gs' OPTION; do
|
||
case "$OPTION" in
|
||
c )
|
||
COPY="yes"
|
||
;;
|
||
d )
|
||
DELAY="$OPTARG"
|
||
;;
|
||
g )
|
||
GIMP="yes"
|
||
;;
|
||
s )
|
||
SELECT="yes"
|
||
;;
|
||
? )
|
||
echo "Usage: $(basename "$0") [-c] [-d DELAY] [-g] [-s]"
|
||
exit 1
|
||
;;
|
||
esac
|
||
done
|
||
|
||
if [ -n "$DELAY" ]
|
||
then sleep "$DELAY"
|
||
fi
|
||
|
||
if [ "$SELECT" = "yes" ]
|
||
then grim -g "$(slurp)" "$OUTFILE"
|
||
else grim "$OUTFILE"
|
||
fi
|
||
|
||
if [ "$GIMP" = "yes" ]
|
||
then gimp "$OUTFILE"
|
||
fi
|
||
|
||
if [ "$COPY" = "yes" ]
|
||
then wl-copy < "$OUTFILE"
|
||
fi
|
||
|
||
# wl-copy < "$OUTFILE"
|
||
#+end_src
|
||
|
||
*** sshbind
|
||
:PROPERTIES:
|
||
:HEADER-ARGS: :shebang "#!/usr/bin/env fish" :mkdirp yes :tangle ~/.local/bin/sshbind
|
||
:END:
|
||
Something that I did not know for quite some time, but that is
|
||
actually crazy useful about SSH is its ability to bind locally the
|
||
port of a remote machine, and vice versa. The syntax is actually very
|
||
simple, but I prefer a more intuitive way of writing it. Its usage is
|
||
~sshbind PORT FROMHOST TOHOST~.
|
||
#+BEGIN_SRC fish
|
||
ssh -L $argv[1]:$argv[3]:$argv[1] $argv[2] -N
|
||
#+END_SRC
|
||
|
||
*** Nsxiv key handler
|
||
:PROPERTIES:
|
||
:HEADER-ARGS: :mkdirp yes :tangle no :noweb yes
|
||
:END:
|
||
One thing I like with ~nsxiv~ is you can trigger different behaviours
|
||
based on keypresses. For instance, with my current nsxiv
|
||
configuration, if I press the space key followed by a character, it
|
||
can delete to the trashcan, delete definitively, or open the current
|
||
image in GIMP. All of that is done through one script file stored in
|
||
~$HOME/.config/nsxiv/exec/key-handler~. The fact it reacts to first
|
||
thespace bar instead of /Ctrl-x/ is because I use a custom version of
|
||
nsxiv I first modified to fit the bépo layout, and then I decided to
|
||
change the prefix to fit how I use Emacs and StumpWM. You can read the
|
||
PKGBUILD and my nsxiv patch [[https://labs.phundrak.com/phundrak/dotfiles/src/branch/master/Documents/code/PKGBUILDs/sxiv][in my dotfiles repo]].
|
||
|
||
#+header: :shebang "#!/usr/bin/env fish" :tangle ~/.config/nsxiv/exec/key-handler
|
||
#+begin_src fish
|
||
<<nsxiv-read-files>>
|
||
|
||
<<nsxiv-switch-statement>>
|
||
#+end_src
|
||
|
||
Here is a step by step explanation of the source code. First, we want
|
||
to store in the variable ~FILES~ all the files marked in nsxiv. This is
|
||
done with a ~while~ loop and the ~read~ command.
|
||
#+name: nsxiv-read-files
|
||
#+begin_src fish
|
||
while read file
|
||
set -g FILES "$file" $FILES
|
||
end
|
||
#+end_src
|
||
|
||
We can then read from the first member of ~argv~ which key the user
|
||
pressed. Depending on it, we can choose what to execute.
|
||
#+name: nsxiv-switch-statement
|
||
#+begin_src fish
|
||
switch "$argv[1]"
|
||
<<nsxiv-trash>>
|
||
<<nsxiv-rm>>
|
||
<<nsxiv-gimp>>
|
||
<<nsxiv-jpeg>>
|
||
<<nsxiv-rotate-clockwise>>
|
||
<<nsxiv-rotate-counter-clockwise>>
|
||
<<nsxiv-yank>>
|
||
end
|
||
#+end_src
|
||
|
||
The first option with the letter ~d~ is to move the file to the trash
|
||
can. For this, we use the ~trash~ command from ~trash-cli~.
|
||
#+name: nsxiv-trash
|
||
#+begin_src fish
|
||
case "d"
|
||
trash $FILES
|
||
#+end_src
|
||
|
||
In case we want to definitively delete a file without using the trash
|
||
can, we can use ~rm~ instead when we press the ~D~ key.
|
||
#+name: nsxiv-rm
|
||
#+begin_src fish
|
||
case "D"
|
||
rm $FILES
|
||
#+end_src
|
||
|
||
It’s not rare I want to modify an image I have open in nsxiv,
|
||
especially since screenshots are automatically open in this image
|
||
viewer aften they are taken. In that case, a simple command will do.
|
||
#+name: nsxiv-gimp
|
||
#+begin_src fish
|
||
case "g"
|
||
gimp $FILES
|
||
#+end_src
|
||
|
||
Often, I use nsxiv to convert an image to a JPEG file, because my
|
||
internet connection is not that great and JPEG screenshots are faster
|
||
to upload than PNG screenshots. So what I do is for each file
|
||
selected, I take the base name of the file (i.e. remove its
|
||
extension), and then I use the ~convert~ command from ~imagemagik~ to
|
||
convert it from its original format to a JPG format --- ~imagemagik~
|
||
detects the formats based on the extension.
|
||
#+name: nsxiv-jpeg
|
||
#+begin_src fish
|
||
case "j"
|
||
for f in $FILES
|
||
set basename (echo "$f" | sed 's/\.[^.]*$//')
|
||
convert "$f" "$basename.jpg"
|
||
end
|
||
#+end_src
|
||
|
||
I have two commands to rotate my files, and both only differ by the
|
||
angle of rotation. Once again I rely on ~convert~ in both cases. To
|
||
rotate clockwise, this is the code needed.
|
||
#+name: nsxiv-rotate-clockwise
|
||
#+begin_src fish
|
||
case "r"
|
||
for f in $FILES
|
||
convert -rotate 90 "$f" "$f"
|
||
end
|
||
#+end_src
|
||
|
||
On the other hand, to rotate counter-clockwise, we need this code:
|
||
#+name: nsxiv-rotate-counter-clockwise
|
||
#+begin_src fish
|
||
case "R"
|
||
for f in $FILES
|
||
convert -rotate 270 "$f" "$f"
|
||
end
|
||
#+end_src
|
||
|
||
Lastly, when I want to copy a file, I just hit the ~y~ key for “yank”
|
||
(that’s a term from Emacs). For that, I rely on ~file~ to tell me the
|
||
mimetype of the image, then I can pass it to ~xclip~ along with the
|
||
filename to copy it to the clipboard. In this case, we only copy the
|
||
first of the selected files since the clipboard cannot hold several
|
||
files at once.
|
||
#+name: nsxiv-yank
|
||
#+begin_src fish
|
||
case "y"
|
||
set FILE "$FILES[1]"
|
||
set TYPE (file -i "$FILE" | sed -r 's|.*(image/[a-z]+);.*|\1|')
|
||
xclip -sel clip -t "$TYPE" -i "$FILE"
|
||
#+end_src
|
||
|
||
*** Secure key generator
|
||
:PROPERTIES:
|
||
:HEADER-ARGS: :shebang "#!/usr/bin/env sh" :tangle ~/.local/bin/skg
|
||
:END:
|
||
#+begin_src bash
|
||
tr -cd '[:alnum:]' < /dev/urandom | fold -w 64 | head -n 1 | tr -d '\n'
|
||
#+end_src
|
||
|
||
*** Starwars
|
||
:PROPERTIES:
|
||
:HEADER-ARGS: :shebang "#!/usr/bin/env fish" :mkdirp yes :tangle ~/.local/bin/starwars
|
||
:END:
|
||
This is a one-liner that allows you to watch Star Wars episode 4 in ASCII art in
|
||
your terminal. Here is the code:
|
||
#+BEGIN_SRC fish
|
||
telnet towel.blinkenlights.nl
|
||
#+END_SRC
|
||
|
||
*** Toggle touchpad tapping
|
||
:PROPERTIES:
|
||
:HEADER-ARGS: :shebang "#!/usr/bin/env fish" :mkdirp yes :tangle ~/.local/bin/tttapping
|
||
:END:
|
||
For some reason, my firmware does not recognize the function key for
|
||
toggling the touchpad. I’m not going to really complain about it since
|
||
it lets me program it like I want. Since I often don’t need to
|
||
completely deactivate the touchpad, I’ll instead toggle whether
|
||
tapping is enabled or not when pressing ~XF86TouchpadToggle~. And for
|
||
that, I need this small script that will actually toggle it, and it
|
||
will be used in my window manager configuration.
|
||
|
||
First let’s declare some variables to make this script more personal. With my
|
||
current computer (a Gazelle by System76), the name of my touchpad is the
|
||
following:
|
||
#+BEGIN_SRC fish
|
||
set TPNAME "ELAN0412:00 04F3:3162 Touchpad"
|
||
#+END_SRC
|
||
|
||
Let’s now get the identifier of the touchpad for ~xinput~:
|
||
#+BEGIN_SRC fish
|
||
set TPID (xinput list | grep $TPNAME | awk '{print $6}' | sed 's|id=\(.*\)|\1|g')
|
||
#+END_SRC
|
||
|
||
Now, let’s detect the current status of the touchpad:
|
||
#+BEGIN_SRC fish
|
||
set TPSTATUS (xinput list-props $TPID | grep "Tapping Enabled" | \
|
||
grep -v "Default" | awk '{print $5}')
|
||
#+END_SRC
|
||
|
||
This will set ~TPSTATUS~ either to ~0~, meaning tapping is disabled, or to ~1~,
|
||
meaning it’s enabled. I will consider any other value as being disabled.
|
||
#+BEGIN_SRC fish
|
||
test [[ $TPSTATUS = "1" ]] && set NEWTPSTATUS 0 || set NEWTPSTATUS 1
|
||
#+END_SRC
|
||
|
||
Finally, let’s update the touchpad’s options:
|
||
#+BEGIN_SRC fish
|
||
xinput set-prop $TPNAME "libinput Tapping Enabled" $NEWTPSTATUS
|
||
#+END_SRC
|
||
|
||
*** Wacom setup
|
||
:PROPERTIES:
|
||
:HEADER-ARGS: :shebang "#!/usr/bin/env sh" :mkdirp yes :tangle ~/.local/bin/wacom-setup
|
||
:END:
|
||
I made a small and quick utility to set up my Wacom tablet to bind it
|
||
to one screen when in Xorg. This is quite easy, we simply need to find
|
||
the Wacom stylus’ ID and assign it to the display we want.
|
||
#+begin_src sh
|
||
ID=$(xinput | grep -oPi "wacom.+stylus.+id=\K([0-9]+)")
|
||
SCREEN=$(xrandr -q --current | \
|
||
grep -iPo '^([^ ])+(?= connected)' | \
|
||
rofi -dmenu -i -p 'Select your display' | \
|
||
tr -d '\n')
|
||
xinput map-to-output "$ID" "$SCREEN"
|
||
#+end_src
|
||
|
||
** Emacs stuff
|
||
*** Dired
|
||
:PROPERTIES:
|
||
:HEADER-ARGS: :shebang "#!/bin/bash" :mkdirp yes :tangle ~/.local/bin/dired
|
||
:END:
|
||
This script allows me to open anything in dired from the command line.
|
||
#+BEGIN_SRC bash
|
||
emacsclient -c -a emacs -e "(dired \"$*\")"
|
||
#+END_SRC
|
||
|
||
*** Ediff
|
||
:PROPERTIES:
|
||
:HEADER-ARGS: :shebang "#!/bin/bash" :mkdirp yes :tangle ~/.local/bin/ediff
|
||
:END:
|
||
I want Ediff as my merge tool, not just with Git but with other
|
||
programs tooa, such as =pacdiff=.
|
||
#+begin_src bash
|
||
emacsclient -c -a emacs -e "(ediff-files \"$1\" \"$2\")"
|
||
#+end_src
|
||
|
||
*** Emacsmail
|
||
:PROPERTIES:
|
||
:HEADER-ARGS: :shebang "#!/bin/bash" :mkdirp yes :tangle ~/.local/bin/emacsmail
|
||
:END:
|
||
This short script is used in my =~/.local/share/applications/mu4e.desktop= file
|
||
in order to send to Emacs any ~mailto:~ requests made in my system.
|
||
#+BEGIN_SRC bash
|
||
emacsclient -c -n -a emacs -e "(browse-url-mail \"$*\")"
|
||
#+END_SRC
|
||
|
||
*** Restart Emacs
|
||
:PROPERTIES:
|
||
:HEADER-ARGS: :shebang "#!/bin/bash" :mkdirp yes :tangle ~/.local/bin/restart-emacs
|
||
:END:
|
||
Believe me or not, it happens I restart Emacs. I generally start Emacs
|
||
manually with =emacs --daemon= because of an issue rendering =lsp-mode=
|
||
useless when started by the user systemd service.
|
||
#+begin_src bash
|
||
PID_EMACS=$(pidof emacs)
|
||
killall emacs
|
||
echo "Waiting for Emacs to be killed... (pid: $PID_EMACS)"
|
||
if timeout 30 tail --pid=$PID_EMACS -f /dev/null ; then
|
||
emacs --daemon
|
||
else
|
||
echo "Failed to kill Emacs after 30s"
|
||
fi
|
||
#+end_src
|
||
|
||
** Media
|
||
*** mp42webm
|
||
:PROPERTIES:
|
||
:HEADER-ARGS: :shebang "#!/usr/bin/env fish" :mkdirp yes :tangle ~/.local/bin/mp42webm
|
||
:END:
|
||
This function allows me to convert easily a MP4 video to the webm
|
||
format. Nothing too fancy here.
|
||
#+BEGIN_SRC fish
|
||
ffmpeg -i $argv[1] -c:v libvpx -crf 10 -b:v 1M -c:a libvorbis $argv[1].webm
|
||
#+END_SRC
|
||
|
||
*** youtube-dl wrappers
|
||
**** ytplay
|
||
:PROPERTIES:
|
||
:HEADER-ARGS: :shebang "#!/usr/bin/env fish" :mkdirp yes :tangle ~/.local/bin/ytplay
|
||
:END:
|
||
~ytplay~ is a simple script I’ve written that allows me to play in MPV
|
||
any YouTube video at the desired resolution. The script relies on
|
||
~dmenu~ (or ~rofi~ in dmenu-mode), ~youtube-dl~ and of course ~mpv~ itself.
|
||
#+BEGIN_SRC fish
|
||
set URL (rofi -dmenu -i -p "Video URL")
|
||
if test -n "$URL"
|
||
set FORMAT \
|
||
(youtube-dl --list-formats "$URL" | \
|
||
egrep "webm.*[0-9]+x[0-9]+" | \
|
||
awk '{print $3 " " $1}' | \
|
||
sort -gu | \
|
||
rofi -dmenu -i -p "Resolution" | \
|
||
string split " ")
|
||
set FCODE $FORMAT[2]
|
||
mpv --ytdl-format=$FCODE+bestaudio/best "$URL"
|
||
end
|
||
#+END_SRC
|
||
|
||
I’ll even add a ~.desktop~ entry for this script:
|
||
#+BEGIN_SRC conf-desktop :tangle ~/.local/share/applications/ytplay.desktop :mkdirp yes
|
||
[Desktop Entry]
|
||
Type=Application
|
||
Version=1.0
|
||
Name=ytplay (YouTube in mpv)
|
||
Comment=Play YouTube videos in mpv
|
||
Exec=/home/phundrak/.local/bin/ytplay
|
||
Path=/home/phundrak/.local/bin
|
||
Terminal=false
|
||
Categories=Media
|
||
#+END_SRC
|
||
|
||
**** ytdl - a ~youtube-dl~ wrapper
|
||
:PROPERTIES:
|
||
:HEADER-ARGS: :shebang "#!/usr/bin/env fish" :mkdirp yes :tangle ~/.local/bin/ytdl
|
||
:HEADER-ARGS:EMACS-LISP: :exports none :tangle no
|
||
:END:
|
||
This script is a wrapper around ~youtube-dl~ which I use mainly for
|
||
archiving YouTube videos on my NAS (at the time I’m writing this, I
|
||
have already 2.1 TB worth of videos archived). The principle behind
|
||
this script is quite simple: I want to avoid as much as possible to
|
||
redownload any video already downloaded in order to avoid pinging too
|
||
much YouTube’s servers, 429 Too Many Requests errors are really
|
||
annoying, and it comes really early when you have only a couple of new
|
||
videos to download among the few 14k videos already downloaded.
|
||
|
||
Be aware this script was written for the Fish shell (3.1.0 and above), and makes
|
||
use of youtube-dl 2020.03.24 and above, [[https://github.com/jorgebucaran/fish-getopts][Fish getopts]] and [[https://github.com/BurntSushi/ripgrep][ripgrep]].
|
||
|
||
***** Setting default values
|
||
Some variables in this script will have default values, we do not want to have a
|
||
mile-long command each time we wish to download a single video. We’ll also set
|
||
some global variables that won’t change:
|
||
#+NAME: ytdl-default-vars
|
||
| Variable Name | Default Value | String? |
|
||
|------------------+-----------------------------------------------------------+---------|
|
||
| ~YTDL_SHARED_DIR~ | ~$HOME/.local/share/ytdl~ | no |
|
||
| ~FORMAT_DEFAULT~ | ~%(uploader)s/%(upload_date)s ~- ~%(title)s.%(ext)s~ | yes |
|
||
| ~DOWNFILE_DEFAULT~ | ~$YTDL_SHARED_DIR/downloaded~ | no |
|
||
| ~ERRFILE_DEFAULT~ | ~$YTDL_SHARED_DIR/video-errors~ | no |
|
||
| ~LOGFILE_DEFAULT~ | ~$YTDL_SHARED_DIR/ytdl.log~ | no |
|
||
| ~PREFFERED_FORMAT~ | ~bestvideo[ext=mp4]+bestaudio[ext=m4a]/bestvideo+bestaudio~ | yes |
|
||
| ~VERSION~ | ~0.3~ | yes |
|
||
|
||
There is one more default variable pointing to ytdl’s root directory which
|
||
depends on whether the videos directory has a French or English name:
|
||
#+NAME: ytdl-default-vars-root
|
||
#+BEGIN_SRC fish :tangle no
|
||
if test -d "$HOME/Vidéos"
|
||
set -g ROOTDIR_DEFAULT "$HOME/Vidéos" # French name
|
||
else
|
||
set -g ROOTDIR_DEFAULT "$HOME/Videos" # English name
|
||
end
|
||
#+END_SRC
|
||
|
||
#+NAME: ytdl-default-vars-make
|
||
#+header: :wrap "src fish :tangle no"
|
||
#+BEGIN_SRC emacs-lisp :var vars=ytdl-default-vars :exports results
|
||
(let ((clean-string (lambda (str) (replace-regexp-in-string "~" "" str))))
|
||
(mapconcat (lambda (var)
|
||
(let ((varname (funcall clean-string (car var)))
|
||
(varvalue (funcall clean-string (cadr var)))
|
||
(string? (string= (nth 2 var) "yes")))
|
||
(format "set -g %-16s \"%s\"" varname varvalue)))
|
||
vars
|
||
"\n"))
|
||
#+END_SRC
|
||
|
||
#+RESULTS: ytdl-default-vars-make
|
||
#+begin_src fish :tangle no
|
||
set -g YTDL_SHARED_DIR "$HOME/.local/share/ytdl"
|
||
set -g FORMAT_DEFAULT "%(uploader)s/%(upload_date)s - %(title)s.%(ext)s"
|
||
set -g DOWNFILE_DEFAULT "$YTDL_SHARED_DIR/downloaded"
|
||
set -g ERRFILE_DEFAULT "$YTDL_SHARED_DIR/video-errors"
|
||
set -g LOGFILE_DEFAULT "$YTDL_SHARED_DIR/ytdl.log"
|
||
set -g PREFFERED_FORMAT "bestvideo[ext=mp4]+bestaudio[ext=m4a]/bestvideo+bestaudio"
|
||
set -g VERSION "0.3"
|
||
#+end_src
|
||
|
||
#+BEGIN_SRC fish :noweb yes
|
||
<<ytdl-default-vars-make()>>
|
||
<<ytdl-default-vars-root>>
|
||
#+END_SRC
|
||
|
||
We’ll also create the directory pointed at by ~YTDL_SHARED_DIR~ if it doesn’t
|
||
exist already:
|
||
#+BEGIN_SRC fish
|
||
mkdir -p $YTDL_SHARED_DIR
|
||
#+END_SRC
|
||
|
||
***** Help message
|
||
The next step is displaying the help message for the script. For that, just a
|
||
long string echo’d will do, wrapped in the function ~_ytdl_help~.
|
||
#+BEGIN_SRC fish
|
||
function _ytdl_help
|
||
echo "Usage: ytdl [OPTION]... URL [URL]...
|
||
|
||
-4, --ipv4
|
||
Download with forced IPv4
|
||
Default: no
|
||
|
||
-6, --ipv6
|
||
Download with forced IPv6
|
||
Default: no
|
||
|
||
-a, --batch-file <file>
|
||
File containing URLs to download, one URL per line. Lines starting with
|
||
'#', ';' or ']' are considered as comments and ignored.
|
||
Default: None
|
||
|
||
-c, --id-cache <file>
|
||
File containing the video IDs that were already downloaded, one ID per
|
||
line.
|
||
Default: $DOWNFILE_DEFAULT
|
||
|
||
-d, --directory <dir>
|
||
Root directory in which to download videos.
|
||
Default: $ROOTDIR_DEFAULT
|
||
|
||
-e, --error-file <file>
|
||
File containing the IDs of videos that failed to download, one ID per
|
||
line
|
||
Default: $ERRFILE_DEFAULT
|
||
|
||
-f, --format <format>
|
||
Format name for downloaded videos, including path relative to root
|
||
directory
|
||
Default: $FORMAT_DEFAULT
|
||
|
||
-l, --logs <file>
|
||
File in which to store logs.
|
||
Default: $LOGFILE_DEFAULT
|
||
|
||
-V, --verbose
|
||
Show verbose output
|
||
Default: no
|
||
|
||
-v, --version
|
||
Show version of ytdl.
|
||
|
||
-h, --help
|
||
Shows this help message"
|
||
end
|
||
#+END_SRC
|
||
|
||
We also have the function ~_ytdl_version~ to display the current version of
|
||
~ytdl~:
|
||
#+BEGIN_SRC fish
|
||
function _ytdl_version
|
||
echo "ytdl 0.3, developped for fish 3.1.0 and youtube-dl 2020.03.24 or newer"
|
||
echo "requires Fish getopts <https://github.com/jorgebucaran/fish-getopts>"
|
||
echo "and ripgrep <https://github.com/BurntSushi/ripgrep>"
|
||
end
|
||
#+END_SRC
|
||
|
||
***** Arguments Handling
|
||
The function ~_ytdl_parse_ops~ is a little bit trickier: we use ~getopts~ to
|
||
parse the arguments passed to the script in order to get some preferences from
|
||
the user. Here is a quick reference on what options are available and what they
|
||
do:
|
||
#+NAME: ytdl-table-arguments
|
||
| Short | Long | Takes a value? | Associated Variable | Default Value | What it does |
|
||
|-------+------------+----------------+---------------------+-------------------+----------------------|
|
||
| 4 | ipv4 | no | ~IPV4~ | None | Force IPv4 |
|
||
| 6 | ipv6 | no | ~IPV6~ | None | Force IPv6 |
|
||
| a | batch-file | yes | ~FILE~ | None | Batch file |
|
||
| c | cache | yes | ~DOWNFILE~ | ~$DOWNFILE_DEFAULT~ | Cache file |
|
||
| d | directory | yes | ~ROOTDIR~ | ~$ROOTDIR_DEFAULT~ | Root directory |
|
||
| e | error-file | yes | ~ERRFILE~ | ~$ERRFILE_DEFAULT~ | Error logs |
|
||
| f | format | yes | ~FORMAT~ | ~$FORMAT_DEFAULT~ | Filename format |
|
||
| l | logs | yes | ~LOGFILE~ | ~$LOGFILE_DEFAULT~ | Logs |
|
||
| V | verbose | no | ~VERBOSE~ | ~1~ | Verbose output |
|
||
| v | version | command | None | None | Script version |
|
||
| h | help | command | None | None | Display this message |
|
||
|
||
We can also pass individual YouTube URLs without any options or switches
|
||
associated to them, they will be downloaded as part of a single queue.
|
||
|
||
#+NAME: ytdl-arg-handling-gen
|
||
#+header: :cache yes :wrap "src fish :tangle no :exports code"
|
||
#+BEGIN_SRC emacs-lisp :var args=ytdl-table-arguments :exports results
|
||
(let ((clean-string (lambda (str) (replace-regexp-in-string "~" "" str))))
|
||
(mapconcat (lambda (arg)
|
||
(let* ((short (format "%s" (nth 0 arg)))
|
||
(long (nth 1 arg))
|
||
(arg? (string= "yes" (nth 2 arg)))
|
||
(var (unless (string= "None" (nth 3 arg))
|
||
(funcall clean-string (nth 3 arg)))))
|
||
(format "case %s %s\n\t%s"
|
||
short long
|
||
(if var (format "set -g %s %s" var
|
||
(if arg? "$value" ""))
|
||
(format "_ytdl_%s && exit"
|
||
(if (string= "h" short) "help" "version"))))))
|
||
args
|
||
"\n"))
|
||
#+END_SRC
|
||
|
||
#+RESULTS[df04709ff17a3d37a20528a5066a2efcda0cad5c]: ytdl-arg-handling-gen
|
||
#+begin_src fish :tangle no :exports code
|
||
case 4 ipv4
|
||
set -g IPV4
|
||
case 6 ipv6
|
||
set -g IPV6
|
||
case a batch-file
|
||
set -g FILE $value
|
||
case c cache
|
||
set -g DOWNFILE $value
|
||
case d directory
|
||
set -g ROOTDIR $value
|
||
case e error-file
|
||
set -g ERRFILE $value
|
||
case f format
|
||
set -g FORMAT $value
|
||
case l logs
|
||
set -g LOGFILE $value
|
||
case V verbose
|
||
set -g VERBOSE
|
||
case v version
|
||
_ytdl_version && exit
|
||
case h help
|
||
_ytdl_help && exit
|
||
#+end_src
|
||
|
||
The following shows how ~getopts~ is used to catch the options and switches
|
||
passed to the script:
|
||
#+NAME: ytdl-getopts
|
||
#+BEGIN_SRC fish :noweb yes :tangle no
|
||
getopts $argv | while read -l key value
|
||
switch $key
|
||
<<ytdl-arg-handling-gen()>>
|
||
case _
|
||
for v in $value
|
||
set -g VIDEOS $VIDEOS $v
|
||
end
|
||
end
|
||
end
|
||
#+END_SRC
|
||
|
||
#+NAME: ytdl-arg-set-default-value-gen
|
||
#+header: :cache yes :wrap "src fish :tangle no :exports code"
|
||
#+BEGIN_SRC emacs-lisp :var args=ytdl-table-arguments
|
||
(let* ((clean-string (lambda (str) (replace-regexp-in-string "~" "" str)))
|
||
(args (seq-filter (lambda (arg)
|
||
(let* ((var (unless (string= "None" (nth 3 arg))
|
||
(funcall clean-string (nth 3 arg))))
|
||
(default (funcall clean-string (format "%s" (nth 4 arg))))
|
||
(default (unless (string= "None" default)
|
||
default)))
|
||
(and var default)))
|
||
args)))
|
||
(mapconcat (lambda (arg)
|
||
(let* ((var (funcall clean-string (nth 3 arg)))
|
||
(default (funcall clean-string (format "%s" (nth 4 arg)))))
|
||
(format "if set -q $%s\n\tset -g %s %s\nend"
|
||
var var default)))
|
||
args
|
||
"\n"))
|
||
#+END_SRC
|
||
|
||
#+RESULTS[5a32e875ea434a3522bd2272f4e5fd422d722dca]: ytdl-arg-set-default-value-gen
|
||
#+begin_src fish :tangle no :exports code
|
||
if set -q $DOWNFILE
|
||
set -g DOWNFILE $DOWNFILE_DEFAULT
|
||
end
|
||
if set -q $ROOTDIR
|
||
set -g ROOTDIR $ROOTDIR_DEFAULT
|
||
end
|
||
if set -q $ERRFILE
|
||
set -g ERRFILE $ERRFILE_DEFAULT
|
||
end
|
||
if set -q $FORMAT
|
||
set -g FORMAT $FORMAT_DEFAULT
|
||
end
|
||
if set -q $LOGFILE
|
||
set -g LOGFILE $LOGFILE_DEFAULT
|
||
end
|
||
if set -q $VERBOSE
|
||
set -g VERBOSE 1
|
||
end
|
||
#+end_src
|
||
|
||
Some values need to be set to their default, so let’s assign them their value if
|
||
no user value was passed:
|
||
#+NAME: ytdl-arg-set-default-value
|
||
#+BEGIN_SRC fish :noweb yes :tangle no
|
||
<<ytdl-arg-set-default-value-gen()>>
|
||
set -g FORMAT "$ROOTDIR/$FORMAT"
|
||
#+END_SRC
|
||
|
||
Both these code blocks are executed in ~_ytdl_parse_ops~:
|
||
#+BEGIN_SRC fish :noweb yes
|
||
function _ytdl_parse_ops
|
||
<<ytdl-getopts>>
|
||
<<ytdl-arg-set-default-value>>
|
||
end
|
||
#+END_SRC
|
||
|
||
***** Logging
|
||
~_ytdl_log~ is a very simple function used for logging information for the user
|
||
in the file pointed to by ~LOGFILE~. The first argument the function should
|
||
receive is its log level. I generally use either ~"INFO"~ or ~"ERR"~. The second
|
||
argument is the message to log.
|
||
#+BEGIN_SRC fish
|
||
function _ytdl_log
|
||
set -l INFOLEVEL $argv[1]
|
||
set -l MSG $argv[2]
|
||
set -l LOG (printf "[%s] %s %s\n" $INFOLEVEL (date +"%F %T") $MSG)
|
||
printf "%s\n" $LOG >> $LOGFILE
|
||
if test $VERBOSE -eq 1
|
||
echo $LOG
|
||
end
|
||
end
|
||
#+END_SRC
|
||
|
||
***** Download a Single Video
|
||
In order to download a single video, a simple function has been
|
||
written for this that will display when downloaded how far it is down
|
||
the list of videos to be downloaded, and it will add its ID to the
|
||
file listing all videos downloaded. The script will also try to
|
||
download the video according to the ~PREFFERED_FORMAT~ variable, but if
|
||
the download fails it will download the default format selected by
|
||
~youtube-dl~. If both downloads fail, the ID of the video will be added
|
||
to the list of failed videos. If one of the downloads succeeds, it
|
||
will remove the ID from the list of failed downloads.
|
||
|
||
The first argument of the function is the video ID from YouTube, the second
|
||
argument is the position of the video in the queue, and the third argument is
|
||
the queue length –can be the amount of videos in a whole YouTube channel, the
|
||
amount of videos in a playlist, or simply the amount of YouTube URLs passed as
|
||
arguments to the script.
|
||
#+BEGIN_SRC fish
|
||
function _ytdl_download_video
|
||
set ID $argv[1]
|
||
_ytdl_log "INFO" (printf "Downloading video with ID $ID (%4d/%4d)" $argv[2] $argv[3])
|
||
if youtube-dl -f $PREFFERED_FORMAT -ciw -o $FORMAT "https://youtube.com/watch?v=$ID"
|
||
echo $ID >> $DOWNFILE
|
||
else if youtube-dl -ciw -o $FORMAT "https://youtube.com/watch?v=$ID"
|
||
echo $ID >> $DOWNFILE
|
||
else
|
||
_ytdl_log "ERR" "Could not download $VIDEO"
|
||
echo $ID >> $ERRFILE
|
||
end
|
||
end
|
||
#+END_SRC
|
||
/Note that this function is not meant to be called without any checks before./
|
||
It is meant to be called by ~_ytdl_download_queue~ described below.
|
||
|
||
***** Download a Queue of Videos
|
||
One of the main goals of this tool is to check if a video has already been
|
||
downloaded. This is why, as you will see below, we use ripgrep to check if the
|
||
ID of the video we want to download is already present in the list of downloaded
|
||
videos. If not, it will then be downloaded though ~_ytdl_download_video~
|
||
described above.
|
||
#+BEGIN_SRC fish
|
||
function _ytdl_download_queue
|
||
for i in (seq (count $argv))
|
||
rg -- $argv[$i] $DOWNFILE 2&> /dev/null
|
||
if test $status -ne 0
|
||
_ytdl_download_video $argv[$i] $i (count $argv)
|
||
end
|
||
end
|
||
end
|
||
#+END_SRC
|
||
|
||
***** Download Videos From Arguments
|
||
The main aim of this function is to transform the URLs contained in the
|
||
arguments passed to the script to a list of IDs usable later on by ~ytdl~.
|
||
#+BEGIN_SRC fish
|
||
function _ytdl_download_arg_urls
|
||
set -g IDs
|
||
for VIDEO in $argv
|
||
_ytdl_log "Info" "Getting video ID for $VIDEO"
|
||
set -g IDs $IDs (youtube-dl --get-id $VIDEO)
|
||
end
|
||
_ytdl_download_queue $IDs
|
||
end
|
||
#+END_SRC
|
||
|
||
***** Download Videos From a Batch File
|
||
The final function to declare before the main body of the script is
|
||
~_ytdl_download_batch~: it will look for each line, ignoring the ones beginning
|
||
by ~#~, ~;~ and ~]~ (just like ~youtube-dl~) and will download them, assuming
|
||
these are channel URLs or playlist URLs, however it should also work with direct
|
||
video URLs.
|
||
|
||
What this function does is for each line, it will fetch the entirety
|
||
of the video IDs found in a playlist or channel. Then, it will look
|
||
each ID up the list of already downloaded videos and will add all new
|
||
IDs to a queue of videos to be downloaded. It will then pass each new
|
||
video ID to ~_ytdl_download_video~ directly. Beware that if you pass
|
||
directly the URL of the channel, such as
|
||
~https://www.youtube.com/user/enyay~ if you want to download Tom Scott’s
|
||
videos, it will download everything on the main page of their channel,
|
||
which means it will even download videos from playlists they decided
|
||
to put on their channel’s front page, even if it is not theirs. So in
|
||
that case, we need to append ~/videos~ to any channel URL.
|
||
#+BEGIN_SRC fish
|
||
function _ytdl_download_batch
|
||
set -q $FILE
|
||
if test $status -eq 1
|
||
set -g NEW
|
||
set CHANNELS (cat $FILE | grep -vE "#|;|\]")
|
||
for c in $CHANNELS
|
||
_ytdl_log "INFO" "Getting IDs for channel $c"
|
||
if test (egrep '\/c\/|user|channel' (echo $c |psub))
|
||
set -g IDS (youtube-dl --get-id "$c/videos")
|
||
else
|
||
set -g IDS (youtube-dl --get-id $c)
|
||
end
|
||
_ytdl_log "INFO" "Fetching new videos from channel"
|
||
for i in (seq (count $IDS))
|
||
printf "\rsearching (%d/%d)" $IDn (count $IDS)
|
||
rg -- $IDS[$i] $DOWNFILE 2&> /dev/null
|
||
if test $status -ne 0
|
||
set -g NEW $IDS[$i] $NEW
|
||
end
|
||
end
|
||
printf "\n"
|
||
end
|
||
for i in (seq (count $NEW))
|
||
_ytdl_download_video $NEW[$i] $i (count $NEW)
|
||
end
|
||
end
|
||
end
|
||
#+END_SRC
|
||
|
||
***** Main Body
|
||
Now that we have all our functions declared, let’s call them! First, we need to
|
||
parse our arguments. We’ll then download all files passed as arguments. Finally,
|
||
we’ll download videos, playlists and channels specified from a batch file.
|
||
#+BEGIN_SRC fish
|
||
_ytdl_parse_ops $argv
|
||
_ytdl_download_arg_urls $VIDEOS
|
||
_ytdl_download_batch
|
||
#+END_SRC
|
||
|
||
And that’s all! If you’re interested with a very simple interface for
|
||
downloading one video once, I wrote a small [[file:./scripts.md#rofi-ytdl][=rofi-ytdl=]] script that
|
||
calls the ~rofi~ utility to specify a single link and download it.
|
||
|
||
** Misc
|
||
*** Broadway
|
||
:PROPERTIES:
|
||
:HEADER-ARGS: :shebang "#!/usr/bin/env sh" :mkdirp yes :tangle ~/.local/bin/broadway
|
||
:END:
|
||
This simple script launches broadwayd, a utility that renders GTK
|
||
applications as web apps, and a program displayed to broadway
|
||
directly.
|
||
#+begin_src sh
|
||
export display_screen=:5
|
||
broadwayd $display_screen &
|
||
GDK_BACKEND=broadway BROADWAY_DISPLAY=$display_screen "$@"
|
||
#+end_src
|
||
|
||
But let’s cut the middleman for most of my uses of Broadway.
|
||
#+begin_src sh :tangle ~/.local/bin/emacs-web
|
||
export display_screen=:5
|
||
broadwayd $display_screen &
|
||
GDK_BACKEND=broadway BROADWAY_DISPLAY=$display_screen emacs
|
||
#+end_src
|
||
|
||
** Plock
|
||
:PROPERTIES:
|
||
:HEADER-ARGS: :shebang "#!/usr/bin/env sh" :mkdirp yes :tangle ~/.local/bin/plock
|
||
:END:
|
||
~plock~ is a simple script that locks the screen with ~i3lock~ while setting as
|
||
the background image of the locked screen a corrupted screenshot of the screen
|
||
before it was locked.
|
||
#+BEGIN_SRC sh
|
||
TMPBG="/tmp/screen.png"
|
||
if [ "$XDG_SESSION_TYPE" = "wayland" ]
|
||
then
|
||
SCREENER=grim
|
||
LOCKER="swaylock -feF"
|
||
else
|
||
SCREENER=scrot
|
||
LOCKER="i3lock -ef"
|
||
fi
|
||
|
||
$SCREENER "$TMPBG"
|
||
corrupter -add 0 "$TMPBG" "$TMPBG"
|
||
$LOCKER -ti "$TMPBG"
|
||
rm $TMPBG
|
||
#+END_SRC
|
||
|
||
** Polybar-launch (Deprecated) :noexport:
|
||
:PROPERTIES:
|
||
:HEADER-ARGS: :shebang "#!/usr/bin/env bash" :mkdirp yes :tangle ~/.local/bin/polybar-launch
|
||
:END:
|
||
This scripts allows the user to kill polybar and relaunch it, or to simply
|
||
launch it if polybar isn’t launched yet. First thing to do is kill all polybar
|
||
processes.
|
||
#+BEGIN_SRC bash
|
||
killall -q polybar
|
||
#+END_SRC
|
||
|
||
Now we have to wait untill all polybar processes have been shut down.
|
||
#+BEGIN_SRC bash
|
||
while pgrep -u $UID -x polybar >/dev/null; do sleep 1; done
|
||
#+END_SRC
|
||
|
||
Now that our system isn’t running polybar anymore, we’ll launch it again on all
|
||
of our screens. By the way, I have two bars, so I’ll have to lauch them both.
|
||
#+BEGIN_SRC bash
|
||
if type "xrandr"; then
|
||
for m in $(xrandr --query | grep " connected" | cut -d" " -f1); do
|
||
MONITOR=$m polybar --reload top &
|
||
MONITOR=$m polybar --reload bottom &
|
||
done
|
||
else
|
||
polybar --reload top &
|
||
polybar --reload bottom &
|
||
fi
|
||
#+END_SRC
|
||
|
||
And we’re done! Let’s just launch a notification polybar has been relaunched.
|
||
#+BEGIN_SRC bash
|
||
notify-send "Polybar restarted!" -a "polybar-launch"
|
||
#+END_SRC
|
||
|
||
** Rofi utilities
|
||
*** askpass
|
||
:PROPERTIES:
|
||
:HEADER-ARGS: :shebang "#!/usr/bin/env fish" :mkdirp yes :tangle ~/.local/bin/askpass
|
||
:END:
|
||
Askpass is a simple script that invokes ~rofi~ as a way to get from a GUI the
|
||
user’s sudo password. It is inspired by [[https://github.com/ODEX-TOS/tools/blob/master/rofi/askpass][this original tool]], rewritten in fish
|
||
and with [[https://wiki.archlinux.org/index.php/Rofi][rofi]] support instead of [[https://wiki.archlinux.org/index.php/Dmenu][dmenu]]. As you can see, this is a oneliner if we
|
||
ignore the initial shebang. This executable is pointed at by the
|
||
#+BEGIN_SRC fish
|
||
rofi -dmenu -password -no-fixed-num-lines -p (printf $argv[1] | sed s/://)
|
||
#+END_SRC
|
||
|
||
*** awiki
|
||
:PROPERTIES:
|
||
:HEADER-ARGS: :shebang "#!/usr/bin/env fish" :mkdirp yes :tangle ~/.local/bin/awiki
|
||
:END:
|
||
~awiki~ is a simple script used with ~rofi~ that relies on the ~arch-wiki-docs~
|
||
package in order to provide the user a way to quickly find and display any
|
||
English page from the Arch Wiki in a browser. The advantage of using this over
|
||
the ~wiki-search~ utility from the ~arch-wiki-lite~ package is you get instant
|
||
suggestion in rofi using fuzzy-search. The downside is rofi will only help you
|
||
find pages by their title, and it will not help you find keywords in the content
|
||
of said pages.
|
||
|
||
The first step is to create the list of all the pages that are currently stored
|
||
on disk. ~arch-wiki-docs~ stores them in ~/usr/share/doc/arch-wiki/html/en~. A
|
||
simple ~ls~ piped in three ~sed~ will give us a list of page titles. We then
|
||
pipe that into rofi in dmenu mode in order to choose the page we want to
|
||
display. By the way, setting the location of the HTML files will come in handy
|
||
later.
|
||
#+BEGIN_SRC fish
|
||
set WLOCATION /usr/share/doc/arch-wiki/html/en/
|
||
set WPAGE (/bin/ls $WLOCATION | \
|
||
sed -e 's/_/ /g' -e 's/\.html$//' -e 's|.*/\(.*\)|\1|' | \
|
||
rofi -dmenu -p "Arch Wiki" -i)
|
||
set WPAGE (echo $WPAGE | sed -r 's/\s+/_/g')
|
||
#+END_SRC
|
||
|
||
Now, all I need to do is to send this list into rofi and tell it to open the
|
||
result with our favorite browser with ~xdg-open~.
|
||
#+BEGIN_SRC fish
|
||
xdg-open $WLOCATION$WPAGE.html
|
||
#+END_SRC
|
||
|
||
*** ConnectWifi :noexport:
|
||
:PROPERTIES:
|
||
:HEADER-ARGS: :shebang "#!/usr/bin/env fish" :mkdirp yes :tangle ~/.local/bin/connect-wifi
|
||
:END:
|
||
~connect-wifi~ is a small utility tool that allows the user to connect to
|
||
available WiFi networks. The first thing to do is to select the WiFi we want to
|
||
connect to. We’ll use the ~nmcli c s~ command to get the list of the available
|
||
networks, and we’ll chose one with ~rofi~.
|
||
#+BEGIN_SRC fish
|
||
set SELECTEDWIFI (nmcli d w l | \
|
||
egrep -o '([0-9A-F]{2}:){5}[0-9A-F]{2}\s*(.*)Infra' | \
|
||
egrep -o '\s+(.*)\s+' | awk '{$1=$1}1' | \
|
||
rofi -dmenu -p "Select your WiFi network")
|
||
#+END_SRC
|
||
Now, if a network was selected, let’s attempt to connect to it. Otherwise, let’s
|
||
just send a notification no network was selected.
|
||
#+BEGIN_SRC fish
|
||
if test -z $SELECTEDWIFI
|
||
notify-send "No WiFi network selected" -u low && exit
|
||
end
|
||
nmcli c u $SELECTEDWIFI
|
||
#+END_SRC
|
||
|
||
**** TODO fix it
|
||
|
||
*** dmenu
|
||
:PROPERTIES:
|
||
:HEADER-ARGS: :shebang "#!/usr/bin/env fish" :mkdirp yes :tangle ~/.local/bin/dmenu
|
||
:END:
|
||
I wrote this very simple script in order to replace =dmenu= with rofi’s
|
||
emulation of dmenu, since I prefer rofi’s appearance. It basically calls rofi’s
|
||
dmenu emulation with the arguments initially passed to dmenu.
|
||
#+BEGIN_SRC fish
|
||
rofi -dmenu $argv
|
||
#+END_SRC
|
||
|
||
*** Emoji picker
|
||
:PROPERTIES:
|
||
:HEADER-ARGS: :shebang "#!/usr/bin/env bash" :mkdirp yes :tangle ~/.local/bin/rofi-emoji
|
||
:END:
|
||
The emoji picker is a simple fish script that uses rofi and
|
||
[[file:~/.config/emoji.txt][~/.config/emoji.txt]] to provide a small, local search for emojis. It is
|
||
relatively easy to build this file from the Unicode’s [[https://unicode.org/Public/emoji/15.0/emoji-test.txt][test file]]. Once
|
||
the emoji is selected, it is copied to the clipboard using =xclipboard=
|
||
when in a Xorg session or =wl-copy= from =wl-clipboard= when in a Wayland
|
||
session.
|
||
#+BEGIN_SRC bash
|
||
SELECTED_EMOJI=$(grep -v "#" ~/.config/emoji.txt | rofi -dmenu -p "Select emoji" -i | awk '{print $1}' | tr -d '\n')
|
||
if [ "$XDG_SESSION_TYPE" = "wayland" ]
|
||
then printf "%s" "$SELECTED_EMOJI" | wl-copy
|
||
else printf "%s" "$SELECTED_EMOJI" | xclip -sel clip
|
||
fi
|
||
#+END_SRC
|
||
|
||
Also, let’s send a notification telling the user the emoji has been copied!
|
||
#+BEGIN_SRC bash
|
||
# EMOJI=$([ "$XDG_SESSION_TYPE" = "wayland" ] && xclip -o -selection clipboard || wl-paste)
|
||
if [ "$XDG_SESSION_TYPE" = "wayland" ]
|
||
then EMOJI=$(wl-paste)
|
||
else EMOJI=$(xclip -o)
|
||
fi
|
||
|
||
# EMOJI=$(xclip -o -selection clipboard | tr -d '\n')
|
||
test -z "$EMOJI" && notify-send "No emoji copied" -u low && exit
|
||
EMOJI="$EMOJI copied to clipboard"
|
||
notify-send -u low "$EMOJI"
|
||
#+END_SRC
|
||
|
||
It is inspired from [[https://www.youtube.com/watch?v=UCEXY46t3OA][this video]] from [[https://lukesmith.xyz/][Luke Smith]], rewritten in Fish.
|
||
|
||
*** Partition mounting and unmounting
|
||
**** rofi-mount
|
||
:PROPERTIES:
|
||
:HEADER-ARGS: :shebang "#!/usr/bin/env fish" :mkdirp yes :tangle ~/.local/bin/rofi-mount
|
||
:END:
|
||
=rofimount= is a script inspired by [[https://github.com/ihebchagra/dotfiles/blob/master/.local/bin/dmount][this one]], based on dmenu, which
|
||
interactively asks the user what to mount, and where to mount it. What I did was
|
||
replace dmenu with rofi, and fix a couple of bugs I encountered in the original
|
||
script.
|
||
|
||
***** Get the mountable elements
|
||
#+BEGIN_SRC fish
|
||
begin
|
||
#+END_SRC
|
||
|
||
What the script does first is detect everything that can be mounted. Between a
|
||
=begin= and =end=, let’s set =LFS= as a local variable. This is in order to get
|
||
sane variables in the current block.
|
||
#+BEGIN_SRC fish
|
||
set -l LFS
|
||
#+END_SRC
|
||
|
||
Now, let’s detect the amount of mountable Android filesystems, and if any are
|
||
detected, let’s read them into a global variable.
|
||
#+BEGIN_SRC fish
|
||
set -l a (math (jmtpfs -l | wc -l) - 2)
|
||
test $a -ge 0 && jmtpfs -l 2> /dev/null | tail -n $a | read -zg anddrives
|
||
#+END_SRC
|
||
|
||
We’ll do the same for external and internal drives and partitions that can be
|
||
mounted here.
|
||
#+BEGIN_SRC fish
|
||
lsblk -rpo "name,type,size,mountpoint" | \
|
||
awk '$2=="part"&&$4==""{printf "%s (%s)\n",$1,$3}' | \
|
||
read -zg usbdrives
|
||
#+END_SRC
|
||
|
||
Finally, we look for any CD drive that could be mounted on our device.
|
||
#+BEGIN_SRC fish
|
||
blkid /dev/sr0 | awk '{print $1}' | sed 's/://' | read -z cddrives
|
||
#+END_SRC
|
||
|
||
And that’s the end of our first block!
|
||
#+BEGIN_SRC fish
|
||
end
|
||
#+END_SRC
|
||
|
||
Alright, we’ll save what kind on drives we can mount in a temporary
|
||
file called =/tmp/drives=. We’ll make sure it’s blank by erasing it then
|
||
creating it again with =touch=, like so. The =-f= flag on =rm= is here, so
|
||
we get no error if we try to delete a file that doesn’t exist (yet).
|
||
#+BEGIN_SRC fish
|
||
set -g TMPDRIVES /tmp/drives
|
||
rm -f $TMPDRIVES
|
||
touch $TMPDRIVES
|
||
#+END_SRC
|
||
|
||
Now, let’s write what type of drives we can mount in this temporary file.
|
||
#+BEGIN_SRC fish
|
||
test -n "$usbdrives" && echo "USB" >> $TMPDRIVES
|
||
test -n "$cddrives" && echo "CD" >> $TMPDRIVES
|
||
test -n "$anddrives" && echo "Android" >> $TMPDRIVES
|
||
#+END_SRC
|
||
|
||
Now, we want to declare where to look for mount directories. For now, we’ll only
|
||
look in =/media=, but you can add more if you wish.
|
||
#+BEGIN_SRC fish
|
||
set -g basemount /media
|
||
#+END_SRC
|
||
|
||
***** Get the mount point
|
||
Now, let’s declare a function that will allow us to choose the drive
|
||
we want to mount.
|
||
#+BEGIN_SRC fish
|
||
function getmount
|
||
#+END_SRC
|
||
|
||
First, we want to get our mount point. We’ll run a =find= command on
|
||
each of the directories listed in =$basemount= to look for folders on
|
||
which our drive could be mounted. This list will be passed to rofi
|
||
from which we will choose our mount point.
|
||
#+BEGIN_SRC fish
|
||
set -g mp (for d in $basemount
|
||
find $d -maxdepth 5 -type d
|
||
end | rofi -dmenu -i -p 'Type in mount point.')
|
||
#+END_SRC
|
||
|
||
We should verify that something has been actually selected, otherwise we should
|
||
abort the script.
|
||
#+BEGIN_SRC fish
|
||
if test -z $mp || test $mp = ""
|
||
return 1
|
||
end
|
||
#+END_SRC
|
||
|
||
Now, if the selected mount point does not exist, we’ll ask the user whether the
|
||
directory should be created. If no, the script will abort. If yes, an attempt
|
||
will be made at creating the directory as the user; if that fails, a new attempt
|
||
will be made as sudo.
|
||
#+BEGIN_SRC fish
|
||
if test ! -d $mp
|
||
switch (printf "No\\nYes" | rofi -dmenu -i -p "$mp does not exist. Create it?")
|
||
case 'Yes'
|
||
mkdir -p $mp || sudo -A mkdir -p $mp
|
||
case '*'
|
||
return 1
|
||
end
|
||
end
|
||
#+END_SRC
|
||
|
||
Finally, let’s close the function
|
||
#+BEGIN_SRC fish
|
||
end
|
||
#+END_SRC
|
||
|
||
***** Mount a USB drive, hard drive or partition
|
||
Alright, we want to mount a partition that answers by the name of =/dev/sdXX=,
|
||
how do we do that? Let’s create first the function =mountusb= that will take
|
||
care of it for us.
|
||
#+BEGIN_SRC fish
|
||
function mountusb
|
||
#+END_SRC
|
||
|
||
Now, the first thing we want to do is select the partition we want to
|
||
mount. Remember, we stored those in =$usbdrives= earlier, so let’s pipe
|
||
them into rofi, so we can choose from it. Also, =awk= will get their
|
||
path in =/dev=.
|
||
#+BEGIN_SRC fish
|
||
set -g chosen (echo $usbdrives | \
|
||
rofi -dmenu -i -p "Mount which drive?" | \
|
||
awk '{print $1}')
|
||
#+END_SRC
|
||
|
||
As usual after a user selection, let’s verify something has actually been
|
||
selected. If not, let’s abort the script.
|
||
#+BEGIN_SRC fish
|
||
test -z $chosen && return 1
|
||
#+END_SRC
|
||
|
||
Now, let’s select the mount point of our partition. We’ll call the
|
||
function =getmount= described in [[file:./scripts.md#get-the-mount-point][Get the mount point]] to select it.
|
||
#+BEGIN_SRC fish
|
||
getmount
|
||
#+END_SRC
|
||
|
||
Let’s verify the variable =mp= set in =getmount= is not empty, otherwise abort
|
||
the script.
|
||
#+BEGIN_SRC fish
|
||
test -z $mp && return 1
|
||
#+END_SRC
|
||
|
||
Now, let’s mount it! We’ll use a switch which will detect the
|
||
filesystem used, so we know how to mount the partition.
|
||
#+BEGIN_SRC fish
|
||
switch (lsblk -no "fstype" $chosen)
|
||
#+END_SRC
|
||
|
||
We have two named case: =vfat= filesystems.
|
||
#+BEGIN_SRC fish
|
||
case "vfat"
|
||
sudo -A mount -t vfat $chosen $mp -o rw,umask=0000
|
||
#+END_SRC
|
||
|
||
And =ntfs= filesystems.
|
||
#+BEGIN_SRC fish
|
||
case "ntfs"
|
||
sudo -A mount -t ntfs $chosen $mp -o rw,umask=0000
|
||
#+END_SRC
|
||
|
||
Else, we’ll let =mount= determine which filesystem is used by the partition
|
||
(generally =ext4=).
|
||
#+BEGIN_SRC fish
|
||
case '*'
|
||
sudo -A mount $chosen $mp
|
||
#+END_SRC
|
||
|
||
We’ll also run a =chown= on this newly mounted filesystem, so the user
|
||
can access it without any issues.
|
||
#+BEGIN_SRC fish
|
||
sudo -A chown -R $USER:(id -g $USER) $mp
|
||
#+END_SRC
|
||
|
||
Let’s close the switch block and send a notification the partition has been
|
||
mounted.
|
||
#+BEGIN_SRC fish
|
||
end && notify-send -a "dmount" "💻 USB mounting" "$chosen mounted to $mp."
|
||
#+END_SRC
|
||
|
||
And let’s close the function.
|
||
#+BEGIN_SRC fish
|
||
end
|
||
#+END_SRC
|
||
|
||
***** Mount an Android device
|
||
The function that manages to mount Android filesystems is =mountandroid=. Let’s
|
||
declare it.
|
||
#+BEGIN_SRC fish
|
||
function mountandroid -d "Mount an Android device"
|
||
#+END_SRC
|
||
|
||
We’ll select which Android we want to mount. We will be asked through rofi.
|
||
#+BEGIN_SRC fish
|
||
set chosen (echo $anddrives | rofi -dmenu -i -p "Which Android device?" | awk '{print $1 $2}' | sed 's/,$//')
|
||
#+END_SRC
|
||
|
||
Now, we need to get the bus of the Android device we want to mount. It will be
|
||
useful later, after we authorized mounting from our device, to get the path to
|
||
our partition.
|
||
#+BEGIN_SRC fish
|
||
set bus (echo $chosen | sed 's/,.*//')
|
||
#+END_SRC
|
||
|
||
Let’s temporarily mount our device.
|
||
#+BEGIN_SRC fish
|
||
jmtpfs -device=$chosen $mp
|
||
#+END_SRC
|
||
|
||
Now, we need to allow our computer to mount our Android device.
|
||
Depending on the Android version it is running on, we either need to
|
||
specify our device is USB connected in order to exchange files, or
|
||
Android will explicitly ask us if it is OK for our computer to access
|
||
it. Let’s inform the user of that.
|
||
#+BEGIN_SRC fish
|
||
echo "OK" | \
|
||
rofi -dmenu -i -p "Press (Allow) on your phone screen, or set your USB settings to allow file transfer"
|
||
#+END_SRC
|
||
|
||
Now, let’s get the actual path of our Android filesystem we wish to mount, and
|
||
let’s unmount the previous temporary filesystem.
|
||
#+BEGIN_SRC fish
|
||
set newchosen (jmtpfs -l | grep $bus | awk '{print $1 $2}' | sed 's/,$//')
|
||
sudo -A umount $mp
|
||
#+END_SRC
|
||
|
||
Now we can mount the new filesystem and send a notification if everything went
|
||
well.
|
||
#+BEGIN_SRC fish
|
||
jmtpfs -device=$newchosen $mp && \
|
||
notify-send -a "dmount" "🤖 Android Mounting" "Android device mounted to $mp."
|
||
#+END_SRC
|
||
|
||
And now, we can close our function.
|
||
#+BEGIN_SRC fish
|
||
end
|
||
#+END_SRC
|
||
|
||
***** Mount a CD drive
|
||
This part is way easier than the previous functions. As we will see, the
|
||
function =mountcd='s body is only three lines long. First, let’s declare the
|
||
function.
|
||
#+BEGIN_SRC fish
|
||
function mountcd
|
||
#+END_SRC
|
||
|
||
Now, let’s chose the CD drive we want to mount using =rofi=.
|
||
#+BEGIN_SRC fish
|
||
set chosen (echo $cddrives | rofi -dmenu -i -p "Which CD drive?")
|
||
#+END_SRC
|
||
|
||
We’ll also get the mountpoint thanks to the =getmount= function described
|
||
earlier.
|
||
#+BEGIN_SRC fish
|
||
getmount
|
||
#+END_SRC
|
||
|
||
And finally, let’s mount it and send the notification everything went well.
|
||
#+BEGIN_SRC fish
|
||
sudo -A mount $chosen $mp && \
|
||
notify-send -a "dmount" "💿 CD mounting" "$chosen mounted."
|
||
#+END_SRC
|
||
|
||
Finally, let’s close our function.
|
||
#+BEGIN_SRC fish
|
||
end
|
||
#+END_SRC
|
||
|
||
***** Ask what type of drive we want to mount
|
||
The first thing we will be asked if different types of drives are
|
||
detected is which of these types the user wishes to mount. This is
|
||
done with the function =asktype= which is declared below.
|
||
#+BEGIN_SRC fish
|
||
function asktype
|
||
#+END_SRC
|
||
|
||
We will use a switch statement which will use our answer to rofi about
|
||
what we wish to mount.
|
||
#+BEGIN_SRC fish
|
||
switch (cat $TMPDRIVES | rofi -dmenu -i -p "Mount which drive?")
|
||
#+END_SRC
|
||
|
||
If we chose the option "USB", we’ll mount a hard drive, partition or USB drive.
|
||
In which case we’ll call the =mountusb= function.
|
||
#+BEGIN_SRC fish
|
||
case "USB"
|
||
mountusb
|
||
#+END_SRC
|
||
|
||
If we chose the "Android" option, the =mountandroid= function is called.
|
||
#+BEGIN_SRC fish
|
||
case "Android"
|
||
mountandroid
|
||
#+END_SRC
|
||
|
||
Else if we chose the "CD" option, we’ll call the =mountcd= function.
|
||
#+BEGIN_SRC fish
|
||
case "CD"
|
||
mountcd
|
||
#+END_SRC
|
||
|
||
If nothing is selected, the function will naturally exit. Now, let’s close our
|
||
switch statement and our function.
|
||
#+BEGIN_SRC fish
|
||
end
|
||
end
|
||
#+END_SRC
|
||
|
||
***** Launch the mounting functions
|
||
Now that we have declared our functions and set our variables, we’ll
|
||
read the temporary file described in [[file:./scripts.md#get-the-mountable-elements][Get the mountable elements]]. The
|
||
amount of lines is passed in a switch statement.
|
||
#+BEGIN_SRC fish
|
||
switch (wc -l < $TMPDRIVES)
|
||
#+END_SRC
|
||
|
||
If the file has no lines, i.e. it is empty, we have no mountable media. Let’s
|
||
inform our user this is the case.
|
||
#+BEGIN_SRC fish
|
||
case 0
|
||
notify-send "No USB drive or Android device or CD detected" -a "dmount"
|
||
#+END_SRC
|
||
|
||
If we only have one line, we have only one type of mountable media. We’ll pass
|
||
this line to a second switch statement.
|
||
#+BEGIN_SRC fish
|
||
case 1
|
||
switch (cat $TMPDRIVES)
|
||
#+END_SRC
|
||
This will allow the script to automatically detect what type of media it is, and
|
||
mount the corresponding function.
|
||
#+BEGIN_SRC fish
|
||
case "USB"
|
||
mountusb
|
||
case "Android"
|
||
mountandroid
|
||
case "CD"
|
||
mountCD
|
||
#+END_SRC
|
||
Let’s close this nested switch case.
|
||
#+BEGIN_SRC fish
|
||
end
|
||
#+END_SRC
|
||
|
||
If we have more than one line, we’ll have to ask the user what type of media
|
||
they want to mount.
|
||
#+BEGIN_SRC fish
|
||
case '*'
|
||
asktype
|
||
#+END_SRC
|
||
|
||
Now, let’s end our switch statement!
|
||
#+BEGIN_SRC fish
|
||
end
|
||
#+END_SRC
|
||
|
||
Finally, we’ll delete our temporary file.
|
||
#+BEGIN_SRC fish
|
||
rm -f $TMPDRIVES
|
||
#+END_SRC
|
||
|
||
And with that, this is the end of our script!
|
||
|
||
**** rofi-umount
|
||
:PROPERTIES:
|
||
:HEADER-ARGS: :shebang "#!/usr/bin/env fish" :mkdirp yes :tangle ~/.local/bin/rofi-umount
|
||
:END:
|
||
~rofiumount~ is the counterpart of ~rofimount~ for unmounting our mounted
|
||
partitions.
|
||
|
||
***** Get the unmountable drives
|
||
First, we will need to list all the drives that can be safely unmounted. Let’s
|
||
run this.
|
||
#+BEGIN_SRC fish
|
||
set -g drives (lsblk -nrpo "name,type,size,mountpoint" | \
|
||
awk '$2=="part"&&$4!~/\/boot|\/home$|SWAP/&&length($4)>1{printf "%s (%s)\n",$4,$3}')
|
||
#+END_SRC
|
||
|
||
Now, let’s get the android devices that are mounted.
|
||
#+BEGIN_SRC fish
|
||
set -g androids (awk '/jmtpfs/ {print $2}' /etc/mtab)
|
||
#+END_SRC
|
||
|
||
And let’s get the CD drives that are mounted.
|
||
#+BEGIN_SRC fish
|
||
set -g cds (awk '/sr0/ {print $2}' /etc/mtab)
|
||
#+END_SRC
|
||
|
||
We’ll store all of our information in a temporary file, =/tmp/undrives=.
|
||
#+BEGIN_SRC fish
|
||
set -g undrivefile /tmp/undrives
|
||
#+END_SRC
|
||
|
||
Let’s make sure we begin with a clean, empty file.
|
||
#+BEGIN_SRC fish
|
||
rm -f $undrivefile
|
||
touch $undrivefile
|
||
#+END_SRC
|
||
|
||
Depending on if the related variables are set, write the different types of
|
||
mounted drives in the temporary file.
|
||
#+BEGIN_SRC fish
|
||
test -n "$drives" && echo "USB" >> $undrivefile
|
||
test -n "$cds" && echo "CD" >> $undrivefile
|
||
test -n "$androids" && echo "Android" >> $undrivefile
|
||
#+END_SRC
|
||
|
||
***** Unmount disk partitions
|
||
The function =unmountusb= will take care of unmounting any drive we can safely
|
||
unmount. First, let’s declare the function.
|
||
#+BEGIN_SRC fish
|
||
function unmountusb
|
||
#+END_SRC
|
||
|
||
Let’s chose the drive to unmount with rofi.
|
||
#+BEGIN_SRC fish
|
||
set chosen (echo $drives | \
|
||
rofi -dmenu -i -p "Unmount which drive?" | \
|
||
awk '{print $1}')
|
||
#+END_SRC
|
||
|
||
Let’s verify if the user actually selected any drive. If no, let’s abort the
|
||
script.
|
||
#+BEGIN_SRC fish
|
||
test -z "$chosen" && exit 0
|
||
#+END_SRC
|
||
|
||
Now, let’s unmount the chosen drive and send a notification if it has been done.
|
||
#+BEGIN_SRC fish
|
||
sudo -A umount $chosen && \
|
||
notify-send "💻 USB unmounting" "$chosen unmounted." -a "dumount"
|
||
#+END_SRC
|
||
|
||
Now, let’s close the function.
|
||
#+BEGIN_SRC fish
|
||
end
|
||
#+END_SRC
|
||
|
||
***** Unmount Android device
|
||
The function =unmountandroid= will take care of unmounting any mounted Android
|
||
device. First, let’s declare our function.
|
||
#+BEGIN_SRC fish
|
||
function unmountandroid
|
||
#+END_SRC
|
||
|
||
Let the user choose which Android device to unmount.
|
||
#+BEGIN_SRC fish
|
||
set chosen (echo $androids | rofi -dmenu -i -p "Unmount which device?")
|
||
#+END_SRC
|
||
|
||
We’ll verify the user chose any device.
|
||
#+BEGIN_SRC fish
|
||
test -z "$chosen" && exit 0
|
||
#+END_SRC
|
||
|
||
If a device has been chosen, let’s unmount it and send a notification it has
|
||
been successfuly unmounted.
|
||
#+BEGIN_SRC fish
|
||
sudo -A umount -l $chosen && \
|
||
notify-send "🤖 Android unmounting" "$chosen unmounted." -a "dumount"
|
||
#+END_SRC
|
||
|
||
Finally, let’s close the function.
|
||
#+BEGIN_SRC fish
|
||
end
|
||
#+END_SRC
|
||
|
||
***** Unmount CD drive
|
||
=unmountcd= will take care of unmounting any mounted CD drive. Let’s declare
|
||
this function.
|
||
#+BEGIN_SRC fish
|
||
function unmountcd
|
||
#+END_SRC
|
||
|
||
As before, let the user chose which CD drive to unmount.
|
||
#+BEGIN_SRC fish
|
||
set chosen (echo "$cds" | rofi -dmenu -i -p "Unmount which CD?")
|
||
#+END_SRC
|
||
|
||
We’ll verify the user chose any device.
|
||
#+BEGIN_SRC fish
|
||
test -z "$chosen" && exit 0
|
||
#+END_SRC
|
||
|
||
If a drive has been chosen, let’s unmount it and send a notification it has been
|
||
successfuly unmounted.
|
||
#+BEGIN_SRC fish
|
||
sudo -A umount -l $chosen && \
|
||
notify-send "💿 CD unmounting" "$chosen unmounted." -a "dumount"
|
||
#+END_SRC
|
||
|
||
Now, let’s close the function.
|
||
#+BEGIN_SRC fish
|
||
end
|
||
#+END_SRC
|
||
|
||
***** Ask what type of drive to unmount
|
||
If several types of unmountable drives are available, let’s ask the
|
||
user which type to unmount based on the content of the temporary file
|
||
declared in [[file:./scripts.md#get-the-unmountable-drives][Get the unmountable drives]]. First, let’s declare the
|
||
function.
|
||
#+BEGIN_SRC fish
|
||
function asktype
|
||
#+END_SRC
|
||
|
||
Let’s create a switch statement to which will be passed the selection of the
|
||
user from rofi.
|
||
#+BEGIN_SRC fish
|
||
switch (cat $undrivefile | rofi -dmenu -i -p "Unmount which type of device?")
|
||
#+END_SRC
|
||
|
||
Three types of values can be returned: "USB", "CD", or "Android". These values
|
||
will be used to launch their corresponding function.
|
||
#+BEGIN_SRC fish
|
||
case 'USB'
|
||
unmountusb
|
||
case 'CD'
|
||
unmountcd
|
||
case 'Android'
|
||
unmountandroid
|
||
#+END_SRC
|
||
|
||
Let’s close the switch statement.
|
||
#+BEGIN_SRC fish
|
||
end
|
||
#+END_SRC
|
||
|
||
Let’s now close the function.
|
||
#+BEGIN_SRC fish
|
||
end
|
||
#+END_SRC
|
||
|
||
***** Launch the unmounting functions
|
||
Now back to the body of our script, let’s input in a switch case the number of
|
||
lines contained in our temporary file.
|
||
#+BEGIN_SRC fish
|
||
switch (wc -l < $undrivefile)
|
||
#+END_SRC
|
||
|
||
If the file containes no lines. i.e. it is empty, nothing is to be unmounted.
|
||
Let’s inform the user of that.
|
||
#+BEGIN_SRC fish
|
||
case 0
|
||
notify-send "No USB drive or Android device or CD to unmount" -a "dumount"
|
||
#+END_SRC
|
||
|
||
Else, if there is only one type of drive, we’ll automatically let our script
|
||
choose based on the content of this sole line.
|
||
#+BEGIN_SRC fish
|
||
case 1
|
||
switch (cat $undrivefile)
|
||
case 'USB'
|
||
unmountusb
|
||
case 'CD'
|
||
unmountcd
|
||
case 'Android'
|
||
unmountandroid
|
||
end
|
||
#+END_SRC
|
||
|
||
And if there are more types than one, let’s ask the user.
|
||
#+BEGIN_SRC fish
|
||
case '*'
|
||
asktype
|
||
#+END_SRC
|
||
|
||
Let’s close our main switch statement.
|
||
#+BEGIN_SRC fish
|
||
end
|
||
#+END_SRC
|
||
|
||
And finally, let’s delete our temporary file.
|
||
#+BEGIN_SRC fish
|
||
rm -f $undrivefile
|
||
#+END_SRC
|
||
|
||
*** rofi-pass
|
||
:PROPERTIES:
|
||
:HEADER-ARGS: :shebang "#!/usr/bin/env fish" :mkdirp yes :tangle ~/.local/bin/rofi-pass
|
||
:END:
|
||
=rofi-pass= is a simple utility that gets a password stored in the [[https://www.passwordstore.org/][=pass=]]
|
||
password manager with rofi as its interface, and then stores the password in the
|
||
clipboard.
|
||
|
||
Let’s parse all the arguments passed to the script. If one of them is =--type=,
|
||
=-t= or =type=, the script will attempt to type the password to the text area
|
||
already selected without pasting the password to the clipboard.
|
||
#+BEGIN_SRC fish
|
||
for arg in $argv
|
||
switch $arg
|
||
case '--type' '-t' 'type'
|
||
set -g TYPE "yes"
|
||
case '*'
|
||
printf 'Unknown argument: %s\n.' $arg
|
||
exit 1
|
||
end
|
||
end
|
||
#+END_SRC
|
||
|
||
Now, let’s get the list of the passwords that exist in our =pass= repository.
|
||
#+BEGIN_SRC fish
|
||
set passwords (find $HOME/.password-store -type f -name "*.gpg" | \
|
||
string replace -r ".*.password-store/" "" | \
|
||
string replace -r ".gpg" "" | sort)
|
||
#+END_SRC
|
||
|
||
Let the user choose which password they wish to select.
|
||
#+BEGIN_SRC fish
|
||
set password (for elem in $passwords
|
||
echo $elem
|
||
end | rofi -dmenu -i -p "Select your password")
|
||
#+END_SRC
|
||
|
||
Let’s verify we actually selected a password and not just exited. If no password
|
||
was selected, let’s simply exit the script.
|
||
#+BEGIN_SRC fish
|
||
if test -z $password
|
||
exit
|
||
end
|
||
#+END_SRC
|
||
|
||
Depending on the arguments passed earlier, we might want some
|
||
different behaviour.
|
||
#+BEGIN_SRC fish :noweb yes
|
||
if test $TYPE = "yes"
|
||
<<rofi-pass-type>>
|
||
else
|
||
<<rofi-pass-copy>>
|
||
end
|
||
#+END_SRC
|
||
|
||
The default behaviour is to copy the password to the clipboard for 45
|
||
seconds, so let’s do that.
|
||
#+NAME: rofi-pass-copy
|
||
#+BEGIN_SRC fish :noweb yes :tangle no
|
||
pass show -c $password 2> /dev/null
|
||
#+END_SRC
|
||
|
||
Else, if we passed =--type=, =-t= or =type= as an argument of the script, we
|
||
want it to attempt to type our password in the currently selected text input.
|
||
Let’s do that.
|
||
#+NAME: rofi-pass-type
|
||
#+BEGIN_SRC fish :noweb yes :tangle no
|
||
set -l IFS
|
||
<<rofi-pass-type-get-password>>
|
||
printf %s $pass | xvkbd -file -
|
||
#+END_SRC
|
||
|
||
To correctly get the password from ~pass~, we need to parse the output and only
|
||
get the first line, hence the following command.
|
||
#+NAME: rofi-pass-type-get-password
|
||
#+BEGIN_SRC fish :tangle no
|
||
set pass (pass show $password | string split -n \n)[1]
|
||
#+END_SRC
|
||
|
||
*** rofi-ytdl
|
||
:PROPERTIES:
|
||
:HEADER-ARGS: :shebang "#!/usr/bin/env bash" :mkdirp yes :tangle ~/.local/bin/rofi-ytdl
|
||
:END:
|
||
This is just a simple wrapper around [[file:./scripts.md#ytdl-a-youtube-dl-wrapper][ytdl]] so I can easily download a
|
||
video from rofi, which we’ll use first to retrieve the URL of the
|
||
video we want to download, be it from YouTube or other website
|
||
supported by ~youtube-dl~.
|
||
#+BEGIN_SRC bash
|
||
URL=$(echo "Video to download:" | rofi -dmenu -i -p "Video to download:")
|
||
#+END_SRC
|
||
|
||
Now, if the variable ~URL~ is not empty (i.e. the user specified a link and did
|
||
not abort the operation), we’ll proceed to the download. Before it begins, we’ll
|
||
send a notification saying the download is about to begin. When the ~ytdl~
|
||
process ends, we’ll also send a notification notifying the user on the success
|
||
or failure of the download.
|
||
#+BEGIN_SRC bash
|
||
if [ -n "$URL" ]; then
|
||
notify-send -u normal "YTDL" "Starting downloading\n$URL"
|
||
if [[ $(ytdl "$URL") ]]
|
||
then
|
||
notify-send -u normal "YTDL" "Finished downloading!"
|
||
else
|
||
notify-send -u critical "YTDL" "Failed downloading\n$URL"
|
||
fi
|
||
fi
|
||
#+END_SRC
|
||
|
||
** Wallpaper utilities
|
||
*** pape-update
|
||
:PROPERTIES:
|
||
:HEADER-ARGS: :shebang "#!/usr/bin/env sh" :mkdirp yes :tangle ~/.local/bin/pape-update
|
||
:END:
|
||
This little tool sets a random wallpaper using xwallpaper.
|
||
#+BEGIN_SRC sh
|
||
PAPESDIR=$HOME/Pictures/Wallpapers
|
||
PAPE=$(find "$PAPESDIR" -type f | sort -R | tail -1)
|
||
set-pape "$PAPE"
|
||
#+END_SRC
|
||
|
||
*** Select wallpaper
|
||
:PROPERTIES:
|
||
:HEADER-ARGS: :shebang "#!/usr/bin/env sh" :mkdirp yes :tangle ~/.local/bin/select-pape
|
||
:END:
|
||
This script is based on what ~nsxiv~ can do as an image viewer as well as
|
||
xwallpaper.
|
||
#+BEGIN_SRC sh
|
||
PAPE=$(nsxiv -orbft ~/Pictures/Wallpapers/*)
|
||
set-pape "$PAPE"
|
||
#+END_SRC
|
||
|
||
*** Set a wallpaper
|
||
:PROPERTIES:
|
||
:HEADER-ARGS: :shebang "#!/usr/bin/env bash" :mkdirp yes :tangle ~/.local/bin/set-pape
|
||
:END:
|
||
This utility is not meant to be called by the user directly, but rather by
|
||
scripts that may be written by the user. Its role is simple: check if the
|
||
provided wallpaper exists and if it is an image. If both requirements are met,
|
||
the path to this image is then stored in ~$XDG_CACHE_HOME/wallpaper~, or if this
|
||
variable is empty in ~$HOME/.cache/wallpaper~.
|
||
#+BEGIN_SRC sh
|
||
CACHEFILE=$([ -n "$XDG_CACHE_HOME" ] && echo "$XDG_CACHE_HOME/wallpaper" || echo "$HOME/.cache/wallpaper")
|
||
[[ -f $1 ]] && \
|
||
grep image <(file -b --mime-type "$1") && \
|
||
echo "$1" > "$CACHEFILE" \
|
||
&& xwallpaper --zoom "$1"
|
||
#+END_SRC
|
||
|
||
** Wayland
|
||
*** Wayland Environment Variables Setup
|
||
:PROPERTIES:
|
||
:HEADER-ARGS: :shebang "#!/usr/bin/env sh" :mkdirp yes :tangle ~/.local/bin/way-env-setup
|
||
:END:
|
||
#+begin_src sh
|
||
#export GDK_BACKEND=wayland # May cause problems with some xorg applications
|
||
export TDESKTOP_DISABLE_GTK_INTEGRATION=1
|
||
export CLUTTER_BACKEND=wayland
|
||
export BEMENU_BACKEND=wayland
|
||
|
||
# Firefox
|
||
export MOZ_ENABLE_WAYLAND=1
|
||
|
||
#
|
||
# Qt environment
|
||
#
|
||
export QT_QPA_PLATFORM=wayland-egl #error with apps xcb
|
||
export QT_WAYLAND_FORCE_DPI=physical
|
||
export QT_WAYLAND_DISABLE_WINDOWDECORATION=1
|
||
|
||
#
|
||
# Elementary environment
|
||
#
|
||
export ELM_DISPLAY=wl
|
||
export ECORE_EVAS_ENGINE=wayland_egl
|
||
export ELM_ENGINE=wayland_egl
|
||
export ELM_ACCEL=opengl
|
||
# export ELM_SCALE=1
|
||
|
||
#
|
||
# SDL environment
|
||
#
|
||
export SDL_VIDEODRIVER=wayland
|
||
|
||
#
|
||
# Java environment
|
||
#
|
||
export _JAVA_AWT_WM_NONREPARENTING=1
|
||
|
||
export NO_AT_BRIDGE=1
|
||
export WINIT_UNIX_BACKEND=wayland
|
||
export DBUS_SESSION_BUS_ADDRESS
|
||
export DBUS_SESSION_BUS_PID
|
||
#+end_src
|
||
|
||
*** Qtile
|
||
:PROPERTIES:
|
||
:HEADER-ARGS: :shebang "#!/usr/bin/env sh" :mkdirp yes :tangle ~/.local/bin/launch-qtile
|
||
:END:
|
||
#+begin_src sh
|
||
export SDL_VIDEODRIVER=wayland
|
||
export XDG_SESSION_TYPE=wayland
|
||
export XDG_SESSION_DESKTOP=wlroots
|
||
export XDG_CURRENT_TYPE=wlroots
|
||
export XDG_CURRENT_DESKTOP=wlroots
|
||
. /etc/X11/xinit/xinitrc.d/50-systemd-user.sh
|
||
. way-env-setup
|
||
systemctl --user import-environment DISPLAY WAYLAND_DISPLAY XDG_CURRENT_DESKTOP
|
||
exec qtile start -b wayland
|
||
#+end_src
|
||
|
||
*** Newm
|
||
:PROPERTIES:
|
||
:HEADER-ARGS: :shebang "#!/usr/bin/env sh" :mkdirp yes :tangle ~/.local/bin/launch-newm
|
||
:END:
|
||
#+begin_src sh
|
||
export SDL_VIDEODRIVER=wayland
|
||
export XDG_SESSION_TYPE=wayland
|
||
export XDG_SESSION_DESKTOP=wlroots
|
||
export XDG_CURRENT_TYPE=wlroots
|
||
export XDG_CURRENT_DESKTOP=wlroots
|
||
. /etc/X11/xinit/xinitrc.d/50-systemd-user.sh
|
||
. way-env-setup
|
||
systemctl --user import-environment DISPLAY WAYLAND_DISPLAY XDG_CURRENT_DESKTOP
|
||
exec start-newm
|
||
#+end_src
|
||
|
||
*** Sway
|
||
:PROPERTIES:
|
||
:HEADER-ARGS: :shebang "#!/usr/bin/env sh" :mkdirp yes :tangle ~/.local/bin/swaybar-cmd
|
||
:END:
|
||
#+begin_src sh :tangle ~/.local/bin/launch-sway
|
||
export SDL_VIDEODRIVER=wayland
|
||
# export XDG_SESSION_TYPE=wayland
|
||
# export XDG_SESSION_DESKTOP=wlroots
|
||
# export XDG_CURRENT_TYPE=wlroots
|
||
export XDG_CURRENT_DESKTOP=sway
|
||
. /etc/X11/xinit/xinitrc.d/50-systemd-user.sh
|
||
. way-env-setup
|
||
systemctl --user import-environment DISPLAY WAYLAND_DISPLAY XDG_CURRENT_DESKTOP
|
||
exec sway --unsupported-gpu
|
||
#+end_src
|
||
|
||
Below is the script I use to display information in sway’s bar. It is
|
||
divided by functions that are then called all together. First, getting
|
||
the date is quite straightforward.
|
||
#+begin_src sh
|
||
sb_date() {
|
||
echo "$(date +'%Y-%m-%d %H:%M:%S') | "
|
||
}
|
||
#+end_src
|
||
|
||
Then we can get what piece of audio is playing. Note however that
|
||
something will be displayed only if something is playing.
|
||
#+begin_src sh
|
||
sb_playerctl() {
|
||
PLAYING=""
|
||
if [ "$(playerctl status)" = "Playing" ]; then
|
||
PLAYING_ARTIST="$(playerctl metadata xesam:artist)"
|
||
PLAYING_TITLE="$(playerctl metadata xesam:title)"
|
||
PLAYING=" $PLAYING_ARTIST - $PLAYING_TITLE | "
|
||
fi
|
||
echo "$PLAYING"
|
||
}
|
||
#+end_src
|
||
|
||
The battery is relatively straightforward too. I should however find
|
||
how to get estimates of battery time. I’m sure I can come up with
|
||
something, but for now I’m just too lazy.
|
||
#+begin_src sh
|
||
sb_battery() {
|
||
CAPACITY="$(cat /sys/class/power_supply/BAT0/capacity)%"
|
||
STATE="$(cat /sys/class/power_supply/BAT0/status)"
|
||
echo "$CAPACITY ($STATE)"
|
||
}
|
||
#+end_src
|
||
|
||
Now comes an indicator for Docker containers which will be displayed
|
||
only if at least one is running.
|
||
#+begin_src sh
|
||
sb_docker() {
|
||
DOCKER_RUNNING="$(docker ps -q | wc -l)"
|
||
# echo "Docker: $DOCKER_RUNNING"
|
||
if [ "$DOCKER_RUNNING" = "0" ]
|
||
then echo ""
|
||
else echo "Docker: $DOCKER_RUNNING | "
|
||
fi
|
||
}
|
||
#+end_src
|
||
|
||
Finally, let’s display everything.
|
||
#+begin_src sh
|
||
echo "$(sb_docker)$(sb_playerctl)$(sb_date)$(sb_battery) "
|
||
#+end_src
|
||
|
||
** Weather
|
||
:PROPERTIES:
|
||
:HEADER-ARGS: :shebang "#!/usr/bin/env fish" :mkdirp yes :tangle ~/.local/bin/we
|
||
:END:
|
||
A quick and useful script I often use is a ~curl~ request to [[http://v2.wttr.in/][v2.wttr.in]] to get a
|
||
weather forecast in the terminal. By default, I want the request to be about the
|
||
city I live in, but it is also possible for the script to accept as its
|
||
arguments a search inquiry.
|
||
#+BEGIN_SRC fish
|
||
if count $argv > /dev/null
|
||
set -l SEARCH (string join '+' $argv)
|
||
curl http://v2.wttr.in/~$SEARCH
|
||
else
|
||
curl http://v2.wttr.in/Aubervilliers
|
||
end
|
||
#+END_SRC
|
||
|
||
** Wrappers
|
||
:PROPERTIES:
|
||
:HEADER-ARGS: :shebang "#!/usr/bin/env sh" :mkdirp yes
|
||
:END:
|
||
In order to avoid clutter in my =$HOME= directory, I have some wrappers
|
||
around some commands that simply add some options by default.
|
||
|
||
#+begin_src sh :tangle ~/.local/bin/adb
|
||
HOME="$XDG_DATA_HOME"/android /usr/bin/adb "$@"
|
||
#+end_src
|
||
|
||
#+begin_src sh :tangle ~/.local/bin/mbsync
|
||
/usr/bin/mbsync -c "$XDG_CONFIG_HOME"/isync/mbsyncrc "$@"
|
||
#+end_src
|
||
|
||
#+begin_src sh :tangle ~/.local/bin/wget
|
||
/usr/bin/wget --hsts-file="$XDG_DATA_HOME"/wget-hsts "$@"
|
||
#+end_src
|
||
|
||
#+begin_src sh :tangle ~/.local/bin/yarn
|
||
/usr/bin/yarn --use-yarnrc "$XDG_CONFIG_HOME"/yarn/config "$@"
|
||
#+end_src
|