mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-08-06 21:48:30 -04:00
core/cli: expose QR code CLI
port 1.5
(cherry picked from commit e6504add7b)
This commit is contained in:
@@ -759,6 +759,7 @@ func getCommonCommands() []*cobra.Command {
|
|||||||
greeterCmd,
|
greeterCmd,
|
||||||
setupCmd,
|
setupCmd,
|
||||||
colorCmd,
|
colorCmd,
|
||||||
|
qrCmd,
|
||||||
screenshotCmd,
|
screenshotCmd,
|
||||||
notifyActionCmd,
|
notifyActionCmd,
|
||||||
notifyCmd,
|
notifyCmd,
|
||||||
|
|||||||
@@ -0,0 +1,265 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/clipboard"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/qrcode"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
|
||||||
|
"github.com/mattn/go-isatty"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
qrEcc string
|
||||||
|
qrVersion int
|
||||||
|
qrOutput string
|
||||||
|
qrStdout bool
|
||||||
|
qrClipboard bool
|
||||||
|
qrCopyText bool
|
||||||
|
qrRender bool
|
||||||
|
qrNoRender bool
|
||||||
|
qrInvert bool
|
||||||
|
qrQuietZone int
|
||||||
|
qrModuleSize int
|
||||||
|
qrFg string
|
||||||
|
qrBg string
|
||||||
|
qrTransparent bool
|
||||||
|
qrBorder int
|
||||||
|
qrShape string
|
||||||
|
qrLogo string
|
||||||
|
qrLogoScale int
|
||||||
|
|
||||||
|
qrWifiPassword string
|
||||||
|
qrWifiSecurity string
|
||||||
|
qrWifiHidden bool
|
||||||
|
)
|
||||||
|
|
||||||
|
var qrCmd = &cobra.Command{
|
||||||
|
Use: "qr [text]",
|
||||||
|
Short: "Generate QR codes",
|
||||||
|
Long: `Generate a QR code from text, stdin, or a WiFi network.
|
||||||
|
|
||||||
|
By default the code is rendered to the terminal when stdout is a TTY, or
|
||||||
|
written as PNG bytes to stdout when piped. Use flags to also copy to the
|
||||||
|
clipboard, save a PNG, or tune encoding and colors.
|
||||||
|
|
||||||
|
Input:
|
||||||
|
dms qr "https://example.com" # encode an argument
|
||||||
|
echo -n "data" | dms qr # encode stdin
|
||||||
|
dms qr - # encode stdin explicitly
|
||||||
|
|
||||||
|
Output (combine freely):
|
||||||
|
dms qr "text" --clipboard # copy PNG image to clipboard
|
||||||
|
dms qr "text" --copy-text # copy the source text to clipboard
|
||||||
|
dms qr "text" -o code.png # save a PNG file
|
||||||
|
dms qr "text" > code.png # PNG to stdout (piped)
|
||||||
|
dms qr "text" --render # force terminal render
|
||||||
|
|
||||||
|
Encoding & style:
|
||||||
|
--ecc L|M|Q|H # error correction (default M)
|
||||||
|
--qr-version 10 # force symbol version (1-40)
|
||||||
|
--module-size 12 --fg '#000' ... # PNG sizing and colors
|
||||||
|
--shape circle # round modules
|
||||||
|
--logo icon.png # center logo (bumps --ecc to H)
|
||||||
|
--invert # flip colors for light terminals
|
||||||
|
|
||||||
|
WiFi:
|
||||||
|
dms qr wifi MySSID -p secret # build from an explicit password
|
||||||
|
dms qr wifi MySSID # pull the saved secret from the shell`,
|
||||||
|
Args: cobra.ArbitraryArgs,
|
||||||
|
Run: runQR,
|
||||||
|
}
|
||||||
|
|
||||||
|
var qrWifiCmd = &cobra.Command{
|
||||||
|
Use: "wifi <ssid>",
|
||||||
|
Short: "Generate a WiFi QR code",
|
||||||
|
Long: `Generate a QR code that joins a WiFi network when scanned.
|
||||||
|
|
||||||
|
With --password the code is built entirely offline. Without it, the saved
|
||||||
|
credentials are fetched from the running DMS shell (like the network panel).`,
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
Run: runQRWifi,
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
qrCmd.PersistentFlags().StringVar(&qrEcc, "ecc", "", "Error correction level: L, M, Q, H (default M, or H with --logo)")
|
||||||
|
qrCmd.PersistentFlags().IntVar(&qrVersion, "qr-version", 0, "Force QR symbol version 1-40 (0 = auto)")
|
||||||
|
qrCmd.PersistentFlags().StringVarP(&qrOutput, "output", "o", "", "Write a PNG to this file")
|
||||||
|
qrCmd.PersistentFlags().BoolVar(&qrStdout, "stdout", false, "Write PNG bytes to stdout")
|
||||||
|
qrCmd.PersistentFlags().BoolVar(&qrClipboard, "clipboard", false, "Copy the PNG image to the clipboard")
|
||||||
|
qrCmd.PersistentFlags().BoolVar(&qrCopyText, "copy-text", false, "Copy the source text to the clipboard")
|
||||||
|
qrCmd.PersistentFlags().BoolVar(&qrRender, "render", false, "Force terminal rendering")
|
||||||
|
qrCmd.PersistentFlags().BoolVar(&qrNoRender, "no-render", false, "Never render to the terminal")
|
||||||
|
qrCmd.PersistentFlags().BoolVar(&qrInvert, "invert", false, "Swap colors (for light terminals)")
|
||||||
|
qrCmd.PersistentFlags().IntVar(&qrQuietZone, "quiet-zone", 2, "Terminal margin in modules")
|
||||||
|
qrCmd.PersistentFlags().IntVar(&qrModuleSize, "module-size", 0, "PNG pixels per module (0 = auto)")
|
||||||
|
qrCmd.PersistentFlags().StringVar(&qrFg, "fg", "", "Dark module color (#RGB or #RRGGBB)")
|
||||||
|
qrCmd.PersistentFlags().StringVar(&qrBg, "bg", "", "Light module color (#RGB or #RRGGBB)")
|
||||||
|
qrCmd.PersistentFlags().BoolVar(&qrTransparent, "transparent", false, "Transparent PNG background")
|
||||||
|
qrCmd.PersistentFlags().IntVar(&qrBorder, "border", -1, "PNG border in pixels (-1 = auto)")
|
||||||
|
qrCmd.PersistentFlags().StringVar(&qrShape, "shape", "square", "PNG module shape (square, circle)")
|
||||||
|
qrCmd.PersistentFlags().StringVar(&qrLogo, "logo", "", "Center a PNG/JPEG logo on the PNG output")
|
||||||
|
qrCmd.PersistentFlags().IntVar(&qrLogoScale, "logo-scale", 0, "Max logo size as 1/N of the code (0 = library default of 5)")
|
||||||
|
|
||||||
|
qrWifiCmd.Flags().StringVarP(&qrWifiPassword, "password", "p", "", "WiFi password (offline build)")
|
||||||
|
qrWifiCmd.Flags().StringVar(&qrWifiSecurity, "security", "WPA", "Security type (WPA, WEP, nopass)")
|
||||||
|
qrWifiCmd.Flags().BoolVar(&qrWifiHidden, "hidden", false, "Mark the network as hidden")
|
||||||
|
|
||||||
|
qrCmd.AddCommand(qrWifiCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func runQR(cmd *cobra.Command, args []string) {
|
||||||
|
text := strings.Join(args, " ")
|
||||||
|
if text == "" || text == "-" {
|
||||||
|
data, err := io.ReadAll(os.Stdin)
|
||||||
|
if err != nil {
|
||||||
|
fatalf("Error reading stdin: %v", err)
|
||||||
|
}
|
||||||
|
text = strings.TrimRight(string(data), "\n")
|
||||||
|
}
|
||||||
|
if text == "" {
|
||||||
|
fatalf("Error: no input (provide text, pipe stdin, or use a subcommand)")
|
||||||
|
}
|
||||||
|
emitQR(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
func runQRWifi(cmd *cobra.Command, args []string) {
|
||||||
|
ssid := args[0]
|
||||||
|
if qrWifiPassword != "" || strings.EqualFold(qrWifiSecurity, "nopass") {
|
||||||
|
emitQR(qrcode.WiFiString(qrWifiSecurity, ssid, qrWifiPassword, qrWifiHidden))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
content, err := fetchWifiQRContent(ssid)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||||
|
fmt.Fprintln(os.Stderr, "Hint: pass --password to build the code without the shell.")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if qrWifiHidden {
|
||||||
|
content = strings.TrimSuffix(content, ";") + "H:true;;"
|
||||||
|
}
|
||||||
|
emitQR(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
func emitQR(text string) {
|
||||||
|
renderTerm := shouldRenderTerminal()
|
||||||
|
pngToStdout := qrStdout || (!renderTerm && qrOutput == "" && !qrClipboard && !qrCopyText)
|
||||||
|
|
||||||
|
if pngToStdout || qrOutput != "" || qrClipboard {
|
||||||
|
png, err := qrcode.RenderPNG(text, qrcode.ImageOptions{
|
||||||
|
ECC: effectiveEcc(),
|
||||||
|
Version: qrVersion,
|
||||||
|
ModuleSize: qrModuleSize,
|
||||||
|
Fg: qrFg,
|
||||||
|
Bg: qrBg,
|
||||||
|
Transparent: qrTransparent,
|
||||||
|
Border: qrBorder,
|
||||||
|
Shape: qrShape,
|
||||||
|
Logo: qrLogo,
|
||||||
|
LogoScale: qrLogoScale,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
fatalf("Error encoding QR: %v", err)
|
||||||
|
}
|
||||||
|
emitPNG(png, pngToStdout)
|
||||||
|
}
|
||||||
|
|
||||||
|
if qrCopyText {
|
||||||
|
if err := clipboard.CopyText(text); err != nil {
|
||||||
|
fatalf("Error copying text: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !renderTerm {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
out, err := qrcode.RenderTerminal(text, qrcode.TermOptions{
|
||||||
|
ECC: effectiveEcc(),
|
||||||
|
Version: qrVersion,
|
||||||
|
QuietZone: qrQuietZone,
|
||||||
|
Invert: qrInvert,
|
||||||
|
Fg: qrFg,
|
||||||
|
Bg: qrBg,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
fatalf("Error rendering QR: %v", err)
|
||||||
|
}
|
||||||
|
dst := os.Stdout
|
||||||
|
if pngToStdout {
|
||||||
|
dst = os.Stderr
|
||||||
|
}
|
||||||
|
fmt.Fprint(dst, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
func emitPNG(png []byte, toStdout bool) {
|
||||||
|
if qrOutput != "" {
|
||||||
|
if err := os.WriteFile(qrOutput, png, 0o644); err != nil {
|
||||||
|
fatalf("Error writing file: %v", err)
|
||||||
|
}
|
||||||
|
fmt.Fprintln(os.Stderr, qrOutput)
|
||||||
|
}
|
||||||
|
if toStdout {
|
||||||
|
os.Stdout.Write(png)
|
||||||
|
}
|
||||||
|
if qrClipboard {
|
||||||
|
if err := clipboard.Copy(png, "image/png"); err != nil {
|
||||||
|
fatalf("Error copying image: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func shouldRenderTerminal() bool {
|
||||||
|
switch {
|
||||||
|
case qrNoRender:
|
||||||
|
return false
|
||||||
|
case qrRender:
|
||||||
|
return true
|
||||||
|
case qrStdout, qrOutput != "", qrClipboard, qrCopyText:
|
||||||
|
return false
|
||||||
|
default:
|
||||||
|
return isatty.IsTerminal(os.Stdout.Fd())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func effectiveEcc() string {
|
||||||
|
switch {
|
||||||
|
case qrEcc != "":
|
||||||
|
return qrEcc
|
||||||
|
case qrLogo != "":
|
||||||
|
return "H"
|
||||||
|
default:
|
||||||
|
return "M"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fatalf(format string, args ...any) {
|
||||||
|
fmt.Fprintf(os.Stderr, format+"\n", args...)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchWifiQRContent(ssid string) (string, error) {
|
||||||
|
resp, err := sendServerRequest(models.Request{
|
||||||
|
ID: 1,
|
||||||
|
Method: "network.qrcode-content",
|
||||||
|
Params: map[string]any{"ssid": ssid},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if resp.Error != "" {
|
||||||
|
return "", fmt.Errorf("%s", resp.Error)
|
||||||
|
}
|
||||||
|
if resp.Result == nil {
|
||||||
|
return "", fmt.Errorf("empty response")
|
||||||
|
}
|
||||||
|
content, ok := (*resp.Result).(string)
|
||||||
|
if !ok {
|
||||||
|
return "", fmt.Errorf("unexpected response format")
|
||||||
|
}
|
||||||
|
return content, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,282 @@
|
|||||||
|
package qrcode
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
_ "image/jpeg"
|
||||||
|
_ "image/png"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
qr "github.com/yeqown/go-qrcode/v2"
|
||||||
|
"github.com/yeqown/go-qrcode/writer/standard"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TermOptions struct {
|
||||||
|
ECC string
|
||||||
|
Version int
|
||||||
|
QuietZone int
|
||||||
|
Invert bool
|
||||||
|
Fg string
|
||||||
|
Bg string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ImageOptions struct {
|
||||||
|
ECC string
|
||||||
|
Version int
|
||||||
|
ModuleSize int
|
||||||
|
Fg string
|
||||||
|
Bg string
|
||||||
|
Transparent bool
|
||||||
|
Border int
|
||||||
|
Shape string
|
||||||
|
Logo string
|
||||||
|
LogoScale int
|
||||||
|
}
|
||||||
|
|
||||||
|
var wifiEscaper = strings.NewReplacer(`\`, `\\`, `;`, `\;`, `,`, `\,`, `:`, `\:`, `"`, `\"`)
|
||||||
|
|
||||||
|
func WiFiString(security, ssid, password string, hidden bool) string {
|
||||||
|
if security == "" {
|
||||||
|
security = "WPA"
|
||||||
|
}
|
||||||
|
var b strings.Builder
|
||||||
|
fmt.Fprintf(&b, "WIFI:T:%s;S:%s;", security, wifiEscaper.Replace(ssid))
|
||||||
|
if !strings.EqualFold(security, "nopass") {
|
||||||
|
fmt.Fprintf(&b, "P:%s;", wifiEscaper.Replace(password))
|
||||||
|
}
|
||||||
|
if hidden {
|
||||||
|
b.WriteString("H:true;")
|
||||||
|
}
|
||||||
|
b.WriteString(";")
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Colors are painted explicitly on both halves of each ▀ cell so polarity
|
||||||
|
// does not depend on the terminal theme.
|
||||||
|
func RenderTerminal(text string, opt TermOptions) (string, error) {
|
||||||
|
fg, err := parseColor(opt.Fg, color.RGBA{A: 255})
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
bg, err := parseColor(opt.Bg, color.RGBA{R: 255, G: 255, B: 255, A: 255})
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if opt.Invert {
|
||||||
|
fg, bg = bg, fg
|
||||||
|
}
|
||||||
|
|
||||||
|
mat, err := encode(text, opt.ECC, opt.Version)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
grid := bitmapWithQuietZone(mat, opt.QuietZone)
|
||||||
|
|
||||||
|
var b strings.Builder
|
||||||
|
for y := 0; y < len(grid); y += 2 {
|
||||||
|
for x := range grid[y] {
|
||||||
|
top := moduleColor(grid[y][x], fg, bg)
|
||||||
|
bottom := bg
|
||||||
|
if y+1 < len(grid) {
|
||||||
|
bottom = moduleColor(grid[y+1][x], fg, bg)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(&b, "\x1b[38;2;%d;%d;%dm\x1b[48;2;%d;%d;%dm▀", top.R, top.G, top.B, bottom.R, bottom.G, bottom.B)
|
||||||
|
}
|
||||||
|
b.WriteString("\x1b[0m\n")
|
||||||
|
}
|
||||||
|
return b.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func RenderPNG(text string, opt ImageOptions) ([]byte, error) {
|
||||||
|
encOpts, err := encodeOptions(opt.ECC, opt.Version)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
imgOpts, err := imageOptions(opt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
q, err := qr.NewWith(text, encOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
w := standard.NewWithWriter(nopCloser{&buf}, imgOpts...)
|
||||||
|
if err := q.Save(w); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeOptions(ecc string, version int) ([]qr.EncodeOption, error) {
|
||||||
|
eccOpt, err := eccOption(ecc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
opts := []qr.EncodeOption{eccOpt}
|
||||||
|
switch {
|
||||||
|
case version == 0:
|
||||||
|
case version >= 1 && version <= 40:
|
||||||
|
opts = append(opts, qr.WithVersion(version))
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("QR version must be 1-40, got %d", version)
|
||||||
|
}
|
||||||
|
return opts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func eccOption(level string) (qr.EncodeOption, error) {
|
||||||
|
switch strings.ToUpper(level) {
|
||||||
|
case "", "M":
|
||||||
|
return qr.WithErrorCorrectionLevel(qr.ErrorCorrectionMedium), nil
|
||||||
|
case "L":
|
||||||
|
return qr.WithErrorCorrectionLevel(qr.ErrorCorrectionLow), nil
|
||||||
|
case "Q":
|
||||||
|
return qr.WithErrorCorrectionLevel(qr.ErrorCorrectionQuart), nil
|
||||||
|
case "H":
|
||||||
|
return qr.WithErrorCorrectionLevel(qr.ErrorCorrectionHighest), nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("invalid error correction level %q (want L, M, Q, or H)", level)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func imageOptions(opt ImageOptions) ([]standard.ImageOption, error) {
|
||||||
|
if opt.ModuleSize < 0 || opt.ModuleSize > 255 {
|
||||||
|
return nil, fmt.Errorf("module size must be 0-255, got %d", opt.ModuleSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := []standard.ImageOption{standard.WithBuiltinImageEncoder(standard.PNG_FORMAT)}
|
||||||
|
if opt.ModuleSize > 0 {
|
||||||
|
opts = append(opts, standard.WithQRWidth(uint8(opt.ModuleSize)))
|
||||||
|
}
|
||||||
|
if opt.Border >= 0 {
|
||||||
|
opts = append(opts, standard.WithBorderWidth(opt.Border))
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case opt.Transparent:
|
||||||
|
opts = append(opts, standard.WithBgTransparent())
|
||||||
|
case opt.Bg != "":
|
||||||
|
c, err := parseColor(opt.Bg, color.RGBA{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
opts = append(opts, standard.WithBgColor(c))
|
||||||
|
}
|
||||||
|
if opt.Fg != "" {
|
||||||
|
c, err := parseColor(opt.Fg, color.RGBA{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
opts = append(opts, standard.WithFgColor(c))
|
||||||
|
}
|
||||||
|
|
||||||
|
switch strings.ToLower(opt.Shape) {
|
||||||
|
case "", "square":
|
||||||
|
case "circle":
|
||||||
|
opts = append(opts, standard.WithCircleShape())
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("invalid shape %q (want square or circle)", opt.Shape)
|
||||||
|
}
|
||||||
|
|
||||||
|
if opt.Logo != "" {
|
||||||
|
img, err := loadImage(opt.Logo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
opts = append(opts, standard.WithLogoImage(img))
|
||||||
|
if opt.LogoScale > 0 {
|
||||||
|
opts = append(opts, standard.WithLogoSizeMultiplier(opt.LogoScale))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return opts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func encode(text, ecc string, version int) (qr.Matrix, error) {
|
||||||
|
opts, err := encodeOptions(ecc, version)
|
||||||
|
if err != nil {
|
||||||
|
return qr.Matrix{}, err
|
||||||
|
}
|
||||||
|
q, err := qr.NewWith(text, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return qr.Matrix{}, err
|
||||||
|
}
|
||||||
|
mw := &matrixWriter{}
|
||||||
|
if err := q.Save(mw); err != nil {
|
||||||
|
return qr.Matrix{}, err
|
||||||
|
}
|
||||||
|
return mw.mat, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func bitmapWithQuietZone(mat qr.Matrix, quiet int) [][]bool {
|
||||||
|
if quiet < 0 {
|
||||||
|
quiet = 0
|
||||||
|
}
|
||||||
|
src := mat.Bitmap()
|
||||||
|
h := len(src)
|
||||||
|
w := 0
|
||||||
|
if h > 0 {
|
||||||
|
w = len(src[0])
|
||||||
|
}
|
||||||
|
out := make([][]bool, h+quiet*2)
|
||||||
|
for y := range out {
|
||||||
|
out[y] = make([]bool, w+quiet*2)
|
||||||
|
}
|
||||||
|
for y := range h {
|
||||||
|
for x := range w {
|
||||||
|
out[y+quiet][x+quiet] = src[y][x]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func moduleColor(dark bool, fg, bg color.RGBA) color.RGBA {
|
||||||
|
if dark {
|
||||||
|
return fg
|
||||||
|
}
|
||||||
|
return bg
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseColor(hex string, def color.RGBA) (color.RGBA, error) {
|
||||||
|
hex = strings.TrimPrefix(strings.TrimSpace(hex), "#")
|
||||||
|
if hex == "" {
|
||||||
|
return def, nil
|
||||||
|
}
|
||||||
|
if len(hex) == 3 {
|
||||||
|
hex = fmt.Sprintf("%c%c%c%c%c%c", hex[0], hex[0], hex[1], hex[1], hex[2], hex[2])
|
||||||
|
}
|
||||||
|
var r, g, b int
|
||||||
|
if len(hex) != 6 {
|
||||||
|
return def, fmt.Errorf("invalid color %q (want #RGB or #RRGGBB)", hex)
|
||||||
|
}
|
||||||
|
if _, err := fmt.Sscanf(hex, "%02x%02x%02x", &r, &g, &b); err != nil {
|
||||||
|
return def, fmt.Errorf("invalid color %q (want #RGB or #RRGGBB)", hex)
|
||||||
|
}
|
||||||
|
return color.RGBA{R: uint8(r), G: uint8(g), B: uint8(b), A: 255}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadImage(path string) (image.Image, error) {
|
||||||
|
f, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
img, _, err := image.Decode(f)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("decode %s: %w", path, err)
|
||||||
|
}
|
||||||
|
return img, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type matrixWriter struct{ mat qr.Matrix }
|
||||||
|
|
||||||
|
func (w *matrixWriter) Write(m qr.Matrix) error { w.mat = m; return nil }
|
||||||
|
func (w *matrixWriter) Close() error { return nil }
|
||||||
|
|
||||||
|
type nopCloser struct{ *bytes.Buffer }
|
||||||
|
|
||||||
|
func (nopCloser) Close() error { return nil }
|
||||||
@@ -43,6 +43,8 @@ func HandleRequest(conn net.Conn, req models.Request, manager *Manager) {
|
|||||||
handleGetNetworkInfo(conn, req, manager)
|
handleGetNetworkInfo(conn, req, manager)
|
||||||
case "network.qrcode":
|
case "network.qrcode":
|
||||||
handleGetNetworkQRCode(conn, req, manager)
|
handleGetNetworkQRCode(conn, req, manager)
|
||||||
|
case "network.qrcode-content":
|
||||||
|
handleGetNetworkQRCodeContent(conn, req, manager)
|
||||||
case "network.delete-qrcode":
|
case "network.delete-qrcode":
|
||||||
handleDeleteQRCode(conn, req, manager)
|
handleDeleteQRCode(conn, req, manager)
|
||||||
case "network.ethernet.info":
|
case "network.ethernet.info":
|
||||||
@@ -341,6 +343,22 @@ func handleGetNetworkQRCode(conn net.Conn, req models.Request, manager *Manager)
|
|||||||
models.Respond(conn, req.ID, content)
|
models.Respond(conn, req.ID, content)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handleGetNetworkQRCodeContent(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
|
ssid, err := params.String(req.Params, "ssid")
|
||||||
|
if err != nil {
|
||||||
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
content, err := manager.GetWiFiQRContent(ssid)
|
||||||
|
if err != nil {
|
||||||
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
models.Respond(conn, req.ID, content)
|
||||||
|
}
|
||||||
|
|
||||||
func handleDeleteQRCode(conn net.Conn, req models.Request, _ *Manager) {
|
func handleDeleteQRCode(conn net.Conn, req models.Request, _ *Manager) {
|
||||||
path, err := params.String(req.Params, "path")
|
path, err := params.String(req.Params, "path")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -473,6 +473,10 @@ func (m *Manager) GetNetworkInfoDetailed(ssid string) (*NetworkInfoResponse, err
|
|||||||
return m.backend.GetWiFiNetworkDetails(ssid)
|
return m.backend.GetWiFiNetworkDetails(ssid)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Manager) GetWiFiQRContent(ssid string) (string, error) {
|
||||||
|
return m.backend.GetWiFiQRCodeContent(ssid)
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Manager) GetNetworkQRCode(ssid string) ([2]string, error) {
|
func (m *Manager) GetNetworkQRCode(ssid string) ([2]string, error) {
|
||||||
content, err := m.backend.GetWiFiQRCodeContent(ssid)
|
content, err := m.backend.GetWiFiQRCodeContent(ssid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -5,12 +5,14 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/qrcode"
|
||||||
)
|
)
|
||||||
|
|
||||||
const qrCodeTmpPrefix = "/tmp/dank-wifi-qrcode-"
|
const qrCodeTmpPrefix = "/tmp/dank-wifi-qrcode-"
|
||||||
|
|
||||||
func FormatWiFiQRString(securityType, ssid, password string) string {
|
func FormatWiFiQRString(securityType, ssid, password string) string {
|
||||||
return fmt.Sprintf("WIFI:T:%s;S:%s;P:%s;;", securityType, ssid, password)
|
return qrcode.WiFiString(securityType, ssid, password, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func qrCodePaths(ssid string) (themed, normal string) {
|
func qrCodePaths(ssid string) (themed, normal string) {
|
||||||
|
|||||||
Reference in New Issue
Block a user