#!/usr/bin/env sh #* jq to parse and create a metadata. #* Users are advised to use bindd to explicitly add the description #* Please inform us if there are new Categories upstream will try to add comments to this script #* Khing šŸ¦† pkill -x rofi && exit scrDir=$(dirname "$(realpath "$0")") source "$scrDir/globalcontrol.sh" confDir="${XDG_CONFIG_HOME:-$HOME/.config}" keyconfDir="$confDir/hypr" kb_hint_conf=("$keyconfDir/hyprland.conf" "$keyconfDir/keybindings.conf" "$keyconfDir/userprefs.conf" ) tmpMapDir="/tmp" tmpMap="$tmpMapDir/hyde-keybinds.jq" keycodeFile="${hydeConfDir}/keycode.kb" modmaskFile="${hydeConfDir}/modmask.kb" keyFile="${hydeConfDir}/key.kb" categoryFile="${hydeConfDir}/category.kb" dispatcherFile="${hydeConfDir}/dispatcher.kb" roDir="$confDir/rofi" roconf="$roDir/clipboard.rasi" HELP() { cat <') -f Add custom file -w Custom width -h Custom height --help Display this help message Example: $(basename "$0") -j -p -d '>' -f custom_file.txt -w 80 -h 90" Users can also add a global overrides inside ${hydeConfDir}/hyde.conf Available overrides: kb_hint_delim=">" ﯦ add a custom custom delimiter kb_hint_conf=("file1.conf" "file2.conf") ﯦ add a custom keybinds.conf path (add it like an array) kb_hint_width="30em" ﯦ custom width supports [ 'em' '%' 'px' ] kb_hint_height="35em" ﯦ custom height supports [ 'em' '%' 'px' ] kb_hint_line=13 ﯦ adjust how many lines are listed Users can also add a key overrides inside ${hydeConfDir} List of file override: ${keycodeFile} => keycode ${modmaskFile} => modmask ${keyFile} => keys ${categoryFile} => category ${dispatcherFile} => dispatcher Example for keycode override create a file named $keycodeFile and use the following format: "number": "display name/symbol", "61": "/", HELP } while [ "$#" -gt 0 ]; do case "$1" in -j) # show the json format kb_hint_json=true ;; -p) # show the pretty format kb_hint_pretty=true ;; -d) # Add custom delimiter symbol default '>' shift kb_hint_delim="$1" ;; -f) # Add custom file shift kb_hint_conf+=("${@}") ;; -w) # Custom kb_hint_width shift kb_hint_width="$1" ;; -h) # Custom height shift kb_hint_height="$1" ;; -l) # Custom number of line shift kb_hint_line="$1" ;; -*) # Add Help message HELP exit ;; esac shift done #? Read all the variables in the configuration file #! Intentional globbing on the $keyconf variable # shellcheck disable=SC2086 keyVars="$(awk -F '=' '/^ *\$/ && !/^ *#[^#]/ || /^ *##/ {gsub(/^ *\$| *$/, "", $1); gsub(/#.*/, "", $2); gsub(/^ *| *$/, "", $2); print $1 "='\''"$2"'\''"}' ${kb_hint_conf[@]})" keyVars+=" " keyVars+="HOME=$HOME" # echo "$keyVars" #? This part substitutes the variables into the actual value. #TODO It will be easier if hyprland will expose the variables. substitute_vars() { local s="$1" local IFS=$'\n' for var in $keyVars; do varName="${var%%=*}" varValue="${var#*=}" varValue="${varValue#\'}" varValue="${varValue%\'}" s="${s//\$$varName/$varValue}" done IFS=$' \t\n' echo "$s" } #? Other accurate but risky option # eval "$keyVars" # substitute_vars() { # local s="$1" # local IFS=$'\n' # for var in $keyVars; do # varName="${var%%=*}" # s="${s//\$$varName/${!varName}}" # done # IFS=$' \t\n' # echo "$s" # } initialized_comments=$(awk -F ',' '!/^#/ && /bind*/ && $3 ~ /exec/ && NF && $4 !~ /^ *$/ { print $4}' ${kb_hint_conf[@]} | sed "s#\"#'#g") comments=$(substitute_vars "$initialized_comments" | awk -F'#' \ '{gsub(/^ */, "", $1);\ gsub(/ *$/, "", $1);\ split($2, a, " ");\ a[1] = toupper(substr(a[1], 1, 1)) substr(a[1], 2);\ $2 = a[1]; for(i=2;\ i<=length(a); i++) $2 = $2" "a[i];\ gsub(/^ */, "", $2);\ gsub(/ *$/, "", $2);\ if (length($1) > 0) print "\""$1"\" : \""(length($2) > 0 ? $2 : $1)"\","}' | awk '!seen[$0]++') # echo "$comments" cat <$tmpMap # hyde-keybinds.jq #! This is Our Translator for some binds #šŸ¦† def executables_mapping: { #? Derived from .args to parse scripts to be Readable #? Auto Generated Comment Conversion $comments #? Defaults " empty " : "Empty", "r+1" : "Relative Right", "r-1" : "Relative Left", "e+1" : "Next", "e-1" : "Previous", "movewindow" : "Move window", "resizewindow" : "Resize window", "d" : "Down", "l" : "Left", "r" : "Right", "u" : "Up", "f" : "Forward", "b" : "Backward", }; def keycode_mapping: { #? Fetches keycode from a file "0": "", $([ -f "${keycodeFile}" ] && cat "${keycodeFile}") }; def modmask_mapping: { #? Define mapping for modmask numbers represents bitmask "64": "ļ‰ā€‚", #? SUPER ļ‰ 󰻀 Also added 2 En space ' ' <<< "8": "ALT", "4": "CTRL", "1": "SHIFT", "0": " ", $([ -f "${modmaskFile}" ] && cat "${modmaskFile}") }; def key_mapping: { #?Define mappings for .keys to be readable symbols "mouse_up" : "󱕑", "mouse_down" : "󱕐", "mouse:272" : "ī’ó°½", "mouse:273" : "ó°½īæ", "UP" : "", "DOWN" : "", "LEFT" : "", "RIGHT" : "", "XF86AudioLowerVolume" : "󰝞", "XF86AudioMicMute" : "󰍭", "XF86AudioMute" : "󰓄", "XF86AudioNext" : "󰒭", "XF86AudioPause" : "󰏤", "XF86AudioPlay" : "󰐊", "XF86AudioPrev" : "󰒮", "XF86AudioRaiseVolume" : "󰝝", "XF86MonBrightnessDown" : "󰃜", "XF86MonBrightnessUp" : "󰃠", "switch:on:Lid Switch" : "󰛧", "backspace" : "󰁮 ", $([ -f "${keyFile}" ] && cat "${keyFile}") }; def category_mapping: { #? Define Category Names, derive from Dispatcher #? This will serve as the Group header "exec" : "Execute a Command:", "global": "Global:", "exit" : "Exit Hyprland Session", "fullscreen" : "Toggle Functions", "fakefullscreen" : "Toggle Functions", "mouse" : "Mouse functions", "movefocus" : "Window functions", "movewindow" : "Window functions", "resizeactive" : "Window functions", "togglefloating" : "Toggle Functions", "togglegroup" : "Toggle Functions", "togglespecialworkspace" : "Toggle Functions", "togglesplit" : "Toggle Functions", "workspace" : "Navigate Workspace", "movetoworkspace" : "Navigate Workspace", "movetoworkspacesilent" : "Navigate Workspace", "changegroupactive" : "Change Active Group", $([ -f "${categoryFile}" ] && cat "${categoryFile}") }; def arg_mapping: { #! Do not Change this used for Demo only... As this will change .args! will be fatal "arg2": "mapped_arg2", }; def description_mapping: { #? Derived from dispatcher and Gives Description for Dispatchers; Basically translates dispatcher. "movefocus": "Move Focus", "resizeactive": "Resize Active Floating Window", "exit" : "End Hyprland Session", "movetoworkspacesilent" : "Silently Move to Workspace", "movewindow" : "Move Window", "exec" : "" , #? Remove exec as executable will give the Description from separate function "movetoworkspace" : "Move To Workspace:", "workspace" : "Navigate to Workspace:", "togglefloating" : "Toggle Floating", "fullscreen" : "Toggle Fullscreen", "togglegroup" : "Toggle Group", "togglesplit" : "Toggle Split", "togglespecialworkspace" : "Toggle Special Workspace", "mouse" : "Use Mouse", "changegroupactive" : "Switch Active group", $([ -f "${dispatcherFile}" ] && cat "${dispatcherFile}") }; OVERRIDES #? Script to re Modify hyprctl json output #? Basically we are using jq to handle json data and outputs a pretty and friendly output jsonData="$( hyprctl binds -j | jq -L "$tmpMapDir" -c ' include "hyde-keybinds"; #? Functions to Convert modmask into Keys, There should be a better math for this but Im lazy #? Also we can just map it manually too def get_keys: if . == 0 then "" elif . >= 64 then . -= 64 | modmask_mapping["64"] + " " + get_keys elif . >= 32 then . -= 32 | modmask_mapping["32"] + " " + get_keys elif . >= 16 then . -= 16 | modmask_mapping["16"] + " " + get_keys elif . >= 8 then . -= 8 | modmask_mapping["8"] + " " + get_keys elif . >= 4 then . -= 4 | modmask_mapping["4"] + " " + get_keys elif . >= 2 then . -= 2 | modmask_mapping["2"] + " " + get_keys elif . >= 1 then . -= 1 | modmask_mapping["1"] + " " + get_keys else . end; def get_keycode: (keycode_mapping[(. | tostring)] // .); #? use the keycode conversion... and turn to string .[] | #? Filter 1 .dispatcher as $dispatcher | .desc_dispatcher = $dispatcher | #? Value conversions for the description .dispatcher as $dispatcher | .category = $dispatcher | #? Value conversions for the category .arg as $arg | .desc_executable = $arg | #? creates new key .desc_executable to be use later .modmask |= (get_keys | ltrimstr(" ")) | #? Execute Momask conversions, b .keycode |= (get_keycode // .) | #? Apply the get_keycode transformation .key |= (key_mapping[.] // .) | #? Apply the get_key # .keybind = (.modmask | tostring // "") + (.key // "") | #! Same as below but without the keycode .keybind = (.modmask | tostring // "") + (.key // "") + ((.keycode // 0) | tostring) | #? Show the keybindings .flags = " locked=" + (.locked | tostring) + " mouse=" + (.mouse | tostring) + " release=" + (.release | tostring) + " repeat=" + (.repeat | tostring) + " non_consuming=" + (.non_consuming | tostring) | #? This are the flags repeat,lock etc .category |= (category_mapping[.] // .) | #? Group by Categories will be use for headers #!if .modmask and .modmask != " " and .modmask != "" then .modmask |= (split(" ") | map(select(length > 0)) | if length > 1 then join(" + ") else .[0] end) else .modmask = "" end | if .keybind and .keybind != " " and .keybind != "" then .keybind |= (split(" ") | map(select(length > 0)) | if length > 1 then join(" + ") else .[0] end) else .keybind = "" end | #? Clean up .arg |= (arg_mapping[.] // .) | #? See above for how arg is converted #! .desc_executable |= gsub(".sh"; "") | #? Maybe Useful soon removes ".sh" to file #? Creates a key desc... for fallback if "has description" is false .desc_executable |= (executables_mapping[.] // .) | #? exclusive for "exec" dispatchers .desc_dispatcher |= (description_mapping[.] // .) | #? for all other dispatchers .description = if .has_description == false then "\(.desc_dispatcher) \(.desc_executable)" else.description end ' #* <---- There is a ' do not delete this' )" #? Now we have the metadata we can Group it accordingly GROUP() { awk -v cols="$cols" -F '!=!' ' { category = $1 binds[category] = binds[category]? binds[category] "\n" $0 : $0 } END { n = asorti(binds, b) for (i = 1; i <= n; i++) { print b[i] # Print the header name gsub(/\[.*\] =/, "", b[i]) split(binds[b[i]], lines, "\n") for (j in lines) { line = substr(lines[j], index(lines[j], "=") + 2) print line } for (j = 1; j <= cols; j++) printf "━" printf "\n" } }' } #? Display the JSON format [ "$kb_hint_json" = true ] && jq <<< "$jsonData" && exit 0 #? Format this is how the keybinds are displayed. DISPLAY() { awk -v kb_hint_delim="${kb_hint_delim:->}" -F '!=!' '{if ($0 ~ /=/ && $6 != "") printf "%-25s %-2s %-30s\n", $5, kb_hint_delim, $6; else if ($0 ~ /=/) printf "%-25s\n", $5; else print $0}'; } #? Extra design use for distinction header="$(printf "%-35s %-1s %-20s\n" "󰌌 Keybinds" "ó±§£" "Description")" cols=$(tput cols) cols=${cols:-999} linebreak="$(printf '%.0s━' $(seq 1 "${cols}") "")" #! this Part Gives extra loading time as I don't have efforts to make single space for each class metaData="$(jq -r '"\(.category) !=! \(.modmask) !=! \(.key) !=! \(.dispatcher) !=! \(.arg) !=! \(.keybind) !=! \(.description) !=! \(.flags)"' <<< "${jsonData}" | tr -s ' ' | sort -k 1)" #? This formats the pretty output display="$(GROUP <<< "$metaData" | DISPLAY)" # output=$(echo -e "${header}\n${linebreak}\n${primMenu}\n${linebreak}\n${display}") output=$(echo -e "${header}\n${linebreak}\n${display}") [ "$kb_hint_pretty" = true ] && echo -e "$output" && exit 0 #? will display on the terminal if rofi is not found or have -j flag if ! command -v rofi &>/dev/null; then echo "$output" echo "rofi not detected. Displaying on terminal instead" exit 0 fi #? Put rofi configuration here # Read hypr theme border wind_border=$((hypr_border * 3 / 2)) elem_border=$([ "$hypr_border" -eq 0 ] && echo "5" || echo "$hypr_border") # TODO Dynamic scaling for text and the window >>> I do not know if rofi is capable of this r_width="width: ${kb_hint_width:-35em};" r_height="height: ${kb_hint_height:-35em};" r_listview="listview { lines: ${kb_hint_line:-13}; }" r_override="window {$r_height $r_width border: ${hypr_width}px; border-radius: ${wind_border}px;} entry {border-radius: ${elem_border}px;} element {border-radius: ${elem_border}px;} ${r_listview} " # Read hypr font size fnt_override=$(gsettings get org.gnome.desktop.interface font-name | awk '{gsub(/'\''/,""); print $NF}') fnt_override="configuration {font: \"JetBrainsMono Nerd Font ${fnt_override}\";}" # Read hypr theme icon icon_override=$(gsettings get org.gnome.desktop.interface icon-theme | sed "s/'//g") icon_override="configuration {icon-theme: \"${icon_override}\";}" #? Actions to do when selected selected=$(echo "$output" | rofi -dmenu -p -i -theme-str "${fnt_override}" -theme-str "${r_override}" -theme-str "${icon_override}" -config "${roconf}" | sed 's/.*\s*//') if [ -z "$selected" ]; then exit 0; fi sel_1=$(awk -F "${kb_hint_delim:->}" '{print $1}' <<< "$selected" | awk '{$1=$1};1') sel_2=$(awk -F "${kb_hint_delim:->}" '{print $2}' <<< "$selected" | awk '{$1=$1};1') run="$(grep "$sel_1" <<< "$metaData" | grep "$sel_2")" run_flg="$(echo "$run" | awk -F '!=!' '{print $8}')" run_sel="$(echo "$run" | awk -F '!=!' '{gsub(/^ *| *$/, "", $5); if ($5 ~ /[[:space:]]/ && $5 !~ /^[0-9]+$/ && substr($5, 1, 1) != "-") print $4, "\""$5"\""; else print $4, $5}')" # echo "$run_sel" ; echo "$run_flg" #? RUN() { case "$(eval "hyprctl dispatch $run_sel")" in *"Not enough arguments"*) exec $0 ;; esac } #? If flag is repeat then repeat rofi if not then just execute once if [ -n "$run_sel" ] && [ "$(echo "$run_sel" | wc -l)" -eq 1 ]; then eval "$run_flg" if [ "$repeat" = true ]; then while true; do repeat_command=$(echo -e "Repeat" | rofi -dmenu -no-custom -p "[Enter] repeat; [ESC] exit") #? Needed a separate Rasi ? Dunno how to make; Maybe Something like confirmation rasi for buttons Yes and No then the -p will be the Question like Proceed? Repeat? if [ "$repeat_command" = "Repeat" ]; then # Repeat the command here RUN else exit 0 fi done else RUN fi else exec $0 fi