chore: upload file changes
All checks were successful
Cross-Compile Binaries / compile-linux (push) Successful in 4m1s
Cross-Compile Binaries / compile-windows (push) Successful in 7m32s

This commit is contained in:
2026-01-12 16:28:52 +01:00
parent 6533efb18e
commit 41c8436ca2
12 changed files with 308 additions and 51 deletions

View File

@@ -0,0 +1,109 @@
name: Cross-Compile Binaries
on:
workflow_dispatch:
push:
branches:
- '*'
schedule:
- cron: '0 0 * * 6'
jobs:
compile-linux:
runs-on: ubuntu-latest
strategy:
fail-fast: false
steps:
- name: Checkout and pull the code
uses: actions/checkout@v6
- name: Setup the Go programming language
uses: actions/setup-go@v6
with:
go-version: 'stable'
- name: Install build dependencies
run: |
mv /etc/apt/sources.list.d/microsoft-prod.list /etc/apt/sources.list.d/microsoft-prod.list.disabled
until apt-get update; do
echo -e "-----\napt-get update failed, retrying in 1s...\n-----"
sleep 1s
done
apt-get install -y \
build-essential libgl1-mesa-dev libx11-dev libxrandr-dev \
libxi-dev libxcursor-dev libxinerama-dev libglfw3-dev \
libxxf86vm-dev
- name: Compile the fyne application for native Linux
run: |
export CGO_ENABLED=1
export CC=gcc
export CXX=g++
export GOOS=linux
export GOARCH=amd64
go build -o ./akartontv ./src
- name: upload the building actifacts
uses: DaanSelen/upload-artifact-gitea@main
with:
name: package-linux64
path: |
./akartontv
retention-days: 3
overwrite: true
compile-windows:
runs-on: ubuntu-latest
strategy:
fail-fast: false
steps:
- name: Checkout and pull the code
uses: actions/checkout@v6
- name: Setup the Go programming language
uses: actions/setup-go@v6
with:
go-version: 'stable'
- name: Install build dependencies
run: |
mv /etc/apt/sources.list.d/microsoft-prod.list /etc/apt/sources.list.d/microsoft-prod.list.disabled
until apt-get update; do
echo -e "-----\napt-get update failed, retrying in 1s...\n-----"
sleep 1s
done
apt-get install -y \
build-essential libgl1-mesa-dev libx11-dev libxrandr-dev \
libxi-dev libxcursor-dev libxinerama-dev libglfw3-dev \
libxxf86vm-dev gcc-mingw-w64 gcc-multilib
- name: Install go binary
run: |
go install github.com/tc-hib/go-winres@latest
env:
GOBIN: /usr/local/bin
- name: Compile the fyne application for Windows
run: |
export CGO_ENABLED=1
export GOOS=windows
export GOARCH=amd64
export CC=x86_64-w64-mingw32-gcc
export CXX=x86_64-w64-mingw32-g++
export CGO_LDFLAGS="-static-libgcc -static-libstdc++"
go-winres simply --icon icon.png --manifest gui
mv *.syso ./src
go build -o ./akartontv.exe -ldflags -H=windowsgui ./src
- name: upload the building actifacts
uses: DaanSelen/upload-artifact-gitea@main
with:
name: package-win64
path: |
./akartontv.exe
retention-days: 3
overwrite: true

BIN
akartontv Executable file

Binary file not shown.

2
build.sh Normal file
View File

@@ -0,0 +1,2 @@
#!/bin/bash
go build -o akartontv src/*.go

BIN
example.pptx Normal file

Binary file not shown.

2
go.mod
View File

@@ -4,6 +4,7 @@ go 1.25.5
require (
fyne.io/fyne/v2 v2.7.2
github.com/pkg/sftp v1.13.10
golang.org/x/crypto v0.46.0
)
@@ -26,6 +27,7 @@ require (
github.com/hack-pad/safejs v0.1.1 // indirect
github.com/jeandeaual/go-locale v0.0.0-20250612000132-0ef82f21eade // indirect
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 // indirect
github.com/kr/fs v0.1.0 // indirect
github.com/kr/text v0.1.0 // indirect
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
github.com/nicksnyder/go-i18n/v2 v2.6.1 // indirect

4
go.sum
View File

@@ -42,6 +42,8 @@ github.com/jeandeaual/go-locale v0.0.0-20250612000132-0ef82f21eade h1:FmusiCI1wH
github.com/jeandeaual/go-locale v0.0.0-20250612000132-0ef82f21eade/go.mod h1:ZDXo8KHryOWSIqnsb/CiDq7hQUYryCgdVnxbj8tDG7o=
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 h1:YLvr1eE6cdCqjOe972w/cYF+FjW34v27+9Vo5106B4M=
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25/go.mod h1:kLgvv7o6UM+0QSf0QjAse3wReFDsb9qbZJdfexWlrQw=
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
@@ -53,6 +55,8 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWb
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pkg/profile v1.7.0 h1:hnbDkaNWPCLMO9wGLdBFTIZvzDrDfBM2072E1S9gJkA=
github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo=
github.com/pkg/sftp v1.13.10 h1:+5FbKNTe5Z9aspU88DPIKJ9z2KZoaGCu6Sr6kKR/5mU=
github.com/pkg/sftp v1.13.10/go.mod h1:bJ1a7uDhrX/4OII+agvy28lzRvQrmIQuaHrcI1HbeGA=
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/rymdport/portal v0.4.2 h1:7jKRSemwlTyVHHrTGgQg7gmNPJs88xkbKcIL3NlcmSU=

View File

@@ -14,13 +14,12 @@ systemctl enable --now ssh
# Create the systemd userland folder
if [[ ! -d /home/${userland_name}/.config/systemd/user ]]; then
mkdir -p /home/${userland_name}/.config/systemd/user -m 644
mkdir -p /home/${userland_name}/.config/systemd/user -m 755
chown -R ${userland_name}:${userland_name} /home/${userland_name}/.config/systemd/user
fi
# Create the program place onto /opt
if [[ ! -d /opt/${program_name} ]]; then
mkdir -p /opt/${program_name} -m 644
mkdir -p /opt/${program_name} -m 755
chown -R ${userland_name}:${userland_name} /opt/${program_name}
fi
fi

BIN
scholing.pptx Normal file

Binary file not shown.

View File

@@ -1,15 +1,21 @@
package main
import (
"io"
"log"
"net"
"os"
"time"
"github.com/pkg/sftp"
"golang.org/x/crypto/ssh"
)
func translateRaspiName(targetName string) (string, string, string, string) {
for _, raspiObj := range raspiList {
if targetName == "" {
break
}
if raspiObj[0] == targetName {
// Found!
return raspiObj[1], raspiObj[2], raspiObj[3], raspiObj[4]
@@ -19,7 +25,7 @@ func translateRaspiName(targetName string) (string, string, string, string) {
return "", "", "", ""
}
func verifyCred(targetName string) bool {
func createSSHClient(targetName string) (*ssh.Client, error) {
hostname, port, username, password := translateRaspiName(targetName)
log.Println("Connecting to:", hostname, port)
@@ -29,18 +35,85 @@ func verifyCred(targetName string) bool {
ssh.Password(password),
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(), // OK for internal tools (and me in production)
Timeout: 5 * time.Second,
Timeout: 3 * time.Second,
}
connAddr := net.JoinHostPort(hostname, port)
client, err := ssh.Dial("tcp", connAddr, config)
return ssh.Dial("tcp", connAddr, config)
}
func verifyCred(targetName string) bool {
sshClient, err := createSSHClient(targetName)
if err != nil {
log.Printf("Err occurred %v", err)
return false
}
defer client.Close()
defer sshClient.Close()
log.Println("Success!")
return true
}
func sftpUploadFile(targetName, localPath, remotePath string) bool {
sshClient, err := createSSHClient(targetName)
if err != nil {
log.Printf("Failed to init the SSH connection %v", err)
return false
}
defer sshClient.Close()
sftpClient, err := sftp.NewClient(sshClient)
if err != nil {
log.Printf("Failed to start SFTP: %v", err)
return false
}
defer sftpClient.Close()
localFile, err := os.Open(localPath)
if err != nil {
log.Printf("Failed to open local file: %v", err)
return false
}
defer localFile.Close()
remoteFile, err := sftpClient.Create(remotePath)
if err != nil {
log.Printf("Failed to open remote file handle: %v", err)
return false
}
bytesCopied, err := io.Copy(remoteFile, localFile)
if err != nil {
log.Printf("Failed to copy local file to the remote location: %v", err)
return false
}
log.Printf("Succesfully uploaded %d bytes to %s", bytesCopied, remotePath)
return true
}
func restartShow(targetName string) bool {
sshClient, err := createSSHClient(targetName)
if err != nil {
log.Printf("Failed to init the SSH connection: %v", err)
return false
}
defer sshClient.Close()
session, err := sshClient.NewSession()
if err != nil {
log.Printf("Failed to create session over the connection: %v", err)
}
defer session.Close()
output, err := session.CombinedOutput("systemctl --user restart pres")
if err != nil {
log.Printf("Failed to restart the show over SSH: %v", err)
}
log.Println(output)
return true
}

View File

@@ -10,18 +10,34 @@ import (
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/dialog"
"fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/storage"
"fyne.io/fyne/v2/widget"
)
func flashRed(btnPtr *widget.Button) {
var (
presFileFilter []string = []string{".pptx", ".odp"}
videoFileFilter []string = []string{".mp4", ".mkv", ".mov", ".webm"}
)
func flashColor(btnPtr *widget.Button, color string) {
var sleepAmount int = 1
// "red" or "green"
// Set red on UI thread
fyne.CurrentApp().Driver().DoFromGoroutine(func() {
btnPtr.Importance = widget.DangerImportance
switch color {
case "red":
btnPtr.Importance = widget.DangerImportance
case "green":
btnPtr.Importance = widget.SuccessImportance
sleepAmount = 3
}
refreshButtons(btnPtr)
}, false)
log.Println("Waiting")
time.Sleep(1 * time.Second)
log.Println("Waiting to change button")
time.Sleep(time.Duration(sleepAmount) * time.Second)
log.Println("Done waiting")
// Reset on UI thread
fyne.CurrentApp().Driver().DoFromGoroutine(func() {
@@ -30,10 +46,6 @@ func flashRed(btnPtr *widget.Button) {
}, false)
}
func drawVerifyProcess() {
}
func drawSeparator(trailNewLine bool) *fyne.Container {
emptyLabel := widget.NewLabel("") // Give it some space at the bottom with an empty line
separatorLine := canvas.NewLine(color.Gray{Y: 128})
@@ -55,7 +67,7 @@ func drawSeparator(trailNewLine bool) *fyne.Container {
return separContainer
}
func drawButtonRow(targetMode *int) *fyne.Container {
func drawModeRow(targetMode *int) *fyne.Container {
actionText := widget.NewLabel("Select Mode")
// targetMode is defined as:
// 0 undefined / not used
@@ -101,7 +113,7 @@ func drawButtonRow(targetMode *int) *fyne.Container {
return modeCol
}
func drawTargetSection(raspiNames []string, raspiTarget *string) *fyne.Container {
func drawTargetSection(raspiNames []string, raspiTarget *string, uploadBtn, reloadBtn *widget.Button) *fyne.Container {
actionText := widget.NewLabel("Select Target")
var verifyBtn *widget.Button
@@ -112,9 +124,13 @@ func drawTargetSection(raspiNames []string, raspiTarget *string) *fyne.Container
log.Println("Selected target:", selected)
} else {
log.Println("Deselected Target")
*raspiTarget = ""
}
// Reload all buttons!
verifyBtn.Importance = widget.LowImportance
refreshButtons(verifyBtn)
uploadBtn.Importance = widget.LowImportance
reloadBtn.Importance = widget.LowImportance
refreshButtons(verifyBtn, uploadBtn, reloadBtn)
})
selecCol := container.NewVBox(
@@ -128,17 +144,23 @@ func drawTargetSection(raspiNames []string, raspiTarget *string) *fyne.Container
refreshButtons(verifyBtn)
log.Println("Verifying credentials...")
uploadBtn.Disable()
reloadBtn.Disable()
refreshButtons(verifyBtn, uploadBtn, reloadBtn)
go func() {
credOK = verifyCred(*raspiTarget)
if credOK {
// Must update UI on main thread
fyne.CurrentApp().Driver().DoFromGoroutine(func() {
verifyBtn.Importance = widget.SuccessImportance
refreshButtons(verifyBtn)
}, true)
go flashColor(verifyBtn, "green") // flashcolor should handle RunOnMain internally
uploadBtn.Enable()
reloadBtn.Enable()
}, false)
} else {
flashRed(verifyBtn) // flashRed should handle RunOnMain internally
go flashColor(verifyBtn, "red") // flashcolor should handle RunOnMain internally
}
}()
})
@@ -156,7 +178,7 @@ func drawTargetSection(raspiNames []string, raspiTarget *string) *fyne.Container
return wholeCol
}
func drawFileSelection(localPath *string, parentWindow *fyne.Window) *fyne.Container {
func drawFileSelection(localPath *string, targetMode *int, parentWindow *fyne.Window) *fyne.Container {
actionText := widget.NewLabel("Select File")
pathLabel := widget.NewLabel("No File Selected Yet...")
@@ -165,7 +187,7 @@ func drawFileSelection(localPath *string, parentWindow *fyne.Window) *fyne.Conta
log.Println("Ignore the next error. ~Refer: https://github.com/fyne-io/fyne/issues/4110")
// Use NewFileOpen to get the dialog object
uploadDiag := dialog.NewFileOpen(func(r fyne.URIReadCloser, err error) {
uploadSelecDiag := dialog.NewFileOpen(func(r fyne.URIReadCloser, err error) {
if r != nil {
defer r.Close()
@@ -176,12 +198,19 @@ func drawFileSelection(localPath *string, parentWindow *fyne.Window) *fyne.Conta
}
}, *parentWindow)
//fileFilter := storage.NewExtensionFileFilter() // <- needs a list[str] of extensions if we want to filter
//uploadDiag.SetFilter(fileFilter)
var fileFilter storage.FileFilter
switch *targetMode {
case 1:
fileFilter = storage.NewExtensionFileFilter(presFileFilter)
case 2:
fileFilter = storage.NewExtensionFileFilter(videoFileFilter)
}
uploadSelecDiag.SetFilter(fileFilter)
// Resize the dialog to the whole window, if it wants to do that -
uploadDiag.Resize(windowSize)
uploadDiag.Show()
uploadSelecDiag.Resize(windowSize)
uploadSelecDiag.Show()
})
uploadWide := container.NewGridWrap(
buttonSize,
@@ -201,14 +230,42 @@ func drawFileSelection(localPath *string, parentWindow *fyne.Window) *fyne.Conta
return fileSelecCol
}
func drawFooter(app fyne.App, targetMode *int) *fyne.Container {
// targetMode *int
func drawFooter(app fyne.App, raspiTarget, localUploadPath *string) (*fyne.Container, *widget.Button, *widget.Button) {
remotePresPath := "/opt/akartontv/presentation.pptx"
//remoteVideoPath := "/opt/akartontv/video.mp4"
// Configuration of the bottom of the application
showBtn := widget.NewButton("Print Current Value", func() {
log.Println(*targetMode)
var uploadBtn *widget.Button
uploadBtn = widget.NewButton("Upload File", func() {
uploadBtn.Importance = widget.HighImportance
refreshButtons(uploadBtn)
if sftpUploadFile(*raspiTarget, *localUploadPath, remotePresPath) {
go flashColor(uploadBtn, "green")
} else {
go flashColor(uploadBtn, "red")
}
})
showWide := container.NewGridWrap(
uploadWide := container.NewGridWrap(
buttonSize,
showBtn,
uploadBtn,
)
var reloadBtn *widget.Button
reloadBtn = widget.NewButton("Restart Program", func() {
reloadBtn.Importance = widget.HighImportance
refreshButtons(uploadBtn)
if restartShow(*raspiTarget) {
go flashColor(reloadBtn, "green") // flashcolor should handle RunOnMain internally
} else {
go flashColor(reloadBtn, "red")
}
})
reloadWide := container.NewGridWrap(
buttonSize,
reloadBtn,
)
cancelBtn := widget.NewButton("Exit", func() {
@@ -221,10 +278,13 @@ func drawFooter(app fyne.App, targetMode *int) *fyne.Container {
)
bottom := container.NewHBox(
showWide, // left
uploadWide, // left
reloadWide, // also next to the one above
layout.NewSpacer(), // flexible space
cancelWide, // right
)
return bottom
uploadBtn.Disable()
reloadBtn.Disable()
return bottom, uploadBtn, reloadBtn
}

View File

@@ -22,9 +22,8 @@ const (
)
var (
windowSize fyne.Size = fyne.NewSize(750, 800) // Default Window size
//dialogSize fyne.Size = fyne.NewSize(750, 700) // For some reason when we use windowSize for that, its still smaller, this is also smaller. But a better fit
buttonSize fyne.Size = fyne.NewSize(200, 50) // Default button size
windowSize fyne.Size = fyne.NewSize(750, 700) // Default Window size
buttonSize fyne.Size = fyne.NewSize(200, 50) // Default button size
entrySize fyne.Size = fyne.NewSize(200, 40)
)
@@ -38,24 +37,32 @@ func main() {
raspiNames := getRaspiNames()
// Define variables and print them out for debug
// Presentation = 1
// Video = 2
var targetMode int = 0
var raspiTarget string
var raspiTarget string = ""
var localUploadPath string
log.Println("Current mode:", targetMode)
log.Println("Current target:", raspiTarget)
if raspiTarget == "" {
log.Println("Current target: None")
} else {
log.Println("Current target:", raspiTarget)
}
// Call the draw functions -> ./src/draw.go
modeBtnRow := drawButtonRow(&targetMode)
drawSelecRow := drawTargetSection(raspiNames, &raspiTarget)
fileSelectRow := drawFileSelection(&localUploadPath, &w)
footerRow := drawFooter(app, &targetMode)
footerRow, uploadBtn, reloadBtn := drawFooter(app, &raspiTarget, &localUploadPath)
modeBtnRow := drawModeRow(&targetMode)
selectionRow := drawTargetSection(raspiNames, &raspiTarget, uploadBtn, reloadBtn)
fileSelectRow := drawFileSelection(&localUploadPath, &targetMode, &w)
center := container.NewVBox(
modeBtnRow,
drawSeparator(true),
drawSelecRow,
drawSeparator(false),
widget.NewLabel(""),
fileSelectRow,
drawSeparator(true),
selectionRow,
drawSeparator(false),
)
content := container.NewBorder(
@@ -64,7 +71,6 @@ func main() {
nil, nil, // left, right
center, //center
)
log.Println(raspiTarget)
w.SetContent(content)
w.ShowAndRun()
@@ -72,7 +78,9 @@ func main() {
func refreshButtons(givenButtons ...*widget.Button) {
for _, btn := range givenButtons {
btn.Refresh()
fyne.CurrentApp().Driver().DoFromGoroutine(func() {
btn.Refresh()
}, false)
}
}

View File

@@ -10,6 +10,6 @@ var (
raspiList [][]string = [][]string{
{"Kantine Pi", "172.16.64.228", "22", "systemec", ""},
{"Productie Pi", "172.16.64.229", "22", "", ""},
{"Sales Pi", "", "", "", ""},
{"Sales Pi", "192.168.110.175", "22", "systemec", "Doerak2003!"},
}
)