Skip to content

Instantly share code, notes, and snippets.

@laytan
Last active August 29, 2024 17:36
Show Gist options
  • Save laytan/afb84f03bbd2700a011f84c39be39e25 to your computer and use it in GitHub Desktop.
Save laytan/afb84f03bbd2700a011f84c39be39e25 to your computer and use it in GitHub Desktop.
Odin raw mode terminal
package main
import "core:c/libc"
import "core:fmt"
import "core:io"
import "core:os"
import "core:time"
import "core:unicode/utf8"
@(require)
import psx "core:sys/posix"
@(require)
import win "core:sys/windows"
orig_mode: (win.DWORD when ODIN_OS == .Windows else psx.termios)
enable_raw_mode :: proc() {
when ODIN_OS == .Windows {
// Get a handle to the standard input.
stdin := win.GetStdHandle(win.STD_INPUT_HANDLE)
assert(stdin != win.INVALID_HANDLE_VALUE)
// Get the original terminal mode.
ok := win.GetConsoleMode(stdin, &orig_mode)
assert(ok)
// Reset to the original attributes at the end of the program.
libc.atexit(disable_raw_mode)
// Copy, and remove the
// ENABLE_ECHO_INPUT (so what is typed is not shown) and
// ENABLE_LINE_INPUT (so we get each input instead of an entire line at once) flags.
raw := orig_mode
raw &= ~win.ENABLE_ECHO_INPUT
raw &= ~win.ENABLE_LINE_INPUT
ok = win.SetConsoleMode(stdin, raw)
assert(ok)
} else {
// Get the original terminal attributes.
res := psx.tcgetattr(psx.STDIN_FILENO, &orig_mode)
assert(res == .OK)
// Reset to the original attributes at the end of the program.
libc.atexit(disable_raw_mode)
// Copy, and remove the
// ECHO (so what is typed is not shown) and
// ICANON (so we get each input instead of an entire line at once) flags.
raw := orig_mode
raw.c_lflag -= {.ECHO, .ICANON}
res = psx.tcsetattr(psx.STDIN_FILENO, .TCSANOW, &raw)
assert(res == .OK)
}
}
disable_raw_mode :: proc "c" () {
when ODIN_OS == .Windows {
stdin := win.GetStdHandle(win.STD_INPUT_HANDLE)
assert(stdin != win.INVALID_HANDLE_VALUE)
win.SetConsoleMode(stdin, orig_mode)
} else {
psx.tcsetattr(psx.STDIN_FILENO, .TCSANOW, &orig_mode)
}
}
set_utf8_terminal :: proc() {
when ODIN_OS == .Windows {
win.SetConsoleOutputCP(win.CP_UTF8)
}
}
get_password :: proc(allocator := context.allocator) -> string {
enable_raw_mode()
defer disable_raw_mode()
fmt.print("Enter password: ")
buf := make([dynamic]byte, allocator)
in_stream := os.stream_from_handle(os.stdin)
for {
// Read a single character at a time.
ch, sz, err := io.read_rune(in_stream)
switch {
case err != nil:
fmt.eprintfln("\nError: %v", err)
os.exit(1)
case ch == '\n':
fmt.println()
return string(buf[:])
case ch == '\u007f': // Backspace.
_, bs_sz := utf8.decode_last_rune(buf[:])
if bs_sz > 0 {
resize(&buf, len(buf)-bs_sz)
// Replace last star with a space.
fmt.print("\b \b")
}
case:
bytes, _ := utf8.encode_rune(ch)
append(&buf, ..bytes[:sz])
fmt.print('*')
}
}
}
draw_progress_bar :: proc(title: string, percent: int, width := 25) {
fmt.printf("\r%v[", title, flush=false) // Put cursor back at the start of the line
done := percent * width / 100
left := width - done
for _ in 0..<done {
fmt.printf("|", flush=false)
}
for _ in 0..<left {
fmt.printf(" ", flush=false)
}
fmt.printf("] %d%%", percent)
}
main :: proc() {
set_utf8_terminal()
password := get_password()
defer delete(password)
for i in 0..=100 {
draw_progress_bar("Processing login: ", i)
time.sleep(50 * time.Millisecond)
}
fmt.println("\nDone")
fmt.printfln("\nYour password was: \"%s\"", password)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment