diff options
Diffstat (limited to 'dotfiles/system/.local/bin')
100 files changed, 3885 insertions, 0 deletions
diff --git a/dotfiles/system/.local/bin/AAXtoMP3 b/dotfiles/system/.local/bin/AAXtoMP3 new file mode 100755 index 0000000..adc91ef --- /dev/null +++ b/dotfiles/system/.local/bin/AAXtoMP3 @@ -0,0 +1,908 @@ +#!/usr/bin/env bash + + +# ======================================================================== +# Command Line Options + +# Usage Synopsis. +usage=$'\nUsage: AAXtoMP3 [--flac] [--aac] [--opus ] [--single] [--level <COMPRESSIONLEVEL>] +[--chaptered] [-e:mp3] [-e:m4a] [-e:m4b] [--authcode <AUTHCODE>] [--no-clobber] +[--target_dir <PATH>] [--complete_dir <PATH>] [--validate] [--loglevel <LOGLEVEL>] +[--keep-author <N>] [--author <AUTHOR>] [--{dir,file,chapter}-naming-scheme <STRING>] +[--use-audible-cli-data] [--continue <CHAPTERNUMBER>] {FILES}\n' +codec=libmp3lame # Default encoder. +extension=mp3 # Default encoder extension. +level=-1 # Compression level. Can be given for mp3, flac and opus. -1 = default/not specified. +mode=chaptered # Multi file output +auth_code= # Required to be set via file or option. +targetdir= # Optional output location. Note default is basedir of AAX file. +dirNameScheme= # Custom directory naming scheme, default is $genre/$author/$title +customDNS=0 +fileNameScheme= # Custom file naming scheme, default is $title +customFNS=0 +chapterNameScheme= # Custom chapter naming scheme, default is '$title-$(printf %0${#chaptercount}d $chapternum) $chapter' (BookTitle-01 Chapter 1) +customCNS=0 +completedir= # Optional location to move aax files once the decoding is complete. +container=mp3 # Just in case we need to change the container. Used for M4A to M4B +VALIDATE=0 # Validate the input aax file(s) only. No Transcoding of files will occur +loglevel=1 # Loglevel: 0: Show progress only; 1: default; 2: a little more information, timestamps; 3: debug +noclobber=0 # Default off, clobber only if flag is enabled +continue=0 # Default off, If set Transcoding is skipped and chapter splitting starts at chapter continueAt. +continueAt=1 # Optional chapter to continue splitting the chapters. +keepArtist=-1 # Default off, if set change author metadata to use the passed argument as field +authorOverride= # Override the author, ignoring the metadata +audibleCli=0 # Default off, Use additional data gathered from mkb79/audible-cli +aaxc_key= # Initialize variables, in case we need them in debug_vars +aaxc_iv= # Initialize variables, in case we need them in debug_vars + +# ----- +# Code tip Do not have any script above this point that calls a function or a binary. If you do +# the $1 will no longer be a ARGV element. So you should only do basic variable setting above here. +# +# Process the command line options. This allows for un-ordered options. Sorta like a getops style +while true; do + case "$1" in + # Flac encoding + -f | --flac ) codec=flac; extension=flac; mode=single; container=flac; shift ;; + # Apple m4a music format. + -a | --aac ) codec=copy; extension=m4a; mode=single; container=m4a; shift ;; + # Ogg Format + -o | --opus ) codec=libopus; extension=opus; container=ogg; shift ;; + # If appropriate use only a single file output. + -s | --single ) mode=single; shift ;; + # If appropriate use only a single file output. + -c | --chaptered ) mode=chaptered; shift ;; + # This is the same as --single option. + -e:mp3 ) codec=libmp3lame; extension=mp3; mode=single; container=mp3; shift ;; + # Identical to --acc option. + -e:m4a ) codec=copy; extension=m4a; mode=single; container=mp4; shift ;; + # Similar to --aac but specific to audio books + -e:m4b ) codec=copy; extension=m4b; mode=single; container=mp4; shift ;; + # Change the working dir from AAX directory to what you choose. + -t | --target_dir ) targetdir="$2"; shift 2 ;; + # Use a custom directory naming scheme, with variables. + -D | --dir-naming-scheme ) dirNameScheme="$2"; customDNS=1; shift 2 ;; + # Use a custom file naming scheme, with variables. + -F | --file-naming-scheme ) fileNameScheme="$2"; customFNS=1; shift 2 ;; + # Use a custom chapter naming scheme, with variables. + --chapter-naming-scheme ) chapterNameScheme="$2"; customCNS=1; shift 2 ;; + # Move the AAX file to a new directory when decoding is complete. + -C | --complete_dir ) completedir="$2"; shift 2 ;; + # Authorization code associate with the AAX file(s) + -A | --authcode ) auth_code="$2"; shift 2 ;; + # Don't overwrite the target directory if it already exists + -n | --no-clobber ) noclobber=1; shift ;; + # Extremely verbose output. + -d | --debug ) loglevel=3; shift ;; + # Set loglevel. + -l | --loglevel ) loglevel="$2"; shift 2 ;; + # Validate ONLY the aax file(s) No transcoding occurs + -V | --validate ) VALIDATE=1; shift ;; + # continue splitting chapters at chapter continueAt + --continue ) continueAt="$2"; continue=1; shift 2 ;; + # Use additional data got with mkb79/audible-cli + --use-audible-cli-data ) audibleCli=1; shift ;; + # Compression level + --level ) level="$2"; shift 2 ;; + # Keep author number n + --keep-author ) keepArtist="$2"; shift 2 ;; + # Author override + --author ) authorOverride="$2"; shift 2 ;; + # Command synopsis. + -h | --help ) printf "$usage" $0 ; exit ;; + # Standard flag signifying the end of command line processing. + -- ) shift; break ;; + # Anything else stops command line processing. + * ) break ;; + + esac +done + +# ----- +# Empty argv means we have nothing to do so lets bark some help. +if [ "$#" -eq 0 ]; then + printf "$usage" $0 + exit 1 +fi + +# Setup safer bash script defaults. +set -o errexit -o noclobber -o nounset -o pipefail + +# ======================================================================== +# Utility Functions + +# ----- +# debug +# debug "Some longish message" +debug() { + if [ $loglevel == 3 ] ; then + echo "$(date "+%F %T%z") DEBUG ${1}" + fi +} + +# ----- +# debug dump contents of a file to STDOUT +# debug "<full path to file>" +debug_file() { + if [ $loglevel == 3 ] ; then + echo "$(date "+%F %T%z") DEBUG" + echo "=Start==========================================================================" + cat "${1}" + echo "=End============================================================================" + fi +} + +# ----- +# debug dump a list of internal script variables to STDOUT +# debug_vars "Some Message" var1 var2 var3 var4 var5 +debug_vars() { + if [ $loglevel == 3 ] ; then + msg="$1"; shift ; # Grab the message + args=("$@") # Grab the rest of the args + + # determine the length of the longest key + l=0 + for (( n=0; n<${#args[@]}; n++ )) ; do + (( "${#args[$n]}" > "$l" )) && l=${#args[$n]} + done + + # Print the Debug Message + echo "$(date "+%F %T%z") DEBUG ${msg}" + echo "=Start==========================================================================" + + # Using the max length of a var name we dynamically create the format. + fmt="%-"${l}"s = %s\n" + + for (( n=0; n<${#args[@]}; n++ )) ; do + eval val="\$${args[$n]}" ; # We save off the value of the var in question for ease of coding. + + echo "$(printf "${fmt}" ${args[$n]} "${val}" )" + done + echo "=End============================================================================" + fi +} + +# ----- +# log +log() { + if [ "$((${loglevel} > 1))" == "1" ] ; then + echo "$(date "+%F %T%z") ${1}" + else + echo "${1}" + fi +} + +# ----- +#progressbar produces a progressbar in the style of +# process: |####### | XX% (part/total unit) +# which is gonna be overwritten by the next line. + +progressbar() { + #get input + part=${1} + total=${2} + + #compute percentage and make print_percentage the same length regardless of the number of digits. + percentage=$((part*100/total)) + if [ "$((percentage<10))" = "1" ]; then print_percentage=" $percentage" + elif [ "$((percentage<100))" = "1" ]; then print_percentage=" $percentage" + else print_percentage="$percentage"; fi + + #draw progressbar with one # for every 5% and blank spaces for the missing part. + progressbar="" + for (( n=0; n<(percentage/5); n++ )) ; do progressbar="$progressbar#"; done + for (( n=0; n<(20-(percentage/5)); n++ )) ; do progressbar="$progressbar "; done + + #print progressbar + echo -ne "Chapter splitting: |$progressbar| $print_percentage% ($part/$total chapters)\r" +} +# Print out what we have already after command line processing. +debug_vars "Command line options as set" codec extension mode container targetdir completedir auth_code keepArtist authorOverride audibleCli + +# ======================================================================== +# Variable validation + +if [ $(uname) = 'Linux' ]; then + GREP="grep" + FIND="find" + SED="sed" +else + GREP="ggrep" + FIND="gfind" + SED="gsed" +fi + + +# ----- +# Detect which annoying version of grep we have +if ! [[ $(type -P "$GREP") ]]; then + echo "$GREP (GNU grep) is not in your PATH" + echo "Without it, this script will break." + echo "On macOS, you may want to try: brew install grep" + exit 1 +fi + +# ----- +# Detect which annoying version of find we have +if ! [[ $(type -P "$FIND") ]]; then + echo "$FIND (GNU find) is not in your PATH" + echo "Without it, this script will break." + echo "On macOS, you may want to try: brew install findutils" + exit 1 +fi + +# ----- +# Detect which annoying version of sed we have +if ! [[ $(type -P "$SED") ]]; then + echo "$SED (GNU sed) is not in your PATH" + echo "Without it, this script will break." + echo "On macOS, you may want to try: brew install gnu-sed" + exit 1 +fi + +# ----- +# Detect ffmpeg and ffprobe +if [[ "x$(type -P ffmpeg)" == "x" ]]; then + echo "ERROR ffmpeg was not found on your env PATH variable" + echo "Without it, this script will break." + echo "INSTALL:" + echo "MacOS: brew install ffmpeg" + echo "Ubuntu: sudo apt-get update; sudo apt-get install ffmpeg libav-tools x264 x265 bc" + echo "Ubuntu (20.04): sudo apt-get update; sudo apt-get install ffmpeg x264 x265 bc" + echo "RHEL: yum install ffmpeg" + exit 1 +fi + +# ----- +# Detect ffmpeg and ffprobe +if [[ "x$(type -P ffprobe)" == "x" ]]; then + echo "ERROR ffprobe was not found on your env PATH variable" + echo "Without it, this script will break." + echo "INSTALL:" + echo "MacOS: brew install ffmpeg" + echo "Ubuntu: sudo apt-get update; sudo apt-get install ffmpeg libav-tools x264 x265 bc" + echo "RHEL: yum install ffmpeg" + exit 1 +fi + + +# ----- +# Detect if we need mp4art for cover additions to m4a & m4b files. +if [[ "x${container}" == "xmp4" && "x$(type -P mp4art)" == "x" ]]; then + echo "WARN mp4art was not found on your env PATH variable" + echo "Without it, this script will not be able to add cover art to" + echo "m4b files. Note if there are no other errors the AAXtoMP3 will" + echo "continue. However no cover art will be added to the output." + echo "INSTALL:" + echo "MacOS: brew install mp4v2" + echo "Ubuntu: sudo apt-get install mp4v2-utils" +fi + +# ----- +# Detect if we need mp4chaps for adding chapters to m4a & m4b files. +if [[ "x${container}" == "xmp4" && "x$(type -P mp4chaps)" == "x" ]]; then + echo "WARN mp4chaps was not found on your env PATH variable" + echo "Without it, this script will not be able to add chapters to" + echo "m4a/b files. Note if there are no other errors the AAXtoMP3 will" + echo "continue. However no chapter data will be added to the output." + echo "INSTALL:" + echo "MacOS: brew install mp4v2" + echo "Ubuntu: sudo apt-get install mp4v2-utils" +fi + +# ----- +# Detect if we need mediainfo for adding description and narrator +if [[ "x$(type -P mediainfo)" == "x" ]]; then + echo "WARN mediainfo was not found on your env PATH variable" + echo "Without it, this script will not be able to add the narrator" + echo "and description tags. Note if there are no other errors the AAXtoMP3" + echo "will continue. However no such tags will be added to the output." + echo "INSTALL:" + echo "MacOS: brew install mediainfo" + echo "Ubuntu: sudo apt-get install mediainfo" +fi + +# ----- +# Obtain the authcode from either the command line, local directory or home directory. +# See Readme.md for details on how to acquire your personal authcode for your personal +# audible AAX files. +if [ -z $auth_code ]; then + if [ -r .authcode ]; then + auth_code=`head -1 .authcode` + elif [ -r ~/.authcode ]; then + auth_code=`head -1 ~/.authcode` + fi +fi + +# ----- +# Check the target dir for if set if it is writable +if [[ "x${targetdir}" != "x" ]]; then + if [[ ! -w "${targetdir}" || ! -d "${targetdir}" ]] ; then + echo "ERROR Target Directory does not exist or is not writable: \"$targetdir\"" + echo "$usage" + exit 1 + fi +fi + +# ----- +# Check the completed dir for if set if it is writable +if [[ "x${completedir}" != "x" ]]; then + if [[ ! -w "${completedir}" || ! -d "${completedir}" ]] ; then + echo "ERROR Complete Directory does not exist or is not writable: \"$completedir\"" + echo "$usage" + exit 1 + fi +fi + +# ----- +# Check whether the loglevel is valid +if [ "$((${loglevel} < 0 || ${loglevel} > 3 ))" = "1" ]; then + echo "ERROR loglevel has to be in the range from 0 to 3!" + echo " 0: Show progress only" + echo " 1: default" + echo " 2: a little more information, timestamps" + echo " 3: debug" + echo "$usage" + exit 1 +fi +# ----- +# If a compression level is given, check whether the given codec supports compression level specifiers and whether the level is valid. +if [ "${level}" != "-1" ]; then + if [ "${codec}" == "flac" ]; then + if [ "$((${level} < 0 || ${level} > 12 ))" = "1" ]; then + echo "ERROR Flac compression level has to be in the range from 0 to 12!" + echo "$usage" + exit 1 + fi + elif [ "${codec}" == "libopus" ]; then + if [ "$((${level} < 0 || ${level} > 10 ))" = "1" ]; then + echo "ERROR Opus compression level has to be in the range from 0 to 10!" + echo "$usage" + exit 1 + fi + elif [ "${codec}" == "libmp3lame" ]; then + if [ "$((${level} < 0 || ${level} > 9 ))" = "1" ]; then + echo "ERROR MP3 compression level has to be in the range from 0 to 9!" + echo "$usage" + exit 1 + fi + else + echo "ERROR This codec doesnt support compression levels!" + echo "$usage" + exit 1 + fi +fi + +# ----- +# Clean up if someone hits ^c or the script exits for any reason. +trap 'rm -r -f "${working_directory}"' EXIT + +# ----- +# Set up some basic working files ASAP. Note the trap will clean this up no matter what. +working_directory=`mktemp -d 2>/dev/null || mktemp -d -t 'mytmpdir'` +metadata_file="${working_directory}/metadata.txt" + +# ----- +# Validate the AAX and extract the metadata associated with the file. +validate_aax() { + local media_file + media_file="$1" + + # Test for existence + if [[ ! -r "${media_file}" ]] ; then + log "ERROR File NOT Found: ${media_file}" + return 1 + else + if [[ "${VALIDATE}" == "1" ]]; then + log "Test 1 SUCCESS: ${media_file}" + fi + fi + + # Clear the errexit value we want to capture the output of the ffprobe below. + set +e errexit + + # Take a look at the aax file and see if it is valid. If the source file is aaxc, we give ffprobe additional flags + output="$(ffprobe -loglevel warning ${decrypt_param} -i "${media_file}" 2>&1)" + + # If invalid then say something. + if [[ $? != "0" ]] ; then + # No matter what lets bark that something is wrong. + log "ERROR: Invalid File: ${media_file}" + elif [[ "${VALIDATE}" == "1" ]]; then + # If the validate option is present then lets at least state what is valid. + log "Test 2 SUCCESS: ${media_file}" + fi + + # This is a big test only performed when the --validate switch is passed. + if [[ "${VALIDATE}" == "1" ]]; then + output="$(ffmpeg -hide_banner ${decrypt_param} -i "${media_file}" -vn -f null - 2>&1)" + if [[ $? != "0" ]] ; then + log "ERROR: Invalid File: ${media_file}" + else + log "Test 3 SUCCESS: ${media_file}" + fi + fi + + # Dump the output of the ffprobe command. + debug "$output" + + # Turn it back on. ffprobe is done. + set -e errexit +} + +validate_extra_files() { + local extra_media_file extra_find_command + extra_media_file="$1" + # Bash trick to delete, non greedy, from the end up until the first '-' + extra_title="${extra_media_file%-*}" + + # Using this is not ideal, because if the naming scheme is changed then + # this part of the script will break + # AAX file: BookTitle-LC_128_44100_stereo.aax + # Cover file: BookTitle_(1215).jpg + # Chapter file: BookTitle-chapters.json + + # Chapter + extra_chapter_file="${extra_title}-chapters.json" + + # Cover + extra_dirname="$(dirname "${extra_media_file}")" + extra_find_command='$FIND "${extra_dirname}" -maxdepth 1 -regex ".*/${extra_title##*/}_([0-9]+)\.jpg"' + # We want the output of the find command, we will turn errexit on later + set +e errexit + extra_cover_file="$(eval ${extra_find_command})" + extra_eval_comm="$(eval echo ${extra_find_command})" + set -e errexit + + if [[ "${aaxc}" == "1" ]]; then + # bash trick to get file w\o extention (delete from end to the first '.') + extra_voucher="${extra_media_file%.*}.voucher" + if [[ ! -r "${extra_voucher}" ]] ; then + log "ERROR File NOT Found: ${extra_voucher}" + return 1 + fi + aaxc_key=$(jq -r '.content_license.license_response.key' "${extra_voucher}") + aaxc_iv=$(jq -r '.content_license.license_response.iv' "${extra_voucher}") + fi + + debug_vars "Audible-cli variables" extra_media_file extra_title extra_chapter_file extra_cover_file extra_find_command extra_eval_comm extra_dirname extra_voucher aaxc_key aaxc_iv + + # Test for chapter file existence + if [[ ! -r "${extra_chapter_file}" ]] ; then + log "ERROR File NOT Found: ${extra_chapter_file}" + return 1 + fi + if [[ "x${extra_cover_file}" == "x" ]] ; then + log "ERROR Cover File NOT Found" + return 1 + fi + + debug "All expected audible-cli related file are here" +} + +# ----- +# Inspect the AAX and extract the metadata associated with the file. +save_metadata() { + local media_file + media_file="$1" + ffprobe -i "$media_file" 2> "$metadata_file" + if [[ $(type -P mediainfo) ]]; then + echo "Mediainfo data START" >> "$metadata_file" + # Mediainfo output is structured like ffprobe, so we append it to the metadata file and then parse it with get_metadata_value() + # mediainfo "$media_file" >> "$metadata_file" + # Or we only get the data we are intrested in: + # Description + echo "Track_More :" "$(mediainfo --Inform="General;%Track_More%" "$media_file")" >> "$metadata_file" + # Narrator + echo "nrt :" "$(mediainfo --Inform="General;%nrt%" "$media_file")" >> "$metadata_file" + # Publisher + echo "pub :" "$(mediainfo --Inform="General;%pub%" "$media_file")" >> "$metadata_file" + echo "Mediainfo data END" >> "$metadata_file" + fi + if [[ "${audibleCli}" == "1" ]]; then + # If we use data we got with audible-cli, we delete conflicting chapter infos + $SED -i '/^ Chapter #/d' "${metadata_file}" + # Some magic: we parse the .json generated by audible-cli. + # to get the output structure like the one generated by ffprobe, + # we use some characters (#) as placeholder, add some new lines, + # put a ',' after the start value, we calculate the end of each chapter + # as start+length, and we convert (divide) the time stamps from ms to s. + # Then we delete all ':' since they make a filename invalid. + jq -r '.content_metadata.chapter_info.chapters[] | "Chapter # start: \(.start_offset_ms/1000), end: \((.start_offset_ms+.length_ms)/1000) \n#\n# Title: \(.title)"' "${extra_chapter_file}" \ + | tr -d ':' >> "$metadata_file" + fi + debug "Metadata file $metadata_file" + debug_file "$metadata_file" +} + +# ----- +# Reach into the meta data and extract a specific value. +# This is a long pipe of transforms. +# This finds the first occurrence of the key : value pair. +get_metadata_value() { + local key + key="$1" + # Find the key in the meta data file # Extract field value # Remove the following /'s "(Unabridged) <blanks> at start end and multiples. + echo "$($GREP --max-count=1 --only-matching "${key} *: .*" "$metadata_file" | cut -d : -f 2- | $SED -e 's#/##g;s/ (Unabridged)//;s/^[[:blank:]]\+//g;s/[[:blank:]]\+$//g' | $SED 's/[[:blank:]]\+/ /g')" +} + +# ----- +# specific variant of get_metadata_value bitrate is important for transcoding. +get_bitrate() { + get_metadata_value bitrate | $GREP --only-matching '[0-9]\+' +} + +# Save the original value, since in the for loop we overwrite +# $audibleCli in case the file is aaxc. If the file is the +# old aax, reset the variable to be the one passed by the user +originalAudibleCliVar=$audibleCli +# ======================================================================== +# Main Transcode Loop +for aax_file +do + # If the file is in aaxc format, set the proper variables + if [[ ${aax_file##*.} == "aaxc" ]]; then + # File is the new .aaxc + aaxc=1 + audibleCli=1 + else + # File is the old .aax + aaxc=0 + # If some previous file in the loop are aaxc, the $audibleCli variable has been overwritten, so we reset it to the original one + audibleCli=$originalAudibleCliVar + fi + + debug_vars "Variables set based on file extention" aaxc originalAudibleCliVar audibleCli + + # No point going on if no authcode found and the file is aax. + # If we use aaxc as input, we do not need it + # if the string $auth_code is null and the format is not aaxc; quit. We need the authcode + if [ -z $auth_code ] && [ "${aaxc}" = "0" ]; then + echo "ERROR Missing authcode, can't decode $aax_file" + echo "$usage" + exit 1 + fi + + # Validate the input aax file. Note this happens no matter what. + # It's just that if the validate option is set then we skip to next file. + # If however validate is not set and we proceed with the script any errors will + # case the script to stop. + + # If the input file is aaxc, we need to first get the audible_key and audible_iv + # We get them in the function validate_extra_files + + if [[ ${audibleCli} == "1" ]] ; then + # If we have additional files (obtained via audible-cli), be sure that they + # exists and they are in the correct location. + validate_extra_files "${aax_file}" + fi + + # Set the needed params to decrypt the file. Needed in all command that require ffprobe or ffmpeg + # After validate_extra_files, since the -audible_key and -audible_iv are read in that function + if [[ ${aaxc} == "1" ]] ; then + decrypt_param="-audible_key ${aaxc_key} -audible_iv ${aaxc_iv}" + else + decrypt_param="-activation_bytes ${auth_code}" + fi + + validate_aax "${aax_file}" + if [[ ${VALIDATE} == "1" ]] ; then + # Don't bother doing anything else with this file. + continue + fi + + # ----- + # Make sure everything is a variable. Simplifying Command interpretation + save_metadata "${aax_file}" + genre=$(get_metadata_value genre) + if [ "x${authorOverride}" != "x" ]; then + #Manual Override + artist="${authorOverride}" + album_artist="${authorOverride}" + else + if [ "${keepArtist}" != "-1" ]; then + # Choose artist from the one that are present in the metadata. Comma separated list of names + # remove leading space; 'C. S. Lewis' -> 'C.S. Lewis' + artist="$(get_metadata_value artist | cut -d',' -f"$keepArtist" | $SED -E 's|^ ||g; s|\. +|\.|g; s|((\w+\.)+)|\1 |g')" + album_artist="$(get_metadata_value album_artist | cut -d',' -f"$keepArtist" | $SED -E 's|^ ||g; s|\. +|\.|g; s|((\w+\.)+)|\1 |g')" + else + # The default + artist=$(get_metadata_value artist) + album_artist="$(get_metadata_value album_artist)" + fi + fi + title=$(get_metadata_value title) + title=${title:0:128} + bitrate="$(get_bitrate)k" + album="$(get_metadata_value album)" + album_date="$(get_metadata_value date)" + copyright="$(get_metadata_value copyright)" + + # Get more tags with mediainfo + if [[ $(type -P mediainfo) ]]; then + narrator="$(get_metadata_value nrt)" + description="$(get_metadata_value Track_More)" + publisher="$(get_metadata_value pub)" + else + narrator="" + description="" + publisher="" + fi + + # Define the output_directory + if [ "${customDNS}" == "1" ]; then + currentDirNameScheme="$(eval echo "${dirNameScheme}")" + else + # The Default + currentDirNameScheme="${genre}/${artist}/${title}" + fi + + # If we defined a target directory, use it. Otherwise use the location of the AAX file + if [ "x${targetdir}" != "x" ] ; then + output_directory="${targetdir}/${currentDirNameScheme}/" + else + output_directory="$(dirname "${aax_file}")/${currentDirNameScheme}/" + fi + + # Define the output_file + if [ "${customFNS}" == "1" ]; then + currentFileNameScheme="$(eval echo "${fileNameScheme}")" + else + # The Default + currentFileNameScheme="${title}" + fi + output_file="${output_directory}/${currentFileNameScheme}.${extension}" + + if [[ "${noclobber}" = "1" ]] && [[ -d "${output_directory}" ]]; then + log "Noclobber enabled but directory '${output_directory}' exists. Exiting to avoid overwriting" + exit 0 + fi + mkdir -p "${output_directory}" + + if [ "$((${loglevel} > 0))" = "1" ]; then + # Fancy declaration of which book we are decoding. Including the AUTHCODE. + dashline="----------------------------------------------------" + log "$(printf '\n----Decoding---%s%s--%s--' "${title}" "${dashline:${#title}}" "${auth_code}")" + log "Source: ${aax_file}" + fi + + # Big long DEBUG output. Fully describes the settings used for transcoding. + # Note this is a long debug command. It's not critical to operation. It's purely for people debugging + # and coders wanting to extend the script. + debug_vars "Book and Variable values" title auth_code aaxc aaxc_key aaxc_iv mode aax_file container codec bitrate artist album_artist album album_date genre copyright narrator description publisher currentDirNameScheme output_directory currentFileNameScheme output_file metadata_file working_directory + + + # Display the total length of the audiobook in format hh:mm:ss + # 10#$var force base-10 interpretation. By default it's base-8, so values like 08 or 09 are not octal numbers + total_length="$(ffprobe -v error ${decrypt_param} -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "${aax_file}" | cut -d . -f 1)" + hours="$((total_length/3600))" + if [ "$((hours<10))" = "1" ]; then hours="0$hours"; fi + minutes="$((total_length/60-60*10#$hours))" + if [ "$((minutes<10))" = "1" ]; then minutes="0$minutes"; fi + seconds="$((total_length-3600*10#$hours-60*10#$minutes))" + if [ "$((seconds<10))" = "1" ]; then seconds="0$seconds"; fi + log "Total length: $hours:$minutes:$seconds" + + # If level != -1 specify a compression level in ffmpeg. + compression_level_param="" + if [ "${level}" != "-1" ]; then + compression_level_param="-compression_level ${level}" + fi + + # ----- + if [ "${continue}" == "0" ]; then + # This is the main work horse command. This is the primary transcoder. + # This is the primary transcode. All the heavy lifting is here. + debug 'ffmpeg -loglevel error -stats ${decrypt_param} -i "${aax_file}" -vn -codec:a "${codec}" -ab ${bitrate} -map_metadata -1 -metadata title="${title}" -metadata artist="${artist}" -metadata album_artist="${album_artist}" -metadata album="${album}" -metadata date="${album_date}" -metadata track="1/1" -metadata genre="${genre}" -metadata copyright="${copyright}" "${output_file}"' + </dev/null ffmpeg -loglevel error \ + -stats \ + ${decrypt_param} \ + -i "${aax_file}" \ + -vn \ + -codec:a "${codec}" \ + ${compression_level_param} \ + -ab ${bitrate} \ + -map_metadata -1 \ + -metadata title="${title}" \ + -metadata artist="${artist}" \ + -metadata album_artist="${album_artist}" \ + -metadata album="${album}" \ + -metadata date="${album_date}" \ + -metadata track="1/1" \ + -metadata genre="${genre}" \ + -metadata copyright="${copyright}" \ + -metadata description="${description}" \ + -metadata composer="${narrator}" \ + -metadata publisher="${publisher}" \ + -f ${container} \ + "${output_file}" + if [ "$((${loglevel} > 0))" == "1" ]; then + log "Created ${output_file}." + fi + # ----- + fi + # Grab the cover art if available. + cover_file="${output_directory}/cover.jpg" + extra_crop_cover='' + if [ "${continue}" == "0" ]; then + if [ "${audibleCli}" == "1" ]; then + # We have a better quality cover file, copy it. + if [ "$((${loglevel} > 1))" == "1" ]; then + log "Copy cover file to ${cover_file}..." + fi + cp "${extra_cover_file}" "${cover_file}" + + # We now set a variable, ${extra_crop_cover}, which contains an additional + # ffmpeg flag. It crops the cover so the width and the height is divisible by two. + # Since the standard (in the aax file) image resolution is 512, we set the flag + # only if we use a custom cover art. + extra_crop_cover='-vf crop=trunc(iw/2)*2:trunc(ih/2)*2' + else + # Audible-cli not used, extract the cover from the aax file + if [ "$((${loglevel} > 1))" == "1" ]; then + log "Extracting cover into ${cover_file}..." + fi + </dev/null ffmpeg -loglevel error -activation_bytes "${auth_code}" -i "${aax_file}" -an -codec:v copy "${cover_file}" + fi + fi + + # ----- + # If mode=chaptered, split the big converted file by chapter and remove it afterwards. + # Not all audio encodings make sense with multiple chapter outputs (see options section) + if [ "${mode}" == "chaptered" ]; then + # Playlist m3u support + playlist_file="${output_directory}/${currentFileNameScheme}.m3u" + if [ "${continue}" == "0" ]; then + if [ "$((${loglevel} > 0))" == "1" ]; then + log "Creating PlayList ${currentFileNameScheme}.m3u" + fi + echo '#EXTM3U' > "${playlist_file}" + fi + + # Determine the number of chapters. + chaptercount=$($GREP -Pc "Chapter.*start.*end" $metadata_file) + if [ "$((${loglevel} > 0))" == "1" ]; then + log "Extracting ${chaptercount} chapter files from ${output_file}..." + if [ "${continue}" == "1" ]; then + log "Continuing at chapter ${continueAt}:" + fi + fi + chapternum=1 + #start progressbar for loglevel 0 and 1 + if [ "$((${loglevel} < 2))" == "1" ]; then + progressbar 0 ${chaptercount} + fi + # We pipe the metadata_file in read. + # Example of the section that we are interested in: + # + # Chapter #0:0: start 0.000000, end 1928.231474 + # Metadata: + # title : Chapter 1 + # + # Then read the line in these variables: + # first Chapter + # _ #0:0: + # _ start + # chapter_start 0.000000, + # _ end + # chapter_end 1928.231474 + while read -r -u9 first _ _ chapter_start _ chapter_end + do + # Do things only if the line starts with 'Chapter' + if [[ "${first}" = "Chapter" ]]; then + # The next line (Metadata:...) gets discarded + read -r -u9 _ + # From the line 'title : Chapter 1' we save the third field and those after in chapter + read -r -u9 _ _ chapter + + # The formatting of the chapters names and the file names. + # Chapter names are used in a few place. + # Define the chapter_file + if [ "${customCNS}" == "1" ]; then + chapter_title="$(eval echo "${chapterNameScheme}")" + else + # The Default + chapter_title="${title}-$(printf %0${#chaptercount}d $chapternum) ${chapter}" + fi + chapter_file="${output_directory}/${chapter_title}.${extension}" + + # Since the .aax file allready got converted we can use + # -acodec copy, which is much faster than a reencodation. + # Since there is an issue when using copy on flac, where + # the duration of the chapters gets shown as if they where + # as long as the whole audiobook. + chapter_codec="" + if test "${extension}" = "flac"; then + chapter_codec="flac "${compression_level_param}"" + else + chapter_codec="copy" + fi + + #Since there seems to be a bug in some older versions of ffmpeg, which makes, that -ss and -to + #have to be apllied to the output file, this makes, that -ss and -to get applied on the input for + #ffmpeg version 4+ and on the output for all older versions. + split_input="" + split_output="" + if [ "$(($(ffmpeg -version | $SED -E 's/[^0-9]*([0-9]).*/\1/g;1q') > 3))" = "1" ]; then + split_input="-ss ${chapter_start%?} -to ${chapter_end}" + else + split_output="-ss ${chapter_start%?} -to ${chapter_end}" + fi + + # Big Long chapter debug + debug_vars "Chapter Variables:" cover_file chapter_start chapter_end chapternum chapter chapterNameScheme chapter_title chapter_file + if [ "$((${continueAt} > ${chapternum}))" = "0" ]; then + # Extract chapter by time stamps start and finish of chapter. + # This extracts based on time stamps start and end. + if [ "$((${loglevel} > 1))" == "1" ]; then + log "Splitting chapter ${chapternum}/${chaptercount} start:${chapter_start%?}(s) end:${chapter_end}(s)" + fi + </dev/null ffmpeg -loglevel quiet \ + -nostats \ + ${split_input} \ + -i "${output_file}" \ + -i "${cover_file}" \ + ${extra_crop_cover} \ + ${split_output} \ + -map 0:0 \ + -map 1:0 \ + -acodec ${chapter_codec} \ + -metadata:s:v title="Album cover" \ + -metadata:s:v comment="Cover (Front)" \ + -metadata track="${chapternum}" \ + -metadata title="${chapter}" \ + -metadata:s:a title="${chapter}" \ + -metadata:s:a track="${chapternum}" \ + -map_chapters -1 \ + -f ${container} \ + "${chapter_file}" + # ----- + if [ "$((${loglevel} < 2))" == "1" ]; then + progressbar ${chapternum} ${chaptercount} + fi + # OK lets get what need for the next chapter in the Playlist m3u file. + # Playlist creation. + duration=$(echo "${chapter_end} - ${chapter_start%?}" | bc) + echo "#EXTINF:${duration%.*},${title} - ${chapter}" >> "${playlist_file}" + echo "${chapter_title}.${extension}" >> "${playlist_file}" + fi + chapternum=$((chapternum + 1 )) + fi + done 9< "$metadata_file" + + # Clean up of working directory stuff. + rm "${output_file}" + if [ "$((${loglevel} > 1))" == "1" ]; then + log "Done creating chapters for ${output_directory}." + else + #ending progress bar + echo "" + fi + else + # Perform file tasks on output file. + # ---- + # ffmpeg seems to copy only chapter position, not chapter names. + if [[ ${container} == "mp4" && $(type -P mp4chaps) ]]; then + ffprobe -i "${aax_file}" -print_format csv -show_chapters 2>/dev/null | awk -F "," '{printf "CHAPTER%02d=%02d:%02d:%02.3f\nCHAPTER%02dNAME=%s\n", NR, $5/60/60, $5/60%60, $5%60, NR, $8}' > "${output_directory}/${currentFileNameScheme}.chapters.txt" + mp4chaps -i "${output_file}" + fi + fi + + # ----- + # Announce that we have completed the transcode + if [ "$((${loglevel} > 0))" == "1" ]; then + log "Complete ${title}" + fi + # Lastly get rid of any extra stuff. + rm "${metadata_file}" + + # Move the aax file if the decode is completed and the --complete_dir is set to a valid location. + # Check the target dir for if set if it is writable + if [[ "x${completedir}" != "x" ]]; then + if [ "$((${loglevel} > 0))" == "1" ]; then + log "Moving Transcoded ${aax_file} to ${completedir}" + fi + mv "${aax_file}" "${completedir}" + fi + +done diff --git a/dotfiles/system/.local/bin/airplanemodetoggle b/dotfiles/system/.local/bin/airplanemodetoggle new file mode 100755 index 0000000..c98e144 --- /dev/null +++ b/dotfiles/system/.local/bin/airplanemodetoggle @@ -0,0 +1,33 @@ +#!/bin/bash + +if [ "$(printf "On\\nOff" | dmenu -i -p "Set airplane mode:")" = "On" ] +then + notify-send "Airplane Mode" "Turning on airplane mode...." + sudo systemctl stop bluetooth.service + sudo systemctl stop expressvpn.service + sudo systemctl stop sshd.service + sudo systemctl stop syncthing@cjennings.service + sudo systemctl stop avahi-daemon.service + sudo systemctl stop cronie.service + sudo systemctl stop cups.service + sudo ip link set wlp170s0 down + sudo systemctl stop wpa_supplicant.service + sudo systemctl stop NetworkManager.service + sudo nmcli radio all off + sudo powertop --auto-tune + notify-send "Airplane Mode" "Airplane mode is now on." +else + notify-send "Airplane Mode" "Turning off airplane mode....." + sudo nmcli radio wifi on + sudo systemctl start NetworkManager.service + sudo systemctl start wpa_supplicant.service + sudo ip link set wlp170s0 up + sudo systemctl start bluetooth.service + sudo systemctl start expressvpn.service + sudo systemctl start sshd.service + sudo systemctl start syncthing@cjennings.service + sudo systemctl start avahi-daemon.service + sudo systemctl start cronie.service + sudo systemctl start cups.service + notify-send "Airplane Mode" "Airplane mode is now off." +fi diff --git a/dotfiles/system/.local/bin/audioselect b/dotfiles/system/.local/bin/audioselect new file mode 100755 index 0000000..13ffc36 --- /dev/null +++ b/dotfiles/system/.local/bin/audioselect @@ -0,0 +1,70 @@ +#!/bin/sh +# Craig Jennings +# convenience script to switch audio devices +# need bluez and bluez-utils + +# DEVICE MAC ADDRESSES +marshall_earbuds_device="00:25:D1:1B:39:CA" +marshall_headset_device="9C:0D:AC:05:1E:C9" + +# SINKS (Audio Out) +marshall_headset_sink="bluez_output.9C_0D_AC_05_1E_C9.a2dp-sink" +marshall_earbuds_sink="bluez_output.00_25_D1_1B_39_CA.a2dp_sink" +builtin_sink="alsa_output.pci-0000_00_1f.3.analog-stereo" +jabra_510_sink="alsa_output.usb-0b0e_Jabra_SPEAK_510_USB_1C48F9C067D5020A00-00.analog-stereo" +steelseries_sink="alsa_output.usb-SteelSeries_SteelSeries_Arctis_7-00.stereo-game" +emberton_sink="bluez_sink.04_21_44_89_D0_BE.a2dp_sink" + +# SOURCES (Audio In) +jabra_510_source="alsa_input.usb-0b0e_Jabra_SPEAK_510_USB_1C48F9C067D5020A00-00.mono-fallback" +builtin_source="alsa_input.pci-0000_00_1f.3.analog-stereo" +steelseries_source="alsa_input.usb-SteelSeries_SteelSeries_Arctis_7-00.mono-chat" + +CHOICES="Cancel\nToggle Mute Speaker\nToggle Mute Mic\nMarshall Headset & Jabra Mic\nMarshall Headset & Default Mic\nMarshall Earbuds & Jabra Mic\nJabra Speaker & Mic\nBuilt-In Audio" + +CHOSEN=$(echo -e "$CHOICES" | dmenu -l 10) + +case "$CHOSEN" in +"Toggle Mute Speaker") + pactl set-sink-mute 0 toggle + dwmstatus + ;; +"Toggle Mute Mic") + pactl set-source-mute 0 toggle + dwmstatus + ;; +"Marshall Headset & Jabra Mic") + bluetooth power on + bluetoothctl connect $marshall_headset_device + pactl set-default-sink $marshall_headset_sink + pactl set-default-source $jabra_510_source + ;; +"Marshall Headset & Default Mic") + bluetooth power on + bluetoothctl connect $marshall_headset_device + pactl set-default-sink $marshall_headset_sink + pactl set-default-source $builtin_source + ;; +"Marshall Earbuds & Jabra Mic") + bluetooth power on + bluetoothctl connect $marshall_earbuds_device + pactl set-default-sink $marshall_earbuds_sink + pactl set-default-source $jabra_510_source + ;; +"Built-In Audio") + pactl set-default-sink $builtin_audio_sink + pactl set-default-source $builtin_audio_source + ;; +"Jabra Speaker & Mic") + pactl set-default-sink $jabra_510_sink + pactl set-default-source $jabra_510_source + ;; +"Emberton & Built-In") + pactl set-default-sink $emberton_sink + pactl set-default-source $builtin_audio_source + ;; +"Steelseries Headset") + pactl set-default-sink $steelseries_sink + pactl set-default-source $steelseries_source + ;; +esac diff --git a/dotfiles/system/.local/bin/battery_monitor b/dotfiles/system/.local/bin/battery_monitor new file mode 100755 index 0000000..7c6e013 --- /dev/null +++ b/dotfiles/system/.local/bin/battery_monitor @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +# battery_monitor +# Intended to be run via .xinitrc +# - Exit automatically if no battery (desktop) +# - When below 15%, warn user of low battery +# - When below 10%, suspend within 10 seconds if not charging +# +# Craig Jennings <c@cjennings.net> + +# check if acpi is installed +if ! command -v acpi &> /dev/null; then + echo "acpi is not installed. Cannot continue. Exiting...." + exit 1 +fi + +# exit if a battery exists +if [ ! -d "/sys/class/power_supply/BAT0" ] && [ ! -d "/sys/class/power_supply/BAT1" ]; then + echo "Acpi is installed but no battery detected. Assuming this is a desktop and exiting...." + exit 1 +fi + +while true; do + # Get the current battery percentage using acpi + battery_percentage=$(acpi -b | awk -F ', ' '{print $2}' | tr -d '%') + # battery_percentage=$(acpi -b | awk -F ', ' '{print $2}' | sed 's/%//') + + # When below 10%, suspend within 10 seconds if not charging + if [ "$battery_percentage" -lt 11 ] && ! acpi -a | grep -q "on-line" ; then + # Send a notification of sleeping in 10 seconds + notify-send "Critical Battery" "Battery is at $battery_percentage%. System entering sleep in 30 seconds." + + # sleep for 10 seconds, then abort if charging + sleep 30 + + # Check if the system is charging (AC adapter connected) + if acpi -a | grep -q "on-line"; then + notify-send "Charging" "The system is now charging. No action taken." + else + notify-send "Critical Battery" "Putting the system to sleep." + sudo systemctl suspend + fi + fi + + # When below 15%, warn user + if [ "$battery_percentage" -lt 15 ] && ! acpi -a | grep -q "on-line" ; then + # Send a notification using notify-send and dunst + notify-send "Low Battery" "Battery is at $battery_percentage%. System will automatically sleep at 10%." + fi + + # Sleep for 5 minutes before checking again + sleep 300 +done diff --git a/dotfiles/system/.local/bin/bookfind b/dotfiles/system/.local/bin/bookfind new file mode 100755 index 0000000..c5cc1bc --- /dev/null +++ b/dotfiles/system/.local/bin/bookfind @@ -0,0 +1,5 @@ +#!/bin/sh +# allows user to open a calibre book using dmenu + +find ~/Library/ -type f \( -iname \*.pdf -o -iname \*.epub \) | dmenu -i -l 20 -p "Choose an ebook:" + diff --git a/dotfiles/system/.local/bin/brightness b/dotfiles/system/.local/bin/brightness new file mode 100755 index 0000000..9142f33 --- /dev/null +++ b/dotfiles/system/.local/bin/brightness @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +# Craig Jennings <c@cjennings.net> +# DWM convenience script for changing backlight +# depends on xbacklight + +increment=10 + +case $1 in + "max") + sudo xbacklight -set 100%; + ;; + "min") + sudo xbacklight -set 5%; + ;; + "up") + # get current setting as an int + current=$( printf "%.0f" "$(xbacklight -get)" ) + + # add the increment + newvalue=$(("$current" + "$increment")) + + # don't let the brightness go above 100 + [ "$newvalue" -ge 100 ] && newvalue=100; + + # set the value + xbacklight -set "$newvalue"; + ;; + "down") + current=$( printf "%.0f" "$(xbacklight -get)" ) + newvalue=$(("$current" - "$increment")) + [ "$newvalue" -le 5 ] && newvalue=5; + xbacklight -set "$newvalue"; + ;; +esac +newvalue=$( printf "%.0f" "$(xbacklight -get)") +notify-send "backlight" "backlight now set to $newvalue" diff --git a/dotfiles/system/.local/bin/bsdnet_bounce b/dotfiles/system/.local/bin/bsdnet_bounce new file mode 100755 index 0000000..e4eec08 --- /dev/null +++ b/dotfiles/system/.local/bin/bsdnet_bounce @@ -0,0 +1,6 @@ +if [ $(id -u) -eq 0 ] +then + service netif restart && service routing restart +else + echo "You must be root to run this script" +fi diff --git a/dotfiles/system/.local/bin/build.emacs.aur.sh b/dotfiles/system/.local/bin/build.emacs.aur.sh new file mode 100755 index 0000000..a185437 --- /dev/null +++ b/dotfiles/system/.local/bin/build.emacs.aur.sh @@ -0,0 +1,6 @@ +#!/bin/sh +rm -rf emacs-git +git clone https://aur.archlinux.org/emacs-git.git +cd emacs-git +sed -i 's/^JIT=\( \)/JIT="YES"/' PKGBUILD +makepkg --syncdeps --install diff --git a/dotfiles/system/.local/bin/build.emacs.src.sh b/dotfiles/system/.local/bin/build.emacs.src.sh new file mode 100755 index 0000000..275a453 --- /dev/null +++ b/dotfiles/system/.local/bin/build.emacs.src.sh @@ -0,0 +1,76 @@ +#!/bin/sh + +EMACSGITREPO=~/code/emacs +TAG=emacs-29.3 + +printf "\n\n... buildin' Emacs time ...\n" + +clone_repo() { + printf "...grabbing a fresh copy of the source. this takes a minute...\n\n" + git clone https://github.com/mirrors/emacs.git $EMACSGITREPO + cd $EMACSGITREPO +} + +nuke_repo() { + cd $HOME + printf "...nuking %s...\n\n" $EMACSGITREPO + sudo rm -rf $EMACSGITREPO +} + +pull_latest() { + printf "...okay, but lemme tidy up this mess first...\n\n" + cd $EMACSGITREPO + make clean + printf "...pulling some fresh source code...\n\n" + git pull +} + +build_emacs() { + printf "...checking out tag %s...\n\n" $TAG + git checkout $TAG + printf "...starting the build...\n\n" + cd $EMACSGITREPO + $EMACSGITREPO/autogen.sh + $EMACSGITREPO/configure --with-x-toolkit=lucid \ + --with-modules \ + --with-xwidgets \ + --with-treesitter \ + --with-mailutils \ + --with-libsystemd \ + --with-imagemagick \ + --with-xml2 \ + --with-libsystemd \ + -with-gif --with-jpeg --with-png --with-tiff \ + CFLAGS='-O2 -march=native' + make -j$(nproc) +} + +if [ -d "$EMACSGITREPO" ]; then + printf "\n...one sec. an Emacs repository exists at %s already. Shall I first...\n" $EMACSGITREPO + printf "(r)efresh the existing repository with any latest commits, or\n" + printf "(c)lobber what's there and grab a fresh copy of the source, or \n" + printf "(b)uild what's there?\n\n" + read -p "Your choice: (r/c/s): " answer + printf "\n\n" + case ${answer:0:1} in + c|C ) + nuke_repo + clone_repo + ;; + r|R) + pull_latest + ;; + b|B) + printf "...ok, just buildin'...\n\n" + ;; + * ) + printf "wake up. you didn't choose a valid option. enjoy your day.\n\n" + exit 1 + ;; + esac +else + printf "...everything checks out, so let's get this rolling....\n" + clone_repo +fi + +build_emacs diff --git a/dotfiles/system/.local/bin/calibre-install b/dotfiles/system/.local/bin/calibre-install new file mode 100755 index 0000000..42b007c --- /dev/null +++ b/dotfiles/system/.local/bin/calibre-install @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +# remember location +export SRCDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" + +# download and install latest calibre +sudo -v && wget -nv -O- https://download.calibre-ebook.com/linux-installer.sh | sudo sh /dev/stdin + +# install current plugins and config +# tar -xf $SRCDIR/../assets/calibre.tar.gz -C ~/.config/ diff --git a/dotfiles/system/.local/bin/colorpick b/dotfiles/system/.local/bin/colorpick new file mode 100755 index 0000000..b5e1aff --- /dev/null +++ b/dotfiles/system/.local/bin/colorpick @@ -0,0 +1,6 @@ +#!/bin/sh +# displays colorpicker app +# turns cursor into crosshairs, adds preview at bottom left +# selected color is added to the clipboard + +colorpicker --short --one-shot --preview | xsel -b diff --git a/dotfiles/system/.local/bin/cron/README.md b/dotfiles/system/.local/bin/cron/README.md new file mode 100644 index 0000000..fa0c354 --- /dev/null +++ b/dotfiles/system/.local/bin/cron/README.md @@ -0,0 +1,11 @@ +# Important Note + +These cronjobs have components that require information about your current display to display notifications correctly. + +When you add them as cronjobs, I recommend you precede the command with commands as those below: + +``` +export DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(id -u $USER)/bus; export DISPLAY=:0; . $HOME/.zprofile; then_command_goes_here +``` + +This ensures that notifications will display, xdotool commands will function and environmental variables will work as well. diff --git a/dotfiles/system/.local/bin/cron/checkup b/dotfiles/system/.local/bin/cron/checkup new file mode 100755 index 0000000..bd3c634 --- /dev/null +++ b/dotfiles/system/.local/bin/cron/checkup @@ -0,0 +1,17 @@ +#!/bin/sh + +# Syncs repositories and downloads updates, meant to be run as a cronjob. + +notify-send "π¦ Repository Sync" "Checking for package updates..." + +sudo pacman -Syyuw --noconfirm || notify-send "Error downloading updates. + +Check your internet connection, if pacman is already running, or run update manually to see errors." +pkill -RTMIN+8 "${STATUSBAR:-dwmblocks}" + +if pacman -Qu | grep -v "\[ignored\]" +then + notify-send "π Repository Sync" "Updates available. Click statusbar icon (π¦) for update." +else + notify-send "π¦ Repository Sync" "Sync complete. No new packages for update." +fi diff --git a/dotfiles/system/.local/bin/cron/crontog b/dotfiles/system/.local/bin/cron/crontog new file mode 100755 index 0000000..5aba5e6 --- /dev/null +++ b/dotfiles/system/.local/bin/cron/crontog @@ -0,0 +1,6 @@ +#!/bin/sh + +# Toggles all cronjobs off/on. +# Stores disabled crontabs in ~/.consaved until restored. + +([ -f "${XDG_CONFIG_HOME:-$HOME/.config}"/cronsaved ] && crontab - < "${XDG_CONFIG_HOME:-$HOME/.config}"/cronsaved && rm "${XDG_CONFIG_HOME:-$HOME/.config}"/cronsaved && notify-send "π Cronjobs re-enabled.") || ( crontab -l > "${XDG_CONFIG_HOME:-$HOME/.config}"/cronsaved && crontab -r && notify-send "π Cronjobs saved and disabled.") diff --git a/dotfiles/system/.local/bin/debugemacs b/dotfiles/system/.local/bin/debugemacs new file mode 100755 index 0000000..4585be1 --- /dev/null +++ b/dotfiles/system/.local/bin/debugemacs @@ -0,0 +1,4 @@ + #!/bin/sh + EMACS_PID=`pgrep emacs` + cd /home/cjennings/Projects/emacs/src + exec -a debug-emacs $TERM -e gdb /usr/local/bin/emacs $EMACS_PID diff --git a/dotfiles/system/.local/bin/displayselect b/dotfiles/system/.local/bin/displayselect new file mode 100755 index 0000000..f9e8062 --- /dev/null +++ b/dotfiles/system/.local/bin/displayselect @@ -0,0 +1,83 @@ +#!/bin/sh + +# A UI for detecting and selecting all displays. Probes xrandr for connected +# displays and lets user select one to use. User may also select "manual +# selection" which opens arandr. + +twoscreen() { # If multi-monitor is selected and there are two screens. + + mirror=$(printf "no\\nyes" | dmenu -i -p "Mirror displays?") + # Mirror displays using native resolution of external display and a scaled + # version for the internal display + if [ "$mirror" = "yes" ]; then + external=$(echo "$screens" | dmenu -i -p "Optimize resolution for:") + internal=$(echo "$screens" | grep -v "$external") + + res_external=$(xrandr --query | sed -n "/^$external/,/\+/p" | \ + tail -n 1 | awk '{print $1}') + res_internal=$(xrandr --query | sed -n "/^$internal/,/\+/p" | \ + tail -n 1 | awk '{print $1}') + + res_ext_x=$(echo "$res_external" | sed 's/x.*//') + res_ext_y=$(echo "$res_external" | sed 's/.*x//') + res_int_x=$(echo "$res_internal" | sed 's/x.*//') + res_int_y=$(echo "$res_internal" | sed 's/.*x//') + + scale_x=$(echo "$res_ext_x / $res_int_x" | bc -l) + scale_y=$(echo "$res_ext_y / $res_int_y" | bc -l) + + xrandr --output "$external" --auto --scale 1.0x1.0 \ + --output "$internal" --auto --same-as "$external" \ + --scale "$scale_x"x"$scale_y" + else + + primary=$(echo "$screens" | dmenu -i -p "Select primary display:") + secondary=$(echo "$screens" | grep -v "$primary") + direction=$(printf "left\\nright" | dmenu -i -p "What side of $primary should $secondary be on?") + xrandr --output "$primary" --auto --scale 1.0x1.0 --output "$secondary" --"$direction"-of "$primary" --auto --scale 1.0x1.0 + fi + } + +morescreen() { # If multi-monitor is selected and there are more than two screens. + primary=$(echo "$screens" | dmenu -i -p "Select primary display:") + secondary=$(echo "$screens" | grep -v "$primary" | dmenu -i -p "Select secondary display:") + direction=$(printf "left\\nright" | dmenu -i -p "What side of $primary should $secondary be on?") + tertiary=$(echo "$screens" | grep -v "$primary" | grep -v "$secondary" | dmenu -i -p "Select third display:") + xrandr --output "$primary" --auto --output "$secondary" --"$direction"-of "$primary" --auto --output "$tertiary" --"$(printf "left\\nright" | grep -v "$direction")"-of "$primary" --auto + } + +multimon() { # Multi-monitor handler. + case "$(echo "$screens" | wc -l)" in + 2) twoscreen ;; + *) morescreen ;; + esac ;} + +onescreen() { # If only one output available or chosen. + xrandr --output "$1" --auto --scale 1.0x1.0 $(echo "$allposs" | grep -v "\b$1" | awk '{print "--output", $1, "--off"}' | paste -sd ' ' -) + } + +postrun() { # Stuff to run to clean up. + setbg # Fix background if screen size/arangement has changed. + remaps # Re-remap keys if keyboard added (for laptop bases) + { killall dunst ; setsid -f dunst ;} >/dev/null 2>&1 # Restart dunst to ensure proper location on screen + } + +# Get all possible displays +allposs=$(xrandr -q | grep "connected") + +# Get all connected screens. +screens=$(echo "$allposs" | awk '/ connected/ {print $1}') + +# If there's only one screen +[ "$(echo "$screens" | wc -l)" -lt 2 ] && + { onescreen "$screens"; postrun; notify-send "π» Only one screen detected." "Using it in its optimal settings..."; exit ;} + +# Get user choice including multi-monitor and manual selection: +chosen=$(printf "%s\\nmulti-monitor\\nmanual selection" "$screens" | dmenu -i -p "Select display arangement:") && +case "$chosen" in + "manual selection") arandr ; exit ;; + "multi-monitor") multimon ;; + *) onescreen "$chosen" ;; +esac + +postrun diff --git a/dotfiles/system/.local/bin/dmenuexitmenu b/dotfiles/system/.local/bin/dmenuexitmenu new file mode 100755 index 0000000..5570364 --- /dev/null +++ b/dotfiles/system/.local/bin/dmenuexitmenu @@ -0,0 +1,12 @@ +#!/bin/sh + +menuitems=("Lock ξ² \nSuspend σ°€ \nLogout σ°© \nReboot σ°ͺ \nShutdown ο \nCancel σ°Ί") +choice=$(echo -e $menuitems | dmenu -nb "#DAA520" -nf "#2E3440" -sb "#2E3440" -sf "#DAA520") + +case "$choice" in + "Logout σ°© ") loginctl terminate-user $(whoami) ;; + "Lock ξ² ") slock ;; + "Suspend σ°€ ") systemctl suspend;; + "Shutdown ο ") systemctl poweroff;; + "Reboot σ°ͺ ") systemctl reboot ;; +esac diff --git a/dotfiles/system/.local/bin/dmenuhandler b/dotfiles/system/.local/bin/dmenuhandler new file mode 100755 index 0000000..1c48f3a --- /dev/null +++ b/dotfiles/system/.local/bin/dmenuhandler @@ -0,0 +1,21 @@ +#!/bin/sh + +# Feed this script a link and it will give dmenu +# some choice programs to use to open it. +feed="${1:-$(printf "%s" | dmenu -p 'Paste URL or file path')}" + +case "$(printf "Copy URL\\nsxiv\\nsetbg\\nPDF\\nbrowser\\nlynx\\nvim\\nmpv\\nmpv loop\\nmpv float\\nqueue download\\nqueue yt-dlp\\nqueue yt-dlp audio" | dmenu -i -p "Open it with?")" in + "Copy URL") echo "$feed" | xclip -selection clipboard ;; + mpv) setsid -f mpv -quiet "$feed" >/dev/null 2>&1 ;; + "mpv loop") setsid -f mpv -quiet --loop "$feed" >/dev/null 2>&1 ;; + "mpv float") setsid -f "$TERMINAL" -e mpv --geometry=+0-0 --autofit=30% --title="mpvfloat" "$feed" >/dev/null 2>&1 ;; + "queue yt-dlp") qndl "$feed" >/dev/null 2>&1 ;; + "queue yt-dlp audio") qndl "$feed" 'yt-dlp --embed-metadata -icx -f bestaudio/best' >/dev/null 2>&1 ;; + "queue download") qndl "$feed" 'curl -LO' >/dev/null 2>&1 ;; + PDF) curl -sL "$feed" > "/tmp/$(echo "$feed" | sed "s|.*/||;s/%20/ /g")" && zathura "/tmp/$(echo "$feed" | sed "s|.*/||;s/%20/ /g")" >/dev/null 2>&1 ;; + sxiv) curl -sL "$feed" > "/tmp/$(echo "$feed" | sed "s|.*/||;s/%20/ /g")" && sxiv -a "/tmp/$(echo "$feed" | sed "s|.*/||;s/%20/ /g")" >/dev/null 2>&1 ;; + vim) curl -sL "$feed" > "/tmp/$(echo "$feed" | sed "s|.*/||;s/%20/ /g")" && setsid -f "$TERMINAL" -e "$EDITOR" "/tmp/$(echo "$feed" | sed "s|.*/||;s/%20/ /g")" >/dev/null 2>&1 ;; + setbg) curl -L "$feed" > $XDG_CACHE_HOME/pic ; xwallpaper --zoom $XDG_CACHE_HOME/pic >/dev/null 2>&1 ;; + browser) setsid -f "$BROWSER" "$feed" >/dev/null 2>&1 ;; + lynx) lynx "$feed" >/dev/null 2>&1 ;; +esac diff --git a/dotfiles/system/.local/bin/dmenumount b/dotfiles/system/.local/bin/dmenumount new file mode 100755 index 0000000..3cb1f81 --- /dev/null +++ b/dotfiles/system/.local/bin/dmenumount @@ -0,0 +1,67 @@ +#!/bin/sh + +# Gives a dmenu prompt to mount unmounted drives and Android phones. If +# they're in /etc/fstab, they'll be mounted automatically. Otherwise, you'll +# be prompted to give a mountpoint from already existsing directories. If you +# input a novel directory, it will prompt you to create that directory. + +getmount() { \ + [ -z "$chosen" ] && exit 1 + # shellcheck disable=SC2086 + mp="$(find $1 2>/dev/null | dmenu -i -p "Type in mount point.")" || exit 1 + test -z "$mp" && exit 1 + if [ ! -d "$mp" ]; then + mkdiryn=$(printf "No\\nYes" | dmenu -i -p "$mp does not exist. Create it?") || exit 1 + [ "$mkdiryn" = "Yes" ] && (mkdir -p "$mp" || sudo -A mkdir -p "$mp") + fi + } + +mountusb() { \ + chosen="$(echo "$usbdrives" | dmenu -i -p "Mount which drive?")" || exit 1 + chosen="$(echo "$chosen" | awk '{print $1}')" + sudo -A mount "$chosen" 2>/dev/null && notify-send "π» USB mounting" "$chosen mounted." && exit 0 + alreadymounted=$(lsblk -nrpo "name,type,mountpoint" | awk '$3!~/\/boot|\/home$|SWAP/&&length($3)>1{printf "-not ( -path *%s -prune ) ",$3}') + getmount "/mnt /media /mount /home -maxdepth 5 -type d $alreadymounted" + partitiontype="$(lsblk -no "fstype" "$chosen")" + case "$partitiontype" in + "vfat") sudo -A mount -t vfat "$chosen" "$mp" -o rw,umask=0000;; + "exfat") sudo -A mount "$chosen" "$mp" -o uid="$(id -u)",gid="$(id -g)";; + *) sudo -A mount "$chosen" "$mp"; user="$(whoami)"; ug="$(groups | awk '{print $1}')"; sudo -A chown "$user":"$ug" "$mp";; + esac + notify-send "π» USB mounting" "$chosen mounted to $mp." + } + +mountandroid() { \ + chosen="$(echo "$anddrives" | dmenu -i -p "Which Android device?")" || exit 1 + chosen="$(echo "$chosen" | cut -d : -f 1)" + getmount "$HOME -maxdepth 3 -type d" + simple-mtpfs --device "$chosen" "$mp" + echo "OK" | dmenu -i -p "Tap Allow on your phone if it asks for permission and then press enter" || exit 1 + simple-mtpfs --device "$chosen" "$mp" + notify-send "π€ Android Mounting" "Android device mounted to $mp." + } + +asktype() { \ + choice="$(printf "USB\\nAndroid" | dmenu -i -p "Mount a USB drive or Android device?")" || exit 1 + case $choice in + USB) mountusb ;; + Android) mountandroid ;; + esac + } + +anddrives=$(simple-mtpfs -l 2>/dev/null) +usbdrives="$(lsblk -rpo "name,type,size,mountpoint" | grep 'part\|rom' | awk '$4==""{printf "%s (%s)\n",$1,$3}')" + +if [ -z "$usbdrives" ]; then + [ -z "$anddrives" ] && echo "No USB drive or Android device detected" && exit + echo "Android device(s) detected." + mountandroid +else + if [ -z "$anddrives" ]; then + echo "USB drive(s) detected." + mountusb + else + echo "Mountable USB drive(s) and Android device(s) detected." + asktype + fi +fi diff --git a/dotfiles/system/.local/bin/dmenumountcifs b/dotfiles/system/.local/bin/dmenumountcifs new file mode 100755 index 0000000..46c2b57 --- /dev/null +++ b/dotfiles/system/.local/bin/dmenumountcifs @@ -0,0 +1,19 @@ +#!/bin/sh +# Gives a dmenu prompt to mount unmounted local NAS shares for read/write. +# Requirements - "%wheel ALL=(ALL) NOPASSWD: ALL" +# +# Browse for mDNS/DNS-SD services using the Avahi daemon... +srvname=$(avahi-browse _smb._tcp -t | awk '{print $4}' | dmenu -i -p "Which NAS?") || exit 1 +notify-send "Searching for network shares..." "Please wait..." +# Choose share disk... +share=$(smbclient -L "$srvname" -N | grep Disk | awk '{print $1}' | dmenu -i -p "Mount which share?") || exit 1 +# Format URL... +share2mnt=//"$srvname".local/"$share" + +sharemount() { + mounted=$(mount -v | grep "$share2mnt") || ([ ! -d /mnt/"$share" ] && sudo mkdir /mnt/"$share") + [ -z "$mounted" ] && sudo mount -t cifs "$share2mnt" -o user=nobody,password="",noperm /mnt/"$share" && notify-send "Netshare $share mounted" && exit 0 + notify-send "Netshare $share already mounted"; exit 1 +} + +sharemount diff --git a/dotfiles/system/.local/bin/dmenurecord b/dotfiles/system/.local/bin/dmenurecord new file mode 100755 index 0000000..b83a7c5 --- /dev/null +++ b/dotfiles/system/.local/bin/dmenurecord @@ -0,0 +1,123 @@ +#!/bin/sh + +# Usage: +# `$0`: Ask for recording type via dmenu +# `$0 screencast`: Record both audio and screen +# `$0 video`: Record only screen +# `$0 audio`: Record only audio +# `$0 kill`: Kill existing recording +# +# If there is already a running instance, user will be prompted to end it. + +updateicon() { \ + echo "$1" > /tmp/recordingicon + pkill -RTMIN+9 "${STATUSBAR:-dwmblocks}" + } + +killrecording() { + recpid="$(cat /tmp/recordingpid)" + # kill with SIGTERM, allowing finishing touches. + kill -15 "$recpid" + rm -f /tmp/recordingpid + updateicon "" + pkill -RTMIN+9 "${STATUSBAR:-dwmblocks}" + # even after SIGTERM, ffmpeg may still run, so SIGKILL it. + sleep 3 + kill -9 "$recpid" + exit + } + +screencast() { \ + ffmpeg -y \ + -f x11grab \ + -framerate 60 \ + -s "$(xdpyinfo | awk '/dimensions/ {print $2;}')" \ + -i "$DISPLAY" \ + -f alsa -i default \ + -r 30 \ + -c:v h264 -crf 0 -preset ultrafast -c:a aac \ + "$HOME/screencast-$(date '+%y%m%d-%H%M-%S').mp4" & + echo $! > /tmp/recordingpid + updateicon "βΊοΈποΈ" + } + +video() { ffmpeg \ + -f x11grab \ + -s "$(xdpyinfo | awk '/dimensions/ {print $2;}')" \ + -i "$DISPLAY" \ + -c:v libx264 -qp 0 -r 30 \ + "$HOME/video-$(date '+%y%m%d-%H%M-%S').mkv" & + echo $! > /tmp/recordingpid + updateicon "βΊοΈ" + } + +webcamhidef() { ffmpeg \ + -f v4l2 \ + -i /dev/video0 \ + -video_size 1920x1080 \ + "$HOME/webcam-$(date '+%y%m%d-%H%M-%S').mkv" & + echo $! > /tmp/recordingpid + updateicon "π₯" + } + +webcam() { ffmpeg \ + -f v4l2 \ + -i /dev/video0 \ + -video_size 640x480 \ + "$HOME/webcam-$(date '+%y%m%d-%H%M-%S').mkv" & + echo $! > /tmp/recordingpid + updateicon "π₯" + } + + +audio() { \ + ffmpeg \ + -f alsa -i default \ + -c:a flac \ + "$HOME/audio-$(date '+%y%m%d-%H%M-%S').flac" & + echo $! > /tmp/recordingpid + updateicon "ποΈ" + } + +askrecording() { \ + choice=$(printf "screencast\\nvideo\\nvideo selected\\naudio\\nwebcam\\nwebcam (hi-def)" | dmenu -i -p "Select recording style:") + case "$choice" in + screencast) screencast;; + audio) audio;; + video) video;; + *selected) videoselected;; + webcam) webcam;; + "webcam (hi-def)") webcamhidef;; + esac + } + +asktoend() { \ + response=$(printf "No\\nYes" | dmenu -i -p "Recording still active. End recording?") && + [ "$response" = "Yes" ] && killrecording + } + +videoselected() +{ + slop -f "%x %y %w %h" > /tmp/slop + read -r X Y W H < /tmp/slop + rm /tmp/slop + + ffmpeg \ + -f x11grab \ + -framerate 60 \ + -video_size "$W"x"$H" \ + -i :0.0+"$X,$Y" \ + -c:v libx264 -qp 0 -r 30 \ + "$HOME/box-$(date '+%y%m%d-%H%M-%S').mkv" & + echo $! > /tmp/recordingpid + updateicon "βΊοΈ" +} + +case "$1" in + screencast) screencast;; + audio) audio;; + video) video;; + *selected) videoselected;; + kill) killrecording;; + *) ([ -f /tmp/recordingpid ] && asktoend && exit) || askrecording;; +esac diff --git a/dotfiles/system/.local/bin/dmenuumount b/dotfiles/system/.local/bin/dmenuumount new file mode 100755 index 0000000..946d12c --- /dev/null +++ b/dotfiles/system/.local/bin/dmenuumount @@ -0,0 +1,44 @@ +#!/bin/sh + +# A dmenu prompt to unmount drives. +# Provides you with mounted partitions, select one to unmount. +# Drives mounted at /, /boot and /home will not be options to unmount. + +unmountusb() { + [ -z "$drives" ] && exit + chosen="$(echo "$drives" | dmenu -i -p "Unmount which drive?")" || exit 1 + chosen="$(echo "$chosen" | awk '{print $1}')" + [ -z "$chosen" ] && exit + sudo -A umount "$chosen" && notify-send "π» USB unmounting" "$chosen unmounted." + } + +unmountandroid() { \ + chosen="$(awk '/simple-mtpfs/ {print $2}' /etc/mtab | dmenu -i -p "Unmount which device?")" || exit 1 + [ -z "$chosen" ] && exit + sudo -A umount -l "$chosen" && notify-send "π€ Android unmounting" "$chosen unmounted." + } + +asktype() { \ + choice="$(printf "USB\\nAndroid" | dmenu -i -p "Unmount a USB drive or Android device?")" || exit 1 + case "$choice" in + USB) unmountusb ;; + Android) unmountandroid ;; + esac + } + +drives=$(lsblk -nrpo "name,type,size,mountpoint,label" | awk -F':' '{gsub(/ /,":")}$4!~/\/boot|\/efi|\/home$|SWAP/&&length($4)>1{printf "%s (%s) %s\n",$4,$3,$5}') + +if ! grep simple-mtpfs /etc/mtab; then + [ -z "$drives" ] && echo "No drives to unmount." && exit + echo "Unmountable USB drive detected." + unmountusb +else + if [ -z "$drives" ] + then + echo "Unmountable Android device detected." + unmountandroid + else + echo "Unmountable USB drive(s) and Android device(s) detected." + asktype + fi +fi diff --git a/dotfiles/system/.local/bin/dmenuunicode b/dotfiles/system/.local/bin/dmenuunicode new file mode 100755 index 0000000..b25876f --- /dev/null +++ b/dotfiles/system/.local/bin/dmenuunicode @@ -0,0 +1,18 @@ +#!/bin/sh + +# The famous "get a menu of emojis to copy" script. + +# Get user selection via dmenu from emoji file. +chosen=$(cut -d ';' -f1 ~/.local/share/emoji | dmenu -i -l 30 | sed "s/ .*//") + +# Exit if none chosen. +[ -z "$chosen" ] && exit + +# If you run this command with an argument, it will automatically insert the +# character. Otherwise, show a message that the emoji has been copied. +if [ -n "$1" ]; then + xdotool type "$chosen" +else + printf "$chosen" | xclip -selection clipboard + notify-send "'$chosen' copied to clipboard." & +fi diff --git a/dotfiles/system/.local/bin/dotfiles_pushall b/dotfiles/system/.local/bin/dotfiles_pushall new file mode 100755 index 0000000..3eef2c6 --- /dev/null +++ b/dotfiles/system/.local/bin/dotfiles_pushall @@ -0,0 +1,6 @@ +#!/bin/bash +# Craig Jennings <craigmartinjennings@gmail.com> +# tired of pushing to multiple locations with multiple commands +/usr/bin/git --git-dir="$HOME"/.dotfiles/ --work-tree="$HOME" push origin main && \ +/usr/bin/git --git-dir="$HOME"/.dotfiles/ --work-tree="$HOME" push source main && \ +/usr/bin/git --git-dir="$HOME"/.dotfiles/ --work-tree="$HOME" push github main diff --git a/dotfiles/system/.local/bin/dwmstatus b/dotfiles/system/.local/bin/dwmstatus new file mode 100755 index 0000000..d0cff02 --- /dev/null +++ b/dotfiles/system/.local/bin/dwmstatus @@ -0,0 +1,79 @@ +#!/bin/sh +# displays status of microphone, camera, wifi, free home disk space, and date/time +# uses icons found in nerd fonts here: https://github.com/ryanoasis/nerd-fonts.git + +export DISPLAY=:0 +unset status + +########################################################################## +# MIC # +########################################################################## + +# micsymbol_on="ο°" +# micsymbol_off="ο±" + +# amixer get Capture | grep '\[off\]' && mic="$micsymbol_off" || mic="$micsymbol_on" +# status="$mic " + + +########################################################################## +# VOLUME # +########################################################################## + +speakersymbol="ξ" +if grep -q "yes" <<< $(pactl get-sink-mute $(pactl get-default-sink)) ; then + speakersymbol="ξ" + vol="" +else + vol=$(echo $(pactl get-sink-volume $(pactl get-default-sink)) | cut -d"/" -f2 | xargs) +fi +status+="$speakersymbol $vol " + + +########################################################################## +# WIFI # +########################################################################## + +wifisymbol_on="ξ" +wifisymbol_off="ξ" +# note: assumes we're using network-manager +ssid="$(nmcli -t -f active,ssid dev wifi | grep -E '^yes' | cut -d: -f2)" +wifi="$wifisymbol_off" +if [ "$ssid" != "" ]; then wifi="$wifisymbol_on $ssid"; fi +status+="$wifi " + + +########################################################################## +# BATTERY # +########################################################################## + +# desktops don't typically have batteries. if no batteries are found, skip this section +if [[ -n $(find /sys/class/power_supply/ -name "BAT?") ]]; then + + # however, laptops may have multiple batteries, so list them individually + for battery in /sys/class/power_supply/BAT?; do + batstat=$(sed "s/[Dd]ischarging/σ±/;s/[Nn]ot charging/σ°Ή/;s/[Cc]harging/σ°/;s/[Uu]nknown/ξ¦/;s/[Ff]ull/ξ₯/" "$battery"/status) + battery_level=$(cat "$battery"/capacity 2>/dev/null) + status+="${batstat} ${battery_level}% " + done +fi + + +########################################################################## +# /HOME DISK # +########################################################################## + +# disksymbol="ο" +# disk=$(df -hl | awk '{ if ($6 == "/home") print $4 " free" }') +# status+="$disksymbol $disk " + + +########################################################################## +# DATE / TIME # +########################################################################## + +# Format Example: Thu Mar 25 03:37 PM CDT +calendarsymbol="ο³" +status+="$calendarsymbol $(/bin/date +'%a %b %d %I:%M %p %Z')" + +xsetroot -name "$status" diff --git a/dotfiles/system/.local/bin/ec b/dotfiles/system/.local/bin/ec new file mode 100755 index 0000000..b409195 --- /dev/null +++ b/dotfiles/system/.local/bin/ec @@ -0,0 +1,2 @@ +#!/bin/sh +emacsclient -c -a "" $1 $2 $3 $4 & diff --git a/dotfiles/system/.local/bin/em b/dotfiles/system/.local/bin/em new file mode 100755 index 0000000..b409195 --- /dev/null +++ b/dotfiles/system/.local/bin/em @@ -0,0 +1,2 @@ +#!/bin/sh +emacsclient -c -a "" $1 $2 $3 $4 & diff --git a/dotfiles/system/.local/bin/et b/dotfiles/system/.local/bin/et new file mode 100755 index 0000000..1c3c4a0 --- /dev/null +++ b/dotfiles/system/.local/bin/et @@ -0,0 +1,2 @@ +#!/bin/sh +emacsclient -c -nw --alternate-editor="" $1 $2 $3 $4 & diff --git a/dotfiles/system/.local/bin/exitmenu b/dotfiles/system/.local/bin/exitmenu new file mode 100755 index 0000000..2c55b34 --- /dev/null +++ b/dotfiles/system/.local/bin/exitmenu @@ -0,0 +1,16 @@ +#!/bin/sh + +CHOICES="Shutdown\nReboot\nLock\nHibernate\nSuspend\nToggle Airplane Mode\nToggle Powersave Mode" +CHOSEN=$(echo -e "$CHOICES" | dmenu -i) + +case $CHOSEN in + "Shutdown") shutdown now ;; + "Reboot") reboot ;; + "Lock") xscreensaver-command --lock ;; + "Hiberbate") systemctl hibernate ;; + "Suspend") systemctl suspend ;; + "Toggle Airplane Mode") airplanemodetoggle ;; + "Toggle Powersave Mode") lowpowertoggle && notify-send "Battery Status" "$(acpi -b)" ;; +esac + +dwmstatus diff --git a/dotfiles/system/.local/bin/extractaudio b/dotfiles/system/.local/bin/extractaudio new file mode 100755 index 0000000..a665451 --- /dev/null +++ b/dotfiles/system/.local/bin/extractaudio @@ -0,0 +1,2 @@ +#!/bin/sh +ffmpeg -i $1 -q:a 0 -map a $1.mp3 diff --git a/dotfiles/system/.local/bin/gitconfig_defaults b/dotfiles/system/.local/bin/gitconfig_defaults new file mode 100755 index 0000000..c2f18ae --- /dev/null +++ b/dotfiles/system/.local/bin/gitconfig_defaults @@ -0,0 +1,5 @@ +git config --global user.email "craigmartinjennings@gmail.com" +git config --global user.name "Craig Jennings"" +git config --global merge.tool meld +git config --global core.editor "emacsclient -c -a''" +git config --global fetch.prune true diff --git a/dotfiles/system/.local/bin/gruv b/dotfiles/system/.local/bin/gruv new file mode 100755 index 0000000..b3e7f35 --- /dev/null +++ b/dotfiles/system/.local/bin/gruv @@ -0,0 +1,3 @@ +#!/bin/bash +dir=$pwd +for file in $(ls $dir | shuf) ; do mpv --no-video "$file"; done diff --git a/dotfiles/system/.local/bin/ifinstalled b/dotfiles/system/.local/bin/ifinstalled new file mode 100755 index 0000000..c192eba --- /dev/null +++ b/dotfiles/system/.local/bin/ifinstalled @@ -0,0 +1,12 @@ +#!/bin/sh + +# Some optional functions in LARBS require programs not installed by default. I +# use this little script to check to see if a command exists and if it doesn't +# it informs the user that they need that command to continue. This is used in +# various other scripts for clarity's sake. + +for x in "$@"; do + if ! which "$x" >/dev/null 2>&1 && ! pacman -Qq "$x" >/dev/null 2>&1; then + notify-send "π¦ $x" "must be installed for this function." && exit 1 ; + fi +done diff --git a/dotfiles/system/.local/bin/lfrun b/dotfiles/system/.local/bin/lfrun new file mode 100755 index 0000000..5bb0ba3 --- /dev/null +++ b/dotfiles/system/.local/bin/lfrun @@ -0,0 +1,19 @@ +#!/bin/sh +set -e + +cleanup() { + exec 3>&- + rm "$FIFO_UEBERZUG" +} + +if [ -n "$SSH_CLIENT" ] || [ -n "$SSH_TTY" ]; then + lf "$@" +else + [ ! -d "$HOME/.cache/lf" ] && mkdir --parents "$HOME/.cache/lf" + export FIFO_UEBERZUG="$HOME/.cache/lf/ueberzug-$$" + mkfifo "$FIFO_UEBERZUG" + ueberzug layer -s <"$FIFO_UEBERZUG" -p json & + exec 3>"$FIFO_UEBERZUG" + trap cleanup EXIT + lf "$@" 3>&- +fi diff --git a/dotfiles/system/.local/bin/lfub b/dotfiles/system/.local/bin/lfub new file mode 100755 index 0000000..9012f50 --- /dev/null +++ b/dotfiles/system/.local/bin/lfub @@ -0,0 +1,24 @@ +#!/bin/sh + +# This is a wrapper script for lb that allows it to create image previews with +# ueberzug. This works in concert with the lf configuration file and the +# lf-cleaner script. + +set -e + +cleanup() { + exec 3>&- + rm "$FIFO_UEBERZUG" +} + +if [ -n "$SSH_CLIENT" ] || [ -n "$SSH_TTY" ]; then + lf "$@" +else + [ ! -d "$HOME/.cache/lf" ] && mkdir -p "$HOME/.cache/lf" + export FIFO_UEBERZUG="$HOME/.cache/lf/ueberzug-$$" + mkfifo "$FIFO_UEBERZUG" + ueberzug layer -s <"$FIFO_UEBERZUG" -p json & + exec 3>"$FIFO_UEBERZUG" + trap cleanup HUP INT QUIT TERM PWR EXIT + lf "$@" 3>&- +fi diff --git a/dotfiles/system/.local/bin/linkhandler b/dotfiles/system/.local/bin/linkhandler new file mode 100755 index 0000000..cc971fc --- /dev/null +++ b/dotfiles/system/.local/bin/linkhandler @@ -0,0 +1,26 @@ +#!/bin/sh + +# Feed script a url or file location. +# If an image, it will view in sxiv, +# if a video or gif, it will view in mpv +# if a music file or pdf, it will download, +# otherwise it opens link in browser. + +if [ -z "$1" ]; then + url="$(xclip -o)" +else + url="$1" +fi + +case "$url" in + *mkv|*webm|*mp4|*youtube.com/watch*|*youtube.com/playlist*|*youtu.be*|*hooktube.com*|*bitchute.com*|*videos.lukesmith.xyz*|*odysee.com*) + setsid -f mpv -quiet "$url" >/dev/null 2>&1 ;; + *png|*jpg|*jpe|*jpeg|*gif) + curl -sL "$url" > "/tmp/$(echo "$url" | sed "s/.*\///;s/%20/ /g")" && sxiv -a "/tmp/$(echo "$url" | sed "s/.*\///;s/%20/ /g")" >/dev/null 2>&1 & ;; + *pdf|*cbz|*cbr) + curl -sL "$url" > "/tmp/$(echo "$url" | sed "s/.*\///;s/%20/ /g")" && zathura "/tmp/$(echo "$url" | sed "s/.*\///;s/%20/ /g")" >/dev/null 2>&1 & ;; + *mp3|*flac|*opus|*mp3?source*) + qndl "$url" 'curl -LO' >/dev/null 2>&1 ;; + *) + [ -f "$url" ] && setsid -f "$TERMINAL" -e "$EDITOR" "$url" >/dev/null 2>&1 || setsid -f "$BROWSER" "$url" >/dev/null 2>&1 +esac diff --git a/dotfiles/system/.local/bin/lkg b/dotfiles/system/.local/bin/lkg new file mode 100755 index 0000000..aa9b3f1 --- /dev/null +++ b/dotfiles/system/.local/bin/lkg @@ -0,0 +1,4 @@ +#!/bin/sh +# Craig Jennings <c@cjennings.net> + +sudo zfs destroy -rR zroot@lkg && sudo zfs snapshot -r zroot@lkg && echo "lkg snapshot reset" || echo "lkg snapshot failed!" diff --git a/dotfiles/system/.local/bin/lkg_rollback b/dotfiles/system/.local/bin/lkg_rollback new file mode 100755 index 0000000..3cf4c59 --- /dev/null +++ b/dotfiles/system/.local/bin/lkg_rollback @@ -0,0 +1,12 @@ +#!/bin/sh + +sudo zfs rollback -rR zroot/var@lkg +sudo zfs rollback -rR zroot/var/log@lkg +sudo zfs rollback -rR zroot/usr@lkg +sudo zfs rollback -rR zroot/usr/home@lkg +sudo zfs rollback -rR zroot/ROOT/default@lkg +sudo zfs rollback -rR zroot/ROOT@lkg +sudo zfs rollback -rR zroot@lkg + +echo ""; echo "rollback complete. rebooting..." +sudo shutdown -r now diff --git a/dotfiles/system/.local/bin/lsbak b/dotfiles/system/.local/bin/lsbak new file mode 100755 index 0000000..7803135 --- /dev/null +++ b/dotfiles/system/.local/bin/lsbak @@ -0,0 +1 @@ +ls -laF /media/backup/renovo diff --git a/dotfiles/system/.local/bin/mkplaylist b/dotfiles/system/.local/bin/mkplaylist new file mode 100755 index 0000000..66b6e9c --- /dev/null +++ b/dotfiles/system/.local/bin/mkplaylist @@ -0,0 +1,173 @@ +#!/usr/bin/env bash +# Craig Jennings <c@cjennings.net> +# Basically just a bash wrapper around a find/grep/awk command pipe +# to generate m3u playlists from video or audio files in a directory. + +# One m3u playlist will be placed in the MUSIC_DIR, and another +# will be placed inside each playlist directory. +# It also converts .opus and .ogg files to .m4a for Android playback. + +# Note: +# This script requires the following utilities to be on the path: +# mid3v2 (aur package: python-mutagen) +# tageditor (aur package: tageditor) +# metaflac (aur package: flac) + +set -e + +MUSIC_DIR="$HOME/music" +# REQUIRED_TOOLS=("mid3v2" "tageditor") +REQUIRED_TOOLS=("mid3v2" "metaflac" "tageditor") + +# ---------------------------- Functions ---------------------------- + +usage () { + printf "\nUsage: mkplaylist <playlist_name>\n\n" + printf "mkplaylist - creates an m3u playlist in the $MUSIC_DIR directory\n" + printf "based the music and video files in directory which m3uplaylist is called.\n\n" + printf " - this script should be run in the directory containing the music or video files\n" + printf " - <playlist_name> is mandatory and shouldn't end with '.m3u' extension\n" + printf " - change the destination ($MUSIC_DIR) by editing this script\n\n" +} + +tag_music_file() { + while IFS= read -r file; do + filename=$(basename "$file") + extension="${filename##*.}" + artist=$(basename "$file" | cut -d '-' -f 1) + title=$(basename "$file" | cut -d '-' -f 2- | cut -d '.' -f 1) + outputfile="$(dirname "$file")/$title.flac" + + # If file is not already flac, convert it + if [ "$extension" != "flac" ]; then + + # Delete all tags using mid3v2 + mid3v2 --delete-all "$file" + ffmpeg -i "$file" -vn -c:a flac "$outputfile" + file="$outputfile" # Now we're working with the new FLAC file + + fi + + # Set artist and song title tags using metaflac + metaflac --set-tag="ARTIST=$artist" --set-tag="TITLE=$title" "$file" + + done +} + +# tag_music_file() { +# while IFS= read -r file; do +# filename=$(basename "$file") +# extension="${filename##*.}" +# artist=$(basename "$file" | cut -d '-' -f 1) +# title=$(basename "$file" | cut -d '-' -f 2- | cut -d '.' -f 1) +# outputfile="$(dirname "$file")/$title.flac" + +# # Delete all tags using mid3v2 +# mid3v2 --delete-all "$file" + +# # If file is not already flac, convert it +# if [ "$extension" != "flac" ]; then +# ffmpeg -i "$file" -vn -c:a flac "$outputfile" +# file="$outputfile" # Now we're working with the new FLAC file +# fi + +# # Set artist and song title tags using metaflac +# metaflac --set-tag="ARTIST=$artist" --set-tag="TITLE=$title" "$file" +# done +# } + +# tag_music_file() { +# while IFS= read -r file; do +# # Extract artist and song title from filename +# artist=$(basename "$file" | cut -d '-' -f 1) +# title=$(basename "$file" | cut -d '-' -f 2- | cut -d '.' -f 1) +# outputfile="$(dirname "$file")/$title.flac" + +# # Delete all tags using mid3v2 +# mid3v2 --delete-all "$file" + +# # Convert to flac and save to new file +# ffmpeg -i "$file" -vn -c:a flac "$outputfile" + +# # Set artist and song title tags using metaflac +# metaflac --set-tag="ARTIST=$artist" --set-tag="TITLE=$title" "$outputfile" +# done +# } + +# tag_music_file() { +# while IFS= read -r file; do +# # Extract artist and song title from filename +# artist=$(basename "$file" | cut -d '-' -f 1) +# title=$(basename "$file" | cut -d '-' -f 2 | cut -d '.' -f 1) +# outputfile="$(dirname "$file")/$title.flac" + +# # Delete all tags using mid3v2 +# mid3v2 --delete-all "$file" + +# # Set artist and song title tags using mid3v2 +# mid3v2 --artist="$artist" --song="$title" "$file" +# # Convert to flac and save to new file +# ffmpeg -i "$file" -vn -c:a flac "$outputfile" +# done +# } + +# tag_music_file() { +# while IFS= read -r file; do +# # Extract artist and song title from filename +# artist=$(basename "$file" | cut -d '-' -f 1) +# title=$(basename "$file" | cut -d '-' -f 2 | cut -d '.' -f 1) + +# # Delete all tags using mid3v2 +# mid3v2 --delete-all "$file" + +# # Set artist and song title tags using mid3v2 +# mid3v2 --artist="$artist" --song="$title" "$file" +# done +# } + + generate_music_m3u() { + printf "retagging music files....\n" + find "$(pwd)" -print | file -if - | grep -E '(audio)' | awk -F: '{print $1}' | tag_music_file + + printf "generating playlist.'%s'...\n" "$LOCAL_PLAYLIST" + find "$(pwd)" -print | file -if - | grep -E '(video|audio)' | + awk -F: '{print $1}' | while read -r line; do basename "$line"; done > "$LOCAL_PLAYLIST" + printf "generating playlist '%s'....\n" "$MUSIC_PLAYLIST" + find "$(pwd)" -print | file -if - | grep -E '(video|audio)' | awk -F: '{print $1}' > "$MUSIC_PLAYLIST" + printf "Done.\n\n" + } + + # ----------------------------- Script ---------------------------- + + # display usage if user specifically requests it + TYPE=$(tr '[a-z]' '[A-Z]' <<< "$@"); + [ "$TYPE" = "HELP" ] && usage && exit 1 + [ "$TYPE" = "-H" ] && usage&& exit 1 + + # check that all necessary tools are installed + for tool in ${REQUIRED_TOOLS[@]}; do + if ! type "$tool" >/dev/null 2>&1; then + printf "ERROR: The script requires '%s' but it is not installed or not in PATH.\n" "$tool" + exit 1 + fi + done + + # use directory name for playlist name when parameter doesn't exist + if [ $# -eq 0 ] + then + set -- "$(basename "$PWD")" + echo "no playlist name entered, so using directory name: '$(basename "$PWD")'" + fi + + # ask to overwrite if the playlist already exists + MUSIC_PLAYLIST="$MUSIC_DIR/$@.m3u" + LOCAL_PLAYLIST="./$@.m3u" + + if [ -f "$MUSIC_PLAYLIST" ]; then + read -p "$MUSIC_PLAYLIST exists. Overwrite (y/n) " yn + if [ "$yn" != "y" ] && [ "$yn" != "Y" ]; then + exit 0 + fi + fi + + generate_music_m3u diff --git a/dotfiles/system/.local/bin/monitor b/dotfiles/system/.local/bin/monitor new file mode 100755 index 0000000..480e1bd --- /dev/null +++ b/dotfiles/system/.local/bin/monitor @@ -0,0 +1,50 @@ +#!/bin/sh +# Craig Jennings +# convenience script to switch monitors + +# this script assumes there are at most two monitors attached and we want to switch between then +CHOICES="Laptop\nLaptop-Scaled\nHome-Display\nExternal-Auto\nExternal-Scaled\nVirtualbox" + +# laptops always have a monitor connected when running the script. +LAPTOP=$(xrandr -q | grep primary | awk '$2 == "connected" {print $1}') +echo "primary monitor is $LAPTOP" + +# an external monitor will always be a connected monitor that isn't primary +EXTERNAL=$(xrandr -q | grep -v primary | awk '$2 == "connected" {print $1}') + +# start by resetting +xrandr -s 0 + +# disable if called automatically, otherwise you'll want the menu +# if there's only one monitor connected, setup laptop monitor +# if [ -z "$EXTERNAL" ]; then +# xrandr -s 0 +# xrandr --output "$LAPTOP" --auto --dpi 144 --scale 0.6 +# exit 0 +# fi + +CHOSEN=$(echo -e "$CHOICES" | dmenu -i) + +case "$CHOSEN" in +"Laptop") + xrandr --output "$LAPTOP" --auto --output "$EXTERNAL" --off + ;; +"Laptop-Scaled") + xrandr --output "$LAPTOP" --auto --dpi 144 --scale 0.6 --output "$EXTERNAL" --off + ;; +"External") + xrandr --output "$EXTERNAL" --auto --dpi 96 --mode 3440x1440 --scale 1.0 --output "$LAPTOP" --off + ;; +"External-Auto") + xrandr --output "$EXTERNAL" --auto --output "$LAPTOP" --off + ;; +"External-Scaled") + xrandr --output "$EXTERNAL" --auto --scale 0.6 --output "$LAPTOP" --off + ;; +"Virtualbox") + xrandr --output "$LAPTOP" --auto --mode 1920x1080 + ;; +esac + +# restore the wallpaper after resolution change +nitrogen --restore diff --git a/dotfiles/system/.local/bin/mpd_play_yt_stream b/dotfiles/system/.local/bin/mpd_play_yt_stream new file mode 100755 index 0000000..b53f298 --- /dev/null +++ b/dotfiles/system/.local/bin/mpd_play_yt_stream @@ -0,0 +1,14 @@ +#!/bin/bash +# +MYHOST='127.0.0.1' # or your MPD host + +mpduri="$(yt-dlp -f best -g $1)#" +# mpduri="$(yt-dlp -g $1)#" +# TAG=$(yt-dlp -i --get-filename $1) +# cadena="{\"title\":\"$TAG\"}" +# echo "$cadena" +# mpduri="$mpduri$cadena" +# echo "$mpduri" +mpc insert "$mpduri" +mpc next +mpc play diff --git a/dotfiles/system/.local/bin/msmtp-enqueue.sh b/dotfiles/system/.local/bin/msmtp-enqueue.sh new file mode 100755 index 0000000..c9beaca --- /dev/null +++ b/dotfiles/system/.local/bin/msmtp-enqueue.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env sh + +QUEUEDIR=$HOME/.msmtpqueue + +# Set secure permissions on created directories and files +umask 077 + +# Change to queue directory (create it if necessary) +if [ ! -d "$QUEUEDIR" ]; then + mkdir -p "$QUEUEDIR" || exit 1 +fi +cd "$QUEUEDIR" || exit 1 + +# Create new unique filenames of the form +# MAILFILE: ccyy-mm-dd-hh.mm.ss[-x].mail +# MSMTPFILE: ccyy-mm-dd-hh.mm.ss[-x].msmtp +# where x is a consecutive number only appended if you send more than one +# mail per second. +BASE="$(date +%Y-%m-%d-%H.%M.%S)" +if [ -f "$BASE.mail" ] || [ -f "$BASE.msmtp" ]; then + TMP="$BASE" + i=1 + while [ -f "$TMP-$i.mail" ] || [ -f "$TMP-$i.msmtp" ]; do + i=$((i + 1)) + done + BASE="$BASE-$i" +fi +MAILFILE="$BASE.mail" +MSMTPFILE="$BASE.msmtp" + +# Write command line to $MSMTPFILE +echo "$@" > "$MSMTPFILE" || exit 1 + +# Write the mail to $MAILFILE +cat > "$MAILFILE" || exit 1 + +# If we are online, run the queue immediately. +# Replace the test with something suitable for your site. +#ping -c 1 -w 2 SOME-IP-ADDRESS > /dev/null +#if [ $? -eq 0 ]; then +# msmtp-runqueue.sh > /dev/null & +#fi + +exit 0 diff --git a/dotfiles/system/.local/bin/msmtp-listqueue.sh b/dotfiles/system/.local/bin/msmtp-listqueue.sh new file mode 100755 index 0000000..cc97c58 --- /dev/null +++ b/dotfiles/system/.local/bin/msmtp-listqueue.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env sh + +QUEUEDIR=$HOME/.msmtpqueue + +for i in $QUEUEDIR/*.mail; do + grep -E -s --colour -h '(^From:|^To:|^Subject:)' "$i" || echo "No mail in queue"; + echo " " +done diff --git a/dotfiles/system/.local/bin/msmtp-runqueue.sh b/dotfiles/system/.local/bin/msmtp-runqueue.sh new file mode 100755 index 0000000..1200610 --- /dev/null +++ b/dotfiles/system/.local/bin/msmtp-runqueue.sh @@ -0,0 +1,69 @@ +#!/usr/bin/env sh + +QUEUEDIR="$HOME/.msmtpqueue" +LOCKFILE="$QUEUEDIR/.lock" +MAXWAIT=120 + +OPTIONS=$* + +# eat some options that would cause msmtp to return 0 without sendmail mail +case "$OPTIONS" in + *--help*) + echo "$0: send mails in $QUEUEDIR" + echo "Options are passed to msmtp" + exit 0 + ;; + *--version*) + echo "$0: unknown version" + exit 0 + ;; +esac + +# wait for a lock that another instance has set +WAIT=0 +while [ -e "$LOCKFILE" ] && [ "$WAIT" -lt "$MAXWAIT" ]; do + sleep 1 + WAIT="$((WAIT + 1))" +done +if [ -e "$LOCKFILE" ]; then + echo "Cannot use $QUEUEDIR: waited $MAXWAIT seconds for" + echo "lockfile $LOCKFILE to vanish, giving up." + echo "If you are sure that no other instance of this script is" + echo "running, then delete the lock file." + exit 1 +fi + +# change into $QUEUEDIR +cd "$QUEUEDIR" || exit 1 + +# check for empty queuedir +if [ "$(echo ./*.mail)" = './*.mail' ]; then + echo "No mails in $QUEUEDIR" + exit 0 +fi + +# lock the $QUEUEDIR +touch "$LOCKFILE" || exit 1 + +# process all mails +for MAILFILE in *.mail; do + MSMTPFILE="$(echo $MAILFILE | sed -e 's/mail/msmtp/')" + echo "*** Sending $MAILFILE to $(sed -e 's/^.*-- \(.*$\)/\1/' $MSMTPFILE) ..." + if [ ! -f "$MSMTPFILE" ]; then + echo "No corresponding file $MSMTPFILE found" + echo "FAILURE" + continue + fi + msmtp $OPTIONS $(cat "$MSMTPFILE") < "$MAILFILE" + if [ $? -eq 0 ]; then + rm "$MAILFILE" "$MSMTPFILE" + echo "$MAILFILE sent successfully" + else + echo "FAILURE" + fi +done + +# remove the lock +rm -f "$LOCKFILE" + +exit 0 diff --git a/dotfiles/system/.local/bin/open-file-in-eww b/dotfiles/system/.local/bin/open-file-in-eww new file mode 100755 index 0000000..e77899e --- /dev/null +++ b/dotfiles/system/.local/bin/open-file-in-eww @@ -0,0 +1,2 @@ +#!/bin/sh +emacsclient --eval "(eww-open-file \"$1\")" diff --git a/dotfiles/system/.local/bin/opus2mp3 b/dotfiles/system/.local/bin/opus2mp3 new file mode 100755 index 0000000..eef37ed --- /dev/null +++ b/dotfiles/system/.local/bin/opus2mp3 @@ -0,0 +1,3 @@ +#!/bin/sh +# Craig Jennings Monday, April 25, 2022 +for f in *.opus; do ffmpeg -i "$f" -codec:v copy -codec:a libmp3lame -q:a 2 "${f%.opus}.mp3"; done diff --git a/dotfiles/system/.local/bin/project b/dotfiles/system/.local/bin/project new file mode 100755 index 0000000..cf5918d --- /dev/null +++ b/dotfiles/system/.local/bin/project @@ -0,0 +1,100 @@ +#!/usr/bin/env bash + +echo "" + +# Check parameter +if [ "$#" -ne 1 ] || [ "$1" != "start" ] && [ "$1" != "end" ]; then + echo "This script must be called with either 'start' or 'end' as a parameter." + exit 1 +fi + +CHECK_MARK="\033[0;32m\xE2\x9C\x94\033[0m" +CLEAR_LINE="\033[1K" + +# Define directories to process +project_dirs="$HOME/projects" +code_dirs="$HOME/code" +sync_dirs="$HOME/sync" + + +# Git pull quietly unless there's an error +git_maybe_pull() { + git fetch --quiet + if ! git diff --quiet HEAD FETCH_HEAD; then + git pull --quiet + + # clear line and message + echo -ne "\033[1K" + echo -e "\\rpulled remote changes into $1" + fi +} + +# Git stash quietly unless there's an error +git_stash () { + git stash > /dev/null 2>&1 || \ + echo "git stash error in $1: $? " +} + +# Git stash pop quietly unless there's an error +git_stash_pop () { + git stash pop > /dev/null 2>&1 || \ + echo "git stash error in $1: $? " +} + +# Function to process a directory +process_directory() { + if [ -d "$1/.git" ]; then + # Check remote repository + cd "$1" + + # skip URLs with http/s URLS as they're directories cloned for reference only + # skip git directories with no remote repository associated as well + remote_url=$(git config --get remote.origin.url) + if [ -n "$remote_url" ]; then + # if remote URL is http or https or empty, skip the directory + if [ -z "$remote_url" ] || echo "$remote_url" | grep -E -q "^(http|https)://"; then + return + fi + + # clear line and update directory + echo -ne "$CLEAR_LINE" + echo -ne "\\rchecking: $1 " + + if [ "$2" = "start" ]; then + if [ -n "$(git status --porcelain)" ]; then + # notify user of uncommitted work + echo ""; echo ">>>> uncommitted work found in $1"; + + # git stash, pull latest files, then pop uncommitted work + git_stash "$1" + git_maybe_pull "$1" + git_stash_pop "$1" + else + # retrieve any latest changes + git_maybe_pull "$1" + fi + elif [ "$2" = "end" ]; then + # Check for uncommitted work + if [ -n "$(git status --porcelain)" ]; then + echo ""; echo ">>>> Uncommitted work found in $1. <<<<"; echo "" + fi + return # Skip pulling changes + fi + fi + fi +} + +# Process directories +for directory in "$project_dirs"/*; do + process_directory "$directory" "$1" +done +for directory in "$sync_dirs"/*; do + process_directory "$directory" "$1" +done +for directory in "$code_dirs"/*; do + process_directory "$directory" "$1" +done + +# clear line and message finished +echo -ne "\033[1K" +echo -ne "\\rfinished.\n" diff --git a/dotfiles/system/.local/bin/prompt b/dotfiles/system/.local/bin/prompt new file mode 100755 index 0000000..666434f --- /dev/null +++ b/dotfiles/system/.local/bin/prompt @@ -0,0 +1,8 @@ +#!/bin/sh + +# A dmenu binary prompt script. +# Gives a dmenu prompt labeled with $1 to perform command $2. +# For example: +# `./prompt "Do you want to shutdown?" "shutdown -h now"` + +[ "$(printf "No\\nYes" | dmenu -i -p "$1" -nb darkred -sb red -sf white -nf gray )" = "Yes" ] && $2 diff --git a/dotfiles/system/.local/bin/protonvpn b/dotfiles/system/.local/bin/protonvpn new file mode 100755 index 0000000..0f24068 --- /dev/null +++ b/dotfiles/system/.local/bin/protonvpn @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +flatpak run com.protonvpn.www
\ No newline at end of file diff --git a/dotfiles/system/.local/bin/ps-mem b/dotfiles/system/.local/bin/ps-mem new file mode 100755 index 0000000..b24b003 --- /dev/null +++ b/dotfiles/system/.local/bin/ps-mem @@ -0,0 +1,28 @@ +#!/bin/bash +# Craig Jennings <c@cjennings.net> +# Outputs a process's memory usage in multiple size units. + +# Get a list of all processes +procs=$(ps aux --sort=-%mem | awk '{print $2, $4, $11}' | fzf) + +# Check if a process was selected +if [ -z "$procs" ]; then + echo "No process selected." + exit 1 +fi + +# Get the PID of the selected process (first field) +PID=$(echo $procs | awk '{print $1}') + +# Get the process name +PROCNAME=$(ps -p $PID -o comm=) + +# Get the memory usage +KB=$(pmap -x $PID | grep total | awk '{print $4}') + +# Convert to MB and GB +MB=$(echo "scale=2; $KB / 1024" | bc) +GB=$(echo "scale=2; $MB / 1024" | bc) + +# Print the memory usage +printf "$PROCNAME (pid $PID) mem usage: $KB KB | $MB MB | $GB GB\n\n" diff --git a/dotfiles/system/.local/bin/recordnow b/dotfiles/system/.local/bin/recordnow new file mode 100755 index 0000000..4e2d04a --- /dev/null +++ b/dotfiles/system/.local/bin/recordnow @@ -0,0 +1,26 @@ +#!/usr/bin/env sh +# Craig Jennings <c@cjennings.net> + +# Start a screen recording using ffmpeg to capture the entire +# screen along with all audio and the microphone. + +# Make sure that ffmpeg is in the path and the destination directory +# exists. + +LOCATION="$HOME/videos/recordings" +NAME=$(date +'%Y-%m-%d-%H-%M-%S') +echo $NAME + +# create the directory if it doesn't exist +if [ ! -d "$LOCATION" ]; then + mkdir -p "$LOCATION" +fi + +# error out if ffmpeg isn't installed +if ! command -v ffmpeg &> /dev/null +then + echo "ERROR: ffmpeg couldn't be found. Please ensure it's installed and added to your PATH." + exit +fi + +ffmpeg -framerate 30 -f x11grab -i :0.0+ -f pulse -i alsa_input.pci-0000_00_1b.0.analog-stereo -ac 1 -f pulse -i alsa_output.pci-0000_00_1b.0.analog-stereo.monitor -ac 2 "$LOCATION/$NAME".mkv diff --git a/dotfiles/system/.local/bin/refresharchkeys b/dotfiles/system/.local/bin/refresharchkeys new file mode 100755 index 0000000..db1e755 --- /dev/null +++ b/dotfiles/system/.local/bin/refresharchkeys @@ -0,0 +1,6 @@ +#!/bin/sh +sudo rm -R /etc/pacman.d/gnupg +sudo pacman-key --init +sudo pacman-key --populate archlinux +sudo pacman -Sy archlinux-keyring +sudo pacman -Syu diff --git a/dotfiles/system/.local/bin/remaps b/dotfiles/system/.local/bin/remaps new file mode 100755 index 0000000..c95ac84 --- /dev/null +++ b/dotfiles/system/.local/bin/remaps @@ -0,0 +1,11 @@ +#!/bin/sh + +# This script is called on startup to remap keys. +# Decrease key repeat delay to 300ms and increase key repeat rate to 50 per second. +xset r rate 300 50 +# Map the caps lock key to super, and map the menu key to right super. +setxkbmap -option caps:super,altwin:menu_win +# When caps lock is pressed only once, treat it as escape. +killall xcape 2>/dev/null ; xcape -e 'Super_L=Escape' +# Turn off caps lock if on since there is no longer a key for it. +xset -q | grep "Caps Lock:\s*on" && xdotool key Caps_Lock diff --git a/dotfiles/system/.local/bin/resetmimetypes b/dotfiles/system/.local/bin/resetmimetypes new file mode 100755 index 0000000..b744c24 --- /dev/null +++ b/dotfiles/system/.local/bin/resetmimetypes @@ -0,0 +1,273 @@ +# Remove Old Databases +rm ~/.config/mimeapps.list >> /dev/null 2>&1 +rm ~/.local/share/applications/mimeinfo.cache >> /dev/null 2>&1 +rm ~/.local/share/applications/mimeinfo.list >> /dev/null 2>&1 + +# Directory Node: thunar +# Open directory nodes in thunar +xdg-mime default thunar.desktop inode/directory + +# Audio Files: audacious +xdg-mime default audacious.desktop audio/basic +xdg-mime default audacious.desktop audio/flac.wav +xdg-mime default audacious.desktop audio/mp4 +xdg-mime default audacious.desktop audio/mpeg +xdg-mime default audacious.desktop audio/ogg +xdg-mime default audacious.desktop audio/opus +xdg-mime default audacious.desktop audio/vnd.rn-realaudio +xdg-mime default audacious.desktop audio/vnd.wav +xdg-mime default audacious.desktop audio/vorbis +xdg-mime default audacious.desktop audio/x-aiff +xdg-mime default audacious.desktop audio/x-mpegurl + +# Video Files: mpv +# xdg-mime default mpv.desktop application/mpeg4-iod +# xdg-mime default mpv.desktop application/mpeg4-muxcodetable +# xdg-mime default mpv.desktop application/ogg +# xdg-mime default mpv.desktop application/vnd.apple.mpegurl +# xdg-mime default mpv.desktop application/x-quicktime-media-link +# xdg-mime default mpv.desktop application/x-quicktimeplayer +# xdg-mime default mpv.desktop application/x-shockwave-flash +# xdg-mime default mpv.desktop video/avi +# xdg-mime default mpv.desktop video/divx +# xdg-mime default mpv.desktop video/flv +# xdg-mime default mpv.desktop video/mp4 +# xdg-mime default mpv.desktop video/mp4 +# xdg-mime default mpv.desktop video/mp4v-es +# xdg-mime default mpv.desktop video/mpeg +# xdg-mime default mpv.desktop video/mpeg-system +# xdg-mime default mpv.desktop video/msvideo +# xdg-mime default mpv.desktop video/ogg +# xdg-mime default mpv.desktop video/quicktime +# xdg-mime default mpv.desktop video/vnd.divx +# xdg-mime default mpv.desktop video/vnd.mpegurl +# xdg-mime default mpv.desktop video/vnd.rn-realvideo +# xdg-mime default mpv.desktop video/webm +# xdg-mime default mpv.desktop video/x-avi +# xdg-mime default mpv.desktop video/x-flv +# xdg-mime default mpv.desktop video/x-m4v +# xdg-mime default mpv.desktop video/x-matroska +# xdg-mime default mpv.desktop video/x-mpeg +# xdg-mime default mpv.desktop video/x-mpeg-system +# xdg-mime default mpv.desktop video/x-mpeg2 +# xdg-mime default mpv.desktop video/x-ms-wmv +# xdg-mime default mpv.desktop video/x-msvideo +# xdg-mime default mpv.desktop video/x-theora +# xdg-mime default mpv.desktop video/x-theora+ogg +# xdg-mime default mpv.desktop x-content/video-dvd +# xdg-mime default mpv.desktop x-content/video-svcd +# xdg-mime default mpv.desktop x-content/video-vcd + +# Video Files: vlc +xdg-mime default vlc.desktop application/mpeg4-iod +xdg-mime default vlc.desktop application/mpeg4-muxcodetable +xdg-mime default vlc.desktop application/ogg +xdg-mime default vlc.desktop application/vnd.apple.mpegurl +xdg-mime default vlc.desktop application/x-quicktime-media-link +xdg-mime default vlc.desktop application/x-quicktimeplayer +xdg-mime default vlc.desktop application/x-shockwave-flash +xdg-mime default vlc.desktop video/avi +xdg-mime default vlc.desktop video/divx +xdg-mime default vlc.desktop video/flv +xdg-mime default vlc.desktop video/mp4 +xdg-mime default vlc.desktop video/mp4 +xdg-mime default vlc.desktop video/mp4v-es +xdg-mime default vlc.desktop video/mpeg +xdg-mime default vlc.desktop video/mpeg-system +xdg-mime default vlc.desktop video/msvideo +xdg-mime default vlc.desktop video/ogg +xdg-mime default vlc.desktop video/quicktime +xdg-mime default vlc.desktop video/vnd.divx +xdg-mime default vlc.desktop video/vnd.mpegurl +xdg-mime default vlc.desktop video/vnd.rn-realvideo +xdg-mime default vlc.desktop video/webm +xdg-mime default vlc.desktop video/x-avi +xdg-mime default vlc.desktop video/x-flv +xdg-mime default vlc.desktop video/x-m4v +xdg-mime default vlc.desktop video/x-matroska +xdg-mime default vlc.desktop video/x-mpeg +xdg-mime default vlc.desktop video/x-mpeg-system +xdg-mime default vlc.desktop video/x-mpeg2 +xdg-mime default vlc.desktop video/x-ms-wmv +xdg-mime default vlc.desktop video/x-msvideo +xdg-mime default vlc.desktop video/x-theora +xdg-mime default vlc.desktop video/x-theora+ogg +xdg-mime default vlc.desktop x-content/video-dvd +xdg-mime default vlc.desktop x-content/video-svcd +xdg-mime default vlc.desktop x-content/video-vcd + + +# Images: NSXIV +xdg-mime default nsxiv.desktop image/bmp +xdg-mime default nsxiv.desktop image/gif +xdg-mime default nsxiv.desktop image/jpegmix.desktop; +xdg-mime default nsxiv.desktop image/jpg +xdg-mime default nsxiv.desktop image/png +xdg-mime default nsxiv.desktop image/tiff +xdg-mime default nsxiv.desktop image/x-bmp +xdg-mime default nsxiv.desktop image/x-eps +xdg-mime default nsxiv.desktop image/x-ico +xdg-mime default nsxiv.desktop image/x-icon +xdg-mime default nsxiv.desktop image/x-xbitmap +xdg-mime default nsxiv.desktop image/x-xpixmapq + +# Torrent Files: Transmission Remote +xdg-mime default io.github.TransmissionRemoteGtk-gtk.desktop application/x-bittorrent +xdg-mime default io.github.TransmissionRemoteGtk.desktop x-scheme-handler/magnet + +# Web Browser: Chromium Browser +# xdg-mime default chromium.desktop application/rdf+xml +# xdg-mime default chromium.desktop application/rss+xml +# xdg-mime default chromium.desktop application/xhtml+xml +# xdg-mime default chromium.desktop application/xhtml_xml +# xdg-mime default chromium.desktop application/xml +# # xdg-mime default chromium.desktop image/gif +# # xdg-mime default chromium.desktop image/jpeg +# # xdg-mime default chromoum.desktop image/png +# # xdg-mime default chromium.desktop image/webp +# xdg-mime default chromium.desktop text/html +# xdg-mime default chromium.desktop text/xml +# xdg-mime default chromium.desktop x-scheme-handler/http +# xdg-mime default chromium.desktop x-scheme-handler/https + +# Web Browser: Google-Chrome Browser +# xdg-mime default google-chrome.desktop application/rdf+xml +# xdg-mime default google-chrome.desktop application/rss+xml +# xdg-mime default google-chrome.desktop application/xhtml+xml +# xdg-mime default google-chrome.desktop application/xhtml_xml +# xdg-mime default google-chrome.desktop application/xml +# # xdg-mime default google-chrome.desktop image/gif +# # xdg-mime default google-chrome.desktop image/jpeg +# # xdg-mime default chromoum.desktop image/png +# # xdg-mime default google-chrome.desktop image/webp +# xdg-mime default google-chrome.desktop text/html +# xdg-mime default google-chrome.desktop text/xml +# xdg-mime default google-chrome.desktop x-scheme-handler/http +# xdg-mime default google-chrome.desktop x-scheme-handler/https + + +# Web Browser: Firefox +xdg-mime default firefox.desktop application/rdf+xml +xdg-mime default firefox.desktop application/rss+xml +xdg-mime default firefox.desktop application/xhtml+xml +xdg-mime default firefox.desktop application/xhtml_xml +xdg-mime default firefox.desktop application/xml +# xdg-mime default firefox.desktop image/gif +# xdg-mime default firefox.desktop image/jpeg +# xdg-mime default firefox.desktop image/png +# xdg-mime default firefox.desktop image/webp +xdg-mime default firefox.desktop text/html +xdg-mime default firefox.desktop text/xml +xdg-mime default firefox.desktop x-scheme-handler/http +xdg-mime default firefox.desktop x-scheme-handler/https + +# Mobi Ebooks: calibre-ebook-reader +xdg-mime default calibre-ebook-viewer.desktop application/x-mobi8-ebook +xdg-mime default calibre-ebook-viewer.desktop application/x-mobipocket-ebook + +# Text and Source Code: Emacsclient +xdg-mime default emacsclient.desktop application/x-shellscript +xdg-mime default emacsclient.desktop text/english +xdg-mime default emacsclient.desktop text/markdown +xdg-mime default emacsclient.desktop text/plain +xdg-mime default emacsclient.desktop text/x-c +xdg-mime default emacsclient.desktop text/x-c++ +xdg-mime default emacsclient.desktop text/x-c++hdr +xdg-mime default emacsclient.desktop text/x-c++src +xdg-mime default emacsclient.desktop text/x-chdr +xdg-mime default emacsclient.desktop text/x-csrc +xdg-mime default emacsclient.desktop text/x-java +xdg-mime default emacsclient.desktop text/x-makefile +xdg-mime default emacsclient.desktop text/x-moc +xdg-mime default emacsclient.desktop text/x-pascal +xdg-mime default emacsclient.desktop text/x-tcl +xdg-mime default emacsclient.desktop text/x-tex + +# PDF/EPUB: Emacsclient +# xdg-mime default emacsclient.desktop application/eps +# xdg-mime default emacsclient.desktop application/epub+zip +# xdg-mime default emacsclient.desktop application/oxps +# xdg-mime default emacsclient.desktop application/pdf +# xdg-mime default emacsclient.desktop application/postscript +# xdg-mime default emacsclient.desktop application/ps +# xdg-mime default emacsclient.desktop application/x-fictionbook; +# xdg-mime default emacsclient.desktop image/eps +# xdg-mime default emacsclient.desktop image/vnd.djvu +# xdg-mime default emacsclient.desktop image/vnd.djvu+multipage + +# ePUB: Foliate +# xdg-mime default com.github.johnfactotum.Foliate.desktop application/epub+zip + +# PDF ePUB: Zathura +xdg-mime default org.pwmt.zathura.desktop application/eps +xdg-mime default org.pwmt.zathura.desktop application/epub+zip +xdg-mime default org.pwmt.zathura.desktop application/oxps +xdg-mime default org.pwmt.zathura.desktop application/pdf +xdg-mime default org.pwmt.zathura.desktop application/postscript +xdg-mime default org.pwmt.zathura.desktop application/ps +xdg-mime default org.pwmt.zathura.desktop application/x-fictionbook; +xdg-mime default org.pwmt.zathura.desktop image/eps +xdg-mime default org.pwmt.zathura.desktop image/vnd.djvu +xdg-mime default org.pwmt.zathura.desktop image/vnd.djvu+multipage + +# PDF ePUB: Evince +# xdg-mime default org.gnome.Evince.desktop application/eps +# # xdg-mime default org.gnome.Evince.desktop application/epub+zip +# xdg-mime default org.gnome.Evince.desktop application/oxps +# xdg-mime default org.gnome.Evince.desktop application/pdf +# xdg-mime default org.gnome.Evince.desktop application/postscript +# xdg-mime default org.gnome.Evince.desktop application/ps +# # xdg-mime default org.gnome.Evince.desktop application/x-fictionbook; +# # xdg-mime default org.gnome.Evince.desktop image/eps +# xdg-mime default org.gnome.Evince.desktop image/vnd.djvu +# xdg-mime default org.gnome.Evince.desktop image/vnd.djvu+multipage + +# Comics Files: Zathura +xdg-mime default org.pwmt.zathura.desktop application/vnd.comicbook+zip +xdg-mime default org.pwmt.zathura.desktop application/vnd.comicbook-rar + +# Libreoffice Writer +xdg-mime default libreoffice-writer.desktop application/clarisworks +xdg-mime default libreoffice-writer.desktop application/macwriteii +xdg-mime default libreoffice-writer.desktop application/msword +xdg-mime default libreoffice-writer.desktop application/prs.plucker +xdg-mime default libreoffice-writer.desktop application/rtf +xdg-mime default libreoffice-writer.desktop application/vnd.lotus-wordpro +xdg-mime default libreoffice-writer.desktop application/vnd.ms-word +xdg-mime default libreoffice-writer.desktop application/vnd.ms-word.template.macroEnabled.12 +xdg-mime default libreoffice-writer.desktop application/vnd.ms-works +xdg-mime default libreoffice-writer.desktop application/vnd.oasis.opendocument.text +xdg-mime default libreoffice-writer.desktop application/vnd.oasis.opendocument.text-flat-xml +xdg-mime default libreoffice-writer.desktop application/vnd.oasis.opendocument.text-master +xdg-mime default libreoffice-writer.desktop application/vnd.oasis.opendocument.text-master-template +xdg-mime default libreoffice-writer.desktop application/vnd.oasis.opendocument.text-template +xdg-mime default libreoffice-writer.desktop application/vnd.oasis.opendocument.text-web +xdg-mime default libreoffice-writer.desktop application/vnd.openxmlformats-officedocument +xdg-mime default libreoffice-writer.desktop application/vnd.openxmlformats-officedocument.wordprocessingml.document +xdg-mime default libreoffice-writer.desktop application/vnd.palm +xdg-mime default libreoffice-writer.desktop application/vnd.stardivision.writer-global +xdg-mime default libreoffice-writer.desktop application/vnd.sun.xml.writer +xdg-mime default libreoffice-writer.desktop application/vnd.sun.xml.writer.global +xdg-mime default libreoffice-writer.desktop application/vnd.sun.xml.writer.template +xdg-mime default libreoffice-writer.desktop application/vnd.wordperfect +xdg-mime default libreoffice-writer.desktop application/wordperfect +xdg-mime default libreoffice-writer.desktop application/x-abiword +xdg-mime default libreoffice-writer.desktop application/x-aportisdoc +xdg-mime default libreoffice-writer.desktop application/x-doc +xdg-mime default libreoffice-writer.desktop application/x-extension-txt +xdg-mime default libreoffice-writer.desktop application/x-fictionbook+xml +xdg-mime default libreoffice-writer.desktop application/x-hwp +xdg-mime default libreoffice-writer.desktop application/x-iwork-pages-sffpages +xdg-mime default libreoffice-writer.desktop application/x-mswrite +xdg-mime default libreoffice-writer.desktop application/x-sony-bbeb +xdg-mime default libreoffice-writer.desktop application/x-starwriter +xdg-mime default libreoffice-writer.desktop application/x-t602 +xdg-mime default libreoffice-writer.desktop text/rtf + +# FTP Scheme-Handler: Filezilla +xdg-mime default filezilla.desktop x-scheme-handler/ftp + +# Org-Protocol Scheme-Handler: emacsclient +xdg-mime default org-protocol.desktop x-scheme-handler/org-protocol + +update-desktop-database ~/.local/share/applications/ diff --git a/dotfiles/system/.local/bin/samedir b/dotfiles/system/.local/bin/samedir new file mode 100755 index 0000000..371ec64 --- /dev/null +++ b/dotfiles/system/.local/bin/samedir @@ -0,0 +1,10 @@ +#!/bin/sh + +# Open a terminal window in the same directory as the currently active window. + +PID=$(xprop -id "$(xprop -root | xprop -root | sed -n "/_NET_ACTIVE_WINDOW/ s/^.*# // p")" | sed -n "/PID/ s/^.*= // p") +PID="$(pstree -lpA "$PID")" +PID="${PID##*"${SHELL##*/}"(}" +PID="${PID%%)*}" +cd "$(readlink /proc/"$PID"/cwd)" || return 1 +"$TERMINAL" diff --git a/dotfiles/system/.local/bin/screenshotmenu b/dotfiles/system/.local/bin/screenshotmenu new file mode 100755 index 0000000..c899dfc --- /dev/null +++ b/dotfiles/system/.local/bin/screenshotmenu @@ -0,0 +1,13 @@ +#!/bin/sh +# Requires maim, xdotool, and dmenu +# Uses dmenu to choose the type of screenshot to take, + +case "$(printf "full screen\\nselected area\\ncurrent window\\nselected area (copy)\\ncurrent window (copy)\\nfull screen (copy)" | dmenu -l 6 -i -p "Screenshot which area?")" in + "full screen") file="$(date +%Y.%m.%d-%M%S).png" && maim ~/pictures/screenshots/$file && notify-send "Image selection saved to ~/pictures/screenshots/$file" ;; + "full screen (5 sec delay)") file="$(date +%Y.%m.%d-%M%S).png" && sleep 5 && maim ~/pictures/screenshots/$file && notify-send "Image selection saved to ~/pictures/screenshots/$file" ;; + "selected area") file="$(date +%Y.%m.%d-%M%S).png" && maim -s ~/pictures/screenshots/$file && notify-send "Image selection saved to ~/pictures/screenshots/$file" ;; + "current window") maim -i "$(xdotool getactivewindow)" '~/pictures/screenshots/$(date +%Y.%m.%d-%M%S).png' && notify-send "Image selection saved to ~/pictures/screenshots/" ;; + "selected area (copy)") maim -s | xclip -selection clipboard -t image/png && notify-send "Image selection copied to clipboard." ;; + "current window (copy)") maim -i "$(xdotool getactivewindow)" | xclip -selection clipboard -t image/png && notify-send "Image selection copied to clipboard." ;; + "full screen (copy)") maim | xclip -selection clipboard -t image/png && notify-send "Image copied to clipboard." ;; +esac diff --git a/dotfiles/system/.local/bin/setbg b/dotfiles/system/.local/bin/setbg new file mode 100755 index 0000000..b72dc7d --- /dev/null +++ b/dotfiles/system/.local/bin/setbg @@ -0,0 +1,34 @@ +#!/bin/sh + +# This script does the following: +# Run by itself, set the wallpaper (at X start). +# If given a file, set that as the new wallpaper. +# If given a directory, choose random file in it. +# If wal is installed, also generates a colorscheme. + +# Location of link to wallpaper link. +bgloc="${XDG_DATA_HOME:-$HOME/.local/share}/bg" + +# Configuration files of applications that have their themes changed by pywal. +dunstconf="${XDG_CONFIG_HOME:-$HOME/.config}/dunst/dunstrc" +zathuraconf="${XDG_CONFIG_HOME:-$HOME/.config}/zathura/zathurarc" + +trueloc="$(readlink -f "$1")" && +case "$(file --mime-type -b "$trueloc")" in + image/* ) ln -sf "$(readlink -f "$1")" "$bgloc" && notify-send -i "$bgloc" "Changing wallpaper..." ;; + inode/directory ) ln -sf "$(find "$trueloc" -iregex '.*.\(jpg\|jpeg\|png\|gif\)' -type f | shuf -n 1)" "$bgloc" && notify-send -i "$bgloc" "Random Wallpaper chosen." ;; + *) notify-send "πΌοΈ Error" "Not a valid image or directory." ; exit 1;; +esac + +# If pywal is installed, use it. +if command -v wal >/dev/null 2>&1 ; then + wal -n -i "$(readlink -f $bgloc)" -o "${XDG_CONFIG_HOME:-$HOME/.config}/wal/postrun" >/dev/null 2>&1 +# If pywal is removed, return config files to normal. +else + [ -f "$dunstconf.bak" ] && unlink "$dunstconf" && mv "$dunstconf.bak" "$dunstconf" + [ -f "$zathuraconf.bak" ] && unlink "$zathuraconf" && mv "$zathuraconf.bak" "$zathuraconf" +fi + +xwallpaper --zoom "$bgloc" +# If running, dwm hit the key to refresh the color scheme. +pidof dwm >/dev/null && xdotool key super+F5 diff --git a/dotfiles/system/.local/bin/ssh-createkeys b/dotfiles/system/.local/bin/ssh-createkeys new file mode 100755 index 0000000..a1c14b6 --- /dev/null +++ b/dotfiles/system/.local/bin/ssh-createkeys @@ -0,0 +1,3 @@ +#!/bin/sh +# use strong passwords +ssh-keygen -t ed25519 -a 100 diff --git a/dotfiles/system/.local/bin/startdwm b/dotfiles/system/.local/bin/startdwm new file mode 100755 index 0000000..2f2628d --- /dev/null +++ b/dotfiles/system/.local/bin/startdwm @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +# Craig Jennings <c@cjennings.net> +# starts the dwm process for a login manager +# saves the logfile of any output to a logfile + +dbus-update-activation-environment & + +logdir="$HOME/.local/var/logs" +logfile="$logdir/$(date +%Y-%m-%d_%H.dwm.log)" + +if ! [ -d "$logdir" ]; then + mkdir -p "$logdir" +fi + +# allow sudo use of display +xhost si:localuser:root + +# merge local configuration for X client aplications +[ -f ~/.Xresources ] && xrdb -merge -I $HOME ~/.Xresources + +# merge xmodmap configuration +[[ -f ~/.Xmodmap ]] && xmodmap ~/.Xmodmap + +# start desktop environment applications +(conky | while read LINE; do xsetroot -name "$LINE"; done) & +xscreensaver --no-splash & +xautolock -time 5 -locker "xscreensaver-command -activate" & +picom & +sxhkd & +emacs --daemon & +nitrogen --restore & +nm-applet & +blueman-applet & +battery_monitor & +mpd & +caffeine & +protonmail-bridge --no-window & +dunst & +signal-desktop --start-in-tray & +sudo powertop --auto-tune & +flameshot & +# dropbox & +touchpad-app & + +# start dwm +echo "$(date): Starting dwm" >> "$logfile" 2>&1 +exec dwm > $logfile 2>&1 diff --git a/dotfiles/system/.local/bin/starth b/dotfiles/system/.local/bin/starth new file mode 100755 index 0000000..98513d6 --- /dev/null +++ b/dotfiles/system/.local/bin/starth @@ -0,0 +1,9 @@ +#!/bin/sh +# startup file for Hyprland + +export WLR_EGL_NO_MODIFIERS=1 +export XDG_CURRENT_DESKTOP=Hyprland +export XDG_SESSION_TYPE=wayland +export XDG_SESSION_DESKTOP=Hyprland + +exec Hyprland diff --git a/dotfiles/system/.local/bin/statusbar/sb-battery b/dotfiles/system/.local/bin/statusbar/sb-battery new file mode 100755 index 0000000..93cbe08 --- /dev/null +++ b/dotfiles/system/.local/bin/statusbar/sb-battery @@ -0,0 +1,37 @@ +#!/bin/sh + +# Prints all batteries, their percentage remaining and an emoji corresponding +# to charge status (π for plugged up, π for discharging on battery, etc.). + +case $BLOCK_BUTTON in + 3) notify-send "π Battery module" "π: discharging +π: not charging +β»: stagnant charge +π: charging +β‘: charged +β: battery very low! +- Scroll to change adjust xbacklight." ;; + 4) xbacklight -inc 10 ;; + 5) xbacklight -dec 10 ;; + 6) "$TERMINAL" -e "$EDITOR" "$0" ;; +esac + +# Loop through all attached batteries and format the info +for battery in /sys/class/power_supply/BAT?*; do + # If non-first battery, print a space separator. + [ -n "${capacity+x}" ] && printf " " + # Sets up the status and capacity + case "$(cat "$battery/status" 2>&1)" in + "Full") status="β‘" ;; + "Discharging") status="π" ;; + "Charging") status="π" ;; + "Not charging") status="π" ;; + "Unknown") status="β»οΈ" ;; + *) exit 1 ;; + esac + capacity="$(cat "$battery/capacity" 2>&1)" + # Will make a warn variable if discharging and low + [ "$status" = "π" ] && [ "$capacity" -le 25 ] && warn="β" + # Prints the info + printf "%s%s%d%%" "$status" "$warn" "$capacity"; unset warn +done && printf "\\n" diff --git a/dotfiles/system/.local/bin/statusbar/sb-clock b/dotfiles/system/.local/bin/statusbar/sb-clock new file mode 100755 index 0000000..e1ca8c7 --- /dev/null +++ b/dotfiles/system/.local/bin/statusbar/sb-clock @@ -0,0 +1,29 @@ +#!/bin/sh + +clock=$(date '+%I') + +case "$clock" in + "00") icon="π" ;; + "01") icon="π" ;; + "02") icon="π" ;; + "03") icon="π" ;; + "04") icon="π" ;; + "05") icon="π" ;; + "06") icon="π" ;; + "07") icon="π" ;; + "08") icon="π" ;; + "09") icon="π" ;; + "10") icon="π" ;; + "11") icon="π" ;; + "12") icon="π" ;; +esac + +case $BLOCK_BUTTON in + 1) notify-send "This Month" "$(cal --color=always | sed "s/..7m/<b><span color=\"red\">/;s|..27m|</span></b>|")" && notify-send "Appointments" "$(calcurse -d3)" ;; + 2) setsid -f "$TERMINAL" -e calcurse ;; + 3) notify-send "π
Time/date module" "\- Left click to show upcoming appointments for the next three days via \`calcurse -d3\` and show the month via \`cal\` +- Middle click opens calcurse if installed" ;; + 6) "$TERMINAL" -e "$EDITOR" "$0" ;; +esac + +date "+%Y %b %d (%a) $icon%I:%M%p" diff --git a/dotfiles/system/.local/bin/statusbar/sb-cpu b/dotfiles/system/.local/bin/statusbar/sb-cpu new file mode 100755 index 0000000..1572b52 --- /dev/null +++ b/dotfiles/system/.local/bin/statusbar/sb-cpu @@ -0,0 +1,12 @@ +#!/bin/sh + +case $BLOCK_BUTTON in + 1) notify-send "π₯ CPU hogs" "$(ps axch -o cmd:15,%cpu --sort=-%cpu | head)\\n(100% per core)" ;; + 2) setsid -f "$TERMINAL" -e htop ;; + 3) notify-send "π₯ CPU module " "\- Shows CPU temperature. +- Click to show intensive processes. +- Middle click to open htop." ;; + 6) "$TERMINAL" -e "$EDITOR" "$0" ;; +esac + +sensors | awk '/Core 0/ {print "π‘" $3}' diff --git a/dotfiles/system/.local/bin/statusbar/sb-cpubars b/dotfiles/system/.local/bin/statusbar/sb-cpubars new file mode 100755 index 0000000..297424e --- /dev/null +++ b/dotfiles/system/.local/bin/statusbar/sb-cpubars @@ -0,0 +1,44 @@ +#!/bin/sh + +# Module showing CPU load as a changing bars. +# Just like in polybar. +# Each bar represents amount of load on one core since +# last run. + +# Cache in tmpfs to improve speed and reduce SSD load +cache=/tmp/cpubarscache + +case $BLOCK_BUTTON in + 2) setsid -f "$TERMINAL" -e htop ;; + 3) notify-send "πͺ¨ CPU load module" "Each bar represents +one CPU core";; + 6) "$TERMINAL" -e "$EDITOR" "$0" ;; +esac + +# id total idle +stats=$(awk '/cpu[0-9]+/ {printf "%d %d %d\n", substr($1,4), ($2 + $3 + $4 + $5), $5 }' /proc/stat) +[ ! -f $cache ] && echo "$stats" > "$cache" +old=$(cat "$cache") +printf "πͺ¨" +echo "$stats" | while read -r row; do + id=${row%% *} + rest=${row#* } + total=${rest%% *} + idle=${rest##* } + + case "$(echo "$old" | awk '{if ($1 == id) + printf "%d\n", (1 - (idle - $3) / (total - $2))*100 /12.5}' \ + id="$id" total="$total" idle="$idle")" in + + "0") printf "β";; + "1") printf "β";; + "2") printf "β";; + "3") printf "β";; + "4") printf "β
";; + "5") printf "β";; + "6") printf "β";; + "7") printf "β";; + "8") printf "β";; + esac +done; printf "\\n" +echo "$stats" > "$cache" diff --git a/dotfiles/system/.local/bin/statusbar/sb-disk b/dotfiles/system/.local/bin/statusbar/sb-disk new file mode 100755 index 0000000..e947509 --- /dev/null +++ b/dotfiles/system/.local/bin/statusbar/sb-disk @@ -0,0 +1,23 @@ +#!/bin/sh + +# Status bar module for disk space +# $1 should be drive mountpoint, otherwise assumed /. + +location=${1:-/} + +[ -d "$location" ] || exit + +case $BLOCK_BUTTON in + 1) notify-send "π½ Disk space" "$(df -h --output=target,used,size)" ;; + 3) notify-send "π½ Disk module" "\- Shows used hard drive space. +- Click to show all disk info." ;; + 6) "$TERMINAL" -e "$EDITOR" "$0" ;; +esac + +case "$location" in + "/home"* ) icon="π " ;; + "/mnt"* ) icon="πΎ" ;; + *) icon="π₯";; +esac + +printf "%s: %s\n" "$icon" "$(df -h "$location" | awk ' /[0-9]/ {print $3 "/" $2}')" diff --git a/dotfiles/system/.local/bin/statusbar/sb-doppler b/dotfiles/system/.local/bin/statusbar/sb-doppler new file mode 100755 index 0000000..b5833a7 --- /dev/null +++ b/dotfiles/system/.local/bin/statusbar/sb-doppler @@ -0,0 +1,279 @@ +#!/bin/sh + +# Show a Doppler RADAR of a user's preferred location. + +secs=600 # Download a new doppler radar if one hasn't been downloaded in $secs seconds. +radarloc="${XDG_CACHE_HOME:-$HOME/.cache}/radar" +doppler="${XDG_CACHE_HOME:-$HOME/.cache}/doppler.gif" + +pickloc() { chosen="$(echo "US: Northeast +US: Southeast +US: PacNorthWest +US: PacSouthWest +US: UpperMissVly +US: SouthMissVly +US: SouthPlains +US: NorthRockies +US: SouthRockies +US: Alaska +US: Carib +US: Hawaii +US: CentGrLakes +US: Conus-Large +US: KABR: Aberdeen, SD +US: KBIS: Bismarck, ND +US: KFTG: Denver/Boulder, CO +US: KDMX: Des Moines, IA +US: KDTX: Detroit, MI +US: KDDC: Dodge City, KS +US: KDLH: Duluth, MN +US: KCYS: Cheyenne, WY +US: KLOT: Chicago, IL +US: KGLD: Goodland, KS +US: KUEX: Hastings, NE +US: KGJX: Grand Junction, CO +US: KGRR: Grand Rapids, MI +US: KMVX: Fargo/Grand Forks, ND +US: KGRB: Green Bay, WI +US: KIND: Indianapolis, IN +US: KJKL: Jackson, KY +US: KARX: La Crosse, WI +US: KILX: Lincoln/Central Illinois, IL +US: KLVX: Louisville, KY +US: KMQT: Marquette +US: KMKX: Milwaukee, WI +US: KMPX: Minneapolis, MN +US: KAPX: Gaylord/Alpena, MI +US: KLNX: North Platte, NE +US: KIWX: N. Webster/Northern, IN +US: KOAX: Omaha, NE +US: KPAH: Paducah, KY +US: KEAX: Pleasant Hill, MO +US: KPUX: Pueblo, CO +US: KDVN: Quad Cities, IA +US: KUDX: Rapid City, SD +US: KRIW: Riverton, WY +US: KSGF: Springfield, MO +US: KLSX: St. LOUIS, MO +US: KFSD: Sioux Falls, IA +US: KTWX: Topeka, KS +US: KICT: Wichita, KS +US: KVWX: Paducah, KY +US: ICAO: Responsible Wfo +US: KLTX: WILMINGTON, NC +US: KCCX: State College/Central, PA +US: KLWX: Sterling, VA +US: KFCX: Blacksburg/Roanoke, VA +US: KRAX: Raleigh/Durham, NC +US: KGYX: Portland, ME +US: KDIX: Mt Holly/Philadelphia, PA +US: KPBZ: Pittsburgh, PA +US: KAKQ: Wakefield, VA +US: KMHX: Morehead City, NC +US: KGSP: Greer/Greenville/Sprtbg, SC +US: KILN: Wilmington/Cincinnati, OH +US: KCLE: Cleveland, OH +US: KCAE: Columbia, SC +US: KBGM: Binghamton, NY +US: KENX: Albany, NY +US: KBUF: Buffalo, NY +US: KCXX: Burlington, VT +US: KCBW: Caribou, ME +US: KBOX: Boston /Taunton, MA +US: KOKX: New York City, NY +US: KCLX: Charleston, SC +US: KRLX: Charleston, WV +US: ICAO: Responsible WFO +US: KBRO: Brownsville, TX +US: KABX: Albuquerque, NM +US: KAMA: Amarillo, TX +US: KFFC: Peachtree City/Atlanta, GA +US: KEWX: Austin/Sanantonio, TX +US: KBMX: Birmingham, AL +US: KCRP: Corpus Christi, TX +US: KFWS: Dallas / Ft. Worth, TX +US: KEPZ: El Paso, TX +US: KHGX: Houston/ Galveston, TX +US: KJAX: Jacksonville, FL +US: KBYX: Key West, FL +US: KMRX: Morristown/knoxville, TN +US: KLBB: Lubbock, TX +US: KLZK: Little Rock, AR +US: KLCH: Lake Charles, LA +US: KOHX: Nashville, TN +US: KMLB: Melbourne, FL +US: KNQA: Memphis, TN +US: KAMX: Miami, FL +US: KMAF: Midland/odessa, TX +US: KTLX: Norman, OK +US: KHTX: Huntsville, AL +US: KMOB: Mobile, AL +US: KTLH: Tallahassee, FL +US: KTBW: Tampa Bay Area, FL +US: KSJT: San Angelo, TX +US: KINX: Tulsa, OK +US: KSRX: Tulsa, OK +US: KLIX: New Orleans/slidell, LA +US: KDGX: Jackson, MS +US: KSHV: Shreveport, LA +US: ICAO: Responsible WFO +US: KLGX: Seattle / Tacoma, WA +US: KOTX: Spokane, WA +US: KEMX: Tucson, AZ +US: KYUX: Phoenix, AZ +US: KNKX: San Diego, CA +US: KMUX: Monterey/san Francisco, CA +US: KHNX: San Joaquin/hanford, CA +US: KSOX: San Diego, CA +US: KATX: Seattle / Tacoma, WA +US: KIWA: Phoenix, AZ +US: KRTX: Portland, OR +US: KSFX: Pocatello, ID +US: KRGX: Reno, NV +US: KDAX: Sacramento, CA +US: KMTX: Salt Lake City, UT +US: KPDT: Pendleton, OR +US: KMSX: Missoula, MT +US: KESX: Las Vegas, NV +US: KVTX: Los Angeles, CA +US: KMAX: Medford, OR +US: KFSX: Flagstaff, AZ +US: KGGW: Glasgow, MT +US: KLRX: Elko, NV +US: KBHX: Eureka, CA +US: KTFX: Great Falls, MT +US: KCBX: Boise, ID +US: KBLX: Billings, MT +US: KICX: Salt Lake City, UT +US: ICAO: Responsible Wfo W/ MSCF +US: PABC: Anchorage, AK +US: PAPD: Fairbanks, AK +US: PHKM: Honolulu, HI +US: PAHG: Anchorage, AK +US: PAKC: Anchorage, AK +US: PAIH: Anchorage, AK +US: PHMO: Honolulu, HI +US: PAEC: Fairbanks, AK +US: TJUA: San Juan, PR +US: PACG: Juneau, AK +US: PHKI: Honolulu, HI +US: PHWA: Honolulu, HI +US: ICAO: Responsible Wfo W/ MSCF +US: KFDR: Norman, OK +US: PGUA: Guam +US: KBBX: Sacramento, CA +US: KFDX: Albuquerque, NM +US: KGWX: Jackson, MS +US: KDOX: Wakefield, VA +US: KDYX: San Angelo, TX +US: KEYX: Las Vegas, NV +US: KEVX: Mobile, AL +US: KHPX: Paducah, KY +US: KTYX: Burlington, VT +US: KGRK: Dallas / Ft. Worth, TX +US: KPOE: Lake Charles, LA +US: KEOX: Tallahassee, FL +US: KHDX: El Paso, TX +US: KDFX: San Antonio, TX +US: KMXX: Birmingham, AL +US: KMBX: Bismarck, ND +US: KVAX: Jacksonville, FL +US: KJGX: Peachtree City/atlanta, GA +US: KVNX: Norman, OK +US: KVBX: Vandenberg Afb: Orcutt, CA +EU: Europe +EU: GB: Great Brittain +EU: SCAN: Scandinavia +EU: ALPS: The Alps +EU: NL: The Netherlands +EU: DE: Germany +EU: SP: Spain +EU: FR: France +EU: IT: Italy +EU: PL: Poland +EU: GR: Greece +EU: TU: Turkey +EU: RU: Russia +EU: BA: Bahrain +EU: BC: Botswana +EU: SE: Republic of Seychelles +EU: HU: Hungary +EU: UK: Ukraine +AF: AF: Africa +AF: WA: West Africa +AF: ZA: South Africa +AF: DZ: Algeria +AF: CE: Canary Islands +AF: NG: Nigeria +AF: TD: Chad +AF: CG: Democratic Republic of Congo +AF: EG: Egypt +AF: ET: Ethiopia +AF: CM: Cameroon +AF: IS: Israel +AF: LY: Libya +AF: MG: Madagascar +AF: MO: Morocco +AF: BW: Namibia +AF: SA: Saudi Arabia +AF: SO: Somalia +AF: SD: Sudan +AF: TZ: Tanzania +AF: TN: Tunisia +AF: ZM: Zambia +AF: KE: Kenya +AF: AO: Angola +DE: BAW: Baden-WΓΌrttemberg +DE: BAY: Bavaria +DE: BBB: Berlin +DE: BBB: Brandenburg +DE: HES: Hesse +DE: MVP: Mecklenburg-Western Pomerania +DE: NIB: Lower Saxony +DE: NIB: Bremen +DE: NRW: North Rhine-Westphalia +DE: RPS: Rhineland-Palatinate +DE: RPS: Saarland +DE: SAC: Saxony +DE: SAA: Saxony-Anhalt +DE: SHH: Schleswig-Holstein +DE: SHH: Hamburg +DE: THU: Thuringia" | dmenu -r -i -l 50 -p "Select a radar to use as default:" | tr "[:lower:]" "[:upper:]")" + +# Ensure user did not escape. +[ -z "$chosen" ] && exit 1 + +# Set continent code and radar code. +continentcode=${chosen%%:*} +radarcode=${chosen#* } radarcode=${radarcode%:*} + +# Print codes to $radarloc file. + printf "%s,%s\\n" "$continentcode" "$radarcode" > "$radarloc" ;} + +getdoppler() { + cont=$(cut -c -2 "$radarloc") + loc=$(cut -c 4- "$radarloc") + notify-send "π¦οΈ Doppler RADAR" "Pulling most recent Doppler RADAR for $loc." + case "$cont" in + "US") curl -sL "https://radar.weather.gov/ridge/lite/${loc}_loop.gif" > "$doppler" ;; + "EU") curl -sL "https://api.sat24.com/animated/${loc}/rainTMC/2/" > "$doppler" ;; + "AF") curl -sL "https://api.sat24.com/animated/${loc}/rain/2/" > "$doppler" ;; + "DE") loc="$(echo "$loc" | tr "[:upper:]" "[:lower:]")" + curl -sL "https://www.dwd.de/DWD/wetter/radar/radfilm_${loc}_akt.gif" > "$doppler" ;; + esac +} + +showdoppler() { setsid -f mpv --no-osc --loop=inf --no-terminal "$doppler" ;} + +case $BLOCK_BUTTON in + 1) [ ! -f "$radarloc" ] && pickloc && getdoppler + [ $(($(date '+%s') - $(stat -c %Y "$doppler"))) -gt "$secs" ] && getdoppler + showdoppler ;; + 2) pickloc && getdoppler && showdoppler ;; + 3) notify-send "πΊοΈ Doppler RADAR module" "\- Left click for local Doppler RADAR. +- Middle click to update RADAR location. +After $secs seconds, new clicks will also automatically update the doppler RADAR." ;; + 6) "$TERMINAL" -e "$EDITOR" "$0" ;; +esac + +echo πΊοΈ diff --git a/dotfiles/system/.local/bin/statusbar/sb-forecast b/dotfiles/system/.local/bin/statusbar/sb-forecast new file mode 100755 index 0000000..45584c5 --- /dev/null +++ b/dotfiles/system/.local/bin/statusbar/sb-forecast @@ -0,0 +1,35 @@ +#!/bin/sh + +# Displays todays precipication chance (β) and daily low (π₯Ά) and high (π). +# Usually intended for the statusbar. + +# If we have internet, get a weather report from wttr.in and store it locally. +# You could set up a shell alias to view the full file in a pager in the +# terminal if desired. This function will only be run once a day when needed. +weatherreport="${XDG_CACHE_HOME:-$HOME/.cache}/weatherreport" +getforecast() { curl -sf "wttr.in/$LOCATION" > "$weatherreport" || exit 1 ;} + +# Some very particular and terse stream manipulation. We get the maximum +# precipitation chance and the daily high and low from the downloaded file and +# display them with coresponding emojis. +showweather() { printf "%s" "$(sed '16q;d' "$weatherreport" | + grep -wo "[0-9]*%" | sort -rn | sed "s/^/β/g;1q" | tr -d '\n')" +sed '13q;d' "$weatherreport" | grep -o "m\\([-+]\\)*[0-9]\\+" | sed 's/+//g' | sort -n -t 'm' -k 2n | sed -e 1b -e '$!d' | tr '\n|m' ' ' | awk '{print " π₯Ά" $1 "Β°","π" $2 "Β°"}' ;} + +case $BLOCK_BUTTON in + 1) setsid -f "$TERMINAL" -e less -Srf "$weatherreport" ;; + 2) getforecast && showweather ;; + 3) notify-send "π Weather module" "\- Left click for full forecast. +- Middle click to update forecast. +β: Chance of rain/snow +π₯Ά: Daily low +π: Daily high" ;; + 6) "$TERMINAL" -e "$EDITOR" "$0" ;; +esac + +# The test if our forcecast is updated to the day. If it isn't download a new +# weather report from wttr.in with the above function. +[ "$(stat -c %y "$weatherreport" 2>/dev/null | cut -d' ' -f1)" = "$(date '+%Y-%m-%d')" ] || + getforecast + +showweather diff --git a/dotfiles/system/.local/bin/statusbar/sb-help-icon b/dotfiles/system/.local/bin/statusbar/sb-help-icon new file mode 100755 index 0000000..8fa4a52 --- /dev/null +++ b/dotfiles/system/.local/bin/statusbar/sb-help-icon @@ -0,0 +1,17 @@ +#!/bin/sh + +# The clickable help menu. Middle click to restart wm. + +# If dwm is running, use dwm's readme and restart. +pidof dwm >/dev/null && + READMEFILE=/usr/local/share/dwm/larbs.mom + restartwm() { pkill -HUP dwm ;} || + restartwm() { i3 restart ;} + +case $BLOCK_BUTTON in + 1) groff -mom "${READMEFILE:-${XDG_DATA_HOME:-$HOME/.local/share}/larbs/readme.mom}" -Tpdf | zathura - ;; + 2) restartwm ;; + 3) notify-send "β Help module" "\- Left click to open LARBS guide. +- Middle click to refresh window manager." ;; + 6) "$TERMINAL" -e "$EDITOR" "$0" ;; +esac; echo "β" diff --git a/dotfiles/system/.local/bin/statusbar/sb-internet b/dotfiles/system/.local/bin/statusbar/sb-internet new file mode 100755 index 0000000..94b7da2 --- /dev/null +++ b/dotfiles/system/.local/bin/statusbar/sb-internet @@ -0,0 +1,26 @@ +#!/bin/sh + +# Show wifi πΆ and percent strength or π‘ if none. +# Show π if connected to ethernet or β if none. +# Show π if a vpn connection is active + +case $BLOCK_BUTTON in + 1) "$TERMINAL" -e nmtui; pkill -RTMIN+4 dwmblocks ;; + 3) notify-send "π Internet module" "\- Click to connect +β: wifi disabled +π‘: no wifi connection +πΆ: wifi connection with quality +β: no ethernet +π: ethernet working +π: vpn is active +" ;; + 6) "$TERMINAL" -e "$EDITOR" "$0" ;; +esac + +if grep -xq 'up' /sys/class/net/w*/operstate 2>/dev/null ; then + wifiicon="$(awk '/^\s*w/ { print "πΆ", int($3 * 100 / 70) "% " }' /proc/net/wireless)" +elif grep -xq 'down' /sys/class/net/w*/operstate 2>/dev/null ; then + grep -xq '0x1003' /sys/class/net/w*/flags && wifiicon="π‘ " || wifiicon="β " +fi + +printf "%s%s%s\n" "$wifiicon" "$(sed "s/down/β/;s/up/π/" /sys/class/net/e*/operstate 2>/dev/null)" "$(sed "s/.*/π/" /sys/class/net/tun*/operstate 2>/dev/null)" diff --git a/dotfiles/system/.local/bin/statusbar/sb-iplocate b/dotfiles/system/.local/bin/statusbar/sb-iplocate new file mode 100755 index 0000000..02adab8 --- /dev/null +++ b/dotfiles/system/.local/bin/statusbar/sb-iplocate @@ -0,0 +1,10 @@ +#!/bin/sh + +# Gets your public ip address checks which country you are in and +# displays that information in the statusbar +# +# https://www.maketecheasier.com/ip-address-geolocation-lookups-linux/ + +ifinstalled "geoip" || exit +addr="$(curl ifconfig.me 2>/dev/null)" || exit +grep "flag: " "${XDG_DATA_HOME:-$HOME/.local/share}/larbs/emoji" | grep "$(geoiplookup "$addr" | sed 's/.*, //')" | sed "s/flag: //;s/;.*//" diff --git a/dotfiles/system/.local/bin/statusbar/sb-kbselect b/dotfiles/system/.local/bin/statusbar/sb-kbselect new file mode 100755 index 0000000..f0c923f --- /dev/null +++ b/dotfiles/system/.local/bin/statusbar/sb-kbselect @@ -0,0 +1,16 @@ +#!/bin/sh +# works on any init system +# requirements: dmenu, xorg-setxkbmap +kb="$(setxkbmap -query | grep -oP 'layout:\s*\K\w+')" || exit 1 + +case $BLOCK_BUTTON in + 1) kb_choice="$(awk '/! layout/{flag=1; next} /! variant/{flag=0} flag {print $2, "- " $1}' /usr/share/X11/xkb/rules/base.lst | dmenu -l 15)" + kb="$(echo "$kb_choice" | awk '{print $3}')" + setxkbmap "$kb" + pkill -RTMIN+30 "${STATUSBAR:-dwmblocks}";; + 3) notify-send "β¨ Keyboard/language module" "$(printf "%s" "\- Current layout: $(setxkbmap -query | grep -oP 'layout:\s*\K\w+')") +- Left click to change keyboard.";; + 6) "$TERMINAL" -e "$EDITOR" "$0" ;; +esac + +echo "$kb" diff --git a/dotfiles/system/.local/bin/statusbar/sb-mailbox b/dotfiles/system/.local/bin/statusbar/sb-mailbox new file mode 100755 index 0000000..2132184 --- /dev/null +++ b/dotfiles/system/.local/bin/statusbar/sb-mailbox @@ -0,0 +1,20 @@ +#!/bin/sh + +# Displays number of unread mail and an loading icon if updating. +# When clicked, brings up `neomutt`. + +case $BLOCK_BUTTON in + 1) setsid -f "$TERMINAL" -e neomutt ;; + 2) setsid -f mw -Y >/dev/null ;; + 3) notify-send "π¬ Mail module" "\- Shows unread mail +- Shows π if syncing mail +- Left click opens neomutt +- Middle click syncs mail" ;; + 6) "$TERMINAL" -e "$EDITOR" "$0" ;; +esac + +unread="$(find "${XDG_DATA_HOME:-$HOME/.local/share}"/mail/*/[Ii][Nn][Bb][Oo][Xx]/new/* -type f | wc -l 2>/dev/null)" + +pidof mbsync >/dev/null 2>&1 && icon="π" + +[ "$unread" = "0" ] && [ "$icon" = "" ] || echo "π¬$unread$icon" diff --git a/dotfiles/system/.local/bin/statusbar/sb-memory b/dotfiles/system/.local/bin/statusbar/sb-memory new file mode 100755 index 0000000..01d3daf --- /dev/null +++ b/dotfiles/system/.local/bin/statusbar/sb-memory @@ -0,0 +1,12 @@ +#!/bin/sh + +case $BLOCK_BUTTON in + 1) notify-send "π§ Memory hogs" "$(ps axch -o cmd:15,%mem --sort=-%mem | head)" ;; + 2) setsid -f "$TERMINAL" -e htop ;; + 3) notify-send "π§ Memory module" "\- Shows Memory Used/Total. +- Click to show memory hogs. +- Middle click to open htop." ;; + 6) "$TERMINAL" -e "$EDITOR" "$0" ;; +esac + +free --mebi | sed -n '2{p;q}' | awk '{printf ("π§ %2.2fGiB/%2.2fGiB\n", ( $3 / 1024), ($2 / 1024))}' diff --git a/dotfiles/system/.local/bin/statusbar/sb-moonphase b/dotfiles/system/.local/bin/statusbar/sb-moonphase new file mode 100755 index 0000000..fab8b4d --- /dev/null +++ b/dotfiles/system/.local/bin/statusbar/sb-moonphase @@ -0,0 +1,37 @@ +#!/bin/sh + +# Shows the current moon phase. + +moonfile="${XDG_DATA_HOME:-$HOME/.local/share}/moonphase" + +[ "$(stat -c %y "$moonfile" 2>/dev/null | cut -d' ' -f1)" = "$(date '+%Y-%m-%d')" ] || + { curl -sf "wttr.in/?format=%m" > "$moonfile" || exit 1 ;} + +icon="$(cat "$moonfile")" + +case "$icon" in + π) name="New" ;; + π) name="Waxing Crescent" ;; + π) name="First Quarter" ;; + π) name="Waxing Gibbous" ;; + π) name="Full" ;; + π) name="Waning Gibbous" ;; + π) name="Last Quarter" ;; + π) name="Waning Crescent" ;; + *) exit 1 ;; +esac + +echo "${icon-?}" + +case $BLOCK_BUTTON in + 3) notify-send "π Moon phase module" "Displays current moon phase. +- π: New +- π: Waxing Crescent +- π: First Quarter +- π: Waxing Gibbous +- π: Full +- π: Waning Gibbous +- π: Last Quarter +- π: Waning Crescent" ;; + 6) "$TERMINAL" -e "$EDITOR" "$0" ;; +esac diff --git a/dotfiles/system/.local/bin/statusbar/sb-mpdup b/dotfiles/system/.local/bin/statusbar/sb-mpdup new file mode 100755 index 0000000..af81a7d --- /dev/null +++ b/dotfiles/system/.local/bin/statusbar/sb-mpdup @@ -0,0 +1,8 @@ +#!/bin/sh + +# This loop will update the mpd statusbar module whenever a command changes the +# music player's status. mpd must be running on X's start for this to work. + +while : ; do + mpc idle >/dev/null && kill -45 "$(pidof "${STATUSBAR:-dwmblocks}")" || break +done diff --git a/dotfiles/system/.local/bin/statusbar/sb-music b/dotfiles/system/.local/bin/statusbar/sb-music new file mode 100755 index 0000000..7ea7032 --- /dev/null +++ b/dotfiles/system/.local/bin/statusbar/sb-music @@ -0,0 +1,19 @@ +#!/bin/sh + +filter() { mpc | sed "/^volume:/d;s/\\&/&/g;s/\\[paused\\].*/βΈ/g;/\\[playing\\].*/d;/^ERROR/Q" | paste -sd ' ' -;} + +pidof -x sb-mpdup >/dev/null 2>&1 || sb-mpdup >/dev/null 2>&1 & + +case $BLOCK_BUTTON in + 1) mpc status | filter ; setsid -f "$TERMINAL" -e ncmpcpp ;; # right click, pause/unpause + 2) mpc toggle | filter ;; # right click, pause/unpause + 3) mpc status | filter ; notify-send "π΅ Music module" "\- Shows mpd song playing. +- βΈ when paused. +- Left click opens ncmpcpp. +- Middle click pauses. +- Scroll changes track.";; # right click, pause/unpause + 4) mpc prev | filter ;; # scroll up, previous + 5) mpc next | filter ;; # scroll down, next + 6) mpc status | filter ; "$TERMINAL" -e "$EDITOR" "$0" ;; + *) mpc status | filter ;; +esac diff --git a/dotfiles/system/.local/bin/statusbar/sb-nettraf b/dotfiles/system/.local/bin/statusbar/sb-nettraf new file mode 100755 index 0000000..178f677 --- /dev/null +++ b/dotfiles/system/.local/bin/statusbar/sb-nettraf @@ -0,0 +1,29 @@ +#!/bin/sh + +# Module showing network traffic. Shows how much data has been received (RX) or +# transmitted (TX) since the previous time this script ran. So if run every +# second, gives network traffic per second. + +case $BLOCK_BUTTON in + 1) setsid -f "$TERMINAL" -e bmon ;; + 3) notify-send "π Network traffic module" "π»: Traffic received +πΊ: Traffic transmitted" ;; + 6) "$TERMINAL" -e "$EDITOR" "$0" ;; +esac + +update() { + sum=0 + for arg; do + read -r i < "$arg" + sum=$(( sum + i )) + done + cache=/tmp/${1##*/} + [ -f "$cache" ] && read -r old < "$cache" || old=0 + printf %d\\n "$sum" > "$cache" + printf %d\\n $(( sum - old )) +} + +rx=$(update /sys/class/net/[ew]*/statistics/rx_bytes) +tx=$(update /sys/class/net/[ew]*/statistics/tx_bytes) + +printf "π»%4sB πΊ%4sB\\n" $(numfmt --to=iec $rx $tx) diff --git a/dotfiles/system/.local/bin/statusbar/sb-news b/dotfiles/system/.local/bin/statusbar/sb-news new file mode 100755 index 0000000..fe701db --- /dev/null +++ b/dotfiles/system/.local/bin/statusbar/sb-news @@ -0,0 +1,17 @@ +#!/bin/sh + +# Displays number of unread news items and an loading icon if updating. +# When clicked, brings up `newsboat`. + +case $BLOCK_BUTTON in + 1) setsid "$TERMINAL" -e newsboat ;; + 2) setsid -f newsup >/dev/null exit ;; + 3) notify-send "π° News module" "\- Shows unread news items +- Shows π if updating with \`newsup\` +- Left click opens newsboat +- Middle click syncs RSS feeds +<b>Note:</b> Only one instance of newsboat (including updates) may be running at a time." ;; + 6) "$TERMINAL" -e "$EDITOR" "$0" ;; +esac + + cat /tmp/newsupdate 2>/dev/null || echo "$(newsboat -x print-unread | awk '{ if($1>0) print "π°" $1}')$(cat "${XDG_CONFIG_HOME:-$HOME/.config}"/newsboat/.update 2>/dev/null)" diff --git a/dotfiles/system/.local/bin/statusbar/sb-pacpackages b/dotfiles/system/.local/bin/statusbar/sb-pacpackages new file mode 100755 index 0000000..37ebed3 --- /dev/null +++ b/dotfiles/system/.local/bin/statusbar/sb-pacpackages @@ -0,0 +1,29 @@ +#!/bin/sh + +# Displays number of upgradeable packages. +# For this to work, have a `pacman -Sy` command run in the background as a +# cronjob every so often as root. This script will then read those packages. +# When clicked, it will run an upgrade via pacman. +# +# Add the following text as a file in /usr/share/libalpm/hooks/statusbar.hook: +# +# [Trigger] +# Operation = Upgrade +# Type = Package +# Target = * +# +# [Action] +# Description = Updating statusbar... +# When = PostTransaction +# Exec = /usr/bin/pkill -RTMIN+8 dwmblocks # Or i3blocks if using i3. + +case $BLOCK_BUTTON in + 1) setsid -f "$TERMINAL" -e sb-popupgrade ;; + 2) notify-send "$(/usr/bin/pacman -Qu)" ;; + 3) notify-send "π Upgrade module" "π¦: number of upgradable packages +- Left click to upgrade packages +- Middle click to show upgradable packages" ;; + 6) "$TERMINAL" -e "$EDITOR" "$0" ;; +esac + +pacman -Qu | grep -Fcv "[ignored]" | sed "s/^/π¦/;s/^π¦0$//g" diff --git a/dotfiles/system/.local/bin/statusbar/sb-popupgrade b/dotfiles/system/.local/bin/statusbar/sb-popupgrade new file mode 100755 index 0000000..29d6230 --- /dev/null +++ b/dotfiles/system/.local/bin/statusbar/sb-popupgrade @@ -0,0 +1,9 @@ +#!/bin/sh + +printf "Beginning upgrade.\\n" + +yay -Syu +pkill -RTMIN+8 "${STATUSBAR:-dwmblocks}" + +printf "\\nUpgrade complete.\\nPress <Enter> to exit window.\\n\\n" +read -r _ diff --git a/dotfiles/system/.local/bin/statusbar/sb-price b/dotfiles/system/.local/bin/statusbar/sb-price new file mode 100755 index 0000000..42c84c1 --- /dev/null +++ b/dotfiles/system/.local/bin/statusbar/sb-price @@ -0,0 +1,50 @@ +#!/bin/sh + +# Usage: +# price <url> <Name of currency> <icon> <Price to show in> +# price bat "Basic Attention Token" π¦ +# When the name of the currency is multi-word, put it in quotes. + +[ -z "$3" ] && exit 1 + +# use $4 as currency, if not passed in use "usd" as default +currency="${4:-usd}" +interval="@14d" # History contained in chart preceded by '@' (7d = 7 days) +dir="${XDG_DATA_HOME:-$HOME/.local/share}/crypto-prices" +pricefile="$dir/$1-$currency" +chartfile="$dir/$1-$currency-chart" + +updateprice() { temp="$(mktemp)" + curl -s "$currency.rate.sx/1$1" > "$temp" && + mv -f "$temp" "$pricefile" && + curl -s "$currency.rate.sx/$1$interval" > "$temp" && + mv -f "$temp" "$chartfile" ;} + +[ -d "$dir" ] || mkdir -p "$dir" + +[ "$(stat -c %x "$pricefile" 2>/dev/null | cut -d' ' -f1)" != "$(date '+%Y-%m-%d')" ] && + updateprice "$1" + +case $BLOCK_BUTTON in + 1) setsid "$TERMINAL" -e less -Srf "$chartfile" ;; + 2) notify-send -u low "$3 Updating..." "Updating $2 price..." + updateprice "$1" && notify-send "$3 Update complete." "$2 price is now +\$$(cat "$pricefile")" ;; + 3) uptime="$(date -d "$(stat -c %x "$pricefile")" '+%D at %T' | sed "s|$(date '+%D')|Today|")" + notify-send "$3 $2 module" "\- <b>Exact price: \$$(cat "$pricefile")</b> +- Left click for chart of changes. +- Middle click to update. +- Shows π if updating prices. +- <b>Last updated: + $uptime</b>" ;; + 6) "$TERMINAL" -e "$EDITOR" "$0" ;; +esac + +case "$currency" in + usd) symb="$" ;; + gbp) symb="Β£" ;; + eur) symb="β¬" ;; + btc) symb="βΏ" ;; +esac + +printf "$3$symb%0.2f$after" "$(cat "$pricefile")" diff --git a/dotfiles/system/.local/bin/statusbar/sb-tasks b/dotfiles/system/.local/bin/statusbar/sb-tasks new file mode 100755 index 0000000..586300e --- /dev/null +++ b/dotfiles/system/.local/bin/statusbar/sb-tasks @@ -0,0 +1,20 @@ +#!/bin/sh + +# Originally by Andr3as07 <https://github.com/Andr3as07> +# Some changes by Luke +# Rebuild by Tenyun + +# This block displays the number running background tasks. Requires tsp. + +num=$(tsp -l | awk -v numr=0 -v numq=0 '{if (/running/)numr++; if (/queued/)numq++} END{print numr+numq"("numq")"}') + +# Handle mouse clicks +case $BLOCK_BUTTON in + 1) setsid -f "$TERMINAL" -e tsp -l ;; + 3) notify-send "Tasks module" "π€: number of running/queued background tasks +- Left click opens tsp" ;; # Right click + 2) $EDITOR "$0" ;; # Middle click +esac + +[ "$num" != "0(0)" ] && + echo "π€$num" diff --git a/dotfiles/system/.local/bin/statusbar/sb-torrent b/dotfiles/system/.local/bin/statusbar/sb-torrent new file mode 100755 index 0000000..6527005 --- /dev/null +++ b/dotfiles/system/.local/bin/statusbar/sb-torrent @@ -0,0 +1,27 @@ +#!/bin/sh + +transmission-remote -l | grep % | + sed " # The letters are for sorting and will not appear. + s/.*Stopped.*/A π/; + s/.*Seeding.*/Z π±/; + s/.*100%.*/N β
/; + s/.*Idle.*/B π°οΈ/; + s/.*Uploading.*/L β¬οΈ/; + s/.*%.*/M β¬οΈ/" | + sort -h | uniq -c | awk '{print $3 $1}' | paste -sd ' ' - + +case $BLOCK_BUTTON in + 1) setsid -f "$TERMINAL" -e tremc ;; + 2) td-toggle ;; + 3) notify-send "π± Torrent module" "\- Left click to open tremc. +- Middle click to toggle transmission. +- Shift click to edit script. +Module shows number of torrents: +π: paused +π°: idle (seeds needed) +πΌ: uploading (unfinished) +π½: downloading +β
: done +π±: done and seeding" ;; + 6) "$TERMINAL" -e "$EDITOR" "$0" ;; +esac diff --git a/dotfiles/system/.local/bin/statusbar/sb-volume b/dotfiles/system/.local/bin/statusbar/sb-volume new file mode 100755 index 0000000..3cfdc45 --- /dev/null +++ b/dotfiles/system/.local/bin/statusbar/sb-volume @@ -0,0 +1,30 @@ +#!/bin/sh + +# Prints the current volume or π if muted. + +case $BLOCK_BUTTON in + 1) setsid -f "$TERMINAL" -e pulsemixer ;; + 2) pamixer -t ;; + 4) pamixer --allow-boost -i 1 ;; + 5) pamixer --allow-boost -d 1 ;; + 3) notify-send "π’ Volume module" "\- Shows volume π, π if muted. +- Middle click to mute. +- Scroll to change." ;; + 6) "$TERMINAL" -e "$EDITOR" "$0" ;; +esac + +[ $(pamixer --get-mute) = true ] && echo π && exit + +vol="$(pamixer --get-volume)" + +if [ "$vol" -gt "70" ]; then + icon="π" +elif [ "$vol" -gt "30" ]; then + icon="π" +elif [ "$vol" -gt "0" ]; then + icon="π" +else + echo π && exit +fi + +echo "$icon$vol%" diff --git a/dotfiles/system/.local/bin/steam b/dotfiles/system/.local/bin/steam new file mode 100755 index 0000000..3d30238 --- /dev/null +++ b/dotfiles/system/.local/bin/steam @@ -0,0 +1,2 @@ +#!/bin/sh +flatpak run com.valvesoftware.Steam >> "$HOME/.local/var/logs/steam.log" 2>&1 diff --git a/dotfiles/system/.local/bin/sudo-update-grub b/dotfiles/system/.local/bin/sudo-update-grub new file mode 100755 index 0000000..5d67823 --- /dev/null +++ b/dotfiles/system/.local/bin/sudo-update-grub @@ -0,0 +1 @@ +sudo grub-mkconfig -o /boot/grub/grub.cfg diff --git a/dotfiles/system/.local/bin/sysupdate b/dotfiles/system/.local/bin/sysupdate new file mode 100755 index 0000000..357348d --- /dev/null +++ b/dotfiles/system/.local/bin/sysupdate @@ -0,0 +1,5 @@ +#!/bin/sh +# Craig Jennings <c@cjennings.net> + + +yay -Syu --noconfirm
\ No newline at end of file diff --git a/dotfiles/system/.local/bin/td-toggle b/dotfiles/system/.local/bin/td-toggle new file mode 100755 index 0000000..de1a0e6 --- /dev/null +++ b/dotfiles/system/.local/bin/td-toggle @@ -0,0 +1,12 @@ +#!/bin/sh + +# If transmission-daemon is running, will ask to kill, else will ask to start. + +if pidof transmission-daemon >/dev/null ; +then + [ "$(printf "No\\nYes" | dmenu -i -p "Turn off transmission-daemon?")" = "Yes" ] && killall transmission-daemon && notify-send "transmission-daemon disabled." +else + ifinstalled transmission-cli || exit + [ "$(printf "No\\nYes" | dmenu -i -p "Turn on transmission daemon?")" = "Yes" ] && transmission-daemon && notify-send "transmission-daemon enabled." +fi +sleep 3 && pkill -RTMIN+7 "${STATUSBAR:-dwmblocks}" diff --git a/dotfiles/system/.local/bin/timezone-change b/dotfiles/system/.local/bin/timezone-change new file mode 100755 index 0000000..c5a4e5a --- /dev/null +++ b/dotfiles/system/.local/bin/timezone-change @@ -0,0 +1,68 @@ +#!/usr/bin/env bash +# Craig Jennings <c@cjennings.net> + +# Convenience script since I can't ever remember the two part TZ identifier to +# change timezones. + +case $1 in + "eastern"|"ny"|"nyc"|"boston"|"dc") + sudo timedatectl set-timezone "US/Eastern" + ;; + "central"|"nola"|"home"|"chicago") + sudo timedatectl set-timezone "US/Central" + ;; + "mountain") + sudo timedatectl set-timezone "US/Mountain" + ;; + "pacific"|"sf"|"oakland"|"berkeley"|"hb"|"california") + sudo timedatectl set-timezone "US/Pacific" + ;; + "london"|"england"|"britain"|"gb") + sudo timedatectl set-timezone "Europe/London" + ;; + "hawaii") + sudo timedatectl set-timezone "US/Hawaii" + ;; + "armenia"|"yerevan") + sudo timedatectl set-timezone "Asia/Yerevan" + ;; + "greece"|"athens") + sudo timedatectl set-timezone "Europe/Athens" + ;; + "germany"|"berlin") + sudo timedatectl set-timezone "Europe/Berlin" + ;; + "turkey"|"istanbul") + sudo timedatectl set-timezone "Europe/Istanbul" + ;; + "portugal"|"lisbon") + sudo timedatectl set-timezone "Europe/Portugal" + ;; + "spain"|"madrid") + sudo timedatectl set-timezone "Europe/Madrid" + ;; + "france"|"paris") + sudo timedatectl set-timezone "Europe/Paris" + ;; + "italy"|"rome"|"naples"|"ischia") + sudo timedatectl set-timezone "Europe/Rome" + ;; + "austria"|"vienna") + sudo timedatectl set-timezone "Europe/Vienna" + ;; + "japan"|"tokyo") + sudo timedatectl set-timezone "Asia/Tokyo" + ;; + "jamaica") + sudo timedatectl set-timezone "America/Jamaica" + ;; + "st_lucia"|"grenadines"|"st_vincent"|"nevis"|"st_kitts"|"puerto_rico") + sudo timedatectl set-timezone "America/Puerto_Rico" + ;; + *) + echo + "Invalid option chosen." + echo + "Some valid options are: eastern, central, pacific, rome, london, st_lucia, italy, france, spain ." + ;; +esac diff --git a/dotfiles/system/.local/bin/timezone-set b/dotfiles/system/.local/bin/timezone-set new file mode 100755 index 0000000..8f48729 --- /dev/null +++ b/dotfiles/system/.local/bin/timezone-set @@ -0,0 +1,17 @@ +#!/bin/sh +# Craig Jennings <c@cjennings.net> + +# sets timezone based on the ip-address when there's network connectivity + +# Check network status +if ping -q -c 1 -W 1 google.com >/dev/null; then + NEW_TIMEZONE="$(curl --fail --silent https://ipapi.co/timezone)" + if sudo timedatectl set-timezone "$NEW_TIMEZONE"; then + notify-send "Setting timezone to $NEW_TIMEZONE successul." + else + notify-send "Attempt to set timezone failed." + fi +else + notify-send "No network connection detected. Cannot set timezone automatically." +fi +dwmstatus diff --git a/dotfiles/system/.local/bin/toggle-touchpad b/dotfiles/system/.local/bin/toggle-touchpad new file mode 100755 index 0000000..9dde99b --- /dev/null +++ b/dotfiles/system/.local/bin/toggle-touchpad @@ -0,0 +1,16 @@ +#!/bin/sh +# Toggle touchpad status +# Using libinput and xinput + +# Use xinput list and do a search for touchpads. Then get the first one and get its name. +device="$(xinput list | grep -P '(?<= )[\w\s:]*(?i)(touchpad|synaptics)(?-i).*?(?=\s*id)' -o | head -n1)" + +# If it was activated disable it and if it wasn't disable it +if [[ "$(xinput list-props "$device" | grep -P ".*Device Enabled.*\K.(?=$)" -o)" == "1" ]] +then + xinput disable "$device" + notify-send "Touchpad" "Touchpad disabled" +else + xinput enable "$device" + notify-send "Touchpad" "Touchpad enabled" +fi diff --git a/dotfiles/system/.local/bin/torwrap b/dotfiles/system/.local/bin/torwrap new file mode 100755 index 0000000..8b20ad4 --- /dev/null +++ b/dotfiles/system/.local/bin/torwrap @@ -0,0 +1,7 @@ +#!/bin/sh + +ifinstalled tremc transmission-cli || exit + +! pidof transmission-daemon >/dev/null && transmission-daemon && notify-send "Starting torrent daemon..." + +$TERMINAL -e tremc; pkill -RTMIN+7 "${STATUSBAR:-dwmblocks}" diff --git a/dotfiles/system/.local/bin/touchpad-app b/dotfiles/system/.local/bin/touchpad-app new file mode 100755 index 0000000..6310c6d --- /dev/null +++ b/dotfiles/system/.local/bin/touchpad-app @@ -0,0 +1,3 @@ +#!/bin/sh + +/usr/bin/touchpad-indicator &
\ No newline at end of file diff --git a/dotfiles/system/.local/bin/transadd b/dotfiles/system/.local/bin/transadd new file mode 100755 index 0000000..a598fad --- /dev/null +++ b/dotfiles/system/.local/bin/transadd @@ -0,0 +1,9 @@ +#!/bin/sh + +# Mimeapp script for adding torrent to transmission-daemon, but will also start the daemon first if not running. + +# transmission-daemon sometimes fails to take remote requests in its first moments, hence the sleep. + +pidof transmission-daemon >/dev/null || (transmission-daemon && notify-send "Starting transmission daemon..." && sleep 3 && pkill -RTMIN+7 "${STATUSBAR:-dwmblocks}") + +transmission-remote -a "$@" && notify-send "π½ Torrent added." diff --git a/dotfiles/system/.local/bin/update-backup-repositories b/dotfiles/system/.local/bin/update-backup-repositories new file mode 100755 index 0000000..a91b438 --- /dev/null +++ b/dotfiles/system/.local/bin/update-backup-repositories @@ -0,0 +1,56 @@ +#!/bin/sh +# Craig Jennings <c@cjennings.net> +# updates or clones repositories for backup + +REPOS_HOME="/media/backup/repositories/" + +# Create REPOS_HOME dir if not exists +if [ ! -d "$REPOS_HOME" ]; then + mkdir -p "$REPOS_HOME" +fi + +# Make sure REPOS_HOME dir is writable +if [ ! -w "$REPOS_HOME" ]; then + echo "Directory $REPOS_HOME is not writable." + exit 1 +fi + +# Make sure git is available +if ! command -v git >/dev/null 2>&1; then + echo "Git command does not exist. Please install git." + exit 1 +fi + +cd "$REPOS_HOME" + +repos=" +git@cjennings.net:archsetup.git +git@cjennings.net:dmenu.git +git@cjennings.net:dotemacs.git +git@cjennings.net:dotfiles.git +git@cjennings.net:dwm.git +git@cjennings.net:pinentry-dmenu.git +git@cjennings.net:rsyncshot.git +git@cjennings.net:st.git +git@github.com:cjennings/emacs-wttrin.git +https://github.com/d12frosted/elpa-mirror.git +https://github.com/mirrors/emacs.git +" + +for repo in $repos; do + dir=$(echo "$repo" | awk -F'[/:]' '{gsub(/.git/, "", $NF); print $NF}') + # Note on the above awk command: + # awk -F'[/:]': uses awk with a regex field delimiter that matches both : and / + # which will work with both ssh and http style URLs. + # '{gsub(/.git/, "", $NF); print $NF}: This removes the ".git" postfixes from the last field ($NF) + # which leaves only the repo name. + + fullpath="$REPOS_HOME$dir" + if [ -d "$fullpath" ]; then + echo "Repository $dir exists at $fullpath, pulling..." + (cd "$fullpath" && git pull) + else + echo "Repository $dir doesn't exist at $fullpath, cloning..." + cd "$REPOS_HOME" && git clone --depth 1 "$repo" "$fullpath" + fi +done diff --git a/dotfiles/system/.local/bin/updatemirrors b/dotfiles/system/.local/bin/updatemirrors new file mode 100755 index 0000000..3ba4f7f --- /dev/null +++ b/dotfiles/system/.local/bin/updatemirrors @@ -0,0 +1,20 @@ +#!/bin/sh +# cjennings generates arch linux mirror list +echo "Updating mirrorlist. Please be patient. This may take a few minutes...." +echo " " + +sudo reflector \ + --connection-timeout 3 \ + --download-timeout 3 \ + --protocol https \ + --country US \ + --age 18 \ + --latest 20 \ + --score 10 \ + --fastest 5 \ + --sort score \ + --save /etc/pacman.d/mirrorlist > /dev/null 2>&1 + +cat /etc/pacman.d/mirrorlist +echo " " +echo "Done." diff --git a/dotfiles/system/.local/bin/virtstart b/dotfiles/system/.local/bin/virtstart new file mode 100755 index 0000000..f9a326f --- /dev/null +++ b/dotfiles/system/.local/bin/virtstart @@ -0,0 +1,8 @@ +#!/bin/bash +# launch a virtual machine in fullscreen. +# the argument is the machine name + +export LIBVIRT_DEFAULT_URI="qemu:///system" + +/usr/bin/virsh start $1 +/usr/bin/virt-viewer -f -w -a $1 diff --git a/dotfiles/system/.local/bin/wallsearch b/dotfiles/system/.local/bin/wallsearch new file mode 100755 index 0000000..f71d150 --- /dev/null +++ b/dotfiles/system/.local/bin/wallsearch @@ -0,0 +1,43 @@ +#!/bin/bash + +walldir="$HOME/Pictures/wallpaper/incoming" +tmpdir="$HOME/.wallsearch" +maxpage=5 +tagoptions="CANCEL\n#minimalism\n#digital art\n#4K\n#nature\n#abstract\n#landscape\n#cityscape\n#surf\n#technology" +sortoptions="CANCEL\ndate_added\nrelevance\nrandom\nfavorites\ntoplist" + +if [ -d $tmpdir ]; then + rm -rf $tmpdir +fi +mkdir -p $tmpdir + +if [ -z $1 ]; then + query=$(echo -e $tagoptions | dmenu -p "Search Wallhaven: " -i) +else + query=$1 +fi + +[ $query == "CANCEL" ] && notify-send "Cancelled wallpaper search." && exit 0; + +sorting=$(echo -e $sortoptions | dmenu -p "Sort Order: " -i) + +[ $sorting == "CANCEL" ] && notify-send "Cancelled wallpaper search." && exit 0; + +query="$(sed 's/#//g' <<<$query)" +query="$(sed 's/ /+/g' <<<$query)" +echo #query + +notify-send "wallsearch" "Searching wallpapers..." + +for i in $(seq 1 10); +do + curl -s https://wallhaven.cc/api/v1/search\?atleast\=1920x1080\&sorting\=$sorting\&q\=$query\&purity=111\&page\=$i > tmp.txt + page=$(cat tmp.txt | jq '.' | grep -Eoh "https:\/\/w\.wallhaven.cc\/full\/.*(jpg|png)\b") + wget -nc -P $tmpdir $page + notify-send "wallsearch" "Searching wallpapers..." +done + +rm tmp.txt +notify-send "wallsearch" "Done searching wallpaper." +sxiv -to $tmpdir/* | xargs -I % mv % $walldir +#rm -rf $tmpdir diff --git a/dotfiles/system/.local/bin/ytp b/dotfiles/system/.local/bin/ytp new file mode 100755 index 0000000..93ca9fc --- /dev/null +++ b/dotfiles/system/.local/bin/ytp @@ -0,0 +1 @@ +yt-dlp --ignore-config --yes-playlist --add-metadata -i -o '%(channel)s-%(playlist_title)s-%(playlist_index)s-%(title)s.%(ext)s' $1 $2 $3 $4 |
