Full Code of olup/kobowriter for AI

main 80564fb6ac46 cached
18 files
33.4 KB
11.7k tokens
52 symbols
1 requests
Download .txt
Repository: olup/kobowriter
Branch: main
Commit: 80564fb6ac46
Files: 18
Total size: 33.4 KB

Directory structure:
gitextract__t0h6qw4/

├── .gitignore
├── Makefile
├── README.md
├── event/
│   ├── key.go
│   └── keyEvents.go
├── findKeyboard.go
├── go.mod
├── go.sum
├── main.go
├── matrix/
│   └── matrix.go
├── screener/
│   ├── image.go
│   └── screen.go
├── utils/
│   ├── text.go
│   └── utils.go
└── views/
    ├── document.go
    ├── menu.go
    ├── qr.go
    └── textView.go

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
kobowriter

================================================
FILE: Makefile
================================================
.PHONY: build
build: 
	CGO_ENABLED=1 GOARCH=arm GOOS=linux CC=${CROSS_TC}-gcc CXX=${CROSS_TC}-g++ go build -o ./build/kobowriter

================================================
FILE: README.md
================================================
<p align="center">
  <img src="assets/kobowriter.png" />
</p>

# Kobowriter

This small project aims to let you use your old KOBO e-reader (mine is a GLO HD) as a simple, distraction free typewriter.

For years I thought that e-ink was the ultimate medium to write in broad daylight without eye strain or focus fatigue. It seems that others have had the same ideas, as we can see in the [Freewrite](https://getfreewrite.com/) or [Pomera](https://www.kickstarter.com/projects/2132003782/pomera-pocket-typewriter-with-e-ink?ref=category_newest&amp;ref=discovery) products.

This project brings the same form factor in a considerably cheaper way (especially if like me you already have a KOBO at hand).

> Note that the installed software should let you use switch between your normal kobo stock software and the KoboWriter one; so your kobo is still usable in its default way.

> Because XCSoar USB OTG should work for many KOBO devices (touch, Mini, Glo HD and pretty much all the later ones), this project would work there too. But as of now this program has only been built and tested for the KOBO GLO HD and only supports the AZERTY (French) keyboard. You can open issues if you need to support other devices / keyboards

## How it looks

![From face](assets/face.jpg)

![From side](assets/side.jpg)

*TODO add video*

## How it works

The kobo e-readers have a Micro-USB connector to charge and transfer files. With proper kernel modification this USB socket can be used as OTG, letting one plug in any kind of USB device.

Such kernel was compiled by the [XCSoar](https://github.com/XCSoar/XCSoar) project in order to turn the kobo into a fliying assistant supported by an external GPS. 

We use their modifications to connect a USB keyboard to the OTG port.

However, the kobo giving no power through its USB socket, the keyboard has to be powered on its own - you can either use a cheap USB otg power cable [like this one](https://www.amazon.com/AuviPal-Micro-USB-Cable-Power/dp/B07FY9Z9GD/ref=sr_1_3?crid=13TQ5BP3TUJT5&dchild=1&keywords=powered+usb+otg&qid=1630094365&sprefix=powered+%2Caps%2C536&sr=8-3) or modify the keyboard, like I did.

The software lets you use the keyboard to write and edit text files. It's coded in Go, compiled with a toolchain prepared for the KOBO devices, and relies largely on the excellent [FBInk](https://github.com/NiLuJe/FBInk) library to drive the screen, through its extremely useful port in Go, [go-fbink](https://github.com/shermp/go-fbink-v2).

## How to build it

> Note that we also provide ready made precompiled binaries for your KOBO

First you need to download and build the **koxtoolchain** on your development computer. This toolchain, once built, will let you build Go programs that can run on the KOBO.

*TODO : Detailed step to build project*

## How to install

You can build the software, put it on a KOBO with XCSoar software, and launch it any way you see fit.

Or you can use our modified XCSoar installer that will get you the XCSoar program, kernel, and Kobowriter in just one step:

> You do this at your own risk!

- Download the `KoboRoot.tar.gz` from the release page
- Connect your Kobo and place the archive in the .kobo (hidden) directory
- eject safely, unplug, and let the Kobo update
  
From now on your Kobo will start up on XCSoar launcher. From there you can start the stock Kobo software, turn on USB-OTG or start the KoboWriter.

> Note that when USB-OTG is enable, you won't be able to start the stock Kobo software. But you need to have it on in order to use the KoboWriter software. Changing the USB-OTG setting requires a restart.

- When you start the KOBO, if not activated yet, from the XCSoar laucher tap on `system` and the `enable USB-OTG` and then restart the device.

If, like me, you use the KOBO only for KoboWriter, then your device should always boot in this state. In this case, only this last step is required:

- From XCSoar launcher tap `tools` and then `KoboWriter`.

Plugin you powered USB keyboard and you should be good to go ;-)


================================================
FILE: event/key.go
================================================
//AZERTYkeybindings

package event

var KeyCode = map[int]string{
	0: "KEY_RESERVED",
	1: "KEY_ESC",

	2:  "&",
	3:  "é",
	4:  "\"",
	5:  "'",
	6:  "(",
	7:  "-",
	8:  "è",
	9:  "_",
	10: "ç",
	11: "à",
	12: ")",
	13: "=",

	14: "KEY_BACKSPACE",
	15: "KEY_TAB",

	16: "a",
	17: "z",
	18: "e",
	19: "r",
	20: "t",
	21: "y",
	22: "u",
	23: "i",
	24: "o",
	25: "p",
	26: "^",
	27: "$",
	28: "KEY_ENTER",
	29: "KEY_L_CTRL",

	30: "q",
	31: "s",
	32: "d",
	33: "f",
	34: "g",
	35: "h",
	36: "j",
	37: "k",
	38: "l",
	39: "m",
	40: "ù",
	41: "*",

	42: "KEY_L_SHIFT",
	43: "<",
	44: "w",
	45: "x",
	46: "c",
	47: "v",
	48: "b",
	49: "n",
	50: ",",
	51: ";",
	52: ":",
	53: "!",
	54: "KEY_R_SHIFT",

	55: "KEY_KPASTERISK",
	56: "KEY_L_ALT",

	57: "KEY_SPACE",
	58: "KEY_CAPSLOCK",
	59: "KEY_F1",
	60: "KEY_F2",
	61: "KEY_F3",
	62: "KEY_F4",
	63: "KEY_F5",
	64: "KEY_F6",
	65: "KEY_F7",
	66: "KEY_F8",
	67: "KEY_F9",
	68: "KEY_F10",

	87: "KEY_F11",
	88: "KEY_F12",

	100: "KEY_ALT_GR",

	103: "KEY_UP",
	105: "KEY_LEFT",
	106: "KEY_RIGHT",
	108: "KEY_DOWN",

	111: "KEY_DEL",

	183: "KEY_F13",
	184: "KEY_F14",
	185: "KEY_F15",
	186: "KEY_F16",
	187: "KEY_F17",
	188: "KEY_F18",
	189: "KEY_F19",
	190: "KEY_F20",
	191: "KEY_F21",
	192: "KEY_F22",
	193: "KEY_F23",
	194: "KEY_F24",
}

var KeyCodeMaj = map[int]string{
	2:  "1",
	3:  "2",
	4:  "3",
	5:  "4",
	6:  "5",
	7:  "6",
	8:  "7",
	9:  "8",
	10: "9",
	11: "0",
	12: "°",
	13: "+",

	16: "A",
	17: "Z",
	18: "E",
	19: "R",
	20: "T",
	21: "Y",
	22: "U",
	23: "I",
	24: "O",
	25: "P",
	26: "¨",
	27: "£",

	30: "Q",
	31: "S",
	32: "D",
	33: "F",
	34: "G",
	35: "H",
	36: "J",
	37: "K",
	38: "L",
	39: "M",
	40: "%",
	41: "µ",

	43: ">",
	44: "W",
	45: "X",
	46: "C",
	47: "V",
	48: "B",
	49: "N",
	50: "?",
	51: ".",
	52: "/",
	53: "§",
}

var KeyCodeAltGr = map[int]string{
	3:  "~",
	4:  "#",
	5:  "{",
	6:  "[",
	7:  "|",
	8:  "`",
	9:  "\\",
	10: "^",
	11: "@",
	12: "]",
	13: "}",

	16: "æ",
	17: "«",
	18: "€",
	19: "¶",
	20: "ŧ",
	21: "←",
	22: "↓",
	23: "→",
	24: "ø",
	25: "þ",
	26: "¨",
	27: "¤",

	30: "@",
	31: "ß",
	32: "ð",
	33: "đ",
	34: "ŋ",
	35: "ħ",

	37: "ĸ",
	38: "ł",
	39: "µ",

	41: "`",

	43: "|",
	44: "ł",
	45: "»",
	46: "¢",
	47: "“",
	48: "”",
	49: "n",
	50: "´",
	51: "─",
	52: "·",
}


================================================
FILE: event/keyEvents.go
================================================
package event

import (
	"github.com/MarinX/keylogger"
	"github.com/asaskevich/EventBus"
	"github.com/olup/kobowriter/utils"
)

type KeyEvent struct {
	IsCtrl      bool
	IsAlt       bool
	IsAltGr     bool
	IsShift     bool
	IsShiftLock bool
	KeyCode     int
	IsChar      bool
	KeyChar     string
	KeyValue    string
}

func BindKeyEvent(k *keylogger.KeyLogger, b EventBus.Bus) {
	event := KeyEvent{
		IsShift:     false,
		IsShiftLock: false,
		IsAltGr:     false,
		IsAlt:       false,
		IsCtrl:      false,
	}

	events := k.Read()
	for e := range events {
		if e.Type == keylogger.EvKey {

			keyValue := KeyCode[int(e.Code)]
			if keyValue == "" {
				continue
			}

			event.KeyChar = ""
			event.IsChar = false
			event.KeyCode = int(e.Code)
			event.KeyValue = keyValue

			if e.KeyPress() {
				switch keyValue {
				case "KEY_L_SHIFT", "KEY_R_SHIFT":
					event.IsShift = true
				case "KEY_CAPSLOCK":
					event.IsShiftLock = !event.IsShiftLock
				case "KEY_ALT_GR":
					event.IsAltGr = true
				case "KEY_L_ALT":
					event.IsAlt = true
				case "KEY_L_CTRL", "KEY_R_CTRL":
					event.IsCtrl = true

				}
			}

			if e.KeyRelease() {
				switch keyValue {
				case "KEY_L_SHIFT", "KEY_R_SHIFT":
					event.IsShift = false
				case "KEY_ALT_GR":
					event.IsAltGr = false
				case "KEY_L_GR":
					event.IsAlt = false
				case "KEY_L_CTRL", "KEY_R_CTRL":
					event.IsCtrl = false
				}
			} else {

				// letters
				if utils.IsLetter(keyValue) {
					event.IsChar = true
					if event.IsShift || event.IsShiftLock {
						event.KeyChar = KeyCodeMaj[int(e.Code)]
					} else if event.IsAltGr {
						event.KeyChar = KeyCodeAltGr[int(e.Code)]
					} else {
						event.KeyChar = KeyCode[int(e.Code)]
					}
				}

				b.Publish("KEY", event)
			}

		}
	}
	println("lost keyboadr")
	b.Publish("REQUIRE_KEYBOARD")
}


================================================
FILE: findKeyboard.go
================================================
package main

import (
	"fmt"
	"os/exec"
	"time"

	"github.com/MarinX/keylogger"
	"github.com/asaskevich/EventBus"

	"github.com/olup/kobowriter/event"
	"github.com/olup/kobowriter/screener"
)

func findKeyboard(screen *screener.Screen, bus EventBus.Bus) {
	// get key logger
	keyboard := keylogger.FindKeyboardDevice()

	buttonLogger, _ := keylogger.New("/dev/input/event0")
	buttonChannel := buttonLogger.Read()

	screen.Clear()

	for len(keyboard) <= 0 {
		screen.PrintAlert("No keyboard found.\n\nPlug your keyboard or clic main button to quit.\n\nNote that [USB OTG MODE] must be turned on in order to detect the keyboard.", 30)
		time.Sleep(1 * time.Second)
		select {
		case _ = <-buttonChannel:
			println("Quitting program")
			bus.Publish("QUIT")
			exec.Command("/opt/xcsoar/bin/KoboMenu").Start()
			return
		default:
		}
		keyboard = keylogger.FindKeyboardDevice()
	}

	screen.Clear()
	fmt.Println("Found a keyboard at", keyboard)

	k, _ := keylogger.New(keyboard)
	go event.BindKeyEvent(k, bus)
	bus.Publish("ROUTING", "document")
	return
}


================================================
FILE: go.mod
================================================
module github.com/olup/kobowriter

go 1.16

require (
	github.com/MarinX/keylogger v0.0.0-20210528193429-a54d7834cc1a
	github.com/asaskevich/EventBus v0.0.0-20200907212545-49d423059eef
	github.com/fogleman/gg v1.3.0
	github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
	github.com/matoous/go-nanoid/v2 v2.0.0
	github.com/shermp/go-fbink-v2 v1.20.2
	github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
	golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d // indirect
)


================================================
FILE: go.sum
================================================
github.com/MarinX/keylogger v0.0.0-20210528193429-a54d7834cc1a h1:ItKXWegGGThcahUf+ylKFa5pwqkRJofaOyeGdzwO2mM=
github.com/MarinX/keylogger v0.0.0-20210528193429-a54d7834cc1a/go.mod h1:aKzZ7D15UvH5LboXkeLmcNi+s/f805vUfB+BfW1fqd4=
github.com/asaskevich/EventBus v0.0.0-20200907212545-49d423059eef h1:2JGTg6JapxP9/R33ZaagQtAM4EkkSYnIAlOG5EI8gkM=
github.com/asaskevich/EventBus v0.0.0-20200907212545-49d423059eef/go.mod h1:JS7hed4L1fj0hXcyEejnW57/7LCetXggd+vwrRnYeII=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8=
github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/matoous/go-nanoid v1.5.0 h1:VRorl6uCngneC4oUQqOYtO3S0H5QKFtKuKycFG3euek=
github.com/matoous/go-nanoid v1.5.0/go.mod h1:zyD2a71IubI24efhpvkJz+ZwfwagzgSO6UNiFsZKN7U=
github.com/matoous/go-nanoid/v2 v2.0.0 h1:d19kur2QuLeHmJBkvYkFdhFBzLoo1XVm2GgTpL+9Tj0=
github.com/matoous/go-nanoid/v2 v2.0.0/go.mod h1:FtS4aGPVfEkxKxhdWPAspZpZSh1cOjtM7Ej/So3hR0g=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/shermp/go-fbink-v2 v1.20.2 h1:EtRKDZwrc8fkNGDZppsYB2nxcMosYU7hqYLovMP78/4=
github.com/shermp/go-fbink-v2 v1.20.2/go.mod h1:88bOAwruwze/4JB/KW8uoyPtWm5OPa1BZEraFMHJgpQ=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d h1:RNPAfi2nHY7C2srAV8A49jpsYr0ADedCk1wq6fTMTvs=
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=


================================================
FILE: main.go
================================================
package main

import (
	"fmt"
	"os/exec"

	"github.com/asaskevich/EventBus"

	_ "embed"

	"github.com/olup/kobowriter/screener"
	"github.com/olup/kobowriter/utils"
	"github.com/olup/kobowriter/views"
)

var saveLocation = "/mnt/onboard/.adds/kobowriter"
var filename = "autosave.txt"

func main() {
	fmt.Println("Program started")

	// kill all nickel related stuff. Will need a reboot to find back the usual
	fmt.Println("Killing XCSoar programs ...")
	exec.Command("killall", "-s", "SIGKILL", "KoboMenu").Run()

	// rotate screen
	fmt.Println("Rotate screen ...")
	exec.Command(`fbdepth`, `--rota`, `2`).Run()

	// initialise fbink
	fmt.Println("Init FBInk ...")

	screen := screener.InitScreen()
	defer screen.Clean()

	bus := EventBus.New()

	c := make(chan bool)
	defer close(c)

	bus.SubscribeAsync("REQUIRE_KEYBOARD", func() {
		findKeyboard(screen, bus)
	}, false)

	bus.SubscribeAsync("QUIT", func() {
		screen.PrintAlert("Good Bye !", 500)

		// quitting
		c <- true
		return
	}, false)

	var unmount func()
	bus.SubscribeAsync("ROUTING", func(routeName string) {
		if unmount != nil {
			unmount()
		}

		switch routeName {
		case "document":
			config := utils.LoadConfig(saveLocation)
			unmount = views.Document(screen, bus, config.LastOpenedDocument)
		case "menu":
			unmount = views.MainMenu(screen, bus, saveLocation)
		case "file-menu":
			unmount = views.FileMenu(screen, bus, saveLocation)
		case "settings-menu":
			unmount = views.SettingsMenu(screen, bus, saveLocation)
		case "qr":
			unmount = views.Qr(screen, bus, saveLocation)

		default:
			unmount = views.Document(screen, bus, "")
		}

	}, false)

	// init
	bus.Publish("REQUIRE_KEYBOARD")

	for quit := range c {
		if quit {
			break
		}
	}

	println("yo")

}


================================================
FILE: matrix/matrix.go
================================================
package matrix

import (
	"strings"

	"github.com/olup/kobowriter/utils"
)

type MatrixElement struct {
	Content    rune
	IsInverted bool
}

type Matrix [][]MatrixElement

func CreateNewMatrix(width int, height int) Matrix {
	a := make([][]MatrixElement, height)
	for i := range a {
		a[i] = make([]MatrixElement, width)
		for j := range a[i] {
			a[i][j] = MatrixElement{
				Content:    ' ',
				IsInverted: false,
			}
		}
	}
	return a
}

func CreateMatrixFromText(text string, width int) Matrix {

	wrapped := utils.WrapText(text, int(width))

	wrapedArray := strings.Split(wrapped, "\n")
	result := CreateNewMatrix(width, len(wrapedArray))

	for i := range result {
		for j := range result[i] {
			if j < utils.LenString(wrapedArray[i]) {
				result[i][j].Content = []rune(wrapedArray[i])[j]
			}
		}
	}

	return result
}

func PasteMatrix(baseMatrix Matrix, topMatrix Matrix, offsetX int, offsetY int) Matrix {
	resultMatrix := CopyMatrix(baseMatrix)
	for i := range resultMatrix {
		localI := i - offsetY
		if localI < 0 || localI >= len(topMatrix) {
			continue
		}

		for j := range resultMatrix[i] {
			localJ := j - offsetX
			if localJ < 0 || localJ >= len(topMatrix[localI]) {
				continue
			}

			resultMatrix[i][j] = topMatrix[localI][localJ]

		}
	}

	return resultMatrix
}

func MatrixToText(matrix Matrix) string {
	stringz := make([]string, len(matrix))
	for i := range matrix {
		for _, elem := range matrix[i] {
			stringz[i] = stringz[i] + string(elem.Content)
		}
	}
	return strings.Join(stringz, "")
}

func InverseMatrix(in Matrix) (out Matrix) {
	out = in
	for i := range out {
		for j, elem := range out[i] {
			out[i][j].IsInverted = !elem.IsInverted
		}
	}
	return
}

func FillMatrix(in Matrix, char rune) (out Matrix) {
	out = CopyMatrix(in)
	for i := range out {
		for j := range out[i] {
			out[i][j].Content = char
		}
	}
	return
}

func CopyMatrix(in Matrix) (out Matrix) {
	if len(in) == 0 {
		return Matrix{}
	}
	out = CreateNewMatrix(len(in[0]), len(in))
	for i := range out {
		for j := range out[i] {
			out[i][j].Content = in[i][j].Content
			out[i][j].IsInverted = in[i][j].IsInverted
		}
	}
	return
}


================================================
FILE: screener/image.go
================================================
package screener

import (
	"image"
	"image/png"
	"io"
)

// Get the bi-dimensional pixel array
func getPixels(file io.Reader) ([]byte, error) {
	image.RegisterFormat("png", "png", png.Decode, png.DecodeConfig)
	img, _, err := image.Decode(file)

	if err != nil {
		return nil, err
	}

	bounds := img.Bounds()
	width, height := bounds.Max.X, bounds.Max.Y

	var pixels []byte

	for y := 0; y < height; y++ {
		for x := 0; x < width; x++ {
			r, g, b, a := img.At(x, y).RGBA()
			pixels = append(pixels, byte(r/257), byte(g/257), byte(b/257), byte(a/257))
		}
	}

	return pixels, nil
}

// Get the bi-dimensional pixel array
func getPixelsFromImage(img image.Image) ([]byte, error) {

	bounds := img.Bounds()
	width, height := bounds.Max.X, bounds.Max.Y

	var pixels []byte

	for y := 0; y < height; y++ {
		for x := 0; x < width; x++ {
			r, g, b, a := img.At(x, y).RGBA()
			pixels = append(pixels, byte(r/257), byte(g/257), byte(b/257), byte(a/257))
		}
	}

	return pixels, nil
}


================================================
FILE: screener/screen.go
================================================
package screener

import (
	"bytes"
	"image"
	"math"

	"github.com/fogleman/gg"
	"github.com/olup/kobowriter/matrix"
	"github.com/shermp/go-fbink-v2/gofbink"
)

type Screen struct {
	originalMatrix matrix.Matrix
	presentMatrix  matrix.Matrix
	fb             *gofbink.FBInk
	state          gofbink.FBInkState
	Width          int
	Height         int
	fontType       string
	ttSize         int
}

var dc = gg.NewContext(25, 40)
var charCache = map[string][]byte{}

func InitScreen() (s *Screen) {
	s = &Screen{}
	s.fontType = "bitmap"

	s.state = gofbink.FBInkState{}

	fbinkOpts := gofbink.FBInkConfig{}
	rOpts := gofbink.RestrictedConfig{
		Fontmult: 3,
		Fontname: gofbink.Ctrld,
	}
	s.fb = gofbink.New(&fbinkOpts, &rOpts)

	s.fb.Open()
	s.fb.Init(&fbinkOpts)
	s.fb.AddOTfont("/mnt/onboard/.adds/kobowriter/inc.ttf", gofbink.FntRegular)

	s.fb.GetState(&fbinkOpts, &s.state)

	// clear screen on initialisation
	s.ClearFlash()

	if s.fontType == "truetype" {
		dc.LoadFontFace("inc.ttf", 96)
		s.ttSize = 40
		s.Width = int(s.state.ScreenWidth) / ((s.ttSize / 5) * 3)
		s.Height = int(s.state.ScreenHeight) / s.ttSize
	} else {
		s.Width = int(s.state.MaxCols)
		s.Height = int(s.state.MaxRows)
	}

	s.presentMatrix = matrix.CreateNewMatrix(s.Width, s.Height)
	s.originalMatrix = matrix.CreateNewMatrix(s.Width, s.Height)

	println("Screen struct inited")

	return

}

func (s *Screen) Clean() {
	s.fb.Close()
}

func (s *Screen) Print(matrix matrix.Matrix) {
	printDiff(s.presentMatrix, matrix, s.fb, s.fontType, s.ttSize)
	s.presentMatrix = matrix
}

func same(a matrix.MatrixElement, b matrix.MatrixElement) bool {
	return a.Content == b.Content && a.IsInverted == b.IsInverted
}

func printDiff(previous matrix.Matrix, next matrix.Matrix, fb *gofbink.FBInk, fontType string, ttSize int) {
	for i := range previous {
		for j := range previous[i] {
			if !same(previous[i][j], next[i][j]) {
				if fontType == "truetype" {
					ttWidth := ((ttSize / 5) * 3)
					fb.ClearScreen(&gofbink.FBInkConfig{
						IsInverted: next[i][j].IsInverted,
						NoRefresh:  true,
					}, &gofbink.FBInkRect{
						Top:    uint16(i * ttSize),
						Left:   uint16(j * ttWidth),
						Height: uint16(ttSize),
						Width:  uint16(ttWidth),
					})

					fb.PrintOT(string(next[i][j].Content), &gofbink.FBInkOTConfig{
						Margins: struct {
							Top    int16
							Bottom int16
							Left   int16
							Right  int16
						}{
							Top:  int16(i * ttSize),
							Left: int16(j * ttWidth),
						},
						SizePx:      uint16(ttSize),
						IsFormatted: false,
					}, &gofbink.FBInkConfig{IsInverted: next[i][j].IsInverted, NoRefresh: true})

				} else {
					fb.FBprint(string(next[i][j].Content), &gofbink.FBInkConfig{
						Row:        int16(i),
						Col:        int16(j),
						NoRefresh:  true,
						IsInverted: next[i][j].IsInverted,
					})
				}

			}

		}
	}

	fb.Refresh(0, 0, 0, 0, &gofbink.FBInkConfig{})
}

func (s *Screen) PrintPng(imgBytes []byte, w int, h int, x int, y int) {
	img, _, _ := image.Decode(bytes.NewReader(imgBytes))
	buffer, _ := getPixelsFromImage(img)
	s.fb.PrintRawData(buffer, w, h, uint16(x), uint16(y), &gofbink.FBInkConfig{})
}

func getCharImage(s string) []byte {
	if char, ok := charCache[s]; ok {
		return char
	} else {
		dc.SetRGB(1, 1, 1)
		dc.Clear()

		dc.SetRGB(0, 0, 0)
		dc.DrawString(s, 0, 35)
		img := dc.Image()
		buffer, _ := getPixelsFromImage(img)
		charCache[s] = buffer
		return buffer
	}
}

func (s *Screen) PrintAlert(message string, width int) {
	thisMatrix := matrix.CreateMatrixFromText(message, width)
	x := math.Floor((float64(s.state.MaxCols)/2)-float64(width)/2) - 1
	y := math.Floor((float64(s.state.MaxRows)/2)-float64(len(thisMatrix))/2) - 1
	outerMatrix := matrix.CreateNewMatrix(width+2, len(thisMatrix)+2)
	thisMatrix = matrix.PasteMatrix(outerMatrix, thisMatrix, 1, 1)
	thisMatrix = matrix.InverseMatrix(thisMatrix)
	s.Print(matrix.PasteMatrix(s.originalMatrix, thisMatrix, int(x), int(y)))
}

func (s *Screen) Clear() {
	s.fb.ClearScreen(&gofbink.FBInkConfig{}, &gofbink.FBInkRect{})
	s.presentMatrix = matrix.FillMatrix(s.presentMatrix, ' ')
}

func (s *Screen) ClearFlash() {
	s.fb.ClearScreen(&gofbink.FBInkConfig{IsFlashing: true}, &gofbink.FBInkRect{})
	s.presentMatrix = matrix.FillMatrix(s.presentMatrix, ' ')
}

func (s *Screen) RefreshFlash() {
	presenMatrix := s.presentMatrix
	s.ClearFlash()
	s.Print(presenMatrix)
}

func (s *Screen) GetOriginalMatrix() matrix.Matrix {
	return matrix.CopyMatrix(s.originalMatrix)
}


================================================
FILE: utils/text.go
================================================
package utils

import "strings"

func WrapLine(text string, lineWidth int) (wrapped string) {
	if text == "" {
		return ""
	}

	words := strings.Split(text, " ")
	if len(words) == 0 {
		return
	}
	wrapped = words[0]
	spaceLeft := lineWidth - len(wrapped)
	for _, word := range words[1:] {
		if LenString(word)+1 > spaceLeft {
			wrapped += "\n" + word
			spaceLeft = lineWidth - LenString(word)
		} else {
			wrapped += " " + word
			spaceLeft -= 1 + LenString(word)
		}
	}

	return
}

func WrapText(text string, lineWidth int) string {
	lines := strings.Split(text, "\n")
	if len(lines) == 0 {
		return ""
	}
	for i := range lines {
		lines[i] = WrapLine(lines[i], lineWidth)
	}

	return strings.Join(lines, "\n")

}


================================================
FILE: utils/utils.go
================================================
package utils

import (
	"encoding/json"
	"os"
	"path"
	"strings"
	"unicode/utf8"

	gonanoid "github.com/matoous/go-nanoid/v2"
)

type Config struct {
	LastOpenedDocument string `json:"lastOpenDocument"`
}

func LoadConfig(saveLocation string) Config {
	content, err := os.ReadFile(path.Join(saveLocation, "config.json"))

	if err != nil {
		id, _ := gonanoid.New()
		return Config{
			LastOpenedDocument: id + ".txt",
		}
	}

	var config Config

	// we unmarshal our byteArray which contains our
	// jsonFile's content into 'users' which we defined above
	json.Unmarshal(content, &config)
	return config
}

func SaveConfig(config Config, saveLocation string) {

	// we unmarshal our byteArray which contains our
	// jsonFile's content into 'users' which we defined above
	content, _ := json.Marshal(config)
	os.WriteFile(path.Join(saveLocation, "config.json"), []byte(content), 777)
}

func IsLetter(s string) bool {
	return !strings.Contains(s, "KEY")
}

func InsertAt(text string, insert string, index int) string {
	if index == LenString(text) {
		return text + insert
	}
	runeText := []rune(text)
	return string(append(runeText[:index], append([]rune(insert), runeText[index:]...)...))
}

func DeleteAt(text string, index int) string {
	runeText := []rune(text)
	return string(append(runeText[:index-1], runeText[index:]...))
}

func LenString(s string) int {
	return utf8.RuneCountInString(s)
}


================================================
FILE: views/document.go
================================================
package views

import (
	"os"
	"path"
	"time"

	"github.com/asaskevich/EventBus"
	"github.com/olup/kobowriter/event"
	"github.com/olup/kobowriter/matrix"
	"github.com/olup/kobowriter/screener"
	"github.com/olup/kobowriter/utils"
)

func Document(screen *screener.Screen, bus EventBus.Bus, documentPath string) func() {
	docContent := []byte("")
	if documentPath != "" {
		docContent, _ = os.ReadFile(documentPath)
	}
	text := &TextView{
		width:       int(screen.Width) - 4,
		height:      int(screen.Height) - 2,
		content:     "",
		scroll:      0,
		cursorIndex: 0,
	}

	text.setContent(string(docContent))
	text.setCursorIndex(utils.LenString(string(docContent)))

	onEvent := func(e event.KeyEvent) {
		linesToMove := 1
		if e.IsCtrl {
			linesToMove = text.height
		}

		// if date combo
		if e.IsChar {
			text.setContent(utils.InsertAt(text.content, e.KeyChar, text.cursorIndex))
			text.setCursorIndex(text.cursorIndex + 1)
		} else {
			// if is modifier key
			switch e.KeyValue {
			case "KEY_BACKSPACE":
				text.setContent(utils.DeleteAt(text.content, text.cursorIndex))
				text.setCursorIndex(text.cursorIndex - 1)
			case "KEY_DEL":
				if text.cursorIndex < utils.LenString(text.content) {
					text.setContent(utils.DeleteAt(text.content, text.cursorIndex+1))
				}
			case "KEY_SPACE":
				text.setContent(utils.InsertAt(text.content, " ", text.cursorIndex))
				text.setCursorIndex(text.cursorIndex + 1)
			case "KEY_ENTER":
				text.setContent(utils.InsertAt(text.content, "\n", text.cursorIndex))
				text.setCursorIndex(text.cursorIndex + 1)
			case "KEY_RIGHT":
				text.setCursorIndex(text.cursorIndex + 1)
			case "KEY_LEFT":
				text.setCursorIndex(text.cursorIndex - 1)
			case "KEY_DOWN":
				text.setCursorPos(Position{
					x: text.cursorPos.x,
					y: text.cursorPos.y + linesToMove,
				})
			case "KEY_UP":
				text.setCursorPos(Position{
					x: text.cursorPos.x,
					y: text.cursorPos.y - linesToMove,
				})
			case "KEY_ESC":
				bus.Publish("ROUTING", "menu")
			case "KEY_F1":
				text.setContent(utils.InsertAt(text.content, time.Now().Format("02/01/2006"), text.cursorIndex))
				text.setCursorIndex(text.cursorIndex + 10)

			case "KEY_F12":
				screen.RefreshFlash()
			}
		}

		compiledMatrix := matrix.PasteMatrix(screen.GetOriginalMatrix(), text.renderMatrix(), 2, 1)
		screen.Print(compiledMatrix)

		if documentPath != "" {
			os.WriteFile(path.Join(documentPath), []byte(text.content), 0644)
		}
	}

	bus.SubscribeAsync("KEY", onEvent, false)

	// display
	bus.Publish("KEY", event.KeyEvent{})

	return func() {
		bus.Unsubscribe("KEY", onEvent)
	}
}


================================================
FILE: views/menu.go
================================================
package views

import (
	"os"
	"os/exec"
	"path"
	"sort"
	"strings"

	"github.com/asaskevich/EventBus"
	gonanoid "github.com/matoous/go-nanoid/v2"
	"github.com/olup/kobowriter/event"
	"github.com/olup/kobowriter/matrix"
	"github.com/olup/kobowriter/screener"
	"github.com/olup/kobowriter/utils"
)

type Option struct {
	label  string
	action func()
}

func createMenu(title string, options []Option) func(screen *screener.Screen, bus EventBus.Bus) func() {
	return func(screen *screener.Screen, bus EventBus.Bus) func() {
		selected := 0
		onKey := func(e event.KeyEvent) {

			if e.KeyValue == "KEY_UP" && selected > 0 {
				selected--
			}
			if e.KeyValue == "KEY_DOWN" && selected < len(options)-1 {
				selected++
			}

			if e.KeyValue == "KEY_ENTER" {
				options[selected].action()
			}

			line := 1

			matrixx := screen.GetOriginalMatrix()
			matrixx = matrix.PasteMatrix(matrixx, matrix.CreateMatrixFromText(title+"\n"+strings.Repeat("=", utils.LenString(title)), utils.LenString(title)), 4, line)

			line += 2

			for i, option := range options {
				optionMatrix := matrix.CreateMatrixFromText(option.label, utils.LenString(option.label))
				if selected == i {
					optionMatrix = matrix.InverseMatrix(optionMatrix)
				}
				matrixx = matrix.PasteMatrix(matrixx, optionMatrix, 4, line+i)
			}

			screen.Print(matrixx)
		}

		bus.SubscribeAsync("KEY", onKey, false)

		// display
		bus.Publish("KEY", event.KeyEvent{})

		return func() {
			bus.Unsubscribe("KEY", onKey)
		}
	}
}

func MainMenu(screen *screener.Screen, bus EventBus.Bus, saveLocation string) func() {
	options := []Option{
		{
			label: "Back",
			action: func() {
				bus.Publish("ROUTING", "document")
			},
		},
		{
			label: "Export as QR code",
			action: func() {
				bus.Publish("ROUTING", "qr")
			},
		},
		{
			label: "Open Document",
			action: func() {
				bus.Publish("ROUTING", "file-menu")
			},
		},
		{
			label: "New Document",
			action: func() {
				id, _ := gonanoid.New()

				config := utils.LoadConfig(saveLocation)
				config.LastOpenedDocument = path.Join(saveLocation, id+".txt")
				utils.SaveConfig(config, saveLocation)

				bus.Publish("ROUTING", "document")
			},
		},
		{
			label: "Settings",
			action: func() {
				bus.Publish("ROUTING", "settings-menu")
			},
		},
		{
			label: "Quit to XCSoar",
			action: func() {
				exec.Command("/opt/xcsoar/bin/KoboMenu").Start()
				bus.Publish("QUIT")
			},
		},
	}

	return createMenu("Menu", options)(screen, bus)
}

func FileMenu(screen *screener.Screen, bus EventBus.Bus, saveLocation string) func() {
	files, _ := os.ReadDir(saveLocation)
	options := []Option{
		{
			label: "Back",
			action: func() {
				bus.Publish("ROUTING", "menu")
			},
		},
	}

	sort.Slice(files, func(i, j int) bool {
		infoI, _ := files[i].Info()
		modTimeI := infoI.ModTime().Unix()

		infoJ, _ := files[j].Info()
		modTimeJ := infoJ.ModTime().Unix()

		return modTimeI > modTimeJ
	})

	for _, file := range files {

		if strings.HasSuffix(file.Name(), ".txt") {
			filePath := path.Join(saveLocation, file.Name())
			content, _ := os.ReadFile(path.Join(saveLocation, file.Name()))

			label := strings.Split(string(content), "\n")[0]
			if utils.LenString(label) > 30 {
				label = string([]rune(label)[0:30]) + "..."
			}
			options = append(options, Option{
				label: label,

				action: func() {
					config := utils.LoadConfig(saveLocation)
					config.LastOpenedDocument = filePath
					utils.SaveConfig(config, saveLocation)

					bus.Publish("ROUTING", "document")
				},
			})
		}

	}

	return createMenu("Open File", options)(screen, bus)
}

func SettingsMenu(screen *screener.Screen, bus EventBus.Bus, saveLocation string) func() {
	options := []Option{
		{
			label: "Back",
			action: func() {
				bus.Publish("ROUTING", "menu")
			},
		},
		{
			label: "Toggle light",
			action: func() {
				lightPath := "/sys/class/backlight/mxc_msp430_fl.0/brightness"
				light := "0"
				presentLightRaw, _ := os.ReadFile(lightPath)
				presentLight := strings.TrimSuffix(string(presentLightRaw), "\n")

				if presentLight == "0" {
					light = "10"
				} else {
					light = "0"
				}

				os.WriteFile(lightPath, []byte(light), os.ModePerm)
			},
		},
	}

	return createMenu("Open File", options)(screen, bus)
}


================================================
FILE: views/qr.go
================================================
package views

import (
	"os"

	"github.com/asaskevich/EventBus"
	"github.com/olup/kobowriter/event"
	"github.com/olup/kobowriter/screener"
	"github.com/olup/kobowriter/utils"
	"github.com/skip2/go-qrcode"
)

func Qr(screen *screener.Screen, bus EventBus.Bus, saveLocation string) func() {
	onKey := func(event event.KeyEvent) {
		screen.Clear()
		bus.Publish("ROUTING", "menu")
	}

	bus.SubscribeAsync("KEY", onKey, false)

	// Display QR on mount
	screen.Clear()
	config := utils.LoadConfig(saveLocation)
	content, err := os.ReadFile(config.LastOpenedDocument)
	if err != nil {
		bus.Publish("ROUTING", "menu")
	}

	image, _ := qrcode.Encode(string(content), qrcode.High, 800)

	screen.PrintPng(image, 800, 800, 100, 100)

	return func() {
		bus.Unsubscribe("KEY", onKey)
	}
}


================================================
FILE: views/textView.go
================================================
package views

import (
	"strings"
	"unicode/utf8"

	"github.com/olup/kobowriter/matrix"
	"github.com/olup/kobowriter/utils"
)

type TextView struct {
	content     string
	width       int
	height      int
	wrapContent []string
	cursorIndex int
	cursorPos   Position
	lineCount   []int
	scroll      int
}

type Position struct {
	x int
	y int
}

func (t *TextView) init(width int) {
	t.width = width
}

func (t *TextView) setContent(text string) {
	t.content = text
	t.wrapContent = strings.Split(utils.WrapText(text, t.width), "\n")

	lineCount := []int{}
	for _, line := range t.wrapContent {
		lineCount = append(lineCount, utf8.RuneCountInString(line)+1)
	}
	t.lineCount = lineCount
}

func (t *TextView) setCursorIndex(index int) {

	// Bounds
	if index < 0 {
		index = 0
	}
	if index > utils.LenString(t.content) {
		index = utils.LenString(t.content)
	}

	// Processing
	t.cursorIndex = index
	x := 0
	y := 0

	agg := 0

	for i, count := range t.lineCount {
		aggNext := count + agg
		if aggNext > t.cursorIndex {
			y = i
			x = t.cursorIndex - agg
			break
		}
		agg = aggNext
	}

	t.cursorPos = Position{
		x,
		y,
	}

	t.updateScroll()

}

func (t *TextView) setCursorPos(position Position) {
	// Bounds
	if position.y < 0 {
		position.y = 0
	}

	if position.x < 0 {
		position.x = 0
	}

	if position.y > len(t.lineCount)-1 {
		position.y = len(t.lineCount) - 1
	}

	if t.lineCount[position.y]-1 < position.x {
		position.x = t.lineCount[position.y] - 1
	}

	// Procesing

	agg := 0

	for i := 0; i < position.y; i++ {
		agg += t.lineCount[i]
	}

	agg += position.x

	t.cursorPos = position
	t.cursorIndex = agg
	t.updateScroll()

}

func (t *TextView) renderMatrix() matrix.Matrix {
	textMatrix := matrix.CreateMatrixFromText(t.content, t.width)
	if t.cursorPos.x >= 0 && t.cursorPos.y >= 0 && t.cursorPos.x < t.width {
		textMatrix[t.cursorPos.y][t.cursorPos.x].IsInverted = true
	}
	endBound := t.scroll + t.height
	if endBound > len(textMatrix) {
		endBound = len(textMatrix)
	}
	scrolledTextMatrix := textMatrix[t.scroll:endBound]
	return scrolledTextMatrix
}

func (t *TextView) updateScroll() {
	y := t.cursorPos.y

	if y > t.scroll+t.height-1 {
		t.scroll = y - 5
	}
	if y < t.scroll {
		t.scroll = y - t.height + 5
	}
	if t.scroll > len(t.wrapContent) {
		t.scroll = len(t.wrapContent) - 5
	}
	if t.scroll < 0 {
		t.scroll = 0
	}
}
Download .txt
gitextract__t0h6qw4/

├── .gitignore
├── Makefile
├── README.md
├── event/
│   ├── key.go
│   └── keyEvents.go
├── findKeyboard.go
├── go.mod
├── go.sum
├── main.go
├── matrix/
│   └── matrix.go
├── screener/
│   ├── image.go
│   └── screen.go
├── utils/
│   ├── text.go
│   └── utils.go
└── views/
    ├── document.go
    ├── menu.go
    ├── qr.go
    └── textView.go
Download .txt
SYMBOL INDEX (52 symbols across 12 files)

FILE: event/keyEvents.go
  type KeyEvent (line 9) | type KeyEvent struct
  function BindKeyEvent (line 21) | func BindKeyEvent(k *keylogger.KeyLogger, b EventBus.Bus) {

FILE: findKeyboard.go
  function findKeyboard (line 15) | func findKeyboard(screen *screener.Screen, bus EventBus.Bus) {

FILE: main.go
  function main (line 19) | func main() {

FILE: matrix/matrix.go
  type MatrixElement (line 9) | type MatrixElement struct
  type Matrix (line 14) | type Matrix
  function CreateNewMatrix (line 16) | func CreateNewMatrix(width int, height int) Matrix {
  function CreateMatrixFromText (line 30) | func CreateMatrixFromText(text string, width int) Matrix {
  function PasteMatrix (line 48) | func PasteMatrix(baseMatrix Matrix, topMatrix Matrix, offsetX int, offse...
  function MatrixToText (line 70) | func MatrixToText(matrix Matrix) string {
  function InverseMatrix (line 80) | func InverseMatrix(in Matrix) (out Matrix) {
  function FillMatrix (line 90) | func FillMatrix(in Matrix, char rune) (out Matrix) {
  function CopyMatrix (line 100) | func CopyMatrix(in Matrix) (out Matrix) {

FILE: screener/image.go
  function getPixels (line 10) | func getPixels(file io.Reader) ([]byte, error) {
  function getPixelsFromImage (line 34) | func getPixelsFromImage(img image.Image) ([]byte, error) {

FILE: screener/screen.go
  type Screen (line 13) | type Screen struct
    method Clean (line 68) | func (s *Screen) Clean() {
    method Print (line 72) | func (s *Screen) Print(matrix matrix.Matrix) {
    method PrintPng (line 128) | func (s *Screen) PrintPng(imgBytes []byte, w int, h int, x int, y int) {
    method PrintAlert (line 150) | func (s *Screen) PrintAlert(message string, width int) {
    method Clear (line 160) | func (s *Screen) Clear() {
    method ClearFlash (line 165) | func (s *Screen) ClearFlash() {
    method RefreshFlash (line 170) | func (s *Screen) RefreshFlash() {
    method GetOriginalMatrix (line 176) | func (s *Screen) GetOriginalMatrix() matrix.Matrix {
  function InitScreen (line 27) | func InitScreen() (s *Screen) {
  function same (line 77) | func same(a matrix.MatrixElement, b matrix.MatrixElement) bool {
  function printDiff (line 81) | func printDiff(previous matrix.Matrix, next matrix.Matrix, fb *gofbink.F...
  function getCharImage (line 134) | func getCharImage(s string) []byte {

FILE: utils/text.go
  function WrapLine (line 5) | func WrapLine(text string, lineWidth int) (wrapped string) {
  function WrapText (line 29) | func WrapText(text string, lineWidth int) string {

FILE: utils/utils.go
  type Config (line 13) | type Config struct
  function LoadConfig (line 17) | func LoadConfig(saveLocation string) Config {
  function SaveConfig (line 35) | func SaveConfig(config Config, saveLocation string) {
  function IsLetter (line 43) | func IsLetter(s string) bool {
  function InsertAt (line 47) | func InsertAt(text string, insert string, index int) string {
  function DeleteAt (line 55) | func DeleteAt(text string, index int) string {
  function LenString (line 60) | func LenString(s string) int {

FILE: views/document.go
  function Document (line 15) | func Document(screen *screener.Screen, bus EventBus.Bus, documentPath st...

FILE: views/menu.go
  type Option (line 18) | type Option struct
  function createMenu (line 23) | func createMenu(title string, options []Option) func(screen *screener.Sc...
  function MainMenu (line 68) | func MainMenu(screen *screener.Screen, bus EventBus.Bus, saveLocation st...
  function FileMenu (line 118) | func FileMenu(screen *screener.Screen, bus EventBus.Bus, saveLocation st...
  function SettingsMenu (line 167) | func SettingsMenu(screen *screener.Screen, bus EventBus.Bus, saveLocatio...

FILE: views/qr.go
  function Qr (line 13) | func Qr(screen *screener.Screen, bus EventBus.Bus, saveLocation string) ...

FILE: views/textView.go
  type TextView (line 11) | type TextView struct
    method init (line 27) | func (t *TextView) init(width int) {
    method setContent (line 31) | func (t *TextView) setContent(text string) {
    method setCursorIndex (line 42) | func (t *TextView) setCursorIndex(index int) {
    method setCursorPos (line 78) | func (t *TextView) setCursorPos(position Position) {
    method renderMatrix (line 112) | func (t *TextView) renderMatrix() matrix.Matrix {
    method updateScroll (line 125) | func (t *TextView) updateScroll() {
  type Position (line 22) | type Position struct
Condensed preview — 18 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (39K chars).
[
  {
    "path": ".gitignore",
    "chars": 10,
    "preview": "kobowriter"
  },
  {
    "path": "Makefile",
    "chars": 128,
    "preview": ".PHONY: build\nbuild: \n\tCGO_ENABLED=1 GOARCH=arm GOOS=linux CC=${CROSS_TC}-gcc CXX=${CROSS_TC}-g++ go build -o ./build/ko"
  },
  {
    "path": "README.md",
    "chars": 4034,
    "preview": "<p align=\"center\">\n  <img src=\"assets/kobowriter.png\" />\n</p>\n\n# Kobowriter\n\nThis small project aims to let you use your"
  },
  {
    "path": "event/key.go",
    "chars": 2259,
    "preview": "//AZERTYkeybindings\n\npackage event\n\nvar KeyCode = map[int]string{\n\t0: \"KEY_RESERVED\",\n\t1: \"KEY_ESC\",\n\n\t2:  \"&\",\n\t3:  \"é\""
  },
  {
    "path": "event/keyEvents.go",
    "chars": 1828,
    "preview": "package event\n\nimport (\n\t\"github.com/MarinX/keylogger\"\n\t\"github.com/asaskevich/EventBus\"\n\t\"github.com/olup/kobowriter/ut"
  },
  {
    "path": "findKeyboard.go",
    "chars": 1055,
    "preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"os/exec\"\n\t\"time\"\n\n\t\"github.com/MarinX/keylogger\"\n\t\"github.com/asaskevich/EventBus\"\n\n\t\"gi"
  },
  {
    "path": "go.mod",
    "chars": 502,
    "preview": "module github.com/olup/kobowriter\n\ngo 1.16\n\nrequire (\n\tgithub.com/MarinX/keylogger v0.0.0-20210528193429-a54d7834cc1a\n\tg"
  },
  {
    "path": "go.sum",
    "chars": 2939,
    "preview": "github.com/MarinX/keylogger v0.0.0-20210528193429-a54d7834cc1a h1:ItKXWegGGThcahUf+ylKFa5pwqkRJofaOyeGdzwO2mM=\ngithub.co"
  },
  {
    "path": "main.go",
    "chars": 1743,
    "preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"os/exec\"\n\n\t\"github.com/asaskevich/EventBus\"\n\n\t_ \"embed\"\n\n\t\"github.com/olup/kobowriter/sc"
  },
  {
    "path": "matrix/matrix.go",
    "chars": 2144,
    "preview": "package matrix\n\nimport (\n\t\"strings\"\n\n\t\"github.com/olup/kobowriter/utils\"\n)\n\ntype MatrixElement struct {\n\tContent    rune"
  },
  {
    "path": "screener/image.go",
    "chars": 981,
    "preview": "package screener\n\nimport (\n\t\"image\"\n\t\"image/png\"\n\t\"io\"\n)\n\n// Get the bi-dimensional pixel array\nfunc getPixels(file io.R"
  },
  {
    "path": "screener/screen.go",
    "chars": 4496,
    "preview": "package screener\n\nimport (\n\t\"bytes\"\n\t\"image\"\n\t\"math\"\n\n\t\"github.com/fogleman/gg\"\n\t\"github.com/olup/kobowriter/matrix\"\n\t\"g"
  },
  {
    "path": "utils/text.go",
    "chars": 718,
    "preview": "package utils\n\nimport \"strings\"\n\nfunc WrapLine(text string, lineWidth int) (wrapped string) {\n\tif text == \"\" {\n\t\treturn "
  },
  {
    "path": "utils/utils.go",
    "chars": 1401,
    "preview": "package utils\n\nimport (\n\t\"encoding/json\"\n\t\"os\"\n\t\"path\"\n\t\"strings\"\n\t\"unicode/utf8\"\n\n\tgonanoid \"github.com/matoous/go-nano"
  },
  {
    "path": "views/document.go",
    "chars": 2603,
    "preview": "package views\n\nimport (\n\t\"os\"\n\t\"path\"\n\t\"time\"\n\n\t\"github.com/asaskevich/EventBus\"\n\t\"github.com/olup/kobowriter/event\"\n\t\"g"
  },
  {
    "path": "views/menu.go",
    "chars": 4265,
    "preview": "package views\n\nimport (\n\t\"os\"\n\t\"os/exec\"\n\t\"path\"\n\t\"sort\"\n\t\"strings\"\n\n\t\"github.com/asaskevich/EventBus\"\n\tgonanoid \"github"
  },
  {
    "path": "views/qr.go",
    "chars": 779,
    "preview": "package views\n\nimport (\n\t\"os\"\n\n\t\"github.com/asaskevich/EventBus\"\n\t\"github.com/olup/kobowriter/event\"\n\t\"github.com/olup/k"
  },
  {
    "path": "views/textView.go",
    "chars": 2351,
    "preview": "package views\n\nimport (\n\t\"strings\"\n\t\"unicode/utf8\"\n\n\t\"github.com/olup/kobowriter/matrix\"\n\t\"github.com/olup/kobowriter/ut"
  }
]

About this extraction

This page contains the full source code of the olup/kobowriter GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 18 files (33.4 KB), approximately 11.7k tokens, and a symbol index with 52 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!