Terminal Takeaway πŸ₯‘

True and some people may have not noticed you can do multiple votes.

It’s good information though, i’d prefer to do posts people would like most.

Auto-type the contents of your clipboard


Why auto-type the clipboard?

There’s infrequent occasions I can’t paste something but really need to. Usually it’s when i’m using Spice to perform a new OS install or i’m looking at a TTY. There’s also times I have to use specific remote viewers for work that don’t have clipboard support.

Why isn’t this an xdotool one-liner script?

Auto-typing the contents of the clipboard is deceptively difficult. It can be done in a one-liner but the outcome is best summarized as spray and pray.

  • Most auto-typers including xdotool will turn accidental presses of ctrl, shift and alt into shortcut commands (see: fireworks). This is especially problematic if you’re launching the one-liner with a shortcut combo as it’ll react to these keys the moment it starts.
  • It’s a freight train that won’t stop till it’s done, hope you pasted to the right place.
  • Unless that one-liner is very long, there’s no removal of artifacts or trailing newlines. If text is selected from a document to the last letter of a line, it’ll always include a newline which will auto-run commands if pasted to a terminal and produce un-intuitive auto-type results.

All that’s fixed, introducing…


( pronounced like an ancient Roman name )

  • Auto-types the contents of the keyboard at a user defined rate.
  • Stops typing the moment the current window loses focus so it can be ended with a mouse click.
  • Freezes keyboard input until typing finishes which maintains typing integrity.
  • Removes the trailing newline associated with selecting text to the end of the line.
  • When encountering a newline, the Enter key is pressed.
#!/bin/env sh

# To do: <FOSS License here>

# Examples of usage:
#     cliptokeys
#     cliptokeys 0.5   # 0.5 seconds between keystrokes

# Dependencies:
# xdotool, xinput and (xsel or xclip)

# Installation:
# mkdir -p ~/.local/bin/
# mv ./cliptokeys ~/.local/bin/
# chmod u+x ~/.local/bin/cliptokeys

[ -z "$TypeWaitSec" ] && TypeWaitSec='0.05'

# Check for missing package(s)
if ! which xinput 2> /dev/null; then
	echo "Missing package: xinput"
elif ! which xdotool 2> /dev/null; then
	echo "Missing package: xdotool"

FocusedWindowId=$(xdotool getwindowfocus)

# Get clipboard contents
if which xsel 2> /dev/null; then
	ClipOut=$(xsel -ob)
elif which xclip 2> /dev/null; then
	ClipOut=$(xclip -o -sel c)
	echo "Missing package: xsel OR xclip"

# Handle keyboard locking
KeyboardIds=$(xinput list | grep keyboard | cut -d '=' -f 2 | cut -f 1)
KeyboardStates() {
	for Id in $KeyboardIds; do xinput --$1 $Id; done
trap 'KeyboardStates enable' EXIT
KeyboardStates disable

# Read the output of the clipboard one character at a time into xdotool to type
ClipOutLen=$(wc -c <<< $ClipOut) # Note: Using `expr` doesn't count last newline

while read -rN1 Char; do
	(( CharCount++ ))

	# If the current window loses focus, exit
	[ "$FocusedWindowId" != "$(xdotool getwindowfocus)" ] && exit

	if [ "$Char" = $'\n' ]; then
		# If the newline isn't the last character type the Enter key. Otherwise ignore.
		[ "$CharCount" -ne "$ClipOutLen" ] && xdotool key Return
		xdotool type "$Char"
	sleep $TypeWaitSec

done <<< $ClipOut

Working with remote viewers

Remote viewers often capture keystrokes so local keyboard shorcuts don’t work. You can get around this by clicking on the titlebar of the viewer, hit your chosen shortcut to start the script and it should β€œtype” the text into the focused window inside the viewer. In my case it this works with spice viewer launched from virt-manager.

The Digital Ocean browser based console hates pasting long strings, like SSH keys, and drops tons of characters throughout the string. This probably would have saved me the grief.

Screenshot > text recognition > clipboard

Similar to edge cases you may need cliptokeys for, you may also want to get text that’s stuck in an image or an interface that doesn’t allow select to copy.

Scripting screenshot selection

# Try it
sudo [apt/dnf] install maim
maim --select --hidecursor --format=png --quality=10 "~/MyScreenshot.png"
xdg-open "~/MyScreenshot.png"

There’s A LOT of ways to take a screenshot but I wanted something as script friendly as possible. maim fit the bill well though a good improvement would be supporting what the user already has in a way that’s minimally invasive to the script.

OCR (Optical Character Recognition)

# Try it (bad result)
sudo [apt/dnf] install tesseract
tesseract "~/MyScreenshot.png" "~/MyText.txt"
cat ~/MyText.txt

tesseract is the go-to Linux solution for OCR (it’s been in the TT rules since day 1). From my personal guess it was developed to handle scanned documents way back. The images from which would be very large so if you zoomed to 100% you’d see huge text.

Solving problems: This may be why tesseract is unusable for reading small text from screenshots. Luckily the problem is fixed by up-sizing images by 400% from a great answer here.

Upsizing images for good OCR

A natural choice for scripting image editting is ImageMagik though I have a feeling there’s a lot of ways to do this and i’d love to make the script use Krita, GIMP, ect if the user already has them.

# Try it (good result)
sudo apt install imagemagik # Debian, Ubuntu
sudo dnf install ImageMagik # Fedora

mogrify -modulate 100,0 -resize 400% "~/MyScreenshot.png"
tesseract "~/MyScreenshot.png" "~/MyText.txt"
cat ~/MyText.txt

β€œAll of your namespace is belong to us.”
~ 2050/1/1, ImageMagik upon conquering all of Linux namespace.

Combining the above, I present…

screentoocr (beta)

A project with a terrible name that runs your area selected screenshot through OCR and places the interpreted text in your clipboard.

This project is somewhat accurate but would benefit a lot from better image preparation for OCR such as sharpening edges and contrast.


# sed -i 's/ForumCantShowThisCharacter//g' "$TmpFilePathPrefix.txt"

This command is intended to remove instances of an artifact character that keeps showing up for reasons I don’t know but the forum won’t show it. Just run the program, copy the character into the sed command and uncomment it.

#!/usr/bin/env sh

# Dependencies:
# ImageMagick, sed, xsel/xclip, tesseract, maim


function CleanUp(){
	[ -f "$TmpFilePathPrefix.txt" ] && rm "$TmpFilePathPrefix.txt"
	[ -f "$TmpFilePathPrefix.png" ] && rm "$TmpFilePathPrefix.png"
trap CleanUp EXIT

# User drags cursor over what they want OCR'ed
maim --select --hidecursor --format=png --quality=10 "$TmpFilePathPrefix.png"

# Resize to 400%
mogrify -modulate 100,0 -resize 400% "$TmpFilePathPrefix.png"

# OCR the image
tesseract "$TmpFilePathPrefix.png" "$TmpFilePathPrefix" &> /dev/null

# Remove an artifact that keeps showing up for some reason
# sed -i 's///g' "$TmpFilePathPrefix.txt"

# Place text in clipboard
if [ $(which xsel 2> /dev/null) ]; then
	cat "$TmpFilePathPrefix.txt" | xsel --clipboard
elif [ $(which xclip 2> /dev/null) ]; then
	xclip -selection clipboard "$TmpFilePathPrefix.txt"
	echo "Missing packages: xsel OR xclip"

BONUS: Screenshot > clipboard

Putting an area selected screenshot in your clipboard as an image:

#!/usr/bin/env sh

# Dependencies: maim, xclip

trap '[ -f "$TmpPath" ] && rm "$TmpPath"' EXIT
maim --select --hidecursor --format=png --quality=10 "$TmpPath"
xclip -selection clipboard -t image/png "$TmpPath"
1 Like

Commenting multi-line commands

Multi-line commands combined with full length flags go a long way for decipherability at a glance. Example:

# Try it:
df \
--local \
--all \

…but they can be taken one step further with comments.

It took a while to figure this out and some asking around but the solution is a comment within a subshell prior to the \ next line indicator.

Below is an rsync explicitly defining what it’s doing (favoring no alias bundles like --archive) and the comments come from the man page.

Performance Note: As creating multiple subshells prior to the command running isn’t performant, this is for situations that aren’t millisecond sensitive (although technically commenting anywhere isn’t performative as shell scripts are an interpreted language).

#!/usr/bin/env sh
rsync \
	--update                `# skip files that are newer on the receiver `\
	--links                 `# copy symlinks as symlinks `\
	--atime-preserve=system	`# preserve access times `\
	--hard-links            `# preserve hard links `\
	--executability         `# preserve executability `\
	--acls                  `# preserve ACLs (implies --perms) `\
	--xattrs                `# preserve extended attributes `\
	--owner                 `# preserve owner (super-user only) `\
	--group                 `# preserve group `\
	--devices               `# preserve device files (super-user only) `\
	--specials              `# preserve special files `\
	--times                 `# preserve modification times `\
	--atimes                `# preserve access (use) times `\
	--devices               `# preserve device files (super-user only) `\
	--itemize-changes       `# output a change-summary for all updates `\
	--delete-during         `# receiver deletes during the transfer `\
	--force                 `# force deletion of dirs even if not empty `\
	--recursive             `# recurse into directories `\
	MySourceDir \
1 Like

Put the current date in your clipboard with a keyboard shortcut using XFCE4.

XFCE4 (maybe others) won’t run scripts as a shortcut command but can pass them as an argument to something that will. Ex: /usr/bin/sh -c '<command>'

App Menu > Keyboard > Application Shortcuts (tab) > + Add

# Format: YYYY-MM-DD

# Using xsel
/usr/bin/sh -c 'date +%Y-%m-%d | xsel --clipboard'

# Using xclip
/usr/bin/sh -c 'date +%Y-%m-%d | xclip -selection clipboard'

I get so much custom mileage out of XFCE. Folks be like: with this new release of XYZ you can … Meanwhile, I’ve been doing that for 4 years with XFCE.


Fix an Odysee/YouTube video only playing in 1 ear

So far this is the best approach i’ve found, would love to hear pipewire or pulseaudio solutions.

Listen to a video in mono audio in-place

# Try it

# Listen to an audio test in your default browser
xdg-open 'https://youtu.be/6TWJaFD6R2s?t=6'

# Listen to it in mpv using mono audio
mpv --audio-channels=mono 'https://youtu.be/6TWJaFD6R2s?t=6'
# Example alias for your ~/.bashrc
alias ytv-mpv-mono='mpv --audio-channels=mono '

Download a video and convert it to mono permanently

youtube-dl 'https://youtu.be/6TWJaFD6R2s'
ls # Get the video's filename
ffmpeg -i <VideoFilenameHere> -af "pan=mono|c0=FL" <NewVideoFilenameHere>

Q: I don’t have mpv or ffmpeg how do I get them?

# Debian/Ubuntu
sudo apt install mpv
sudo apt install ffmpeg

# Fedora
# Enable the RPM Fusion 'Free' repo, https://docs.fedoraproject.org/en-US/quick-docs/setup_rpmfusion/
sudo dnf install https://download1.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm
sudo dnf install mpv
sudo dnf install ffmpeg
1 Like

Creating a dummy webcam that displays a static image (and more)

Your refusal to make a Zoom account has finally paid off… you too can now have a static image!

We’re using this beautiful piece of kit: GitHub - umlaeute/v4l2loopback: v4l2-loopback device

sudo [apt/dnf] install v4l-utils v4l2loopback ffmpeg

# Load the v4l2loopback module (this needs to be done every reboot)
sudo modprobe v4l2loopback
# If this doesn't work, see troubleshooting section: https://github.com/umlaeute/v4l2loopback

# Get the path to the v4l2loopback video device named "Dummy"
v4l2-ctl --list-device

# Example output:
Dummy video device (0x0000) (platform:v4l2loopback-000):
# Stream an image to a v4l2loopback device on /dev/video0 using ffmpeg
ffmpeg -loop 1 -re -i '~/Pictures/MyImage.png' -f v4l2 -vcodec rawvideo -pix_fmt yuv420p /dev/video0

# Test the webcam's output with mpv or vlc
# More options here: https://wiki.archlinux.org/title/Webcam_setup#Applications
mpv av://v4l2:/dev/video0
vlc v4l2://:input-slave=alsa://:v4l-vdev='/dev/video0'

Quick notes

  • Zoom will flip the image horizontally and zoom in unless it’s a 16:9 aspect ratio. Doing a horizontal flip and making the image 1920x1080 (which is 16:9) fixed this for me.

Bonus bits

# Create 2 dummy webcams as /dev/video5 and /dev/video6 calling them Cam1 and Cam2
sudo modprobe v4l2loopback video_nr=5,6 card_label=Cam1,Cam2

# See OPTIONS section for more: https://github.com/umlaeute/v4l2loopback
# Stream a video to the dummy webcam
ffmpeg -re -i testsrc.avi -f v4l2 /dev/video0
# Stream your desktop to the dummy webcam
ffmpeg -f x11grab -r 15 -s 1280x720 -i :0.0+0,0 -vcodec rawvideo -pix_fmt yuv420p -threads 0 -f v4l2 /dev/video0
# Record the dummy webcam
ffmpeg -f v4l2 -i /dev/video0 -c copy ~/feed.mkv

Interesting. If I run into something similar I may post a script that keeps it running properly.

Side note for all… wttr.in, the project that allows you to wget the weather in terminal appears to be a Web API for delivering output from the wego project.

So if you want to run it locally without needing a web server you can install wego. I added it to the post above.

1 Like

I’d love a way to remove window decorations (borders) for just one window in XFCE. That’s probably the only thing it’s missing from my perspective.

It can be done by switching theme but that’s pretty clunky and affects all windows. I haven’t seen anything yet that’d let me do it. :confused:

It’s hard for me to read this without seeing it as a challenge. :wink:

1 Like


If you’re recovering from TTY or just want a TUI to manage your mounts, bashmount makes life very easy especially with mounting luks partitions.

It’s packaged for Arch, Fedora and Gentoo but can also be installed with a simple wget because it’s a self-contained BASH script with no dependencies.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━  Internal Media  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

 1) sda1:          EFI System Par...  600M  
 2) sda2:          -                  1G    
 4) zram0:         -                  4G    
 5) luks-5044...:  fedora_main        952.3G [/home]
 6) nvme0n1p1:     EFI System Par...  600M   [/boot/efi]
 7) nvme0n1p2:     -                  1G     [/boot]

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━  Commands  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

 e: eject          i: info        m: mount        o: open        u: unmount

 [Enter]: refresh           a: unmount all        q: quit        ?: help





# Option1: Install via package manager from Arch, Fedora or Gentoo repos
sudo [dnf|?] install bashmount

# Option2: Install from GitHub
sudo wget -qO '/usr/local/bin/bashmount' 'https://raw.githubusercontent.com/jamielinux/bashmount/master/bashmount'
cat /usr/local/bin/bashmount # Inspect
chmod +x /usr/local/bin/bashmount # Make exectuable to all users

# Run


  • There’s an example configuration file you can use here.
  • If you want regular users to be able to manage mounting without sudo, install package udisks2.

Using Matrix with curl

Thank you to Jivan Pal in DLN Matrix for going above and beyond helping me through this.

Disclaimer: This is an unusually raw guide because of the enormity of the subject. It’s intended for getting the reader’s foot in the door and assumes they’re somewhat savy with scripting, JSON, security and the basics of using Element. Do not hesitate to ask questions, I will help you.

curl + Matrix

Matrix is an open messaging protocol with clients similar to Discord and is open to 3rd-party apps by design. Thanks to their very comprehensive REST API, you can do almost anything with curl including but not limited to building a fully featured BASH driven chat client or telling your toaster how brown you’d like your toast.

Guides Warning

One of the hardest hurdles was learning that most Matrix guides (including those on the Matrix site) are out of date and use deprecated APIs which often work on martix.org but not on other deployments. I’d highly recommend sticking to the latest API documentation.

Quickie on Security

  • Create NEW ACCOUNTS for learning the API and for the scripts you’d like to run unless the task can’t be done without access to your personal account.
  • Tokens let you do almost anything your full account can do so keep them safe.
    • chmod 700 your scripts before you start on them.
    • pass is a good example of storing tokens in encrypted files which is extremely easy to use with scripts. For a little more security you can also deliver tokens to curl as inline variables instead of holding them in memory.

Getting the values we need

We need at minimum a base url, access token and optionally a room id to use the Matrix API.

You can use something like jq to extract values from JSON but this is intended as a pure curl terminal guide just to get your feet wet.

# IF the account is on matrix.org use this

# ELIF the account is on a custom domain, get a BaseURL from a GET request
curl '<Base URL of Matrix instance without subdomain(s)>/.well-known/matrix/client/'
BaseUrl='<Extract from JSON: m.homeserver.base_url>'

# Get an access token and user id
# (This command will prompt for Username and Password inline)
# NOTE: Insure your password doesn't contain characters that'll
# escape the "" JSON encapsulation or otherwise muddle the delivery.
# (needs improvement)
curl \
-d '{"type":"m.login.password", "user":"'"$(read -p "Username: " U; printf '%s' "$U")"'", "password":"'"$(read -sp "Password: " P; printf '%s' "$P")"'"}' \

AccessToken='<Extract from JSON: access_token>'

# Get the ID of the room you'd like to interact with
# Easiest: In the Element GUI, use the burger menu
# next to room > Settings > Advanced
RoomId='<Internal room ID>'

# You should have values similar to these by the end


# Whoami
curl \
-X GET \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer '"$AccessToken" \

# Get messages from a room
curl \
-X GET \
-H 'Authorization: Bearer '"$AccessToken" \
NextSyncToken='<Extract from JSON: end>'

# Generate a random, collision free nonce for sending a message
# NOTE: Messages using the same nonce are ignored, it needs
# to be regenerated for every message.
TxnId=$( xxd -l 32 -c 32 -p < /dev/random )

# Send a message to a room
curl \
-X PUT \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer '"$AccessToken" \
-d '{"msgtype":"m.text", "body":"This message was sent from curl"}' \

Revoking tokens

In the Element client…

  • Click your username (top left)
  • Click Security & Privacy
  • Check the sessions you’d like to delete
  • Click β€œDelete X sessions”


Latest API documentation: Client-Server API | Matrix Specification


Anyone have a favorite program for reading/writing JSON from the terminal?

jq is the best tool for the job


Good one… It’s also in practically every repo.

I’ve heard some good things about jshon as a faster alternative though it’s not on Fedora and I like things that are easy to add to guides.

jq -cn -r --arg body mybody '{"msgtype":"m.text", "body":$body}' {"msgtype":"m.text","body":"mybody"}

Ive found that even with larger json arrays jq acts well.

take this array…

curl -s https://raw.githubusercontent.com/prust/wikipedia-movie-data/master/movies.json | jq ".[]"

This array of 28795 movies takes less than a second to run.

curl -s https://raw.githubusercontent.com/prust/wikipedia-movie-data/master/movies.json | jq -rc ".[] | [.title,.year,.genres[]] | @csv" takes even less time, which means Im probably just limited to what Alacrity can render

1 Like

The link was worth my visit today. Thanks!

Non-iffy ways of setting default values

Note the use of the β€œ:” which prevents the variable running as a command.

# If Color DOESN'T contain a value, set it to green
unset Color
: ${Color:='green'}
echo "$Color" # Output: green

Setting temporary defaults

# If Color1 DOESN'T contain a value, Color2's value is green
unset Color1
echo "$Color1" # Output:
echo "$Color2" # Output: green

# If Color1 DOES contain a value, Color2's value is green
echo "$Color1" # Output: red
echo "$Color2" # Output: green