summaryrefslogtreecommitdiff
path: root/gui/draw/keyboard.go
diff options
context:
space:
mode:
authorCalvin Morrison <calvin@pobox.com>2026-08-22 13:16:35 -0400
committerCalvin Morrison <calvin@pobox.com>2026-08-22 13:16:35 -0400
commit9aacce8b3b54060d0037eca897856d7273e6e5e8 (patch)
treea98ea0cb151a7ded56fe681d6782986b2807dfcf /gui/draw/keyboard.go
parent5ef4699d05bc919255449a9af780f216a0589a72 (diff)
gui: a /dev/draw layer in go, with no cgo and no devdraw
9fans.net/go/draw builds for plan9 but shells out to plan9port's devdraw, which 9front does not have. This talks to /dev/draw itself: the protocol is file i/o, so a pure-go client is a few hundred lines under an already-complete idea. draw/keyboard.go opens /dev/cons and turns the console raw, as libdraw's initkeyboard does. Without it rio keeps its line editor on the window and paints what you type over the drawing. draw/snarf.go writes /dev/snarf, which is what every other program on the system means by copy. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'gui/draw/keyboard.go')
-rw-r--r--gui/draw/keyboard.go65
1 files changed, 65 insertions, 0 deletions
diff --git a/gui/draw/keyboard.go b/gui/draw/keyboard.go
new file mode 100644
index 0000000..3885364
--- /dev/null
+++ b/gui/draw/keyboard.go
@@ -0,0 +1,65 @@
+package draw
+
+import (
+ "bufio"
+ "os"
+ "unicode/utf8"
+)
+
+// Keyboardctl delivers runes typed at the window.
+//
+// Opening it also turns the console raw. Without that, rio keeps its own
+// line editor on the window and echoes what you type into the text layer,
+// painting over whatever the program has drawn. Holding consctl open is
+// what keeps raw mode; closing it puts the window back as it was.
+type Keyboardctl struct {
+ C <-chan rune
+ ctl *os.File
+ cons *os.File
+}
+
+func OpenKeyboard(dev string) (*Keyboardctl, error) {
+ ctl, err := os.OpenFile(dev+"/consctl", os.O_WRONLY, 0)
+ if err != nil {
+ return nil, err
+ }
+ if _, err := ctl.WriteString("rawon"); err != nil {
+ ctl.Close()
+ return nil, err
+ }
+ cons, err := os.Open(dev + "/cons")
+ if err != nil {
+ ctl.Close()
+ return nil, err
+ }
+
+ ch := make(chan rune, 32)
+ k := &Keyboardctl{C: ch, ctl: ctl, cons: cons}
+ go func() {
+ defer close(ch)
+ br := bufio.NewReader(cons)
+ var buf []byte
+ b := make([]byte, 1)
+ for {
+ n, err := br.Read(b)
+ if n == 0 || err != nil {
+ return
+ }
+ buf = append(buf, b[0])
+ // a rune may arrive a byte at a time
+ if r, sz := utf8.DecodeRune(buf); r != utf8.RuneError || sz > 1 {
+ ch <- r
+ buf = buf[:0]
+ } else if len(buf) >= utf8.UTFMax {
+ buf = buf[:0]
+ }
+ }
+ }()
+ return k, nil
+}
+
+func (k *Keyboardctl) Close() error {
+ k.cons.Close()
+ // dropping consctl is what restores rio's own line editing
+ return k.ctl.Close()
+}