diff --git a/.gitignore b/.gitignore index 31899a0..2642ee7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ build debian/changelog +pkg/ diff --git a/Makefile b/Makefile index f91dbc2..0f574ad 100644 --- a/Makefile +++ b/Makefile @@ -15,7 +15,7 @@ WORKDIR := $(shell pwd) BUILD_PATH := $(WORKDIR)/build DOCKER_IMAGE_BUILD = mcuadros/octoprint-tft-build -DEBIAN_PACKAGES = JESSIE STRETCH +DEBIAN_PACKAGES = STRETCH STRETCH_NAME := stretch STRETCH_IMAGE := golang:1.9-stretch STRETCH_GO_TAGS := gtk_3_22 @@ -26,15 +26,15 @@ JESSIE_GO_TAGS := gtk_3_14 # Build information -GIT_COMMIT = $(shell git rev-parse HEAD | cut -c1-7) -DEV_PREFIX := 0.0 -VERSION ?= $(DEV_PREFIX)~git$(GIT_COMMIT) +#GIT_COMMIT = $(shell git rev-parse HEAD | cut -c1-7) +#DEV_PREFIX := 1.0 +VERSION := 1.3 BUILD_DATE ?= $(shell date --utc +%Y%m%d-%H:%M:%S) -BRANCH = $(shell git rev-parse --abbrev-ref HEAD) +#BRANCH = $(shell git rev-parse --abbrev-ref HEAD) -ifneq ($(BRANCH), master) - VERSION := $(shell echo $(BRANCH)| sed -e 's/v//g') -endif +#ifneq ($(BRANCH), master) +# VERSION := $(shell echo $(BRANCH)| sed -e 's/v//g') +#endif # Package information PACKAGE_NAME = octoprint-tft @@ -66,7 +66,7 @@ build-internal: prepare-internal cp ../*.deb /build/; prepare-internal: - dch --create -v $(VERSION)-1 --package $(PACKAGE_NAME) empty; \ + dch --create -v $(VERSION) --package $(PACKAGE_NAME) empty; \ cd $(WORKDIR)/..; \ tar -czf octoprint-tft_$(VERSION).orig.tar.gz --exclude-vcs OctoPrint-TFT diff --git a/README.md b/README.md index 131387a..693ebe6 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -OctoPrint-TFT [![GitHub release](https://img.shields.io/github/release/mcuadros/OctoPrint-TFT.svg)](https://github.com/mcuadros/OctoPrint-TFT/releases) [![license](https://img.shields.io/github/license/mcuadros/OctoPrint-TFT.svg)]() +OctoPrint-TFT [![GitHub release](https://img.shields.io/github/release/Z-Bolt/OctoPrint-TFT.svg)](https://github.com/Z-Bolt/OctoPrint-TFT/releases) [![license](https://img.shields.io/github/license/Z-Bolt/OctoPrint-TFT.svg)]() ============= _OctoPrint-TFT_, a touch interface for TFT touch modules based on GTK+3. diff --git a/debian/local/disablescreenblank.sh b/debian/local/disablescreenblank.sh new file mode 100644 index 0000000..d52f01f --- /dev/null +++ b/debian/local/disablescreenblank.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +screen=${1:-0} + +# wait 10s for the display manager service to start and attach to screen +sleep 10 + +/usr/bin/xset -display :$screen s off # deactivate screen saver +/usr/bin/xset -display :$screen -dpms # disable DPMS +/usr/bin/xset -display :$screen s noblank # disable screen blanking \ No newline at end of file diff --git a/debian/local/octoprint-tft-environment b/debian/local/octoprint-tft-environment index e5c386c..6de059c 100644 --- a/debian/local/octoprint-tft-environment +++ b/debian/local/octoprint-tft-environment @@ -19,3 +19,6 @@ OCTOPRINT_TFT_STYLE_PATH=/opt/octoprint-tft/styles/default/ # Resolution of the application, should be configured to the resolution of your # screen, for example 800x480. By default 480x320. OCTOPRINT_TFT_RESOLUTION= + +# Location of file for logging (Optional) +OCTOPRINT_TFT_LOG_FILE= diff --git a/debian/octoprint-tft.install b/debian/octoprint-tft.install index 615c5c9..0837585 100644 --- a/debian/octoprint-tft.install +++ b/debian/octoprint-tft.install @@ -1,2 +1,3 @@ debian/local/octoprint-tft-environment etc debian/local/insserv.conf.d etc +debian/local/disablescreenblank.sh etc \ No newline at end of file diff --git a/debian/octoprint-tft.postinst b/debian/octoprint-tft.postinst index 082bb47..669589d 100755 --- a/debian/octoprint-tft.postinst +++ b/debian/octoprint-tft.postinst @@ -35,6 +35,7 @@ if [ "$1" = configure ] && [ -d /etc/systemd/system/ ]; then echo "Display manager service is masked" >&2 elif [ -e "$SERVICE" ]; then ln -sf "$SERVICE" "$DEFAULT_SERVICE" + chmod +x /etc/disablescreenblank.sh else echo "WARNING: $SERVICE is the selected default display manager but does not exist" >&2 rm -f "$DEFAULT_SERVICE" diff --git a/debian/octoprint-tft.service b/debian/octoprint-tft.service index c4f43fa..c992c47 100644 --- a/debian/octoprint-tft.service +++ b/debian/octoprint-tft.service @@ -4,13 +4,14 @@ Conflicts=getty@tty7.service After=systemd-user-sessions.service getty@tty7.service plymouth-quit.service [Service] +Type=notify +NotifyAccess=all EnvironmentFile=/etc/octoprint-tft-environment ExecStart=/usr/bin/xinit /usr/bin/OctoPrint-TFT -- :0 -nolisten tcp -nocursor +ExecStartPost=/etc/disablescreenblank.sh 0 StandardOutput=journal Restart=always -RestartSec=1s -TimeoutStopSec=5s -IgnoreSIGPIPE=no +WatchdogSec=10s [Install] WantedBy=graphical.target diff --git a/main.go b/main.go index c51de09..742c9d2 100644 --- a/main.go +++ b/main.go @@ -9,9 +9,8 @@ import ( "strconv" "strings" - "github.com/mcuadros/OctoPrint-TFT/ui" - "github.com/gotk3/gotk3/gtk" + "github.com/mcuadros/OctoPrint-TFT/ui" "gopkg.in/yaml.v1" ) @@ -61,6 +60,7 @@ func main() { gtk.Init(nil) settings, _ := gtk.SettingsGetDefault() + settings.SetProperty("gtk-application-prefer-dark-theme", true) width, height := getSize() diff --git a/styles/default/images/logo-white.svg b/styles/default/images/logo-white.svg new file mode 100644 index 0000000..1bd4d0b --- /dev/null +++ b/styles/default/images/logo-white.svg @@ -0,0 +1,12 @@ + + + + Slice + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/styles/default/images/open.svg b/styles/default/images/open.svg new file mode 100644 index 0000000..7e5b3c8 --- /dev/null +++ b/styles/default/images/open.svg @@ -0,0 +1,11 @@ + + + + open + Created with Sketch. + + + + + + \ No newline at end of file diff --git a/styles/default/style.css b/styles/default/style.css index c847e9e..78643ee 100644 --- a/styles/default/style.css +++ b/styles/default/style.css @@ -1,3 +1,4 @@ + .dialog { border: 2px solid black; } .notification { @@ -14,3 +15,47 @@ .error { background-color:rgba(204, 30, 30, 0.7); } + +scrollbar, scrollbar button { + border: none; + background-color: #000; +} + +window { + background-color: #000; +} + +scrollbar slider { + /* -GtkScrollbar-has-backward-stepper: true; */ + /* -GtkScrollbar-has-forward-stepper: true; */ + min-width: 52px; + border-radius: 8px; +} + +progress, trough { + min-height: 20px; +} + + + +/* GtkWindow { + background-color: green; + border-width: 3px; + border-color: blue; +} */ + +/* GtkButton { + background-image: none; + background-color:#1010FF; + margin:2px; + border-radius:0; +} */ + + +/* button, entry { + color: #ff00ea; + font: 12px "Comic Sans"; + background-color: #1010FF; + } */ + + diff --git a/ui/common.go b/ui/common.go index c0f4dc7..b67e6e2 100644 --- a/ui/common.go +++ b/ui/common.go @@ -16,9 +16,6 @@ import ( var Version = "0.1.x" var Build = "no-set" -const panelW = 4 -const panelH = 2 - type Panel interface { Grid() *gtk.Grid Show() @@ -27,10 +24,12 @@ type Panel interface { } type CommonPanel struct { - UI *UI - g *gtk.Grid - b *BackgroundTask - p Panel + UI *UI + g *gtk.Grid + b *BackgroundTask + p Panel + panelW int + panelH int buttons []gtk.IWidget } @@ -40,14 +39,14 @@ func NewCommonPanel(ui *UI, parent Panel) CommonPanel { g.SetRowHomogeneous(true) g.SetColumnHomogeneous(true) - return CommonPanel{UI: ui, g: g, p: parent} + return CommonPanel{UI: ui, g: g, p: parent, panelW: 4, panelH: 2} } func (p *CommonPanel) Initialize() { - last := panelW * panelH + last := p.panelW * p.panelH if last < len(p.buttons) { - cols := math.Ceil(float64(len(p.buttons)) / float64(panelW)) - last = int(cols) * panelW + cols := math.Ceil(float64(len(p.buttons)) / float64(p.panelW)) + last = int(cols) * p.panelW } for i := len(p.buttons) + 1; i < last; i++ { @@ -62,8 +61,8 @@ func (p *CommonPanel) Parent() Panel { } func (p *CommonPanel) AddButton(b gtk.IWidget) { - x := len(p.buttons) % panelW - y := len(p.buttons) / panelW + x := len(p.buttons) % p.panelW + y := len(p.buttons) / p.panelW p.g.Attach(b, x+1, y, 1, 1) p.buttons = append(p.buttons, b) } @@ -160,6 +159,49 @@ type Step struct { Value interface{} } +func MustPressedButton(label, i string, pressed func(), speed time.Duration) *gtk.Button { + img := MustImageFromFile(i) + released := make(chan bool) + var mutex sync.Mutex + + b, err := gtk.ButtonNewWithLabel(label) + if err != nil { + panic(err) + } + + b.SetImage(img) + b.SetAlwaysShowImage(true) + b.SetImagePosition(gtk.POS_TOP) + b.SetVExpand(true) + b.SetHExpand(true) + + if pressed != nil { + b.Connect("pressed", func() { + go func() { + for { + select { + case <-released: + return + default: + mutex.Lock() + pressed() + time.Sleep(speed * time.Millisecond) + mutex.Unlock() + } + } + }() + }) + } + + if released != nil { + b.Connect("released", func() { + released <- true + }) + } + + return b +} + func MustStepButton(image string, s ...Step) *StepButton { var l string if len(s) != 0 { diff --git a/ui/default.go b/ui/default.go index 12e3e89..da708cf 100644 --- a/ui/default.go +++ b/ui/default.go @@ -21,9 +21,10 @@ func (m *defaultPanel) initialize() { m.Grid().Attach(MustButtonImage("Heat Up", "heat-up.svg", m.showTemperature), 2, 0, 1, 1) m.Grid().Attach(MustButtonImage("Move", "move.svg", m.showMove), 3, 0, 1, 1) m.Grid().Attach(MustButtonImage("Home", "home.svg", m.showHome), 4, 0, 1, 1) - m.Grid().Attach(MustButtonImage("Filament", "filament.svg", m.showFilament), 1, 1, 1, 1) - m.Grid().Attach(MustButtonImage("Control", "control.svg", m.showControl), 2, 1, 1, 1) - m.Grid().Attach(MustButtonImage("Files", "files.svg", m.showFiles), 3, 1, 1, 1) + + m.Grid().Attach(MustButtonImage("Print", "files.svg", m.showFiles), 1, 1, 1, 1) + m.Grid().Attach(MustButtonImage("Filament", "filament.svg", m.showFilament), 2, 1, 1, 1) + m.Grid().Attach(MustButtonImage("Control", "control.svg", m.showControl), 3, 1, 1, 1) m.Grid().Attach(MustButtonImage("System", "settings.svg", m.showSystem), 4, 1, 1, 1) } diff --git a/ui/filament.go b/ui/filament.go index b1bc6a1..9c41585 100644 --- a/ui/filament.go +++ b/ui/filament.go @@ -27,7 +27,7 @@ func FilamentPanel(ui *UI, parent Panel) Panel { m := &filamentPanel{CommonPanel: NewCommonPanel(ui, parent), labels: map[string]*LabelWithImage{}, } - + m.panelH = 3 m.b = NewBackgroundTask(time.Second*5, m.updateTemperatures) m.initialize() filamentPanelInstance = m @@ -39,6 +39,9 @@ func FilamentPanel(ui *UI, parent Panel) Panel { func (m *filamentPanel) initialize() { defer m.Initialize() + m.Grid().Attach(m.createLoadButton(), 1, 1, 1, 1) + m.Grid().Attach(m.createUnloadButton(), 4, 1, 1, 1) + m.Grid().Attach(m.createExtrudeButton("Extrude", "extrude.svg", 1), 1, 0, 1, 1) m.Grid().Attach(m.createExtrudeButton("Retract", "retract.svg", -1), 4, 0, 1, 1) @@ -46,13 +49,13 @@ func (m *filamentPanel) initialize() { m.box.SetVAlign(gtk.ALIGN_CENTER) m.box.SetMarginStart(10) - m.Grid().Attach(m.box, 2, 0, 2, 1) + m.Grid().Attach(m.box, 2, 0, 2, 2) m.amount = MustStepButton("move-step.svg", Step{"5mm", 5}, Step{"10mm", 10}, Step{"1mm", 1}) - m.Grid().Attach(m.amount, 2, 1, 1, 1) + m.Grid().Attach(m.amount, 2, 2, 1, 1) - m.Grid().Attach(m.createToolButton(), 1, 1, 1, 1) - m.Grid().Attach(m.createFlowrateButton(), 3, 1, 1, 1) + m.Grid().Attach(m.createToolButton(), 1, 2, 1, 1) + m.Grid().Attach(m.createFlowrateButton(), 3, 2, 1, 1) } func (m *filamentPanel) updateTemperatures() { @@ -134,8 +137,37 @@ func (m *filamentPanel) createFlowrateButton() *StepButton { return b } +func (m *filamentPanel) createLoadButton() gtk.IWidget { + + return MustButtonImage("Load", "extrude.svg", func() { + cmd := &octoprint.CommandRequest{} + cmd.Commands = []string{"G92 E0", "G0 E600 F5000", "G92 E0", "G0 E65 F500"} + + Logger.Info("Sending filament load request") + if err := cmd.Do(m.UI.Printer); err != nil { + Logger.Error(err) + return + } + }) +} + +func (m *filamentPanel) createUnloadButton() gtk.IWidget { + + return MustButtonImage("Unload", "retract.svg", func() { + cmd := &octoprint.CommandRequest{} + cmd.Commands = []string{"G92 E0", "G0 E-800 F5000"} + + Logger.Info("Sending filament unload request") + if err := cmd.Do(m.UI.Printer); err != nil { + Logger.Error(err) + return + } + }) +} + func (m *filamentPanel) createExtrudeButton(label, image string, dir int) gtk.IWidget { - return MustButtonImage(label, image, func() { + + return MustPressedButton(label, image, func() { cmd := &octoprint.ToolExtrudeRequest{} cmd.Amount = m.amount.Value().(int) * dir @@ -144,5 +176,16 @@ func (m *filamentPanel) createExtrudeButton(label, image string, dir int) gtk.IW Logger.Error(err) return } - }) + }, 200) + + // return MustButtonImage(label, image, func() { + // cmd := &octoprint.ToolExtrudeRequest{} + // cmd.Amount = m.amount.Value().(int) * dir + + // Logger.Infof("Sending extrude request, with amount %d", cmd.Amount) + // if err := cmd.Do(m.UI.Printer); err != nil { + // Logger.Error(err) + // return + // } + // }) } diff --git a/ui/files.go b/ui/files.go index 48baeee..b9fc31d 100644 --- a/ui/files.go +++ b/ui/files.go @@ -10,17 +10,44 @@ import ( "github.com/mcuadros/go-octoprint" ) +type locationHistory struct { + locations []octoprint.Location +} + +func (l *locationHistory) current() octoprint.Location { + return l.locations[len(l.locations)-1] +} + +func (l *locationHistory) goForward(folder string) { + newLocation := string(l.current()) + "/" + folder + l.locations = append(l.locations, octoprint.Location(newLocation)) +} + +func (l *locationHistory) goBack() { + l.locations = l.locations[0 : len(l.locations)-1] +} + +func (l *locationHistory) isRoot() bool { + if len(l.locations) > 1 { + return false + } else { + return true + } +} + var filesPanelInstance *filesPanel type filesPanel struct { CommonPanel - list *gtk.Box + list *gtk.Box + location locationHistory } func FilesPanel(ui *UI, parent Panel) Panel { if filesPanelInstance == nil { - m := &filesPanel{CommonPanel: NewCommonPanel(ui, parent)} + l := locationHistory{locations: []octoprint.Location{octoprint.Local}} + m := &filesPanel{CommonPanel: NewCommonPanel(ui, parent), location: l} m.initialize() filesPanelInstance = m } @@ -33,6 +60,7 @@ func (m *filesPanel) initialize() { m.list.SetVExpand(true) scroll, _ := gtk.ScrolledWindowNew(nil, nil) + scroll.SetProperty("overlay-scrolling", false) scroll.Add(m.list) box := MustBox(gtk.ORIENTATION_VERTICAL, 0) @@ -53,7 +81,7 @@ func (m *filesPanel) createActionBar() gtk.IWidget { bar.Add(m.createRefreshButton()) bar.Add(m.createInitReleaseSDButton()) - bar.Add(MustButton(MustImageFromFileWithSize("back.svg", 40, 40), m.UI.GoHistory)) + bar.Add(m.createBackButton()) return bar } @@ -62,46 +90,49 @@ func (m *filesPanel) createRefreshButton() gtk.IWidget { return MustButton(MustImageFromFileWithSize("refresh.svg", 40, 40), m.doLoadFiles) } +func (m *filesPanel) createBackButton() gtk.IWidget { + return MustButton(MustImageFromFileWithSize("back.svg", 40, 40), func() { + if m.location.isRoot() { + m.UI.GoHistory() + } else { + m.location.goBack() + m.doLoadFiles() + } + }) + // return MustButton(MustImageFromFileWithSize("refresh.svg", 40, 40), m.doLoadFiles) +} + func (m *filesPanel) doLoadFiles() { - Logger.Info("Refreshing list of files") - m.doRefreshSD() + var files []*octoprint.FileInformation - local := m.doLoadFilesFromLocation(octoprint.Local) - sdcard := m.doLoadFilesFromLocation(octoprint.SDCard) + Logger.Info("Loading list of files from: ", string(m.location.current())) - s := byDate(local) - s = append(s, sdcard...) + // m.doRefreshSD() + r := &octoprint.FilesRequest{Location: m.location.current(), Recursive: false} + folder, err := r.Do(m.UI.Printer) + if err != nil { + Logger.Error(err) + files = []*octoprint.FileInformation{} + } else { + files = folder.Files + } + + s := byDate(files) + // s = append(s, sdcard...) sort.Sort(s) EmptyContainer(&m.list.Container) for _, f := range s { if f.IsFolder() { - continue + m.addFolder(m.list, f) + } else { + m.addFile(m.list, f) } - - m.addFile(m.list, f) } m.list.ShowAll() } -func (m *filesPanel) doRefreshSD() { - if err := (&octoprint.SDRefreshRequest{}).Do(m.UI.Printer); err != nil { - Logger.Error(err) - } -} - -func (m *filesPanel) doLoadFilesFromLocation(l octoprint.Location) []*octoprint.FileInformation { - r := &octoprint.FilesRequest{Location: l, Recursive: true} - files, err := r.Do(m.UI.Printer) - if err != nil { - Logger.Error(err) - return []*octoprint.FileInformation{} - } - - return files.Files -} - func (m *filesPanel) addFile(b *gtk.Box, f *octoprint.FileInformation) { frame, _ := gtk.FrameNew("") @@ -119,7 +150,7 @@ func (m *filesPanel) addFile(b *gtk.Box, f *octoprint.FileInformation) { labels.Add(info) actions := MustBox(gtk.ORIENTATION_HORIZONTAL, 5) - actions.Add(m.createLoadAndPrintButton("load.svg", f, false)) + // actions.Add(m.createLoadAndPrintButton("load.svg", f, false)) actions.Add(m.createLoadAndPrintButton("status.svg", f, true)) file := MustBox(gtk.ORIENTATION_HORIZONTAL, 5) @@ -138,6 +169,41 @@ func (m *filesPanel) addFile(b *gtk.Box, f *octoprint.FileInformation) { b.Add(frame) } +func (m *filesPanel) addFolder(b *gtk.Box, f *octoprint.FileInformation) { + frame, _ := gtk.FrameNew("") + + name := MustLabel(f.Name) + name.SetMarkup(fmt.Sprintf("%s", filenameEllipsis(f.Name))) + name.SetHExpand(true) + + info := MustLabel("") + info.SetMarkup(fmt.Sprintf("Size: %s", + humanize.Bytes(uint64(f.Size)), + )) + + labels := MustBox(gtk.ORIENTATION_VERTICAL, 5) + labels.Add(name) + labels.Add(info) + + actions := MustBox(gtk.ORIENTATION_HORIZONTAL, 5) + actions.Add(m.createOpenFolderButton(f)) + + file := MustBox(gtk.ORIENTATION_HORIZONTAL, 5) + file.SetMarginTop(15) + file.SetMarginEnd(15) + file.SetMarginStart(15) + file.SetMarginBottom(15) + file.SetHExpand(true) + + file.Add(MustImageFromFileWithSize("files.svg", 35, 35)) + + file.Add(labels) + file.Add(actions) + + frame.Add(file) + b.Add(frame) +} + func (m *filesPanel) createLoadAndPrintButton(img string, f *octoprint.FileInformation, print bool) gtk.IWidget { return MustButton( MustImageFromFileWithSize(img, 30, 30), @@ -156,6 +222,15 @@ func (m *filesPanel) createLoadAndPrintButton(img string, f *octoprint.FileInfor ) } +func (m *filesPanel) createOpenFolderButton(f *octoprint.FileInformation) gtk.IWidget { + return MustButton(MustImageFromFileWithSize("open.svg", 30, 30), func() { + m.location.goForward(f.Path) + // m.currentFolder = octoprint.Location(f.Origin + "/" + string()) + // m.parentFolder = octoprint.Location(f.Origin) + m.doLoadFiles() + }) +} + func (m *filesPanel) createInitReleaseSDButton() gtk.IWidget { release := MustImageFromFileWithSize("sd_eject.svg", 40, 40) init := MustImageFromFileWithSize("sd.svg", 40, 40) diff --git a/ui/logger.go b/ui/logger.go index 8d9c705..d6ccc76 100644 --- a/ui/logger.go +++ b/ui/logger.go @@ -1,6 +1,7 @@ package ui import ( + "os" "path" "runtime" "strings" @@ -63,7 +64,22 @@ func (h NotificationsHook) Fire(entry *logrus.Entry) error { var Logger *logrus.Entry func init() { - logrus.AddHook(ContextHook{}) - logrus.SetLevel(logrus.DebugLevel) - Logger = logrus.WithFields(logrus.Fields{}) + var LogFile = os.Getenv("OCTOPRINT_TFT_LOG_FILE") + + var log = logrus.New() + log.AddHook(ContextHook{}) + log.SetLevel(logrus.DebugLevel) + + if LogFile == "" { + log.Out = os.Stdout + } else { + file, err := os.OpenFile(LogFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) + if err == nil { + log.Out = file + } else { + log.Info("Failed to log to file, using default stderr") + } + } + + Logger = log.WithFields(logrus.Fields{}) } diff --git a/ui/move.go b/ui/move.go index b199b94..923672d 100644 --- a/ui/move.go +++ b/ui/move.go @@ -1,8 +1,6 @@ package ui import ( - "strings" - "github.com/gotk3/gotk3/gtk" "github.com/mcuadros/go-octoprint" ) @@ -17,6 +15,8 @@ type movePanel struct { func MovePanel(ui *UI, parent Panel) Panel { if movePanelInstance == nil { m := &movePanel{CommonPanel: NewCommonPanel(ui, parent)} + m.panelH = 3 + m.panelW = 3 m.initialize() movePanelInstance = m } @@ -26,23 +26,24 @@ func MovePanel(ui *UI, parent Panel) Panel { func (m *movePanel) initialize() { defer m.Initialize() + m.Grid().Attach(m.createMoveButton("X-", "move-x-.svg", octoprint.XAxis, -1), 0, 1, 1, 1) + m.Grid().Attach(m.createMoveButton("X+", "move-x+.svg", octoprint.XAxis, 1), 2, 1, 1, 1) + m.Grid().Attach(m.createMoveButton("Y+", "move-y+.svg", octoprint.YAxis, 1), 1, 0, 1, 1) + m.Grid().Attach(m.createMoveButton("Y-", "move-y-.svg", octoprint.YAxis, -1), 1, 2, 1, 1) - m.AddButton(m.createMoveButton("X+", "move-x+.svg", octoprint.XAxis, 1)) - m.AddButton(m.createMoveButton("Y+", "move-y+.svg", octoprint.YAxis, 1)) - m.AddButton(m.createMoveButton("Z+", "move-z+.svg", octoprint.ZAxis, 1)) + m.Grid().Attach(m.createMoveButton("Z+", "move-z+.svg", octoprint.ZAxis, 1), 3, 0, 1, 1) + m.Grid().Attach(m.createMoveButton("Z-", "move-z-.svg", octoprint.ZAxis, -1), 3, 1, 1, 1) m.step = MustStepButton("move-step.svg", Step{"5mm", 5}, Step{"10mm", 10}, Step{"1mm", 1}, ) - m.AddButton(m.step) - m.AddButton(m.createMoveButton("X-", "move-x-.svg", octoprint.XAxis, -1)) - m.AddButton(m.createMoveButton("Y-", "move-y-.svg", octoprint.YAxis, -1)) - m.AddButton(m.createMoveButton("Z-", "move-z-.svg", octoprint.ZAxis, -1)) + m.Grid().Attach(m.step, 2, 2, 1, 1) } func (m *movePanel) createMoveButton(label, image string, a octoprint.Axis, dir int) gtk.IWidget { - return MustButtonImage(label, image, func() { + + return MustPressedButton(label, image, func() { distance := m.step.Value().(int) * dir cmd := &octoprint.PrintHeadJogRequest{} @@ -55,13 +56,14 @@ func (m *movePanel) createMoveButton(label, image string, a octoprint.Axis, dir cmd.Z = distance } - Logger.Warningf("Jogging print head axis %s in %dmm", - strings.ToUpper(string(a)), distance, - ) + // Logger.Warningf("Jogging print head axis %s in %dmm", + // strings.ToUpper(string(a)), distance, + // ) if err := cmd.Do(m.UI.Printer); err != nil { Logger.Error(err) return } - }) + + }, 200) } diff --git a/ui/splash.go b/ui/splash.go index c421f57..2114547 100644 --- a/ui/splash.go +++ b/ui/splash.go @@ -14,8 +14,8 @@ func NewSplashPanel(ui *UI) *SplashPanel { } func (m *SplashPanel) initialize() { - logo := MustImageFromFile("octoprint-logo.png") - m.Label = MustLabel("Connecting to OctoPrint...") + logo := MustImageFromFile("logo-white.svg") + m.Label = MustLabel("Initializing printer...") box := MustBox(gtk.ORIENTATION_VERTICAL, 15) box.SetVAlign(gtk.ALIGN_CENTER) diff --git a/ui/status.go b/ui/status.go index 5e11611..37081c8 100644 --- a/ui/status.go +++ b/ui/status.go @@ -16,15 +16,16 @@ type statusPanel struct { step *StepButton pb *gtk.ProgressBar - bed, tool0, tool1 *LabelWithImage - file, left *LabelWithImage - print, pause, stop *gtk.Button + bed, tool0, tool1, tool2, tool3 *LabelWithImage + file, left *LabelWithImage + print, pause, stop *gtk.Button } func StatusPanel(ui *UI, parent Panel) Panel { if statusPanelInstance == nil { m := &statusPanel{CommonPanel: NewCommonPanel(ui, parent)} - m.b = NewBackgroundTask(time.Second*5, m.update) + m.panelH = 3 + m.b = NewBackgroundTask(time.Second*2, m.update) m.initialize() statusPanelInstance = m @@ -36,10 +37,10 @@ func StatusPanel(ui *UI, parent Panel) Panel { func (m *statusPanel) initialize() { defer m.Initialize() - m.Grid().Attach(m.createMainBox(), 1, 0, 4, 1) - m.Grid().Attach(m.createPrintButton(), 1, 1, 1, 1) - m.Grid().Attach(m.createPauseButton(), 2, 1, 1, 1) - m.Grid().Attach(m.createStopButton(), 3, 1, 1, 1) + m.Grid().Attach(m.createMainBox(), 1, 0, 4, 2) + m.Grid().Attach(m.createPrintButton(), 1, 2, 1, 1) + m.Grid().Attach(m.createPauseButton(), 2, 2, 1, 1) + m.Grid().Attach(m.createStopButton(), 3, 2, 1, 1) } func (m *statusPanel) createProgressBar() *gtk.ProgressBar { @@ -86,6 +87,8 @@ func (m *statusPanel) createTemperatureBox() *gtk.Box { m.bed = MustLabelWithImage("bed.svg", "") m.tool0 = MustLabelWithImage("extruder.svg", "") m.tool1 = MustLabelWithImage("extruder.svg", "") + m.tool2 = MustLabelWithImage("extruder.svg", "") + m.tool3 = MustLabelWithImage("extruder.svg", "") temp := MustBox(gtk.ORIENTATION_VERTICAL, 5) temp.SetHAlign(gtk.ALIGN_START) @@ -94,6 +97,8 @@ func (m *statusPanel) createTemperatureBox() *gtk.Box { temp.Add(m.bed) temp.Add(m.tool0) temp.Add(m.tool1) + temp.Add(m.tool2) + temp.Add(m.tool3) return temp } @@ -128,16 +133,9 @@ func (m *statusPanel) createPauseButton() gtk.IWidget { } func (m *statusPanel) createStopButton() gtk.IWidget { - m.stop = MustButtonImage("Stop", "stop.svg", func() { - defer m.updateTemperature() - - Logger.Warning("Stopping job") - if err := (&octoprint.CancelRequest{}).Do(m.UI.Printer); err != nil { - Logger.Error(err) - return - } - }) - + m.stop = MustButtonImage("Stop", "stop.svg", + ConfirmStopDialog(m.UI.w, "Are you sure you want to stop current print?", m), + ) return m.stop } @@ -156,6 +154,9 @@ func (m *statusPanel) updateTemperature() { m.doUpdateState(&s.State) m.tool1.Hide() + m.tool2.Hide() + m.tool3.Hide() + for tool, s := range s.Temperature.Current { text := fmt.Sprintf("%s: %.0f°C / %.0f°C", strings.Title(tool), s.Actual, s.Target) switch tool { @@ -166,6 +167,12 @@ func (m *statusPanel) updateTemperature() { case "tool1": m.tool1.Label.SetLabel(text) m.tool1.Show() + case "tool2": + m.tool2.Label.SetLabel(text) + m.tool2.Show() + case "tool3": + m.tool3.Label.SetLabel(text) + m.tool3.Show() } } } @@ -220,15 +227,19 @@ func (m *statusPanel) updateJob() { var text string switch s.Progress.Completion { case 100: - text = fmt.Sprintf("Job Completed in %s", time.Duration(int64(s.Job.LastPrintTime)*1e9)) + text = fmt.Sprintf("Completed in %s", time.Duration(int64(s.Job.LastPrintTime)*1e9)) case 0: text = "Warming up ..." default: + Logger.Info(s.Progress.PrintTime) + e := time.Duration(int64(s.Progress.PrintTime) * 1e9) l := time.Duration(int64(s.Progress.PrintTimeLeft) * 1e9) - text = fmt.Sprintf("Elapsed/Left: %s / %s", e, l) + // eta := time.Now().Add(l).Format("3:04 PM") if l == 0 { - text = fmt.Sprintf("Elapsed: %s", e) + text = fmt.Sprintf("Print Time: %s", e) + } else { + text = fmt.Sprintf("Print Time: %s | Left: %s", e, l) } } @@ -236,9 +247,52 @@ func (m *statusPanel) updateJob() { } func filenameEllipsis(name string) string { - if len(name) > 26 { - return name[:23] + "..." + l := len(name) + if l > 32 { + return name[:12] + "..." + name[l-17:l] } return name } + +func btou(b bool) uint8 { + if b { + return 1 + } + return 0 +} + +func ConfirmStopDialog(parent *gtk.Window, msg string, ma *statusPanel) func() { + return func() { + win := gtk.MessageDialogNewWithMarkup( + parent, + gtk.DIALOG_MODAL, + gtk.MESSAGE_INFO, + gtk.BUTTONS_YES_NO, + "", + ) + + win.SetMarkup(CleanHTML(msg)) + defer win.Destroy() + + box, _ := win.GetContentArea() + box.SetMarginStart(15) + box.SetMarginEnd(15) + box.SetMarginTop(15) + box.SetMarginBottom(15) + + ctx, _ := win.GetStyleContext() + ctx.AddClass("dialog") + + ergebnis := win.Run() + + if ergebnis == int(gtk.RESPONSE_YES) { + + Logger.Warning("Stopping job") + if err := (&octoprint.CancelRequest{}).Do(ma.UI.Printer); err != nil { + Logger.Error(err) + return + } + } + } +} diff --git a/ui/system.go b/ui/system.go index 6f51367..7db1b97 100644 --- a/ui/system.go +++ b/ui/system.go @@ -1,9 +1,8 @@ package ui import ( - "bytes" "fmt" - "io/ioutil" + "net" "github.com/dustin/go-humanize" "github.com/gotk3/gotk3/gtk" @@ -101,20 +100,17 @@ func (m *systemPanel) createInfoBox() gtk.IWidget { main.SetHExpand(true) main.SetHAlign(gtk.ALIGN_CENTER) main.SetVExpand(true) - main.Add(MustImageFromFileWithSize("octoprint-logo.png", 140, 140)) + + img := MustImageFromFileWithSize("logo-white.svg", 140, 112) + img.SetMarginTop(35) + main.Add(img) info := MustBox(gtk.ORIENTATION_VERTICAL, 0) info.SetVExpand(true) info.SetVAlign(gtk.ALIGN_CENTER) - m.addOctoPrintTFT(info) - - title := MustLabel("Versions Information") - title.SetMarginTop(15) - title.SetMarginBottom(5) - info.Add(title) + m.addNetwork(info) m.addOctoPrint(info) - m.addOctoPi(info) m.addSystemInfo(info) main.Add(info) @@ -122,35 +118,50 @@ func (m *systemPanel) createInfoBox() gtk.IWidget { return main } -func (m *systemPanel) addOctoPrintTFT(box *gtk.Box) { - title := MustLabel("OctoPrint-TFT Version") - title.SetMarginBottom(5) +// func (m *systemPanel) addOctoPrintTFT(box *gtk.Box) { +// title := MustLabel("OctoPrint-TFT Version") +// title.SetMarginTop(15) +// title.SetMarginBottom(5) - info := MustBox(gtk.ORIENTATION_VERTICAL, 0) - box.Add(info) +// info := MustBox(gtk.ORIENTATION_VERTICAL, 0) +// box.Add(info) - info.Add(title) - info.Add(MustLabel("%s (%s)", Version, Build)) -} +// info.Add(title) +// info.Add(MustLabel("%s (%s)", Version, Build)) +// } -func (m *systemPanel) addOctoPi(box *gtk.Box) { - v, err := ioutil.ReadFile("/etc/octopi_version") - if err != nil { - Logger.Error(err) - return - } +func (m *systemPanel) addNetwork(box *gtk.Box) { + title := MustLabel("Network Information") + title.SetMarginTop(40) + title.SetMarginBottom(5) + + box.Add(title) + addrs, _ := net.InterfaceAddrs() - box.Add(MustLabel("OctoPi Version: %s", bytes.Trim(v, "\n"))) + for _, address := range addrs { + if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { + if ipnet.IP.To4() != nil { + box.Add(MustLabel("IP Address %s", ipnet.IP.String())) + } + } + } } func (m *systemPanel) addOctoPrint(box *gtk.Box) { + title := MustLabel("Versions Information") + title.SetMarginTop(15) + title.SetMarginBottom(5) + box.Add(title) + r, err := (&octoprint.VersionRequest{}).Do(m.UI.Printer) if err != nil { Logger.Error(err) return } + box.Add(MustLabel("UI Version: %s (%s)", Version, Build)) box.Add(MustLabel("OctoPrint Version: %s (%s)", r.Server, r.API)) + } func (m *systemPanel) addSystemInfo(box *gtk.Box) { diff --git a/ui/temperature.go b/ui/temperature.go index 4f9f617..bb6eff6 100644 --- a/ui/temperature.go +++ b/ui/temperature.go @@ -48,10 +48,10 @@ func (m *temperaturePanel) initialize() { m.Grid().Attach(m.box, 2, 0, 2, 1) m.Grid().Attach(m.createToolButton(), 1, 1, 1, 1) - m.amount = MustStepButton("move-step.svg", Step{"5°C", 5.}, Step{"10°C", 10.}, Step{"1°C", 1.}) + m.amount = MustStepButton("move-step.svg", Step{"10°C", 10.}, Step{"5°C", 5.}, Step{"1°C", 1.}) m.Grid().Attach(m.amount, 2, 1, 1, 1) - m.Grid().Attach(MustButtonImage("Profiles", "heat-up.svg", m.profilePanel), 3, 1, 1, 1) + m.Grid().Attach(MustButtonImage("More", "heat-up.svg", m.profilePanel), 3, 1, 1, 1) } func (m *temperaturePanel) createToolButton() *StepButton { @@ -69,13 +69,14 @@ func (m *temperaturePanel) createToolButton() *StepButton { } func (m *temperaturePanel) createChangeButton(label, image string, value float64) gtk.IWidget { - return MustButtonImage(label, image, func() { + + return MustPressedButton(label, image, func() { target := value * m.amount.Value().(float64) if err := m.increaseTarget(m.tool.Value().(string), target); err != nil { Logger.Error(err) return } - }) + }, 100) } func (m *temperaturePanel) increaseTarget(tool string, value float64) error { @@ -153,7 +154,7 @@ func (m *temperaturePanel) addNewTool(tool string) { m.tool.AddStep(Step{strings.Title(tool), tool}) m.tool.Callback() - Logger.Infof("New tool detected %s", tool) + Logger.Infof("Tool detected %s", tool) } func (m *temperaturePanel) loadTemperatureData(tool string, d *octoprint.TemperatureData) { @@ -211,6 +212,7 @@ func (m *profilesPanel) createProfileButton(img string, p *octoprint.Temperature if err := m.setProfile(p); err != nil { Logger.Error(err) } + m.UI.GoHistory() }) } diff --git a/ui/ui.go b/ui/ui.go index e64d518..dd20a60 100644 --- a/ui/ui.go +++ b/ui/ui.go @@ -6,6 +6,7 @@ import ( "sync" "time" + "github.com/coreos/go-systemd/daemon" "github.com/gotk3/gotk3/gdk" "github.com/gotk3/gotk3/gtk" "github.com/mcuadros/go-octoprint" @@ -80,6 +81,8 @@ func (ui *UI) initialize() { ui.g = MustGrid() ui.o.Add(ui.g) ui.o.AddOverlay(ui.Notifications) + + daemon.SdNotify(false, "READY=1") } func (ui *UI) loadStyle() { @@ -97,6 +100,8 @@ func (ui *UI) loadStyle() { var errMercyPeriod = time.Second * 30 func (ui *UI) verifyConnection() { + daemon.SdNotify(false, "WATCHDOG=1") + splash := NewSplashPanel(ui) s, err := (&octoprint.ConnectionRequest{}).Do(ui.Printer) diff --git a/vendor/github.com/coreos/go-systemd/.travis.yml b/vendor/github.com/coreos/go-systemd/.travis.yml new file mode 100644 index 0000000..cfea999 --- /dev/null +++ b/vendor/github.com/coreos/go-systemd/.travis.yml @@ -0,0 +1,45 @@ +dist: trusty +sudo: required +services: + - docker + +language: go +go: + - "1.10.x" + - "1.11.x" +go_import_path: github.com/coreos/go-systemd + +env: + global: + - IMPORTPATH=github.com/coreos/go-systemd + - GOPATH=/opt + - DEP_BINDIR=/tmp + - BUILD_DIR=/opt/src/github.com/coreos/go-systemd + matrix: + - DOCKER_BASE=ubuntu:16.04 + - DOCKER_BASE=ubuntu:18.04 + - DOCKER_BASE=debian:stretch + +before_install: + - sudo apt-get -qq update + - sudo apt-get install -y libsystemd-journal-dev libsystemd-daemon-dev + - curl https://raw.githubusercontent.com/golang/dep/master/install.sh | INSTALL_DIRECTORY=${DEP_BINDIR} sh + - ${DEP_BINDIR}/dep ensure -v + - docker pull ${DOCKER_BASE} + - docker run --privileged -e GOPATH=${GOPATH} --cidfile=/tmp/cidfile ${DOCKER_BASE} /bin/bash -c "apt-get update && apt-get install -y sudo build-essential git golang dbus libsystemd-dev libpam-systemd systemd-container" + - docker commit `cat /tmp/cidfile` go-systemd/container-tests + - rm -f /tmp/cidfile + +install: + - docker run --shm-size=2gb -d --cidfile=/tmp/cidfile --privileged -e GOPATH=${GOPATH} -v ${PWD}:${BUILD_DIR} go-systemd/container-tests /bin/systemd --system + +script: + - ./scripts/travis/pr-test.sh go_fmt + - ./scripts/travis/pr-test.sh build_source + - ./scripts/travis/pr-test.sh build_tests + - docker exec --privileged `cat /tmp/cidfile` /bin/bash -c "cd ${BUILD_DIR} && ./scripts/travis/pr-test.sh run_tests" + - ./scripts/travis/pr-test.sh go_vet + - ./scripts/travis/pr-test.sh license_check + +after_script: + - docker kill `cat /tmp/cidfile` diff --git a/vendor/github.com/coreos/go-systemd/CONTRIBUTING.md b/vendor/github.com/coreos/go-systemd/CONTRIBUTING.md new file mode 100644 index 0000000..0551ed5 --- /dev/null +++ b/vendor/github.com/coreos/go-systemd/CONTRIBUTING.md @@ -0,0 +1,77 @@ +# How to Contribute + +CoreOS projects are [Apache 2.0 licensed](LICENSE) and accept contributions via +GitHub pull requests. This document outlines some of the conventions on +development workflow, commit message formatting, contact points and other +resources to make it easier to get your contribution accepted. + +# Certificate of Origin + +By contributing to this project you agree to the Developer Certificate of +Origin (DCO). This document was created by the Linux Kernel community and is a +simple statement that you, as a contributor, have the legal right to make the +contribution. See the [DCO](DCO) file for details. + +# Email and Chat + +The project currently uses the general CoreOS email list and IRC channel: +- Email: [coreos-dev](https://groups.google.com/forum/#!forum/coreos-dev) +- IRC: #[coreos](irc://irc.freenode.org:6667/#coreos) IRC channel on freenode.org + +Please avoid emailing maintainers found in the MAINTAINERS file directly. They +are very busy and read the mailing lists. + +## Getting Started + +- Fork the repository on GitHub +- Read the [README](README.md) for build and test instructions +- Play with the project, submit bugs, submit patches! + +## Contribution Flow + +This is a rough outline of what a contributor's workflow looks like: + +- Create a topic branch from where you want to base your work (usually master). +- Make commits of logical units. +- Make sure your commit messages are in the proper format (see below). +- Push your changes to a topic branch in your fork of the repository. +- Make sure the tests pass, and add any new tests as appropriate. +- Submit a pull request to the original repository. + +Thanks for your contributions! + +### Coding Style + +CoreOS projects written in Go follow a set of style guidelines that we've documented +[here](https://github.com/coreos/docs/tree/master/golang). Please follow them when +working on your contributions. + +### Format of the Commit Message + +We follow a rough convention for commit messages that is designed to answer two +questions: what changed and why. The subject line should feature the what and +the body of the commit should describe the why. + +``` +scripts: add the test-cluster command + +this uses tmux to setup a test cluster that you can easily kill and +start for debugging. + +Fixes #38 +``` + +The format can be described more formally as follows: + +``` +: + + + +