Terminal Takeaway πŸ₯‘

Only 28 lines to start an X Session from TTY

sx is a replacement for startx and xinit which i’m currently using to start XFCE.

sx is a tiny fraction of the size of startx and xinit showing what cutting a few features and edge cases can do for making code easy to read and hack on.

Compare https://github.com/Earnestly/sx -to- https://github.com/freedesktop/xorg-xinit

Installation:

# Set up the init script for sx

mkdir -p ~/.config/sx/
touch ~/.config/sx/sxrc

# sx requires this be executable
chmod 700 ~/.config/sx/sxrc
vim ~/.config/sx/sxrc

# Paste what would normally be in your ~/.xinitrc file

# For example, I need my dunst notifier started along with
# startxfce4 so my ~/.config/sx/sxrc looks like this:

dunst &
startxfce4

# Download script and install to /usr/local/bin/
sudo sh -c "wget -q -O - https://raw.githubusercontent.com/Earnestly/sx/master/sx > /usr/local/bin/sx"

# Confirm against original
cat /usr/local/bin/sx

sudo chmod 755 /usr/local/bin/sx

# sx is now ready! Enter a TTY and launch an X Session using:
sx

Note: I boot to a TTY but If you’re using a Desktop Manager like LightDM you’ll need to configure it to use sx. Alternatively you can switch to a TTY and start an additional X Session using sx as it uses the TTY you’re in not TTY7.

1 Like

Weather report in terminal

# Try it
wget -qO- "https://wttr.in/"
     \  /       Partly cloudy
   _ /"".-.     +22(25) Β°C     
     \_(   ).   ↙ 19 km/h      
     /(___(__)  10 km          
                0.0 mm         
                                                       β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                                                       
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€  Sat 17 Jul β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚            Morning           β”‚             Noon      β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜     Evening           β”‚             Night            β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚               Mist           β”‚  _`/"".-.     Light rain sho…│    \  /       Partly cloudy  β”‚    \  /       Partly cloudy  β”‚
β”‚  _ - _ - _ -  18 Β°C          β”‚   ,\_(   ).   20 Β°C          β”‚  _ /"".-.     21 Β°C          β”‚  _ /"".-.     18 Β°C          β”‚
β”‚   _ - _ - _   ↙ 14-17 km/h   β”‚    /(___(__)  ↙ 22-25 km/h   β”‚    \_(   ).   ↓ 22-25 km/h   β”‚    \_(   ).   ↓ 20-28 km/h   β”‚
β”‚  _ - _ - _ -  7 km           β”‚      β€˜ β€˜ β€˜ β€˜  10 km          β”‚    /(___(__)  10 km          β”‚    /(___(__)  10 km          β”‚
β”‚               0.1 mm | 66%   β”‚     β€˜ β€˜ β€˜ β€˜   0.1 mm | 33%   β”‚               0.0 mm | 0%    β”‚               0.0 mm | 0%    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                                       β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                                                       
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€  Sun 18 Jul β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚            Morning           β”‚             Noon      β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜     Evening           β”‚             Night            β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚               Cloudy         β”‚  _`/"".-.     Patchy rain po…│    \  /       Partly cloudy  β”‚    \  /       Partly cloudy  β”‚
β”‚      .--.     20 Β°C          β”‚   ,\_(   ).   +23(24) Β°C     β”‚  _ /"".-.     22 Β°C          β”‚  _ /"".-.     19 Β°C          β”‚
β”‚   .-(    ).   ↙ 20-23 km/h   β”‚    /(___(__)  ↓ 22-26 km/h   β”‚    \_(   ).   ↓ 23-33 km/h   β”‚    \_(   ).   ↓ 23-37 km/h   β”‚
β”‚  (___.__)__)  10 km          β”‚      β€˜ β€˜ β€˜ β€˜  10 km          β”‚    /(___(__)  10 km          β”‚    /(___(__)  10 km          β”‚
β”‚               0.1 mm | 61%   β”‚     β€˜ β€˜ β€˜ β€˜   0.0 mm | 30%   β”‚               0.0 mm | 0%    β”‚               0.0 mm | 0%    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                                       β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                                                       
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€  Mon 19 Jul β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚            Morning           β”‚             Noon      β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜     Evening           β”‚             Night            β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚    \  /       Partly cloudy  β”‚    \  /       Partly cloudy  β”‚    \  /       Partly cloudy  β”‚    \  /       Partly cloudy  β”‚
β”‚  _ /"".-.     20 Β°C          β”‚  _ /"".-.     +23(24) Β°C     β”‚  _ /"".-.     22 Β°C          β”‚  _ /"".-.     19 Β°C          β”‚
β”‚    \_(   ).   ↙ 14-16 km/h   β”‚    \_(   ).   ↙ 16-18 km/h   β”‚    \_(   ).   ↓ 13-21 km/h   β”‚    \_(   ).   ↓ 7-14 km/h    β”‚
β”‚    /(___(__)  10 km          β”‚    /(___(__)  10 km          β”‚    /(___(__)  10 km          β”‚    /(___(__)  10 km          β”‚
β”‚               0.0 mm | 0%    β”‚               0.0 mm | 0%    β”‚               0.0 mm | 0%    β”‚               0.0 mm | 0%    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Moon phase in terminal

# Try it
wget -qO- "https://wttr.in/moon"
                       -------.	 
                         . .   `--.	 
                         .       . `-.	 
                        .  @@@@@      `-.	 
                      @   @@@@@@@   .    \	 
                      @   @@@@@@@       . \.	 
                      @.   @@@@@@@   O      \	 
                          @@@@@@@@@@     @@@ \	 
                       . @@@@@@@@@@@@@ o @@@@|	 
                          @@@@@@@@@@@@    @@  \	 First Quarter +
                       o     @@@@@@@@ @@@@    |	 0 10:02:27
                           .  @@   . @@@@@@@  |	 Full Moon -
                       .-.     @@@   @@@@@@@  |	 6  6:23:22
                       `-'   . @@@@   @@@@  o /	 
                                @@   .       |	 
                      /  .  O    .     o   . /	 
                               .    .       /	 
                      __   .      .   .-. /'	 
                                     `-' /	 
                         o    O   .   .-'	 
                              .    .-'	 
                        .      .--'	 
                       -------'	 

Some highlights:

#!/bin/sh
# ^-- So the forum colorizes this correctly

# Output a basic usage guide (check the Git for full instructions):
wget -qO- "https://wttr.in/:help"

# Weather is by IP location by default. Select a city as follows:
wget -qO- "https://wttr.in/berlin"

# You can also add URL parameters, this one selects the German language using "lang=de":
wget -qO- "https://wttr.in/berlin?lang=de"

# Get the report in JSON using "format=j1":
wget -qO- "https://wttr.in/berlin?lang=de&format=j1"

# Data rich output using "format=v2"
wget -qO- "https://wttr.in?format=v2"

Alternatively you can use curl

wget
	-q		# Quiet
	-O -	# Output to terminal
	-qO-	# ^ these combined

wttr.in is a Web API wrapper for delivering output from the included wego project. Check out the projects below:

wttr.in (wego as a web service)

wego

4 Likes

I set up with a systemd user service with a shell script using wttr.in, to populate the weather in swaybar originally. wttr.in is pretty neat.

Description="Pull weather using wttr.in for sway bar" 

[Service]
Type=simple
ExecStart=%h/bin/weathercache.bash

[Install]
WantedBy=sway-session.target  
Description=Pull from wttr.in every 30 minutes

[Timer]
Persistent=true
OnBootSec=30
OnCalendar=*:0/30
Unit=weathercache.service

[Install]
WantedBy=timers.target
#!/bin/bash
LOCATION="zipcode"
FORMAT="2"
/usr/bin/curl https://wttr.in/${LOCATION}?format="${FORMAT}&u" > ~/.cache/weather.cache
1 Like

I love this thread!

3 Likes

Resizing your GUI terminal emulator on demand

Note: This guide is for xfce4-terminal, mate-terminal and gnome-terminal though it may work on others. It doesn’t work in konsole (info) or alacritty, these will likely need something like a wmctrl or xdotool solution.

Instructions at the bottom for how to make this work in xterm.

# Try it

# Check terminal size
stty size

# Resize terminal to 30 rows x 125 columns using a native escape sequence
printf '\033[8;30;125t'

# Check just the rows
stty size | cut -d ' ' -f 1
# Check just the columns
stty size | cut -d ' ' -f 2

Sometimes wrapped text can lead to undesirable outcomes, using wget -qO- "https://wttr.in/" for example needs 125 columns to prevent wrapping.

w

A quick n’ dirty solution to set 125 columns without adjusting the rows would be:

printf '\033[8;%d;125t' "$(stty size | cut -d ' ' -f 1)"
wget -qO- "https://wttr.in/"

…though a more elegant solution would only change the rows if they were too small and be easier to read:

nano ~/.bashrc
# Add the following to the bottom:
# Show me the weather
function weather (){
	# If the terminal has less than 125 columns, increase the columns to 125
	CurCols=$(stty size | cut -d ' ' -f 2)
	if [ "$CurCols" -lt "125" ]; then
		NewCols=125
		NewRows=$(stty size | cut -d ' ' -f 1)
		printf '\033[8;%d;%dt' "$NewRows" "$NewCols"
	fi
	wget -qO- "https://wttr.in/"
}
# Save & quit
# Open a new terminal
weather

Making this work in XTerm

XTerm needs allowWindowOps set to true for this to work. At startup your distro will run something similar too xrdb < ~/.Xresources, xrdb < ~/.Xdefaults or neither. If it’s neither one of these need to be added to startup.

For this example i’m using ~/.Xresources

nano ~/.Xresources
# Add the following to the bottom:
xterm*allowWindowOps: true
# Save & quit

# Load the configuration
xrdb < ~/.Xresources

# Open a new xterm window
# Test
printf '\033[8;20;40t'
2 Likes

Extra: Universal GUI terminal resizing script

Intended for xfce4-terminal, mate-terminal, gnome-terminal (see: above)

sudo touch /usr/local/bin/termsizeto
sudo chmod 755 /usr/local/bin/termsizeto
sudo nano /usr/local/bin/termsizeto

# Paste the following:
#!/usr/bin/env sh

# Examples of use:
# termsizeto 70 20			# Set terminal size to 70 columns, 20 rows
# termsizeto 70 auto		# Set terminal size to 70 columns, existing rows
# termsizeto 70 20 min		# Set terminal minimum size to 70 columns, 20 rows
# termsizeto 70 20 max		# Set terminal maximum size to 70 columns, 20 rows

# Get requested terminal size
NewCols=$1
NewRows=$2

# Get current terminal size
CurCols=$(stty size | cut -d ' ' -f 2)
CurRows=$(stty size | cut -d ' ' -f 1)

# Normalize blank and "auto" sizes to the current terminal size
[ -z "$NewCols" -o "$NewCols" = "auto" ] && NewCols=$CurCols
[ -z "$NewRows" -o "$NewRows" = "auto" ] && NewRows=$CurRows

# If a terminal size is within min/max tolerances, set the requested
# size to the current terminal size so it doesn't change
if [ "$3" = "min" ]; then
	[ "$NewCols" -lt "$CurCols" ] && NewCols=$CurCols
	[ "$NewRows" -lt "$CurRows" ] && NewRows=$CurRows
elif [ "$3" = "max" ]; then
	[ "$NewCols" -gt "$CurCols" ] && NewCols=$CurCols
	[ "$NewRows" -gt "$CurRows" ] && NewRows=$CurRows
fi

# If the requested terminal size is different, change the terminal's size
[ "$CurCols" -ne "$NewCols" -o "$CurRows" -ne "$NewRows" ] && printf '\033[8;%d;%dt' "$NewRows" "$NewCols"

# Save & quit
# Test
termsizeto 70 20

Example aliases for https://wttr.in/

nano ~/.bashrc

# Add to the bottom:
alias weather='wget -qO- "https://wttr.in/" && [ -x "$(command -v "termsizeto")" ] && termsizeto 125 40 min'
alias moon='wget -qO- "https://wttr.in/moon" && [ -x "$(command -v "termsizeto")" ] && termsizeto 70 27 min'
# Save & quit
# Test
# Open a new terminal and make it's size very small
moon
weather

demo

1 Like

Tmux version would be sweeeeeet!

newsboat… there can only be one

newsboat is the RSS feeder of choice for many terminal connoisseurs but there’s an unseemly error that occurs if one attempts to run more than one instance.

$ newsboat
Starting newsboat 2.20.1...
Error: an instance of newsboat is already running (PID: 55701)

Proposing two potential shims for placement in one’s ~/.bashrc to handle newsboat startup.

Either: kill-ing the existing instance prior to running newsboat.

function newsboat (){
	PidOfNewsboat=$( pidof newsboat )
	[ -n "$PidOfNewsboat" ] && kill $PidOfNewsboat
	/usr/bin/newsboat "$@"
}

Or: Using xdotool to shift to the running instance if one exists.

function newsboat (){
	if [ -n "$( pidof newsboat )" ]; then

		# Switch to the workspace `newsboat` is on and
		# unminimize, raise and focus the window.
		xdotool search --name "newsboat" windowactivate
	else

		# `xdotool` needs a unique name applied to the
		# window so it can find it later.
		xprop -id "$WINDOWID" -f WM_NAME 8u -set WM_NAME "newsboat"

		/usr/bin/newsboat "$@"
	fi
}

Notes:

  • When using a shim, always remember to use the full path for the program you’re intending to run with the shim or it’ll create an infinite loop re-running the shim.

  • While xdotool does have the option to search by pid ( ex: xdotool search --pid $Pid ), i’ve never gotten it to work and having dug into getting a window id from a pid it gets very convoluted very quickly. Tools like xdotool, wmctrl and xwit abstract away a lot of weirdness.

Bonus

# Change the titlebar name of the current window
xprop -id "$WINDOWID" -f _NET_WM_NAME 8u -set _NET_WM_NAME "New Title"
3 Likes

Cool. This problem always bothers me with newsboat.

ShellCheck

A terminal analysis tool (also available in many plugins) for getting feedback on ways to improve your Shell scripts.

Live Demo

Click β€œLoad random example” top-left for a nice showcase.

The goals of ShellCheck

  • "To point out and clarify typical beginner’s syntax issues that cause a shell to give cryptic error messages.
  • To point out and clarify typical intermediate level semantic problems that cause a shell to behave strangely and counter-intuitively.
  • To point out subtle caveats, corner cases and pitfalls that may cause an advanced user’s otherwise working script to fail under future circumstances."

https://github.com/koalaman/shellcheck

Available in most popular repos, i’d HIGHLY recommend checking out the project’s git repo for more information.

In action

Example of usage

sudo apt install shellcheck # Debian, Ubuntu
sudo dnf install ShellCheck # Fedora

shellcheck /usr/bin/firefox
In /usr/bin/firefox line 16:
MOZ_APP_LAUNCHER=`which $0`
             ^--------^ SC2006: Use $(...) notation instead of legacy backticked `...`.
                    ^-- SC2086: Double quote to prevent globbing and word splitting.

Did you mean: 
MOZ_APP_LAUNCHER=$(which "$0")
# Wait... firefox is a shell script??

ls -l `which firefox`
lrwxrwxrwx 1 root root 25 Oct 13  2020 /usr/bin/firefox -> ../lib/firefox/firefox.sh
less /usr/lib/firefox/firefox.sh
# Read about how 4 dudes made a debugger helper for alpha/beta phases

# Run the firefox binary directly as God intended
/usr/lib/firefox/firefox
2 Likes

Nice. I learned something about my own scripts. Namely:

  echo -e "###   "$(date '+%Y/%m/%d %H:%M:%S') >> ${podcast_directory}/${podcast_name}.log
                   ^-- SC2046: Quote this to prevent word splitting.

could be written as:

echo -e "###   $(date '+%Y/%m/%d %H:%M:%S')" >> ${podcast_directory}/${podcast_name}.log
  

I forgot that, like perl and php, variable interpolation can occur in double quotes in bash.

Mouse support in terminal with pure BASH

Adding an action to mouse input can be as easy as:

function MouseClick1() {
	echo 'Mouse button 1 was clicked!'
}

This has been a pet Shell project of mine for some time and while it’s definitely not finished this is the first easy to use version. Please excuse the lack of code comments and explanations, this project went a mile deep and I honestly wouldn’t know where to start. If there’s something you’d like to know just ask.

The GIF fps is pretty low so it catches some blank frames but it’s smoother in practice (especially in Alacritty). I also don’t know any tricks yet for making Shell scripts visually performative.

Captures

  • L/M/R mouse click
  • L/M/R mouse drag
  • L/M/R mouse button state (up/down)
  • Scroll wheel up/down
  • Row/Column Coordinates of mouse actions

mouse-in-terminal demo

mouse-in-terminal

mouse-in-terminal

#!/usr/bin/env bash

# To do: <FOSS License here>

# Must be run in the active session to catch inputs
# Examples: `source mouse-in-terminal` or `. mouse-in-terminal`

LogVerbose=
Log(){
	[ -n "$LogVerbose" ] && echo "$1"
}

FunctionExists() {
	declare -f -F "$1" > /dev/null
	return $?
}

# Enable capture
printf '\033[?1000;1002;1006;1015h'

# Declare binds
declare -A Bindings=(
	['<0;']='ReadInput MouseClick1 \<0;'
	['<1;']='ReadInput MouseClick2 \<1;'
	['<2;']='ReadInput MouseClick3 \<2;'
	['<32;']='ReadInput MouseDrag1 \<32;'
	['<33;']='ReadInput MouseDrag2 \<33;' # Supported in Alacritty
	['<34;']='ReadInput MouseDrag3 \<34;'
	['<64;']='ReadInput MouseScrollUp \<64;'
	['<65;']='ReadInput MouseScrollDown \<65;'
)

# Apply binds
for KeySeq in "${!Bindings[@]}"; do
	bind -x "\"\033[$KeySeq\":${Bindings[$KeySeq]}"
done

ReadInput() {
	[ -n "$LogVerbose" ] && Log '=== Read Input ==='
	declare -A Input=()
	Type=$1
	Input['Type']=$Type
	Axis='X'
	Buffer=''

	while read -r -n 1 -s Key; do
		[ -n "$LogVerbose" ] && Log "Reading:$Key"
		Buffer="$Buffer$Key"

		if [[ $Key == ';' ]]; then
			Axis='Y'
		elif [[ $Key =~ [0-9] ]]; then
			Input[$Axis]="${Input[$Axis]}$Key"
		else
			Input['State']=$Key
			break
		fi
	done

	if [ -n "$LogVerbose" ]; then
		Log "EscSeq=\033[$2$Buffer"
		Log "Buffer=$Buffer"
		for AKey in "${!Input[@]}"; do
		    Log "${AKey}=${Input[$AKey]}"
		done
	fi

	# If a function for the type of mouse input exists, run it.
	FunctionExists "${Type}" && ${Type}
}

What I used with it to produce the GIF demo

PS1=''
tput civis
tput setaf 2 # Set color to green
clear

function MouseClick1() {
	tput setaf 2
	DrawBox "${Input['X']}" "${Input['Y']}" "β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ Click"
}

function MouseDrag1() {
	tput setaf 1
	DrawBox "${Input['X']}" "${Input['Y']}" "β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ Drag"
}

function MouseClick2() {
	tput setaf 5
	DrawBox "${Input['X']}" "${Input['Y']}" "β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ M-Click"
}

function MouseDrag2() {
	tput setaf 5
	DrawBox "${Input['X']}" "${Input['Y']}" "β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ M-Drag"
}

function MouseClick3() {
	tput setaf 4
	DrawBox "${Input['X']}" "${Input['Y']}" "β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ R-Click"
}

function MouseDrag3() {
	tput setaf 3
	DrawBox "${Input['X']}" "${Input['Y']}" "β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ R-Drag"
}

ScrollY=10
function MouseScrollUp() {
	tput setaf 6
	(( ScrollY-- ))
	DrawBox 3 "$ScrollY" "β–ˆβ–ˆ Scroll Up"
}
function MouseScrollDown() {
	tput setaf 6
	(( ScrollY++ ))
	DrawBox 3 "$ScrollY" "β–ˆβ–ˆ Scroll Down"
}

function DrawBox() {
	X=$(( $1 - 3 ))
	Y=$(( $2 - 1 ))

	# Only draw if coords are different
	if [ "$X" != "$LastX" -o "$X" != "$LastY" ]; then

		clear
		tput home
		echo -e "\n  x=$X y=$Y state="${Input['State']}""

		LastX=$X
		LastY=$Y

		# Draw new box
		for N in 0 1 2; do
			DrawAtPos "$(( Y + N ))" "$X" "$3"
		done
	fi
}

function DrawAtPos(){
	printf '\033[s' # Save cursor position
	printf "\33[%d;%dH%s" "$@"
	printf '\033[u' # Restore cursor position
}

Showcase for attaching a function to a mouse event

# The script will call functions with the following names when
# a mouse event occurs matching what the name implies. If a
# function for the action doesn't exist, it'll do nothing.

# MouseClick1
# MouseClick2
# MouseClick3
# MouseDrag1
# MouseDrag2
# MouseDrag3
# MouseScrollUp
# MouseScrollDown

# To run a function when a user clicks mouse button 1 for
# instance, just name it "MouseClick1", example:

function MouseClick1() {
	X="${Input['X']}" # Column action occured on
	Y="${Input['Y']}" # Row action occured on
	Type="${Input['Type']}" # Source of the action (ex: MouseClick1)
	State="${Input['State']}" # M is down, m is up (as read)

	if [ "$State" = "m" ]; then HumanReadableState='up'
	elif [ "$State" = "M" ]; then HumanReadableState='down'
	fi

	echo "You did $Type at coordinate $X,$Y with the button $HumanReadableState"
}
2 Likes

A tremendous thank you to the Terminalforlife for covering this thread and helping with feedback!

If you’d like to watch the thread review live here’s the link:

https://www.youtube.com/watch?v=VMiv6Kt3r7s
If you’re not subscribed you’re missing out, TFL’s a wonderful channel.

4 Likes

Get rec -d

Recording microphone input from the terminal.

There’s an app named sox with general repo availability. It’s β€œthe Swiss Army knife of audio manipulation” and they have great examples on their man page.

sudo apt install sox

# Record input from the default input device to file test.ogg
rec --default-device ./test.ogg
# Ctrl+c to quit

# Play recorded audio
play ./test.ogg

The format of the output above is read from the file extension. For example sox will error if given one it doesn’t support.

rec -d ./test.omg

rec FAIL formats: no handler for file extension `omg’

Examples of recording popular formats:

rec -d ./test.ogg
rec -d ./test.mp3
rec -d ./test.wav
rec -d ./test.flac

Sox can record, play and manipulate a tremendous amount of audio formats from .la to .fap (PARIS Audio File), see the full list here.

3 Likes

Used to use sox back about 10 years ago with my recording work. I remember that at that time there was a curious gotcha with the silence on the end of a file. Sox couldn’t remove silence at the end of a file but you could β€œreverse” the file, trim the silence at the beginning, and then β€œre-reverse” the file to the normal order.

I’m not sure why the development on sox stopped. Personally, I moved on to ffmpeg.

I must say that this record from the default device looks very easy.

Deleting a file named --help

Credit to Kris Occhipinti’s channel: Deleting files with Double Dashes in your SHELL - YouTube

The following will fail to delete a file named --help and instead use the file name as if it was an option.

rm --help
rm '--help'
rm "--help"
rm ~/test/*
rm \-\-help
File='--help'
rm "$File"

This applies to anything starting with a -, so -help and -------help are both interpreted as options instead of values

This is also not limited to rm, it’s a quirk of how variable expansion works in the Shell so programs can’t tell how/if things are encapsulated when they read arguments.

This may explain why some projects use syntax like myoption= for interpretation.

Looking at man bash:

--        A  --  signals the end of options and disables further option
          processing.  Any arguments after the -- are treated as  file‐
          names and arguments.  An argument of - is equivalent to --.

By using a -- before --help it removes the problem.

# We can now remove file: --help
rm -- --help

This behavior also occures in ZSH (see: Kris’s video) and probably other shells.

It’s something worth noting for situations where someone else may be in control of file names which are having commands run against them. It could be worth using -- as a best practice in those situations.

3 Likes

I thought I could do it with

rm -i *.

… but no joy.

Nice. So esoteric – but may be handy some day.

1 Like

Decide the next post

  • Self-host a high frequency VPN leak tester
  • Auto-type the contents of your clipboard
  • Self-host your own pure BASH website
  • Visualize ping over time optimized for peripheral awareness
  • Connect and select bluetooth headphones by mac address (Pipewire guide)
  • Use optical recognition to convert text in a selected area of the screen into clipboard text
  • Password creator that uses atmospheric microphone noise for absolute randomness

0 voters

Is it just a thing for TT to always have votes end in perfect draws? lol Next DLN video by any creator posted to the forum decides the pick according to the 1st letter of the first word used at/after the 5 minute mark.

b to g = Auto-type clipboard
h to m = Mic noise password gen
n to s = Self-host BASH website
t to y = Optical recognition to clipboard
a or z = Move to next letter

edit
Just posted, word at 5-min was β€œcalled” so auto-type clipboard is next though i’ll try to get to the other things that were voted on in the near future.

The thing is the vote count is just too low I guess.