Skip to content

Commit 8f62628

Browse files
committed
Refactor library API and provide more helpers. Support Go 1.9.
* 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
1 parent 8f7a4f7 commit 8f62628

15 files changed

Lines changed: 568 additions & 122 deletions

File tree

Dockerfile

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
FROM ubuntu:xenial
2+
3+
RUN apt-get update && \
4+
apt-get install -y xz-utils build-essential libc6-i386 wget nano && \
5+
apt-get clean
6+
7+
# download Pocketbook SDK
8+
RUN wget https://storage.googleapis.com/dennwc-public/pbsdk-linux-1.1.0.deb -qO /tmp/pbsdk-linux.deb && \
9+
dpkg -i /tmp/pbsdk-linux.deb && \
10+
rm /tmp/pbsdk-linux.deb
11+
12+
ADD ./patches/* /tmp/
13+
14+
# download specified Go binary release that will act as a bootstrap compiler for Go toolchain
15+
# download sources for that release and apply the patch
16+
# build a new toolchain and remove an old one
17+
RUN wget https://dl.google.com/go/go1.9.4.linux-amd64.tar.gz -qO /tmp/go.tar.gz && \
18+
tar -xf /tmp/go.tar.gz && \
19+
rm /tmp/go.tar.gz && \
20+
wget https://dl.google.com/go/go1.9.4.src.tar.gz -qO /tmp/go.tar.gz && \
21+
mkdir -p /gosrc && tar -xf /tmp/go.tar.gz -C /gosrc && \
22+
rm /tmp/go.tar.gz && \
23+
patch /gosrc/go/src/cmd/go/internal/work/build.go < /tmp/go-pb.patch && \
24+
patch /gosrc/go/src/net/dnsconfig_unix.go < /tmp/dns-pb.patch && \
25+
cd /gosrc/go/src && GOROOT_BOOTSTRAP=/go ./make.bash && \
26+
rm -r /go && mv /gosrc/go /go && rm -r /gosrc
27+
28+
WORKDIR /app
29+
VOLUME /app
30+
31+
ADD build.sh /
32+
ENTRYPOINT ["/build.sh"]
33+
34+
ADD ./* /gopath/src/github.com/dennwc/inkview/

README.md

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,63 @@
1-
Go SDK for Pocketbook based on libinkview.
1+
# Go SDK for Pocketbook
22

3-
To build your app or examples, run:
3+
Unofficial Go SDK for Pocketbook based on libinkview.
4+
5+
Supports graphical user interfaces and CLI apps.
6+
7+
## Build a CLI app
8+
9+
Standard Go compiler should be able to cross-compile the binary
10+
for the device (no need for SDK):
11+
12+
```
13+
GOOS=linux GOARCH=arm GOARM=5 go build main.go
14+
```
15+
16+
Note that some additional workarounds are necessary if you want to access
17+
a network from your app. In this case you may still need SDK.
18+
19+
Although this binary will run on the device, you will need a third-party
20+
application to actually see an output of you program (like
21+
[pbterm](http://users.physik.fu-berlin.de/~jtt/PB/)).
22+
23+
The second option is to wrap the program into `RunCLI` - it will
24+
emulate terminal output and write it to device display.
25+
26+
## Build an app with UI
27+
28+
To build your app or any example, run (requires Docker):
429

530
```bash
6-
cd ./go_app_path/
31+
cd ./examples/devinfo/
732
docker run --rm -v $PWD:/app dennwc/pocketbook-go-sdk main.go
8-
```
33+
```
34+
35+
You may also need to mount GOPATH to container to build your app:
36+
37+
```
38+
docker run --rm -v $PWD:/app -v $GOPATH:/gopath dennwc/pocketbook-go-sdk main.go
39+
```
40+
41+
To run an binary, copy it into `applications/app-name.app` folder
42+
on the device and it should appear in the applications list.
43+
44+
## Notes on networking
45+
46+
By default, device will try to shutdown network interface to save battery,
47+
thus you will need to call SDK functions to keep device online (see `KeepNetwork`).
48+
49+
Also note that establishing TLS will require Go to read system
50+
certificate pool that might take up to 30 sec on some devices and will
51+
lead to TLS handshake timeouts. You will need to call `InitCerts` first
52+
to fix the problem.
53+
54+
IPv6 is not enabled on some devices, thus a patch to Go DNS lib is required
55+
to skip lookup on IPv6 address (SDK already includes the patch).
56+
Similar problems may arise when trying to dial IPv6 directly.
57+
58+
## Notes on workdir
59+
60+
Application will have a working directory set to FS root, and not to
61+
a parent directory.
62+
To use relative paths properly change local dir to a binary's parent
63+
directory: `os.Chdir(filepath.Dir(os.Args[0]))`.

app.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package ink
2+
3+
type App interface {
4+
// Init is called when application is started.
5+
Init() error
6+
// Close is called before exiting an application.
7+
Close() error
8+
9+
// Draw is called each time an application view should be updated.
10+
// Can be queued by Repaint.
11+
Draw()
12+
13+
//// Show is called when application becomes active.
14+
//// Delivered on application start and when switching from another app.
15+
//Show() bool
16+
//// Hide is called when application becomes inactive (switching to another app).
17+
//Hide() bool
18+
19+
// Key is called on each key-related event.
20+
Key(e KeyEvent) bool
21+
// Pointer is called on each pointer-related event.
22+
Pointer(e PointerEvent) bool
23+
// Touch is called on each touch-related event.
24+
Touch(e TouchEvent) bool
25+
// Orientation is called each time an orientation of device changes.
26+
Orientation(o Orientation) bool
27+
}

build.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/usr/bin/env bash
2+
export GOROOT=/go
3+
export GOPATH=/gopath
4+
export PATH="$GOROOT/bin:$PATH"
5+
cd /app
6+
CC=arm-none-linux-gnueabi-gcc GOOS=linux GOARCH=arm GOARM=5 CGO_ENABLED=1 go build "$@"

certs.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package ink
2+
3+
import (
4+
"crypto/x509"
5+
"encoding/asn1"
6+
)
7+
8+
// InitCerts will read system certificates pool.
9+
//
10+
// This pool is usually populated by the first call to tls.Dial or similar,
11+
// but this operation might take up to 30 sec on some devices, leading to handshake timeout.
12+
//
13+
// Calling this function before dialing will fix the problem.
14+
func InitCerts() error {
15+
// hand-crafted fake cert that will force system pool to be populated
16+
// but will fail with an error directly after this
17+
cert := x509.Certificate{
18+
Raw: []byte{0},
19+
UnhandledCriticalExtensions: []asn1.ObjectIdentifier{nil},
20+
}
21+
_, err := cert.Verify(x509.VerifyOptions{})
22+
if _, ok := err.(x509.SystemRootsError); ok {
23+
return err
24+
}
25+
return nil
26+
}

cli.go

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
package ink
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"io"
7+
"sync"
8+
"sync/atomic"
9+
"time"
10+
)
11+
12+
var DefaultFontHeight = 14
13+
14+
type RunFunc func(ctx context.Context, w io.Writer) error
15+
16+
func newLogWriter(log *Log, update func()) *logWriter {
17+
return &logWriter{log: log, update: update}
18+
}
19+
20+
type logWriter struct {
21+
log *Log
22+
update func()
23+
}
24+
25+
func (w *logWriter) Write(p []byte) (int, error) {
26+
defer w.update()
27+
return w.log.Write(p)
28+
}
29+
func (w *logWriter) draw() {
30+
w.log.Draw()
31+
}
32+
func (w *logWriter) close() {
33+
w.log.Close()
34+
}
35+
36+
func newCliApp(fnc RunFunc, c RunConfig) *cliApp {
37+
return &cliApp{cli: fnc, conf: c}
38+
}
39+
40+
type cliApp struct {
41+
redraws int32 // atomic
42+
43+
conf RunConfig
44+
cli RunFunc
45+
46+
wg sync.WaitGroup
47+
err error
48+
stop func()
49+
50+
log *logWriter
51+
52+
stopNet func()
53+
54+
rmu sync.Mutex
55+
running bool
56+
}
57+
58+
func (app *cliApp) setRunning(v bool) {
59+
app.rmu.Lock()
60+
app.running = v
61+
app.rmu.Unlock()
62+
}
63+
64+
func (app *cliApp) isRunning() bool {
65+
app.rmu.Lock()
66+
v := app.running
67+
app.rmu.Unlock()
68+
return v
69+
}
70+
71+
func (app *cliApp) redraw() {
72+
// allow only one repaint in queue
73+
if atomic.CompareAndSwapInt32(&app.redraws, 0, 1) {
74+
Repaint()
75+
}
76+
}
77+
func (app *cliApp) draw() {
78+
ClearScreen()
79+
app.log.draw()
80+
FullUpdate()
81+
atomic.StoreInt32(&app.redraws, 0)
82+
}
83+
84+
func (app *cliApp) println(args ...interface{}) {
85+
fmt.Fprintln(app.log, args...)
86+
}
87+
88+
func (app *cliApp) Init() error {
89+
ClearScreen()
90+
l := NewLog(Pad(Screen(), 10), DefaultFontHeight)
91+
app.log = newLogWriter(l, app.redraw)
92+
93+
if app.conf.Certs {
94+
now := time.Now()
95+
app.println("reading certs...")
96+
app.draw()
97+
if err := InitCerts(); err != nil {
98+
app.println("error reading certs:", err)
99+
} else {
100+
app.println("loaded certs in", time.Since(now))
101+
}
102+
app.draw()
103+
}
104+
105+
if app.conf.Network {
106+
var err error
107+
app.stopNet, err = KeepNetwork()
108+
if err != nil {
109+
app.println("cannot connect to the network:", err)
110+
} else {
111+
app.println("network connected")
112+
}
113+
app.draw()
114+
}
115+
116+
app.setRunning(true)
117+
118+
ctx, cancel := context.WithCancel(context.Background())
119+
app.stop = cancel
120+
app.wg.Add(1)
121+
go func() {
122+
defer app.wg.Done()
123+
err := app.cli(ctx, app.log)
124+
if app.stopNet != nil {
125+
app.stopNet()
126+
}
127+
app.err = err
128+
if err != nil {
129+
app.println("error:", err)
130+
}
131+
app.println("<press any key to exit>")
132+
app.redraw()
133+
}()
134+
return nil
135+
}
136+
137+
func (app *cliApp) stopCli() error {
138+
if !app.isRunning() {
139+
return app.err
140+
}
141+
app.stop()
142+
app.wg.Wait()
143+
app.setRunning(false)
144+
return app.err
145+
}
146+
147+
func (app *cliApp) Close() error {
148+
err := app.stopCli()
149+
app.log.close()
150+
if app.stopNet != nil {
151+
app.stopNet()
152+
}
153+
return err
154+
}
155+
156+
func (app *cliApp) Draw() {
157+
app.draw()
158+
}
159+
160+
func (*cliApp) Show() bool {
161+
return false
162+
}
163+
164+
func (*cliApp) Hide() bool {
165+
return false
166+
}
167+
168+
func (app *cliApp) Key(e KeyEvent) bool {
169+
if app.isRunning() || (e.Key == KeyPrev && e.State == KeyStateDown) {
170+
Exit()
171+
return true
172+
}
173+
return false
174+
}
175+
176+
func (app *cliApp) Pointer(e PointerEvent) bool {
177+
if app.isRunning() {
178+
Exit()
179+
return true
180+
}
181+
if e.State == PointerDown {
182+
app.redraw()
183+
}
184+
return true
185+
}
186+
187+
func (*cliApp) Touch(e TouchEvent) bool {
188+
return false
189+
}
190+
191+
func (*cliApp) Orientation(o Orientation) bool {
192+
return false
193+
}
194+
195+
type RunConfig struct {
196+
Certs bool // initialize certificate pool
197+
Network bool // keep networking enabled while app is running
198+
}
199+
200+
// RunCLI starts a command-line application that can write to device display.
201+
// Context will be cancelled when application is closed.
202+
// Provided callback can use any SDK functions.
203+
func RunCLI(fnc RunFunc, c *RunConfig) error {
204+
if c == nil {
205+
c = &RunConfig{}
206+
}
207+
return Run(newCliApp(fnc, *c))
208+
}

0 commit comments

Comments
 (0)