Skip to content

Instantly share code, notes, and snippets.

@YclepticStudios
Last active March 3, 2026 06:28
Show Gist options
  • Select an option

  • Save YclepticStudios/bf8fc41c4074217fe741286a2359e82b to your computer and use it in GitHub Desktop.

Select an option

Save YclepticStudios/bf8fc41c4074217fe741286a2359e82b to your computer and use it in GitHub Desktop.
Helper script for recording Pebble watch emulator GIFs on Linux (tested on Ubuntu 24)
#!/bin/bash
# =============================================================================
# record_pebble.sh
#
# DESCRIPTION:
# Records the selected Pebble emulator window and converts the captured
# output to a minimal, pixel perfect animated GIF.
#
# USAGE:
# ./record_pebble.sh [output.gif]
#
# DEPENDENCIES:
# sudo apt install xdotool ffmpeg xwininfo
#
# TIPS:
# - Call 'light_enable(true)' in your app's startup handler to keep the
# backlight at full brightness throughout the recording.
# - Disable vibrations, if your app has them, to prevent the emulator screen
# from shaking and interfering with the recording.
# =============================================================================
set -euo pipefail
# Variables
OUTPUT="${1:-output.gif}"
TMP_DIR=$(mktemp -d)
MP4="$TMP_DIR/recording.mp4"
DECIMATED="$TMP_DIR/decimated.mp4"
PALETTE="$TMP_DIR/palette.png"
FPS=15
# Error handling
trap 'echo "Error on line $LINENO" >&2; rm -rf "$TMP_DIR"' ERR
trap 'rm -rf "$TMP_DIR"' EXIT
# Get targeted window information
echo "Click the emulator window to record..."
WIN_INFO=$(xwininfo)
WIN_ID=$(echo "$WIN_INFO" | grep "Window id:" | awk '{print $4}')
X=$(echo "$WIN_INFO" | grep "Absolute upper-left X" | awk '{print $NF}')
Y=$(echo "$WIN_INFO" | grep "Absolute upper-left Y" | awk '{print $NF}')
W=$(echo "$WIN_INFO" | grep "Width:" | awk '{print $NF}')
H=$(echo "$WIN_INFO" | grep "Height:" | awk '{print $NF}')
# Remove padding from basalt emulator (better way to do this?)
if [[ "$W" -eq 148 && "$H" -eq 172 ]]; then
X=$((X + 2))
Y=$((Y + 2))
W=$((W - 4))
H=$((H - 4))
fi
# Transfer focus from the terminal to the selected window
xdotool windowactivate --sync "$WIN_ID"
# Capture the screen as a lossless video to the temp directory
echo "Capturing window. Press Ctrl+C to stop recording..."
ffmpeg -f x11grab -draw_mouse 0 -framerate $FPS -i "$DISPLAY" \
-vf "crop=${W}:${H}:${X}:${Y}" -c:v libx264rgb -crf 0 "$MP4" -v error || true
# Strip out unused frames, maintaining the lossless quality
echo "Processing recording..."
ffmpeg -i "$MP4" -vf "mpdecimate,fps=$FPS" -c:v libx264rgb -crf 0 \
"$DECIMATED" -v error
# Generate a minimal color pallet
ffmpeg -i "$DECIMATED" -vf "palettegen=max_colors=64:reserve_transparent=0" \
"$PALETTE" -v error
# Encode the video as a gif
ffmpeg -i "$DECIMATED" -i "$PALETTE" \
-filter_complex "[0:v][1:v]paletteuse=dither=none" -y "$OUTPUT" -v error
echo "GIF saved to: $OUTPUT"
@YclepticStudios
Copy link
Author

YclepticStudios commented Feb 28, 2026

Aplite Basalt Chalk Diorite Emery Flint Gabbro
Aplite Basalt Chalk Diorite Emery Flint Gabbro

See the script header for dependencies and instructions. Note that this script will likely only work on similar environments (tested on Ubuntu 24), but could likely be modified to work on more systems relatively easily.

Example usage and output:

./record_pebble.sh output.gif
Click the emulator window to record...
Capturing window. Press Ctrl+C to stop recording...
Processing recording...
GIF saved to: output.gif

@ericmigi
Copy link

ericmigi commented Mar 2, 2026

@YclepticStudios very cool! Do you mind if I borrow this method and incorporate into https://github.com/coredevices/pebble-tool?

@ericmigi
Copy link

ericmigi commented Mar 2, 2026

just merged it - it works on Mac, want to try on Ubuntu 24.04?

@YclepticStudios
Copy link
Author

@ericmigi Don't mind at all! I had to change this line to get it to work on Linux (do see caveats below though).

--- a/pebble_tool/commands/screenshot.py
+++ b/pebble_tool/commands/screenshot.py
@@ -270,7 +270,7 @@ class ScreenshotCommand(PebbleCommand):
                                 capture_output=True, text=True)
         if result.returncode != 0 or not result.stdout.strip():
             raise ToolError("Could not find QEMU emulator window. Is the emulator running?")
-        win_id = result.stdout.strip().split('\n')[0]
+        win_id = result.stdout.strip().split('\n')[-1]
 
         result = subprocess.run(['xwininfo', '-id', win_id],
                                 capture_output=True, text=True)

Now for the caveats. This method is going to be quite brittle, at least on Linux. The ffmpeg video processing part should be pretty portable, but the window capturing is not very robust.

  1. Using x11grab will probably fail on Wayland based systems depending on how QEMU is running
  2. Using xdotool search and grabbing the first (or last) window handle isn't deterministic. Ignoring the potential for multiple windows, each QEMU window has 3 window handles. You want the inner most one to avoid dealing with window title and background shading offsets, but the order they are returned from xdotool isn't deterministic. You would probably have to query the hierarchy to get that method to work reliably. I was able to avoid that issue in my script by grabbing the window the user clicked on, though that's obviously not your situation.
  3. This will likely break on multi-monitor systems since it assumes the first display when capturing (I use a single monitor, so couldn't test).

Cool stuff though and I'd love to see it implemented into pebble-tool! Might want a more portable window IDing / recording method though.

@ericmigi
Copy link

ericmigi commented Mar 3, 2026

Thanks, I just pushed a fix!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment