mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-08-01 19:18:28 -04:00
feat: add QR Generator launcher (#2941)
* feat: add QR Generator modal with auto-generate on input debounce - QRGeneratorModal with dual-buffer sequential fade animation - Auto-generate QR code on typing stop (200ms debounce) - Clear text button on input field - Escape key closes modal - Register modal in PopoutService and AppSearchService - Go backend for text QR code generation * fix: add QR Generator toggle to Launcher DMS settings * qr: also add qrg built in launcher plugin --------- Co-authored-by: bbedward <bbedward@gmail.com>
This commit is contained in:
@@ -43,6 +43,8 @@ func HandleRequest(conn *models.Conn, req models.Request, manager *Manager) {
|
||||
handleGetNetworkQRCode(conn, req, manager)
|
||||
case "network.qrcode-content":
|
||||
handleGetNetworkQRCodeContent(conn, req, manager)
|
||||
case "network.generate-qrcode":
|
||||
handleGenerateQRCode(conn, req)
|
||||
case "network.delete-qrcode":
|
||||
handleDeleteQRCode(conn, req, manager)
|
||||
case "network.ethernet.info":
|
||||
@@ -365,6 +367,22 @@ func handleGetNetworkQRCodeContent(conn *models.Conn, req models.Request, manage
|
||||
models.Respond(conn, req.ID, content)
|
||||
}
|
||||
|
||||
func handleGenerateQRCode(conn *models.Conn, req models.Request) {
|
||||
text, err := params.String(req.Params, "text")
|
||||
if err != nil {
|
||||
models.RespondError(conn, req.ID, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
paths, err := generateTextQRCode(text)
|
||||
if err != nil {
|
||||
models.RespondError(conn, req.ID, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
models.Respond(conn, req.ID, paths)
|
||||
}
|
||||
|
||||
func handleDeleteQRCode(conn *models.Conn, req models.Request, _ *Manager) {
|
||||
path, err := params.String(req.Params, "path")
|
||||
if err != nil {
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
package network
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/yeqown/go-qrcode/v2"
|
||||
"github.com/yeqown/go-qrcode/writer/standard"
|
||||
)
|
||||
|
||||
const textQRCodeTmpPrefix = "/tmp/dank-text-qrcode-"
|
||||
|
||||
func generateTextQRCode(text string) ([2]string, error) {
|
||||
qrc, err := qrcode.New(text)
|
||||
if err != nil {
|
||||
return [2]string{}, fmt.Errorf("failed to create QR code for text: %w", err)
|
||||
}
|
||||
|
||||
pathThemed, pathNormal := textQRCodePaths(text)
|
||||
|
||||
if err := saveQRCodePNG(qrc, pathThemed, standard.WithBgTransparent(), standard.WithFgColorRGBHex("#ffffff")); err != nil {
|
||||
return [2]string{}, err
|
||||
}
|
||||
if err := saveQRCodePNG(qrc, pathNormal); err != nil {
|
||||
return [2]string{}, err
|
||||
}
|
||||
|
||||
return [2]string{pathThemed, pathNormal}, nil
|
||||
}
|
||||
|
||||
// Write to a temp file and rename into place so the shell's Image never
|
||||
// observes a partially written PNG.
|
||||
func saveQRCodePNG(qrc *qrcode.QRCode, path string, opts ...standard.ImageOption) error {
|
||||
tmpPath := path + ".tmp"
|
||||
opts = append(opts, standard.WithBuiltinImageEncoder(standard.PNG_FORMAT))
|
||||
|
||||
w, err := standard.New(tmpPath, opts...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create QR code writer: %w", err)
|
||||
}
|
||||
if err := qrc.Save(w); err != nil {
|
||||
os.Remove(tmpPath)
|
||||
return fmt.Errorf("failed to save QR code: %w", err)
|
||||
}
|
||||
if err := os.Rename(tmpPath, path); err != nil {
|
||||
os.Remove(tmpPath)
|
||||
return fmt.Errorf("failed to move QR code into place: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Paths are unique per generation, not per text: the library's mask selection
|
||||
// is non-deterministic, so regenerating the same text produces different
|
||||
// bytes, and reusing a path lets the shell's URL-keyed pixmap cache serve a
|
||||
// stale pattern over the new file.
|
||||
func textQRCodePaths(text string) (themed, normal string) {
|
||||
hash := fmt.Sprintf("%x", sha256.Sum256([]byte(text)))[:8]
|
||||
nonce := time.Now().UnixNano()
|
||||
themed = fmt.Sprintf("%s%s-%d-themed.png", textQRCodeTmpPrefix, hash, nonce)
|
||||
normal = fmt.Sprintf("%s%s-%d-normal.png", textQRCodeTmpPrefix, hash, nonce)
|
||||
return
|
||||
}
|
||||
@@ -24,7 +24,7 @@ func qrCodePaths(ssid string) (themed, normal string) {
|
||||
|
||||
func isValidQRCodePath(path string) bool {
|
||||
clean := filepath.Clean(path)
|
||||
return strings.HasPrefix(clean, qrCodeTmpPrefix) && strings.HasSuffix(clean, ".png")
|
||||
return (strings.HasPrefix(clean, qrCodeTmpPrefix) || strings.HasPrefix(clean, textQRCodeTmpPrefix)) && strings.HasSuffix(clean, ".png")
|
||||
}
|
||||
|
||||
var safePathChar = regexp.MustCompile(`[^a-zA-Z0-9_-]`)
|
||||
|
||||
Reference in New Issue
Block a user