The tray and AirDrop helper now live in Verae NetPrint.app so notifications can start, /api/stamp uses the same client as Print-Job and refuses mock receipts, and the default printer name includes the hostname with an mDNS rename when that name is taken. Co-authored-by: George Lambert <marchon@gmail.com>
216 lines
4.6 KiB
Go
216 lines
4.6 KiB
Go
//go:build darwin
|
|
|
|
package netprint
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"runtime"
|
|
"sync"
|
|
"unsafe"
|
|
|
|
"github.com/ebitengine/purego"
|
|
)
|
|
|
|
// darwinDNS registers with mDNSResponder through DNSServiceRegister.
|
|
// Subtypes are part of the regtype. The call does not use cgo; libSystem is
|
|
// loaded when the process runs on a Mac. A name conflict is reported on
|
|
// conflicts so the advertiser can register "Name (2)" instead.
|
|
type darwinDNS struct {
|
|
mu sync.Mutex
|
|
refs []uintptr
|
|
stop []func()
|
|
conflicts chan struct{}
|
|
}
|
|
|
|
func newDarwinDNS() dnsSDClient {
|
|
return &darwinDNS{conflicts: make(chan struct{}, 1)}
|
|
}
|
|
|
|
func (d *darwinDNS) Conflicts() <-chan struct{} { return d.conflicts }
|
|
|
|
// regState is the DNSServiceRegister context. It stays pinned until the
|
|
// registration is deallocated so the callback can read it.
|
|
type regState struct {
|
|
name string
|
|
conflicts chan struct{}
|
|
}
|
|
|
|
var (
|
|
dnsOnce sync.Once
|
|
dnsErr error
|
|
|
|
dnsRegister func(sdRef *uintptr, flags uint32, ifIndex uint32, name, regtype, domain, host *byte, port uint16, txtLen uint16, txtRecord unsafe.Pointer, callback uintptr, context unsafe.Pointer) int32
|
|
dnsProcess func(sdRef uintptr) int32
|
|
dnsDeallocate func(sdRef uintptr)
|
|
|
|
dnsReplyOnce sync.Once
|
|
dnsReplyPtr uintptr
|
|
|
|
regMu sync.Mutex
|
|
regStates = map[uintptr]*regState{}
|
|
)
|
|
|
|
func loadDNSService() error {
|
|
dnsOnce.Do(func() {
|
|
lib, err := purego.Dlopen("/usr/lib/libSystem.B.dylib", 1)
|
|
if err != nil {
|
|
dnsErr = err
|
|
return
|
|
}
|
|
purego.RegisterLibFunc(&dnsRegister, lib, "DNSServiceRegister")
|
|
purego.RegisterLibFunc(&dnsProcess, lib, "DNSServiceProcessResult")
|
|
purego.RegisterLibFunc(&dnsDeallocate, lib, "DNSServiceRefDeallocate")
|
|
})
|
|
return dnsErr
|
|
}
|
|
|
|
func (d *darwinDNS) Register(inst dnsSDInstance) error {
|
|
if err := loadDNSService(); err != nil {
|
|
return fmt.Errorf("DNSServiceRegister: %w", err)
|
|
}
|
|
name := cString(inst.Name)
|
|
regtype := cString(inst.RegType)
|
|
domain := cString("local")
|
|
var host *byte
|
|
hostBuf := []byte{}
|
|
if inst.Host != "" {
|
|
hostBuf = append([]byte(inst.Host), 0)
|
|
host = &hostBuf[0]
|
|
}
|
|
txt := encodeDNSTXT(inst.TXT)
|
|
var txtPtr unsafe.Pointer
|
|
if len(txt) > 0 {
|
|
txtPtr = unsafe.Pointer(&txt[0])
|
|
}
|
|
st := ®State{name: inst.Name, conflicts: d.conflicts}
|
|
var pin runtime.Pinner
|
|
pin.Pin(st)
|
|
key := uintptr(unsafe.Pointer(st))
|
|
regMu.Lock()
|
|
regStates[key] = st
|
|
regMu.Unlock()
|
|
flags := uint32(0)
|
|
var cb uintptr
|
|
var ctx unsafe.Pointer
|
|
keep := false
|
|
if reply := dnsReplyCallback(); reply != 0 {
|
|
flags = dnsFlagNoAutoRename
|
|
cb = reply
|
|
ctx = unsafe.Pointer(st)
|
|
keep = true
|
|
}
|
|
release := func() {
|
|
regMu.Lock()
|
|
delete(regStates, key)
|
|
regMu.Unlock()
|
|
pin.Unpin()
|
|
}
|
|
if !keep {
|
|
release()
|
|
}
|
|
var ref uintptr
|
|
rc := dnsRegister(&ref, flags, 0, name, regtype, domain, host, dnsPort(inst.Port), uint16(len(txt)), txtPtr, cb, ctx)
|
|
runtime.KeepAlive(name)
|
|
runtime.KeepAlive(regtype)
|
|
runtime.KeepAlive(domain)
|
|
runtime.KeepAlive(hostBuf)
|
|
runtime.KeepAlive(txt)
|
|
runtime.KeepAlive(st)
|
|
if rc != 0 {
|
|
if keep {
|
|
release()
|
|
}
|
|
if rc == dnsErrNameConflict {
|
|
return errNameConflict
|
|
}
|
|
return fmt.Errorf("DNSServiceRegister %s %s: code %d", inst.Name, inst.RegType, rc)
|
|
}
|
|
done := make(chan struct{})
|
|
go func() {
|
|
defer close(done)
|
|
for {
|
|
if err := dnsProcess(ref); err != 0 {
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
d.mu.Lock()
|
|
d.refs = append(d.refs, ref)
|
|
d.stop = append(d.stop, func() {
|
|
dnsDeallocate(ref)
|
|
<-done
|
|
if keep {
|
|
release()
|
|
}
|
|
})
|
|
d.mu.Unlock()
|
|
log.Printf("netprint: DNSServiceRegister %s %s port %d", inst.Name, inst.RegType, inst.Port)
|
|
return nil
|
|
}
|
|
|
|
func dnsReplyCallback() uintptr {
|
|
dnsReplyOnce.Do(func() {
|
|
defer func() {
|
|
if recover() != nil {
|
|
dnsReplyPtr = 0
|
|
}
|
|
}()
|
|
dnsReplyPtr = purego.NewCallback(dnsRegisterReply)
|
|
})
|
|
return dnsReplyPtr
|
|
}
|
|
|
|
func dnsRegisterReply(sdRef, flags, errCode, namePtr, regtype, domain, context uintptr) uintptr {
|
|
if context == 0 {
|
|
return 0
|
|
}
|
|
regMu.Lock()
|
|
st := regStates[context]
|
|
regMu.Unlock()
|
|
if st == nil || st.conflicts == nil || int32(uint32(errCode)) != dnsErrNameConflict {
|
|
return 0
|
|
}
|
|
select {
|
|
case st.conflicts <- struct{}{}:
|
|
default:
|
|
}
|
|
runtime.KeepAlive(namePtr)
|
|
return 0
|
|
}
|
|
|
|
func (d *darwinDNS) Deregister(inst dnsSDInstance) error {
|
|
d.mu.Lock()
|
|
if len(d.stop) == 0 {
|
|
d.mu.Unlock()
|
|
return nil
|
|
}
|
|
stop := d.stop[0]
|
|
d.stop = d.stop[1:]
|
|
if len(d.refs) > 0 {
|
|
d.refs = d.refs[1:]
|
|
}
|
|
d.mu.Unlock()
|
|
stop()
|
|
return nil
|
|
}
|
|
|
|
func cString(s string) *byte {
|
|
b := append([]byte(s), 0)
|
|
return &b[0]
|
|
}
|
|
|
|
func encodeDNSTXT(txt []string) []byte {
|
|
var b []byte
|
|
for _, s := range txt {
|
|
if s == "" {
|
|
continue
|
|
}
|
|
if len(s) > 255 {
|
|
s = s[:255]
|
|
}
|
|
b = append(b, byte(len(s)))
|
|
b = append(b, s...)
|
|
}
|
|
return b
|
|
}
|