171 lines
4.5 KiB
Go
171 lines
4.5 KiB
Go
//go:build linux
|
|
|
|
package main
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"strings"
|
|
|
|
"github.com/VeraeLLC/PrintToVeraeTimeStamp/internal/vlog"
|
|
)
|
|
|
|
// Linux desktop sign-in dialogs (zenity, GNOME/Cinnamon/XFCE/KDE all ship or
|
|
// package it). Launched by the CUPS backend in the job user's session when a
|
|
// print needs a key, or by hand: verae-print key-dialog.
|
|
|
|
const (
|
|
dlgTitle = "Verae Print Stamp"
|
|
machineWord = "computer"
|
|
keyStoreDesc = "~/.verae, readable only by you"
|
|
)
|
|
|
|
func zenityPath() string {
|
|
p, err := exec.LookPath("zenity")
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
return p
|
|
}
|
|
|
|
func guiAvailable() bool {
|
|
if os.Getenv("DISPLAY") == "" && os.Getenv("WAYLAND_DISPLAY") == "" {
|
|
return false
|
|
}
|
|
return zenityPath() != ""
|
|
}
|
|
|
|
func openURL(u string) {
|
|
cmd := exec.Command("xdg-open", u)
|
|
cmd.Stdout, cmd.Stderr = nil, nil
|
|
_ = cmd.Start()
|
|
go func() { _ = cmd.Wait() }()
|
|
}
|
|
|
|
// zenity renders --text as Pango markup.
|
|
func markupEscape(s string) string {
|
|
return strings.NewReplacer("&", "&", "<", "<", ">", ">").Replace(s)
|
|
}
|
|
|
|
// zen runs zenity; returns stdout (trimmed) and exit code. Exit 1 = cancel or
|
|
// an extra button (whose label is printed), 5 = timeout, -1 = could not run.
|
|
func zen(args ...string) (string, int) {
|
|
base := []string{"--title=" + dlgTitle, "--width=520", "--timeout=900"}
|
|
cmd := exec.Command(zenityPath(), append(base, args...)...)
|
|
cmd.Stderr = nil
|
|
out, err := cmd.Output()
|
|
s := strings.TrimRight(string(out), "\n")
|
|
if err != nil {
|
|
var ee *exec.ExitError
|
|
if errors.As(err, &ee) {
|
|
return s, ee.ExitCode()
|
|
}
|
|
return "", -1
|
|
}
|
|
return s, 0
|
|
}
|
|
|
|
// ask shows a dialog; returns (button, answer). The default button is OK, the
|
|
// first other button is Cancel, the rest are extra buttons.
|
|
func ask(msg, def string, hidden, withAnswer bool, buttons []string, defBtn string) (string, string, error) {
|
|
if !guiAvailable() {
|
|
return "", "", fmt.Errorf("no desktop session for dialogs")
|
|
}
|
|
var others []string
|
|
for _, b := range buttons {
|
|
if b != defBtn {
|
|
others = append(others, b)
|
|
}
|
|
}
|
|
var args []string
|
|
switch {
|
|
case withAnswer:
|
|
args = []string{"--entry", "--text=" + markupEscape(msg), "--entry-text=" + def}
|
|
if hidden {
|
|
args = append(args, "--hide-text")
|
|
}
|
|
case len(others) == 0:
|
|
args = []string{"--info", "--text=" + markupEscape(msg)}
|
|
default:
|
|
args = []string{"--question", "--text=" + markupEscape(msg)}
|
|
}
|
|
args = append(args, "--ok-label="+defBtn)
|
|
if len(others) > 0 {
|
|
args = append(args, "--cancel-label="+others[0])
|
|
for _, b := range others[1:] {
|
|
args = append(args, "--extra-button="+b)
|
|
}
|
|
}
|
|
out, code := zen(args...)
|
|
switch code {
|
|
case 0:
|
|
return defBtn, out, nil
|
|
case 1:
|
|
for _, b := range others[min(1, len(others)):] {
|
|
if out == b {
|
|
return b, "", nil
|
|
}
|
|
}
|
|
if len(others) > 0 {
|
|
return others[0], "", nil
|
|
}
|
|
return defBtn, "", nil // info dialog closed
|
|
case -1:
|
|
return "", "", fmt.Errorf("zenity could not start")
|
|
default:
|
|
return "", "", errCancel // timeout / window killed
|
|
}
|
|
}
|
|
|
|
func say(msg string) { _, _, _ = ask(msg, "", false, false, []string{"OK"}, "OK") }
|
|
|
|
func choose(msg string, opts []string) (string, error) {
|
|
if !guiAvailable() {
|
|
return "", fmt.Errorf("no desktop session for dialogs")
|
|
}
|
|
args := []string{"--list", "--radiolist", "--hide-header", "--height=470",
|
|
"--text=" + markupEscape(msg), "--column=", "--column=Option",
|
|
"--ok-label=Continue", "--cancel-label=Skip"}
|
|
for i, o := range opts {
|
|
sel := "FALSE"
|
|
if i == 0 {
|
|
sel = "TRUE"
|
|
}
|
|
args = append(args, sel, o)
|
|
}
|
|
out, code := zen(args...)
|
|
if code != 0 || out == "" {
|
|
return "", errCancel
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// runKeyDialog: verae-print key-dialog — the desktop "get a signing key" flow.
|
|
func runKeyDialog() int {
|
|
vlog.Init("installer", false)
|
|
if !guiAvailable() {
|
|
fmt.Println("key-dialog needs a desktop session with zenity installed (sudo apt install zenity); or run: verae-print setup")
|
|
return 1
|
|
}
|
|
vlog.Printf("key-dialog start user=%s display=%s", os.Getenv("USER"), os.Getenv("DISPLAY"))
|
|
release, ok := promptLock()
|
|
if !ok {
|
|
vlog.Printf("key-dialog: another Verae sign-in window is already open")
|
|
return 1
|
|
}
|
|
reason := os.Getenv("VERAE_KEY_REASON")
|
|
result, saved := keyFlow(reason)
|
|
release()
|
|
vlog.Printf("key-dialog result: %s", result)
|
|
fmt.Println("key:", result)
|
|
if saved || strings.HasPrefix(result, "kept") {
|
|
if n := len(listPending()); n > 0 {
|
|
done, remaining, _ := retryPending(stampFile)
|
|
vlog.Printf("key-dialog: retried held print jobs: %d stamped, %d remaining", done, remaining)
|
|
}
|
|
return 0
|
|
}
|
|
return 1
|
|
}
|