Skip to content

Instantly share code, notes, and snippets.

@quonic
Last active June 13, 2026 06:51
Show Gist options
  • Select an option

  • Save quonic/f3e2d56c5714165ef7d8638c87b94950 to your computer and use it in GitHub Desktop.

Select an option

Save quonic/f3e2d56c5714165ef7d8638c87b94950 to your computer and use it in GitHub Desktop.
"DVD" bouncing logo in the Linux terminal using alt mode and checking for SIGINT to quit cleanly
package main
import "core:fmt"
import "core:sync"
import "core:sys/linux"
import "core:time"
running := true
sigint_caught: b32
// ANSI Escape Sequences
ALT_SCREEN_ON: string : "\x1b[?1049h"
ALT_SCREEN_OFF: string : "\x1b[?1049l"
HIDE_CURSOR: string : "\x1b[?25l"
SHOW_CURSOR: string : "\x1b[?25h"
CLEAR_SCREEN: string : "\x1b[2J"
// Logo settings
LOGO_WIDTH := 6
LOGO_HEIGHT := 3
LOGO_STR: string : " ODIN "
// Instructions
instr := "Press Ctrl+C to exit"
main :: proc() {
when ODIN_OS != .Linux {
fmt.eprintln("Only works on Linux. Exiting.")
return
}
// Setup the native Linux Sig_Action structure
act: linux.Sig_Action
act.handler = handle_sigint
// sa_mask is mapped directly via an array of integers or sets depending on the target
// A zeroed struct means an empty signal mask block
act.flags = {}
// Register the signal handler via the rt_sigaction API
err := linux.rt_sigaction(.SIGINT, &act, nil)
if err != .NONE {
fmt.eprintln("Failed to set native Linux signal handler:", err)
return
}
// Setup alternate screen and hide cursor
fmt.print(ALT_SCREEN_ON)
fmt.print(HIDE_CURSOR)
fmt.print(CLEAR_SCREEN)
defer {
// Cleanup: Reset to main screen and show cursor on exit
fmt.print(ALT_SCREEN_OFF)
fmt.print(SHOW_CURSOR)
}
// Initial state
term_w, term_h := 80, 24 // Fallback default
// Try to fetch actual terminal size
if size, ok := get_terminal_size(); ok {
term_w, term_h = size.x, size.y
}
pos_x := term_w / 3
pos_y := term_h / 3
dir_x := 1
dir_y := 1
color_idx := 0
colors := []string {
"\x1b[31m", // Red
"\x1b[32m", // Green
"\x1b[33m", // Yellow
"\x1b[34m", // Blue
"\x1b[35m", // Magenta
"\x1b[36m", // Cyan
"\x1b[97m", // White
}
// Main loop
running := true
for running {
// Thread-safe check of our boolean flag using atomic operations
if sync.atomic_load(&sigint_caught) {
break
}
// Update terminal size dynamically
if size, ok := get_terminal_size(); ok {
term_w, term_h = size.x, size.y
}
// Clear screen for the frame
fmt.print(CLEAR_SCREEN)
// Move logo and handle collisions
pos_x += dir_x
pos_y += dir_y
color_changed := false
if pos_x <= 0 {
pos_x = 0
dir_x = 1
color_changed = true
} else if pos_x >= term_w - LOGO_WIDTH {
pos_x = term_w - LOGO_WIDTH
dir_x = -1
color_changed = true
}
if pos_y <= 0 {
pos_y = 0
dir_y = 1
color_changed = true
} else if pos_y >= term_h - LOGO_HEIGHT {
pos_y = term_h - LOGO_HEIGHT
dir_y = -1
color_changed = true
}
// Cycle color when hitting a wall
if color_changed {
color_idx = (color_idx + 1) % len(colors)
}
// Draw logo
move_cursor(pos_x, pos_y)
fmt.print(colors[color_idx])
fmt.print("┌──────┐")
move_cursor(pos_x, pos_y + 1)
fmt.print("")
fmt.print(LOGO_STR)
fmt.print("")
move_cursor(pos_x, pos_y + 2)
fmt.print("└──────┘")
// Draw instructions at the bottom
move_cursor((term_w - len(instr)) / 2, term_h - 1)
fmt.print(instr)
// Reset terminal text color
fmt.print("\x1b[0m")
// Sleep for 30ms (~33 FPS)
time.sleep(30 * time.Millisecond)
}
}
move_cursor :: proc(x: int, y: int) {
fmt.printf("\x1b[%d;%dH", y + 1, x + 1)
}
get_terminal_size :: proc() -> (pos: [2]int, ok: bool) {
when ODIN_OS == .Linux {
// Use ioctl to get terminal size
ioctl := linux.SYS_ioctl
Winsize :: struct {
ws_row: u16,
ws_col: u16,
ws_xpixel: u16,
ws_ypixel: u16,
}
ws: Winsize
fd := linux.STDIN_FILENO
err := linux.ioctl(fd, linux.TIOCGWINSZ, uintptr(&ws))
if err == 0 {
return [2]int{int(ws.ws_col), int(ws.ws_row)}, true
}
// Hardcoded fallback if ioctl fails
return [2]int{80, 24}, true
} else {
return [2]int{80, 24}, true
}
}
handle_sigint :: proc "c" (sig: linux.Signal) {
sync.atomic_store(&sigint_caught, true)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment