Skip to content

Commit

Permalink
Merge pull request #55 from laytan/raw-console
Browse files Browse the repository at this point in the history
add raw console example
  • Loading branch information
laytan authored Oct 31, 2024
2 parents d3b4b8f + db0ad6b commit 5456b67
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ jobs:
odin check by_example/dynamic_arrays $FLAGS
odin check by_example/hellope $FLAGS
odin check by_example/os_args $FLAGS
odin check by_example/raw_console $FLAGS
odin check by_example/raw_console -target:windows_amd64 $FLAGS
odin check by_example/read_console_input $FLAGS
odin check by_example/strings $FLAGS
Expand Down
85 changes: 85 additions & 0 deletions by_example/raw_console/main.odin
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package main

import "core:fmt"
import "core:io"
import "core:os"
import "core:time"
import "core:unicode/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)
}

enable_raw_mode :: proc() {
_enable_raw_mode()
}

disable_raw_mode :: proc "c" () {
_disable_raw_mode()
}

set_utf8_terminal :: proc() {
_set_utf8_terminal()
}
30 changes: 30 additions & 0 deletions by_example/raw_console/raw_posix.odin
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#+build !windows
package main

import psx "core:sys/posix"

@(private="file")
orig_mode: psx.termios

_enable_raw_mode :: proc() {
// 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.
psx.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" () {
psx.tcsetattr(psx.STDIN_FILENO, .TCSANOW, &orig_mode)
}

_set_utf8_terminal :: proc() {}
41 changes: 41 additions & 0 deletions by_example/raw_console/raw_windows.odin
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package main

import "core:c/libc"
import win32 "core:sys/windows"

@(private="file")
orig_mode: win32.DWORD

_enable_raw_mode :: proc() {
// Get a handle to the standard input.
stdin := win32.GetStdHandle(win32.STD_INPUT_HANDLE)
assert(stdin != win32.INVALID_HANDLE_VALUE)

// Get the original terminal mode.
ok := win32.GetConsoleMode(stdin, &orig_mode)
assert(ok == true)

// 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 &= ~win32.ENABLE_ECHO_INPUT
raw &= ~win32.ENABLE_LINE_INPUT
ok = win32.SetConsoleMode(stdin, raw)
assert(ok == true)
}

_disable_raw_mode :: proc "c" () {
stdin := win32.GetStdHandle(win32.STD_INPUT_HANDLE)
assert_contextless(stdin != win32.INVALID_HANDLE_VALUE)

win32.SetConsoleMode(stdin, orig_mode)
}

_set_utf8_terminal :: proc() {
win32.SetConsoleOutputCP(.UTF8)
win32.SetConsoleCP(.UTF8)
}

0 comments on commit 5456b67

Please sign in to comment.