diff --git a/.config/hypr/bindings.conf b/.config/hypr/bindings.conf index 88497ed..0842e51 100644 --- a/.config/hypr/bindings.conf +++ b/.config/hypr/bindings.conf @@ -18,8 +18,8 @@ bindld = ,XF86KbdLightOnOff, Keyboard backlight cycle, exec, station-brightness- # Precise 1% multimedia adjustments with Alt modifier bindeld = ALT, XF86AudioRaiseVolume, Volume up precise, exec, $osdclient --output-volume +1 bindeld = ALT, XF86AudioLowerVolume, Volume down precise, exec, $osdclient --output-volume -1 -# bindeld = ALT, XF86MonBrightnessUp, Brightness up precise, exec, omarchy-brightness-display +1% -# bindeld = ALT, XF86MonBrightnessDown, Brightness down precise, exec, omarchy-brightness-display 1%- +bindeld = ALT, XF86MonBrightnessUp, Brightness up precise, exec, station-brightness-display +1% +bindeld = ALT, XF86MonBrightnessDown, Brightness down precise, exec, station-brightness-display 1%- # Requires playerctl bindld = , XF86AudioNext, Next track, exec, $osdclient --playerctl next @@ -28,7 +28,7 @@ bindld = , XF86AudioPlay, Play, exec, $osdclient --playerctl play-pause bindld = , XF86AudioPrev, Previous track, exec, $osdclient --playerctl previous # Switch audio output with Super + Mute -# bindld = SUPER, XF86AudioMute, Switch audio output, exec, omarchy-cmd-audio-switch +bindld = SUPER, XF86AudioMute, Switch audio output, exec, station-cmd-audio-switch # ========================== # Copy & Paste @@ -36,23 +36,23 @@ bindld = , XF86AudioPrev, Previous track, exec, $osdclient --playerctl previous bindd = SUPER, C, Universal copy, sendshortcut, CTRL, Insert, bindd = SUPER, V, Universal paste, sendshortcut, SHIFT, Insert, bindd = SUPER, X, Universal cut, sendshortcut, CTRL, X, -# bindd = SUPER CTRL, V, Clipboard manager, exec, omarchy-launch-walker -m clipboard +bindd = SUPER CTRL, V, Clipboard manager, exec, station-launch-walker -m clipboard # Close windows bindd = SUPER, Q, Close window, killactive, -# bindd = CTRL ALT, DELETE, Close all windows, exec, omarchy-hyprland-window-close-all +bindd = CTRL ALT, DELETE, Close all windows, exec, station-hyprland-window-close-all # ========================== # Tiling # ========================== -# bindd = SUPER, J, Toggle window split, togglesplit, # dwindle +bindd = SUPER CTRL, J, Toggle window split, togglesplit, # dwindle bindd = SUPER, P, Pseudo window, pseudo, # dwindle bindd = SUPER, T, Toggle window floating/tiling, togglefloating, bindd = SUPER, F, Full screen, fullscreen, 0 bindd = SUPER CTRL, F, Tiled full screen, fullscreenstate, 0 2 bindd = SUPER ALT, F, Full width, fullscreen, 1 -# bindd = SUPER, O, Pop window out (float & pin), exec, omarchy-hyprland-window-pop -# bindd = SUPER, L, Toggle workspace layout, exec, omarchy-hyprland-workspace-layout-toggle +bindd = SUPER, O, Pop window out (float & pin), exec, station-hyprland-window-pop +# bindd = SUPER CTRL, L, Toggle workspace layout, exec, station-hyprland-workspace-layout-toggle # Move focus with SUPER + arrow keys bindd = SUPER, H, Move window focus left, movefocus, l @@ -172,15 +172,15 @@ bindd = SUPER ALT, code:14, Switch to group window 5, changegroupactive, 5 # bindd = SUPER, Slash, Cycle monitor scaling, exec, omarchy-hyprland-monitor-scaling-cycle # Menus -# bindd = SUPER, SPACE, Launch apps, exec, omarchy-launch-walker -# bindd = SUPER CTRL, E, Emoji picker, exec, omarchy-launch-walker -m symbols -# bindd = SUPER CTRL, C, Capture menu, exec, omarchy-menu capture -# bindd = SUPER CTRL, O, Toggle menu, exec, omarchy-menu toggle -# bindd = SUPER ALT, SPACE, Omarchy menu, exec, omarchy-menu -# bindd = SUPER, ESCAPE, System menu, exec, omarchy-menu system -# bindld = , XF86PowerOff, Power menu, exec, omarchy-menu system -# bindd = SUPER, K, Show key bindings, exec, omarchy-menu-keybindings -# bindd = , XF86Calculator, Calculator, exec, gnome-calculator +bindd = SUPER, SPACE, Launch apps, exec, station-launch-walker +bindd = SUPER CTRL, E, Emoji picker, exec, station-launch-walker -m symbols +bindd = SUPER CTRL, C, Capture menu, exec, station-menu capture +bindd = SUPER CTRL, O, Toggle menu, exec, station-menu toggle +bindd = SUPER ALT, SPACE, Omarchy menu, exec, station-menu +bindd = SUPER, ESCAPE, System menu, exec, station-menu system +bindld = , XF86PowerOff, Power menu, exec, station-menu system +bindd = SUPER, K, Show key bindings, exec, station-menu-keybindings +bindd = , XF86Calculator, Calculator, exec, gnome-calculator # Aesthetics # bindd = SUPER SHIFT, SPACE, Toggle top bar, exec, omarchy-toggle-waybar diff --git a/.local/bin/station-cmd-audio-switch b/.local/bin/station-cmd-audio-switch new file mode 100755 index 0000000..75ad5c3 --- /dev/null +++ b/.local/bin/station-cmd-audio-switch @@ -0,0 +1,66 @@ +#!/bin/bash + +# Switch between audio outputs while preserving the mute status. By default mapped to Super + Mute. + +focused_monitor="$(hyprctl monitors -j | jq -r '.[] | select(.focused == true).name')" + +sinks=$(pactl -f json list sinks | jq '[.[] | select((.ports | length == 0) or ([.ports[]? | .availability != "not available"] | any))]') +sinks_count=$(echo "$sinks" | jq '. | length') + +if (( sinks_count == 0 )); then + swayosd-client \ + --monitor "$focused_monitor" \ + --custom-message "No audio devices found" + exit 1 +fi + +current_sink_name=$(pactl get-default-sink) +current_sink_index=$(echo "$sinks" | jq -r --arg name "$current_sink_name" 'map(.name) | index($name)') + +if [[ $current_sink_index != "null" ]]; then + next_sink_index=$(((current_sink_index + 1) % sinks_count)) +else + next_sink_index=0 +fi + +next_sink=$(echo "$sinks" | jq -r ".[$next_sink_index]") +next_sink_name=$(echo "$next_sink" | jq -r '.name') + +next_sink_description=$(echo "$next_sink" | jq -r '.description') +if [[ $next_sink_description == "(null)" ]] || [[ $next_sink_description == "null" ]] || [[ -z $next_sink_description ]]; then + # For Bluetooth devices, the friendly name is on the Device entry (device.id), not the Sink entry (object.id) + device_id=$(echo "$next_sink" | jq -r '.properties."device.id"') + if [[ $device_id != "null" ]] && [[ -n $device_id ]]; then + next_sink_description=$(wpctl status | grep -E "^\s*│?\s+${device_id}\." | sed -E 's/^.*[0-9]+\.\s+//' | sed -E 's/\s+\[.*$//') + fi + # Fall back to object.id lookup if device.id didn't yield a result + if [[ -z $next_sink_description ]]; then + sink_id=$(echo "$next_sink" | jq -r '.properties."object.id"') + next_sink_description=$(wpctl status | grep -E "\s+\*?\s+${sink_id}\." | sed -E 's/^.*[0-9]+\.\s+//' | sed -E 's/\s+\[.*$//') + fi +fi + +next_sink_volume=$(echo "$next_sink" | jq -r \ + '.volume | to_entries[0].value.value_percent | sub("%"; "")') +next_sink_is_muted=$(echo "$next_sink" | jq -r '.mute') + +if [[ $next_sink_is_muted = "true" ]] || (( next_sink_volume == 0 )); then + icon_state="muted" +elif (( next_sink_volume <= 33 )); then + icon_state="low" +elif (( next_sink_volume <= 66 )); then + icon_state="medium" +else + icon_state="high" +fi + +next_sink_volume_icon="sink-volume-${icon_state}-symbolic" + +if [[ $next_sink_name != $current_sink_name ]]; then + pactl set-default-sink "$next_sink_name" +fi + +swayosd-client \ + --monitor "$focused_monitor" \ + --custom-message "$next_sink_description" \ + --custom-icon "$next_sink_volume_icon" diff --git a/.local/bin/station-cmd-screenrecord b/.local/bin/station-cmd-screenrecord new file mode 100755 index 0000000..bfc0cc9 --- /dev/null +++ b/.local/bin/station-cmd-screenrecord @@ -0,0 +1,174 @@ +#!/bin/bash + +# Start and stop a screenrecording, which will be saved to ~/Videos by default. +# Alternative location can be set via STATION_SCREENRECORD_DIR or XDG_VIDEOS_DIR ENVs. +# Resolution is capped to 4K for monitors above 4K, native otherwise. +# Override via --resolution= (e.g. --resolution=1920x1080, --resolution=0x0 for native). + +[[ -f ~/.config/user-dirs.dirs ]] && source ~/.config/user-dirs.dirs +OUTPUT_DIR="${STATION_SCREENRECORD_DIR:-${XDG_VIDEOS_DIR:-$HOME/Videos}}" + +if [[ ! -d $OUTPUT_DIR ]]; then + notify-send "Screen recording directory does not exist: $OUTPUT_DIR" -u critical -t 3000 + exit 1 +fi + +DESKTOP_AUDIO="false" +MICROPHONE_AUDIO="false" +WEBCAM="false" +WEBCAM_DEVICE="" +RESOLUTION="" +STOP_RECORDING="false" +RECORDING_FILE="/tmp/station-screenrecord-filename" + +for arg in "$@"; do + case "$arg" in + --with-desktop-audio) DESKTOP_AUDIO="true" ;; + --with-microphone-audio) MICROPHONE_AUDIO="true" ;; + --with-webcam) WEBCAM="true" ;; + --webcam-device=*) WEBCAM_DEVICE="${arg#*=}" ;; + --resolution=*) RESOLUTION="${arg#*=}" ;; + --stop-recording) STOP_RECORDING="true" ;; + esac +done + +start_webcam_overlay() { + cleanup_webcam + + # Auto-detect first available webcam if none specified + if [[ -z $WEBCAM_DEVICE ]]; then + WEBCAM_DEVICE=$(v4l2-ctl --list-devices 2>/dev/null | grep -m1 "^[[:space:]]*/dev/video" | tr -d '\t') + if [[ -z $WEBCAM_DEVICE ]]; then + notify-send "No webcam devices found" -u critical -t 3000 + return 1 + fi + fi + + # Get monitor scale + local scale=$(hyprctl monitors -j | jq -r '.[] | select(.focused == true) | .scale') + + # Target width (base 360px, scaled to monitor) + local target_width=$(awk "BEGIN {printf \"%.0f\", 360 * $scale}") + + # Try preferred 16:9 resolutions in order, use first available + local preferred_resolutions=("640x360" "1280x720" "1920x1080") + local video_size_arg="" + local available_formats=$(v4l2-ctl --list-formats-ext -d "$WEBCAM_DEVICE" 2>/dev/null) + + for resolution in "${preferred_resolutions[@]}"; do + if echo "$available_formats" | grep -q "$resolution"; then + video_size_arg="-video_size $resolution" + break + fi + done + + ffplay -f v4l2 $video_size_arg -framerate 30 "$WEBCAM_DEVICE" \ + -vf "scale=${target_width}:-1" \ + -window_title "WebcamOverlay" \ + -noborder \ + -fflags nobuffer -flags low_delay \ + -probesize 32 -analyzeduration 0 \ + -loglevel quiet & + sleep 1 +} + +cleanup_webcam() { + pkill -f "WebcamOverlay" 2>/dev/null +} + +default_resolution() { + local width height + read -r width height < <(hyprctl monitors -j | jq -r '.[] | select(.focused == true) | "\(.width) \(.height)"') + if ((width > 3840 || height > 2160)); then + echo "3840x2160" + else + echo "0x0" + fi +} + +start_screenrecording() { + local filename="$OUTPUT_DIR/screenrecording-$(date +'%Y-%m-%d_%H-%M-%S').mp4" + local audio_devices="" + local audio_args=() + + [[ $DESKTOP_AUDIO == "true" ]] && audio_devices+="default_output" + + if [[ $MICROPHONE_AUDIO == "true" ]]; then + # Merge audio tracks into one - separate tracks only play one at a time in most players + [[ -n $audio_devices ]] && audio_devices+="|" + audio_devices+="default_input" + fi + + [[ -n $audio_devices ]] && audio_args+=(-a "$audio_devices" -ac aac) + + local resolution="${RESOLUTION:-$(default_resolution)}" + + gpu-screen-recorder -w portal -k auto -s "$resolution" -f 60 -fm cfr -fallback-cpu-encoding yes -o "$filename" "${audio_args[@]}" & + local pid=$! + + # Wait for recording to actually start (file appears after portal selection) + while kill -0 $pid 2>/dev/null && [[ ! -f $filename ]]; do + sleep 0.2 + done + + if kill -0 $pid 2>/dev/null; then + echo "$filename" >"$RECORDING_FILE" + toggle_screenrecording_indicator + fi +} + +stop_screenrecording() { + pkill -SIGINT -f "^gpu-screen-recorder" # SIGINT required to save video properly + + # Wait a maximum of 5 seconds to finish before hard killing + local count=0 + while pgrep -f "^gpu-screen-recorder" >/dev/null && ((count < 50)); do + sleep 0.1 + count=$((count + 1)) + done + + toggle_screenrecording_indicator + cleanup_webcam + + if pgrep -f "^gpu-screen-recorder" >/dev/null; then + pkill -9 -f "^gpu-screen-recorder" + notify-send "Screen recording error" "Recording process had to be force-killed. Video may be corrupted." -u critical -t 5000 + else + trim_first_frame + notify-send "Screen recording saved to $OUTPUT_DIR" -t 2000 + fi + + rm -f "$RECORDING_FILE" +} + +toggle_screenrecording_indicator() { + pkill -RTMIN+8 waybar +} + +screenrecording_active() { + pgrep -f "^gpu-screen-recorder" >/dev/null +} + +trim_first_frame() { + local latest + latest=$(cat "$RECORDING_FILE" 2>/dev/null) + + if [[ -n $latest && -f $latest ]]; then + local trimmed="${latest%.mp4}-trimmed.mp4" + if ffmpeg -y -ss 0.1 -i "$latest" -c copy "$trimmed" -loglevel quiet 2>/dev/null; then + mv "$trimmed" "$latest" + else + rm -f "$trimmed" + fi + fi +} + +if screenrecording_active; then + stop_screenrecording +elif [[ $STOP_RECORDING == "true" ]]; then + exit 1 +else + [[ $WEBCAM == "true" ]] && start_webcam_overlay + + start_screenrecording || cleanup_webcam +fi diff --git a/.local/bin/station-cmd-screenshot b/.local/bin/station-cmd-screenshot new file mode 100755 index 0000000..94a2f9c --- /dev/null +++ b/.local/bin/station-cmd-screenshot @@ -0,0 +1,130 @@ +#!/bin/bash + +# Take a screenshot of the whole screen, a specific window, or a user-drawn region. +# Saves to ~/Pictures by default, but that can be changed via STATION_SCREENSHOT_DIR or XDG_PICTURES_DIR ENVs. +# Editor defaults to Satty but can be changed via --editor= or STATION_SCREENSHOT_EDITOR env + +[[ -f ~/.config/user-dirs.dirs ]] && source ~/.config/user-dirs.dirs +OUTPUT_DIR="${STATION_SCREENSHOT_DIR:-${XDG_PICTURES_DIR:-$HOME/Pictures}}" + +if [[ ! -d $OUTPUT_DIR ]]; then + notify-send "Screenshot directory does not exist: $OUTPUT_DIR" -u critical -t 3000 + exit 1 +fi + +pkill slurp && exit 0 + +SCREENSHOT_EDITOR="${STATION_SCREENSHOT_EDITOR:-satty}" + +# Parse --editor flag from any position +ARGS=() +for arg in "$@"; do + if [[ $arg == --editor=* ]]; then + SCREENSHOT_EDITOR="${arg#--editor=}" + else + ARGS+=("$arg") + fi +done +set -- "${ARGS[@]}" + +open_editor() { + local filepath="$1" + if [[ $SCREENSHOT_EDITOR == "satty" ]]; then + satty --filename "$filepath" \ + --output-filename "$filepath" \ + --actions-on-enter save-to-clipboard \ + --save-after-copy \ + --copy-command 'wl-copy' + else + $SCREENSHOT_EDITOR "$filepath" + fi +} + +MODE="${1:-smart}" +PROCESSING="${2:-slurp}" + +# accounting for portrait/transformed displays +JQ_MONITOR_GEO=' +def format_geo: +.x as $x | .y as $y | + (.width / .scale | floor) as $w | + (.height / .scale | floor) as $h | + .transform as $t | + if $t == 1 or $t == 3 then + "\($x),\($y) \($h)x\($w)" + else + "\($x),\($y) \($w)x\($h)" + end; + ' + + get_rectangles() { + local active_workspace=$(hyprctl monitors -j | jq -r '.[] | select(.focused == true) | .activeWorkspace.id') + hyprctl monitors -j | jq -r --arg ws "$active_workspace" "${JQ_MONITOR_GEO} .[] | select(.activeWorkspace.id == (\$ws | tonumber)) | format_geo" + hyprctl clients -j | jq -r --arg ws "$active_workspace" '.[] | select(.workspace.id == ($ws | tonumber)) | "\(.at[0]),\(.at[1]) \(.size[0])x\(.size[1])"' + } + +# Select based on mode +case "$MODE" in + region) + hyprpicker -r -z >/dev/null 2>&1 & PID=$! + sleep .1 + SELECTION=$(slurp 2>/dev/null) + kill $PID 2>/dev/null + ;; + windows) + hyprpicker -r -z >/dev/null 2>&1 & PID=$! + sleep .1 + SELECTION=$(get_rectangles | slurp -r 2>/dev/null) + kill $PID 2>/dev/null + ;; + fullscreen) + SELECTION=$(hyprctl monitors -j | jq -r "${JQ_MONITOR_GEO} .[] | select(.focused == true) | format_geo") + ;; + smart|*) + RECTS=$(get_rectangles) + hyprpicker -r -z >/dev/null 2>&1 & PID=$! + sleep .1 + SELECTION=$(echo "$RECTS" | slurp 2>/dev/null) + kill $PID 2>/dev/null + + # If the selection area is L * W < 20, we'll assume you were trying to select whichever + # window or output it was inside of to prevent accidental 2px snapshots + if [[ $SELECTION =~ ^([0-9]+),([0-9]+)[[:space:]]([0-9]+)x([0-9]+)$ ]]; then + if ((${BASH_REMATCH[3]} * ${BASH_REMATCH[4]} < 20)); then + click_x="${BASH_REMATCH[1]}" + click_y="${BASH_REMATCH[2]}" + + while IFS= read -r rect; do + if [[ $rect =~ ^([0-9]+),([0-9]+)[[:space:]]([0-9]+)x([0-9]+) ]]; then + rect_x="${BASH_REMATCH[1]}" + rect_y="${BASH_REMATCH[2]}" + rect_width="${BASH_REMATCH[3]}" + rect_height="${BASH_REMATCH[4]}" + + if ((click_x >= rect_x && click_x < rect_x + rect_width && click_y >= rect_y && click_y < rect_y + rect_height)); then + SELECTION="${rect_x},${rect_y} ${rect_width}x${rect_height}" + break + fi + fi + done <<<"$RECTS" + fi + fi + ;; +esac + +[[ -z $SELECTION ]] && exit 0 + +FILENAME="screenshot-$(date +'%Y-%m-%d_%H-%M-%S').png" +FILEPATH="$OUTPUT_DIR/$FILENAME" + +if [[ $PROCESSING == "slurp" ]]; then + grim -g "$SELECTION" "$FILEPATH" || exit 1 + wl-copy <"$FILEPATH" + + ( + ACTION=$(notify-send "Screenshot saved to clipboard and file" "Edit with Super + Alt + , (or click this)" -t 10000 -i "$FILEPATH" -A "default=edit") + [[ $ACTION == "default" ]] && open_editor "$FILEPATH" + ) & + else + grim -g "$SELECTION" - | wl-copy +fi diff --git a/.local/bin/station-hyprland-window-close-all b/.local/bin/station-hyprland-window-close-all new file mode 100755 index 0000000..5b3c342 --- /dev/null +++ b/.local/bin/station-hyprland-window-close-all @@ -0,0 +1,9 @@ +#!/bin/bash + +# Close all open windows +hyprctl clients -j | \ + jq -r ".[].address" | \ + xargs -I{} hyprctl dispatch closewindow address:{} + +# Move to first workspace +hyprctl dispatch workspace 1 diff --git a/.local/bin/station-hyprland-window-pop b/.local/bin/station-hyprland-window-pop new file mode 100755 index 0000000..40cec30 --- /dev/null +++ b/.local/bin/station-hyprland-window-pop @@ -0,0 +1,46 @@ +#!/bin/bash + +# Toggle to pop-out a tile to stay fixed on a display basis. + +# Usage: +# station-hyprland-window-pop [width height [x y]] +# +# Arguments: +# width Optional. Width of the floating window. Default: 1300 +# height Optional. Height of the floating window. Default: 900 +# x Optional. X position of the window. Must provide both X and Y to take effect. +# y Optional. Y position of the window. Must provide both X and Y to take effect. +# +# Behavior: +# - If the window is already pinned, it will be unpinned and removed from the pop layer. +# - If the window is not pinned, it will be floated, resized, moved/centered, pinned, brought to top, and popped. + +width=${1:-1300} +height=${2:-900} +x=${3:-} +y=${4:-} + +active=$(hyprctl activewindow -j) +pinned=$(echo "$active" | jq ".pinned") +addr=$(echo "$active" | jq -r ".address") + +if [[ $pinned == "true" ]]; then + hyprctl -q --batch \ + "dispatch pin address:$addr;" \ + "dispatch togglefloating address:$addr;" \ + "dispatch tagwindow -pop address:$addr;" +elif [[ -n $addr ]]; then + hyprctl dispatch togglefloating address:$addr + hyprctl dispatch resizeactive exact $width $height address:$addr + + if [[ -n $x && -n $y ]]; then + hyprctl dispatch moveactive $x $y address:$addr + else + hyprctl dispatch centerwindow address:$addr + fi + + hyprctl -q --batch \ + "dispatch pin address:$addr;" \ + "dispatch alterzorder top address:$addr;" \ + "dispatch tagwindow +pop address:$addr;" +fi diff --git a/.local/bin/station-hyprland-workspace-layout-toggle b/.local/bin/station-hyprland-workspace-layout-toggle new file mode 100755 index 0000000..4b66831 --- /dev/null +++ b/.local/bin/station-hyprland-workspace-layout-toggle @@ -0,0 +1,14 @@ +#!/bin/bash + +# Toggle the layout on the current active workspace between dwindle and scrolling + +ACTIVE_WORKSPACE=$(hyprctl activeworkspace -j | jq -r '.id') +CURRENT_LAYOUT=$(hyprctl activeworkspace -j | jq -r '.tiledLayout') + +case "$CURRENT_LAYOUT" in + dwindle) NEW_LAYOUT=scrolling ;; + *) NEW_LAYOUT=dwindle ;; +esac + +hyprctl keyword workspace $ACTIVE_WORKSPACE, layout:$NEW_LAYOUT +notify-send "󱂬 Workspace layout set to $NEW_LAYOUT" diff --git a/.local/bin/station-launch-floating-terminal-with-presentation b/.local/bin/station-launch-floating-terminal-with-presentation new file mode 100755 index 0000000..81f0a0d --- /dev/null +++ b/.local/bin/station-launch-floating-terminal-with-presentation @@ -0,0 +1,7 @@ +#!/bin/bash + +# Launch a floating terminal with the Station logo presentation, then execute the command passed in, and finally end with the station-show-done presentation. +# Used by actions such as Update System. + +cmd="$*" +exec setsid uwsm-app -- xdg-terminal-exec --app-id=org.station.terminal --title=Station -e bash -c "station-show-logo; $cmd; if (( \$? != 130 )); then station-show-done; fi" diff --git a/.local/bin/station-launch-walker b/.local/bin/station-launch-walker new file mode 100755 index 0000000..02ddf11 --- /dev/null +++ b/.local/bin/station-launch-walker @@ -0,0 +1,15 @@ +#!/bin/bash + +# Launch the Walker application launcher while ensuring that it's data provider (called elephant) is running first. + +# Ensure elephant is running before launching walker +if ! pgrep -x elephant > /dev/null; then + setsid uwsm-app -- elephant & +fi + +# Ensure walker service is running +if ! pgrep -f "walker --gapplication-service" > /dev/null; then + setsid uwsm-app -- walker --gapplication-service & +fi + +exec walker --width 644 --maxheight 300 --minheight 300 "$@" diff --git a/.local/bin/station-menu b/.local/bin/station-menu new file mode 100755 index 0000000..865eb51 --- /dev/null +++ b/.local/bin/station-menu @@ -0,0 +1,346 @@ +#!/bin/bash + +# Launch the Station Menu or takes a parameter to jump straight to a submenu. + +Set to true when going directly to a submenu, so we can exit directly +BACK_TO_EXIT=false + +back_to() { + local parent_menu="$1" + + if [[ $BACK_TO_EXIT == "true" ]]; then + exit 0 + elif [[ -n $parent_menu ]]; then + "$parent_menu" + else + show_main_menu + fi +} + +toggle_existing_menu() { + if pgrep -f "walker.*--dmenu" > /dev/null; then + walker --close > /dev/null 2>&1 + exit 0 + fi +} + +menu() { + local prompt="$1" + local options="$2" + local extra="$3" + local preselect="$4" + + read -r -a args <<<"$extra" + + if [[ -n $preselect ]]; then + local index + index=$(echo -e "$options" | grep -nxF "$preselect" | cut -d: -f1) + if [[ -n $index ]]; then + args+=("-c" "$index") + fi + fi + + echo -e "$options" | station-launch-walker --dmenu --width 295 --minheight 1 --maxheight 630 -p "$prompt…" "${args[@]}" 2>/dev/null +} + +terminal() { + xdg-terminal-exec --app-id=org.station.terminal "$@" +} + +present_terminal() { + station-launch-floating-terminal-with-presentation $1 +} + +open_in_editor() { + notify-send "Editing config file" "$1" + station-launch-editor "$1" +} + +show_trigger_menu() { + case $(menu "Trigger" " Capture\n Share\n󰔎 Toggle\n Hardware") in + *Capture*) show_capture_menu ;; + *Share*) show_share_menu ;; + *Toggle*) show_toggle_menu ;; + *Hardware*) show_hardware_menu ;; + *) show_main_menu ;; + esac +} + +show_capture_menu() { + case $(menu "Capture" " Screenshot\n Screenrecord\n󰃉 Color") in + *Screenshot*) station-cmd-screenshot ;; + *Screenrecord*) show_screenrecord_menu ;; + *Color*) pkill hyprpicker || hyprpicker -a ;; + *) back_to show_trigger_menu ;; + esac +} + +get_webcam_list() { + v4l2-ctl --list-devices 2>/dev/null | while IFS= read -r line; do + if [[ $line != $'\t'* && -n $line ]]; then + local name="$line" + IFS= read -r device || break + device=$(echo "$device" | tr -d '\t' | head -1) + [[ -n $device ]] && echo "$device $name" + fi + done +} + +show_webcam_select_menu() { + local devices=$(get_webcam_list) + local count=$(echo "$devices" | grep -c . 2>/dev/null || echo 0) + + if [[ -z $devices ]] || ((count == 0)); then + notify-send "No webcam devices found" -u critical -t 3000 + return 1 + fi + + if ((count == 1)); then + echo "$devices" | awk '{print $1}' + else + menu "Select Webcam" "$devices" | awk '{print $1}' + fi +} + +show_screenrecord_menu() { + station-cmd-screenrecord --stop-recording && exit 0 + + case $(menu "Screenrecord" " With no audio\n With desktop audio\n With desktop + microphone audio\n With desktop + microphone audio + webcam") in + *"With no audio") station-cmd-screenrecord ;; + *"With desktop audio") station-cmd-screenrecord --with-desktop-audio ;; + *"With desktop + microphone audio") station-cmd-screenrecord --with-desktop-audio --with-microphone-audio ;; + *"With desktop + microphone audio + webcam") + local device=$(show_webcam_select_menu) || { + back_to show_capture_menu + return + } + station-cmd-screenrecord --with-desktop-audio --with-microphone-audio --with-webcam --webcam-device="$device" + ;; + *) back_to show_capture_menu ;; + esac +} + + # show_share_menu() { + # case $(menu "Share" " Clipboard\n File \n Folder") in + # *Clipboard*) station-cmd-share clipboard ;; + # *File*) terminal bash -c "station-cmd-share file" ;; + # *Folder*) terminal bash -c "station-cmd-share folder" ;; + # *) back_to show_trigger_menu ;; + # esac + # } +# +# show_toggle_menu() { +# case $(menu "Toggle" "󱄄 Screensaver\n󰔎 Nightlight\n󱫖 Idle Lock\n󰍜 Top Bar\n󱂬 Workspace Layout\n Window Gaps\n 1-Window Ratio\n󰍹 Display Scaling") in +# +# *Screensaver*) station-toggle-screensaver ;; +# *Nightlight*) station-toggle-nightlight ;; +# *Idle*) station-toggle-idle ;; +# *Bar*) station-toggle-waybar ;; +# *Layout*) station-hyprland-workspace-layout-toggle ;; +# *Ratio*) station-hyprland-window-single-square-aspect-toggle ;; +# *Gaps*) station-hyprland-window-gaps-toggle ;; +# *Scaling*) station-hyprland-monitor-scaling-cycle ;; +# *) back_to show_trigger_menu ;; +# esac +# } +# +# show_hardware_menu() { +# case $(menu "Toggle" " Hybrid GPU") in +# *"Hybrid GPU"*) present_terminal station-toggle-hybrid-gpu ;; +# *) show_trigger_menu ;; +# esac +# } +# +# show_style_menu() { +# case $(menu "Style" " Background\n Hyprland\n󱄄 Screensaver") in +# *Background*) show_background_menu ;; +# *Hyprland*) open_in_editor ~/.config/hypr/looknfeel.conf ;; +# *Screensaver*) open_in_editor ~/.config/station/branding/screensaver.txt ;; +# *) show_main_menu ;; +# esac +# } +# +# show_background_menu() { +# station-launch-walker -m menus:stationBackgroundSelector --width 800 --minheight 400 +# } +# +# show_setup_menu() { +# local options=" Audio\n Wifi\n󰂯 Bluetooth\n󱐋 Power Profile\n System Sleep\n󰍹 Monitors" +# [[ -f ~/.config/hypr/bindings.conf ]] && options="$options\n Keybindings" +# [[ -f ~/.config/hypr/input.conf ]] && options="$options\n Input" +# options="$options\n󰱔 DNS\n Security\n Config" +# +# case $(menu "Setup" "$options") in +# *Audio*) station-launch-audio ;; +# *Wifi*) station-launch-wifi ;; +# *Bluetooth*) station-launch-bluetooth ;; +# *Power*) show_setup_power_menu ;; +# *System*) show_setup_system_menu ;; +# *Monitors*) open_in_editor ~/.config/hypr/monitors.conf ;; +# *Keybindings*) open_in_editor ~/.config/hypr/bindings.conf ;; +# *Input*) open_in_editor ~/.config/hypr/input.conf ;; +# *DNS*) present_terminal station-setup-dns ;; +# *Security*) show_setup_security_menu ;; +# *Config*) show_setup_config_menu ;; +# *) show_main_menu ;; +# esac +# } +# +# show_setup_power_menu() { +# profile=$(menu "Power Profile" "$(station-powerprofiles-list)" "" "$(powerprofilesctl get)") +# +# if [[ $profile == "CNCLD" || -z $profile ]]; then +# back_to show_setup_menu +# else +# powerprofilesctl set "$profile" +# fi +# } +# +# show_setup_security_menu() { +# case $(menu "Setup" "󰈷 Fingerprint\n Fido2") in +# *Fingerprint*) present_terminal station-setup-fingerprint ;; +# *Fido2*) present_terminal station-setup-fido2 ;; +# *) show_setup_menu ;; +# esac +# } +# +# show_setup_config_menu() { +# case $(menu "Setup" " Defaults\n Hyprland\n Hypridle\n Hyprlock\n Hyprsunset\n Swayosd\n󰌧 Walker\n󰍜 Waybar\n󰞅 XCompose") in +# *Defaults*) open_in_editor ~/.config/uwsm/default ;; +# *Hyprland*) open_in_editor ~/.config/hypr/hyprland.conf ;; +# *Hypridle*) open_in_editor ~/.config/hypr/hypridle.conf && station-restart-hypridle ;; +# *Hyprlock*) open_in_editor ~/.config/hypr/hyprlock.conf ;; +# *Hyprsunset*) open_in_editor ~/.config/hypr/hyprsunset.conf && station-restart-hyprsunset ;; +# *Swayosd*) open_in_editor ~/.config/swayosd/config.toml && station-restart-swayosd ;; +# *Walker*) open_in_editor ~/.config/walker/config.toml && station-restart-walker ;; +# *Waybar*) open_in_editor ~/.config/waybar/config.jsonc && station-restart-waybar ;; +# *XCompose*) open_in_editor ~/.config/xcompose && station-restart-xcompose ;; +# *) show_setup_menu ;; +# esac +# } +# +# show_setup_system_menu() { +# local options="" +# +# if [[ -f ~/.local/state/station/toggles/suspend-off ]]; then +# options="$options󰒲 Enable Suspend" +# else +# options="$options󰒲 Disable Suspend" +# fi +# +# if station-hibernation-available; then +# options="$options\n󰤁 Disable Hibernate" +# else +# options="$options\n󰤁 Enable Hibernate" +# fi +# +# case $(menu "System" "$options") in +# *Suspend*) station-toggle-suspend ;; +# *"Enable Hibernate"*) present_terminal station-hibernation-setup ;; +# *"Disable Hibernate"*) present_terminal station-hibernation-remove ;; +# *) show_setup_menu ;; +# esac +# } +# +# show_update_menu() { +# case $(menu "Update" " Config\n Process\n󰇅 Hardware\n Firmware\n Password\n Timezone\n Time") in +# *Config*) show_update_config_menu ;; +# *Process*) show_update_process_menu ;; +# *Hardware*) show_update_hardware_menu ;; +# *Firmware*) present_terminal station-update-firmware ;; +# *Timezone*) present_terminal station-tz-select ;; +# *Time*) present_terminal station-update-time ;; +# *Password*) show_update_password_menu ;; +# *) show_main_menu ;; +# esac +# } +# +# show_update_process_menu() { +# case $(menu "Restart" " Hypridle\n Hyprsunset\n Swayosd\n󰌧 Walker\n󰍜 Waybar") in +# *Hypridle*) station-restart-hypridle ;; +# *Hyprsunset*) station-restart-hyprsunset ;; +# *Swayosd*) station-restart-swayosd ;; +# *Walker*) station-restart-walker ;; +# *Waybar*) station-restart-waybar ;; +# *) show_update_menu ;; +# esac +# } +# +# show_update_config_menu() { +# case $(menu "Use default config" " Hyprland\n Hypridle\n Hyprlock\n Hyprsunset\n󱣴 Plymouth\n Swayosd\n Tmux\n󰌧 Walker\n󰍜 Waybar") in +# *Hyprland*) present_terminal station-refresh-hyprland ;; +# *Hypridle*) present_terminal station-refresh-hypridle ;; +# *Hyprlock*) present_terminal station-refresh-hyprlock ;; +# *Hyprsunset*) present_terminal station-refresh-hyprsunset ;; +# *Plymouth*) present_terminal station-refresh-plymouth ;; +# *Swayosd*) present_terminal station-refresh-swayosd ;; +# *Tmux*) present_terminal station-refresh-tmux ;; +# *Walker*) present_terminal station-refresh-walker ;; +# *Waybar*) present_terminal station-refresh-waybar ;; +# *) show_update_menu ;; +# esac +# } +# +# show_update_hardware_menu() { +# case $(menu "Restart" " Audio\n󱚾 Wi-Fi\n󰂯 Bluetooth") in +# *Audio*) present_terminal station-restart-pipewire ;; +# *Wi-Fi*) present_terminal station-restart-wifi ;; +# *Bluetooth*) present_terminal station-restart-bluetooth ;; +# *) show_update_menu ;; +# esac +# } +# +# show_update_password_menu() { +# case $(menu "Update Password" " Drive Encryption\n User") in +# *Drive*) present_terminal station-drive-set-password ;; +# *User*) present_terminal passwd ;; +# *) show_update_menu ;; +# esac +# } +# +# show_system_menu() { +# local options="󱄄 Screensaver\n Lock\n󰒲 Suspend" +# station-hibernation-available && options="$options\n󰤁 Hibernate" +# options="$options\n󰍃 Logout\n󰜉 Restart\n󰐥 Shutdown" +# +# case $(menu "System" "$options") in +# *Screensaver*) station-launch-screensaver force ;; +# *Lock*) station-lock-screen ;; +# *Suspend*) systemctl suspend ;; +# *Hibernate*) systemctl hibernate ;; +# *Logout*) station-system-logout ;; +# *Restart*) station-system-reboot ;; +# *Shutdown*) station-system-shutdown ;; +# *) back_to show_main_menu ;; +# esac +# } + +show_main_menu() { + go_to_menu "$(menu "Go" "󰀻 Apps\n󱓞 Trigger\n Setup\n Update\n About\n System")" +} + +go_to_menu() { + case "${1,,}" in + *apps*) walker -p "Launch…" ;; + *trigger*) show_trigger_menu ;; + # *toggle*) show_toggle_menu ;; + # *share*) show_share_menu ;; + # *background*) show_background_menu ;; + # *capture*) show_capture_menu ;; + # *theme*) show_theme_menu ;; + # *screenrecord*) show_screenrecord_menu ;; + # *setup*) show_setup_menu ;; + # *power*) show_setup_power_menu ;; + # *update*) show_update_menu ;; + # *system*) show_system_menu ;; + esac +} + +toggle_existing_menu + +if [[ -n $1 ]]; then + BACK_TO_EXIT=true + go_to_menu "$1" +else + show_main_menu +fi diff --git a/.local/bin/station-show-done b/.local/bin/station-show-done new file mode 100755 index 0000000..36f5aec --- /dev/null +++ b/.local/bin/station-show-done @@ -0,0 +1,7 @@ +#!/bin/bash + +# Display a "Done!" message with a spinner and wait for user to press any key. +# Used by various install scripts to indicate completion. + +echo +gum spin --spinner "globe" --title "Done! Press any key to close..." -- bash -c 'read -n 1 -s' diff --git a/.local/bin/station-show-logo b/.local/bin/station-show-logo new file mode 100755 index 0000000..d4ad6e7 --- /dev/null +++ b/.local/bin/station-show-logo @@ -0,0 +1,10 @@ +#!/bin/bash + +# Display the Omarchy logo in the terminal using green color. +# Used by various presentation scripts to show branding. + +clear +echo -e "\033[36m" +cat <~/.local/share/station/logo.txt +echo -e "\033[0m" +echo diff --git a/.local/share/station/logo.txt b/.local/share/station/logo.txt new file mode 100644 index 0000000..edb0095 --- /dev/null +++ b/.local/share/station/logo.txt @@ -0,0 +1,9 @@ + ██████ ▄▄▄█████▓ ▄▄▄ ▄▄▄█████▓ ██▓ ▒█████ ███▄ █ +▒██ ▒ ▓ ██▒ ▓▒▒████▄ ▓ ██▒ ▓▒▒▓██▒▒██▒ ██▒ ██ ▀█ █ +░ ▓██▄ ▒ ▓██░ ▒░▒██ ▀█▄ ▒ ▓██░ ▒░▒▒██▒▒██░ ██▒▓██ ▀█ ██▒ + ▒ ██▒░ ▓██▓ ░ ░██▄▄▄▄██░ ▓██▓ ░ ░░██░▒██ ██░▓██▒ ▐▌██▒ +▒██████▒▒ ▒██▒ ░ ▒▓█ ▓██ ▒██▒ ░ ░░██░░ ████▓▒░▒██░ ▓██░ +▒ ▒▓▒ ▒ ░ ▒ ░░ ░▒▒ ▓▒█ ▒ ░░ ░▓ ░ ▒░▒░▒░ ░ ▒░ ▒ ▒ +░ ░▒ ░ ░ ░ ░ ▒▒ ░ ░ ▒ ░ ░ ▒ ▒░ ░ ░░ ░ ▒░ +░ ░ ░ ░ ░ ▒ ░ ░ ▒ ░░ ░ ░ ▒ ░ ░ ░ + ░ ░ ░ ░ ░ ░