Skip to content

Commit

Permalink
Refactor library API and provide more helpers. Support Go 1.9.
Browse files Browse the repository at this point in the history
* Support for Go 1.9

* Refactor and rewrite most of API surface

* Merged SDK Dockerfile into repository

* Provide helpers for networking, certs and wrapper for CLI apps

* Add README
  • Loading branch information
dennwc committed Feb 19, 2018
1 parent 8f7a4f7 commit 8f62628
Show file tree
Hide file tree
Showing 15 changed files with 568 additions and 122 deletions.
34 changes: 34 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
FROM ubuntu:xenial

RUN apt-get update && \
apt-get install -y xz-utils build-essential libc6-i386 wget nano && \
apt-get clean

# download Pocketbook SDK
RUN wget https://storage.googleapis.com/dennwc-public/pbsdk-linux-1.1.0.deb -qO /tmp/pbsdk-linux.deb && \
dpkg -i /tmp/pbsdk-linux.deb && \
rm /tmp/pbsdk-linux.deb

ADD ./patches/* /tmp/

# download specified Go binary release that will act as a bootstrap compiler for Go toolchain
# download sources for that release and apply the patch
# build a new toolchain and remove an old one
RUN wget https://dl.google.com/go/go1.9.4.linux-amd64.tar.gz -qO /tmp/go.tar.gz && \
tar -xf /tmp/go.tar.gz && \
rm /tmp/go.tar.gz && \
wget https://dl.google.com/go/go1.9.4.src.tar.gz -qO /tmp/go.tar.gz && \
mkdir -p /gosrc && tar -xf /tmp/go.tar.gz -C /gosrc && \
rm /tmp/go.tar.gz && \
patch /gosrc/go/src/cmd/go/internal/work/build.go < /tmp/go-pb.patch && \
patch /gosrc/go/src/net/dnsconfig_unix.go < /tmp/dns-pb.patch && \
cd /gosrc/go/src && GOROOT_BOOTSTRAP=/go ./make.bash && \
rm -r /go && mv /gosrc/go /go && rm -r /gosrc

WORKDIR /app
VOLUME /app

ADD build.sh /
ENTRYPOINT ["/build.sh"]

ADD ./* /gopath/src/github.com/dennwc/inkview/
63 changes: 59 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,63 @@
Go SDK for Pocketbook based on libinkview.
# Go SDK for Pocketbook

To build your app or examples, run:
Unofficial Go SDK for Pocketbook based on libinkview.

Supports graphical user interfaces and CLI apps.

## Build a CLI app

Standard Go compiler should be able to cross-compile the binary
for the device (no need for SDK):

```
GOOS=linux GOARCH=arm GOARM=5 go build main.go
```

Note that some additional workarounds are necessary if you want to access
a network from your app. In this case you may still need SDK.

Although this binary will run on the device, you will need a third-party
application to actually see an output of you program (like
[pbterm](http://users.physik.fu-berlin.de/~jtt/PB/)).

The second option is to wrap the program into `RunCLI` - it will
emulate terminal output and write it to device display.

## Build an app with UI

To build your app or any example, run (requires Docker):

```bash
cd ./go_app_path/
cd ./examples/devinfo/
docker run --rm -v $PWD:/app dennwc/pocketbook-go-sdk main.go
```
```

You may also need to mount GOPATH to container to build your app:

```
docker run --rm -v $PWD:/app -v $GOPATH:/gopath dennwc/pocketbook-go-sdk main.go
```

To run an binary, copy it into `applications/app-name.app` folder
on the device and it should appear in the applications list.

## Notes on networking

By default, device will try to shutdown network interface to save battery,
thus you will need to call SDK functions to keep device online (see `KeepNetwork`).

Also note that establishing TLS will require Go to read system
certificate pool that might take up to 30 sec on some devices and will
lead to TLS handshake timeouts. You will need to call `InitCerts` first
to fix the problem.

IPv6 is not enabled on some devices, thus a patch to Go DNS lib is required
to skip lookup on IPv6 address (SDK already includes the patch).
Similar problems may arise when trying to dial IPv6 directly.

## Notes on workdir

Application will have a working directory set to FS root, and not to
a parent directory.
To use relative paths properly change local dir to a binary's parent
directory: `os.Chdir(filepath.Dir(os.Args[0]))`.
27 changes: 27 additions & 0 deletions app.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package ink

type App interface {
// Init is called when application is started.
Init() error
// Close is called before exiting an application.
Close() error

// Draw is called each time an application view should be updated.
// Can be queued by Repaint.
Draw()

//// Show is called when application becomes active.
//// Delivered on application start and when switching from another app.
//Show() bool
//// Hide is called when application becomes inactive (switching to another app).
//Hide() bool

// Key is called on each key-related event.
Key(e KeyEvent) bool
// Pointer is called on each pointer-related event.
Pointer(e PointerEvent) bool
// Touch is called on each touch-related event.
Touch(e TouchEvent) bool
// Orientation is called each time an orientation of device changes.
Orientation(o Orientation) bool
}
6 changes: 6 additions & 0 deletions build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/usr/bin/env bash
export GOROOT=/go
export GOPATH=/gopath
export PATH="$GOROOT/bin:$PATH"
cd /app
CC=arm-none-linux-gnueabi-gcc GOOS=linux GOARCH=arm GOARM=5 CGO_ENABLED=1 go build "$@"
26 changes: 26 additions & 0 deletions certs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package ink

import (
"crypto/x509"
"encoding/asn1"
)

// InitCerts will read system certificates pool.
//
// This pool is usually populated by the first call to tls.Dial or similar,
// but this operation might take up to 30 sec on some devices, leading to handshake timeout.
//
// Calling this function before dialing will fix the problem.
func InitCerts() error {
// hand-crafted fake cert that will force system pool to be populated
// but will fail with an error directly after this
cert := x509.Certificate{
Raw: []byte{0},
UnhandledCriticalExtensions: []asn1.ObjectIdentifier{nil},
}
_, err := cert.Verify(x509.VerifyOptions{})
if _, ok := err.(x509.SystemRootsError); ok {
return err
}
return nil
}
208 changes: 208 additions & 0 deletions cli.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
package ink

import (
"context"
"fmt"
"io"
"sync"
"sync/atomic"
"time"
)

var DefaultFontHeight = 14

type RunFunc func(ctx context.Context, w io.Writer) error

func newLogWriter(log *Log, update func()) *logWriter {
return &logWriter{log: log, update: update}
}

type logWriter struct {
log *Log
update func()
}

func (w *logWriter) Write(p []byte) (int, error) {
defer w.update()
return w.log.Write(p)
}
func (w *logWriter) draw() {
w.log.Draw()
}
func (w *logWriter) close() {
w.log.Close()
}

func newCliApp(fnc RunFunc, c RunConfig) *cliApp {
return &cliApp{cli: fnc, conf: c}
}

type cliApp struct {
redraws int32 // atomic

conf RunConfig
cli RunFunc

wg sync.WaitGroup
err error
stop func()

log *logWriter

stopNet func()

rmu sync.Mutex
running bool
}

func (app *cliApp) setRunning(v bool) {
app.rmu.Lock()
app.running = v
app.rmu.Unlock()
}

func (app *cliApp) isRunning() bool {
app.rmu.Lock()
v := app.running
app.rmu.Unlock()
return v
}

func (app *cliApp) redraw() {
// allow only one repaint in queue
if atomic.CompareAndSwapInt32(&app.redraws, 0, 1) {
Repaint()
}
}
func (app *cliApp) draw() {
ClearScreen()
app.log.draw()
FullUpdate()
atomic.StoreInt32(&app.redraws, 0)
}

func (app *cliApp) println(args ...interface{}) {
fmt.Fprintln(app.log, args...)
}

func (app *cliApp) Init() error {
ClearScreen()
l := NewLog(Pad(Screen(), 10), DefaultFontHeight)
app.log = newLogWriter(l, app.redraw)

if app.conf.Certs {
now := time.Now()
app.println("reading certs...")
app.draw()
if err := InitCerts(); err != nil {
app.println("error reading certs:", err)
} else {
app.println("loaded certs in", time.Since(now))
}
app.draw()
}

if app.conf.Network {
var err error
app.stopNet, err = KeepNetwork()
if err != nil {
app.println("cannot connect to the network:", err)
} else {
app.println("network connected")
}
app.draw()
}

app.setRunning(true)

ctx, cancel := context.WithCancel(context.Background())
app.stop = cancel
app.wg.Add(1)
go func() {
defer app.wg.Done()
err := app.cli(ctx, app.log)
if app.stopNet != nil {
app.stopNet()
}
app.err = err
if err != nil {
app.println("error:", err)
}
app.println("<press any key to exit>")
app.redraw()
}()
return nil
}

func (app *cliApp) stopCli() error {
if !app.isRunning() {
return app.err
}
app.stop()
app.wg.Wait()
app.setRunning(false)
return app.err
}

func (app *cliApp) Close() error {
err := app.stopCli()
app.log.close()
if app.stopNet != nil {
app.stopNet()
}
return err
}

func (app *cliApp) Draw() {
app.draw()
}

func (*cliApp) Show() bool {
return false
}

func (*cliApp) Hide() bool {
return false
}

func (app *cliApp) Key(e KeyEvent) bool {
if app.isRunning() || (e.Key == KeyPrev && e.State == KeyStateDown) {
Exit()
return true
}
return false
}

func (app *cliApp) Pointer(e PointerEvent) bool {
if app.isRunning() {
Exit()
return true
}
if e.State == PointerDown {
app.redraw()
}
return true
}

func (*cliApp) Touch(e TouchEvent) bool {
return false
}

func (*cliApp) Orientation(o Orientation) bool {
return false
}

type RunConfig struct {
Certs bool // initialize certificate pool
Network bool // keep networking enabled while app is running
}

// RunCLI starts a command-line application that can write to device display.
// Context will be cancelled when application is closed.
// Provided callback can use any SDK functions.
func RunCLI(fnc RunFunc, c *RunConfig) error {
if c == nil {
c = &RunConfig{}
}
return Run(newCliApp(fnc, *c))
}
Loading

0 comments on commit 8f62628

Please sign in to comment.