package main import ( // "github.com/fogleman/gg" "github.com/shermp/go-fbink-v2/gofbink" "github.com/shermp/go-kobo-input/koboin" "image" "git.sr.ht/~adnano/go-gemini" "git.sr.ht/~adnano/go-gemini/tofu" "crypto/x509" // "time" // "bytes" "errors" "net/url" "bufio" "io/ioutil" "fmt" "time" "os" "log" "bytes" ) var ( hosts tofu.KnownHosts hostsfile *tofu.HostWriter scanner *bufio.Scanner ) var height int = 1080 var width int = 1440 // TODO get from device instead of hardcoding // drawOSK renders the onscreen keyboard to an image. func drawOSK(keymap []string) *image.RGBA { var im image.RGBA return &im } func trustCertificate(hostname string, cert *x509.Certificate) error { host := tofu.NewHost(hostname, cert.Raw, cert.NotAfter) knownHost, ok := hosts.Lookup(hostname) if ok && time.Now().Before(knownHost.Expires) { // Check fingerprint if bytes.Equal(knownHost.Fingerprint, host.Fingerprint) { return nil } return errors.New("error: fingerprint does not match!") } else { // Expired cert or new host: TOFU hosts.Add(host) hostsfile.WriteHost(host) return nil } return errors.New("error: fingerprint does not match!") } func do(req *gemini.Request, via []*gemini.Request) (*gemini.Response, error) { client := gemini.Client{ TrustCertificate: trustCertificate, } resp, err := client.Do(req) if err != nil { return resp, err } switch resp.Status.Class() { case gemini.StatusClassInput: // TODO implement input /* input, ok := getInput(resp.Meta, resp.Status == gemini.StatusSensitiveInput) if !ok { break } req.URL.ForceQuery = true req.URL.RawQuery = gemini.QueryEscape(input) return do(req, via) */ case gemini.StatusClassRedirect: via = append(via, req) if len(via) > 5 { return resp, errors.New("too many redirects") } target, err := url.Parse(resp.Meta) if err != nil { return resp, err } target = req.URL.ResolveReference(target) redirect := *req redirect.URL = target return do(&redirect, via) } return resp, err } func main() { // Framebuffer setup fbinkOpts := gofbink.FBInkConfig{} rOpts := gofbink.RestrictedConfig{} fb := gofbink.New(&fbinkOpts, &rOpts) fb.Open() defer fb.Close() fb.Init(&fbinkOpts) fb.ClearScreen(&fbinkOpts) fb.Refresh(0,0,0,0, gofbink.DitherPassthrough, &fbinkOpts) // Touchscreen setup touchPath := "/dev/input/event1" t := koboin.New(touchPath, height, width) if t == nil { return } defer t.Close() // Logging setup var logFile, err= os.Create("/mnt/onboard/gemini.log") var logger *log.Logger = log.New(logFile, "gemini", log.LstdFlags ) path := "/mnt/onboard/.adds/gemini/known-hosts" // TODO don't hardcode if _, err := os.Stat(path); os.IsNotExist(err) { os.Create(path) fb.Println("Created hosts file") } // Load known hosts file err = hosts.Load(path) if err != nil { fb.Println(err) logger.Fatal(err) } hostsfile, err = tofu.NewHostsFile(path) if err != nil { fb.Println(err) logger.Fatal(err) } url := "gemini://gemini.circumlunar.space" req, err := gemini.NewRequest(url) if err != nil { fb.Println(err) logger.Fatal(err) } resp, err := do(req, nil) if err != nil { fb.Println(err) logger.Fatal(err) } defer resp.Body.Close() // Handle response if resp.Status.Class() == gemini.StatusClassSuccess { body, err := ioutil.ReadAll(resp.Body) if err != nil { fb.Println(err) logger.Fatal(err) } fb.Println(string(body)) } else { fb.Println(fmt.Sprintf("%d %s\n", resp.Status, resp.Meta)) } for { x, y, err := t.GetInput() if err != nil { continue } if x < 100 && y < 100 { fb.Println("Exiting...") break } } hostsfile.Close() }