1941 lines
65 KiB
Org Mode
1941 lines
65 KiB
Org Mode
#+TITLE: Executable scripts
|
||
#+setupfile: headers
|
||
#+OPTIONS: auto-id:t
|
||
#+HTML_HEAD_EXTRA: <meta name="description" content="Phundrak’s custom scripts" />
|
||
#+HTML_HEAD_EXTRA: <meta property="og:title" content="Phundrak’s custom scripts" />
|
||
#+HTML_HEAD_EXTRA: <meta property="og:description" content="Source code of Phundrak’s custom scripts" />
|
||
#+PROPERTY: header-args :exports code
|
||
#+PROPERTY: header-args:emacs-lisp :exports none :tangle no
|
||
|
||
* Presentation
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Presentation-721f3cc4
|
||
:END:
|
||
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 fish" :mkdirp yes :tangle ~/.local/bin/autostart
|
||
:CUSTOM_ID: Autostart-a99e99e7
|
||
: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.
|
||
~set-screens~ is a custom script declared [[*set-screens][below]].
|
||
#+NAME: autostart-table
|
||
| Command | Arguments | Run once? |
|
||
|---------------+---------------------------------------+-----------|
|
||
| ~mpc~ | ~stop~ | no |
|
||
| ~xss-lock~ | ~plock~ | yes |
|
||
| ~set-screens~ | | no |
|
||
| ~picom~ | | yes |
|
||
| ~pumopm~ | | yes |
|
||
| ~xfce-polkit~ | | yes |
|
||
| ~nm-applet~ | | yes |
|
||
| ~numlockx~ | ~on~ | yes |
|
||
| ~xwallpaper~ | ~--zoom (cat $HOME/.cache/wallpaper)~ | no |
|
||
|
||
#+NAME: autostart-gen
|
||
#+BEGIN_SRC emacs-lisp :var table=autostart-table :cache yes
|
||
(mapconcat (lambda (start-command)
|
||
(let* ((command (s-replace "~" "" (nth 0 start-command)))
|
||
(arguments (s-replace "~" "" (nth 1 start-command)))
|
||
(once? (string= "yes" (nth 2 start-command))))
|
||
(if once?
|
||
(format "pgrep -x %s 2&>/dev/null || echo (%s) 2&>/dev/null"
|
||
command
|
||
(s-collapse-whitespace (format "%s %s & && disown"
|
||
command
|
||
arguments)))
|
||
(format "%s %s &" command arguments))))
|
||
table
|
||
"\n")
|
||
#+END_SRC
|
||
|
||
#+RESULTS[db0a64491cfacdcecad8ad71beed0e6b48b5af06]: autostart-gen
|
||
: mpc stop &
|
||
: pgrep -x xss-lock 2&>/dev/null || echo (xss-lock plock & && disown) 2&>/dev/null
|
||
: set-screens &
|
||
: pgrep -x picom 2&>/dev/null || echo (picom & && disown) 2&>/dev/null
|
||
: pgrep -x pumopm 2&>/dev/null || echo (pumopm & && disown) 2&>/dev/null
|
||
: pgrep -x xfce-polkit 2&>/dev/null || echo (xfce-polkit & && disown) 2&>/dev/null
|
||
: pgrep -x nm-applet 2&>/dev/null || echo (nm-applet & && disown) 2&>/dev/null
|
||
: pgrep -x numlockx 2&>/dev/null || echo (numlockx on & && disown) 2&>/dev/null
|
||
: xwallpaper --zoom (cat $HOME/.cache/wallpaper) &
|
||
|
||
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 fish :tangle no
|
||
set SOUNDCARD "alsa_output.usb-Focusrite_Scarlett_2i2_USB_Y8KJ6NH094EF1C-00.analog-stereo"
|
||
pactl list short sinks | grep $SOUNDCARD 2&> /dev/null && pactl set-default-sink $SOUNDCARD
|
||
#+END_SRC
|
||
|
||
#+BEGIN_SRC fish :noweb yes
|
||
set -l PATH $PATH /usr/lib/xfce-polkit
|
||
|
||
<<autostart-gen()>>
|
||
<<default-soundcard>>
|
||
#+END_SRC
|
||
|
||
* cli utilities
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: cli-utilities-ec2120b0
|
||
:END:
|
||
** Backup
|
||
:PROPERTIES:
|
||
:HEADER-ARGS: :shebang "#!/usr/bin/env fish" :mkdirp yes :tangle ~/.local/bin/backup
|
||
:CUSTOM_ID: Backup-68c7c63e
|
||
:END:
|
||
~backup~ is a very simple, oneliner 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
|
||
|
||
** Development
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: cli-utilities-Development-baec808a
|
||
:END:
|
||
*** Cppnew :noexport:
|
||
:PROPERTIES:
|
||
:HEADER-ARGS: :shebang "#!/usr/bin/env fish" :tangle no
|
||
:CUSTOM_ID: Cppnew-964e697b
|
||
:END:
|
||
=cppnew= is a small utility that helps you create a new C++ project. Several
|
||
templates are available, the default one using CMake, and three others that are
|
||
a bit more advances, based on:
|
||
- CMake + [[https://conan.io/][Conan]]
|
||
- [[https://mesonbuild.com/][Meson]] + [[https://ninja-build.org/][Ninja]]
|
||
- Meson + Ninja + Conan
|
||
There is also a default [[http://doxygen.nl/][Doxygen]] file included for your documentation, ready to
|
||
go. I even made it so that you can execute it as an executable file, like
|
||
=./doc/Doxyfile= from the project root.
|
||
|
||
The choice is given to the user which of them to use with options that will be
|
||
given to =cppnew=.
|
||
|
||
First of all, if no arguments were passed, return an error.
|
||
#+begin_src fish
|
||
if ! count $argv >/dev/null
|
||
echo "Missing argument: PROJECT" && return -1
|
||
end
|
||
#+end_src
|
||
|
||
Now, let’s set a couple of variables which will prove useful later on when
|
||
trying to set up our project.
|
||
|
||
*** Cnew
|
||
:PROPERTIES:
|
||
:HEADER-ARGS: :shebang "#!/usr/bin/env fish" :mkdirp yes :tangle ~/.local/bin/cnew
|
||
:CUSTOM_ID: Cnew-d9ec9cc4
|
||
:END:
|
||
=cnew= is a small utility script similar to but simpler than cppnew that creates
|
||
a CMake template C project from the template that already exists in
|
||
[[file:~/dev/templateC][~/dev/templateC]]. If no argument was passed, display an error message and exit.
|
||
#+BEGIN_SRC fish
|
||
if ! count $argv > /dev/null
|
||
echo "Missing argument: PROJECT" && return -1
|
||
end
|
||
#+END_SRC
|
||
|
||
Pass the first argument to a switch statement.
|
||
#+BEGIN_SRC fish
|
||
switch "$argv[1]"
|
||
#+END_SRC
|
||
|
||
If the argument is =-h= or =--help=, then display the help message and exit the
|
||
script normally.
|
||
#+BEGIN_SRC fish
|
||
case -h --help
|
||
man ~/dev/fishfunctions/cnew.man
|
||
exit 0
|
||
#+END_SRC
|
||
|
||
Else, the argument is the name of the project the user wants to create.
|
||
#+BEGIN_SRC fish
|
||
case '*'
|
||
set -g project_name $argv[1]
|
||
#+END_SRC
|
||
|
||
Let’s close the switch statement.
|
||
#+BEGIN_SRC fish
|
||
end
|
||
#+END_SRC
|
||
|
||
Now, let’s copy the template where the user is executing =cnew= from, give it
|
||
the name of the project and move to the project.
|
||
#+BEGIN_SRC fish
|
||
cp -r ~/dev/templateC $argv[1]
|
||
cd $argv[1]
|
||
#+END_SRC
|
||
|
||
The default files have a placeholder for the name of the project. Let’s replace
|
||
these placeholders with the project’s name.
|
||
#+BEGIN_SRC fish
|
||
sed -i "s/PROJECTNAME/$argv[1]/g" CMakeLists.txt
|
||
sed -i "s/PROJECTNAME/$argv[1]/g" README.org
|
||
sed -i "s/CPROJECTNAME/$argv[1]/g" doc/Doxyfile
|
||
#+END_SRC
|
||
|
||
Now, let’s create a git repository and initialize it.
|
||
#+BEGIN_SRC fish
|
||
git init
|
||
git add .
|
||
git commit -m "initial commit"
|
||
#+END_SRC
|
||
|
||
And we’re done!
|
||
|
||
*** Dart Language Server
|
||
:PROPERTIES:
|
||
:HEADER-ARGS: :shebang "#!/usr/bin/env fish" :mkdirp yes :tangle ~/.local/bin/dart_language_server
|
||
:CUSTOM_ID: Dart_Language_Server-18c256b1
|
||
:END:
|
||
Spacemacs' recommendations on how to use Dart with LSP is outdated, since
|
||
[[https://github.com/natebosch/dart_language_server][=dart_language_server=]] is obsolete. As recommended by the repo owner, we should
|
||
launch instead the following code:
|
||
#+BEGIN_SRC fish
|
||
/usr/bin/dart $DART_SDK/snapshots/analysis_server.dart.snapshot --lsp
|
||
#+END_SRC
|
||
|
||
So, instead of using the obsolete executable, instead we will be calling the
|
||
analysis server as requested.
|
||
|
||
*** UpdateFlutter
|
||
:PROPERTIES:
|
||
:HEADER-ARGS: :shebang "#!/usr/bin/env fish" :mkdirp yes :tangle ~/.local/bin/UpdateFlutter
|
||
:CUSTOM_ID: UpdateFlutter-1e8fbeb7
|
||
:END:
|
||
This is a simple utility to be ran when the ~flutter~ package is updated.
|
||
#+BEGIN_SRC fish
|
||
sudo chown -R :flutterusers /opt/flutter
|
||
sudo chmod -R g+w /opt/flutter
|
||
sudo chmod a+rw /opt/flutter/version
|
||
sudo chown $USER:(id -g $USER) /opt/flutter/bin/cache
|
||
#+END_SRC
|
||
|
||
** Pinfo :noexport:
|
||
:PROPERTIES:
|
||
:HEADER-ARGS: :shebang "#!/usr/bin/env fish" :tangle no
|
||
:CUSTOM_ID: Pinfo-f3644596
|
||
:END:
|
||
~pinfo~ is a utility that shows system information
|
||
|
||
** set-screens
|
||
:PROPERTIES:
|
||
:HEADER-ARGS: :shebang "#!/usr/bin/env fish" :mkdirp yes :tangle ~/.local/bin/set-screens
|
||
:CUSTOM_ID: set-screens-01bd989a
|
||
:END:
|
||
~set-screens~ is a small script that allows the user to automatically set up an
|
||
external monitor. First, let’s set some variables so we don’t have to type in
|
||
hidden places some values that should be easily modifiable.
|
||
#+BEGIN_SRC fish
|
||
set internal "eDP1"
|
||
set external "HDMI1"
|
||
#+END_SRC
|
||
|
||
Now, let’s set the ~DETECTEDSCREEN~ variable with a simple ~grep~. If the
|
||
variable turns out to be empty, this means the display was not detected.
|
||
However, if it’s not, then it will be an array with its second value that holds
|
||
the maximum resolution the display can handle. It needs to be passed through
|
||
~awk~ in order to get only the resolution itself and not the refresh rate, but
|
||
once we’ve got that, we can set our external monitor as the main monitor with
|
||
its maximum resolution. i3 is also restarted in order to properly display the
|
||
wallpaper and Polybar on the new screen.
|
||
#+BEGIN_SRC fish
|
||
set externaldisplay (xrandr -q --current | grep -A 1 -i "$external connected")
|
||
if test -n "$externaldisplay"
|
||
set resolution (echo $externaldisplay[2] | awk '{$1=$1;print $1}')
|
||
xrandr --output "$external" --primary --auto --mode "$resolution" --right-of "$internal"
|
||
end
|
||
#+END_SRC
|
||
|
||
** sshbind
|
||
:PROPERTIES:
|
||
:HEADER-ARGS: :shebang "#!/usr/bin/env fish" :mkdirp yes :tangle ~/.local/bin/sshbind
|
||
:CUSTOM_ID: sshbind-756fabb1
|
||
: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
|
||
|
||
** Starwars
|
||
:PROPERTIES:
|
||
:HEADER-ARGS: :shebang "#!/usr/bin/env fish" :mkdirp yes :tangle ~/.local/bin/starwars
|
||
:CUSTOM_ID: Starwars-654f8637
|
||
: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
|
||
:CUSTOM_ID: Toggle_touchpad_tapping-23348b00
|
||
:END:
|
||
For some reasons, 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 fish" :mkdirp yes :tangle ~/.local/bin/wacom-setup
|
||
:CUSTOM_ID: Wacom_setup-331fb024
|
||
:END:
|
||
I made a small and quick utility to set up my Wacom tablet so it is only bound
|
||
to one screen.
|
||
|
||
*** Set our variables
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Wacom_setup-Set_our_variables-3cb6d58e
|
||
:END:
|
||
We need some variables in order to correctly set our tablet. First, let’s get
|
||
declare what the name of our tablet is, and what the name of its touchpad is.
|
||
#+BEGIN_SRC fish
|
||
set -g DEVICE "Wacom USB Bamboo PAD Pen stylus"
|
||
#+END_SRC
|
||
|
||
To get the correct values for the area it can cover, we’ll need to reset our
|
||
tablet.
|
||
#+BEGIN_SRC fish
|
||
xsetwacom set "$DEVICE" ResetArea
|
||
#+END_SRC
|
||
|
||
Now we can get the X and Y areas.
|
||
#+BEGIN_SRC fish
|
||
set -g AREATOT (xsetwacom get "$DEVICE" Area)
|
||
set -g AREAX (echo $AREATOT | awk '{print $3}')
|
||
set -g AREAY (echo $AREATOT | awk '{print $4}')
|
||
#+END_SRC
|
||
|
||
*** Select our screen
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Wacom_setup-Select_our_screen-7822c0c3
|
||
:END:
|
||
This function will allow us to select the screen on which the tablet will be
|
||
active. We can also select the option “desktop” so that all screens are
|
||
selected. Let’s declare our function.
|
||
#+BEGIN_SRC fish :noweb yes
|
||
function set_screen
|
||
<<wacom-screen-get-displays>>
|
||
<<wacom-screens-select-screen>>
|
||
<<wacom-screen-get-position>>
|
||
<<wacom-screen-get-dimensions>>
|
||
end
|
||
#+END_SRC
|
||
|
||
First, let’s set what screens are available, including the desktop option.
|
||
#+NAME: wacom-screen-get-displays
|
||
#+BEGIN_SRC fish :tangle no
|
||
set CONNECTED_DISPLAYS (xrandr -q --current | \
|
||
sed -n 's/^\([^ ]\+\) connected .*/\1/p') desktop
|
||
#+END_SRC
|
||
|
||
Now, let’s select the one we wish to use using rofi.
|
||
#+NAME: wacom-screens-select-screen
|
||
#+BEGIN_SRC fish :tangle no
|
||
set -g SCREEN (for d in $CONNECTED_DISPLAYS
|
||
echo $d
|
||
end | rofi -dmenu -i -p "Select your dispaly" | tr -d '\n')
|
||
#+END_SRC
|
||
|
||
We can now get the position of our screen.
|
||
#+NAME: wacom-screen-get-position
|
||
#+BEGIN_SRC fish :tangle no
|
||
set -g POSITION (xrandr -q --current | sed -nr "s/^$SCREEN connected (primary )*([0-9x\+]+).*/\2/p")
|
||
#+END_SRC
|
||
|
||
We’ll also get the width and height of the screen so we can set correctly the
|
||
drawing area of the tablet.
|
||
#+NAME: wacom-screen-get-dimensions
|
||
#+BEGIN_SRC fish :tangle no
|
||
set -g HEIGHT (echo $POSITION | sed -nr 's/[0-9]+x([0-9]+).*/\1/p')
|
||
set -g WIDTH (echo $POSITION | sed -nr 's/([0-9]+)x.*/\1/p')
|
||
#+END_SRC
|
||
|
||
*** Adjust the tablet
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Wacom_setup-Adjust_the_tablet-342acaf3
|
||
:END:
|
||
This function will take care of adjusting our tablet to our screen. Let’s
|
||
declare our function.
|
||
#+BEGIN_SRC fish :noweb yes
|
||
function adjust_device
|
||
<<wacom-tablet-set-ration-area>>
|
||
<<wacom-tablet-set-new-area>>
|
||
<<wacom-tablet-set-device-area-and-screen>>
|
||
end
|
||
#+END_SRC
|
||
|
||
If our screen is too high or too wide for our tablet, we will have to adjust the
|
||
height or width of the area used by the tablet. So let’s get the theoretical new
|
||
height and width of the area.
|
||
#+NAME: wacom-tablet-set-ration-area
|
||
#+BEGIN_SRC fish :tangle no
|
||
set RATIOAREAY (math ceil \($AREAX \* $HEIGHT \/ $WIDTH\))
|
||
set RATIOAREAX (math ceil \($AREAY \* $WIDTH \/ $HEIGHT\))
|
||
#+END_SRC
|
||
|
||
Now, if the current height of the tablet’s area is greater than the theoretical
|
||
new area, it means the current area is too high. Otherwise, it should be the
|
||
other way around. Let’s set =NEWAREAX= and =NEWAREAY= that will be used to set
|
||
the new area for the tablet.
|
||
#+NAME: wacom-tablet-set-new-area
|
||
#+BEGIN_SRC fish :tangle no
|
||
if test $AREAY -gt $RATIOAREAY
|
||
set -g NEWAREAX $AREAX
|
||
set -g NEWAREAY $RATIOAREAY
|
||
else
|
||
set -g NEWAREAX $RATIOAREAX
|
||
set -g NEWAREAY $AREAY
|
||
end
|
||
#+END_SRC
|
||
|
||
Alright, now let’s set the new area with these new variables.
|
||
#+NAME: wacom-tablet-set-device-area-and-screen
|
||
#+BEGIN_SRC fish :tangle no
|
||
xsetwacom set "$DEVICE" Area 0 0 $NEWAREAX $NEWAREAY
|
||
#+END_SRC
|
||
|
||
*** Lauch the functions
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Wacom_setup-Lauch_the_functions-2ab8b4d9
|
||
:END:
|
||
Back to the main body of the script, we can now launch the functions
|
||
sequencially.
|
||
#+BEGIN_SRC fish
|
||
set_screen
|
||
adjust_device
|
||
#+END_SRC
|
||
|
||
* Emacs stuff
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Emacs-stuff-8e76efd4
|
||
:END:
|
||
** Dired
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Emacs-stuff-Dired-2eeca9da
|
||
:HEADER-ARGS: :shebang "#!/bin/bash" :mkdirp yes :tangle ~/.local/bin/dired
|
||
:END:
|
||
#+BEGIN_SRC bash
|
||
emacsclient -c -e "(dired \"$@\")"
|
||
#+END_SRC
|
||
|
||
** Emacsmail
|
||
:PROPERTIES:
|
||
:HEADER-ARGS: :shebang "#!/bin/bash" :mkdirp yes :tangle ~/.local/bin/emacsmail
|
||
:CUSTOM_ID: Emacsmail-afffb7cd
|
||
: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 --eval "(browse-url-mail \"$@\")"
|
||
#+END_SRC
|
||
|
||
* Media
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Media-f869f942
|
||
:END:
|
||
** mp42webm
|
||
:PROPERTIES:
|
||
:HEADER-ARGS: :shebang "#!/usr/bin/env fish" :mkdirp yes :tangle ~/.local/bin/mp42webm
|
||
:CUSTOM_ID: mp42webm-aeacca58
|
||
:END:
|
||
This function allows me to convert easily an 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
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Media-youtube-dl-wrappers-8d8f5f71
|
||
:END:
|
||
*** ytplay
|
||
:PROPERTIES:
|
||
:HEADER-ARGS: :shebang "#!/usr/bin/env fish" :mkdirp yes :tangle ~/.local/bin/ytplay
|
||
:CUSTOM_ID: Weather-4ed00bb0
|
||
: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
|
||
:CUSTOM_ID: ytdl-a-youtube-dl-wrapper-03bd63e0
|
||
: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.1TB
|
||
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
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: ytdl-a-youtube-dl-wrapper-Setting-default-values-da404639
|
||
:END:
|
||
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
|
||
#+BEGIN_SRC emacs-lisp :var vars=ytdl-default-vars
|
||
(mapconcat (lambda (var)
|
||
(let ((varname (car var))
|
||
(varvalue (cadr var))
|
||
(string? (string= (nth 2 var) "yes")))
|
||
(format "set -g %-16s %s" varname (if string? (format "\"%s\"" varvalue)
|
||
varvalue))))
|
||
vars
|
||
"\n")
|
||
#+END_SRC
|
||
|
||
#+RESULTS: ytdl-default-vars-make
|
||
: 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"
|
||
|
||
#+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
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: ytdl-a-youtube-dl-wrapper-Help-message-3773aacd
|
||
:END:
|
||
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
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: ytdl-a-youtube-dl-wrapper-Arguments-Handling-1daebbe8
|
||
:END:
|
||
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
|
||
#+BEGIN_SRC emacs-lisp :var args=ytdl-table-arguments
|
||
(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))
|
||
(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: ytdl-arg-handling-gen
|
||
#+begin_example
|
||
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_example
|
||
|
||
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
|
||
#+BEGIN_SRC emacs-lisp :var args=ytdl-table-arguments
|
||
(let* ((args (-filter (lambda (arg)
|
||
(let* ((var (unless (string= "None" (nth 3 arg)) (nth 3 arg)))
|
||
(default (format "%s" (nth 4 arg)))
|
||
(default (unless (string= "None" default) default)))
|
||
(and var default)))
|
||
args)))
|
||
(mapconcat (lambda (arg)
|
||
(let* ((var (nth 3 arg))
|
||
(default (format "%s" (nth 4 arg))))
|
||
(format "if set -q $%s\n\tset -g %s %s\nend"
|
||
var var default)))
|
||
args
|
||
"\n"))
|
||
#+END_SRC
|
||
|
||
#+RESULTS: ytdl-arg-set-default-value-gen
|
||
#+begin_example
|
||
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_example
|
||
|
||
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
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: ytdl-a-youtube-dl-wrapper-Logging-f4b9815e
|
||
:END:
|
||
~_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
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: ytdl-a-youtube-dl-wrapper-Download-a-Single-Video-afedf321
|
||
:END:
|
||
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
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: ytdl-a-youtube-dl-wrapper-Download-a-Queue-of-Videos-6ef8d51f
|
||
:END:
|
||
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
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: ytdl-a-youtube-dl-wrapper-Download-Videos-From-Arguments-57a5dac1
|
||
:END:
|
||
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
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: ytdl-a-youtube-dl-wrapper-Download-Videos-From-a-Batch-File-0f1382c4
|
||
:END:
|
||
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 entierty 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
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: ytdl-a-youtube-dl-wrapper-Main-Body-8a06cb9e
|
||
:END:
|
||
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 [[#rofi-ytdl-ff8f789d][~rofi-ytdl~]] script that calls the
|
||
~rofi~ utility to specify a single link and download it.
|
||
* Plock
|
||
:PROPERTIES:
|
||
:HEADER-ARGS: :shebang "#!/usr/bin/env fish" :mkdirp yes :tangle ~/.local/bin/plock
|
||
:CUSTOM_ID: Lock-635fcb38
|
||
: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 fish
|
||
set TMPBG /tmp/screen.png
|
||
scrot $TMPBG
|
||
corrupter -add 0 $TMPBG $TMPBG
|
||
i3lock -t -e -f -i $TMPBG
|
||
rm $TMPBG
|
||
#+END_SRC
|
||
|
||
* Polybar-launch (Deprecated) :noexport:
|
||
:PROPERTIES:
|
||
:HEADER-ARGS: :shebang "#!/usr/bin/env fish" :mkdirp yes :tangle ~/.local/bin/polybar-launch
|
||
:CUSTOM_ID: Polybar-launch-36789edc
|
||
: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
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Rofi-utilities-650338d1
|
||
:END:
|
||
** askpass
|
||
:PROPERTIES:
|
||
:HEADER-ARGS: :shebang "#!/usr/bin/env fish" :mkdirp yes :tangle ~/.local/bin/askpass
|
||
:CUSTOM_ID: Askpass-d0d7a8c0
|
||
: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
|
||
:CUSTOM_ID: awiki-7ac5e1d5
|
||
: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
|
||
:CUSTOM_ID: ConnectWifi-16e5e24a
|
||
: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
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: ConnectWifi-fix_it-a4b11503
|
||
:END:
|
||
|
||
** dmenu
|
||
:PROPERTIES:
|
||
:HEADER-ARGS: :shebang "#!/usr/bin/env fish" :mkdirp yes :tangle ~/.local/bin/dmenu
|
||
:CUSTOM_ID: Dmenu-527edf04
|
||
: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 fish" :mkdirp yes :tangle ~/.local/bin/rofi-emoji
|
||
:CUSTOM_ID: Emoji_picker-a1c374ec
|
||
: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. Once the emoji is selected, it is
|
||
copied to the clipboard using =xclipboard=.
|
||
#+BEGIN_SRC fish
|
||
grep -v "#" ~/.config/emoji.txt | rofi -dmenu -p "Select emoji" -i | \
|
||
awk '{print $1}' | tr -d '\n' | xclip -selection clipboard
|
||
#+END_SRC
|
||
|
||
Also, let’s send a notification telling the user the emoji has been copied!
|
||
#+BEGIN_SRC fish
|
||
set emoji (xclip -o -selection clipboard | tr -d '\n')
|
||
test -z "$emoji" && notify-send "No emoji copied" -u low && exit
|
||
set -a 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
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Rofi-utilities-Partition-mounting-and-unmounting-9492ff60
|
||
:END:
|
||
*** Rofi-mount
|
||
:PROPERTIES:
|
||
:HEADER-ARGS: :shebang "#!/usr/bin/env fish" :mkdirp yes :tangle ~/.local/bin/rofi-mount
|
||
:CUSTOM_ID: Rofi-mount-ebbebf68
|
||
: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
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Rofi-mount-Get_the_mountable_elements-24db7834
|
||
:END:
|
||
#+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 si 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
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Rofi-mount-Get_the_mount_point-6c4bac06
|
||
:END:
|
||
Now, let’s declare a function that will allow us to chose 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 chose 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
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Rofi-mount-Mount_a_USB_drive,_hard_drive_or_partition-f5431dbe
|
||
:END:
|
||
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 chose 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 [[#Rofi-mount-Get_the_mount_point-6c4bac06][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
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Rofi-mount-Mount_an_Android_device-5321f9cd
|
||
:END:
|
||
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 explicitely 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 transfert"
|
||
#+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 cam 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
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Rofi-mount-Mount_a_CD_drive-27278199
|
||
:END:
|
||
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
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Rofi-mount-Ask_what_type_of_drive_we_want_to_mount-0c15cffa
|
||
:END:
|
||
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 anwser 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
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Rofi-mount-Launch_the_mounting_functions-218ad001
|
||
:END:
|
||
Now that we have declared our functions and set our variables, we’ll read the
|
||
temporary file described in [[#Rofi-mount-Get_the_mountable_elements-24db7834][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
|
||
:CUSTOM_ID: Rofi-umount-ddde1667
|
||
:END:
|
||
~rofiumount~ is the counterpart of ~rofimount~ for unmounting our mounted
|
||
partitions.
|
||
|
||
**** Get the unmountable drives
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Rofi-umount-Get_the_unmountable_drives-89c71040
|
||
:END:
|
||
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
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Rofi-umount-Unmount_disk_partitions-0d425a47
|
||
:END:
|
||
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
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Rofi-umount-Unmount_Android_device-ae1d5904
|
||
:END:
|
||
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
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Rofi-umount-Unmount_CD_drive-369a2f61
|
||
:END:
|
||
=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
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Rofi-umount-Ask_what_type_of_drive_to_unmount-6287af48
|
||
:END:
|
||
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 [[#Rofi-umount-Get_the_unmountable_drives-89c71040][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
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Rofi-umount-Launch_the_unmounting_functions-7c48a928
|
||
:END:
|
||
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
|
||
:CUSTOM_ID: Rofi-pass-8335357f
|
||
: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
|
||
behavior.
|
||
#+BEGIN_SRC fish :noweb yes
|
||
if test $TYPE = "yes"
|
||
<<rofi-pass-type>>
|
||
else
|
||
<<rofi-pass-copy>>
|
||
end
|
||
#+END_SRC
|
||
|
||
The default behavior 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
|
||
:CUSTOM_ID: rofi-ytdl-ff8f789d
|
||
:END:
|
||
This is just a simple wrapper around [[#ytdl-a-youtube-dl-wrapper-03bd63e0][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 teh 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"
|
||
ytdl "$URL" \
|
||
&& notify-send -u normal "YTDL" "Finished downloading!" \
|
||
|| notify-send -u critical "YTDL" "Failed downloading\n$URL"
|
||
fi
|
||
#+END_SRC
|
||
|
||
* Wallpaper utilities
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Wallpaper-utilities-5c05d470
|
||
:END:
|
||
** pape-update
|
||
:PROPERTIES:
|
||
:HEADER-ARGS: :shebang "#!/usr/bin/env sh" :mkdirp yes :tangle ~/.local/bin/pape-update
|
||
:CUSTOM_ID: pape-update-bdecbadf
|
||
: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:
|
||
:CUSTOM_ID: Wallpaper-utilities-Select-wallpaper-42f477a9
|
||
:HEADER-ARGS: :shebang "#!/usr/bin/env sh" :mkdirp yes :tangle ~/.local/bin/select-pape
|
||
:END:
|
||
This script is base on what ~sxiv~ can do as an image viewer as well as
|
||
xwallpaper.
|
||
#+BEGIN_SRC sh
|
||
PAPE=$(sxiv -orbft ~/Pictures/Wallpapers/*)
|
||
set-pape $PAPE
|
||
#+END_SRC
|
||
|
||
** Set a wallpaper
|
||
:PROPERTIES:
|
||
:HEADER-ARGS: :shebang "#!/usr/bin/env sh" :mkdirp yes :tangle ~/.local/bin/set-pape
|
||
:CUSTOM_ID: Wallpaper-utilities-Set-a-wallpaper-27eda9e6
|
||
: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
|
||
|
||
* Weather
|
||
:PROPERTIES:
|
||
:HEADER-ARGS: :shebang "#!/usr/bin/env fish" :mkdirp yes :tangle ~/.local/bin/we
|
||
:CUSTOM_ID: Weather-4ed00bb0
|
||
: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
|