diff --git a/WINDOWS.md b/WINDOWS.md new file mode 100644 index 0000000..ad8bbed --- /dev/null +++ b/WINDOWS.md @@ -0,0 +1,22 @@ +# Windows support + +Barnard can be built for Windows with a native CGO toolchain. Audio support uses native OpenAL Soft, libopus, libopusfile/libogg, and RNNoise; `ffmpeg.exe` must also be on `PATH` for recording and file playback. + +The easiest supported setup is MSYS2 MinGW-w64. Install the matching architecture's Go toolchain and development packages for OpenAL Soft, opus, opusfile, libogg, RNNoise, FFmpeg, and pkg-config. Build from its MinGW shell: + +``` +go build -o barnard.exe . +``` + +For cross-compilation from Linux, use the matching MinGW compiler and its pkg-config environment, for example: + +``` +GOOS=windows GOARCH=amd64 CGO_ENABLED=1 \ +CC=x86_64-w64-mingw32-gcc \ +PKG_CONFIG=x86_64-w64-mingw32-pkg-config \ +go build -o barnard.exe . +``` + +The dependency include and library paths must point to Windows builds, not host Linux libraries. + +The legacy `--fifo` command control is unavailable on Windows because it uses POSIX filesystem FIFOs. Windows defaults to no notification command; an explicitly configured notification command is run by `cmd.exe /C`. diff --git a/config/notification_unix.go b/config/notification_unix.go new file mode 100644 index 0000000..58a121c --- /dev/null +++ b/config/notification_unix.go @@ -0,0 +1,7 @@ +//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris + +package config + +func defaultNotifyCommand() string { + return "/usr/share/barnard/barnard-sound.sh \"%event\" \"%who\" \"%what\"" +} diff --git a/config/notification_windows.go b/config/notification_windows.go new file mode 100644 index 0000000..fe62cb3 --- /dev/null +++ b/config/notification_windows.go @@ -0,0 +1,9 @@ +//go:build windows + +package config + +// Windows installations do not ship the POSIX notification helper. Users can +// configure a cmd.exe-compatible command explicitly if they want notifications. +func defaultNotifyCommand() string { + return "" +} diff --git a/config/user_config.go b/config/user_config.go index 4e8f0d8..b46aa92 100644 --- a/config/user_config.go +++ b/config/user_config.go @@ -148,7 +148,7 @@ func (c *Config) LoadConfig() { jc.Username = &username } if c.config.NotifyCommand == nil { - ncmd := string("/usr/share/barnard/barnard-sound.sh \"%event\" \"%who\" \"%what\"") + ncmd := defaultNotifyCommand() jc.NotifyCommand = &ncmd } if c.config.NoiseSuppressionEnabled == nil { diff --git a/fifo_unix.go b/fifo_unix.go new file mode 100644 index 0000000..4c186e0 --- /dev/null +++ b/fifo_unix.go @@ -0,0 +1,55 @@ +//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris + +package main + +import ( + "bufio" + "fmt" + "io" + "os" + "strings" + "syscall" +) + +func setup_fifo(fn string) (chan string, error) { + commands := make(chan string) + if fn == "" { + return commands, nil + } + if info, err := os.Lstat(fn); err == nil { + if info.Mode()&os.ModeNamedPipe == 0 { + return commands, fmt.Errorf("FIFO path %q already exists and is not a FIFO", fn) + } + if err := os.Remove(fn); err != nil { + return commands, err + } + } else if !os.IsNotExist(err) { + return commands, err + } + if err := syscall.Mkfifo(fn, 0600); err != nil { + return commands, err + } + file, err := os.OpenFile(fn, os.O_RDWR, os.ModeNamedPipe) + if err != nil { + return commands, err + } + go readFIFO(file, commands) + return commands, nil +} + +// readFIFO forwards complete commands and terminates on EOF or any read +// failure. Retrying an unrecoverable FIFO error used to spin a CPU forever. +func readFIFO(fh io.ReadCloser, out chan<- string) { + defer fh.Close() + defer close(out) + reader := bufio.NewReader(fh) + for { + line, err := reader.ReadBytes('\n') + if len(line) != 0 { + out <- strings.TrimSpace(string(line)) + } + if err != nil { + return + } + } +} diff --git a/fifo_windows.go b/fifo_windows.go new file mode 100644 index 0000000..0c7568f --- /dev/null +++ b/fifo_windows.go @@ -0,0 +1,19 @@ +//go:build windows + +package main + +import ( + "fmt" + "strings" +) + +// Windows named pipes require the Win32 API rather than POSIX filesystem +// FIFOs. Disable the legacy FIFO option until it is replaced with a named-pipe +// listener; ordinary UI and command-line operation remain available. +func setup_fifo(fn string) (chan string, error) { + commands := make(chan string) + if strings.TrimSpace(fn) == "" { + return commands, nil + } + return commands, fmt.Errorf("FIFO control is not supported on Windows") +} diff --git a/main.go b/main.go index 4d28f67..a9cbe96 100644 --- a/main.go +++ b/main.go @@ -3,18 +3,14 @@ package main import _ "net/http/pprof" import ( "al.essio.dev/pkg/shellescape" - "bufio" "crypto/tls" "flag" "fmt" - "io" "log" "net" "net/http" "os" - "os/exec" "strings" - "syscall" barnlog "git.stormux.org/storm/barnard/log" @@ -50,26 +46,16 @@ func do_list_devices() { const notificationQueueSize = 32 -func setup_notify_runner(notify_command string) chan []string { - t := make(chan []string, notificationQueueSize) - var do_nothing = false - var err error - if err != nil { - } - if notify_command == "" { - do_nothing = true - } - go func(events chan []string, cmd_template string, dummy bool) { +func setup_notify_runner(notifyCommand string) chan []string { + events := make(chan []string, notificationQueueSize) + go func() { for event := range events { - if !dummy { - cmd := "/bin/sh" - args := []string{"-c", expandNotification(cmd_template, event)} - x := exec.Command(cmd, args...) - x.Run() + if notifyCommand != "" { + runNotification(expandNotification(notifyCommand, event)) } } - }(t, notify_command, do_nothing) - return t + }() + return events } // expandNotification replaces placeholders in one pass so text supplied for @@ -82,50 +68,6 @@ func expandNotification(template string, event []string) string { ).Replace(template) } -func setup_fifo(fn string) (chan string, error) { - t := make(chan string) - if fn == "" { - return t, nil - } - if info, err := os.Lstat(fn); err == nil { - if info.Mode()&os.ModeNamedPipe == 0 { - return t, fmt.Errorf("FIFO path %q already exists and is not a FIFO", fn) - } - if err := os.Remove(fn); err != nil { - return t, err - } - } else if !os.IsNotExist(err) { - return t, err - } - err := syscall.Mkfifo(fn, 0600) - if err != nil { - return t, err - } - file, err := os.OpenFile(fn, os.O_RDWR, os.ModeNamedPipe) - if err != nil { - return t, err - } - go readFIFO(file, t) - return t, nil -} - -// readFIFO forwards complete commands and terminates on EOF or any read -// failure. Retrying an unrecoverable FIFO error used to spin a CPU forever. -func readFIFO(fh io.ReadCloser, out chan<- string) { - defer fh.Close() - defer close(out) - reader := bufio.NewReader(fh) - for { - line, err := reader.ReadBytes('\n') - if len(line) != 0 { - out <- strings.TrimSpace(string(line)) - } - if err != nil { - return - } - } -} - func main() { // Command line flags server := flag.String("server", "localhost:64738", "the server to connect to") diff --git a/notification_unix.go b/notification_unix.go new file mode 100644 index 0000000..946395c --- /dev/null +++ b/notification_unix.go @@ -0,0 +1,9 @@ +//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris + +package main + +import "os/exec" + +func runNotification(command string) { + _ = exec.Command("/bin/sh", "-c", command).Run() +} diff --git a/notification_windows.go b/notification_windows.go new file mode 100644 index 0000000..5c969e1 --- /dev/null +++ b/notification_windows.go @@ -0,0 +1,9 @@ +//go:build windows + +package main + +import "os/exec" + +func runNotification(command string) { + _ = exec.Command("cmd.exe", "/C", command).Run() +}