M block/block.go => block/block.go +7 -4
@@ 2,6 2,7 @@
package block
import (
+ "context"
"encoding/json"
"fmt"
"os"
@@ 16,7 17,7 @@ import (
"git.sr.ht/~jcc/swaybar-commander/proto"
)
-var execCommand = exec.Command // allow mocking in tests
+var execCommandContext = exec.CommandContext // allow mocking in tests
// Block represents a single block in the swaybar.
type Block struct {
@@ 41,7 42,7 @@ func New(conf config.Block) Block {
// Run runs a Block indefinitely, sending updated swaybar protocol blocks on its
// UpdateChan whenever there is a change.
-func (b *Block) Run(updateRequestSig syscall.Signal) {
+func (b *Block) Run(ctx context.Context, updateRequestSig syscall.Signal) {
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, updateRequestSig)
@@ 53,7 54,7 @@ func (b *Block) Run(updateRequestSig syscall.Signal) {
strconv.Itoa(int(updateRequestSig)),
)
- updateRequesterCmd := execCommand(name, args...)
+ updateRequesterCmd := execCommandContext(ctx, name, args...)
err := updateRequesterCmd.Start()
if err != nil {
fmt.Fprintf(os.Stderr, "failed to start update requester for block %v: %v\n", *b, err)
@@ 67,7 68,7 @@ func (b *Block) Run(updateRequestSig syscall.Signal) {
lastOutput := ""
for {
- cmd := execCommand(b.cmd[0], b.cmd[1:]...)
+ cmd := execCommandContext(ctx, b.cmd[0], b.cmd[1:]...)
outBytes, err := cmd.Output()
if err != nil {
fmt.Fprintf(os.Stderr, "error running command %v: %v", b.cmd, err)
@@ 92,6 93,8 @@ func (b *Block) Run(updateRequestSig syscall.Signal) {
}
select {
+ case <-ctx.Done():
+ return
case <-b.ticker.C:
case <-sigChan:
b.ticker.Reset(b.pollFreq)
M block/block_test.go => block/block_test.go +10 -7
@@ 1,6 1,7 @@
package block
import (
+ "context"
"fmt"
"os"
"os/exec"
@@ 105,7 106,7 @@ func TestRun(t *testing.T) {
})
block.pollFreq = 25 * time.Millisecond
- execCommand = makeFakeExecCommand(
+ execCommandContext = makeFakeExecCommand(
t,
c.stdouts,
c.exitStatuses,
@@ 115,7 116,8 @@ func TestRun(t *testing.T) {
actual := []proto.Block{}
- go block.Run(syscall.Signal(sigrtmin+1))
+ ctx := context.Background()
+ go block.Run(ctx, syscall.Signal(sigrtmin+1))
for i := 0; i < len(c.expected); i++ {
actual = append(actual, <-block.UpdateChan)
@@ 135,7 137,7 @@ func TestRunUpdateRequest(t *testing.T) {
PollSecs: 100,
})
- execCommand = makeFakeExecCommand(
+ execCommandContext = makeFakeExecCommand(
t,
[]string{"a", "b"},
[]int{0, 0},
@@ 161,7 163,8 @@ func TestRunUpdateRequest(t *testing.T) {
}
}
- go block.Run(sig)
+ ctx := context.Background()
+ go block.Run(ctx, sig)
first := <-block.UpdateChan
if first.FullText != "a" {
@@ 193,10 196,10 @@ func makeFakeExecCommand(
exitStatuses []int,
expectedName string,
expectedArgs ...string,
-) func(string, ...string) *exec.Cmd {
+) func(context.Context, string, ...string) *exec.Cmd {
i := 0
- return func(name string, args ...string) *exec.Cmd {
+ return func(ctx context.Context, name string, args ...string) *exec.Cmd {
if name != expectedName || !reflect.DeepEqual(args, expectedArgs) {
t.Fatalf("incorrect exec.Command args: want %v, %v; got %v, %v", expectedName, expectedArgs, name, args)
return nil
@@ 204,7 207,7 @@ func makeFakeExecCommand(
cs := []string{"-test.run=TestHelperProcess", "--", name}
cs = append(cs, args...)
- cmd := exec.Command(os.Args[0], cs...)
+ cmd := exec.CommandContext(ctx, os.Args[0], cs...)
if i >= len(stdouts) || i >= len(exitStatuses) {
<-make(chan int) // block forever and wait for test execution to end
M commander.go => commander.go +11 -1
@@ 21,6 21,16 @@ func main() {
return
}
+ if len(os.Args) == 2 && os.Args[1] == "reload" {
+ err := commands.Reload()
+ if err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(1)
+ }
+
+ return
+ }
+
if len(os.Args) >= 3 && os.Args[1] == "update" {
var instance string
if len(os.Args) == 4 {
@@ 36,6 46,6 @@ func main() {
return
}
- fmt.Fprintf(os.Stderr, "USAGE: %s [update <index>]|[update <name> [<instance>]]\n", os.Args[0])
+ fmt.Fprintf(os.Stderr, "USAGE: %s [reload]|[update <index>]|[update <name> [<instance>]]\n", os.Args[0])
os.Exit(1)
}
M commands/commander.go => commands/commander.go +37 -4
@@ 2,7 2,10 @@
package commands
import (
+ "context"
"fmt"
+ "os"
+ "os/signal"
"sync"
"syscall"
"time"
@@ 14,9 17,33 @@ import (
)
const sigrtmin = 34
+const reloadSignal = syscall.SIGUSR1
// Commander runs the swaybar-commander.
func Commander() error {
+ sigChan := make(chan os.Signal, 1)
+ signal.Notify(sigChan, reloadSignal)
+ firstRun := true
+
+ for {
+ ctx, cancel := context.WithCancel(context.Background())
+
+ go func() {
+ <-sigChan
+ fmt.Fprintln(os.Stderr, "reloading")
+ cancel()
+ }()
+
+ err := runCommander(ctx, firstRun)
+ if err != nil {
+ return err
+ }
+
+ firstRun = false
+ }
+}
+
+func runCommander(ctx context.Context, init bool) error {
conf, err := config.Load()
if err != nil {
return fmt.Errorf("failed to load config: %v", err)
@@ 37,7 64,9 @@ func Commander() error {
}
}
- proto.Init()
+ if init {
+ proto.Init()
+ }
debounce := debounce.New(100 * time.Millisecond)
wg := sync.WaitGroup{}
@@ 47,11 76,15 @@ func Commander() error {
go func(i int) {
defer wg.Done()
- go blocks[i].Run(blockRuntimes[i].Signal)
+ go blocks[i].Run(ctx, blockRuntimes[i].Signal)
for {
- latestPBlocks[i] = <-blocks[i].UpdateChan
- debounce(func() { proto.Send(latestPBlocks) })
+ select {
+ case <-ctx.Done():
+ return
+ case latestPBlocks[i] = <-blocks[i].UpdateChan:
+ debounce(func() { proto.Send(latestPBlocks) })
+ }
}
}(i)
}
A commands/reload.go => commands/reload.go +36 -0
@@ 0,0 1,36 @@
+package commands
+
+import (
+ "fmt"
+)
+
+// Reload requests that all running instances of swaybar-commander reload the
+// config file and restart.
+func Reload() error {
+ runtimePaths := getRuntimeFilePaths()
+ if len(runtimePaths) == 0 {
+ return fmt.Errorf("no instances of swaybar-commander appear to be running")
+ }
+
+ var signalled bool
+
+ for _, path := range runtimePaths {
+ process, _, err := readRuntimeFile(path)
+ if err != nil {
+ return fmt.Errorf("failed to read runtime file %s: %v", path, err)
+ }
+
+ if process == nil {
+ continue
+ }
+
+ process.Signal(reloadSignal)
+ signalled = true
+ }
+
+ if !signalled {
+ return fmt.Errorf("no running instance found")
+ }
+
+ return nil
+}
M swaybar-commander.1.scd => swaybar-commander.1.scd +4 -0
@@ 8,6 8,7 @@ swaybar-commander - swaybar status command for *sway*(1) using the
# SYNOPSIS
*swaybar-commander*++
+*swaybar-commander* reload++
*swaybar-commander* update <index>++
*swaybar-commander* update <name> [<instance>]
@@ 46,6 47,9 @@ $XDG_CONFIG_HOME/swaybar-commander/config.toml. $XDG_CONFIG_HOME defaults to
The config file contains an optional *Defaults* table as well as an array of
*Blocks*.
+To make swaybar-commander reload its configuration, use *swaybar-commander
+reload* or send _SIGUSR1_ to the *swaybar-commander* process.
+
## DEFAULTS
The optional *Defaults* table contains default values for the body properties