Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
ab74106
refactor: split MuPDF warning suppression into per-OS files
Matbe34 May 4, 2026
5587657
refactor: centralize per-OS path constants in platform package
Matbe34 May 4, 2026
5f4da41
refactor: split nss package into linux build + non-linux stub
Matbe34 May 4, 2026
1dd1873
chore: udpate gitignore
Matbe34 May 4, 2026
4aac66f
fix: use os.UserConfigDir for cross-platform config path
Matbe34 May 4, 2026
b2615ae
feat: add GetFingerprint helper and X509Cert field on Certificate
Matbe34 May 4, 2026
2baee4f
feat: add certstore package for per-OS certificate stores
Matbe34 May 4, 2026
e45d76f
feat: dispatch certificate listing per OS via loadPlatformCertificates
Matbe34 May 4, 2026
0865ebb
test: smoke test certstore.OpenPlatformStore on linux
Matbe34 May 4, 2026
082d641
feat: dispatch PDF signing per OS and use widened certstore signer
Matbe34 May 4, 2026
74cd2f5
build: add vet-cross task to catch build-tag mistakes
Matbe34 May 4, 2026
479b0a7
build: add build-windows task for mingw-w64 cross-compile
Matbe34 May 4, 2026
44a34b7
ci: add windows amd64 build workflow
Matbe34 May 4, 2026
22e3861
deps: replace smimesign with nikwo fork for windows cgo type fix
Matbe34 May 4, 2026
63f096d
test: skip NSS smoke test when ListCertificates fails too
Matbe34 May 4, 2026
5f5b97e
build: add wails desktop,production tags to windows build
Matbe34 May 4, 2026
d0bc776
build: build frontend before windows go build so embed has assets
Matbe34 May 4, 2026
da18292
feat: hide console on windows GUI launch and reattach for CLI
Matbe34 May 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions .github/workflows/windows.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
name: Windows Build

on:
push:
branches: [ master ]
pull_request:
branches: [ master ]

jobs:
build:
name: Windows amd64 build
runs-on: windows-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.24.0'
cache: true

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: frontend/package-lock.json

- name: Install frontend dependencies
working-directory: frontend
run: npm ci

- name: Build frontend
# Populates frontend/dist so //go:embed all:frontend/dist has assets.
shell: bash
working-directory: frontend
run: ./build.sh

- name: Setup MSVC environment
# MuPDF static libs shipped by gen2brain/go-fitz are built with MSVC,
# so cgo must use cl.exe to satisfy MSVC-only intrinsics like
# __intrinsic_setjmpex / __memcpy_chk.
uses: ilammy/msvc-dev-cmd@v1
with:
arch: x64

- name: Download Go dependencies
run: go mod download

- name: Setup go-fitz directories
shell: bash
run: |
GOMOD=$(go env GOMODCACHE)
cp -r "$GOMOD/github.com/gen2brain/go-fitz@v1.24.15/include" go-fitz-include
cp -r "$GOMOD/github.com/gen2brain/go-fitz@v1.24.15/libs" go-fitz-libs

- name: Vet
run: go vet ./...

- name: Build
shell: bash
env:
CGO_ENABLED: "1"
CGO_CFLAGS: "-I${{ github.workspace }}/go-fitz-include"
CGO_LDFLAGS: "-L${{ github.workspace }}/go-fitz-libs -lmupdf_windows_amd64 -lmupdfthird_windows_amd64"
run: go build -tags "desktop,production" -ldflags "-H windowsgui" -o lankir.exe .

- name: Verify artifact
shell: bash
run: |
test -f lankir.exe
file lankir.exe || true
ls -lh lankir.exe

- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: lankir-windows-amd64
path: lankir.exe
retention-days: 14
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
/build/
/bin/

docs/plans/
CLAUDE.md
TODOs.md

# Scripts (temporary build files)
/scripts/

Expand Down
27 changes: 27 additions & 0 deletions Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,26 @@ tasks:
cmds:
- ./scripts/build-static.sh

build-windows:
desc: Cross-compile a Windows amd64 CLI binary (requires mingw-w64; not the full Wails app)
cmds:
- |
if ! command -v x86_64-w64-mingw32-gcc >/dev/null; then
echo "error: mingw-w64 not installed (apt install gcc-mingw-w64-x86-64)"
exit 1
fi
- cd {{.FRONTEND_DIR}} && ./build.sh
- mkdir -p {{.BUILD_DIR}}
- |
CGO_ENABLED=1 \
CC=x86_64-w64-mingw32-gcc \
CXX=x86_64-w64-mingw32-g++ \
GOOS=windows GOARCH=amd64 \
CGO_CFLAGS="-I${PWD}/go-fitz-include" \
CGO_LDFLAGS="-L${PWD}/go-fitz-libs -lmupdf_windows_amd64 -lmupdfthird_windows_amd64" \
go build -tags "desktop,production" -ldflags "-H windowsgui" -o {{.BUILD_DIR}}/{{.BINARY_NAME}}.exe .
- echo "✓ Built {{.BUILD_DIR}}/{{.BINARY_NAME}}.exe"

appimage:
desc: Build a truly portable AppImage (runs on any Linux distro)
cmds:
Expand Down Expand Up @@ -224,6 +244,13 @@ tasks:
- echo "Running linters..."
- golangci-lint run ./...

vet-cross:
desc: Run go vet for linux, windows and darwin to catch build-tag mistakes
cmds:
- echo "vet linux..." && GOOS=linux go vet ./...
- echo "vet windows..." && GOOS=windows go vet ./...
- echo "vet darwin..." && GOOS=darwin go vet ./...

deps:
desc: Download and verify dependencies
cmds:
Expand Down
5 changes: 5 additions & 0 deletions console_other.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
//go:build !windows

package main

func attachParentConsole() {}
28 changes: 28 additions & 0 deletions console_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//go:build windows

package main

import (
"os"
"syscall"
)

// attachParentConsole reattaches stdout/stderr to the parent console on
// Windows. The binary is built with -H windowsgui so double-clicking does not
// flash a console window, but CLI invocation from cmd/PowerShell still needs
// stdout to land somewhere visible. AttachConsole returns 0 if there is no
// parent console (e.g. launched from Explorer) — that is the GUI path and we
// silently skip rebinding.
func attachParentConsole() {
const attachParentProcess = ^uintptr(0) // -1, ATTACH_PARENT_PROCESS
kernel32 := syscall.NewLazyDLL("kernel32.dll")
if r1, _, _ := kernel32.NewProc("AttachConsole").Call(attachParentProcess); r1 == 0 {
return
}
if h, err := syscall.GetStdHandle(syscall.STD_OUTPUT_HANDLE); err == nil && h != 0 {
os.Stdout = os.NewFile(uintptr(h), "stdout")
}
if h, err := syscall.GetStdHandle(syscall.STD_ERROR_HANDLE); err == nil && h != 0 {
os.Stderr = os.NewFile(uintptr(h), "stderr")
}
}
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ toolchain go1.24.10
require (
github.com/digitorus/pdfsign v0.0.0-20250819064552-5f74f69dda1d
github.com/gen2brain/go-fitz v1.24.15
github.com/github/smimesign v0.2.0
github.com/google/uuid v1.6.0
github.com/miekg/pkcs11 v1.1.1
github.com/spf13/cobra v1.10.1
Expand Down Expand Up @@ -51,3 +52,5 @@ require (
golang.org/x/sys v0.38.0 // indirect
golang.org/x/text v0.30.0 // indirect
)

replace github.com/github/smimesign => github.com/nikwo/smimesign-fork v0.0.0-20221130091550-0956e442b5b6
15 changes: 15 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=
github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
github.com/certifi/gocertifi v0.0.0-20180118203423-deb3ae2ef261/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/digitorus/pdf v0.1.2 h1:RjYEJNbiV6Kcn8QzRi6pwHuOaSieUUrg4EZo4b7KuIQ=
Expand Down Expand Up @@ -56,8 +58,12 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU=
github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
github.com/nikwo/smimesign-fork v0.0.0-20221130091550-0956e442b5b6 h1:3IrbV7NPAWO10w8u1j6our8+7rSblbGCsXOZ7oCvEYQ=
github.com/nikwo/smimesign-fork v0.0.0-20221130091550-0956e442b5b6/go.mod h1:iZiiwNT4HbtGRVqCQu7uJPEZCuEE5sfSSttcnePkDl4=
github.com/pborman/getopt v0.0.0-20180811024354-2b5b3bfb099b/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
Expand All @@ -72,6 +78,8 @@ github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tkrajina/go-reflector v0.5.8 h1:yPADHrwmUbMq4RGEyaOUpz2H90sRsETNVpjzo3DLVQQ=
Expand All @@ -86,13 +94,18 @@ github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhw
github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o=
github.com/wailsapp/wails/v2 v2.10.2 h1:29U+c5PI4K4hbx8yFbFvwpCuvqK9VgNv8WGobIlKlXk=
github.com/wailsapp/wails/v2 v2.10.2/go.mod h1:XuN4IUOPpzBrHUkEd7sCU5ln4T/p1wQedfxP7fKik+4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
golang.org/x/image v0.32.0 h1:6lZQWq75h7L5IWNk0r+SCpUJ6tUVd3v4ZHnbRKLkUDQ=
golang.org/x/image v0.32.0/go.mod h1:/R37rrQmKXtO6tYXAjtDLwQgFLHmhW+V6ayXlxzP2Pc=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.45.0 h1:RLBg5JKixCy82FtLJpeNlVM0nrSqpCRYzVU1n8kj0tM=
golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
Expand All @@ -102,10 +115,12 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Expand Down
10 changes: 7 additions & 3 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,16 @@ type Service struct {

// NewService creates a config service using the default config directory.
func NewService() (*Service, error) {
homeDir, err := os.UserHomeDir()
// Use os.UserConfigDir() for cross-platform config directory
// Linux: ~/.config/lankir
// Windows: %APPDATA%\lankir (C:\Users\<user>\AppData\Roaming\lankir)
// macOS: ~/Library/Application Support/lankir
configDir, err := os.UserConfigDir()
if err != nil {
return nil, fmt.Errorf("failed to get user home directory: %w", err)
return nil, fmt.Errorf("failed to get user config directory: %w", err)
}

configDir := filepath.Join(homeDir, ".config", "lankir")
configDir = filepath.Join(configDir, "lankir")
return NewServiceWithDir(configDir)
}

Expand Down
44 changes: 8 additions & 36 deletions internal/pdf/render_annots.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,42 +9,11 @@ package pdf
/*
#cgo CFLAGS: -I${SRCDIR}/../../go-fitz-include
#cgo linux,amd64 LDFLAGS: -L${SRCDIR}/../../go-fitz-libs -lmupdf_linux_amd64 -lmupdfthird_linux_amd64 -lm
#cgo windows,amd64 LDFLAGS: -L${SRCDIR}/../../go-fitz-libs -lmupdf_windows_amd64 -lmupdfthird_windows_amd64
#include <mupdf/fitz.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

static int mupdf_warnings_suppressed = 0;
static int saved_stderr = -1;

// suppress_mupdf_warnings redirects stderr to /dev/null to silence MuPDF warnings
void suppress_mupdf_warnings() {
if (mupdf_warnings_suppressed) return;

// Save original stderr
saved_stderr = dup(STDERR_FILENO);

// Redirect stderr to /dev/null
int devnull = open("/dev/null", O_WRONLY);
if (devnull != -1) {
dup2(devnull, STDERR_FILENO);
close(devnull);
mupdf_warnings_suppressed = 1;
}
}

// restore_mupdf_warnings restores stderr to show MuPDF warnings
void restore_mupdf_warnings() {
if (!mupdf_warnings_suppressed || saved_stderr == -1) return;

// Restore original stderr
dup2(saved_stderr, STDERR_FILENO);
close(saved_stderr);
saved_stderr = -1;
mupdf_warnings_suppressed = 0;
}

// RenderResult contains the result of rendering
typedef struct {
Expand Down Expand Up @@ -190,14 +159,17 @@ import (
"unsafe"
)

// SuppressMuPDFWarnings redirects MuPDF warnings to /dev/null
// SuppressMuPDFWarnings redirects MuPDF warnings to the platform null device.
// Not safe for concurrent use; the caller must serialize Suppress / Restore
// calls and pair each Suppress with exactly one Restore.
func SuppressMuPDFWarnings() {
C.suppress_mupdf_warnings()
suppressMuPDFWarnings()
}

// RestoreMuPDFWarnings restores MuPDF warnings output
// RestoreMuPDFWarnings restores stderr to its original target. Safe to call
// when no Suppress is active (no-op).
func RestoreMuPDFWarnings() {
C.restore_mupdf_warnings()
restoreMuPDFWarnings()
}

// renderPageWithAnnotations renders a PDF page including all annotations and signature widgets
Expand Down
37 changes: 37 additions & 0 deletions internal/pdf/render_suppress_posix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//go:build (linux || darwin) && cgo

package pdf

/*
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>

static int saved_stderr_fd = -1;

void suppress_stderr(void) {
fflush(stderr);
if (saved_stderr_fd != -1) return; // already suppressed
int devnull = open("/dev/null", O_WRONLY);
if (devnull == -1) return;
saved_stderr_fd = dup(2); // save original
if (saved_stderr_fd == -1) {
close(devnull);
return;
}
dup2(devnull, 2);
close(devnull);
}

void restore_stderr(void) {
fflush(stderr);
if (saved_stderr_fd == -1) return; // nothing to restore
dup2(saved_stderr_fd, 2);
close(saved_stderr_fd);
saved_stderr_fd = -1;
}
*/
import "C"

func suppressMuPDFWarnings() { C.suppress_stderr() }
func restoreMuPDFWarnings() { C.restore_stderr() }
Loading
Loading