diff options
Diffstat (limited to 'liust-50/cmd/liustatus/status.go')
| -rw-r--r-- | liust-50/cmd/liustatus/status.go | 208 | 
1 files changed, 208 insertions, 0 deletions
diff --git a/liust-50/cmd/liustatus/status.go b/liust-50/cmd/liustatus/status.go new file mode 100644 index 0000000..50ec065 --- /dev/null +++ b/liust-50/cmd/liustatus/status.go @@ -0,0 +1,208 @@ +package main + +import ( +	"fmt" +	"os" +	"strings" +	"time" + +	"janouch.name/desktop-tools/liust-50/charset" +) + +// TODO(p): Make more elaberate animations with these. +var kaomoji = []struct { +	kao, message string +}{ +	{"(o_o)", ""}, +	{"(゚ロ゚)", ""}, +	{"(゚∩゚)", ""}, +	{"(^_^)", ""}, +	{"  (^_^)", ""}, +	{"(^_^)  ", ""}, +	{"(x_x)", "ズキズキ"}, +	{"(T_T)", "ズーン"}, +	{"=^.^=", "ニャー"}, +	{"(>_<)", "ゲップ"}, +	{"(O_O)", "ジー"}, +	{"(-_-)", ""}, +	{"(-_-)", "グーグー"}, +	{"(o_-)", ""}, +	{"(-_o)", ""}, +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +const ( +	displayWidth  = 20 +	displayHeight = 2 +	targetCharset = 0x63 +) + +type DisplayState struct { +	Display [displayHeight][displayWidth]uint8 +} + +type Display struct { +	Current, Last DisplayState +} + +func NewDisplay() *Display { +	t := &Display{} +	for y := 0; y < displayHeight; y++ { +		for x := 0; x < displayWidth; x++ { +			t.Current.Display[y][x] = ' ' +			t.Last.Display[y][x] = ' ' +		} +	} +	return t +} + +func (t *Display) SetLine(row int, content string) { +	if row < 0 || row >= displayHeight { +		return +	} + +	runes := []rune(content) +	for x := 0; x < displayWidth; x++ { +		if x < len(runes) { +			b, ok := charset.ResolveRune(runes[x], targetCharset) +			if ok { +				t.Current.Display[row][x] = b +			} else { +				t.Current.Display[row][x] = '?' +			} +		} else { +			t.Current.Display[row][x] = ' ' +		} +	} +} + +func (t *Display) HasChanges() bool { +	for y := 0; y < displayHeight; y++ { +		for x := 0; x < displayWidth; x++ { +			if t.Current.Display[y][x] != t.Last.Display[y][x] { +				return true +			} +		} +	} +	return false +} + +func (t *Display) Update() { +	for y := 0; y < displayHeight; y++ { +		x := 0 +		for x < displayWidth { +			if t.Current.Display[y][x] == t.Last.Display[y][x] { +				x++ +				continue +			} + +			startX := x +			var changes strings.Builder +			for x < displayWidth && +				t.Current.Display[y][x] != t.Last.Display[y][x] { +				changes.WriteByte(t.Current.Display[y][x]) +				t.Last.Display[y][x] = t.Current.Display[y][x] +				x++ +			} + +			fmt.Printf("\x1b[%d;%dH%s", y+1, startX+1, changes.String()) +		} +	} + +	os.Stdout.Sync() +} + +func centerText(text string, width int) string { +	textLen := len([]rune(text)) +	if textLen >= width { +		return text[:width] +	} +	leftPad := (width - textLen + 1) / 2 +	rightPad := width - textLen - leftPad +	return strings.Repeat(" ", leftPad) + text + strings.Repeat(" ", rightPad) +} + +func kaomojiProducer(lines chan<- string) { +	ticker := time.NewTicker(30_000 * time.Millisecond) +	defer ticker.Stop() + +	idx := 0 +	for { +		km := kaomoji[idx] + +		line := centerText(km.kao, displayWidth) +		if km.message != "" { +			line = line[:14] + km.message +			line += strings.Repeat(" ", displayWidth-len([]rune(line))) +		} + +		lines <- line + +		idx = (idx + 1) % len(kaomoji) +		<-ticker.C +	} +} + +func statusProducer(lines chan<- string) { +	ticker := time.NewTicker(1 * time.Second) +	defer ticker.Stop() + +	temperature, fetcher := "", NewWeatherFetcher() +	temperatureChan := make(chan string) +	go fetcher.Run(5*time.Minute, temperatureChan) + +	for { +		select { +		case newTemperature := <-temperatureChan: +			temperature = newTemperature +		default: +		} + +		now := time.Now() +		status := fmt.Sprintf("%s %3s %s", +			now.Format("Mon _2 Jan"), temperature, now.Format("15:04")) + +		// Ensure exactly 20 characters. +		runes := []rune(status) +		if len(runes) > displayWidth { +			status = string(runes[:displayWidth]) +		} else if len(runes) < displayWidth { +			status = status + strings.Repeat(" ", displayWidth-len(runes)) +		} + +		lines <- status +		<-ticker.C +	} +} + +func main() { +	terminal := NewDisplay() + +	kaomojiChan := make(chan string, 1) +	statusChan := make(chan string, 1) + +	go kaomojiProducer(kaomojiChan) +	go statusProducer(statusChan) + +	// TODO(p): And we might want to disable cursor visibility as well. +	fmt.Printf("\x1bR%c", targetCharset) +	fmt.Print("\x1b[2J") // Clear display + +	go func() { +		kaomojiChan <- centerText(kaomoji[0].kao, displayWidth) +		statusChan <- strings.Repeat(" ", displayWidth) +	}() + +	for { +		select { +		case line := <-kaomojiChan: +			terminal.SetLine(0, line) +		case line := <-statusChan: +			terminal.SetLine(1, line) +		} +		if terminal.HasChanges() { +			terminal.Update() +		} +	} +}  | 
