From dfa8a20e2dfca9370b78fc8743fd8c38049708e1 Mon Sep 17 00:00:00 2001 From: Alexandre CHAZAL Date: Tue, 17 Aug 2021 14:11:27 +0200 Subject: [PATCH] init: repo creation --- .config/i3/config | 177 ++++++ .config/i3/volume | 1273 ++++++++++++++++++++++++++++++++++++++ .config/picom/picom.conf | 49 ++ .config/rofi/config | 1 + 4 files changed, 1500 insertions(+) create mode 100644 .config/i3/config create mode 100755 .config/i3/volume create mode 100755 .config/picom/picom.conf create mode 100755 .config/rofi/config diff --git a/.config/i3/config b/.config/i3/config new file mode 100644 index 0000000..9ed7236 --- /dev/null +++ b/.config/i3/config @@ -0,0 +1,177 @@ +#------------------------------# +# Config by Alexandre CHAZAL # +# Also known as AlxCzl or Tyk0 # +#------------------------------# + +### Modifier +set $mod Mod4 + +### Execs +exec --no-startup-id nm-applet +#exec_always --no-startup-id $HOME/.config/polybar/launch.sh +#exec --no-startup-id feh --no-fehbg --bg-scale $HOME/Images/forbiddencity.jpg +exec --no-startup-id nitrogen --restore +exec --no-startup-id setxkbmap us +exec --no-startup-id xrdb ~/.Xresources + + +### i3 config +# Gaps and borders +hide_edge_borders both +for_window [class="^.*"] border pixel 2 +gaps inner 8 +gaps outer 5 +client.focused #555555 #555555 #ffffff #2e9ef4 #017515 + +# Font +font pango:monospace 9 + +# Floating modifier +floating_modifier $mod + +### Bindings +# Lock screen +bindsym Ctrl+Mod1+l exec --no-startup-id betterlockscreen -l blur + +# start a terminal +bindsym $mod+Return exec --no-startup-id st + +# kill focused window +bindsym $mod+Shift+q kill + +# start dmenu (a program launcher) +bindsym $mod+d exec --no-startup-id rofi -font "Hack 14" -show-icons -show drun + +# change focus +bindsym $mod+j focus left +bindsym $mod+k focus down +bindsym $mod+l focus up +bindsym $mod+semicolon focus right + +# alternatively, you can use the cursor keys: +bindsym $mod+Left focus left +bindsym $mod+Down focus down +bindsym $mod+Up focus up +bindsym $mod+Right focus right + +# move focused window +bindsym $mod+Shift+j move left +bindsym $mod+Shift+k move down +bindsym $mod+Shift+l move up +bindsym $mod+Shift+semicolon move right + +# alternatively, you can use the cursor keys: +bindsym $mod+Shift+Left move left +bindsym $mod+Shift+Down move down +bindsym $mod+Shift+Up move up +bindsym $mod+Shift+Right move right + +# split in horizontal orientation +bindsym $mod+h split h + +# split in vertical orientation +bindsym $mod+v split v + +# enter fullscreen mode for the focused container +bindsym $mod+f fullscreen toggle + +# change container layout (stacked, tabbed, toggle split) +bindsym $mod+s layout stacking +bindsym $mod+w layout tabbed +bindsym $mod+e layout toggle split + +# toggle tiling / floating +bindsym $mod+Shift+space floating toggle + +# change focus between tiling / floating windows +bindsym $mod+space focus mode_toggle + +# focus the parent container +bindsym $mod+a focus parent + +# focus the child container +#bindsym $mod+d focus child + +## Workspaces +# Names +set $ws1 "1" +set $ws2 "2" +set $ws3 "3" +set $ws4 "4" +set $ws5 "5" +set $ws6 "6" +set $ws7 "7" +set $ws8 "8" +set $ws9 "9" +set $ws10 "10" + +# Change workspace +bindsym $mod+1 workspace $ws1 +bindsym $mod+2 workspace $ws2 +bindsym $mod+3 workspace $ws3 +bindsym $mod+4 workspace $ws4 +bindsym $mod+5 workspace $ws5 +bindsym $mod+6 workspace $ws6 +bindsym $mod+7 workspace $ws7 +bindsym $mod+8 workspace $ws8 +bindsym $mod+9 workspace $ws9 +bindsym $mod+0 workspace $ws10 + +# Move program to workspace +bindsym $mod+Shift+1 move container to workspace $ws1 +bindsym $mod+Shift+2 move container to workspace $ws2 +bindsym $mod+Shift+3 move container to workspace $ws3 +bindsym $mod+Shift+4 move container to workspace $ws4 +bindsym $mod+Shift+5 move container to workspace $ws5 +bindsym $mod+Shift+6 move container to workspace $ws6 +bindsym $mod+Shift+7 move container to workspace $ws7 +bindsym $mod+Shift+8 move container to workspace $ws8 +bindsym $mod+Shift+9 move container to workspace $ws9 +bindsym $mod+Shift+0 move container to workspace $ws10 + +# Reload config +bindsym $mod+Shift+c reload +# Reload i3 +bindsym $mod+Shift+r restart + +# Resize mode +mode "resize" { + bindsym j resize shrink width 10 px or 10 ppt + bindsym k resize grow height 10 px or 10 ppt + bindsym l resize shrink height 10 px or 10 ppt + bindsym semicolon resize grow width 10 px or 10 ppt + + bindsym Left resize shrink width 10 px or 10 ppt + bindsym Down resize grow height 10 px or 10 ppt + bindsym Up resize shrink height 10 px or 10 ppt + bindsym Right resize grow width 10 px or 10 ppt + + bindsym Return mode "default" + bindsym Escape mode "default" + bindsym $mod+r mode "default" +} + +bindsym $mod+r mode "resize" + +## Status bar +bar { + status_command i3blocks + position top +} +## Volume +bindsym XF86AudioRaiseVolume exec --no-startup-id pactl set-sink-volume 0 +5% +bindsym XF86AudioLowerVolume exec --no-startup-id pactl set-sink-volume 0 -5% +bindsym XF86AudioMute exec --no-startup-id pactl set-sink-mute 0 toggle + +## Brightness +bindsym XF86MonBrightnessUp exec --no-startup-id xbacklight -inc 10 +bindsym XF86MonBrightnessDown exec --no-startup-id xbacklight -dec 10 + +## Power button +bindsym XF86PowerOff exec --no-startup-id /home/alex/.bin/pmenu + +## Screenshots +# scrot & gimp - root +bindsym Print exec scrot -e 'mv $f /tmp/ && gimp /tmp/$f' +# scrot & gimp - select window or rectangle +bindsym Mod1+Print exec scrot -s -e 'mv $f /tmp/ && gimp /tmp/$f' diff --git a/.config/i3/volume b/.config/i3/volume new file mode 100755 index 0000000..4593733 --- /dev/null +++ b/.config/i3/volume @@ -0,0 +1,1273 @@ +#!/bin/bash +# +# i3-volume +# +# Volume control and volume notifications. +# +# Dependencies: +# awk (POSIX compatible) +# pulseaudio-utils - if using PulseAudio +# alsa-utils - if using amixer +# +# Copyright (c) 2016 Beau Hastings. All rights reserved. +# License: GNU General Public License v2 +# +# Author: Beau Hastings +# URL: https://github.com/hastinbe/i3-volume +# Wiki: https://github.com/hastinbe/i3-volume/wiki/ + +define_helpers() { + empty() { + [[ -z $1 ]] + } + + not_empty() { + [[ -n $1 ]] + } + + isset() { + [[ -v $1 ]] + } + + command_exists() { + command -v "$1" >/dev/null 2>&1; + } + + error() { + echo "$COLOR_RED$*$COLOR_RESET" + } + + has_color() { + (( $(tput colors 2>/dev/null || echo 0) >= 8 )) && [ -t 1 ] + } + + # Converts milliseconds to seconds with rounding up + # + # Arguments: + # milliseconds (integer) An integer in milliseconds + ms_to_secs() { + echo $(( ($1 + (1000 - 1)) / 1000 )) + } + + is_command_hookable() { + ! [[ ${POST_HOOK_EXEMPT_COMMANDS[*]} =~ $1 ]] + } + + has_capability() { + [[ "${NOTIFY_CAPS[*]}" =~ $1 ]] + } + + max() { + echo $(( $1 > $2 ? $1 : $2 )) + } +} + +define_notify() { + # Display a notification indicating muted or current volume. + notify_volume() { + local -r vol=$(get_volume) + local icon + + if is_muted; then + text="Volume muted" + + if $USE_FULLCOLOR_ICONS; then + icon=${ICONS[0]} + else + icon=${ICONS_SYMBOLIC[0]} + fi + else + printf -v text "Volume %3s%%" "$vol" + + icon=$(get_volume_icon "$vol") + + if $SHOW_VOLUME_PROGRESS; then + local -r progress=$(progress_bar "$vol") + text="$text $progress" + fi + fi + + case "$NOTIFICATION_METHOD" in + xosd ) notify_volume_xosd "$vol" "$text" ;; + herbe ) notify_volume_herbe "$text" ;; + volnoti ) notify_volume_volnoti "$vol" ;; + kosd ) notify_volume_kosd "$vol" ;; + dunst ) notify_volume_libnotify "$vol" "$icon" "$text" ;; + notify-osd ) notify_volume_libnotify "$vol" "$icon" "$text" ;; + libnotify ) notify_volume_libnotify "$vol" "$icon" "$text" ;; + haskell-notification-daemon) notify_volume_libnotify "$vol" "$icon" "$text" ;; + * ) notify_volume_libnotify "$vol" "$icon" "$text" ;; + esac + } + + list_notification_methods() { + awk -W posix 'match($0,/ notify_volume_([[:alnum:]]+)/) {print substr($0, 19, RLENGTH-18)}' "${BASH_SOURCE[0]}" || exit "$EX_USAGE" + exit "$EX_OK" + } + + setup_notification_icons() { + if not_empty "$SYMBOLIC_ICON_SUFFIX"; then + apply_symbolic_icon_suffix + fi + } + + show_volume_notification() { + $DISPLAY_NOTIFICATIONS || return + + if empty "$NOTIFICATION_METHOD"; then + load_notify_server_info + NOTIFICATION_METHOD=$NOTIFY_SERVER + fi + + setup_notification_icons + notify_volume + } + + # Loads notification system information via DBus + load_notify_server_info() { + command_exists dbus-send || return + IFS=$'\t' read -r NOTIFY_SERVER _ _ _ < <(dbus-send --print-reply --dest=org.freedesktop.Notifications /org/freedesktop/Notifications org.freedesktop.Notifications.GetServerInformation | awk 'BEGIN { ORS="\t" }; match($0, /^ string ".*"/) {print substr($0, RSTART+11, RLENGTH-12)}') + } + + # Load notification system capabilities via DBus + load_notify_server_caps() { + command_exists dbus-send || return + IFS= read -r -d '' -a NOTIFY_CAPS < <(dbus-send --print-reply=literal --dest="${DBUS_NAME}" "${DBUS_PATH}" "${DBUS_IFAC_FDN}.GetCapabilities" | awk 'RS=" " { if (NR > 2) print $1 }') + } + + # Send notifcation for libnotify-compatible notification daemons. + # + # Arguments: + # Volume (integer) An integer indicating the volume. + # Icon (string) Icon to display. + # Text (string) Notification text. + notify_volume_libnotify() { + local -r vol=$1 + local -r icon=$2 + local -r text=${*:3} + local -a args=( + -t "$EXPIRES" + ) + local -a hints=( + # Replaces previous notification in some notification servers + string:synchronous:volume + # Replaces previous notification in NotifyOSD + string:x-canonical-private-synchronous:volume + ) + + # If we're not drawing our own progress bar, allow the notification daemon to draw its own (if supported) + if ! $SHOW_VOLUME_PROGRESS; then + hints+=(int:value:"$vol") + fi + + (( ${#NOTIFY_CAPS[@]} < 1 )) && load_notify_server_caps + + if has_capability icon-static; then + args+=(-i "$icon") + + # haskell-notification-daemon (aka deadd-notification-center) does not support -i|--icon + hints+=(string:image-path:"$icon") + fi + + if $PLAY_SOUND && has_capability sound; then + hints+=(string:sound-name:audio-volume-change) + fi + + if ! isset NO_NOTIFY_COLOR && [[ $NOTIFICATION_METHOD == "dunst" ]]; then + if is_muted; then + hints+=(string:fgcolor:"$COLOR_MUTED") + else + hints+=(string:fgcolor:"$(volume_color "$vol")") + fi + fi + + if $USE_DUNSTIFY; then + args+=(-r 1000) + + # Transient notifications will bypass the idle_threshold setting. + # Should be boolean, but Notify-OSD doesn't support boolean yet. Dunst checks + # for int and bool with transient so lets play nice with both servers. + hints+=(int:transient:1) + + read -ra hints <<< "${hints[@]/#/-h }" + "${DUNSTIFY_PATH:+${DUNSTIFY_PATH%/}/}dunstify" "${hints[@]}" "${args[@]}" "$text" + elif isset USE_NOTIFY_SEND_PY; then + # Replaces previous notification, but leaves itself running in the bg to work + args+=(--replaces-process volume) + + # By-pass the server's persistence capability, if it should exist + hints+=(boolean:transient:true) + + "${NOTIFY_SEND_PATH:+${NOTIFY_SEND_PATH%/}/}notify-send.py" --hint "${hints[@]}" "${args[@]}" "$text" & + else + read -ra hints <<< "${hints[@]/#/-h }" + "${NOTIFY_SEND_PATH:+${NOTIFY_SEND_PATH%/}/}notify-send" "${hints[@]}" "${args[@]}" "$text" + fi + } + + # Send notification to XOSD. + # + # Arguments: + # Volume (integer) An integer indicating the volume. + # Text (string) Notification text. + notify_volume_xosd() { + local -r vol=$1 + local -r text=${*:2} + local -r delay=$(ms_to_secs "$EXPIRES") + local percentage + + if is_muted; then + color=$COLOR_MUTED + percentage=0 + else + color=$(volume_color "$vol") + percentage=$vol + fi + + "${XOSD_PATH:+${XOSD_PATH%/}/}osd_cat" --align center -b percentage -P "$percentage" -d "$delay" -p top -A center -c "$color" -T "$text" -O 2 -u "$COLOR_XOSD_OUTLINE" & disown + } + + # Send notification to herbe. + # + # Arguments: + # Text (string) Notification text. + # + # Note: a patch with a notify-send script for herbe, not in the current version at this + # time but would make this irrelevant. See https://github.com/dudik/herbe/pull/10 + notify_volume_herbe() { + local -r text=$* + + # Dismiss existing/pending notifications to prevent queuing + pkill -SIGUSR1 herbe + + "${HERBE_PATH:+${HERBE_PATH%/}/}herbe" "$text" & disown + } + + # Send notification to volnoti. + # + # Arguments: + # Volume (integer) An integer indicating the volume. + notify_volume_volnoti() { + local -r vol=$1 + + if is_muted; then + "${VOLNOTI_PATH:+${VOLNOTI_PATH%/}/}volnoti-show" -m "$vol" + else + "${VOLNOTI_PATH:+${VOLNOTI_PATH%/}/}volnoti-show" "$vol" + fi + } + + # Send notification to KOSD. + # + # Arguments: + # Volume (integer) An integer indicating the volume. + notify_volume_kosd() { + local -r vol=$1 + + if is_muted; then + qdbus org.kde.kded /modules/kosd showVolume "$vol" 1 + else + qdbus org.kde.kded /modules/kosd showVolume "$vol" 0 + fi + } +} + +define_output_formats() { + # Outputs the current volume in the default format. + output_volume_default() { + if is_muted; then + echo MUTE + else + echo "$(get_volume)%" + fi + } + + # Outputs the current volume using a custom format string. + # + # Format options: + # %v = volume percentage or "MUTE" when muted + # %s = sink name (PulseAudio only) + # %c = card (alsamixer only) + # %m = mixer (alsamixer only) + # %p = volume progress bar + # %i = volume icon + output_volume_custom() { + local -r format=$* + local -r vol=$(get_volume) + local string + + if is_muted; then + string=${format//\%v/MUTE} + else + string=${format//\%v/$vol%} + fi + + string=${string//\%s/$SINK} + string=${string//\%c/$CARD} + string=${string//\%m/$MIXER} + string=${string//\%p/$(progress_bar "$vol")} + string=${string//\%i/$(get_volume_emoji "$vol")} + + echo -ne "$string" + } + + # Outputs the current volume for i3blocks. + output_volume_i3blocks() { + local short_text + local full_text + + if is_muted; then + short_text="MUTE\n" + full_text="MUTE\n" + else + local -r vol=$(get_volume) + local -r color=$(volume_color "$vol") + + short_text="${vol}%\n" + full_text="${vol}%\n" + + if isset MAX_VOLUME && (( vol > MAX_VOLUME )); then + EXITCODE=$EX_URGENT + fi + fi + + echo -ne "$full_text$short_text" + } + + # Outputs the current volume for xob. + output_volume_xob() { + local -ir vol=$(get_volume) + + if is_muted; then + echo "${vol}!" + else + echo "$vol" + fi + } +} + +define_commands() { + # Increase volume relative to current volume. + # + # Arguments: + # Step (integer) Percentage to increase by. + # Max Volume (optional) (integer|percentage) Maximum volume limit. + increase_volume() { + local step=${1:?$(error 'Step is required')} + local -r max_volume=$2 + + if not_empty "$max_volume"; then + local -r vol=$(get_volume) + + if (( vol + step > max_volume )); then + # Instead of doing nothing, step to max_volume + step=$( max "0" "$(( max_volume - vol ))" ) + fi + fi + + if $USE_AMIXER; then + amixer_increase_volume "$CARD" "$step" + else + pa_increase_volume "$SINK" "$step" + fi + } + + # Decrease volume relative to current volume. + # + # Arguments: + # Step (integer) Percentage to decrease by. + decrease_volume() { + local -r step=${1:?$(error 'Step is required')} + + if $USE_AMIXER; then + amixer_decrease_volume "$CARD" "$step" + else + pa_decrease_volume "$SINK" "$step" + fi + } + + # Set volume. + # + # Arguments: + # Volume (integer|linear factor|percentage|decibel) + # Max Volume (optional) (integer|percentage) Maximum volume limit. + set_volume() { + local -r vol=${1:?$(error 'Volume is required')} + local -r max_volume=$2 + + if not_empty "$max_volume" && (( vol > max_volume )); then + return + fi + + if $USE_AMIXER; then + amixer_set_volume "${vol}%" "$CARD" + else + pa_set_volume "$SINK" "${vol}%" + fi + } + + toggle_mute() { + if $USE_AMIXER; then + amixer_toggle_mute "$CARD" + else + pa_toggle_mute "$SINK" + fi + } + + # Outputs the current volume. + # + # Arguments + # Output method (string) Method to use to output volume. + output_volume() { + local -r for=${1:?$(error 'Output method is required')} + + case "$for" in + i3blocks ) output_volume_i3blocks ;; + xob ) output_volume_xob ;; + default ) output_volume_default ;; + * ) output_volume_custom "$*" ;; + esac + } + + list_output_formats() { + awk -W posix 'match($0,/ output_volume_([[:alnum:]]+)/) {print substr($0, 19, RLENGTH-18)}' "${BASH_SOURCE[0]}" || exit "$EX_USAGE" + exit "$EX_OK" + } + + usage() { + cat <<- EOF 1>&2 +${COLOR_YELLOW}Usage:${COLOR_RESET} $0 [] [] +Control volume and related notifications. + +${COLOR_YELLOW}Commands:${COLOR_RESET} + ${COLOR_GREEN}up ${COLOR_RESET} increase volume + ${COLOR_GREEN}down ${COLOR_RESET} decrease volume + ${COLOR_GREEN}set ${COLOR_RESET} set volume + ${COLOR_GREEN}mute${COLOR_RESET} toggle mute + ${COLOR_GREEN}listen${COLOR_RESET} listen for changes to a PulseAudio sink + ${COLOR_GREEN}output ${COLOR_RESET} output volume in a supported format + custom format substitutions: + %v = volume + %s = sink name (PulseAudio only) + %c = card (alsamixer only) + %m = mixer (alsamixer only) + %p = volume progress bar + %i = volume icon/emoji + + examples: + "Volume is %v" = Volume is 50% + "%i %v %p \n" = 奔 50% ██████████ + ${COLOR_GREEN}outputs${COLOR_RESET} show available output formats + ${COLOR_GREEN}notifications${COLOR_RESET} show available notification methods + ${COLOR_GREEN}help${COLOR_RESET} display help + +${COLOR_YELLOW}Options:${COLOR_RESET} + ${COLOR_GREEN}-a${COLOR_RESET} use amixer + ${COLOR_GREEN}-n${COLOR_RESET} enable notifications + ${COLOR_GREEN}-C${COLOR_RESET} use libcanberra for playing event sounds + ${COLOR_GREEN}-P${COLOR_RESET} play sound for volume changes + ${COLOR_GREEN}-j ${COLOR_RESET} specify custom volume emojis as a comma separated list + ${COLOR_GREEN}-t ${COLOR_RESET} process name of status bar (${COLOR_MAGENTA}requires -u${COLOR_RESET}) + ${COLOR_GREEN}-u ${COLOR_RESET} signal to update status bar (${COLOR_MAGENTA}requires -t${COLOR_RESET}) + ${COLOR_GREEN}-x ${COLOR_RESET} maximum volume + ${COLOR_GREEN}-X ${COLOR_RESET} maximum amplification; if supported (${COLOR_MAGENTA}default: 2${COLOR_RESET}) + ${COLOR_GREEN}-h${COLOR_RESET} display help + +${COLOR_YELLOW}amixer Options:${COLOR_RESET} + ${COLOR_GREEN}-c ${COLOR_RESET} card number to control + ${COLOR_GREEN}-m ${COLOR_RESET} set mixer (${COLOR_MAGENTA}default: Master${COLOR_RESET}) + +${COLOR_YELLOW}PulseAudio Options:${COLOR_RESET} + ${COLOR_GREEN}-s ${COLOR_RESET} symbolic name of sink + +${COLOR_YELLOW}Notification Options:${COLOR_RESET} + ${COLOR_GREEN}-N ${COLOR_RESET} notification method (${COLOR_MAGENTA}default: libnotify${COLOR_RESET}) + ${COLOR_GREEN}-p${COLOR_RESET} enable progress bar + ${COLOR_GREEN}-e ${COLOR_RESET} expiration time of notifications in ms + ${COLOR_GREEN}-l${COLOR_RESET} use fullcolor instead of symbolic icons + ${COLOR_GREEN}-S ${COLOR_RESET} append suffix to symbolic icon names + ${COLOR_GREEN}-y${COLOR_RESET} use dunstify (${COLOR_MAGENTA}default: notify-send${COLOR_RESET}) + +${COLOR_YELLOW}Environment Variables:${COLOR_RESET} + ${COLOR_CYAN}XOSD_PATH${COLOR_RESET} path to osd_cat + ${COLOR_CYAN}HERBE_PATH${COLOR_RESET} path to herbe + ${COLOR_CYAN}VOLNOTI_PATH${COLOR_RESET} path to volnoti-show + ${COLOR_CYAN}DUNSTIFY_PATH${COLOR_RESET} path to dunstify + ${COLOR_CYAN}CANBERRA_PATH${COLOR_RESET} path to canberra-gtk-play + ${COLOR_CYAN}NOTIFY_SEND_PATH${COLOR_RESET} path to notify-send or notify-send.py + ${COLOR_CYAN}USE_NOTIFY_SEND_PY${COLOR_RESET} flag to use notify-send.py instead of notify-send + ${COLOR_CYAN}NO_NOTIFY_COLOR${COLOR_RESET} flag to disable colors in notifications +EOF + exit "$EX_USAGE" + } +} + +# Get the volume as a percentage. +get_volume() { + if $USE_AMIXER; then + amixer_get_volume "$CARD" "$MIXER" + else + pa_get_volume "$SINK" + fi +} + +is_muted() { + if $USE_AMIXER; then + amixer_is_muted "$CARD" + return $? + else + pa_is_muted "$SINK" + return $? + fi +} + +# Gets an icon for the provided volume. +# +# Arguments: +# Volume (integer) An integer indicating the volume. +# +# Returns: +# The volume icon name. +get_volume_icon() { + local -r vol=${1:?$(error 'Volume is required')} + local icon + + if $USE_FULLCOLOR_ICONS; then + if (( vol >= 70 )); then icon=${ICONS[1]} + elif (( vol >= 40 )); then icon=${ICONS[3]} + elif (( vol > 0 )); then icon=${ICONS[2]} + else icon=${ICONS[2]} + fi + else + # Get overamplified icon if available, otherwise default to high volume icon + if (( vol > 100 )); then icon=${ICONS_SYMBOLIC[4]:-${ICONS_SYMBOLIC[1]}} + elif (( vol >= 70 )); then icon=${ICONS_SYMBOLIC[1]} + elif (( vol >= 40 )); then icon=${ICONS_SYMBOLIC[3]} + elif (( vol > 0 )); then icon=${ICONS_SYMBOLIC[2]} + else icon=${ICONS_SYMBOLIC[2]} + fi + fi + + echo "$icon" +} + +# Gets an emoji for the provided volume. +# +# Arguments: +# Volume (integer) An integer indicating the volume. +# +# Returns: +# The volume emoji. +get_volume_emoji() { + local -r vol=${1:?$(error 'Volume is required')} + local icon + + if is_muted; then + icon=${ICONS_EMOJI[0]} + else + if (( vol >= 70 )); then icon=${ICONS_EMOJI[1]} + elif (( vol >= 40 )); then icon=${ICONS_EMOJI[3]} + elif (( vol > 0 )); then icon=${ICONS_EMOJI[2]} + else icon=${ICONS_EMOJI[2]} + fi + fi + + echo "$icon" +} + +# Updates the status line. +# +# Arguments: +# signal (string) The signal used to update the status line. +# proc (string) The name of the status line process. +update_statusline() { + local -r signal=${1:?$(error 'Signal is required')} + local -r proc=${2:?$(error 'Process name is required')} + + pkill "-$signal" "$proc" +} + +# Generates a progress bar for the provided value. +# +# Arguments: +# Percentage (integer) Percentage of progress. +# Maximum (integer) Maximum percentage. (default: 100) +# Divisor (integer) For calculating the ratio of blocks to progress (default: 5) +# +# Returns: +# The progress bar. +progress_bar() { + local -r percent=${1:?$(error 'Percentage is required')} + local -r max_percent=${2:-100} + local -r divisor=${3:-5} + local -r progress=$(( (percent > max_percent ? max_percent : percent) / divisor )) + + printf -v bar "%*s" $progress "" + echo "${bar// /█}" +} + +apply_symbolic_icon_suffix() { + for i in "${!ICONS_SYMBOLIC[@]}"; do + ICONS_SYMBOLIC[$i]="${ICONS_SYMBOLIC[$i]}${SYMBOLIC_ICON_SUFFIX}" + done +} + +# Get color for the given volume +# +# Arguments: +# $1 - The volume +volume_color() { + local -ir vol=${1:?$(error 'A volume is required')} + + if $USE_AMIXER; then + amixer_volume_color "$vol" + else + pa_volume_color "$vol" + fi +} + +# Updates the status bars +# +# Returns +# 0 when no problem occurred +# 1 when one $of signal or $statusline are set but not both +update_statusbar() { + if not_empty "$SIGNAL"; then + if empty "$STATUSLINE"; then + return 1 + fi + update_statusline "$SIGNAL" "$STATUSLINE" + else + if not_empty "$STATUSLINE"; then + return 1 + fi + fi + + return 0 +} + +setup_audio() { + if $USE_AMIXER; then + setup_amixer + else + setup_pulseaudio + fi +} + +# All PulseAudio functions are defined here +define_pulseaudio_functions() { + # Executes `pactl list sinks` or return its output if called previously + pa_list_sinks() { + if $OPT_LISTEN || empty "$PA_LIST_SINKS"; then + PA_LIST_SINKS=$(pactl list sinks) + fi + echo "$PA_LIST_SINKS" + } + + pa_invalidate_cache() { + unset PA_LIST_SINKS + } + + pa_default_sink_name() { + pactl info | awk -F': ' '/^Default Sink: /{print $2}' + } + + # Get the index of a sink name. + # + # Arguments + # Sink name (string) Symbolic name of sink. + pa_get_sink_index() { + local -r sink=${1:?$(error 'Sink name is required')} + + pa_list_sinks | \ + awk -W posix '/^Sink #/{gsub("#", ""); idx = $2} + /^[ \t]+Name: / {insink = $2 == "'"$sink"'"; if (insink) { print idx }; exit}' + } + + # Get the volume as a percentage. + # + # Arguments + # Sink name (string) Symbolic name of sink. + pa_get_volume() { + local -r sink=${1:?$(error 'Sink name is required')} + + pa_list_sinks | \ + awk -W posix '/^[ \t]+Name: / {insink = $2 == "'"$sink"'"} + /^[ \t]+Volume: / && insink {gsub("%,?", ""); print $5; exit}' + } + + # Get the max volume as a percentage. + # + # Arguments + # Sink name (string) Symbolic name of sink. + pa_get_base_volume() { + local -r sink=${1:?$(error 'Sink name is required')} + + pa_list_sinks | \ + awk -W posix '/^[ \t]+Name: / {insink = $2 == "'"$sink"'"} + /^[ \t]+Base Volume: / && insink {gsub("%", ""); print $5; exit}' + } + + # Increase volume relative to current volume using pulseaudio. + # + # Arguments: + # Sink name (string) Symbolic name of sink. + # Step (integer) Percentage to increase by. + pa_increase_volume() { + local -r sink=$1 + local -r step=${2:=-5} + + pa_set_volume "$sink" "+${step}%" + } + + # Decrease volume relative to current volume using pulseaudio. + # + # Arguments: + # Sink name (string) Symbolic name of sink. + # Step (integer|percentage) Percentage to decrease by. + pa_decrease_volume() { + local -r sink=$1 + local -r step=${2:=-5} + + pa_set_volume "$sink" "-${step}%" + } + + # Set volume using pulseaudio. + # + # Arguments: + # Sink name (string) Symbolic name of sink. + # Volume (integer|linear factor|percentage|decibel) + pa_set_volume() { + local -r sink=${1:?$(error 'Sink name is required')} + local -r vol=${2:?$(error 'Volume is required')} + + pa_invalidate_cache + + pactl set-sink-volume "$sink" "$vol" + } + + # Toggle mute using pulseaudio. + # + # Arguments: + # Sink name (string) Symbolic name of sink. + pa_toggle_mute() { + local -r sink=${1:?$(error 'Sink name is required')} + + pa_invalidate_cache + + pactl set-sink-mute "$sink" toggle + } + + # Check if sink is muted. + # + # Arguments: + # Sink name (string) Symbolic name of sink. + # + # Returns: + # 0 when true, 1 when false. + pa_is_muted() { + local -r sink=${1:?$(error 'Sink name is required')} + + pa_list_sinks | \ + awk -W posix '/^[ \t]+Name: / {insink = $2 == "'"$sink"'"} + /^[ \t]+Mute: / && insink && $2 ~ /^yes$/ { exitcode=1 }; END { exit !exitcode }' + } + + # Get the flags of the PulseAudio sink. + # + # Arguments + # Sink name (string) Symbolic name of sink. + pa_get_sink_flags() { + local -r sink=${1:?$(error 'Sink name is required')} + + pa_list_sinks | \ + awk -W posix '/^[ \t]+Name: / {insink = $2 == "'"$sink"'"} + /^[ \t]+Flags: / && insink { for(i = 2; i <= NF; ++i) printf $i FS; exit}' + } + + # Get color for the given volume for PulseAudio + # + # Arguments: + # $1 - The volume + pa_volume_color() { + local -ir vol=${1:?$(error 'A volume is required')} + + if (( vol >= PA_VOLUME_MUTED && vol < PA_BASE_VOLUME )); then + echo "$COLOR_MUTED_TO_BASE" + elif (( vol >= PA_BASE_VOLUME && vol <= PA_VOLUME_NORM )); then + echo "$COLOR_BASE_TO_NORM" + elif (( vol > PA_VOLUME_NORM && vol <= MAX_VOLUME )); then + echo "$COLOR_NORM_TO_MAX" + else + echo "$COLOR_OTHER" + fi + } + + # Listens for PulseAudio events + # + # Arguments: + # Output (optional) (string) An output mode. When set, outputs volume in the output mode format. + listen() { + local -r output=$* + local -r index=$(pa_get_sink_index "$SINK") + + # Output volume so statusbars have something to display before any event occurs + not_empty "$output" && output_volume "$output" + + while IFS= read -r; do + show_volume_notification + update_statusbar + play_volume_changed + not_empty "$output" && output_volume "$output" + done < <(pactl subscribe | stdbuf -oL grep -e "Event 'change' on sink #$index") + } + + # Play a sound file. + # + # Arguments: + # Sound file (string) + pa_play() { + local -r file=$1 + + paplay -d "$SINK" "$file" & + } +} + +# Register PulseAudio related functions and settings +setup_pulseaudio() { + define_pulseaudio_functions + + PA_LIST_SINKS=$(pactl list sinks) || exit 1 + + if empty "$SINK"; then + SINK="$(pa_default_sink_name)" + fi + + # Determine a max volume when it's not specified + if ! isset MAX_VOLUME; then + read -ra SINK_FLAGS < <(pa_get_sink_flags "$SINK") + PA_BASE_VOLUME=$(pa_get_base_volume "$SINK") + + # Does the sink support digital (software) amplification? + if [[ "${SINK_FLAGS[*]}" =~ "DECIBEL_VOLUME" ]]; then + MAX_VOLUME=$((PA_VOLUME_NORM * MAX_AMPLIFICATION)) + else + MAX_VOLUME=$PA_VOLUME_NORM + fi + fi +} + +# All amixer functions are defined here +define_amixer_functions() { + # Get the volume as a percentage. + # + # Arguments + # Card (integer) Card number to control. + # Mixer (string) Name of the mixer. + amixer_get_volume() { + local -r card=$1 + local -r mixer=${2:-Master} + + amixer ${card:+-c "$card" --} sget "$mixer" | \ + awk -W posix -F'[][]' '/dB/ { gsub("%", ""); print $2 }' + } + + # Increase volume relative to current volume using amixer. + # + # Arguments: + # Card (integer) Card number to control. + # Step (integer) Percentage to increase by. + amixer_increase_volume() { + local -r card=$1 + local -r step=${2:=-5} + + amixer_set_volume "${step}%+" "$card" + } + + # Decrease volume relative to current volume using amixer. + # + # Arguments: + # Card (integer) Card number to control. + # Step (integer) Percentage to decrease by. + amixer_decrease_volume() { + local -r card=$1 + local -r step=${2:=-5} + + amixer_set_volume "${step}%-" "$card" + } + + # Set volume using amixer. + # + # Arguments: + # Volume (integer|linear factor|percentage|decibel) + # Card (optional) (integer) Card number to control. + amixer_set_volume() { + local -r vol=${1:?$(error 'Volume is required')} + local -r card=$2 + + amixer -q ${card:+-c "$card" --} set "$MIXER" "$vol" + } + + # Toggle mute using amixer. + # + # Arguments: + # Card (integer) Card number to control. + amixer_toggle_mute() { + local -r card=$1 + + amixer -q ${card:+-c "$card" --} set "$MIXER" toggle + } + + # Check if card is muted. + # + # Arguments: + # Card (optional) (integer) Card number to control. + # + # Returns: + # 0 when true, 1 when false. + amixer_is_muted() { + local -r card=$1 + + amixer ${card:+-c "$card" --} sget "$MIXER" | \ + awk -W posix -F'[][]' '/dB/ && $6 ~ /^off$/ { exitcode=1 }; END { exit !exitcode }' + } + + # Get color for the given volume for amixer + # + # Arguments: + # $1 - The volume + amixer_volume_color() { + local -ir vol=${1:?$(error 'A volume is required')} + + if (( vol >= 0 && vol < 100 )); then + echo "$COLOR_MUTED_TO_BASE" + elif (( vol == 100 )); then + echo "$COLOR_BASE_TO_NORM" + elif (( vol > 100 && vol <= MAX_VOLUME )); then + echo "$COLOR_NORM_TO_MAX" + else + echo "$COLOR_OTHER" + fi + } + + # Play a sound file. + # + # Arguments: + # Sound file (string) + amixer_play() { + local -r file=$1 + + aplay -q "$file" & + } +} + +# Register amixer related functions and settings +setup_amixer() { + define_amixer_functions +} + +setup_color() { + if has_color; then + COLOR_RESET=$'\033[0m' + COLOR_RED=$'\033[0;31m' + COLOR_GREEN=$'\033[0;32m' + COLOR_YELLOW=$'\033[0;33m' + COLOR_MAGENTA=$'\033[0;35m' + COLOR_CYAN=$'\033[0;36m' + fi +} + +# Rearrange all options to place flags first +# Author: greycat +# URL: https://mywiki.wooledge.org/ComplexOptionParsing +arrange_opts() { + local flags args optstr=$1 + shift + + while (($#)); do + case $1 in + --) + args+=("$@") + break; + ;; + -*) + flags+=("$1") + if [[ $optstr == *"${1: -1}:"* ]]; then + flags+=("$2") + shift + fi + ;; + *) + args+=("$1") + ;; + esac + shift + done + OPTARR=("${flags[@]}" "${args[@]}") +} + +parse_opts() { + local optstring=:ac:Ce:hj:lm:nN:pPs:S:t:u:x:X:y + + arrange_opts "$optstring" "$@" + set -- "${OPTARR[@]}" + + OPTIND=1 + + while getopts "$optstring" opt; do + case "$opt" in + a ) USE_AMIXER=true ;; + c ) CARD=$OPTARG ;; + C ) USE_CANBERRA=true ;; + e ) EXPIRES=$OPTARG ;; + j ) IFS=, read -ra ICONS_EMOJI <<< "$OPTARG" ;; + l ) USE_FULLCOLOR_ICONS=true ;; + m ) MIXER=${OPTARG@Q} ;; + n ) DISPLAY_NOTIFICATIONS=true ;; + N ) NOTIFICATION_METHOD=$OPTARG ;; + p ) SHOW_VOLUME_PROGRESS=true ;; + P ) PLAY_SOUND=true ;; + s ) SINK=$OPTARG ;; + S ) SYMBOLIC_ICON_SUFFIX=$OPTARG ;; + t ) STATUSLINE=$OPTARG ;; + u ) SIGNAL=$OPTARG ;; + x ) MAX_VOLUME=$OPTARG ;; + X ) MAX_AMPLIFICATION=$OPTARG ;; + y ) USE_DUNSTIFY=true ;; + h | *) usage ;; + esac + done + + read -ra CMDARGS <<< "${OPTARR[@]:$((OPTIND-1))}" +} + +exec_command() { + IFS=' ' read -ra ARGS <<< "$1" + set -- "${ARGS[@]}" + + COMMAND=${1:?$(error 'A command is required')} + shift + + case "$COMMAND" in + up|raise|increase) + case "$#" in 1) ;; *) usage ;; esac + increase_volume "$1" "$MAX_VOLUME" + ;; + down|lower|decrease) + case "$#" in 1) ;; *) usage ;; esac + decrease_volume "$1" + ;; + set) + case "$#" in 1) ;; *) usage ;; esac + case "$1" in + +*) increase_volume "${1:1}" "$MAX_VOLUME" ;; + -*) decrease_volume "${1:1}" ;; + *) set_volume "$1" "$MAX_VOLUME" ;; + esac + ;; + mute) + toggle_mute + ;; + listen) + listen "$*" + ;; + output) + case "$#" in 0) usage ;; esac + output_volume "$*" + exit "${EXITCODE:-$EX_OK}" + ;; + outputs) + list_output_formats + ;; + notifications) + list_notification_methods + ;; + *) + usage + ;; + esac +} + +play_volume_changed() { + $PLAY_SOUND || return + + # Sound can be handled by the notification method + if $DISPLAY_NOTIFICATIONS && has_capability sound; then + return + fi + + if $USE_CANBERRA; then + ca_play "$SOUND_VOLUME_CHANGED" "Volume Changed" + else + if $USE_AMIXER; then + amixer_play "$SOUND_VOLUME_CHANGED" + else + pa_play "$SOUND_VOLUME_CHANGED" + fi + fi +} + +ca_play() { + local -r file=$1 desc=$2 + + if [[ -f $file ]]; then + "${CANBERRA_PATH:+${CANBERRA_PATH%/}/}canberra-gtk-play" -f "$file" -d "$desc" + else + "${CANBERRA_PATH:+${CANBERRA_PATH%/}/}canberra-gtk-play" -i "audio-volume-change" -d "$desc" + fi +} + +post_command_hook() { + if is_command_hookable "$COMMAND"; then + show_volume_notification + play_volume_changed + update_statusbar || usage + fi +} + +main() { + # Getopt parsing variables + declare OPTIND + declare -a OPTARR CMDARGS + + ########################################################### + # Non-command line option variables + ########################################################### + + # Commands which will not use post_command_hook(), usually because + # they handle notifications and/or statusbar updates manually + declare -a POST_HOOK_EXEMPT_COMMANDS=( + listen + ) + + # Exit codes + declare -ir \ + EX_OK=0 \ + EX_URGENT=33 \ + EX_USAGE=64 + + # Main program exit code + declare -i EXITCODE=$EX_OK + + # Standard notification icons. Usually full color + # Note: order matters; muted, high, low, medium, and optionally overamplified + declare -a ICONS=( + audio-volume-muted + audio-volume-high + audio-volume-low + audio-volume-medium + ) + + # Symbolic notification icons. Usually low color or monochrome + # Note: order matters; muted, high, low, medium, and optionally overamplified + declare -a ICONS_SYMBOLIC=( + audio-volume-muted-symbolic + audio-volume-high-symbolic + audio-volume-low-symbolic + audio-volume-medium-symbolic + ## Only exists in some icon sets + # audio-volume-overamplified-symbolic + ) + + # Emoji-based icons. + declare -a ICONS_EMOJI=( + ﱝ + 墳 + 奄 + 奔 + ) + + # Volume changed sound. + declare SOUND_VOLUME_CHANGED=${SOUND_VOLUME_CHANGED:-/usr/share/sounds/freedesktop/stereo/audio-volume-change.oga} + + # DBUS constants + declare -r \ + DBUS_NAME=org.freedesktop.Notifications \ + DBUS_PATH=/org/freedesktop/Notifications \ + DBUS_IFAC_FDN=org.freedesktop.Notifications + + # Notification server information + declare \ + NOTIFY_SERVER + # NOTIFY_VENDOR \ + # NOTIFY_VERSION \ + # NOTIFY_SPEC_VERSION + + # Notification capabilities + declare -a NOTIFY_CAPS=() + + # PulseAudio sink flags + declare -a SINK_FLAGS=() + + # PulseAudio volume variables and constants. + # Note: unlike in PA, PA_VOLUME_* here are percentages instead of integers + declare -i PA_BASE_VOLUME=100 + declare -ir \ + PA_VOLUME_NORM=100 \ + PA_VOLUME_MUTED=0 + + # Cached output of `pactl list sinks`; so we don't have to call it each time we need it + declare PA_LIST_SINKS + + # Output volume colors + declare -r \ + COLOR_MUTED=${COLOR_MUTED:-#FFFF00} \ + COLOR_MUTED_TO_BASE=${COLOR_MUTED_TO_BASE:-#00FF00} \ + COLOR_BASE_TO_NORM=${COLOR_BASE_TO_NORM:-#FFFF00} \ + COLOR_NORM_TO_MAX=${COLOR_NORM_TO_MAX:-#FF0000} \ + COLOR_OTHER=${COLOR_OTHER:-#FFFFFF} \ + COLOR_XOSD_OUTLINE=${COLOR_XOSD_OUTLINE:-#222222} + + declare \ + COLOR_RESET \ + COLOR_RED \ + COLOR_GREEN \ + COLOR_YELLOW \ + COLOR_MAGENTA \ + COLOR_CYAN + + ########################################################### + # Command line option variables + ########################################################### + declare -l NOTIFICATION_METHOD + + declare \ + COMMAND \ + DISPLAY_NOTIFICATIONS=false \ + SHOW_VOLUME_PROGRESS=false \ + USE_AMIXER=false \ + USE_DUNSTIFY=false \ + USE_FULLCOLOR_ICONS=false \ + CARD \ + MIXER=Master \ + SIGNAL \ + SINK \ + STATUSLINE \ + SYMBOLIC_ICON_SUFFIX \ + NOTIFICATION_METHOD \ + PLAY_SOUND=false \ + USE_CANBERRA=false + + declare -i \ + EXPIRES=1500 \ + MAX_VOLUME \ + MAX_AMPLIFICATION=2 + + define_helpers + define_notify + define_output_formats + define_commands + + setup_color + + parse_opts "$@" + + # Requires options to be parsed first + setup_audio + + exec_command "${CMDARGS[*]}" && post_command_hook + + exit ${EXITCODE:-$EX_OK} +} + +main "$@" diff --git a/.config/picom/picom.conf b/.config/picom/picom.conf new file mode 100755 index 0000000..f36cd4b --- /dev/null +++ b/.config/picom/picom.conf @@ -0,0 +1,49 @@ +backend = "glx"; +vsync = true; + +shadow = true; +dock = { shadow = false; }; +dnd = { shadow = false; }; +shadow-radius = 7; +shadow-offset-x = -10; +shadow-offset-y = -10; +shadow-exclude = +[ + "n:e:Notification", + "_GTK_FRAME_EXTENTS@:c", + "class_g = 'firefox' && argb" +]; +shadow-ignore-shaped = true; + +blur-background-fixed = false; +blur-background-exclude = [ "window_type = 'dock'", "window_type = 'desktop'" ]; + +fading = true; +fade-delta = 7; +fade-in-step = 0.05; +fade-out-step = 0.05; +fade-exclude = []; + +mark-wmwin-focused = true; +mark-ovredir-focused = true; +use-ewmh-active-win = false; +detect-rounded-corners = true; +detect-client-opacity = true; +refresh-rate = 60; +dbe = false; +unredir-if-possible = false; +focus-exclude = []; +detect-transient = true; +detect-client-leader = true; +invert-color-include = []; + +wintypes: { + tooltip = { fade = true; shadow = false; opacity = 0.75; focus = true; }; +}; + +inactive-opacity = 0.95 +opacity-rule = +[ + "100:name *= 'mantablockscreen'", + "100:class_g = 'Firefox'" +]; diff --git a/.config/rofi/config b/.config/rofi/config new file mode 100755 index 0000000..01ecf55 --- /dev/null +++ b/.config/rofi/config @@ -0,0 +1 @@ +rofi.theme: /usr/share/rofi/themes/gruvbox-dark-hard.rasi