diff --git a/home/bin/README.md b/home/bin/README.md new file mode 100644 index 0000000..0c2a3c3 --- /dev/null +++ b/home/bin/README.md @@ -0,0 +1,25 @@ +# Scripts + +## AquaAI + +AquaAI is bash script that interacts with OpenAI and Ollama APIs to enable terminal based interaction with LLMs. + +## connect-nas.sh + +Connect to long storage NAS. + +## convert-mp4-to-resolve.sh + +Converts mp4 video files to a video format where the audio works with the linux version of Davinci Resolve. + +## frc-photo-checklist.py + +Python script to generate a Todoist checklist for taking photos at an FRC event. + +```sh +python frc-photo-checklist.py [Event Key] +``` + +## get-mouse-battery.sh + +Prints out the battery percentage of a mouse and if it is charging. diff --git a/home/bin/audio/audio-lib.sh b/home/bin/audio/audio-lib.sh new file mode 100644 index 0000000..eb915ae --- /dev/null +++ b/home/bin/audio/audio-lib.sh @@ -0,0 +1,121 @@ +# Mix names +MONITOR_LEFT='A' +MONITOR_RIGHT='B' +HEADPHONE_01_LEFT='C' +HEADPHONE_01_RIGHT='D' +HEADPHONE_02_LEFT='E' +HEADPHONE_02_RIGHT='F' +BLANK_LEFT="G" +BLANK_RIGHT='H' + +# Level constants +MUTE='0' +ZERO_DB='0db' + +# Formats a number to match the matrix numbering. +function formatMatrixNum() { + printf "%02d" $1 +} + +# Returns audio card number with matching card name. +function getCardNumber() { + echo $(cat /proc/asound/cards | grep -m 1 $1 | grep -Po '\d{1,2}' | head -1) +} + +# Checks if the card exists and if not exits. +# +# $1 card name +# $2 card number +function checkCard() { + if [ -z "$2" ]; then + echo $1 not connected + exit 1 + else + echo $1 found at hw:$2 + fi +} + +# Prints a list of all controls for the given sound card. +function printControls() { + amixer -c $1 controls +} + +# Sets a mix to a level. +# +# $1 card number +# $2 matrix number +# $3 mix channel +function setMix() { + amixer -c $1 set "Mix $3 Input $(formatMatrixNum $2)" $4 +} + + +# Sets the volume levels for a mono mix. +# +# $1 card number +# $2 matrix number +# $3 mix left channel +# $4 mix right channel +# $5 volume +function setMonoMix() { + setMix $1 $2 $3 $5 + setMix $1 $2 $4 $5 +} + +# Sets the volume levels for a stereo mix. +# +# $1 card number +# $2 matrix number +# $3 mix left channel +# $4 mix right channel +# $5 volume +function setStereoMix() { + matrix=$2 + setMix $1 $matrix $3 $5 + setMix $1 $matrix $4 $MUTE + setMix $1 $((matrix+1)) $3 $MUTE + setMix $1 $((matrix+1)) $4 $5 +} + + +# Sets the volume levels for a mono mix for several outputs. +# +# $1 card number +# $2 matrix number +# $3 mix left channel +# $4 mix right channel +# $5 monitor volume +# $6 first headphone volume +# $7 second headphone volume +function setMono() { + monitor=$3 + headphone1=$4 + headphone2=$5 + if [ -n $headphone1 ]; then headphone1=$monitor; fi + if [ -n $headphone2 ]; then headphone2=$monitor; fi + setMonoMix $1 $2 $MONITOR_LEFT $MONITOR_RIGHT $monitor + setMonoMix $1 $2 $HEADPHONE_01_LEFT $HEADPHONE_01_RIGHT $headphone1 + setMonoMix $1 $2 $HEADPHONE_02_LEFT $HEADPHONE_02_RIGHT $headphone2 + setMonoMix $1 $2 $BLANK_LEFT $BLANK_RIGHT $MUTE +} + +# Sets the volume levels for a stereo mix for several outputs. +# +# $1 card number +# $2 matrix number +# $3 mix left channel +# $4 mix right channel +# $5 monitor volume +# $6 first headphone volume +# $7 second headphone volume +function setStereo() { + monitor=$3 + headphone1=$4 + headphone2=$5 + if [ -n $headphone1 ]; then headphone1=$monitor; fi + if [ -n $headphone2 ]; then headphone2=$monitor; fi + setStereoMix $1 $2 $MONITOR_LEFT $MONITOR_RIGHT $monitor + setStereoMix $1 $2 $HEADPHONE_01_LEFT $HEADPHONE_01_RIGHT $headphone1 + setStereoMix $1 $2 $HEADPHONE_02_LEFT $HEADPHONE_02_RIGHT $headphone2 + setStereoMix $1 $2 $BLANK_LEFT $BLANK_RIGHT $MUTE +} diff --git a/home/bin/audio/executable_aax-convert.sh b/home/bin/audio/executable_aax-convert.sh new file mode 100644 index 0000000..5023e16 --- /dev/null +++ b/home/bin/audio/executable_aax-convert.sh @@ -0,0 +1,11 @@ +#! /bin/bash + +# Load user settings from config file. +. ~/.config/settings.conf + +for file in *.aax; do + convertedFile="./${file%%.*}.m4b" + if [ ! -f "$convertedFile" ]; then + ffmpeg -y -activation_bytes ${activation_bytes} -i ./${file} -codec copy $convertedFile + fi +done diff --git a/home/bin/audio/executable_aquamix.sh b/home/bin/audio/executable_aquamix.sh new file mode 100644 index 0000000..524ddc8 --- /dev/null +++ b/home/bin/audio/executable_aquamix.sh @@ -0,0 +1,159 @@ +#!/bin/bash + +# Script to manuage audio mixing the the main audio interface. + +# Import library +source $(dirname ${BASH_SOURCE[0]})/audio-lib.sh + +INTERFACE_NAME='Clarett+ 8Pre' +INTERFACE_NUM=$(getCardNumber $INTERFACE_NAME) +checkCard "$INTERFACE_NAME" "$INTERFACE_NUM" + +# Sets the volume levels of the first mono instrument. +# +# $1 monitor volume +# $2 first headphone volume +# $3 second headphone volume +function setMonoOne() { + setMono $INTERFACE_NUM 1 $1 $2 $3 +} + +# Sets the volume levels of the second mono instrument. +# +# $1 monitor volume +# $2 first headphone volume +# $3 second headphone volume +function setMonoTwo() { + setMono $INTERFACE_NUM 2 $1 $2 $3 +} + +# Sets the volume levels of the third mono instrument. +# +# $1 monitor volume +# $2 first headphone volume +# $3 second headphone volume +function setMonoThree() { + setMono $INTERFACE_NUM 3 $1 $2 $3 +} + +# Sets the volume levels of the first stereo instrument. +# +# $1 monitor volume +# $2 first headphone volume +# $3 second headphone volume +function setStereoOne() { + setStereo $INTERFACE_NUM 5 $1 $2 $3 +} + +# Sets the volume levels of the second stereo instrument. +# +# $1 monitor volume +# $2 first headphone volume +# $3 second headphone volume +function setStereoTwo() { + setStereo $INTERFACE_NUM 7 $1 $2 $3 +} + +# Sets the volume levels of the third stereo instrument. +# +# $1 monitor volume +# $2 first headphone volume +# $3 second headphone volume +function setStereoThree() { + setStereo $INTERFACE_NUM 9 $1 $2 $3 +} + +# Sets the volume levels of the fourth stereo instrument. +# +# $1 monitor volume +# $2 first headphone volume +# $3 second headphone volume +function setStereoFour() { + setStereo $INTERFACE_NUM 11 $1 $2 $3 +} + +# Sets the volume levels of the fifth stereo instrument. +# +# $1 monitor volume +# $2 first headphone volume +# $3 second headphone volume +function setStereoFive() { + setStereo $INTERFACE_NUM 13 $1 $2 $3 +} + +# Sets the volume levels of the sixth stereo instrument. +# +# $1 monitor volume +# $2 first headphone volume +# $3 second headphone volume +function setStereoSix() { + setStereo $INTERFACE_NUM 15 $1 $2 $3 +} + + + +# Sets the volume levels of the studio microphone. +# +# $1 monitor volume +# $2 first headphone volume +# $3 second headphone volume +function setMic() { + setMono $INTERFACE_NUM 4 $1 $2 $3 +} + +# Sets the volume levels of the computer. +# +# $1 monitor volume +# $2 first headphone volume +# $3 second headphone volume +function setComputerAudio() { + setStereo $INTERFACE_NUM 17 $1 $2 $3 +} + +# Sets the volume levels of all instrument. +# +# $1 monitor volume +# $2 first headphone volume +# $3 second headphone volume +function setInstruments() { + setMonoOne $1 $2 $3 + setMonoTwo $1 $2 $3 + setMonoThree $1 $2 $3 + setStereoOne $1 $2 $3 + setStereoTwo $1 $2 $3 + setStereoThree $1 $2 $3 + setStereoFour $1 $2 $3 + setStereoFive $1 $2 $3 + setStereoSix $1 $2 $3 +} + +function DAWMode() { + setInstruments $MUTE + setMic $MUTE + setComputerAudio $ZERO_DB +} + +function NormalMode() { + setInstruments $ZERO_DB + setMic $MUTE + setComputerAudio $ZERO_DB +} + +function PrintHelp() { + echo AquaMixer + echo '-h --help print out help options' + echo '-d --daw set interface to DAW mode' + echo '-n --normal set interface to normal mode' + exit 0 +} + +for var in "$@"; do + if [ $var == '-h' ] || [ $var == '--help' ]; then + PrintHelp + elif [ $var == '-d' ] || [ $var == '--daw' ]; then + DAWMode + elif [ $var == '-n' ] || [ $var == '--normal' ]; then + NormalMode + fi +done + diff --git a/home/bin/audio/executable_es8start.sh b/home/bin/audio/executable_es8start.sh new file mode 100644 index 0000000..a5f5e9a --- /dev/null +++ b/home/bin/audio/executable_es8start.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +# Script to add another audio interface if available. + +DEVICE_NAME='ES-8' +DEVICE_NUM=$(getCardNumber $DEVICE_NAME) +checkCard $DEVICE_NAME $DEVICE_NUM + +# Start up audio interface +alsa_in -d hw:$DEVICENUM -j "$DEVICENAME In" -q 1 & +alsa_out -d hw:$DEVICENUM -j "$DEVICENAME Out" -q 1 & diff --git a/home/bin/audio/executable_es9start.sh b/home/bin/audio/executable_es9start.sh new file mode 100644 index 0000000..520b54a --- /dev/null +++ b/home/bin/audio/executable_es9start.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +# Script to add another audio interface if available. + +# Import library +source $(dirname $(realpath ${BASH_SOURCE[0]}))/audio-lib.sh + +DEVICE_NAME='ES-9' +DEVICE_NUM=$(getCardNumber $DEVICE_NAME) +checkCard $DEVICE_NAME $DEVICE_NUM + +# Start up ES-5 +pkill es-5-pipewire || true +/opt/es-5-pipewire/es-5-pipewire >/dev/null 2>/dev/null & +sleep 0.1 +jack_connect ES-5:output "$DEVICE_NAME:playback_SL" || true +jack_connect ES-5:output "$DEVICE_NAME:playback_AUX6" || true diff --git a/home/bin/audio/executable_es9stop.sh b/home/bin/audio/executable_es9stop.sh new file mode 100644 index 0000000..6ce30d2 --- /dev/null +++ b/home/bin/audio/executable_es9stop.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +# Script to stop ES-9 audio interface + +pkill alsa_in +pkill alsa_out +pkill es-5-pipewire diff --git a/home/bin/audio/executable_synth-power-prompt.sh b/home/bin/audio/executable_synth-power-prompt.sh new file mode 100644 index 0000000..3fc4295 --- /dev/null +++ b/home/bin/audio/executable_synth-power-prompt.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env sh + +# Script to bring up a prompt to set synth power state. + +current='Keep current state' +off='Power synths off' +on='Power synths on' +selected=$(echo -e "${current}\n${off}\n${on}" | rofi -dmenu -p \ + 'Change synth power' -theme ~/.config/rofi/themes/aqua) + +if [ "$selected" == "$off" ]; then + python ~/.config/scripts/audio/synth-power.py -o +elif [ "$selected" == "$on" ]; then + python ~/.config/scripts/audio/synth-power.py -d +fi diff --git a/home/bin/audio/executable_system-start-audio.sh b/home/bin/audio/executable_system-start-audio.sh new file mode 100644 index 0000000..21c675b --- /dev/null +++ b/home/bin/audio/executable_system-start-audio.sh @@ -0,0 +1,88 @@ +#! /bin/bash + +# Kill Pulse. +function killPulse() { + systemctl --user stop pulseaudio.socket + systemctl --user stop pulseaudio.service + pulseaudio -k + killall pulseaudio +} + +# Start Pulseaudio properly. +function fixPulse() { + PULSE="$(alsamixer 2>&1 | killall alsamixer)" + if [[ $PULSE == *'Connection refused'* ]]; then + echo 'Fixing Pulseaudio' + killPulse + sleep 0.1 + pulseaudio -D + fixPulse + else + echo 'Pulseaudio is working correctly' + fi +} + +# Start up programs that use audio. +function launchi3() { + if [ -z "$skipi3" ]; then + echo Opening i3wm sound workspaces + sleep .1 && i3-msg 'workspace 5; exec brave-browser' + #sleep 5.1 && python ~/bin/start-firefox.py + fi +} + +# Set up sinks. +function setupSinks() { + pactl set-default-sink speakers + pactl set-default-source sm7b +} + +# Connect sinks to audio interface +function connectSinks() { + pw-link speakers:monitor_FL alsa_output.usb-Focusrite_Clarett__8Pre_00002325-00.pro-output-0:playback_AUX0 + pw-link speakers:monitor_FR alsa_output.usb-Focusrite_Clarett__8Pre_00002325-00.pro-output-0:playback_AUX1 + + pw-link alsa_input.usb-Focusrite_Clarett__8Pre_00002325-00.pro-input-0:capture_AUX3 sm7b:input_FL + pw-link alsa_input.usb-Focusrite_Clarett__8Pre_00002325-00.pro-input-0:capture_AUX3 sm7b:input_FR + return $? +} + +function renameInterface() { + for n in `seq 0 17` ; do + jack_property -p -s "alsa:pcm:2:hw:2,0:capture:capture_${n}" http://jackaudio.org/metadata/pretty-name "capture_$((n+1))" + done + for n in `seq 0 19` ; do + jack_property -p -s "alsa:pcm:2:hw:2,0:playback:playback_${n}" http://jackaudio.org/metadata/pretty-name "playback_$((n+1))" + done + for n in `seq 0 19` ; do + jack_property -p -s "alsa:pcm:2:hw:2,0:playback:monitor_${n}" http://jackaudio.org/metadata/pretty-name "monitor_$((n+1))" + done +} + +# Restart the Wireplumber service. +function restartWireplumber() { + systemctl --user stop wireplumber + sleep 5 + systemctl --user restart wireplumber +} + +# arg parser +for arg in "$@" +do + # Skip commands for i3wm + if [[ $arg == *"-s"* ]]; then + skipi3=true + fi +done + +# Wire sinks +setupSinks + +# Eurorack audio interface +sh ~/bin/audio/es9start.sh + +launchi3 +systemctl --user restart polybar +sleep 5 +restartWireplumber + diff --git a/home/bin/audio/synth-power.py b/home/bin/audio/synth-power.py new file mode 100644 index 0000000..0231c19 --- /dev/null +++ b/home/bin/audio/synth-power.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 + +# Program to control synthesizers power state. + +import argparse +import configparser +import os,sys,inspect +currentDir = os.path.dirname(os.path.abspath( + inspect.getfile(inspect.currentframe()))) +parentDir = os.path.dirname(currentDir) +sys.path.insert(0, parentDir) +from homeassistant import HomeAssistant + +# Parse settings config +SCRIPT_DIR = os.path.abspath(os.path.dirname(sys.argv[0])) +configString = '[Settings]\n' + open(SCRIPT_DIR + '/../../settings.conf').read() +configParser = configparser.RawConfigParser() +configParser.read_string(configString) + +# Load needed credentials +HA_IP = configParser.get('Settings', 'HA_IP') +HA_TOKEN = configParser.get('Settings', 'HA_TOKEN') + +# Set power state of the Eurorack. +def setEurorackPower(state): + ha.setOnOff('switch.eurorack_lower', state) + ha.setOnOff('switch.eurorack_top', state) + +# Set power state of the Behringer DeepMind 12. +def setDeepMind12Power(state): + ha.setOnOff('switch.deepmind_12', state) + +# Set power state of the ASM Hydrasynth. +def setHydrasynthPower(state): + ha.setOnOff('switch.hydrasynth', state) + +# Set power state of the Arturia MatrixBrute. +def setMatrixBrutePower(state): + ha.setOnOff('switch.matrixbrute', state) + +# Set power state of all synthesizers. +def setSynthsPower(state): + setEurorackPower(state) + setDeepMind12Power(state) + setHydrasynthPower(state) + setMatrixBrutePower(state) + +parser = argparse.ArgumentParser( + description='Control power state of synthesizers.') +parser.add_argument('-d', '--daw', action='store_true', + help='enable DAW mode', + dest='daw', default=False, required=False) +parser.add_argument('-o', '--off', action='store_true', + help='turn all synths off', + dest='off', default=False, required=False) +args = parser.parse_args() + +ha = HomeAssistant(HA_IP, HA_TOKEN) + +if args.daw: + setSynthsPower(True) +elif args.off: + setSynthsPower(False) diff --git a/home/bin/convert-mp4-to-resolve.sh b/home/bin/convert-mp4-to-resolve.sh new file mode 100644 index 0000000..924cada --- /dev/null +++ b/home/bin/convert-mp4-to-resolve.sh @@ -0,0 +1,9 @@ +#! /bin/bash + +# Create backup directory +mkdir -p ./converted + +# Convert files +for file in *.mp4 *.MP4 *.mov *.MOV; do + ffmpeg -i $file -acodec pcm_s16le -vcodec copy ./converted/${file%%.*}.mov +done diff --git a/home/bin/desktop/executable_polybar-start.sh b/home/bin/desktop/executable_polybar-start.sh new file mode 100644 index 0000000..26e20ef --- /dev/null +++ b/home/bin/desktop/executable_polybar-start.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +# Stop all polybar instances +killall -q polybar +while pgrep -x polybar >/dev/null; do sleep 0.1; done + +for monitor in $(xrandr -q | grep -e "\sconnected\s" | cut -d' ' -f1); do + if [ $monitor == 'DP-2' ] || [ $monitor == 'LVDS-1' ]; then + MONITOR=$monitor polybar aqua & + fi +done + + diff --git a/home/bin/desktop/executable_waybar-start.sh b/home/bin/desktop/executable_waybar-start.sh new file mode 100644 index 0000000..d2e5796 --- /dev/null +++ b/home/bin/desktop/executable_waybar-start.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +# Stop all waybar instances +killall -q waybar +while pgrep -x waybar >/dev/null; do sleep 0.1; done + +waybar & diff --git a/home/bin/desktop/i3wm-close-window.py b/home/bin/desktop/i3wm-close-window.py new file mode 100644 index 0000000..0becc74 --- /dev/null +++ b/home/bin/desktop/i3wm-close-window.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 + +import i3ipc, os + +def onWindowClose(conn, win): + if(win.ipc_data['container']['window_properties']['class'] == 'Bitwig Studio'): + print(win.ipc_data['container']['window_properties']['title']) + if(win.ipc_data['container']['window_properties']['title'] != + 'DSP Performance Graph'): + os.system('sh ~/.config/scripts/audio/aquamix.sh -n') + os.system('sh ~/.config/scripts/audio/synth-power-prompt.sh') + +i3 = i3ipc.Connection() +i3.on('window::close', onWindowClose) +i3.main() + + + + diff --git a/home/bin/executable_aquaai.sh b/home/bin/executable_aquaai.sh new file mode 100644 index 0000000..9aacf03 --- /dev/null +++ b/home/bin/executable_aquaai.sh @@ -0,0 +1,926 @@ +#!/usr/bin/env bash + +# This is a bash script to enable interacting with LLMs via the command line. + +#=============================================================================== + +## Modes +### Default mode +# Default mode uses the default prompt and model for AquaAI. It's nothing +# special. +### Bash mode +# This mode will help with writing bash scripts. +### CLI mode +# CLI mode prompts the AI with system information and will return terminal +# commands. If you wish to run the command simply type run and it will end the +# chat and run the command. You are responsible for validating what the command +# does before running. +### Code Review mode +# This will ask you what changes to look at and will provide a code review of +# the changes. This mode only works if you are currently in a git repo. It can +# look at the past few commits as well as changes that have yet to be committed. +### Reasoning mode +# This uses the best available reasoning model with the default prompt. +# Reasoning models take a task and break them up to subtask to pass to +# specialized models. They are very yappy and take a while to run. Can be good +# for complex tasks. +### Regex mode +# This mode will respond only with regex. +### Git mode +# This mode will respond only with git commands. If you wish to run the command +# simply type run and it will end the chat and run the command. You are +# responsible for validating what the command does before running. + +## Special Input +### Edit +# You can type `edit` or `e` as a response and it will open your editor set with +# the EDITOR variable in your shell session. You can then type your query and +# save and exit. From there the program will send your query to the AI. +### Exit +# You can type `exit` or `q` to end the chat. Personally, I never do this just +# use C-c. +### Run +# If you are in cli mode you can type `run` or `r` and the script will run the +# given commands on your system. You are playing with fire with this, but fire +# is useful and fun just be careful. +### Save +# You can type `save` or `s` as a response and the chat history will be saved +# for use at another time. This will also end the chat. Chats are stored in +# `~/.local/share/aquaai` + +## Adding custom modes +# There are two variables that need to be set to create a custom mode. +### $selected_model will set the model to be used for the chat. +### $system_prompt will be the prompt that controls how the AI behaves. +# introduce more noise into text generation leading to more out there responses. +# +# Defaults are set for all these but to define a custom mode you should override +# at least one of these in a function. Add a custom flag in the switch statement +# at the bottom of this file and call the function there. See `--bash` as an +# example of how to do this. From there add some documentation to the +# print_help() function and then here. + +#=============================================================================== + +# User configurable variables. +# +# The following are settings that can be overwritten by environment variables. +# You can set these in your .bashrc to have them set each time you open a new +# shell. This script is designed not to be modified so updates can be applied by +# replacing the file with the newest version. +# +# +# Set the url of the ollama server. +# +# export AQUAAI_OLLAMA_URL='192.168.1.156:11434' +# +ollama_url=${AQUAAI_OLLAMA_URL:='https://ai.aquamorph.com'} +# +# Set the default model. +# +# export AQUAAI_DEFAULT_MODEL='qwen2.5-7b-instruct' +# +default_model=${AQUAAI_DEFAULT_MODEL:='qwen2.5:32b-instruct'} +# +# Set the default coding model. +# +# export AQUAAI_CODING_MODEL='qwen2.5-7b-coder' +# +coding_model=${AQUAAI_CODING_MODEL:='qwen2.5-32b-coder'} +# +# In multiline mode, users can input multiple lines of text by pressing the +# Enter key. The message will be sent when the user presses C-d on the keyboard. +# +# export AQUAAI_MULTILINE_MODE=true +# +multiline_mode=${AQUAAI_MULTILINE_MODE:=false} +# +# Enable rich formatting for text output. A formatting program is required for +# this see below. +# +# export AQUAAI_RICH_FORMAT_MODE=true +# +rich_format_mode=${AQUAAI_RICH_FORMAT_MODE:=false} +# +# Path to the program used for rich formatting. I am currently using streamdown +# but you are free to use something different as long as it supports streaming +# text and markdown. Go to the GitHub repo to learn to install streamdown and +# configure: https://github.com/day50-dev/Streamdown +# +# export AQUAAI_RICH_FORMAT_PATH=~/.venv/bin/streamdown +# +rich_format_path=${AQUAAI_RICH_FORMAT_PATH:=streamdown} +# +# Ignore certificate checks. +# +# export AQUAAI_INSECURE_MODE=true +# +insecure_mode=${AQUAAI_INSECURE_MODE:=false} +# +# Use OpenAI api design instead of Ollama. +# +# export AQUAAI_OPENAI_API:=true +# +openai_api=${AQUAAI_OPENAI_API:=true} +# +# Set key used to authenticate with the API. +# +# export AQUAAI_KEY:=true +# +key=${AQUAAI_KEY:=''} +#=============================================================================== + +# Constants. +OLLAMA_URL=${ollama_url} +CURL_FLAGS=('-sN') +USER=$(whoami) +DATA_DIR="${HOME}/.local/share/aquaai" +RESPONSE_FIFO="${DATA_DIR}/.response" +AGENT_NAME='AquaAI' + +# Colors. +CLEAR='\033[0m' +BLUE='\033[0;34m' +RED='\e[1;31m' +LIGHT_GRAY='\e[38;5;247m' + +# Globals. +message_history="[]" +cli_mode=false +git_mode=false +code_review_start=false +selected_model=${default_model} + +# Error Codes. +ERROR_NO_SAVEFILE=1 +ERROR_INVALID_TEMP=2 +ERROR_UNKNOWN_OPTION=3 +ERROR_UNKNOWN_MODEL=4 +ERROR_NO_GIT_REPO=5 +ERROR_INVALID_INPUT=6 +ERROR_NO_AUTOSAVE=7 +ERROR_INVALID_SSL=8 +ERROR_UNKNOWN_SSL=9 + +#=============================================================================== + +# Give the AI a name. It improves prompting to call it by name. +function name_agent() { + system_prompt="You are an AI assistant named ${AGENT_NAME}." +} + +# Make the AI write and behave better. +function set_better_conversions() { + system_prompt+=' Be as concise as possible.' + system_prompt+=' Be extremely accurate.' + system_prompt+=' Recommend things I would not realize I would benefit from.' + system_prompt+=' Call out my misconceptions and tell me when I am wrong.' + system_prompt+=" For personal matters ${AGENT_NAME} is encouraging" + system_prompt+=' but brutally honest.' + system_prompt+=' Never sycophantic.' +} + +# Set the formatting for all reponses. +function set_response_format() { + system_prompt+=' Do not wrap response in quotation marks or apostrophes.' + system_prompt+=' Do not use html to format response.' +} + +# Limit format of output to just commands. +function format_for_cli() { + system_prompt+=" ${AGENT_NAME} does not put commands in quotation marks." + system_prompt+=" ${AGENT_NAME} does not put commands in markdown." + system_prompt+=" ${AGENT_NAME} only outputs terminal commands." +} + +# Default prompt. +function set_default_agent() { + name_agent + system_prompt+=" ${AGENT_NAME} follows the users instructions carefully." + system_prompt+=" ${AGENT_NAME} responds using extended markdown." + set_better_conversions + set_response_format +} + +# Set chat to help with command line questions. +function set_cli_agent() { + local os_version=$(cat /etc/os-release | grep 'PRETTY_NAME' | \ + sed 's/PRETTY_NAME=//g' | tr -d '"') + name_agent + system_prompt+=" ${AGENT_NAME} assists users with ${os_version}." + format_for_cli + set_response_format +} + +# Set chat to help with bash questions. +function set_bash_agent() { + name_agent + system_prompt+=" ${AGENT_NAME} assists users with POSIXs bash." + system_prompt+=' Format output for view in a command line.' + system_prompt+=' Do not put commands in quotation marks.' + system_prompt+=' Use double spaces and the function keyword.' + system_prompt+=' Write documentation before the function declaration.' + set_response_format +} + +# Set ai to help with code reviews. +function set_code_review_agent() { + name_agent + system_prompt+=" ${AGENT_NAME} is a senior software engineer performing a" + system_prompt+=' code review for a colleague.' + system_prompt+='' + set_better_conversions + system_prompt+=' Show code snipets when helpful.' + system_prompt+="${AGENT_NAME}'s reports should have the following format:" + system_prompt+='# Typos' + system_prompt+='List of all typos you find.' + system_prompt+='# Formatting and Readability Issues' + system_prompt+='List of all formatting and readability issues you find.' + system_prompt+='# Security Issues' + system_prompt+='List of all security issues you find.' + system_prompt+='# Other' + system_prompt+='List of all other issues you find.' + set_response_format +} + +# Set chat to help with git. +function set_git_agent() { + name_agent + system_prompt+=" ${AGENT_NAME} assists users with git." + format_for_cli + + if is_git_repo; then + system_prompt+="\n\nHere is some information about the current git repo.\n" + local current_branch=$(git branch --show-current) + system_prompt+="Current branch: ${current_branch} \n" + local git_remotes=$(git remote -v) + system_prompt+="Remotes: ${git_remotes}\n" + fi +} + +# Set chat to help with regex. +function set_regex_agent() { + name_agent + system_prompt+=" ${AGENT_NAME} assists users with regex." + system_prompt+=' Only output a single regex expression.' + system_prompt+=' Use BRE and ERE regex.' + format_for_cli +} + +# Set chat to help with Home Assistant. +function set_home_assistant_agent() { + name_agent + system_prompt+=" ${AGENT_NAME} assists users with Home Assistant programming." + system_prompt+=" Templating is done with Jinja2." +} + +#=============================================================================== + +# Get the first available model from a given list. +function get_model_from_list() { + local models=("$@") + for m in "${models[@]}"; do + check_if_model_exists $m true + if [ $? -eq 0 ]; then + echo "${m}" + return + fi + done + print_error "could not find any models on the list." + exit $ERROR_UNKNOWN_MODEL +} + +function load_model_from_list() { + local models=("$@") + selected_model=$(get_model_from_list "${models[@]}") + if [[ $? -ne 0 ]]; then + print_error "could not find any models on the list." + exit $ERROR_UNKNOWN_MODEL + fi +} + +# Set the default coding model. +function set_coding_model() { + local models=(${coding_model} + 'qwen2.5-32b-coder' + 'qwen2.5-7b-coder' + 'llama3.3-70b-instruct' + 'hf.co/Qwen/Qwen2.5-Coder-3B-Instruct-GGUF:Q4_K_M' + 'hf.co/bartowski/Qwen2.5-Coder-1.5B-Instruct-GGUF:Q4_K_M' + 'qwen2.5-coder:3b' + 'qwen2.5-coder:0.5b' + ) + load_model_from_list "${models[@]}" +} + +# Set the default reasoning model. +function set_reasoning_model() { + local models=(${reasing_model} + 'qwen3-32b' + 'gpt-oss-120b' + ) + load_model_from_list "${models[@]}" +} + +#=============================================================================== + +# Print out help menu. +function print_help() { + echo 'Interact with the AquaAI via the command line.' + echo '' + echo '--delete - delete a chat from history' + echo '-l --list - list available models' + echo '--load - load a chat from history' + echo '--restore - load last auto saved chat' + echo '' + echo '--bash - help with bash' + echo '--cli - help with command line' + echo '--code-review - code review of a git project' + echo '-r --reason - help with a reasoning model' + echo '--regex - help with regex' + echo '--git - help with git' +} + +# Print out error message. +function print_error() { + local msg=${1} + printf "${RED}ERROR: ${msg}\n${CLEAR}" +} + +# Check if a given program is installed on the system. +function check_program() { + if ! command -v ${1} 2>&1 >/dev/null; then + print_error "${1} not found. Please install ${1}." + exit ${ERROR_DEPENDENCY} + fi +} + +# Check system for required programs. +function check_requirements() { + check_program curl + check_program jq + check_program fzf + if [ "${rich_format_mode}" == true ]; then + check_program ${rich_format_path} + fi +} + +# Get available models info. +function get_models() { + local model_path='' + if [ "${openai_api}" == true ]; then + model_path='/api/models' + else + model_path='/api/tags' + fi + curl "${CURL_FLAGS[@]}" "${OLLAMA_URL}${model_path}" +} + +# Get a list of available models. +function get_models_list() { + local jq_filter='' + if [ "${openai_api}" == true ]; then + jq_filter='.data[].id' + else + jq_filter='.models[].model' + fi + get_models | jq -r ${jq_filter} +} + +# Print list of models. +function print_models() { + get_models_list | column -t -s $'\t' +} + +# Print message variable. +function print_message_history() { + echo ${message_history} +} + +# Print message variable. +function print_debug() { + echo "Model: ${selected_model}" + echo 'System Prompt:' + print_system_prompt + echo 'Chat History:' + print_message_history | jq +} + +# Print the system prompt. +function print_system_prompt() { + echo -e "${system_prompt}" +} + +# Check if the model exists. +function check_if_model_exists() { + local model=${1} + local enable_rc=${2} + local model_list=($(get_models_list)) + + for m in "${model_list[@]}"; do + if [[ "$m" == "$model" ]]; then + return 0 + fi + done + + if [ ${enable_rc} ]; then + return 1 + else + print_error "model ${model} does not exists." + exit $ERROR_UNKNOWN_MODEL + fi +} + +# Convert string to a safe format for later use. +function convert_to_safe_text() { + echo "${1}" | jq -sR @json +} + +# Set the text output color for user input. +function set_user_color() { + printf "${LIGHT_GRAY}" +} + +# Set the text output color for ai response. +function set_ai_color() { + printf "${CLEAR}" +} + +# Set text color to defaults. +function set_clear_color() { + printf "${CLEAR}" +} + +# Print the header for the ai message +function print_ai_start_message() { + echo -e "\U1F916 AquaAI" +} + +# Print the header for the ai message +function print_user_start_message() { + echo -e "\U1F464 ${USER}" +} + +# Opens the user's preferred text editor to allow them to input text. +function editor_input() { + local editor=${EDITOR:=nano} + local temp_file=$(mktemp) + + ${editor} ${temp_file} + local user_input=$(<"$temp_file") + rm "$temp_file" + + msg=${user_input} +} + +# Check if current directory is managed by git. +function is_git_repo() { + git rev-parse --is-inside-work-tree &> /dev/null +} + +# Check if current directory is managed by git. +function check_git_directory() { + if ! git rev-parse --is-inside-work-tree &> /dev/null; then + print_error 'The current directory is not inside a git repository.' + exit ${ERROR_NO_GIT_REPO} + fi +} + +# Asks the user if they want to include staged git change data. +function gather_staged_changes() { + echo -n 'Do you want to include staged changes? (y/n)? ' + read response + + if [[ "$response" == 'y' || "$response" == 'yes' ]]; then + msg+=$(git diff --cached --patch) + fi +} + +# Ask the user if they want to include changes that have not been committed. +function gather_uncommitted_changes() { + echo -n 'Do you want to include the changes' + echo -n ' you have yet to commit or stash (y/n)? ' + read response + + if [[ "$response" == 'y' || "$response" == 'yes' ]]; then + changes=$(git diff) + msg+=${changes} + fi +} + +# Ask the user for number of commit changes to include in code review. +# Returns a list of changes for the given number of commits. +function gather_commit_changes() { + echo -n 'How many previous commits do you want to include? ' + local count + read count + + # Allow hitting enter as a no response. + if [ -z "$count" ]; then + return + fi + + # Validate that the input is a positive integer. + if ! [[ "$count" =~ ^[0-9]+$ ]] || [ "$count" -lt 0 ]; then + print_error 'Please enter a positive integer for the number of commits.' + exit ${ERROR_INVALID_INPUT} + fi + + hashes=$(git log --format=%H -n ${count}) + for h in ${hashes}; do + commit_message=$(git show ${h}) + msg+="${commit_message}"$'\n' + done +} + +# Create fifo for chat responses. +function create_response_fifo() { + create_data_dir + if [ ! -p ${RESPONSE_FIFO} ]; then + mkfifo ${RESPONSE_FIFO} + fi +} + +# Delete fifo for chat responses. +function remove_response_fifo() { + if [ -p ${RESPONSE_FIFO} ]; then + rm ${RESPONSE_FIFO} + fi +} + +# Create response trap to allow user to stop AquaAI. +function create_response_trap() { + trap 'echo "AquaAI has been interrupted...";' SIGINT +} + +# Remove response trap to allow user to exit program. +function remove_response_trap() { + trap - SIGINT +} + +# Get the first message from a saved chat. +function get_first_chat() { + local file_path=${1} + source <(cat ${file_path} | grep message_history) + message_history=$(echo $message_history | \ + jq -r '[.[] | select(.role == "user")][0].content' \ + 2>/dev/null | sed 's/^"//') + echo -e $message_history | sed 's/"$//' | tr -d '\n' | cut -c 1-80 \ + | sed ':a;N;$!ba;s/\n//g' +} + +# Get an array of all saved chat files. +function get_save_files() { + save_files=() + create_data_dir + + for f in $(find "$DATA_DIR" -type f -name "*.chat"); do + save_files+=("${f}") + done +} + +# Create the data directory if it does not exist. +function create_data_dir() { + if [ ! -d "$DATA_DIR" ]; then + mkdir -p "$DATA_DIR" + fi +} + +# Save the current chat to a file. +function save_chat() { + create_data_dir + local filename=${1} + + if [ -z "$filename" ]; then + print_error 'No filename provided.' + exit ${ERROR_NO_SAVEFILE} + fi + + declare -p selected_model system_prompt \ + message_history cli_mode > "${DATA_DIR}/${filename}" +} + +# Save the current chat to autosave. +function autosave() { + save_chat 'autosave.chat' + echo 'Chat has been auto saved' +} + +# Find all .chat files in DATA_DIR and use fzf to select one. +function select_chat_file() { + selected_file=$(select_chat_with_fzf) + + if [ -z "$selected_file" ]; then + echo 'No file selected.' + exit ${ERROR_NO_SAVEFILE} + fi +} + +# Delete .chat files in DATA_DIR. +function delete_chat_file() { + selected_file=$(select_chat_with_fzf) + + if [ -z "$selected_file" ]; then + echo 'No file selected.' + exit ${ERROR_NO_SAVEFILE} + else + local pretty_name=$(get_first_chat ${selected_file}) + echo -n "do you want to delete '${pretty_name}' (y/n)? " + read response + if [[ "$response" == 'y' || "$response" == 'yes' ]]; then + rm -- "${selected_file}" + echo "Deleted '${pretty_name}'" + fi + fi +} + +# Select saved chat with fzf program. +function select_chat_with_fzf() { + get_friendly_save_names + + local selected_index=$(printf "%s\n" "${friendly_save_files[@]}" \ + | cat -n | fzf --with-nth 2.. \ + | awk '{print $1}') + selected_index=$((${selected_index}-1)) + + if [[ -n $selected_index ]]; then + echo "${save_files[selected_index]}" + fi +} + +# Get an array of the first message in saved chats. +function get_friendly_save_names() { + get_save_files + friendly_save_files=() + for f in "${save_files[@]}"; do + friendly_save_files+=("$(get_first_chat ${f})") + done +} + +# Validate site certificate. +function check_cert() { + curl "${CURL_FLAGS[@]}" ${OLLAMA_URL} 2>&1 >/dev/null + local ec=$? + if [ "${ec}" == '60' ]; then + print_error 'unable to get local issuer certificate.' + echo 'Install the certificate on the system.' + exit ${ERROR_INVALID_SSL} + elif [ "${ec}" != '0' ]; then + print_error 'unknown ssl error.' + exit ${ERROR_UNKNOWN_SSL} + fi +} + +# Update chat history +function update_history() { + local role="$1" + local content="$2" + message_history=$(echo "$message_history" \ + | jq --arg role "$role" --arg content \ + "$content" '. + [{"role": $role, "content": $content}]') +} + +# Read input from the user. +function read_user_input() { + if [ "${multiline_mode}" == true ]; then + msg=$(awk '{if ($0 == "END") exit; else print}') + elif [ "${code_review_start}" == true ]; then + check_git_directory + msg='' + gather_uncommitted_changes + gather_staged_changes + gather_commit_changes + code_review_start=false + else + read msg + fi +} + +# Handle input related to CLI mode. +function handle_cli_mode() { + # Check for cli mode + if [ ${cli_mode} == true ]; then + if [[ -z $msg || $msg == 'run' || $msg == 'r' ]]; then + set_clear_color + autosave + echo + local commands=() + # Get a list of commands + while IFS= read -r line; do + commands+=("${line}") + done <<< "$last_cmd" + for c in "${commands[@]}"; do + # Using eval to handle commands that include pipes. + if [[ "${c}" == *'|'* ]]; then + eval "${c}" + else + ${c} + fi + done + exit 0 + fi + fi +} + +# Check for editor request. +function handle_edit() { + if [[ $msg == 'edit' || $msg == 'e' ]]; then + editor_input + set_user_color + echo "${msg}" + fi +} + +# Check for debug command. +function handle_debug() { + if [[ $msg == 'debug' ]]; then + print_debug + return 1 + fi + return 0 +} + +# Check for save command. +function handle_save() { + if [[ $msg == 'save' || $msg == 's' ]]; then + echo "Saving chat history" + save_chat "$(date +%Y%m%d%H%M%S).chat" + exit 0 + fi +} + +# Chat converstation loop. +function chat_loop() { + check_if_model_exists ${selected_model} + update_history 'system' "$system_prompt" + while true; do + chat + done +} + +# Main chat loop. +function chat() { + # Get user input. + set_user_color + print_user_start_message + read_user_input + echo + + # Handle user input. + local rc=0 + handle_edit + handle_cli_mode + handle_debug + rc=$((rc + $?)) + handle_save + rc=$((rc + $?)) + if [ "$rc" -ne 0 ]; then + return + fi + update_history 'user' "${msg}" + + # Prepare JSON payload. + JSON_PAYLOAD=$(jq -n \ + --arg model "$selected_model" \ + --argjson messages "$message_history" \ + '{model: $model, messages: $messages, stream: true}') + + set_ai_color + print_ai_start_message + + create_response_fifo + create_response_trap + # Render to console. + if [ "${rich_format_mode}" == true ]; then + cat ${RESPONSE_FIFO} | ${rich_format_path} & + else + cat ${RESPONSE_FIFO} & + fi + local flags=("${CURL_FLAGS[@]}") + local chat_path='' + local filter='' + if [ "${openai_api}" == true ]; then + flags+=(-H "Content-Type: application/json") + chat_path='/api/chat/completions' + filter='.choices[].delta.content // empty' + else + chat_path='/api/chat' + filter='.message.content // empty' + fi + local response=$(curl "${flags[@]}" "${OLLAMA_URL}${chat_path}" \ + -d "${JSON_PAYLOAD}" | stdbuf -o0 sed 's/^data: //' \ + | stdbuf -o0 jq -j "${filter}" 2>/dev/null \ + | tee ${RESPONSE_FIFO}) + wait + # Newline for AI response. + if [ "${rich_format_mode}" != true ]; then + echo + fi + # One line reponses do not print out when formatted with Streamdown. + if [[ "$rich_format_path" == *"streamdown"* && \ + "${rich_format_mode}" == true ]]; then + local wc=$(echo "${response}" | wc -l) + if [ ${wc} -eq 1 ]; then + echo "${response}" + fi + fi + remove_response_trap + remove_response_fifo + echo + update_history "assistant" "${response}" + last_cmd="${response}" +} + +#=============================================================================== + +check_requirements +if [ "${insecure_mode}" == true ]; then + CURL_FLAGS+=('-k') +else + check_cert +fi +if [ "${openai_api}" == true ]; then + CURL_FLAGS+=(-H "Authorization: Bearer ${key}") +fi +cmd=chat_loop +set_default_agent + +# Check arguments +for i in "$@"; do + case $i in + -h|--help) + cmd=print_help + ;; + -l|--list) + cmd=print_models + ;; + --delete) + delete_chat_file + exit 0 + ;; + --load) + select_chat_file + source ${selected_file} + cmd=chat_loop + ;; + --restore) + if [ ! -e "${DATA_DIR}/autosave.chat" ]; then + print_error 'auto save does not exit' + exit ${ERROR_NO_AUTOSAVE} + fi + source "${DATA_DIR}/autosave.chat" + cmd=chat_loop + ;; + # Modes + --bash) + set_coding_model + set_bash_agent + cmd=chat_loop + ;; + --cli) + set_coding_model + set_cli_agent + cmd=chat_loop + cli_mode=true + rich_format_mode=false + ;; + --code-review) + set_coding_model + set_code_review_agent + code_review_start=true + cmd=chat_loop + ;; + --git) + set_coding_model + set_git_agent + cmd=chat_loop + cli_mode=true + rich_format_mode=false + ;; + -r|--reason) + set_reasoning_model + cmd=chat_loop + ;; + --regex) + set_coding_model + set_regex_agent + cmd=chat_loop + rich_format_mode=false + ;; + -ha|--home-assistant) + set_coding_model + set_home_assistant_agent + cmd=chat_loop + rich_format_mode=false + ;; + # Other + -*|--*) + echo "Unknown option ${i}" + print_help + exit ERROR_UNKNOWN_OPTION + ;; + esac +done + +${cmd} + diff --git a/home/bin/executable_connect-nas.sh b/home/bin/executable_connect-nas.sh new file mode 100644 index 0000000..6575d75 --- /dev/null +++ b/home/bin/executable_connect-nas.sh @@ -0,0 +1,10 @@ +#! /bin/bash + +# Load user settings from config file. +. ~/.config/settings.conf + +if nc -z $nasip 80 2>/dev/null; then + mount /mnt/share/lNAS +else + echo "$nasip is unreachable" +fi diff --git a/home/bin/executable_get-mouse-battery.sh b/home/bin/executable_get-mouse-battery.sh new file mode 100644 index 0000000..2d83961 --- /dev/null +++ b/home/bin/executable_get-mouse-battery.sh @@ -0,0 +1,11 @@ +#! /bin/bash + +mouseState=$(upower -i /org/freedesktop/UPower/devices/battery_hidpp_battery_0) +batteryPercentage=$(echo $mouseState | grep percentage | grep -Po '\d+%') +charging=$(echo $mouseState | grep 'state: charging') + +if [ ! -z "$charging" ]; then + echo Charging +else + echo $batteryPercentage +fi diff --git a/home/bin/executable_hosts-file.sh b/home/bin/executable_hosts-file.sh new file mode 100644 index 0000000..50dacaa --- /dev/null +++ b/home/bin/executable_hosts-file.sh @@ -0,0 +1,8 @@ +#! /bin/bash + +if [[ $* == *-b* ]]; then + cp /etc/hosts ~/.config/.dotfiles/hosts +else + sudo cp ~/.config/.dotfiles/hosts /etc/hosts +fi + diff --git a/home/bin/executable_i3wm-config-gen.sh b/home/bin/executable_i3wm-config-gen.sh new file mode 100644 index 0000000..d38ff09 --- /dev/null +++ b/home/bin/executable_i3wm-config-gen.sh @@ -0,0 +1,12 @@ +#! /bin/bash + +# Load user settings from config file. +. ~/.config/settings.conf + +cat ~/.config/i3/shared.conf ~/.config/i3/${computer}.conf > ~/.config/i3/config + +if command -v i3-msg &> /dev/null; then + i3-msg reload +elif command -v swaymsg &> /dev/null; then + swaymsg reload +fi diff --git a/home/bin/executable_i3wm-for-window-gen.sh b/home/bin/executable_i3wm-for-window-gen.sh new file mode 100644 index 0000000..5a743ac --- /dev/null +++ b/home/bin/executable_i3wm-for-window-gen.sh @@ -0,0 +1,8 @@ +#! /bin/bash + +# A script to make creating i3wm window actions easier. + +info=$(xprop) +title=$(echo "$info" | grep "WM_NAME(STRING)" | cut -d "\"" -f2 | cut -d "\"" -f1) +class=$(echo "$info" | grep "WM_CLASS(STRING)" | cut -d "\"" -f2 | cut -d "\"" -f1) +echo for_window [class=\"$class\" title=\"$title\"] diff --git a/home/bin/executable_lock.sh b/home/bin/executable_lock.sh new file mode 100644 index 0000000..d6018bb --- /dev/null +++ b/home/bin/executable_lock.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +# i3lock blurred screen inspired by /u/patopop007 and the blog post +# http://plankenau.com/blog/post-10/gaussianlock + +# Timings are on an Intel i7-2630QM @ 2.00GHz + +# Dependencies: +# imagemagick +# i3lock +# scrot (optional but default) + +IMAGE=/tmp/i3lock.png +SCREENSHOT="scrot $IMAGE" # 0.46s + +# Alternate screenshot method with imagemagick. NOTE: it is much slower +# SCREENSHOT="import -window root $IMAGE" # 1.35s + +# Here are some imagemagick blur types +# Uncomment one to use, if you have multiple, the last one will be used + +# All options are here: http://www.imagemagick.org/Usage/blur/#blur_args +BLURTYPE="0x5" # 7.52s +#BLURTYPE="0x2" # 4.39s +#BLURTYPE="5x2" # 3.80s +#BLURTYPE="2x8" # 2.90s +#BLURTYPE="2x3" # 2.92s + +# Get the screenshot, add the blur and lock the screen with it +$SCREENSHOT +convert $IMAGE -blur $BLURTYPE $IMAGE +i3lock -i $IMAGE +rm $IMAGE diff --git a/home/bin/executable_toggle-graphics.sh b/home/bin/executable_toggle-graphics.sh new file mode 100644 index 0000000..892da17 --- /dev/null +++ b/home/bin/executable_toggle-graphics.sh @@ -0,0 +1,17 @@ +#! /bin/bash + +# Script to toggle graphical + +defTarget=$(systemctl get-default) + +if [[ $defTarget == 'graphical.target' ]] +then + echo 'Changing default target to nongraphical shell' + sudo systemctl set-default multi-user.target +else + echo 'Changing default target to graphical shell' + sudo systemctl set-default graphical.target +fi +echo +echo 'Default target changed to '$(systemctl get-default) +echo 'Please reboot for the change to take effect' diff --git a/home/bin/executable_trackpad-toggle.sh b/home/bin/executable_trackpad-toggle.sh new file mode 100644 index 0000000..84355b0 --- /dev/null +++ b/home/bin/executable_trackpad-toggle.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +# Get device id of Synaptics TrackPad +id=$(xinput list --id-only 'SynPS/2 Synaptics TouchPad') + +# Enables TrackPad +trackpadEnable() { + xinput set-prop $id "Device Enabled" 1 + exit +} + +# Disables TrackPad +trackpadDisable() { + xinput set-prop $id "Device Enabled" 0 + exit +} + +# Checks for disable flag +if [ ! -z $1 ] && [ $1 == '-d' ]; then + echo flag worked + trackpadDisable +fi + +# Convert to an arry +read -a trackPadState <<< "$(xinput --list-props $id | grep "Device Enabled")" +devEnabled=${devString_array[3]} + +# Flip the state of the TrackPad +if [ ${trackPadState[3]} -eq 1 ]; then + trackpadDisable +else + trackpadEnable +fi diff --git a/home/bin/executable_update.sh b/home/bin/executable_update.sh new file mode 100644 index 0000000..baa0f33 --- /dev/null +++ b/home/bin/executable_update.sh @@ -0,0 +1,66 @@ +#! /bin/bash + +# This script is a catch all program updater. + +# DNF Updater +function dnfUpdate { + if command -v dnf &> /dev/null; then + echo Updating DNF... + sudo dnf update #--exclude=wine* + fi +} + +# Apt Updater +function aptUpdate { + if command -v apt &> /dev/null; then + echo Updating APT... + sudo apt update + sudo apt upgrade + fi +} + +# Flatpack Updater +function flatpakUpdate { + if command -v flatpak &> /dev/null; then + echo Updating Flatpak... + flatpak uninstall --unused + flatpak update + fi +} + +# Appimage Updater +function appimageUpdate { + am update +} + +# Checks if a program is installed and if it is runs an updater script +function updateProgram { + if command -v $1 &> /dev/null; then + sh $2 + fi +} + +# Manually installed programs updater +function manualUpdate { + if command -v dnf &> /dev/null; then + echo Updating manually installed programs... + SCRIPT_PATH="$HOME/bin/installers" + updateProgram bitwig-studio $SCRIPT_PATH/bitwig-install.sh & + updateProgram /opt/dragonframe5/bin/Dragonframe $SCRIPT_PATH/dragonframe-install.sh & + updateProgram reaper $SCRIPT_PATH/reaper-install.sh & + updateProgram /opt/resolve/bin/resolve $SCRIPT_PATH/resolve-install.sh & + updateProgram /opt/keeweb/keeweb $SCRIPT_PATH/keeweb-install.sh & + updateProgram yabridgectl $SCRIPT_PATH/yabridge-install.sh & + wait + fi +} + +dnfUpdate +aptUpdate +echo '' +flatpakUpdate +echo '' +appimageUpdate +echo '' +manualUpdate + diff --git a/home/bin/frc-photo-checklist.py b/home/bin/frc-photo-checklist.py new file mode 100644 index 0000000..c03df8a --- /dev/null +++ b/home/bin/frc-photo-checklist.py @@ -0,0 +1,173 @@ +#!/usr/bin/env python3 + +# Program to create a photo checklist of a given frc event. + +import configparser +import datetime as dt +import re +import operator +import os +import sys +import tbapy +import todoist + +# getProjectID() returns the project id that matches the name given. +def getProjectID(api, name): + for project in api.state['projects']: + if project['name'] == name: + return project['id'] + print('Error: No project with the name {} found'.format(name)) + exit(1) + +# getChecklistName() +def getChecklistName(evemt): + return '{} Photos'.format(event['name']) + +# getEventListID() returns the id of the checklist for the event and if there is none +# returns -1. +def getEventListID(items, event): + for item in items: + if item['content'] == getChecklistName(event): + return item['id'] + return -1 + +# matchToTeamList() converts a match to two lists of teams +def matchToTeamList(match): + return match['alliances']['red']['team_keys'], match['alliances']['blue']['team_keys'] + +# createChecklistItem() creates a checklist item of the highest priority. +def createChecklistItem(name, api, projectID, item, date): + return api.items.add(name, + project_id=projectID, + parent_id=item['id'], + date_string=date, + priority=4) + +# createPhotoChecklistItem() creates a checklist item that requires a photo. +def createPhotoChecklistItem(name, api, projectID, item, date): + return createChecklistItem('Take photo of **{}**'.format(name), + api, projectID, item, date) + +# createPitList() creates a checklist for taking photos of a teams pit. +def createPitList(api, teams, projectID, checklist, date): + item = api.items.add('**Take** Pit Photos', + project_id=projectID, + parent_id=checklist['id'], + date_string=date, + priority=3) + for team in teams: + createChecklistItem('Pit photo of **{}** {}'.format(team['team_number'], team['nickname']), + api, projectID, item, date) + +# createGroupsList() creates a checklist of the different groups of volenteers. +def createGroupsList(api, projectID, checklist, date): + item = api.items.add('Groups', + project_id=projectID, + parent_id=checklist['id'], + date_string=date, + priority=3) + groups = ['Judges', 'Robot Inspectors', 'Referees', 'Safety Inspectors', + 'Field Reset', 'Queuers', 'CSAs', 'VC and Pit Admin'] + for group in groups: + createPhotoChecklistItem(group, api, projectID, item, date) + +# createWinnersList() creates a checklist of the winners of an event. +def createWinnersList(api, projectID, checklist, date): + item = api.items.add('Winners', + project_id=projectID, + parent_id=checklist['id'], + date_string=date, + priority=3) + groups = ['Chairman\'s award', 'Engineering Inspiration', 'Rookie All-Star', 'Winning Alliance', + 'Winning Team 1', 'Winning Team 2', 'Winning Team 3'] + for group in groups: + createPhotoChecklistItem(group, api, projectID, item, date) + +# createRobotList() creates a checklist for taking photos of a team's robot. +def createRobotList(api, teams, projectID, checklist, date): + item = api.items.add('**Take** Robot Photos', + project_id=projectID, + parent_id=checklist['id'], + date_string=date, + priority=3) + for team in teams: + createChecklistItem('Robot photo of **{}** {}'.format(team['team_number'], team['nickname']), + api, projectID, item, date) + +# Parse settings config +configString = '[Settings]\n' + open('../settings.conf').read() +configParser = configparser.RawConfigParser() +configParser.read_string(configString) + +# Load needed credentials +tbaKey = configParser.get('Settings', 'TBAKey') +todoistToken = configParser.get('Settings', 'TodoistToken') + +# Setup Todoist +api = todoist.TodoistAPI(todoistToken) +api.sync() +projectID = getProjectID(api, '🤖 Robotics') +items = api.state['items'] + +# Setup the Blue Alliance +tba = tbapy.TBA(tbaKey) +eventKey = sys.argv[1] +event = tba.event(eventKey) +setupDay = event['start_date'] +day1 = (dt.datetime.strptime(setupDay, '%Y-%m-%d') + dt.timedelta(days=1)).strftime('%Y-%m-%d') +day2 = event['end_date'] +teams = sorted(tba.event_teams(eventKey), key=operator.attrgetter('team_number')) + +def firstMatch(team, matches): + for match in matches: + red, blue = matchToTeamList(match) + if team in red: + return match, 'red' + elif team in blue: + return match, 'blue' + return None, None + +# Check if list already exists +eventListID = getEventListID(items, event) +if eventListID == -1: + # Create checklist + checklist = api.items.add(getChecklistName(event), + project_id=projectID, + date_string=day2, + priority=2) + # Setup + createPitList(api, teams, projectID, checklist, setupDay) + createChecklistItem('**Schedule** Judges photo', api, projectID, checklist, setupDay) + createChecklistItem('**Schedule** Inspectors photo', api, projectID, checklist, setupDay) + createChecklistItem('**Schedule** Seniors photo', api, projectID, checklist, setupDay) + createGroupsList(api, projectID, checklist, setupDay) + # Day 1 + createPhotoChecklistItem('Guest Speakers', api, projectID, checklist, day1) + createRobotList(api, teams, projectID, checklist, day1) + # Day 2 + createPhotoChecklistItem('Guest Speakers', api, projectID, checklist, day2) + createPhotoChecklistItem('Mentors after parade', api, projectID, checklist, day2) + createPhotoChecklistItem('Seniors', api, projectID, checklist, day2) + createPhotoChecklistItem('Alliances Representatives', api, projectID, checklist, day2) + createWinnersList(api, projectID, checklist, day2) + createChecklistItem('**Email** guest speakers and winners photos', api, projectID, checklist, day2) +else: + print('List already created') + matches = sorted(tba.event_matches(eventKey), key=operator.attrgetter('time')) + if not matches: + print('No match schedule yet') + else: + photoParentID = [item['id'] for item in items if 'parent_id' in item + and item['parent_id'] == eventListID + and 'Robot Photos' in item['content']][0] + robotPhotoList = [item for item in items if 'Robot photo of' in item['content'] + and item['parent_id'] == photoParentID] + for robot in robotPhotoList: + if 'match' not in robot['content']: + team = 'frc{}'.format(re.findall(r'\d+', robot['content'])[0]) + match, side = firstMatch(team, matches) + if match != None: + matchNumber = match['match_number'] + matchTime = dt.datetime.fromtimestamp(match['time']).strftime('%Y-%m-%d %I:%M %p') + robot.update(date_string=matchTime, content='{} match {} {}'.format(robot['content'], matchNumber, side)) +api.commit() diff --git a/home/bin/homeassistant.py b/home/bin/homeassistant.py new file mode 100644 index 0000000..ca0fa58 --- /dev/null +++ b/home/bin/homeassistant.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python3 + +# Python wrapper for REST API for Home Assistant. + +from requests import get, post +import json + +class HomeAssistant(object): + + # Initalizes Home Assistant API wrapper. + def __init__(self, ip, token): + self.url = 'http://{}:8123'.format(ip) + self.headers = { + 'Authorization': 'Bearer {}'.format(token), + 'content-type': 'application/json', + } + + # Sends post requests. + def postService(self, domain, service, data): + response = post("{}/api/services/{}/{}".format(self.url, + domain, + service), + headers=self.headers, data=json.dumps(data)) + response.raise_for_status() + return response + + # Sends get requests and turns requested data. + def getRequest(self, domain): + response = get("{}/api/{}".format(self.url, domain), + headers=self.headers) + return json.loads(response.text) + + # Returns a message if the API is up and running. + def getAPI(self): + return self.getRequest('') + + # Returns the current configuration + def getConfig(self): + return self.getRequest('config') + + # Returns basic information about the Home Assistant instance. + def getDiscoveryInfo(self): + return self.getRequest('discovery_info') + + # Returns an array of event objects. Each event object contains + # event name and listener count. + def getEvents(self): + return self.getRequest('events') + + # Returns an array of service objects. Each object contains the + # domain and which services it contains. + def getServices(self): + return self.getRequest('services') + + # Returns an array of state changes in the past. Each object contains + # further details for the entities. + def getHistory(self, + minimumResponse=False, + entityId='', + startTime='', + endTime='', + significantChangesOnly=False): + options='' + if startTime: + options = '/' + startTime + options += '?' + if endTime: + options += '&end_time=' + endTime + if minimumResponse: + options += '&minimal_response' + if entityId: + options += '&filter_entity_id=' + entityId + if significantChangesOnly: + options += '&significant_changes_only' + return self.getRequest('history/period'+options) + + # Returns an array of logbook entries. + def getLogbook(self, entityId='', startTime='', endTime=''): + options='' + if startTime: + options = '/' + startTime + options += '?' + if endTime: + options += '&end_time=' + endTime + if entityId: + options += '&entity=' + entityId + return self.getRequest('logbook'+options) + + # Returns an array of state objects or a state object for specified + # entity_id. Each state has the following attributes: entity_id, state, + # last_changed and attributes. + def getState(self, entityId=''): + if entityId: + entityId = '/' + entityId + return self.getRequest('states' + entityId) + + # Retrieve all errors logged during the current session of Home Assistant. + def getErrorLog(self): + return self.getRequest('error_log') + + # Returns the data (image) from the specified camera entity_id. + def getCameraProxy(self, entityId): + return self.getRequest('camera_proxy/' + entityId) + + # Runs a Home Assistant scene. + def runScene(self, entityId): + data = {'entity_id': entityId} + self.postService('scene', 'turn_on', data) + + # Runs a Home Assistant script. + def runScript(self, entityId): + data = {'entity_id': entityId} + self.postService('script', 'turn_on', data) + + # Sets the brightness level of a device. + def setLevel(self, entityId, level): + data = {'entity_id': entityId, 'brightness_pct': level} + self.postService('homeassistant', 'turn_on', data) + + # Turns a device off. + def turnOn(self, entityId): + data = {'entity_id': entityId} + self.postService('homeassistant', 'turn_on', data) + + # Turns a device on. + def turnOff(self, entityId): + data = {'entity_id': entityId} + self.postService('homeassistant', 'turn_off', data) + + # Turns a device of the given power state. + def setOnOff(self, entityId, power): + if power: + self.turnOn(entityId) + else: + self.turnOff(entityId) + + # Toggles power state of a given device. + def togglePower(self, entityId): + if self.getState(entityId)['state'] == 'off': + self.turnOn(entityId) + else: + self.turnOff(entityId) diff --git a/home/bin/i3-mouse.py b/home/bin/i3-mouse.py new file mode 100644 index 0000000..e68f4d4 --- /dev/null +++ b/home/bin/i3-mouse.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 +import i3ipc +import sys +import os +import time +import pyautogui +from enum import Enum + +# Enum for mouse direction +class Direction(Enum): + UP = 1 + LEFT = 2 + RIGHT = 3 + DOWN = 4 + NONE = 5 + +# getPoints() returns x and y movement of mouse +def getPoints(): + sen = 10 + t = 0 + delay = 0.01 + + x,y = pyautogui.position() + while True: + time.sleep(delay) + t += delay + if t > 0.5: + return + xp, yp = pyautogui.position() + dx = x - xp + dy = y - yp + if abs(dx) > sen or abs(dy) > sen: + break + return dx, dy + +#pointsToDirection() converts mouse movement points to a direction +def pointsToDirection(points): + if points is None: + return + x, y = points + if abs(x) > abs(y): + if x < 0: + return Direction.RIGHT + else: + return Direction.LEFT + else: + if y > 0: + return Direction.UP + else: + return Direction.DOWN + +i3 = i3ipc.Connection() +focused = i3.get_tree().find_focused() +command = sys.argv[1] + +if command in ['back', 'forward']: + if focused.window_instance not in ['overwatch.exe', 'hl2_linux']: + if sys.argv[1] == 'forward': + i3.command('workspace prev_on_output') + else: + i3.command('workspace next_on_output') + +elif command == 'thumb': + direction = pointsToDirection(getPoints()) + if direction == Direction.UP: + i3.command('move up') + elif direction == Direction.RIGHT: + i3.command('move right') + elif direction == Direction.DOWN: + i3.command('move down') + elif direction == Direction.LEFT: + i3.command('move left') diff --git a/home/bin/installers/blackmagic-parser.py b/home/bin/installers/blackmagic-parser.py new file mode 100644 index 0000000..f19743d --- /dev/null +++ b/home/bin/installers/blackmagic-parser.py @@ -0,0 +1,49 @@ +import json +import os +import re +import urllib.request + +# getJSONData returns JSON data from Blackmagic Design's website. +def getJSONData(): + with urllib.request.urlopen('https://www.blackmagicdesign.com/api/support/us/downloads.json') as url: + return json.loads(url.read().decode()) + +# getDownloads() returns a list of downloads. +def getDownloads(): + return getJSONData()['downloads'] + +# getResolveStudioDownloads() returns a list of DaVinci Resolve Studio downlaods. +def getResolveStudioDownloads(): + return [d for d in getDownloads() if 'davinci-resolve-and-fusion' in d['relatedFamilies'][0] and + 'Studio' in d['name'] and 'Resolve' in d['name']] + +# filterOnlyLinuxSupport() filters a list of downloads to only ones that +# support Linux. +def filterOnlyLinuxSupport(downloads): + return [d for d in downloads if 'Linux' in d['platforms']] + +# getLinuxURL() returns the Linux download info. +def getLinuxURL(download): + return download['urls']['Linux'][0] + +# getURLId() returns the download id. +def getURLId(url): + return url['downloadId'] + +# getURLVersion() returns the url version number. +def getURLVersion(url): + if 'Beta' in url['downloadTitle']: + beta = re.search('Beta \\d+', url['downloadTitle']) + if beta: + beta = re.search('\\d+', beta.group()).group() + else: + beta = '99' + return '{}.{}.{}.{}'.format(url['major'], url['minor'], url['releaseNum'], beta) + +# getDownloadId() returns downlaod id hash. +def getDownloadId(download): + return download['id'] + +for d in filterOnlyLinuxSupport(getResolveStudioDownloads()): + linux = getLinuxURL(d) + print(getURLVersion(linux), getURLId(linux), getDownloadId(d)) diff --git a/home/bin/installers/executable_bitwig-install.sh b/home/bin/installers/executable_bitwig-install.sh new file mode 100644 index 0000000..7c46381 --- /dev/null +++ b/home/bin/installers/executable_bitwig-install.sh @@ -0,0 +1,27 @@ +#! /bin/bash + +# Automatic install script for Bitwig Studio + +# Import library +source $(dirname ${BASH_SOURCE[0]})/install-lib.sh + +site='https://www.bitwig.com/download/' +bitwig=$(searchProgramInstalled bitwig-studio) +bitwigVersion=$(echo $bitwig | awk '{print $2;}'| filterVersion) +urlVersion=$(curl -sL $site | grep 'Bitwig Studio' | filterVersion | head -n 1) +url=https://downloads.bitwig.com/stable/$urlVersion/bitwig-studio-$urlVersion.deb + +checkUptoDate Bitwig $bitwigVersion $urlVersion + +echo Installing Bitwig Studio $urlVersion + +# Setting up and downloading package +downloadPackage bitwig $url $(basename $url) + +# Converting to Fedora friendly package +echo Creating rpm package +package=$(sudo alien -r $(basename $url) | awk '{print $1;}') + +# Installing package +sudo rpm -Uvh --nodeps --force $package +sudo ln -s /usr/lib64/libbz2.so.1.0** /usr/lib64/libbz2.so.1.0 diff --git a/home/bin/installers/executable_dragonframe-install.sh b/home/bin/installers/executable_dragonframe-install.sh new file mode 100644 index 0000000..5e49c2e --- /dev/null +++ b/home/bin/installers/executable_dragonframe-install.sh @@ -0,0 +1,41 @@ +#! /bin/bash + +# Automatic install script for Dragonframe + +# Import library +source $(dirname ${BASH_SOURCE[0]})/install-lib.sh + +# Fix issue with shared library files. +function postInstallFix() { + sudo rm /opt/dragonframe2024/lib/libtiff.so.5 + sudo rm /opt/dragonframe2024/lib/libudev.so.0 + sudo ln -s -f /lib64/libudev.so.1 /opt/dragonframe2024/lib/libudev.so.0 +} + +dragonframe=$(searchProgramInstalled dragonframe20* | \ + awk 'END {print $(NF-2), $(NF-1), $NF}') +dragonframeVersion=$(echo $dragonframe | awk '{print $2;}' | filterVersion) +agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:104.0) Gecko/20100101 Firefox/104.0' +url=$(curl -s https://www.dragonframe.com/downloads/ \ + -A $agent | \ + grep .rpm | grep downloadButton | grep downloadButton | \ + grep -io 'https://[a-z0-9+-._/]*.rpm' | head -n 1) +urlVersion=$(echo $url | awk -F "-" '{ print $2 }') + +# Check if installed to the most recent version +checkUptoDate dragonframe $dragonframeVersion $urlVersion +echo Installing Dragonframe $urlVersion + +# Setting up and downloading package +mkdir -p ~/Downloads/installers/dragonframe +cd ~/Downloads/installers/dragonframe + + +installer=$(basename "$url") +curl -s $url \ + -A $agent \ + -o $installer + +# Install package +sudo dnf install $installer -y + diff --git a/home/bin/installers/executable_install-lib.sh b/home/bin/installers/executable_install-lib.sh new file mode 100644 index 0000000..2c63fe0 --- /dev/null +++ b/home/bin/installers/executable_install-lib.sh @@ -0,0 +1,62 @@ +# Program version number comparison. +function versionGreater() { + if [[ $1 == $2 ]];then + return 0 + fi + local IFS=. + local i ver1=($1) ver2=($2) + # fill empty fields in ver1 with zeros + for ((i=${#ver1[@]}; i<${#ver2[@]}; i++)); do + ver1[i]=0 + done + for ((i=0; i<${#ver1[@]}; i++)); do + if [[ -z ${ver2[i]} ]]; then + # fill empty fields in ver2 with zeros + ver2[i]=0 + fi + if ((10#${ver1[i]} > 10#${ver2[i]})); then + return 1 + fi + if ((10#${ver1[i]} < 10#${ver2[i]})); then + return 2 + fi + done + return 0 +} + +# Check if installed to the most recent version. +function checkUptoDate() { + if versionGreater $2 $3; then + echo $1 is up to date. Installed version $2 web version $3 + exit + else + echo Updating $1 from $2 to $3... + fi +} + +# Returns installed programs with a given name. +function searchProgramInstalled() { + dnf list -q | grep $1 +} + +# Filters string to Semantic Versioning. +function filterVersion() { + grep -Po -m 1 '\d{1,4}\.\d{1,4}\.*\d{0,4}' +} + +# Downloads a file to the given download directory with +# the given name. +function downloadPackage() { + mkdir -p ~/Downloads/installers/${1} + cd ~/Downloads/installers/${1} + wget -O ${3} ${2} +} + +# Get package manager +function packageManager() { + if command -v dnf &> /dev/null; then + echo dnf + elif command -v apt &> /dev/null; then + echo apt + fi +} diff --git a/home/bin/installers/executable_keeweb-install.sh b/home/bin/installers/executable_keeweb-install.sh new file mode 100644 index 0000000..2c3eff2 --- /dev/null +++ b/home/bin/installers/executable_keeweb-install.sh @@ -0,0 +1,25 @@ +#! /bin/bash + +# Automatic install script for KeeWeb password manager + +# Import library +source $(dirname ${BASH_SOURCE[0]})/install-lib.sh + +keeweb=$(searchProgramInstalled KeeWeb) +keewebVersion=$(echo $keeweb | awk '{print $2;}' | filterVersion) +url=$(curl -s https://github.com/keeweb/keeweb/releases | grep .rpm | grep -Po '(?<=href=")[^"]*.rpm'| head -n 1) +url='https://github.com'$url +urlVersion=$(echo $url | filterVersion | head -n 1) + +# Check if installed to the most recent version +checkUptoDate keeweb $keewebVersion $urlVersion +echo Installing KeeWeb $urlVersion + +# Setting up and downloading package +mkdir -p ~/Downloads/installers/keeweb +cd ~/Downloads/installers/keeweb +wget $url + +# Install package +sudo dnf install $(basename $url) -y + diff --git a/home/bin/installers/executable_reaper-install.sh b/home/bin/installers/executable_reaper-install.sh new file mode 100644 index 0000000..9a3b65c --- /dev/null +++ b/home/bin/installers/executable_reaper-install.sh @@ -0,0 +1,26 @@ +#! /bin/bash + +# Automatic install script for Reaper + +# Import library +source $(dirname ${BASH_SOURCE[0]})/install-lib.sh + +# Get download url +reaperVersion=$(head /opt/REAPER/whatsnew.txt | filterVersion) +reaperSite='https://www.reaper.fm' +downloadPage=$(curl -s "${reaperSite}/download.php") +urlVersion=$(echo "$downloadPage" | grep -A 2 'Linux x86_64' | filterVersion) +url="${reaperSite}/$(echo "$downloadPage" | grep linux_x86_64 | grep -Po '(?<=href=")[^"]*'| head -n 1)" + +checkUptoDate Reaper $reaperVersion $urlVersion + +# Setting up and downloading package +downloadPackage reaper $url $(basename $url) + +# Install Reaper. Requires user input +tar -xf $(basename $url) +reaperDir=reaper_linux_x86_64 +sudo sh ./$reaperDir/install-reaper.sh --install /opt --integrate-sys-desktop --usr-local-bin-symlink + +# Delete extracted directory +rm -rd $reaperDir diff --git a/home/bin/installers/executable_resolve-install.sh b/home/bin/installers/executable_resolve-install.sh new file mode 100644 index 0000000..2bb773d --- /dev/null +++ b/home/bin/installers/executable_resolve-install.sh @@ -0,0 +1,102 @@ +#! /bin/bash + +# Automatic install script for DaVinci Resolve + +# Import library +source $(dirname ${BASH_SOURCE[0]})/install-lib.sh + +# Graphics card fix +function graphicsCardFix() { + sudo rm /etc/OpenCL/vendors/mesa.icd + sudo rm /etc/OpenCL/vendors/pocl.icd +} + +# gLib fix +function glibFix() { + sudo mkdir /opt/resolve/libs/_disabled + sudo mv /opt/resolve/libs/libglib-2.0.so* /opt/resolve/libs/_disabled + sudo mv /opt/resolve/libs/libgio-2.0.so* /opt/resolve/libs/_disabled + sudo mv /opt/resolve/libs/libgmodule-2.0.so* /opt/resolve/libs/_disabled +} + +versionFile=/opt/resolve/version.txt + +resolveVersion=$(cat /opt/resolve/docs/ReadMe.html | grep 'DaVinci Resolve Studio' | filterVersion) +url=$(python $(dirname ${BASH_SOURCE[0]})/blackmagic-parser.py | head -n 1) +urlVersion=$(echo $url | awk '{print $1;}') +downloadID=$(echo $url | awk '{print $2;}') +referId=$(echo $url | awk '{print $3;}') + +# Check for beta +major=$(echo $urlVersion | cut -d. -f1) +minor=$(echo $urlVersion | cut -d. -f2) +micro=$(echo $urlVersion | cut -d. -f3) +beta=$(echo $urlVersion | cut -d. -f4) + +if [ "$beta" == '99' ]; then + packageName="DaVinci_Resolve_Studio_${major}.${minor}" +elif [ -n "$beta" ]; then + packageName="DaVinci_Resolve_Studio_${major}.${minor}b${beta}" +else + packageName="DaVinci_Resolve_Studio_${urlVersion}" +fi + +# Get version if beta installed +if [ -n $resolveVersion ]; then + resolveVersion=$(cat $versionFile) +fi + +checkUptoDate Resolve $resolveVersion $urlVersion + +downloadUrl="https://www.blackmagicdesign.com/api/register/us/download/${downloadID}" +useragent="User-Agent: Mozilla/5.0 (X11; Linux) \ + AppleWebKit/537.36 (KHTML, like Gecko) \ + Chrome/77.0.3865.75 \ + Safari/537.36" +reqjson="{ \ + \"firstname\": \"Fedora\", \ + \"lastname\": \"Linux\", \ + \"email\": \"user@getfedora.org\", \ + \"phone\": \"919-555-7428\", \ + \"country\": \"us\", \ + \"state\": \"North Carolina\", \ + \"city\": \"Raleigh\", \ + \"product\": \"DaVinci Resolve\" \ +}" +zipUrl="$(curl \ + -s \ + -H 'Host: www.blackmagicdesign.com' \ + -H 'Accept: application/json, text/plain, */*' \ + -H 'Origin: https://www.blackmagicdesign.com' \ + -H "$useragent" \ + -H 'Content-Type: application/json;charset=UTF-8' \ + -H "Referer: https://www.blackmagicdesign.com/support/download/${referId}/Linux" \ + -H 'Accept-Encoding: gzip, deflate, br' \ + -H 'Accept-Language: en-US,en;q=0.9' \ + -H 'Authority: www.blackmagicdesign.com' \ + -H 'Cookie: _ga=GA1.2.1849503966.1518103294; _gid=GA1.2.953840595.1518103294' \ + --data-ascii "$reqjson" \ + --compressed \ + "$downloadUrl")" + +# Setting up and downloading package +downloadPackage resolve $zipUrl "${packageName}.zip" + +# Installing package +sudo dnf install libxcrypt-compat +unzip -o $packageName +installerName="DaVinci_Resolve_Studio_${major}.${minor}.${micro}" +if [ ! -f ./*${installerName}_Linux.run ]; then + installerName="${packageName}" +fi +echo "Installing ./*${installerName}_Linux.run" +chmod +x ./*${installerName}_Linux.run +sudo SKIP_PACKAGE_CHECK=1 ./*${installerName}_Linux.run -i -y + +# Version number backup +sudo echo $urlVersion > $versionFile + +glibFix + +# Keyboard mapping fix +setxkbmap -option 'caps:super' diff --git a/home/bin/installers/executable_rust-install.sh b/home/bin/installers/executable_rust-install.sh new file mode 100644 index 0000000..c36df6a --- /dev/null +++ b/home/bin/installers/executable_rust-install.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +# Import library +source $(dirname ${BASH_SOURCE[0]})/install-lib.sh + +sudo $(packageManager) install rust cargo diff --git a/home/bin/installers/executable_superslicer-install.sh b/home/bin/installers/executable_superslicer-install.sh new file mode 100644 index 0000000..2276e26 --- /dev/null +++ b/home/bin/installers/executable_superslicer-install.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash + +# Automatic install script for SuperSlicer. + +# Import library +source $(dirname ${BASH_SOURCE[0]})/install-lib.sh + +function desktopFile() { + echo 'Creating desktop file' + echo '[Desktop Entry]' > $1 + echo "Name=${2}" >> $1 + echo 'Comment=3D printing slicer' >> $1 + echo "Exec=${3}" >> $1 + echo 'Icon=' >> $1 + echo 'Type=Application' >> $1 + echo 'Terminal=false' >> $1 +} + +program='SuperSlicer' +programPath="${HOME}/.local/bin/${program}.AppImage" +programVersion=$($programPath --help | grep SuperSlicer | filterVersion) +url=$(curl -s https://api.github.com/repos/supermerill/SuperSlicer/releases) +urlVersion=$(echo $url | grep tag_name | filterVersion | head -n 1) +url=$(echo "$url" | grep browser_download | grep ubuntu | head -n 1 | \ + tr -d '"'| awk '{print $2}') + +# Check if installed to the most recent version +checkUptoDate $program $programVersion $urlVersion +echo Installing $program $urlVersion + +# Setting up and downloading package +cd $(dirname $programPath) +rm $programPath +wget $url -O $program.AppImage +chmod +x $programPath + +# Create desktop file +desktopPath="${HOME}/.local/share/applications/${program}.desktop" +if [ ! -f $desktopPath ]; then + desktopFile $desktopPath $program $programPath +fi diff --git a/home/bin/installers/executable_sway-install.sh b/home/bin/installers/executable_sway-install.sh new file mode 100644 index 0000000..3334255 --- /dev/null +++ b/home/bin/installers/executable_sway-install.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +# Import library +source $(dirname ${BASH_SOURCE[0]})/install-lib.sh + +sudo $(packageManager) install sway waybar rofi mako alacritty diff --git a/home/bin/installers/executable_yabridge-install.sh b/home/bin/installers/executable_yabridge-install.sh new file mode 100644 index 0000000..64de0b5 --- /dev/null +++ b/home/bin/installers/executable_yabridge-install.sh @@ -0,0 +1,38 @@ +#! /bin/bash + +# Automatic install script for yabridge an audio bridge for linux. + +# Import library +source $(dirname ${BASH_SOURCE[0]})/install-lib.sh + +# Install wine if not already installed +if ! command -v wine &> /dev/null; then + if command -v dnf &> /dev/null; then + sudo dnf config-manager --add-repo \ + https://dl.winehq.org/wine-builds/fedora/36/winehq.repo + fi + sudo $(packageManager) install winehq-staging +fi + +program=yabridgectl +programVersion=$(yabridgectl --version | filterVersion) +url=$(curl -s https://api.github.com/repos/robbert-vdh/yabridge/releases | \ + grep .tar.gz | grep releases/download | \ + grep -Po 'https://[^"]*.tar.gz' | grep -v ubuntu | head -n 1) +urlVersion=$(echo $url | filterVersion | head -n 1) + +# Check if installed to the most recent version +checkUptoDate $program $programVersion $urlVersion +echo Installing $program $urlVersion + +# Setting up and downloading package +mkdir -p ~/Downloads/installers/${program} +cd ~/Downloads/installers/${program} +wget $url + +# Install package +tar -xvzf *${urlVersion}.tar.gz +rm -rd ~/.local/share/yabridge +mv ./yabridge ~/.local/share/ +sudo rm /bin/yabridgectl +sudo ln -s ~/.local/share/yabridge/yabridgectl /bin/yabridgectl diff --git a/home/bin/installers/executable_zsh-install.sh b/home/bin/installers/executable_zsh-install.sh new file mode 100644 index 0000000..a203287 --- /dev/null +++ b/home/bin/installers/executable_zsh-install.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +# Install and set zsh as the default shell. + +# Import library +source $(dirname ${BASH_SOURCE[0]})/install-lib.sh + +sudo $(packageManager) install zsh +chsh -s $(which zsh) diff --git a/home/bin/launch-stocks-tracker.py b/home/bin/launch-stocks-tracker.py new file mode 100644 index 0000000..7e183aa --- /dev/null +++ b/home/bin/launch-stocks-tracker.py @@ -0,0 +1,30 @@ +import datetime, holidays, os +from dateutil.tz import tzlocal + +tz = tzlocal() +usHolidays = holidays.US() +def openToday(now = None): + if not now: + now = datetime.datetime.now(tz) + openTime = datetime.time(hour = 9, minute = 30, second = 0) + closeTime = datetime.time(hour = 16, minute = 0, second = 0) + # If it is a holiday + if now.strftime('%Y-%m-%d') in usHolidays: + return False + # If it is a weekend + if now.date().weekday() > 4: + return False + return True + + +def closed(): + now = datetime.datetime.now(tz) + closeTime = datetime.time(hour = 16, minute = 0, second = 0) + # If before 0930 or after 1600 + if (now.time() > closeTime): + return True + return False +if (openToday() and not closed()): + print("Open") + os.system("i3-msg 'workspace 10; exec librewolf --new-window robinhood.com \ + && sleep 1 && firefox -new-tab app.webull.com/watch'") diff --git a/home/bin/start-firefox.py b/home/bin/start-firefox.py new file mode 100644 index 0000000..5ab3292 --- /dev/null +++ b/home/bin/start-firefox.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 + +from i3ipc import Connection, Event +import os, time + +# moveWindowToWorkspace() moves a given window to a given workspace. +def moveWindowToWorkspace(window, workspace): + window.command('move window to workspace ' + workspace) + +# getWindows() returns a list of all open windows on the desktop. +def getWindows(i3): + windows = [] + for con in i3.get_tree(): + if con.window and con.parent.type != 'dockarea': + windows.append(con) + return windows + +# getWindowByName() returns a window with the given name. +def getWindowByName(name, windows): + for win in windows: + if name in win.name: + return win + return + +# filterWindowsByClass() returns a filter list of windows by class. +def filterWindowsByClass(windowClass, windows): + return [w for w in windows if w.window_class == windowClass] + +# doesWindowExist() returns if a given window exists. +def doesWindowExist(window): + return window != None + +# switchWorkspace() switches currently selected workspace. +def switchWorkspace(workspace): + i3.command('workspace ' + workspace) + +# execI3() runs a command from i3wm. +def execI3(program): + i3.command('exec ' + program) + +# launchProgram() launches a program on a given workspace. +def launchProgram(program, workspace): + switchWorkspace(workspace) + execI3(program) + +# isProgramRunning() returns if a program is running and if it is +# moves it to a given workspace. +def isProgramRunning(name, windows, workspace): + for n in name: + program = getWindowByName(n, windows) + if doesWindowExist(program): + moveWindowToWorkspace(program, workspace) + return True + return False + + +def isPagesLoaded(windows): + for w in windows: + if 'http' not in w.name: + return True + return True +i3 = Connection() +windows = filterWindowsByClass('librewolf-default', getWindows(i3)) + +while(not isPagesLoaded(windows)): + time.sleep(0.1) + +switchWorkspace('10') +switchWorkspace('1') + +# Music +if not isProgramRunning(['music.youtube.com'], windows, '10'): + launchProgram('librewolf --new-window music.youtube.com', '10') + +# Stocks +if not isProgramRunning(['Robinhood', 'Webull'], windows, '10'): + os.system('python ~/bin/launch-stocks-tracker.py') + +# Videos +if not isProgramRunning(['odysee.com', 'lbry.tv', 'www.youtube.com', + ' - YouTube', 'hulu.com', 'netflix.com', + 'disneyplus.com', 'tv.youtube.com'], + windows, '10'): + launchProgram('librewolf --new-window youtube.com/feed/subscriptions', '10') + launchProgram('sleep 1 && librewolf -new-tab odysee.com/$/following', '10') diff --git a/home/bin/system/executable_backlight-ctl.sh b/home/bin/system/executable_backlight-ctl.sh new file mode 100644 index 0000000..78e19f9 --- /dev/null +++ b/home/bin/system/executable_backlight-ctl.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash + +# Print help info. +function print_help() { + echo 'Control the backlight brightness with animation.' + echo + echo '-i -- increase brightness' + echo '-d -- decrease brightness' +} + +# Increase backlight brightness. +function increase() { + run_animation 'light -A 0.1' +} + +# Decrease backlight brightness. +function decrease() { + run_animation 'light -U 0.1' +} + +# Run animation task. +function run_animation() { + local cmd="${1}" + i=0 + while [ $i -lt 50 ]; do + ${cmd} + sleep 0.01 + ((i = i + 1)); + done +} + + +# Check arguments +for i in "$@"; do + case $i in + -h|--help) + print_help + exit 0 + ;; + -i) + increase + ;; + -d) + decrease + ;; + *) + print_help + exit 1 + ;; + esac +done + diff --git a/home/bin/system/executable_system-start.sh b/home/bin/system/executable_system-start.sh new file mode 100644 index 0000000..0ee92ab --- /dev/null +++ b/home/bin/system/executable_system-start.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +# Update Passwords +pass git pull + +# Desktop +~/bin/connect-nas.sh +systemctl --user start polybar +systemctl --user restart streamdeck +waybar & +/usr/libexec/polkit-gnome-authentication-agent-1 & + +# Keyring +dbus-update-activation-environment --all +/usr/bin/gnome-keyring-daemon --start --components=secrets,pkcs11,ssh + +# NextCloud sync client +nextcloud --background & diff --git a/home/bin/video/executable_cr3todng.sh b/home/bin/video/executable_cr3todng.sh new file mode 100644 index 0000000..317bc85 --- /dev/null +++ b/home/bin/video/executable_cr3todng.sh @@ -0,0 +1,53 @@ +#! /bin/bash + +# Script to convert CR3 files to DNG. + +WINE_PREFIX=$(echo $HOME/.dng-wine) +CONVERTER_PATH='C:\Program Files\Adobe\Adobe DNG Converter\Adobe DNG Converter.exe' +FLAGS='-c -fl -p1' + +# Converts foreward slashes to back slashes. +function foreward2Back() { + sed 's:/:\\:g' +} + +# Converts a directory path a wine friendly path. +function dir2Wine() { + echo 'z:'"$(echo $( cd "$1" >/dev/null 2>&1 ; pwd -P ) | foreward2Back)" +} + +# Converts file path to a wine friendly path to the file. +function file2Wine() { + echo $(dir2Wine $(dirname $1))\\$(basename $1) +} + +# Converts a CR3 RAW file to DNG RAW. +function convertFile() { + WINEPREFIX="$WINE_PREFIX" wine "$CONVERTER_PATH" \ + $FLAGS $2 "$(file2Wine $1)" & +} + +# Converts all CR3 RAW files in a directory to DNG RAW. +function convertDir() { + for file in $1*.cr3; do + convertFile $file "-d $2" + done +} + +start=`date +%s%N` + +src=$1 +dst=$(dir2Wine $2) + +if [[ -d $src ]]; then + convertDir $src $dst +elif [[ -f $src ]]; then + convertFile $src $dst +else + echo "$src is not valid" + exit 1 +fi + +wait +end=`date +%s%N` +echo Execution time was `expr $end - $start` nanoseconds.