Skip to content

Instantly share code, notes, and snippets.

@antlis
Last active September 12, 2026 20:39
Show Gist options
  • Select an option

  • Save antlis/3301eae14f771e31340cd4781c948682 to your computer and use it in GitHub Desktop.

Select an option

Save antlis/3301eae14f771e31340cd4781c948682 to your computer and use it in GitHub Desktop.
rofi-ayugram-keybindings — AyuGram/Telegram shortcut cheatsheet that focuses the app (i3 criteria) and replays the key via xdotool. See https://antlis.is-a.dev/blog/rofi-cheatsheets
#!/usr/bin/env bash
#
# Mirrored as a public gist (embedded in the write-up) — if you edit this file,
# update the gist too: https://gist.github.com/antlis/3301eae14f771e31340cd4781c948682
# Article: https://antlis.is-a.dev/blog/rofi-cheatsheets
#
# AyuGram / Telegram Desktop keybindings cheatsheet
# Reads from AyuGramDesktop or TelegramDesktop shortcuts JSON.
# Also includes hardcoded Telegram shortcuts not in the JSON.
# On Enter: focus the AyuGram window and replay the shortcut with xdotool
# (these are app-internal keys, not exec/i3 commands). Rows with no single
# replayable key — section headers, blanks, "(not bound)", "Left/Right" — are
# skipped. NB: this fires the real action, including Delete / send / schedule.
TDATA_DIRS=(
"${HOME}/.local/share/AyuGramDesktop/tdata"
"${HOME}/.local/share/TelegramDesktop/tdata"
)
DEFAULTS=""
CUSTOM=""
for dir in "${TDATA_DIRS[@]}"; do
if [[ -f "$dir/shortcuts-default.json" ]]; then
DEFAULTS="$dir/shortcuts-default.json"
CUSTOM="$dir/shortcuts-custom.json"
break
fi
done
if [[ -z "$DEFAULTS" ]]; then
notify-send "keybindings" "No Telegram/Ayugram shortcuts config found"
exit 1
fi
strip_json_comments() {
perl -pe 's{//.*}{}' | perl -0777 -pe 's/,\s*([}\]])/\1/g'
}
declare -A cmd_desc=(
[search]="Search"
[self_chat]="Saved Messages"
[all_chats]="All Chats"
[folder1]="Folder 1"
[folder2]="Folder 2"
[folder3]="Folder 3"
[folder4]="Folder 4"
[folder5]="Folder 5"
[folder6]="Folder 6"
[last_folder]="Last Folder"
[show_archive]="Archive"
[show_contacts]="Contacts"
[lock_telegram]="Lock"
[minimize_telegram]="Minimize"
[quit_telegram]="Quit"
[read_chat]="Read Chat"
[record_voice]="Record Voice"
[close_telegram]="Close"
[show_chat_menu]="Chat Menu"
[show_chat_preview]="Chat Preview"
[previous_chat]="Previous Chat"
[next_chat]="Next Chat"
[previous_folder]="Previous Folder"
[next_folder]="Next Folder"
[first_chat]="First Chat"
[last_chat]="Last Chat"
[pinned_chat1]="Pinned Chat 1"
[pinned_chat2]="Pinned Chat 2"
[pinned_chat3]="Pinned Chat 3"
[pinned_chat4]="Pinned Chat 4"
[pinned_chat5]="Pinned Chat 5"
[pinned_chat6]="Pinned Chat 6"
[pinned_chat7]="Pinned Chat 7"
[pinned_chat8]="Pinned Chat 8"
[media_play]="Play"
[media_stop]="Stop"
[media_previous]="Previous Track"
[media_next]="Next Track"
[media_pause]="Pause"
[media_playpause]="Play/Pause"
[account1]="Account 1"
[account2]="Account 2"
[account3]="Account 3"
[account4]="Account 4"
[account5]="Account 5"
[account6]="Account 6"
[message]="Message"
[message_silently]="Silent Message"
[message_scheduled]="Scheduled Message"
[media_viewer_video_fullscreen]="Video Fullscreen"
[show_scheduled]="Scheduled Messages"
[archive_chat]="Archive Chat"
[record_round]="Record Round Video"
[reaction_send]="Send Reaction"
[scroll_to_unread]="Scroll to Unread"
[toggle_mute]="Toggle Mute"
[edit_message]="Edit Message"
[delete_message]="Delete Message"
[forward_message]="Forward Message"
[reply_message]="Reply"
[select_message]="Select Message"
[show_my_profile]="My Profile"
[show_phone]="Show Phone"
[show_username]="Show Username"
[show_bio]="Show Bio"
[boost_status]="Boost Status"
[open_folder]="Open Folder"
[create_new_group]="New Group"
[create_new_channel]="New Channel"
[create_new_secret_chat]="New Secret Chat"
[show_settings]="Settings"
[show_calls]="Calls"
[next_theme]="Next Theme"
[previous_theme]="Previous Theme"
[toggle_night_mode]="Night Mode"
[show_all_chats]="Show All Chats"
)
rows=()
# ── Configurable shortcuts from JSON (defaults + custom overrides) ──
while IFS=$'\t' read -r keys cmd; do
desc="${cmd_desc[$cmd]:-$cmd}"
if [[ "$keys" == "null" ]]; then
pretty="(not bound)"
else
pretty="${keys^^}"
pretty="${pretty//CTRL+/Ctrl+}"
fi
rows+=("$(printf '%-24s %s' "$pretty" "$desc")")
done < <(
merged=$(mktemp)
trap "rm -f $merged" RETURN
strip_json_comments < "$DEFAULTS" | jq '[.[] | select(.command)]' > "$merged"
# Apply custom overrides if they exist
if [[ -n "$CUSTOM" && -f "$CUSTOM" ]]; then
strip_json_comments < "$CUSTOM" | jq -r '
.[] | select(.command and .keys != null)
| "\(.command)\t\(.keys)"
' | while IFS=$'\t' read -r cmd keys; do
jq --arg cmd "$cmd" --arg keys "$keys" '
map(if .command == $cmd then .keys = $keys else . end)
' "$merged" > "$merged.tmp" && mv "$merged.tmp" "$merged"
done
fi
jq -r '.[] | select(.command) | "\(.keys)\t\(.command)"' "$merged"
)
# ── Hardcoded shortcuts (not in JSON, always active) ──
rows+=("")
rows+=("── Text Formatting ──")
rows+=("$(printf '%-24s %s' "Ctrl+B" "Bold")")
rows+=("$(printf '%-24s %s' "Ctrl+I" "Italic")")
rows+=("$(printf '%-24s %s' "Ctrl+U" "Underline")")
rows+=("$(printf '%-24s %s' "Ctrl+Shift+X" "Strikethrough")")
rows+=("$(printf '%-24s %s' "Ctrl+Shift+M" "Mono")")
rows+=("$(printf '%-24s %s' "Ctrl+Shift+N" "Plain Text")")
rows+=("$(printf '%-24s %s' "Ctrl+K" "Insert Link")")
rows+=("$(printf '%-24s %s' "Ctrl+Shift+E" "Spoiler")")
rows+=("")
rows+=("── Editing ──")
rows+=("$(printf '%-24s %s' "Up" "Edit Last Message")")
rows+=("$(printf '%-24s %s' "Ctrl+Z" "Undo")")
rows+=("$(printf '%-24s %s' "Ctrl+Shift+Z" "Redo")")
rows+=("$(printf '%-24s %s' "Ctrl+X" "Cut")")
rows+=("$(printf '%-24s %s' "Ctrl+C" "Copy")")
rows+=("$(printf '%-24s %s' "Ctrl+V" "Paste")")
rows+=("$(printf '%-24s %s' "Ctrl+A" "Select All")")
rows+=("$(printf '%-24s %s' "Ctrl+Del" "Delete Word")")
rows+=("$(printf '%-24s %s' "Ctrl+Backspace" "Delete Word Left")")
rows+=("")
rows+=("── Navigation ──")
rows+=("$(printf '%-24s %s' "Alt+Left" "Go Back")")
rows+=("$(printf '%-24s %s' "Alt+Right" "Go Forward")")
rows+=("$(printf '%-24s %s' "Ctrl+Tab" "Next Chat (unread)")")
rows+=("$(printf '%-24s %s' "Ctrl+Shift+Tab" "Prev Chat (unread)")")
rows+=("$(printf '%-24s %s' "Alt+Enter" "Jump to Bottom")")
rows+=("")
rows+=("── Messages ──")
rows+=("$(printf '%-24s %s' "Ctrl+Up" "Reply")")
rows+=("$(printf '%-24s %s' "Ctrl+Down" "Cancel Reply")")
rows+=("$(printf '%-24s %s' "Esc" "Cancel / Close")")
rows+=("$(printf '%-24s %s' "Ctrl+F" "Search in Chat")")
rows+=("$(printf '%-24s %s' "Ctrl+Shift+F" "Global Search")")
rows+=("$(printf '%-24s %s' "Delete" "Delete Message")")
rows+=("$(printf '%-24s %s' "Ctrl+Shift+R" "React to Message")")
rows+=("$(printf '%-24s %s' "Ctrl+S" "Schedule Message")")
rows+=("$(printf '%-24s %s' "Ctrl+Shift+D" "Send without Sound")")
rows+=("")
rows+=("── Media ──")
rows+=("$(printf '%-24s %s' "Space" "Play/Pause Media")")
rows+=("$(printf '%-24s %s' "Left/Right" "Seek (in media)")")
rows+=("$(printf '%-24s %s' "F" "Fullscreen (media)")")
rows+=("$(printf '%-24s %s' "Ctrl+Shift+S" "Save As")")
(( ${#rows[@]} == 0 )) && exit 0
sel=$(printf '%s\n' "${rows[@]}" | rofi \
-theme-str 'window { fullscreen: true; } mainbox { padding: 2%; } listview { columns: 2; }' \
-dmenu -i -p 'AyuGram keys' \
-mesg 'AyuGram / Telegram keybindings')
# The key is always the first column (keys never contain spaces); the rest is the
# description. Skip rows that aren't a single replayable keypress.
key="${sel%% *}"
case "$key" in
''|'──'* | '(not') exit 0 ;; # blank / section header / "(not bound)"
esac
[[ "$key" == *"/"* ]] && exit 0 # e.g. Left/Right — not one keypress
# Convert a display combo ("Ctrl+Shift+R") to an xdotool keyspec ("ctrl+shift+r").
to_keyspec() {
local part out=()
local IFS='+'
read -ra parts <<< "$1"
for part in "${parts[@]}"; do
case "${part,,}" in
ctrl|control) out+=("ctrl") ;;
shift) out+=("shift") ;;
alt) out+=("alt") ;;
super|win|meta|cmd) out+=("super") ;;
*)
case "$part" in
Del|Delete) out+=("Delete") ;;
Backspace) out+=("BackSpace") ;;
Enter|Return) out+=("Return") ;;
Esc|Escape) out+=("Escape") ;;
Space) out+=("space") ;;
Tab) out+=("Tab") ;;
PageUp) out+=("Prior") ;;
PageDown) out+=("Next") ;;
Up|Down|Left|Right|Home|End|Insert) out+=("$part") ;;
F[0-9]|F1[0-2]) out+=("$part") ;;
[0-9]) out+=("$part") ;;
[A-Za-z]) out+=("${part,,}") ;;
*) return 1 ;; # unknown token → not fireable
esac ;;
esac
done
echo "${out[*]}"
}
keyspec=$(to_keyspec "$key") || { notify-send "AyuGram keys" "Can't replay: $key"; exit 0; }
# Bail if AyuGram isn't running (i3-msg focus reports success even on zero matches,
# so check for a window first).
xdotool search --class AyuGram >/dev/null 2>&1 \
|| { notify-send "AyuGram keys" "No AyuGram window found"; exit 1; }
# Focus via i3 criteria (matches the real managed window) — xdotool windowactivate
# is unreliable on i3 and picks AyuGram's override-redirect helper windows. Same
# approach telepad uses (~/Projects/github/telepad, src/link.rs).
i3-msg '[class="AyuGramDesktop"] focus' >/dev/null 2>&1
sleep 0.15
xdotool key --clearmodifiers "$keyspec"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment