Compare commits

...

4 Commits

4 changed files with 93 additions and 170 deletions

View File

@ -28,9 +28,13 @@ Vlastnosti:
--- ---
## Rychlý start (gui) ## Rychlý start (GUI)
```bash ```bash
# 1) po buildnutí (go build .) stačí spustit binárku otevře se GUI
./fckeuspy-go
# Alternativně přes go run
go run . gui go run . gui
``` ```
@ -133,11 +137,15 @@ Formát payloadu:
--- ---
## Fyne GUI režim ## Spuštění (GUI / Web)
Spuštění desktopu: Spuštění desktopu (GUI):
```bash ```bash
# po buildnutí (go build .)
./fckeuspy-go
# nebo přes go run
go run . gui go run . gui
``` ```
@ -153,6 +161,8 @@ HTTP režim zůstává:
```bash ```bash
go run . web go run . web
# nebo po buildnutí (go build .)
./fckeuspy-go web
``` ```
--- ---

4
cmd.go
View File

@ -10,6 +10,10 @@ var rootCmd = &cobra.Command{
Use: "fckeuspy", Use: "fckeuspy",
Short: "Hybrid RSA+AES šifrovací nástroj (GUI / HTTP)", Short: "Hybrid RSA+AES šifrovací nástroj (GUI / HTTP)",
Long: "Fckeuspy: nástroj pro šifrování a dešifrování pomocí hybridního RSA-OAEP + AES-GCM, s Fyne GUI a HTTP serverem.", Long: "Fckeuspy: nástroj pro šifrování a dešifrování pomocí hybridního RSA-OAEP + AES-GCM, s Fyne GUI a HTTP serverem.",
// Default: run GUI when executed without subcommand
Run: func(cmd *cobra.Command, args []string) {
runFyne()
},
} }
var guiCmd = &cobra.Command{ var guiCmd = &cobra.Command{

View File

@ -7,7 +7,6 @@ import (
"image/color" "image/color"
"image/draw" "image/draw"
"image/png" "image/png"
"log"
"math" "math"
"os/exec" "os/exec"
@ -32,17 +31,12 @@ func GenerateQRPNG(text string, size int) ([]byte, error) {
// DecodeQR decodes first QR code text from an image. // DecodeQR decodes first QR code text from an image.
func DecodeQR(img image.Image) (string, error) { func DecodeQR(img image.Image) (string, error) {
log.Printf("[qr] start decode: %dx%d %T", img.Bounds().Dx(), img.Bounds().Dy(), img)
// Try basic decode first // Try basic decode first
codes, err := goqr.Recognize(img) codes, err := goqr.Recognize(img)
if err == nil && len(codes) > 0 { if err == nil && len(codes) > 0 {
log.Printf("[qr] basic success length=%d", len(codes))
return string(codes[0].Payload), nil return string(codes[0].Payload), nil
} }
if err != nil { if err != nil {
log.Printf("[qr] basic error: %v", err)
} else {
log.Printf("[qr] basic no codes")
} }
// Convert palette/indexed images to RGBA first to avoid issues with goqr // Convert palette/indexed images to RGBA first to avoid issues with goqr
@ -55,14 +49,8 @@ func DecodeQR(img image.Image) (string, error) {
// Try again after conversion // Try again after conversion
codes, err := goqr.Recognize(img) codes, err := goqr.Recognize(img)
if err == nil && len(codes) > 0 { if err == nil && len(codes) > 0 {
log.Printf("[qr] palette-convert success")
return string(codes[0].Payload), nil return string(codes[0].Payload), nil
} }
if err != nil {
log.Printf("[qr] palette-convert err: %v", err)
} else {
log.Printf("[qr] palette-convert no codes")
}
} }
// Try multiple preprocessing strategies to improve recognition from clipboard screenshots // Try multiple preprocessing strategies to improve recognition from clipboard screenshots
@ -213,20 +201,17 @@ func DecodeQR(img image.Image) (string, error) {
} }
var firstErr error var firstErr error
for idx, attempt := range attempts { for _, attempt := range attempts {
codes, err := goqr.Recognize(attempt) codes, err := goqr.Recognize(attempt)
if err != nil { if err != nil {
if firstErr == nil { if firstErr == nil {
firstErr = err firstErr = err
} }
log.Printf("[qr] attempt %d error: %v", idx, err)
continue continue
} }
if len(codes) > 0 { if len(codes) > 0 {
log.Printf("[qr] attempt %d success (%d codes)", idx, len(codes))
return string(codes[0].Payload), nil return string(codes[0].Payload), nil
} }
log.Printf("[qr] attempt %d no codes", idx)
} }
// ZXing helper used across multiple branches // ZXing helper used across multiple branches
@ -247,25 +232,19 @@ func DecodeQR(img image.Image) (string, error) {
} }
} }
// Also try ZXing on all preprocessed attempts // Also try ZXing on all preprocessed attempts
for idx, attempt := range attempts { for _, attempt := range attempts {
if txt, err := decodeZX(attempt, map[gozxing.DecodeHintType]interface{}{ if txt, err := decodeZX(attempt, map[gozxing.DecodeHintType]interface{}{
gozxing.DecodeHintType_TRY_HARDER: true, gozxing.DecodeHintType_TRY_HARDER: true,
gozxing.DecodeHintType_PURE_BARCODE: true, gozxing.DecodeHintType_PURE_BARCODE: true,
}); err == nil && txt != "" { }); err == nil && txt != "" {
log.Printf("[qr] zxing attempt %d success (pure)", idx)
return txt, nil return txt, nil
} }
if txt, err := decodeZX(attempt, map[gozxing.DecodeHintType]interface{}{ if txt, err := decodeZX(attempt, map[gozxing.DecodeHintType]interface{}{
gozxing.DecodeHintType_TRY_HARDER: true, gozxing.DecodeHintType_TRY_HARDER: true,
}); err == nil && txt != "" { }); err == nil && txt != "" {
log.Printf("[qr] zxing attempt %d success (try-harder)", idx)
return txt, nil return txt, nil
} }
} }
if firstErr != nil {
log.Printf("[qr] preprocess firstErr: %v", firstErr)
}
// --- Heuristic fallback: auto-invert + quiet-zone pad + gozxing --- // --- Heuristic fallback: auto-invert + quiet-zone pad + gozxing ---
addQuietZone := func(src image.Image) image.Image { addQuietZone := func(src image.Image) image.Image {
b := src.Bounds() b := src.Bounds()
@ -315,7 +294,6 @@ func DecodeQR(img image.Image) (string, error) {
modified = append(modified, addQuietZone(invert(base))) modified = append(modified, addQuietZone(invert(base)))
for _, m := range modified { for _, m := range modified {
if codes, err := goqr.Recognize(m); err == nil && len(codes) > 0 { if codes, err := goqr.Recognize(m); err == nil && len(codes) > 0 {
log.Printf("[qr] quiet-zone/invert success")
return string(codes[0].Payload), nil return string(codes[0].Payload), nil
} }
// Also try ZXing on these variants before moving on // Also try ZXing on these variants before moving on
@ -323,13 +301,11 @@ func DecodeQR(img image.Image) (string, error) {
gozxing.DecodeHintType_TRY_HARDER: true, gozxing.DecodeHintType_TRY_HARDER: true,
gozxing.DecodeHintType_PURE_BARCODE: true, gozxing.DecodeHintType_PURE_BARCODE: true,
}); err == nil && txt != "" { }); err == nil && txt != "" {
log.Printf("[qr] gozxing success (quiet-zone+pure)")
return txt, nil return txt, nil
} }
if txt, err := decodeZX(m, map[gozxing.DecodeHintType]interface{}{ if txt, err := decodeZX(m, map[gozxing.DecodeHintType]interface{}{
gozxing.DecodeHintType_TRY_HARDER: true, gozxing.DecodeHintType_TRY_HARDER: true,
}); err == nil && txt != "" { }); err == nil && txt != "" {
log.Printf("[qr] gozxing success (quiet-zone+try-harder)")
return txt, nil return txt, nil
} }
} }
@ -340,27 +316,19 @@ func DecodeQR(img image.Image) (string, error) {
gozxing.DecodeHintType_PURE_BARCODE: true, gozxing.DecodeHintType_PURE_BARCODE: true,
} }
if txt, err := decodeZX(base, hints); err == nil && txt != "" { if txt, err := decodeZX(base, hints); err == nil && txt != "" {
log.Printf("[qr] gozxing success (hints)")
return txt, nil return txt, nil
} else if err != nil {
log.Printf("[qr] gozxing(hints) err: %v", err)
} }
// Try again without PURE_BARCODE in case quiet zone is cropped // Try again without PURE_BARCODE in case quiet zone is cropped
hints2 := map[gozxing.DecodeHintType]interface{}{gozxing.DecodeHintType_TRY_HARDER: true} hints2 := map[gozxing.DecodeHintType]interface{}{gozxing.DecodeHintType_TRY_HARDER: true}
if txt, err := decodeZX(base, hints2); err == nil && txt != "" { if txt, err := decodeZX(base, hints2); err == nil && txt != "" {
log.Printf("[qr] gozxing success (try-harder)")
return txt, nil return txt, nil
} else if err != nil {
log.Printf("[qr] gozxing(try-harder) err: %v", err)
} }
// Try ZXing on rotations as well // Try ZXing on rotations as well
for _, r := range rotate(base) { for _, r := range rotate(base) {
if txt, err := decodeZX(r, hints2); err == nil && txt != "" { if txt, err := decodeZX(r, hints2); err == nil && txt != "" {
log.Printf("[qr] gozxing success (rotated)")
return txt, nil return txt, nil
} }
if txt, err := decodeZX(r, hints); err == nil && txt != "" { if txt, err := decodeZX(r, hints); err == nil && txt != "" {
log.Printf("[qr] gozxing success (rotated pure)")
return txt, nil return txt, nil
} }
} }
@ -392,7 +360,6 @@ func DecodeQR(img image.Image) (string, error) {
tryPure := func(src image.Image) (string, bool) { tryPure := func(src image.Image) (string, bool) {
for _, px := range []int{1, 2, 3} { for _, px := range []int{1, 2, 3} {
if txt, err := decodeZX(inset(src, px), hints); err == nil && txt != "" { if txt, err := decodeZX(inset(src, px), hints); err == nil && txt != "" {
log.Printf("[qr] gozxing pure+inset %d success", px)
return txt, true return txt, true
} }
} }
@ -425,11 +392,9 @@ func DecodeQR(img image.Image) (string, error) {
continue continue
} }
if txt, err := decodeZX(cand, hints); err == nil && txt != "" { if txt, err := decodeZX(cand, hints); err == nil && txt != "" {
log.Printf("[qr] gozxing pure+asym inset %d,%d,%d,%d success", l, t, r, btm)
return txt, nil return txt, nil
} }
if txt, err := decodeZX(cand, hints2); err == nil && txt != "" { if txt, err := decodeZX(cand, hints2); err == nil && txt != "" {
log.Printf("[qr] gozxing asym inset %d,%d,%d,%d success", l, t, r, btm)
return txt, nil return txt, nil
} }
} }
@ -465,7 +430,6 @@ func DecodeQR(img image.Image) (string, error) {
if minX < maxX && minY < maxY { if minX < maxX && minY < maxY {
crop := image.NewRGBA(image.Rect(0, 0, maxX-minX+1, maxY-minY+1)) crop := image.NewRGBA(image.Rect(0, 0, maxX-minX+1, maxY-minY+1))
draw.Draw(crop, crop.Bounds(), base, image.Point{X: minX, Y: minY}, draw.Src) draw.Draw(crop, crop.Bounds(), base, image.Point{X: minX, Y: minY}, draw.Src)
log.Printf("[qr] crop bbox size=%dx%d", crop.Bounds().Dx(), crop.Bounds().Dy())
for _, up := range []int{2, 3, 4} { for _, up := range []int{2, 3, 4} {
cw := crop.Bounds().Dx() * up cw := crop.Bounds().Dx() * up
ch := crop.Bounds().Dy() * up ch := crop.Bounds().Dy() * up
@ -481,10 +445,7 @@ func DecodeQR(img image.Image) (string, error) {
} }
} }
if txt, err := decodeZX(scaled, hints2); err == nil && txt != "" { if txt, err := decodeZX(scaled, hints2); err == nil && txt != "" {
log.Printf("[qr] gozxing crop+scale success up=%d", up)
return txt, nil return txt, nil
} else if err != nil {
log.Printf("[qr] crop up=%d fail: %v", up, err)
} }
} }
} }
@ -499,12 +460,8 @@ func DecodeQR(img image.Image) (string, error) {
c.Stdin = bytes.NewReader(buf.Bytes()) c.Stdin = bytes.NewReader(buf.Bytes())
out, err := c.Output() out, err := c.Output()
if err == nil && len(out) > 0 { if err == nil && len(out) > 0 {
log.Printf("[qr] zbarimg success")
return string(out), nil return string(out), nil
} }
if err != nil {
log.Printf("[qr] zbarimg error: %v", err)
}
} }
} }
return "", errors.New("no qr code found") return "", errors.New("no qr code found")

190
ui.go
View File

@ -10,7 +10,6 @@ import (
"image/color" "image/color"
"image/png" "image/png"
"io" "io"
"log"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
@ -55,6 +54,7 @@ func buildEntries() *uiParts {
p.pubQR.SetMinSize(fyne.NewSize(200, 200)) p.pubQR.SetMinSize(fyne.NewSize(200, 200))
p.peerQR.SetMinSize(fyne.NewSize(200, 200)) p.peerQR.SetMinSize(fyne.NewSize(200, 200))
p.payloadQR.SetMinSize(fyne.NewSize(220, 220)) p.payloadQR.SetMinSize(fyne.NewSize(220, 220))
p.pubQR.FillMode = canvas.ImageFillContain
p.toastLabel.Hide() p.toastLabel.Hide()
return p return p
} }
@ -74,11 +74,16 @@ func (p *uiParts) showToast(s string) {
type simpleTheme struct{} type simpleTheme struct{}
func (simpleTheme) Color(n fyne.ThemeColorName, v fyne.ThemeVariant) color.Color { func (simpleTheme) Color(n fyne.ThemeColorName, v fyne.ThemeVariant) color.Color {
if n == theme.ColorNameBackground { switch n {
case theme.ColorNameBackground:
return color.NRGBA{24, 27, 31, 255} return color.NRGBA{24, 27, 31, 255}
} case theme.ColorNameDisabled:
// Make disabled text brighter for readability on dark background
return color.NRGBA{230, 233, 238, 255}
default:
return theme.DefaultTheme().Color(n, v) return theme.DefaultTheme().Color(n, v)
} }
}
func (simpleTheme) Font(st fyne.TextStyle) fyne.Resource { return theme.DefaultTheme().Font(st) } func (simpleTheme) Font(st fyne.TextStyle) fyne.Resource { return theme.DefaultTheme().Font(st) }
func (simpleTheme) Icon(n fyne.ThemeIconName) fyne.Resource { return theme.DefaultTheme().Icon(n) } func (simpleTheme) Icon(n fyne.ThemeIconName) fyne.Resource { return theme.DefaultTheme().Icon(n) }
func (simpleTheme) Size(n fyne.ThemeSizeName) float32 { return theme.DefaultTheme().Size(n) } func (simpleTheme) Size(n fyne.ThemeSizeName) float32 { return theme.DefaultTheme().Size(n) }
@ -172,7 +177,6 @@ func readImageClipboard() (image.Image, error) {
if os.Getenv("WAYLAND_DISPLAY") != "" && has("wl-paste") { if os.Getenv("WAYLAND_DISPLAY") != "" && has("wl-paste") {
if types, err := exec.Command("wl-paste", "--list-types").Output(); err == nil { if types, err := exec.Command("wl-paste", "--list-types").Output(); err == nil {
log.Printf("[clip] wl types: %s", strings.TrimSpace(string(types)))
pref := []string{"image/png", "image/jpeg", "image/jpg", "image/webp"} pref := []string{"image/png", "image/jpeg", "image/jpg", "image/webp"}
seen := map[string]bool{} seen := map[string]bool{}
for _, p := range pref { for _, p := range pref {
@ -190,23 +194,13 @@ func readImageClipboard() (image.Image, error) {
} }
for _, t := range order { for _, t := range order {
if data, err := exec.Command("wl-paste", "--type", t).Output(); err == nil { if data, err := exec.Command("wl-paste", "--type", t).Output(); err == nil {
log.Printf("[clip] got type %s size=%d", t, len(data))
if img, ok := tryDecode(data); ok { if img, ok := tryDecode(data); ok {
// save debug
_ = os.MkdirAll("qr_debug", 0o755)
fn := filepath.Join("qr_debug", fmt.Sprintf("clip_raw_%d.png", time.Now().UnixNano()))
if f, e := os.Create(fn); e == nil {
_ = png.Encode(f, img)
f.Close()
log.Printf("[clip] saved %s", fn)
}
return img, nil return img, nil
} }
} }
} }
} }
if data, err := exec.Command("wl-paste").Output(); err == nil { if data, err := exec.Command("wl-paste").Output(); err == nil {
log.Printf("[clip] generic wl-paste size=%d", len(data))
if img, ok := tryDecode(data); ok { if img, ok := tryDecode(data); ok {
return img, nil return img, nil
} }
@ -215,14 +209,12 @@ func readImageClipboard() (image.Image, error) {
if has("xclip") { if has("xclip") {
for _, m := range []string{"image/png", "image/jpeg", "image/jpg", "image/bmp"} { for _, m := range []string{"image/png", "image/jpeg", "image/jpg", "image/bmp"} {
if data, err := exec.Command("xclip", "-selection", "clipboard", "-t", m, "-o").Output(); err == nil { if data, err := exec.Command("xclip", "-selection", "clipboard", "-t", m, "-o").Output(); err == nil {
log.Printf("[clip] xclip type %s size=%d", m, len(data))
if img, ok := tryDecode(data); ok { if img, ok := tryDecode(data); ok {
return img, nil return img, nil
} }
} }
} }
if data, err := exec.Command("xclip", "-selection", "clipboard", "-o").Output(); err == nil { if data, err := exec.Command("xclip", "-selection", "clipboard", "-o").Output(); err == nil {
log.Printf("[clip] xclip raw size=%d", len(data))
if img, ok := tryDecode(data); ok { if img, ok := tryDecode(data); ok {
return img, nil return img, nil
} }
@ -234,18 +226,19 @@ func readImageClipboard() (image.Image, error) {
// Identity tab // Identity tab
func buildIdentityTab(parts *uiParts, svc ServiceFacade, vaultPath string) fyne.CanvasObject { func buildIdentityTab(parts *uiParts, svc ServiceFacade, vaultPath string) fyne.CanvasObject {
// Toolbar: choose what to encode into the QR to keep density manageable // Toolbar: choose what to encode into the QR to keep density manageable
mode := "combined" // combined, cert, pub mode := "cert" // cert, pub
chooser := widget.NewSelect([]string{"Veřejný+Cert", "Jen Cert", "Jen Veřejný"}, func(string) {}) options := []string{"Certifikát", "Veřejný klíč"}
chooser.Selected = "Veřejný+Cert" chooser := widget.NewSelect(options, func(string) {})
chooser.Selected = options[0]
var update func() var update func()
chooser.OnChanged = func(v string) { chooser.OnChanged = func(v string) {
switch v { switch v {
case "Jen Cert": case "Certifikát":
mode = "cert" mode = "cert"
case "Jen Veřejný": case "Veřejný klíč":
mode = "pub" mode = "pub"
default: default:
mode = "combined" mode = "cert"
} }
update() update()
} }
@ -267,6 +260,8 @@ func buildIdentityTab(parts *uiParts, svc ServiceFacade, vaultPath string) fyne.
d.Resize(fyne.NewSize(420, 200)) d.Resize(fyne.NewSize(420, 200))
d.Show() d.Show()
}) })
// Keep button minimal; align right
deleteRow := container.NewHBox(layout.NewSpacer(), deleteBtn)
makeQR := func(data string, target *canvas.Image) { makeQR := func(data string, target *canvas.Image) {
if data == "" { if data == "" {
target.Image = nil target.Image = nil
@ -284,38 +279,51 @@ func buildIdentityTab(parts *uiParts, svc ServiceFacade, vaultPath string) fyne.
if parts.showQR { if parts.showQR {
var text string var text string
switch mode { switch mode {
case "cert":
text = strings.TrimSpace(svc.PublicCert())
case "pub": case "pub":
text = strings.TrimSpace(svc.PublicPEM()) text = strings.TrimSpace(svc.PublicPEM())
default: default: // cert
text = strings.TrimSpace(svc.PublicPEM() + "\n" + svc.PublicCert()) text = strings.TrimSpace(svc.PublicCert())
} }
makeQR(text, parts.pubQR) makeQR(text, parts.pubQR)
// debug save for comparison
if parts.pubQR.Image != nil {
_ = os.MkdirAll("qr_debug", 0o755)
if f, e := os.Create(filepath.Join("qr_debug", "identity_current.png")); e == nil {
png.Encode(f, parts.pubQR.Image)
f.Close()
}
}
} else { } else {
parts.pubQR.Image = nil parts.pubQR.Image = nil
parts.pubQR.Refresh() parts.pubQR.Refresh()
} }
} }
update() update()
box := container.NewVBox(container.NewHBox(widget.NewLabel("QR obsah:"), chooser, layout.NewSpacer(), widget.NewButtonWithIcon("Kopírovat jako obrázek", theme.ContentPasteIcon(), func() { copyImageToClipboard(parts.pubQR.Image, parts) })), parts.pubQR) saveQR := widget.NewButtonWithIcon("Uložit QR", theme.DocumentSaveIcon(), func() {
return container.NewVScroll(container.NewVBox(container.NewHBox(widget.NewLabelWithStyle("Moje identita", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}), layout.NewSpacer()), box, deleteBtn)) if parts.pubQR.Image == nil {
return
}
win := fyne.CurrentApp().Driver().AllWindows()[0]
img := parts.pubQR.Image
fd := dialog.NewFileSave(func(wc fyne.URIWriteCloser, err error) {
if err != nil || wc == nil {
return
}
defer wc.Close()
_ = png.Encode(wc, img)
parts.showToast("QR uložen")
}, win)
fd.SetFileName("identity_qr.png")
fd.Show()
})
qrRow := container.NewHBox(
widget.NewLabel("Obsah QR"),
chooser,
layout.NewSpacer(),
widget.NewButtonWithIcon("Kopírovat jako obrázek", theme.ContentPasteIcon(), func() { copyImageToClipboard(parts.pubQR.Image, parts) }),
saveQR,
)
box := container.NewVBox(qrRow, container.NewCenter(parts.pubQR))
return container.NewVScroll(container.NewVBox(container.NewHBox(widget.NewLabelWithStyle("Moje identita", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}), layout.NewSpacer()), box, deleteRow))
} }
// Decrypt tab // Decrypt tab
func buildDecryptTab(parts *uiParts, svc ServiceFacade) fyne.CanvasObject { func buildDecryptTab(parts *uiParts, svc ServiceFacade) fyne.CanvasObject {
parts.plainOut.Disable() parts.plainOut.Disable()
parts.payload.Disable() parts.plainOut.Wrapping = fyne.TextWrapWord
parts.payload.SetPlaceHolder("Cyphertext se načte po vložení QR kódu…") parts.plainOut.SetMinRowsVisible(12)
parts.payload.OnChanged = nil
parts.payloadQR.FillMode = canvas.ImageFillContain parts.payloadQR.FillMode = canvas.ImageFillContain
parts.payloadQR.SetMinSize(fyne.NewSize(260, 260)) parts.payloadQR.SetMinSize(fyne.NewSize(260, 260))
@ -339,7 +347,6 @@ func buildDecryptTab(parts *uiParts, svc ServiceFacade) fyne.CanvasObject {
} }
setPayload := func(cipher string, img image.Image) { setPayload := func(cipher string, img image.Image) {
parts.payload.SetText(cipher)
parts.payloadQR.Image = img parts.payloadQR.Image = img
parts.payloadQR.Refresh() parts.payloadQR.Refresh()
decrypt(cipher) decrypt(cipher)
@ -404,30 +411,26 @@ func buildDecryptTab(parts *uiParts, svc ServiceFacade) fyne.CanvasObject {
fd.Show() fd.Show()
}) })
copyPayloadBtn := widget.NewButtonWithIcon("Kopírovat payload", theme.ContentCopyIcon(), func() {
if parts.payload.Text == "" {
return
}
copyClip(parts.payload.Text, parts)
})
clearBtn := widget.NewButtonWithIcon("Vymazat", theme.ContentClearIcon(), func() { clearBtn := widget.NewButtonWithIcon("Vymazat", theme.ContentClearIcon(), func() {
parts.payload.SetText("")
parts.payloadQR.Image = nil parts.payloadQR.Image = nil
parts.payloadQR.Refresh() parts.payloadQR.Refresh()
parts.plainOut.SetText("") parts.plainOut.SetText("")
parts.showToast("Vymazáno") parts.showToast("Vymazáno")
}) })
toolbar := container.NewHBox(pasteQRBtn, openQRBtn, copyPayloadBtn, clearBtn, layout.NewSpacer()) // Align buttons to the right by placing spacer first
toolbar := container.NewHBox(layout.NewSpacer(), pasteQRBtn, openQRBtn, clearBtn)
copyDecBtn := widget.NewButtonWithIcon("Kopírovat zprávu", theme.ContentCopyIcon(), func() {
if strings.TrimSpace(parts.plainOut.Text) != "" {
copyClip(parts.plainOut.Text, parts)
}
})
return container.NewVBox( return container.NewVBox(
widget.NewLabelWithStyle("Dešifrování", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}), widget.NewLabelWithStyle("Dešifrování", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
toolbar, toolbar,
container.NewHBox(parts.payloadQR, layout.NewSpacer()), container.NewHBox(parts.payloadQR, layout.NewSpacer()),
widget.NewLabel("Payload"), container.NewHBox(widget.NewLabel("Výsledek"), layout.NewSpacer(), copyDecBtn),
parts.payload,
widget.NewLabel("Výsledek"),
parts.plainOut, parts.plainOut,
) )
} }
@ -455,13 +458,11 @@ func openEncryptPopup(parts *uiParts, svc ServiceFacade, ct Contact) {
} }
} }
win := fyne.CurrentApp().Driver().AllWindows()[0] win := fyne.CurrentApp().Driver().AllWindows()[0]
lastCipher := ""
doEncrypt := func() { doEncrypt := func() {
m := strings.TrimSpace(msgEntry.Text) m := strings.TrimSpace(msgEntry.Text)
fyne.Do(func() { fyne.Do(func() {
if m == "" { if m == "" {
status.SetText("Zpráva je prázdná") status.SetText("Zpráva je prázdná")
lastCipher = ""
updateQR("") updateQR("")
return return
} }
@ -476,7 +477,7 @@ func openEncryptPopup(parts *uiParts, svc ServiceFacade, ct Contact) {
fyne.Do(func() { status.SetText("Chyba: " + err.Error()) }) fyne.Do(func() { status.SetText("Chyba: " + err.Error()) })
return return
} }
fyne.Do(func() { lastCipher = res; updateQR(res); status.SetText("Hotovo") }) fyne.Do(func() { updateQR(res); status.SetText("Hotovo") })
}(m) }(m)
} }
var tmr *time.Timer var tmr *time.Timer
@ -486,12 +487,6 @@ func openEncryptPopup(parts *uiParts, svc ServiceFacade, ct Contact) {
} }
tmr = time.AfterFunc(300*time.Millisecond, doEncrypt) tmr = time.AfterFunc(300*time.Millisecond, doEncrypt)
} }
copyPayloadBtn := widget.NewButton("Kopírovat payload", func() {
if lastCipher != "" {
copyClip(lastCipher, parts)
status.SetText("Zkopírováno")
}
})
copyQRBtn := widget.NewButton("Kopírovat QR", func() { copyImageToClipboard(qrImg.Image, parts) }) copyQRBtn := widget.NewButton("Kopírovat QR", func() { copyImageToClipboard(qrImg.Image, parts) })
saveQRBtn := widget.NewButton("Uložit QR", func() { saveQRBtn := widget.NewButton("Uložit QR", func() {
if qrImg.Image == nil { if qrImg.Image == nil {
@ -509,7 +504,7 @@ func openEncryptPopup(parts *uiParts, svc ServiceFacade, ct Contact) {
fd.SetFileName("message_qr.png") fd.SetFileName("message_qr.png")
fd.Show() fd.Show()
}) })
content := container.NewVBox(widget.NewLabel("Zpráva"), msgEntry, widget.NewSeparator(), container.NewHBox(widget.NewLabel("QR kód"), layout.NewSpacer(), copyPayloadBtn, copyQRBtn, saveQRBtn), qrImg, status) content := container.NewVBox(widget.NewLabel("Zpráva"), msgEntry, widget.NewSeparator(), container.NewHBox(widget.NewLabel("QR kód"), layout.NewSpacer(), copyQRBtn, saveQRBtn), qrImg, status)
title := ct.Name title := ct.Name
if title == "" { if title == "" {
title = "(bez názvu)" title = "(bez názvu)"
@ -573,13 +568,6 @@ func buildContactsTab(parts *uiParts, svc ServiceFacade) fyne.CanvasObject {
if existing != nil { if existing != nil {
certValue = existing.Cert certValue = existing.Cert
} }
manualEntry := widget.NewMultiLineEntry()
manualEntry.SetMinRowsVisible(4)
manualEntry.Wrapping = fyne.TextWrapWord
manualEntry.SetPlaceHolder("Sem lze vložit text PEM pokud QR selže…")
if certValue != "" {
manualEntry.SetText(certValue)
}
qrImg := canvas.NewImageFromImage(nil) qrImg := canvas.NewImageFromImage(nil)
qrImg.FillMode = canvas.ImageFillContain qrImg.FillMode = canvas.ImageFillContain
qrImg.SetMinSize(fyne.NewSize(300, 300)) qrImg.SetMinSize(fyne.NewSize(300, 300))
@ -597,23 +585,7 @@ func buildContactsTab(parts *uiParts, svc ServiceFacade) fyne.CanvasObject {
} }
} }
updateQR() updateQR()
// now that updateQR exists, bind manualEntry changes pasteQR := widget.NewToolbarAction(theme.ContentPasteIcon(), func() {
manualEntry.OnChanged = func(s string) {
certValue = s
updateQR()
}
pasteText := widget.NewToolbarAction(theme.ContentPasteIcon(), func() {
clip := fyne.CurrentApp().Clipboard().Content()
if strings.TrimSpace(clip) == "" {
parts.showToast("Schránka prázdná")
return
}
certValue = clip
manualEntry.SetText(certValue)
updateQR()
parts.showToast("Vloženo")
})
pasteQR := widget.NewToolbarAction(theme.ComputerIcon(), func() {
img, err := readImageClipboard() img, err := readImageClipboard()
if err != nil { if err != nil {
parts.showToast("Chyba čtení schránky: " + err.Error()) parts.showToast("Chyba čtení schránky: " + err.Error())
@ -636,7 +608,6 @@ func buildContactsTab(parts *uiParts, svc ServiceFacade) fyne.CanvasObject {
} }
if txt2, err2 := DecodeQR(inv); err2 == nil { if txt2, err2 := DecodeQR(inv); err2 == nil {
certValue = txt2 certValue = txt2
manualEntry.SetText(certValue)
updateQR() updateQR()
parts.showToast("Načteno z invert QR") parts.showToast("Načteno z invert QR")
return return
@ -654,7 +625,6 @@ func buildContactsTab(parts *uiParts, svc ServiceFacade) fyne.CanvasObject {
return return
} }
certValue = txt certValue = txt
manualEntry.SetText(certValue)
updateQR() updateQR()
parts.showToast("Načteno z QR") parts.showToast("Načteno z QR")
}) })
@ -678,37 +648,14 @@ func buildContactsTab(parts *uiParts, svc ServiceFacade) fyne.CanvasObject {
return return
} }
certValue = txt certValue = txt
manualEntry.SetText(certValue)
updateQR() updateQR()
parts.showToast("Načteno z QR") parts.showToast("Načteno z QR")
}, win) }, win)
fd.SetFilter(storage.NewExtensionFileFilter([]string{".png", ".jpg", ".jpeg"})) fd.SetFilter(storage.NewExtensionFileFilter([]string{".png", ".jpg", ".jpeg"}))
fd.Show() fd.Show()
}) })
clearAct := widget.NewToolbarAction(theme.ContentClearIcon(), func() { certValue = ""; manualEntry.SetText(""); updateQR() }) clearAct := widget.NewToolbarAction(theme.ContentClearIcon(), func() { certValue = ""; updateQR() })
copyAct := widget.NewToolbarAction(theme.ContentCopyIcon(), func() { toolbar := widget.NewToolbar(pasteQR, openImg, clearAct)
if certValue != "" {
copyClip(certValue, parts)
}
})
saveAct := widget.NewToolbarAction(theme.DocumentSaveIcon(), func() {
if qrImg.Image == nil {
return
}
win := fyne.CurrentApp().Driver().AllWindows()[0]
img := qrImg.Image
fd := dialog.NewFileSave(func(wc fyne.URIWriteCloser, err error) {
if err != nil || wc == nil {
return
}
defer wc.Close()
_ = png.Encode(wc, img)
parts.showToast("QR uložen")
}, win)
fd.SetFileName("contact_qr.png")
fd.Show()
})
toolbar := widget.NewToolbar(pasteText, pasteQR, openImg, widget.NewToolbarSeparator(), copyAct, saveAct, clearAct)
win := fyne.CurrentApp().Driver().AllWindows()[0] win := fyne.CurrentApp().Driver().AllWindows()[0]
var popup dialog.Dialog var popup dialog.Dialog
save := func(useEncrypt bool) { save := func(useEncrypt bool) {
@ -744,12 +691,19 @@ func buildContactsTab(parts *uiParts, svc ServiceFacade) fyne.CanvasObject {
} }
} }
if ask { if ask {
dialog.NewCustomConfirm("Common Name", "Použít", "Ponechat", widget.NewLabel(fmt.Sprintf("Common Name: %s\nPoužít jako název?", cn)), func(ok bool) { entry := widget.NewEntry()
entry.SetText(name)
content := container.NewVBox(
widget.NewLabel(fmt.Sprintf("Common Name nalezen v certifikátu: %s", cn)),
widget.NewLabel("Chcete použít CN jako název, nebo jej upravit?"),
entry,
)
dialog.NewCustomConfirm("Název kontaktu", "Použít CN", "Uložit", content, func(ok bool) {
if ok { if ok {
proceed(cn) proceed(cn)
} else { return
proceed(name)
} }
proceed(strings.TrimSpace(entry.Text))
}, win).Show() }, win).Show()
return return
} }
@ -772,9 +726,8 @@ func buildContactsTab(parts *uiParts, svc ServiceFacade) fyne.CanvasObject {
parts.showToast("Smazáno") parts.showToast("Smazáno")
}, win).Show() }, win).Show()
}) })
useBtn := widget.NewButton("Použít ve Šifrování", func() { save(true) })
saveBtn := widget.NewButton("Uložit", func() { save(false) }) saveBtn := widget.NewButton("Uložit", func() { save(false) })
row := container.NewHBox(saveBtn, useBtn, layout.NewSpacer(), delBtn) row := container.NewHBox(layout.NewSpacer(), saveBtn, delBtn, layout.NewSpacer())
title := "Nový kontakt" title := "Nový kontakt"
if existing != nil { if existing != nil {
title = "Upravit kontakt" title = "Upravit kontakt"
@ -783,7 +736,6 @@ func buildContactsTab(parts *uiParts, svc ServiceFacade) fyne.CanvasObject {
popup = dialog.NewCustom(title, "Zavřít", container.NewVBox( popup = dialog.NewCustom(title, "Zavřít", container.NewVBox(
widget.NewLabel("Název"), nameEntry, widget.NewLabel("Název"), nameEntry,
widget.NewLabel("Certifikát / Public key (QR)"), toolbar, qrImg, widget.NewLabel("Certifikát / Public key (QR)"), toolbar, qrImg,
widget.NewLabel("Text PEM"), manualEntry,
widget.NewSeparator(), row), win) widget.NewSeparator(), row), win)
popup.Resize(fyne.NewSize(640, 520)) popup.Resize(fyne.NewSize(640, 520))
popup.Show() popup.Show()