From 32d865e24e1d3858ff80ecbc32d74a2ac76284f5 Mon Sep 17 00:00:00 2001 From: eleksir Date: Mon, 19 May 2025 12:19:25 +0300 Subject: [PATCH] Implement cmdrun config parameter --- Makefile | 10 +- cmd/i3status-go/default-config.json | 72 ++++++++++++++ cmd/i3status-go/main.go | 56 +++++++++++ configs/i3status-go-example.json | 2 +- internal/lib/config.go | 148 ++++++++++++++++++++++++++-- internal/lib/spawn.go | 40 ++++++++ internal/lib/vpn.go | 2 +- 7 files changed, 315 insertions(+), 15 deletions(-) diff --git a/Makefile b/Makefile index df8d668..9188181 100644 --- a/Makefile +++ b/Makefile @@ -4,6 +4,7 @@ BUILDOPTS=-ldflags="-s -w" -a -gcflags=all=-l -trimpath -buildvcs=false BINARY=i3status-go TEST1=battery-test +TEST2=cmdrun-test ## Use calssic targets where first one is deafult target all: clean build @@ -16,15 +17,18 @@ battery-test: rm -rf ${TEST1} go build ${BUILDOPTS} -o ${TEST1} ./cmd/${TEST1} +cmdrun-test: + rm -rf ${TEST1} + go build ${BUILDOPTS} -o ${TEST2} ./cmd/${TEST2} + ## Remove binary with golang compiler' means clean: - rm -rf ${BINARY} ${TEST1} - + rm -rf ${BINARY} ${TEST1} ${TEST2} ## Misc target, for development purposes. Updates vendored libs, brutal way. upgrade: $(RM) -r vendor - go get -u -t -tool ./... + go get -u -t ./... go mod tidy go mod vendor diff --git a/cmd/i3status-go/default-config.json b/cmd/i3status-go/default-config.json index f180393..61d09f0 100644 --- a/cmd/i3status-go/default-config.json +++ b/cmd/i3status-go/default-config.json @@ -702,6 +702,78 @@ ] }, +// Prints stdout of command to panel. +"cmdrun" : { + "enabled": false, + + // Delay between two consecutive command batch runs. + "delay": 3, + + // command' binary. + "cmd": "/bin/bash", + + // Arguments for command binary. + "args": ["-c", "echo 42"], + + // If omitted set to default color defined up here. + "color": "#3e78fd", + + // If omitted set to default background color defined up here. + "background": "#000000", + + // If omitted set to default font defined up here. + "font": "Noto Sans", + + // ‘xx-small’, ‘x-small’, ‘small’, ‘medium’, ‘large’, ‘x-large’, ‘xx-large’, or one of relative sizes + // ‘smaller’ or ‘larger’. If omitted set to default font size defined up here. + "font_size": "medium", + + // Re-define separator parameters for each command block here. + "separator": { + "left": { + // Set to false if omitted. + "enabled": true, + + // If omitted set to default color defined for separator up here. + "color": "#3e78fd", + + // If omitted set to default background color defined for separator up here. + "background": "#000000", + + // If omitted set to default symbol defined for separator up here. + "symbol": "▶", + + // If omitted set to default font defined for separator up here. + "font": "Noto Sans", + + // ‘xx-small’, ‘x-small’, ‘small’, ‘medium’, ‘large’, ‘x-large’, ‘xx-large’, or one of relative sizes + // ‘smaller’ or ‘larger’. If omitted set to default font size defined for separator up here. + "font_size": "medium" + }, + + "right": { + // Set to false if omitted. + "enabled": true, + + // If omitted set to default color defined for separator up here. + "color": "#3e78fd", + + // If omitted set to default background color defined for separator up here. + "background": "#000000", + + // If omitted set to default symbol defined for separator up here. + "symbol": "◀", + + // If omitted set to default font defined for separator up here. + "font": "Noto Sans", + + // ‘xx-small’, ‘x-small’, ‘small’, ‘medium’, ‘large’, ‘x-large’, ‘xx-large’, or one of relative sizes + // ‘smaller’ or ‘larger’. If omitted set to default font size defined for separator up here. + "font_size": "medium" + } + } +} + // Whether to display Application Buttons. "app_buttons": { "enabled": true, diff --git a/cmd/i3status-go/main.go b/cmd/i3status-go/main.go index eb69c0f..aebc5f6 100644 --- a/cmd/i3status-go/main.go +++ b/cmd/i3status-go/main.go @@ -84,6 +84,14 @@ func main() { Conf.Clock.FontSize, ) + Conf.Values.RunCommandOutput = fmt.Sprintf( + "?", + Conf.CmdRun.Color, + Conf.CmdRun.Background, + Conf.CmdRun.Font, + Conf.CmdRun.FontSize, + ) + go Conf.Spawner() go Conf.ParseStdin() go Conf.CleanZombies() @@ -143,6 +151,10 @@ func main() { go Conf.UpdateVPNStatus() } + if Conf.CmdRun.Enabled { + go Conf.RunCommand() + } + /* I3bar documentation pretends that message protocol must be valid json. In practice, we only have to print valid header, empty json array and (potentially infinite) json lines (line that is valid json by itself) that is @@ -519,6 +531,50 @@ func main() { j = append(j, b) } + if Conf.CmdRun.Enabled { + var b lib.I3BarOutBlock + + b.Name = `runcommandoutput` + b.Color = Conf.CmdRun.Color + b.Background = Conf.CmdRun.Background + + if Conf.CmdRun.Separator.Left.Enabled { + b.FullText = fmt.Sprintf( + "%s", + Conf.CmdRun.Separator.Left.Color, + Conf.CmdRun.Separator.Left.Background, + Conf.CmdRun.Separator.Left.Font, + Conf.CmdRun.Separator.Left.FontSize, + Conf.CmdRun.Separator.Left.Symbol, + ) + } + + b.FullText += fmt.Sprintf( + "%s", + Conf.CmdRun.Color, + Conf.CmdRun.Background, + Conf.CmdRun.Font, + Conf.CmdRun.FontSize, + Conf.Values.RunCommandOutput, + ) + + if Conf.CmdRun.Separator.Right.Enabled { + b.FullText += fmt.Sprintf( + "%s", + Conf.CmdRun.Separator.Right.Color, + Conf.CmdRun.Separator.Right.Background, + Conf.CmdRun.Separator.Right.Font, + Conf.CmdRun.Separator.Right.FontSize, + Conf.CmdRun.Separator.Right.Symbol, + ) + } + + b.Markup = "pango" + b.Separator = false + + j = append(j, b) + } + if Conf.Clock.Enabled { var b lib.I3BarOutBlock diff --git a/configs/i3status-go-example.json b/configs/i3status-go-example.json index 297e1f1..15c7fc5 120000 --- a/configs/i3status-go-example.json +++ b/configs/i3status-go-example.json @@ -1 +1 @@ -/home/eleksir/git/i3status-go/cmd/i3status-go/default-config.json \ No newline at end of file +../cmd/i3status-go/default-config.json \ No newline at end of file diff --git a/internal/lib/config.go b/internal/lib/config.go index 0ae0851..6146f14 100644 --- a/internal/lib/config.go +++ b/internal/lib/config.go @@ -59,16 +59,17 @@ type Separator struct { type MyConfig struct { Values struct { - PrintOutput bool - CPUTemperature int64 - BatteryString string - ClockTime string - IfStatus string - VPNStatus string - Memory Mem - La string - PA *p.Client - SoundVolume string + PrintOutput bool + CPUTemperature int64 + BatteryString string + ClockTime string + IfStatus string + VPNStatus string + Memory Mem + La string + PA *p.Client + SoundVolume string + RunCommandOutput string } Channels struct { @@ -231,6 +232,18 @@ type MyConfig struct { } `json:"tasks,omitempty"` } `json:"cron,omitempty"` + CmdRun struct { + Enabled bool `json:"enabled,omitempty"` + Color string `json:"color,omitempty"` + Background string `json:"background,omitempty"` + Font string `json:"font,omitempty"` + FontSize string `json:"font_size,omitempty"` + Separator Separator `json:"separator,omitempty"` + Cmd string `json:"cmd,omitempty"` + Delay int `json:"delay,omitempty"` + Args []string `json:"args,omitempty"` + } + AppButtons struct { Enabled bool `json:"enabled,omitempty"` Separator Separator `json:"separator,omitempty"` @@ -1446,6 +1459,121 @@ func ReadConf(defaultConfig []byte) (*MyConfig, error) { } } + // sampleConfig.CmdRun.Enabled will false if not set in config + + if sampleConfig.CmdRun.Cmd == "" && sampleConfig.CmdRun.Enabled { + sampleConfig.CmdRun.Enabled = false + log.Print( + "Setting sampleConfig.CmdRun.Enabled to false because sampleConfig.CmdRun.Cmd is not set", + ) + } + + // sampleConfig.CmdRun.Args can be empty + + if sampleConfig.CmdRun.Color == "" { + sampleConfig.CmdRun.Color = sampleConfig.Color + } + + if sampleConfig.CmdRun.Background == "" { + sampleConfig.CmdRun.Background = sampleConfig.Background + } + + if sampleConfig.CmdRun.Font == "" { + sampleConfig.CmdRun.Font = sampleConfig.Font + } + + if sampleConfig.CmdRun.FontSize == "" { + sampleConfig.CmdRun.FontSize = sampleConfig.FontSize + } + + if sampleConfig.CmdRun.Separator.Left.Color == "" { + sampleConfig.CmdRun.Separator.Left.Color = sampleConfig.Separator.Left.Color + } + + if sampleConfig.CmdRun.Separator.Left.Background == "" { + sampleConfig.CmdRun.Separator.Left.Background = sampleConfig.Separator.Left.Background + } + + if sampleConfig.CmdRun.Separator.Left.Symbol == "" { + sampleConfig.CmdRun.Separator.Left.Symbol = sampleConfig.Separator.Left.Symbol + } + + if sampleConfig.CmdRun.Separator.Left.Font == "" { + sampleConfig.CmdRun.Separator.Left.Font = sampleConfig.Separator.Left.Font + } + + if sampleConfig.CmdRun.Separator.Left.FontSize == "" { + sampleConfig.CmdRun.Separator.Left.FontSize = sampleConfig.Separator.Left.FontSize + } else { + matched, err := regexp.MatchString( + `^(xx-small|x-small|small|medium|large|x-large|xx-large|smaller|larger)$`, + sampleConfig.CmdRun.Separator.Left.FontSize, + ) + + if err != nil { + log.Printf( + "Unable to set sampleConfig.CmdRun.Separator.Left.FontSize: %s, fallback to %s", + err, + sampleConfig.Separator.Left.FontSize, + ) + + sampleConfig.CmdRun.Separator.Left.FontSize = sampleConfig.Separator.Left.FontSize + } + + if !matched { + log.Printf( + "Unable to set sampleConfig.CmdRun.Separator.Left.FontSize, fallback to %s", + sampleConfig.Separator.Left.FontSize, + ) + + sampleConfig.CmdRun.Separator.Left.FontSize = sampleConfig.Separator.Left.FontSize + } + } + + if sampleConfig.CmdRun.Separator.Right.Color == "" { + sampleConfig.CmdRun.Separator.Right.Color = sampleConfig.Separator.Right.Color + } + + if sampleConfig.CmdRun.Separator.Right.Background == "" { + sampleConfig.CmdRun.Separator.Right.Background = sampleConfig.Separator.Right.Background + } + + if sampleConfig.CmdRun.Separator.Right.Symbol == "" { + sampleConfig.CmdRun.Separator.Right.Symbol = sampleConfig.Separator.Right.Symbol + } + + if sampleConfig.CmdRun.Separator.Right.Font == "" { + sampleConfig.CmdRun.Separator.Right.Font = sampleConfig.Separator.Right.Font + } + + if sampleConfig.CmdRun.Separator.Right.FontSize == "" { + sampleConfig.CmdRun.Separator.Right.FontSize = sampleConfig.Separator.Right.FontSize + } else { + matched, err := regexp.MatchString( + `^(xx-small|x-small|small|medium|large|x-large|xx-large|smaller|larger)$`, + sampleConfig.CmdRun.Separator.Right.FontSize, + ) + + if err != nil { + log.Printf( + "Unable to set sampleConfig.CmdRun.Separator.Right.FontSize: %s, fallback to %s", + err, + sampleConfig.Separator.Right.FontSize, + ) + + sampleConfig.CmdRun.Separator.Right.FontSize = sampleConfig.Separator.Right.FontSize + } + + if !matched { + log.Printf( + "Unable to set sampleConfig.CmdRun.Separator.Right.FontSize, fallback to %s", + sampleConfig.Separator.Right.FontSize, + ) + + sampleConfig.CmdRun.Separator.Right.FontSize = sampleConfig.Separator.Right.FontSize + } + } + // sampleConfig.AppButtons.Enabled will false if not set in config if sampleConfig.AppButtons.Separator.Left.Color == "" { diff --git a/internal/lib/spawn.go b/internal/lib/spawn.go index 80534a2..ebff62d 100644 --- a/internal/lib/spawn.go +++ b/internal/lib/spawn.go @@ -1,9 +1,11 @@ package lib import ( + "bytes" "log" "os" "os/exec" + "strings" "syscall" "time" ) @@ -55,3 +57,41 @@ func (c *MyConfig) CleanZombies() { time.Sleep(1 * time.Minute) } } + +// RunProcess runs given command and returns contens of command stdout. On error prints error on stderr and returns +// empty syting. +func RunProcess(command []string) string { + var ( + cmd *exec.Cmd + stdout, stderr bytes.Buffer + ) + + switch len(command) { + case 1: + cmd = exec.Command(command[0], "") + default: + cmd = exec.Command(command[0], command[1:]...) + } + + cmd.Dir = "/" + cmd.Stdout = &stdout + cmd.Stderr = &stderr + + err := cmd.Run() + + if err != nil { + if stdout.String() != "" { + log.Printf("Unable to run '%s': %s, %s", strings.Join(command, " "), err, stdout.String()) + } else { + log.Printf("Unable to run '%s': %s", strings.Join(command, " "), err) + } + + return "" + } else { + if stderr.String() != "" { + log.Printf("%s: %s", strings.Join(command, " "), stderr.String()) + } + } + + return strings.TrimRight(stdout.String(), "\n") +} diff --git a/internal/lib/vpn.go b/internal/lib/vpn.go index bad81bf..9a41d04 100644 --- a/internal/lib/vpn.go +++ b/internal/lib/vpn.go @@ -70,7 +70,7 @@ func (c *MyConfig) UpdateVPNStatus() { func (c *MyConfig) VPNTCPCheck() bool { conn, err := net.DialTimeout( "tcp", - fmt.Sprintf("%s:%d", c.Vpn.TCPCheck.Host, c.Vpn.TCPCheck.Port), + fmt.Sprintf("%s:%d", c.Vpn.TCPCheck.Host, c.Vpn.TCPCheck.Port), //nolint: hostport time.Second*time.Duration(c.Vpn.TCPCheck.Timeout), )