Terminal Takeaway πŸ₯‘

Controlling Keyboard LEDs

Try it:

xset led named "Scroll Lock"
xset -led named "Scroll Lock"

Behavior:

  • xset won’t cause the system to forget or mismatch the organic state of the LED. Turning an LED β€œoff” with xset just returns it to it’s organic state so if it was originally on before turning it β€œon” then it’ll stay on.
  • xset doesn’t activate the mode the LED is associated with. *

Going beyond Scroll Lock:

So what about the other LEDs?? This command will turn them all on…

# 1. Turn on the Christmas Tree!
xset led on

# 2. Express disappointment

# 3. Check if it did anything
xset q
Keyboard Control:
  auto repeat:  on    key click percent:  0    LED mask:  ffffe7fc
  XKB indicators:
    00: Caps Lock:   off    01: Num Lock:    off    02: Scroll Lock: on 
    03: Compose:     on     04: Kana:        on     05: Sleep:       on 
    06: Suspend:     on     07: Mute:        on     08: Misc:        on 
    09: Mail:        on     10: Charging:    on     11: Shift Lock:  off
    12: Group 2:     off    13: Mouse Keys:  on 
# 4. Switch LEDs off
xset led off

* It turns out most distros consider Caps Lock and Num Lock LEDs forbidden fruit and for good reason. Below is a guide to allow you to use them but (at least on my machine) they reliably produce unpredictable keyboard results such as the Enter key being held down, Shift being held down, being totally locked out of the numlock keys and sometimes all of these behaviors stick around even after the LEDs are turned off.

Scroll lock appears to be the only β€œsafe” LED compared to the fore-mentioned. Caps Lock is usually ok and Num Lock opens the door to the chaos dimension.

# Enabling Caps Lock and Num Lock LEDs (caution, see above ^)
# Guide applies to: Debian, Ubuntu, Fedora and CentOS

# === Option 1. Manually adjust: ===
# Caps Lock:
sudo nano /usr/share/X11/xkb/compat/ledcaps
# Change: !allowExplicit; to allowExplicit;

# Num Lock:
sudo nano /usr/share/X11/xkb/compat/lednum
# Change: !allowExplicit; to allowExplicit;

# === Option 2. Auto-adjust: ===
# Caps Lock:
sudo sed -i 's|\!allowExplicit|allowExplicit|g' /usr/share/X11/xkb/compat/ledcaps

# Num Lock:
sudo sed -i 's|\!allowExplicit|allowExplicit|g' /usr/share/X11/xkb/compat/lednum

# Logout and back in

# Test
xset led named "Caps Lock"
xset -led named "Caps Lock"
xset led named "Num Lock"
xset -led named "Num Lock"

Scroll Lock LED alarm script:

cd /usr/local/bin
sudo touch ./sl-alarm
sudo chmod 755 ./sl-alarm
sudo nano ./sl-alarm
# Paste the following:
#!/usr/bin/env sh

# If the application is terminated, set Scroll Lock LED off
trap 'xset -led named "Scroll Lock"' EXIT

while true; do
	xset led named "Scroll Lock"
	sleep 0.4
	xset -led named "Scroll Lock"
	sleep 0.4
done
# Save & quit
# Run
sl-alarm
# Ctrl + c to quit

Extra:

You can also activate LEDs using their id from xset q + 1

# Example:
xset led 1 # Caps Lock
xset led 2 # Num Lock
xset led 3 # Scroll Lock
2 Likes

Does anyone know of a way to send keystrokes to an X window? Other than…

xdotool type "something"
xdotool key "a"
xvkbd -text "something"

List:

xdotool
xvkbd
crikey [website] - thanks to Ethanol

This may not be exactly what you’re looking for but…

I know a program called β€œcrikey”

https://shallowsky.com/software/crikey/

It’s used for outputting a string with a hotkey wherever the cursor is. You can pipe stuff into it as well.
Example:
echo "hello" | crikey -i -s 2

This will print β€œhello” after two seconds to the active window. I use this with zbarcam to get raw barcode data from a webcam and type it into a database form input.

Example:
zbarcam /dev/video0 --raw | crikey -i

1 Like

That’s a good tool to know about.

Ironically one of the things that makes it so cool (handling of special codes) makes it not work for my project although getting more options was sort of a quest for a silver bullet anyway and that’s quickly been turning into the realization I need to do a lot of custom handling to make this work.

I’ve been putting off the description because It’s solvable with a single line of code unless I start describing the 1,000 edge cases :P. I’ll post it soon.

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:

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