From 5a1b44e4b10c61e49bac7e555f45f36ecb789a4a Mon Sep 17 00:00:00 2001 From: Lionarius Date: Thu, 22 May 2025 13:28:23 +0300 Subject: [PATCH] initial --- alacritty/.config/alacritty/alacritty.toml | 3 + bat/.config/bat/config | 28 +++ ghostty/.config/ghostty/config | 10 + .../60-setup-virtual-devices.conf | 55 +++++ scripts/.local/bin/mute_toggle.sh | 203 ++++++++++++++++++ zsh/.zshrc | 95 ++++++++ 6 files changed, 394 insertions(+) create mode 100644 alacritty/.config/alacritty/alacritty.toml create mode 100644 bat/.config/bat/config create mode 100644 ghostty/.config/ghostty/config create mode 100644 pipewire/.config/pipewire/pipewire.conf.d/60-setup-virtual-devices.conf create mode 100755 scripts/.local/bin/mute_toggle.sh create mode 100644 zsh/.zshrc diff --git a/alacritty/.config/alacritty/alacritty.toml b/alacritty/.config/alacritty/alacritty.toml new file mode 100644 index 0000000..41f7ec3 --- /dev/null +++ b/alacritty/.config/alacritty/alacritty.toml @@ -0,0 +1,3 @@ +[font] +normal = { family = "CaskaydiaCove Nerd Font", style = "Regular" } +size = 14 diff --git a/bat/.config/bat/config b/bat/.config/bat/config new file mode 100644 index 0000000..132ac5c --- /dev/null +++ b/bat/.config/bat/config @@ -0,0 +1,28 @@ +# This is `bat`s configuration file. Each line either contains a comment or +# a command-line option that you want to pass to `bat` by default. You can +# run `bat --help` to get a list of all possible configuration options. + +# Specify desired highlighting theme (e.g. "TwoDark"). Run `bat --list-themes` +# for a list of all available themes +#--theme="TwoDark" + +# Enable this to use italic text on the terminal. This is not supported on all +# terminal emulators (like tmux, by default): +#--italic-text=always + +# Uncomment the following line to disable automatic paging: +#--paging=never + +# Uncomment the following line if you are using less version >= 551 and want to +# enable mouse scrolling support in `bat` when running inside tmux. This might +# disable text selection, unless you press shift. +#--pager="less --RAW-CONTROL-CHARS --quit-if-one-screen --mouse" + +# Syntax mappings: map a certain filename pattern to a language. +# Example 1: use the C++ syntax for Arduino .ino files +# Example 2: Use ".gitignore"-style highlighting for ".ignore" files +#--map-syntax "*.ino:C++" +#--map-syntax ".ignore:Git Ignore" + +--map-syntax='*.conf:INI' +--map-syntax ".ignore:Git Ignore" diff --git a/ghostty/.config/ghostty/config b/ghostty/.config/ghostty/config new file mode 100644 index 0000000..d32e058 --- /dev/null +++ b/ghostty/.config/ghostty/config @@ -0,0 +1,10 @@ +# Run `ghostty +show-config --default --docs` to view a list of +# all available config options and their default values. +# +# Additionally, each config option is also explained in detail +# on Ghostty's website, at https://ghostty.org/docs/config. + +theme = "MaterialDarker" +font-family = "CaskaydiaCove Nerd Font" +gtk-single-instance = true +font-size = 14 diff --git a/pipewire/.config/pipewire/pipewire.conf.d/60-setup-virtual-devices.conf b/pipewire/.config/pipewire/pipewire.conf.d/60-setup-virtual-devices.conf new file mode 100644 index 0000000..3aff6cb --- /dev/null +++ b/pipewire/.config/pipewire/pipewire.conf.d/60-setup-virtual-devices.conf @@ -0,0 +1,55 @@ +context.objects = [ + { + factory = adapter + args = { + factory.name = support.null-audio-sink + node.name = "desktop-audio" + node.description = "Desktop Audio" + node.passive = true + media.class = Audio/Sink + audio.position = [ FL FR ] + monitor.channel-volumes = true + monitor.passthrough = true + } + }, + { + factory = adapter + args = { + factory.name = support.null-audio-sink + node.name = "comms-audio" + node.description = "Comms Audio" + node.passive = true + media.class = Audio/Sink + audio.position = [ FL FR ] + monitor.channel-volumes = true + monitor.passthrough = true + } + }, + { + factory = adapter + args = { + factory.name = support.null-audio-sink + node.name = "system-audio" + node.description = "System Audio" + node.passive = true + node.virtual = true + media.class = Audio/Sink + audio.position = [ FL FR ] + monitor.channel-volumes = true + monitor.passthrough = true + } + }, + { + factory = adapter + args = { + factory.name = support.null-audio-sink + node.name = "microphone" + node.description = "Microphone" + node.passive = true + media.class = Audio/Source/Virtual + audio.position = [ FL FR ] + monitor.channel-volumes = true + monitor.passthrough = true + } + } +] diff --git a/scripts/.local/bin/mute_toggle.sh b/scripts/.local/bin/mute_toggle.sh new file mode 100755 index 0000000..23d446c --- /dev/null +++ b/scripts/.local/bin/mute_toggle.sh @@ -0,0 +1,203 @@ +#!/bin/bash +set -euo pipefail # Exit on error, undefined variable, or pipe failure + +# Set STATE_FILE according to XDG Base Directory Specification +XDG_CACHE_HOME="${XDG_CACHE_HOME:-$HOME/.cache}" +readonly STATE_DIR="$XDG_CACHE_HOME/mute_toggle" +readonly STATE_FILE="$STATE_DIR/muted_sources" + +# Store command paths or empty strings if not found +WPCTL_CMD="" +NOTIFY_SEND_CMD="" +CANBERRA_GTK_PLAY_CMD="" + +# Function to play KDE notification sound +play_sound() { + local event="$1" + if [[ -n "$CANBERRA_GTK_PLAY_CMD" ]]; then + "$CANBERRA_GTK_PLAY_CMD" -i "$event" --description="$event" --volume -10 2>/dev/null & disown + fi +} + +# Function to send desktop notification and play sound +# Arguments: +# $1: Status message part (e.g., "Muted", "Unmuted") +# $2: Icon name +# $3: Sound event name +notify_user() { + local status_msg="$1" + local icon_name="$2" + local sound_event="$3" + + play_sound "$sound_event" + + if [[ -n "$NOTIFY_SEND_CMD" ]]; then + "$NOTIFY_SEND_CMD" -a "MicrophoneToggle" -i "$icon_name" \ + "Microphone" "Microphone is $status_msg" -t 500 + fi +} + +# Function to check for required commands +check_requirements() { + if command -v wpctl &>/dev/null; then + WPCTL_CMD="wpctl" + else + echo "Error: wpctl not found. Please install WirePlumber (package wireplumber)." >&2 + exit 1 + fi + + if command -v notify-send &>/dev/null; then + NOTIFY_SEND_CMD="notify-send" + else + echo "Warning: notify-send not found. Desktop notifications will be skipped." >&2 + fi + + if command -v canberra-gtk-play &>/dev/null; then + CANBERRA_GTK_PLAY_CMD="canberra-gtk-play" + else + echo "Warning: canberra-gtk-play not found. Notification sounds will be skipped." >&2 + fi +} + +# Function to extract audio source IDs and their mute states +# Output: Lines of " <0_or_1_for_muted_status>" +extract_sources() { + # The awk script: + # 1. /Audio/,/Video/: Operates only within the 'Audio' section up to 'Video'. + # 2. If /Sources:/: Sets flag 's=1' indicating we are in the Sources sub-section. + # 3. Else if /Filters:|Sinks:|Streams:/: Sets 's=0' if another sub-section starts. + # This handles cases where Sources is not the last sub-section. + # 4. Else if (s && match(...)): If in Sources sub-section and line matches pattern for a source: + # - Extracts ID. + # - Prints ID and 1 if "MUTED]" is found, 0 otherwise. + "$WPCTL_CMD" status 2>/dev/null | awk ' + /Audio/,/Video/ { + if ($0 ~ /Sources:/) s=1 + else if ($0 ~ /Filters:|Sinks:|Streams:/) s=0 + else if (s && match($0, /^[[:space:]]*│[[:space:]]*[* ]?[[:space:]]*([0-9]+)\. (.*)/, a)) { + id = a[1] + print id, ($0 ~ /MUTED\]/ ? 1 : 0) # Check for "MUTED]" for more specificity + } + }' +} + +main() { + local FORCE_MODE=false + if [[ "$#" -gt 0 && "$1" == "--force" ]]; then + FORCE_MODE=true + shift # Consume the --force argument + echo "Running in --force mode: will not save/use saved state." + fi + + check_requirements + + # Ensure state file directory exists (only if we might use it) + if ! "$FORCE_MODE"; then + mkdir -p "$STATE_DIR" || { echo "Error: Cannot create directory for $STATE_DIR" >&2; exit 1; } + fi + + declare -A current_source_states # Stores ID -> current_mute_status (0=unmuted, 1=muted) + declare -A ids_muted_by_script # Stores IDs of sources this script muted previously (from STATE_FILE) + + local -a ids_to_mute_now=() # Array of UNMUTED source IDs to be muted + local -a ids_to_unmute_based_on_script_state=() # Array of MUTED source IDs (that script muted) to be unmuted + local -a ids_currently_muted=() # Array of ALL currently MUTED source IDs (used by --force) + + # Step 1: Load IDs of sources previously muted by this script (if not in force mode) + if ! "$FORCE_MODE"; then + if [[ -f "$STATE_FILE" ]]; then + while IFS= read -r id_from_file || [[ -n "$id_from_file" ]]; do # Handle last line without newline + if [[ "$id_from_file" =~ ^[0-9]+$ ]]; then # Basic validation + ids_muted_by_script["$id_from_file"]=1 + fi + done < "$STATE_FILE" + fi + fi + + # Step 2: Get current state of all audio sources + local no_sources_found=true + while IFS=' ' read -r id muted_status; do + no_sources_found=false + current_source_states["$id"]=$muted_status + if [[ "$muted_status" -eq 0 ]]; then # If currently unmuted + ids_to_mute_now+=("$id") + else # If currently muted + ids_currently_muted+=("$id") # Store all muted IDs for --force unmute + # If not in force mode, and it was muted by this script + if ! "$FORCE_MODE" && [[ "${ids_muted_by_script[$id]+exists}" ]]; then + ids_to_unmute_based_on_script_state+=("$id") + fi + fi + done < <(extract_sources) + + if "$no_sources_found"; then + echo "No active audio sources found." + notify_user "Unavailable (No Sources)" "dialog-error" "dialog-error" # Or some other icon/sound + exit 0 + fi + + # Step 3: Determine action based on force mode or state + local target_ids=() + local action_type="" # "mute" or "unmute" + + if "$FORCE_MODE"; then + if [[ ${#ids_to_mute_now[@]} -gt 0 ]]; then # If any sources are unmuted, force mute + target_ids=("${ids_to_mute_now[@]}") + action_type="mute" + elif [[ ${#ids_currently_muted[@]} -gt 0 ]]; then # If all are muted, force unmute + target_ids=("${ids_currently_muted[@]}") + action_type="unmute" + fi + else # Not force mode, use stateful logic + if [[ ${#ids_to_mute_now[@]} -gt 0 ]]; then # If any sources are unmuted, mute them all + target_ids=("${ids_to_mute_now[@]}") + action_type="mute" + elif [[ ${#ids_to_unmute_based_on_script_state[@]} -gt 0 ]]; then # If all are muted, and some by script, unmute those + target_ids=("${ids_to_unmute_based_on_script_state[@]}") + action_type="unmute" + fi + fi + + # Step 4: Perform the determined action + if [[ "$action_type" == "mute" ]]; then + echo "Muting sources: ${target_ids[*]}" + if ! "$FORCE_MODE"; then # Clear state file if not in force mode + >"$STATE_FILE" || { echo "Error: Cannot clear $STATE_FILE" >&2; exit 1; } + fi + for id in "${target_ids[@]}"; do + if ! "$WPCTL_CMD" set-mute "$id" 1; then + echo "Warning: Failed to mute source $id. Continuing..." >&2 + fi + if ! "$FORCE_MODE"; then # Add to state file if not in force mode + echo "$id" >> "$STATE_FILE" + fi + done + notify_user "Muted" "microphone-sensitivity-muted" "dialog-warning" + elif [[ "$action_type" == "unmute" ]]; then + echo "Unmuting sources: ${target_ids[*]}" + for id in "${target_ids[@]}"; do + if ! "$WPCTL_CMD" set-mute "$id" 0; then + echo "Warning: Failed to unmute source $id. Continuing..." >&2 + fi + done + if ! "$FORCE_MODE"; then # Remove state file if not in force mode + rm -f "$STATE_FILE" || { echo "Error: Cannot remove $STATE_FILE" >&2; exit 1; } + fi + notify_user "Unmuted" "microphone-sensitivity-high" "dialog-information" + else + # This case means: + # 1. No sources are unmuted. + # 2. AND (if not force mode) no sources currently muted were previously muted by this script. + # This can happen if all sources are manually muted, or no relevant action was determined. + echo "No microphone status change needed." + if [[ -f "$STATE_FILE" ]]; then + echo "(State file $STATE_FILE exists, but relevant sources are not currently active or already handled)." + fi + notify_user "Unchanged" "dialog-information" "dialog-information" + fi +} + +# Ensure script is not sourced +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + main "$@" +fi diff --git a/zsh/.zshrc b/zsh/.zshrc new file mode 100644 index 0000000..c69fc53 --- /dev/null +++ b/zsh/.zshrc @@ -0,0 +1,95 @@ +### Added by Zinit's installer +if [[ ! -f $HOME/.local/share/zinit/zinit.git/zinit.zsh ]]; then + print -P "%F{33} %F{220}Installing %F{33}ZDHARMA-CONTINUUM%F{220} Initiative Plugin Manager (%F{33}zdharma-continuum/zinit%F{220})…%f" + command mkdir -p "$HOME/.local/share/zinit" && command chmod g-rwX "$HOME/.local/share/zinit" + command git clone https://github.com/zdharma-continuum/zinit "$HOME/.local/share/zinit/zinit.git" && \ + print -P "%F{33} %F{34}Installation successful.%f%b" || \ + print -P "%F{160} The clone has failed.%f%b" +fi + +source "$HOME/.local/share/zinit/zinit.git/zinit.zsh" +autoload -Uz _zinit +(( ${+_comps} )) && _comps[zinit]=_zinit + +# Load a few important annexes, without Turbo +# (this is currently required for annexes) +zinit light-mode for \ + zdharma-continuum/zinit-annex-as-monitor \ + zdharma-continuum/zinit-annex-bin-gem-node \ + zdharma-continuum/zinit-annex-patch-dl \ + zdharma-continuum/zinit-annex-rust + +### End of Zinit's installer chunk + +zinit light zsh-users/zsh-completions +zinit light zsh-users/zsh-syntax-highlighting +zinit light zsh-users/zsh-autosuggestions +zinit light zsh-users/zsh-history-substring-search +zinit light Aloxaf/fzf-tab +zinit light hlissner/zsh-autopair + +zinit snippet OMZL::git.zsh +zinit snippet OMZP::git +zinit snippet OMZP::sudo +zinit snippet OMZP::archlinux +zinit snippet OMZP::command-not-found + +autoload -Uz compinit && compinit + +zinit cdreplay -q + +# History +HISTSIZE=5000 +HISTFILE=~/.zsh_history +SAVEHIST=$HISTSIZE +HISTDUP=erase +setopt APPEND_HISTORY +setopt SHARE_HISTORY +setopt HIST_IGNORE_SPACE +setopt HIST_SAVE_NO_DUPS +setopt HIST_FIND_NO_DUPS +setopt HIST_IGNORE_DUPS +setopt HIST_IGNORE_ALL_DUPS + +alias cd='z' +alias c='clear' +alias ls='eza --color=always' +alias la='eza -laghm@ --all --icons --git --color=always' + +export WORDCHARS="${WORDCHARS//-}" +export WORDCHARS="${WORDCHARS//\/}" + +export FZF_DEFAULT_COMMAND="fd --hidden --strip-cwd-prefix --exclude .git" +export FZF_CTRL_T_COMMAND="$FZF_DEFAULT_COMMAND" +export FZF_ALT_C_COMMAND="fd --type=d --hidden --strip-cwd-prefix --exclude .git" + +_fzf_compgen_path() { + fd --hidden --exclude .git . "$1" +} + +show_file_or_dir_preview="if [ -d {} ]; then eza --tree --color=always {} | head -200; else bat -n --color=always --line-range :500 {}; fi" + +export FZF_CTRL_T_OPTS="--preview '$show_file_or_dir_preview'" +export FZF_ALT_C_OPTS="--preview 'eza --tree --color=always {} | head -200'" + +_fzf_comprun() { + local command=$1 + shift + + case "$command" in + cd) fzf --preview 'eza --tree --color=always {} | head -200' "$@" ;; + export|unset) fzf --preview "eval 'echo \${}'" "$@" ;; + ssh) fzf --preview 'dig {}' "$@" ;; + *) fzf --preview "$show_file_or_dir_preview" "$@" ;; + esac +} + +bindkey "^[[A" history-substring-search-up # Up +bindkey "^[[B" history-substring-search-down # Down +bindkey "^[[1;5C" forward-word +bindkey "^[[1;5D" backward-word +bindkey '^H' backward-kill-word + +eval "$(zoxide init zsh)" +eval "$(starship init zsh)" +eval "$(fzf --zsh)"