13 Commits
v1.3 ... v1.9

Author SHA1 Message Date
eleksir
d9ce425981 Finally fix parsing of app buttons, now settings and fallback values should work as expected 2025-07-15 14:29:27 +03:00
eleksir
069b09e5ec Update default config example 2025-07-12 14:00:55 +03:00
eleksir
8f2afa0441 Fix font size checks and fallbacks 2025-07-12 13:43:10 +03:00
eleksir
8fa1c6d596 Allow to override (in config) color and font settings for AppButtons block 2025-07-12 12:41:13 +03:00
eleksir
12f4dc75f0 Fix linter warnings 2025-06-24 16:13:36 +03:00
eleksir
913fcc34ee Fix incorrect display measurement units in swap calculation in mem plugin 2025-06-24 16:12:48 +03:00
eleksir
ac856f4174 Fix empty font name and incorrect font size name in VPN plugin output 2025-06-24 16:11:43 +03:00
eleksir
a5d793926b Fix typo in README.md 2025-06-01 18:59:15 +03:00
eleksir
40a94bd001 Add missing runcommand.go 2025-06-01 18:57:47 +03:00
eleksir
d19836b821 Update README.md 2025-05-19 12:27:56 +03:00
eleksir
32d865e24e Implement cmdrun config parameter 2025-05-19 12:19:25 +03:00
eleksir
757fb5e57e Add sysfs method to get battery charge for linux 2025-04-24 20:42:32 +03:00
eleksir
6b81991ebb Add small delay in spawner 2025-04-24 18:38:19 +03:00
11 changed files with 873 additions and 75 deletions

View File

@@ -4,6 +4,7 @@ BUILDOPTS=-ldflags="-s -w" -a -gcflags=all=-l -trimpath -buildvcs=false
BINARY=i3status-go BINARY=i3status-go
TEST1=battery-test TEST1=battery-test
TEST2=cmdrun-test
## Use calssic targets where first one is deafult target ## Use calssic targets where first one is deafult target
all: clean build all: clean build
@@ -16,15 +17,18 @@ battery-test:
rm -rf ${TEST1} rm -rf ${TEST1}
go build ${BUILDOPTS} -o ${TEST1} ./cmd/${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 ## Remove binary with golang compiler' means
clean: clean:
rm -rf ${BINARY} ${TEST1} rm -rf ${BINARY} ${TEST1} ${TEST2}
## Misc target, for development purposes. Updates vendored libs, brutal way. ## Misc target, for development purposes. Updates vendored libs, brutal way.
upgrade: upgrade:
$(RM) -r vendor $(RM) -r vendor
go get -u -t -tool ./... go get -u -t ./...
go mod tidy go mod tidy
go mod vendor go mod vendor

View File

@@ -14,11 +14,12 @@ It is i3bar text generator.
* PulseAudio volume indicator, can adjust master volume too * PulseAudio volume indicator, can adjust master volume too
* Clock * Clock
* Cron jobs (intended to use for show periodic desktop notifications but not limited to) * Cron jobs (intended to use for show periodic desktop notifications but not limited to)
* Show output of one-shot system command
## How to build it ## How to build it
This appliction was tested to compile with go v1.21 but can be built with earlier versions (seems down to 1.18, not This application was tested to compile with go v1.24.
sure).
All you need is go lang compiler and gnu make utility. Invoke All you need is go lang compiler and gnu make utility. Invoke
```bash ```bash

View File

@@ -2,7 +2,7 @@
// Default text and *block* border colors. If not set #3e78fd is used. // Default text and *block* border colors. If not set #3e78fd is used.
"color" : "#3e78fd", "color" : "#3e78fd",
// Default backbround color. If not set value of #edeceb is used. // Default background color. If not set value of #edeceb is used.
"background" : "#000000", "background" : "#000000",
// Can be omitted, then Liberation Mono will be used. // Can be omitted, then Liberation Mono will be used.
@@ -12,7 +12,7 @@
// ‘smaller’ or ‘larger’ // ‘smaller’ or ‘larger’
"font_size": "medium", "font_size": "medium",
"separator" : { "separator": {
"left": { "left": {
// Set to false if omitted. // Set to false if omitted.
"enabled": true, "enabled": true,
@@ -518,6 +518,15 @@
"battery": { "battery": {
"enabled": false, "enabled": false,
// Valid only on linux, where we have mounted sysfs and /sys/class/power_supply/BAT0/capacity available
"use_sysfs": false,
// it is possible to set files here, if none set, /sys/class/power_supply/BAT0/capacity will be taken.
"sysfs_files": [
"/sys/class/power_supply/BAT0/capacity",
"/sys/class/power_supply/HIDP0/capacity"
],
// If omitted set to default color defined up here. // If omitted set to default color defined up here.
"color": "#3e78fd", "color": "#3e78fd",
@@ -693,10 +702,95 @@
] ]
}, },
// 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. // Whether to display Application Buttons.
"app_buttons": { "app_buttons": {
"enabled": true, "enabled": true,
// 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 net-if block here. // Re-define separator parameters for net-if block here.
"separator": { "separator": {
"left": { "left": {
@@ -740,31 +834,36 @@
// ‘smaller’ or ‘larger’. If omitted set to default font size defined for separator up here. // ‘smaller’ or ‘larger’. If omitted set to default font size defined for separator up here.
"font_size": "medium" "font_size": "medium"
} }
}, }
}, },
// Buttons - application launch "buttons" // Buttons - application launch "buttons"
"apps": [ "apps": [
{ {
// Text dispalyed on button // Text displayed on button, pango markup supported.
"full_text" : "📂", "full_text" : "📂",
// Internal name, can be any string, but unique for each app button // Internal name, can be any string, but unique for each app button.
"name" : "thunar", "name" : "thunar",
// Command itself // Command itself.
"cmd" : "thunar", "cmd" : "thunar",
// Array of command args, optional // Array of command args, optional.
"args": [ "." ], "args": [ "." ],
// Regexp that match with Instance component of WM_CLASS(STRING) of xprop output // Regexp that match with Instance component of WM_CLASS(STRING) of xprop output.
"instance": "^thunar$", "instance": "^thunar$",
// Regexp that match with Class component of WM_CLASS(STRING) of xprop output // Regexp that match with Class component of WM_CLASS(STRING) of xprop output.
"class": "^Thunar$", "class": "^Thunar$",
// text color in html notation // Text color in html notation.
"color": "#ffa500", "color": "#ffa500",
// background color in html notation // background color in html notation.
"background" : "#000000", "background" : "#000000",
// border color in html notation // If omitted set to default font defined for app_buttons 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 app_buttons up here.
"font_size": "medium",
// Border color in html notation.
"border": "#666666", "border": "#666666",
// border color if there is at least one window with given Instance and/or Class exist // Border color if there is at least one window with given Instance and/or Class exist.
"border_active": "#3e78fd", "border_active": "#3e78fd",
// Vertical line between objects on bar - if omitted true value is assumed by i3bar, but we set it to false // Vertical line between objects on bar - if omitted true value is assumed by i3bar, but we set it to false
// in that case. It is limitation of golang json parser. // in that case. It is limitation of golang json parser.

View File

@@ -84,6 +84,14 @@ func main() {
Conf.Clock.FontSize, Conf.Clock.FontSize,
) )
Conf.Values.RunCommandOutput = fmt.Sprintf(
"<span color='%s' background='%s' font='%s' size='%s'>?</span>",
Conf.CmdRun.Color,
Conf.CmdRun.Background,
Conf.CmdRun.Font,
Conf.CmdRun.FontSize,
)
go Conf.Spawner() go Conf.Spawner()
go Conf.ParseStdin() go Conf.ParseStdin()
go Conf.CleanZombies() go Conf.CleanZombies()
@@ -143,6 +151,10 @@ func main() {
go Conf.UpdateVPNStatus() 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 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 header, empty json array and (potentially infinite) json lines (line that is valid json by itself) that is
@@ -180,7 +192,14 @@ func main() {
) )
} }
b.FullText = app.FullText b.FullText = fmt.Sprintf(
"<span color='%s' background='%s' font='%s' size='%s'>%s</span>",
app.Color,
app.Background,
app.Font,
app.FontSize,
app.FullText,
)
if num == len(Conf.Apps) { if num == len(Conf.Apps) {
b.FullText = fmt.Sprintf( b.FullText = fmt.Sprintf(
@@ -193,8 +212,6 @@ func main() {
) )
} }
b.Background = app.Background
b.Color = app.Color
b.Instance = app.Instance b.Instance = app.Instance
b.Markup = `pango` b.Markup = `pango`
b.Separator = app.Separator b.Separator = app.Separator
@@ -519,6 +536,50 @@ func main() {
j = append(j, b) 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(
"<span color='%s' background='%s' font='%s' size='%s'>%s</span>",
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(
"<span color='%s' background='%s' font='%s' size='%s'>%s</span>",
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(
"<span color='%s' background='%s' font='%s' size='%s'>%s</span>",
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 { if Conf.Clock.Enabled {
var b lib.I3BarOutBlock var b lib.I3BarOutBlock

View File

@@ -1 +1 @@
/home/eleksir/git/i3status-go/cmd/i3status-go/default-config.json ../cmd/i3status-go/default-config.json

View File

@@ -2,7 +2,12 @@ package lib
import ( import (
"fmt" "fmt"
"log"
"math" "math"
"os"
"path/filepath"
"strconv"
"strings"
"time" "time"
"github.com/distatus/battery" "github.com/distatus/battery"
@@ -11,9 +16,6 @@ import (
// UpdateBatteryInfo updates info about battery charge. // UpdateBatteryInfo updates info about battery charge.
func (c *MyConfig) UpdateBatteryInfo() { func (c *MyConfig) UpdateBatteryInfo() {
var ( var (
ch int
status string
Batts string
InitialDelay = 100 * time.Millisecond InitialDelay = 100 * time.Millisecond
LoopIterationDelay = 5 * time.Second LoopIterationDelay = 5 * time.Second
Delay = InitialDelay Delay = InitialDelay
@@ -26,9 +28,85 @@ func (c *MyConfig) UpdateBatteryInfo() {
ticker.Reset(Delay) ticker.Reset(Delay)
} }
batteries, _ := battery.GetAll() var (
// In theory, this module should give for each entry its separate err, but in practice it gives batteries = []*battery.Battery{}
// one single err for all entries, so we cannot detemine whist exatly entry errored. Batts string
ch int
status string
)
if c.Battery.UseSysfs {
for _, file := range c.Battery.SysfsFiles {
var batt battery.Battery
_, err := os.Stat(file)
// If file exists.
if !os.IsNotExist(err) {
var (
myCharge string
myStatus string
)
b, err := os.ReadFile(file)
if err != nil {
log.Printf("Unable to read file %s: %s", file, err)
continue
}
myCharge = strings.Trim(string(b), "\n")
statusFile := filepath.Dir(file) + "/status"
// If file exists.
if !os.IsNotExist(err) {
b, err := os.ReadFile(statusFile)
if err != nil {
log.Printf("Unable to read file %s: %s", statusFile, err)
continue
}
myStatus = strings.Trim(string(b), "\n")
} else {
continue
}
// Make stub battery class :) .
switch myStatus {
case "Charging":
batt.State.Raw = battery.Charging
case "Discharging":
batt.State.Raw = battery.Discharging
case "Empty":
batt.State.Raw = battery.Empty
case "Full":
batt.State.Raw = battery.Full
default:
batt.State.Raw = battery.Unknown
}
ch, err := strconv.Atoi(myCharge)
if err != nil {
log.Printf("Unable to convert string from file %s to integer", file)
continue
}
batt.Current = float64(ch)
batteries = append(batteries, &batt)
}
}
} else {
// In theory, this module should give for each entry its separate err, but in practice it gives
// one single err for all entries, so we cannot detemine whist exatly entry errored.
batteries, _ = battery.GetAll()
}
var ( var (
battsInfo string battsInfo string
@@ -49,7 +127,11 @@ func (c *MyConfig) UpdateBatteryInfo() {
// N.B. there can be case when battery is overcharged and shows >100%. It also can indicate that // N.B. there can be case when battery is overcharged and shows >100%. It also can indicate that
// calibration data is out of date and battery should be re-calibrated. // calibration data is out of date and battery should be re-calibrated.
ch = int(math.Round((b.Full - (b.Full - b.Current)) * (100 / b.Full))) if c.Battery.UseSysfs {
ch = int(b.Current)
} else {
ch = int(math.Round((b.Full - (b.Full - b.Current)) * (100 / b.Full)))
}
switch { switch {
case ch <= 500 && ch >= 84: case ch <= 500 && ch >= 84:

View File

@@ -21,6 +21,8 @@ type I3BarOutBlock struct {
ShortText string `json:"short_text,omitempty"` ShortText string `json:"short_text,omitempty"`
Color string `json:"color,omitempty"` Color string `json:"color,omitempty"`
Background string `json:"background,omitempty"` Background string `json:"background,omitempty"`
Font string `json:"font,omitempty"`
FontSize string `json:"font_size,omitempty"`
Border string `json:"border,omitempty"` Border string `json:"border,omitempty"`
BorderTop int `json:"border_top"` BorderTop int `json:"border_top"`
BorderRight int `json:"border_right"` BorderRight int `json:"border_right"`
@@ -59,16 +61,17 @@ type Separator struct {
type MyConfig struct { type MyConfig struct {
Values struct { Values struct {
PrintOutput bool PrintOutput bool
CPUTemperature int64 CPUTemperature int64
BatteryString string BatteryString string
ClockTime string ClockTime string
IfStatus string IfStatus string
VPNStatus string VPNStatus string
Memory Mem Memory Mem
La string La string
PA *p.Client PA *p.Client
SoundVolume string SoundVolume string
RunCommandOutput string
} }
Channels struct { Channels struct {
@@ -134,14 +137,16 @@ type MyConfig struct {
} `json:"clock,omitempty"` } `json:"clock,omitempty"`
Battery struct { Battery struct {
Enabled bool `json:"enabled,omitempty"` Enabled bool `json:"enabled,omitempty"`
Color string `json:"color,omitempty"` UseSysfs bool `json:"use_sysfs,omitempty"`
Background string `json:"background,omitempty"` SysfsFiles []string `json:"sysfs_files,omitempty"`
Font string `json:"font,omitempty"` Color string `json:"color,omitempty"`
FontSize string `json:"font_size,omitempty"` Background string `json:"background,omitempty"`
Symbol string `json:"symbol,omitempty"` Font string `json:"font,omitempty"`
SymbolFont string `json:"symbol_font,omitempty"` FontSize string `json:"font_size,omitempty"`
SymbolFontSize string `json:"symbol_font_size,omitempty"` Symbol string `json:"symbol,omitempty"`
SymbolFont string `json:"symbol_font,omitempty"`
SymbolFontSize string `json:"symbol_font_size,omitempty"`
ChargeColor struct { ChargeColor struct {
Full string `json:"full,omitempty"` Full string `json:"full,omitempty"`
@@ -229,9 +234,25 @@ type MyConfig struct {
} `json:"tasks,omitempty"` } `json:"tasks,omitempty"`
} `json:"cron,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 { AppButtons struct {
Enabled bool `json:"enabled,omitempty"` Enabled bool `json:"enabled,omitempty"`
Separator Separator `json:"separator,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"`
} `json:"app_buttons,omitempty"` } `json:"app_buttons,omitempty"`
Apps []struct { Apps []struct {
@@ -243,6 +264,8 @@ type MyConfig struct {
Class string `json:"class,omitempty"` Class string `json:"class,omitempty"`
Color string `json:"color,omitempty"` Color string `json:"color,omitempty"`
Background string `json:"background,omitempty"` Background string `json:"background,omitempty"`
Font string `json:"font,omitempty"`
FontSize string `json:"font_size,omitempty"`
Border string `json:"border,omitempty"` Border string `json:"border,omitempty"`
BorderActive string `json:"border_active,omitempty"` BorderActive string `json:"border_active,omitempty"`
Separator bool `json:"separator,omitempty"` Separator bool `json:"separator,omitempty"`
@@ -449,6 +472,30 @@ func ReadConf(defaultConfig []byte) (*MyConfig, error) {
if sampleConfig.LA.FontSize == "" { if sampleConfig.LA.FontSize == "" {
sampleConfig.LA.FontSize = sampleConfig.FontSize sampleConfig.LA.FontSize = sampleConfig.FontSize
} else {
matched, err := regexp.MatchString(
`^(xx-small|x-small|small|medium|large|x-large|xx-large|smaller|larger)$`,
sampleConfig.LA.FontSize,
)
if err != nil {
log.Printf(
"Unable to set sampleConfig.LA.FontSize: %s, fallback to %s",
err,
sampleConfig.FontSize,
)
sampleConfig.LA.FontSize = sampleConfig.FontSize
}
if !matched {
log.Printf(
"Unable to set sampleConfig.LA.FontSize, fallback to %s",
sampleConfig.FontSize,
)
sampleConfig.LA.FontSize = sampleConfig.FontSize
}
} }
if sampleConfig.LA.Separator.Left.Color == "" { if sampleConfig.LA.Separator.Left.Color == "" {
@@ -503,22 +550,6 @@ func ReadConf(defaultConfig []byte) (*MyConfig, error) {
sampleConfig.LA.Separator.Right.Background = sampleConfig.Separator.Right.Background sampleConfig.LA.Separator.Right.Background = sampleConfig.Separator.Right.Background
} }
if sampleConfig.LA.Separator.Right.Font == "" {
sampleConfig.LA.Separator.Right.Font = sampleConfig.Separator.Right.Font
}
if sampleConfig.LA.Separator.Right.FontSize == "" {
sampleConfig.LA.Separator.Right.FontSize = sampleConfig.Separator.Right.FontSize
}
if sampleConfig.LA.Separator.Right.Color == "" {
sampleConfig.LA.Separator.Right.Color = sampleConfig.Separator.Right.Color
}
if sampleConfig.LA.Separator.Right.Background == "" {
sampleConfig.LA.Separator.Right.Background = sampleConfig.Separator.Right.Background
}
if sampleConfig.LA.Separator.Right.Symbol == "" { if sampleConfig.LA.Separator.Right.Symbol == "" {
sampleConfig.LA.Separator.Right.Symbol = sampleConfig.Separator.Right.Symbol sampleConfig.LA.Separator.Right.Symbol = sampleConfig.Separator.Right.Symbol
} }
@@ -569,6 +600,30 @@ func ReadConf(defaultConfig []byte) (*MyConfig, error) {
if sampleConfig.Mem.FontSize == "" { if sampleConfig.Mem.FontSize == "" {
sampleConfig.Mem.FontSize = sampleConfig.FontSize sampleConfig.Mem.FontSize = sampleConfig.FontSize
} else {
matched, err := regexp.MatchString(
`^(xx-small|x-small|small|medium|large|x-large|xx-large|smaller|larger)$`,
sampleConfig.Mem.FontSize,
)
if err != nil {
log.Printf(
"Unable to set sampleConfig.Mem.FontSize: %s, fallback to %s",
err,
sampleConfig.FontSize,
)
sampleConfig.Mem.FontSize = sampleConfig.FontSize
}
if !matched {
log.Printf(
"Unable to set sampleConfig.Mem.FontSize, fallback to %s",
sampleConfig.FontSize,
)
sampleConfig.Mem.FontSize = sampleConfig.FontSize
}
} }
if sampleConfig.Mem.Separator.Left.Color == "" { if sampleConfig.Mem.Separator.Left.Color == "" {
@@ -673,6 +728,30 @@ func ReadConf(defaultConfig []byte) (*MyConfig, error) {
if sampleConfig.Clock.FontSize == "" { if sampleConfig.Clock.FontSize == "" {
sampleConfig.Clock.FontSize = sampleConfig.FontSize sampleConfig.Clock.FontSize = sampleConfig.FontSize
} else {
matched, err := regexp.MatchString(
`^(xx-small|x-small|small|medium|large|x-large|xx-large|smaller|larger)$`,
sampleConfig.Clock.FontSize,
)
if err != nil {
log.Printf(
"Unable to set sampleConfig.Clock.FontSize: %s, fallback to %s",
err,
sampleConfig.FontSize,
)
sampleConfig.Clock.FontSize = sampleConfig.FontSize
}
if !matched {
log.Printf(
"Unable to set sampleConfig.Clock.FontSize, fallback to %s",
sampleConfig.FontSize,
)
sampleConfig.Clock.FontSize = sampleConfig.FontSize
}
} }
if sampleConfig.Clock.Separator.Left.Enabled { if sampleConfig.Clock.Separator.Left.Enabled {
@@ -778,6 +857,15 @@ func ReadConf(defaultConfig []byte) (*MyConfig, error) {
} }
// sampleConfig.Battery.Enabled will be false if not set in config // sampleConfig.Battery.Enabled will be false if not set in config
// sampleConfig.Battery.UseSysfs will be false if not set in config
if len(sampleConfig.Battery.SysfsFiles) == 0 {
sampleConfig.Battery.SysfsFiles = append(
sampleConfig.Battery.SysfsFiles,
"/sys/class/power_supply/BAT0/capacity",
)
}
if sampleConfig.Battery.Color == "" { if sampleConfig.Battery.Color == "" {
sampleConfig.Battery.Color = sampleConfig.Color sampleConfig.Battery.Color = sampleConfig.Color
} }
@@ -792,6 +880,30 @@ func ReadConf(defaultConfig []byte) (*MyConfig, error) {
if sampleConfig.Battery.FontSize == "" { if sampleConfig.Battery.FontSize == "" {
sampleConfig.Battery.FontSize = sampleConfig.FontSize sampleConfig.Battery.FontSize = sampleConfig.FontSize
} else {
matched, err := regexp.MatchString(
`^(xx-small|x-small|small|medium|large|x-large|xx-large|smaller|larger)$`,
sampleConfig.Battery.FontSize,
)
if err != nil {
log.Printf(
"Unable to set sampleConfig.Battery.FontSize: %s, fallback to %s",
err,
sampleConfig.FontSize,
)
sampleConfig.Battery.FontSize = sampleConfig.FontSize
}
if !matched {
log.Printf(
"Unable to set sampleConfig.Battery.FontSize, fallback to %s",
sampleConfig.FontSize,
)
sampleConfig.Battery.FontSize = sampleConfig.FontSize
}
} }
if sampleConfig.Battery.Symbol == "" { if sampleConfig.Battery.Symbol == "" {
@@ -804,6 +916,30 @@ func ReadConf(defaultConfig []byte) (*MyConfig, error) {
if sampleConfig.Battery.SymbolFontSize == "" { if sampleConfig.Battery.SymbolFontSize == "" {
sampleConfig.Battery.SymbolFontSize = sampleConfig.Battery.FontSize sampleConfig.Battery.SymbolFontSize = sampleConfig.Battery.FontSize
} else {
matched, err := regexp.MatchString(
`^(xx-small|x-small|small|medium|large|x-large|xx-large|smaller|larger)$`,
sampleConfig.Battery.SymbolFontSize,
)
if err != nil {
log.Printf(
"Unable to set sampleConfig.Battery.SymbolFontSize: %s, fallback to %s",
err,
sampleConfig.Battery.FontSize,
)
sampleConfig.Battery.SymbolFontSize = sampleConfig.Battery.FontSize
}
if !matched {
log.Printf(
"Unable to set sampleConfig.Battery.SymbolFontSize, fallback to %s",
sampleConfig.Battery.FontSize,
)
sampleConfig.Battery.SymbolFontSize = sampleConfig.Battery.FontSize
}
} }
// sampleConfig.Battery.ChargeColor.Full will be empty string if not set // sampleConfig.Battery.ChargeColor.Full will be empty string if not set
@@ -919,6 +1055,30 @@ func ReadConf(defaultConfig []byte) (*MyConfig, error) {
if sampleConfig.CPUTemp.FontSize == "" { if sampleConfig.CPUTemp.FontSize == "" {
sampleConfig.CPUTemp.FontSize = sampleConfig.FontSize sampleConfig.CPUTemp.FontSize = sampleConfig.FontSize
} else {
matched, err := regexp.MatchString(
`^(xx-small|x-small|small|medium|large|x-large|xx-large|smaller|larger)$`,
sampleConfig.CPUTemp.FontSize,
)
if err != nil {
log.Printf(
"Unable to set sampleConfig.CPUTemp.FontSize: %s, fallback to %s",
err,
sampleConfig.FontSize,
)
sampleConfig.CPUTemp.FontSize = sampleConfig.FontSize
}
if !matched {
log.Printf(
"Unable to set sampleConfig.CPUTemp.FontSize, fallback to %s",
sampleConfig.FontSize,
)
sampleConfig.CPUTemp.FontSize = sampleConfig.FontSize
}
} }
if sampleConfig.CPUTemp.Separator.Left.Enabled { if sampleConfig.CPUTemp.Separator.Left.Enabled {
@@ -1025,11 +1185,35 @@ func ReadConf(defaultConfig []byte) (*MyConfig, error) {
} }
if sampleConfig.Vpn.Font == "" { if sampleConfig.Vpn.Font == "" {
sampleConfig.Vpn.FontSize = sampleConfig.Font sampleConfig.Vpn.Font = sampleConfig.Font
} }
if sampleConfig.Vpn.FontSize == "" { if sampleConfig.Vpn.FontSize == "" {
sampleConfig.Vpn.FontSize = sampleConfig.FontSize sampleConfig.Vpn.FontSize = sampleConfig.FontSize
} else {
matched, err := regexp.MatchString(
`^(xx-small|x-small|small|medium|large|x-large|xx-large|smaller|larger)$`,
sampleConfig.Vpn.FontSize,
)
if err != nil {
log.Printf(
"Unable to set sampleConfig.Vpn.FontSize: %s, fallback to %s",
err,
sampleConfig.FontSize,
)
sampleConfig.Vpn.FontSize = sampleConfig.FontSize
}
if !matched {
log.Printf(
"Unable to set sampleConfig.Vpn.FontSize, fallback to %s",
sampleConfig.FontSize,
)
sampleConfig.Vpn.FontSize = sampleConfig.FontSize
}
} }
// Check status file at least once per 3 seconds // Check status file at least once per 3 seconds
@@ -1174,6 +1358,30 @@ func ReadConf(defaultConfig []byte) (*MyConfig, error) {
if sampleConfig.SimpleVolumePa.FontSize == "" { if sampleConfig.SimpleVolumePa.FontSize == "" {
sampleConfig.SimpleVolumePa.FontSize = sampleConfig.FontSize sampleConfig.SimpleVolumePa.FontSize = sampleConfig.FontSize
} else {
matched, err := regexp.MatchString(
`^(xx-small|x-small|small|medium|large|x-large|xx-large|smaller|larger)$`,
sampleConfig.SimpleVolumePa.FontSize,
)
if err != nil {
log.Printf(
"Unable to set sampleConfig.SimpleVolumePa.FontSize: %s, fallback to %s",
err,
sampleConfig.FontSize,
)
sampleConfig.SimpleVolumePa.FontSize = sampleConfig.FontSize
}
if !matched {
log.Printf(
"Unable to set sampleConfig.SimpleVolumePa.FontSize, fallback to %s",
sampleConfig.FontSize,
)
sampleConfig.SimpleVolumePa.FontSize = sampleConfig.FontSize
}
} }
if sampleConfig.SimpleVolumePa.Symbol == "" { if sampleConfig.SimpleVolumePa.Symbol == "" {
@@ -1186,6 +1394,30 @@ func ReadConf(defaultConfig []byte) (*MyConfig, error) {
if sampleConfig.SimpleVolumePa.SymbolFontSize == "" { if sampleConfig.SimpleVolumePa.SymbolFontSize == "" {
sampleConfig.SimpleVolumePa.SymbolFontSize = sampleConfig.SimpleVolumePa.FontSize sampleConfig.SimpleVolumePa.SymbolFontSize = sampleConfig.SimpleVolumePa.FontSize
} else {
matched, err := regexp.MatchString(
`^(xx-small|x-small|small|medium|large|x-large|xx-large|smaller|larger)$`,
sampleConfig.SimpleVolumePa.SymbolFontSize,
)
if err != nil {
log.Printf(
"Unable to set sampleConfig.SimpleVolumePa.SymbolFontSize: %s, fallback to %s",
err,
sampleConfig.SimpleVolumePa.FontSize,
)
sampleConfig.SimpleVolumePa.SymbolFontSize = sampleConfig.SimpleVolumePa.FontSize
}
if !matched {
log.Printf(
"Unable to set sampleConfig.SimpleVolumePa.SymbolFontSize, fallback to %s",
sampleConfig.SimpleVolumePa.FontSize,
)
sampleConfig.SimpleVolumePa.SymbolFontSize = sampleConfig.SimpleVolumePa.FontSize
}
} }
if sampleConfig.SimpleVolumePa.Step == 0 { if sampleConfig.SimpleVolumePa.Step == 0 {
@@ -1346,6 +1578,29 @@ func ReadConf(defaultConfig []byte) (*MyConfig, error) {
if sampleConfig.NetIf.FontSize == "" { if sampleConfig.NetIf.FontSize == "" {
sampleConfig.NetIf.FontSize = sampleConfig.FontSize sampleConfig.NetIf.FontSize = sampleConfig.FontSize
} else {
matched, err := regexp.MatchString(
`^(xx-small|x-small|small|medium|large|x-large|xx-large|smaller|larger)$`,
sampleConfig.NetIf.FontSize,
)
if err != nil {
log.Printf(
"Unable to set sampleConfig.NetIf.FontSize: %s, fallback to %s",
err,
sampleConfig.FontSize,
)
sampleConfig.NetIf.FontSize = sampleConfig.FontSize
}
if !matched {
log.Printf("Unable to set sampleConfig.NetIf.FontSize, fallback to %s",
sampleConfig.FontSize,
)
sampleConfig.NetIf.FontSize = sampleConfig.FontSize
}
} }
if sampleConfig.NetIf.Separator.Left.Color == "" { if sampleConfig.NetIf.Separator.Left.Color == "" {
@@ -1435,7 +1690,186 @@ 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
} else {
matched, err := regexp.MatchString(
`^(xx-small|x-small|small|medium|large|x-large|xx-large|smaller|larger)$`,
sampleConfig.CmdRun.FontSize,
)
if err != nil {
log.Printf(
"Unable to set sampleConfig.CmdRun.FontSize: %s, fallback to %s",
err,
sampleConfig.FontSize,
)
sampleConfig.CmdRun.FontSize = sampleConfig.FontSize
}
if !matched {
log.Printf(
"Unable to set sampleConfig.CmdRun.FontSize, fallback to %s",
sampleConfig.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 // sampleConfig.AppButtons.Enabled will false if not set in config
if sampleConfig.AppButtons.Color == "" {
sampleConfig.AppButtons.Color = sampleConfig.Color
}
if sampleConfig.AppButtons.Background == "" {
sampleConfig.AppButtons.Background = sampleConfig.Background
}
if sampleConfig.AppButtons.Font == "" {
sampleConfig.AppButtons.Font = sampleConfig.Font
}
if sampleConfig.AppButtons.FontSize == "" {
sampleConfig.AppButtons.FontSize = sampleConfig.FontSize
} else {
matched, err := regexp.MatchString(
`^(xx-small|x-small|small|medium|large|x-large|xx-large|smaller|larger)$`,
sampleConfig.AppButtons.FontSize,
)
if err != nil {
log.Printf(
"Unable to set sampleConfig.AppButtons.FontSize: %s, fallback to %s",
err,
sampleConfig.FontSize,
)
sampleConfig.AppButtons.FontSize = sampleConfig.FontSize
}
if !matched {
log.Printf(
"Unable to set sampleConfig.AppButtons.FontSize, fallback to %s",
sampleConfig.FontSize,
)
sampleConfig.AppButtons.FontSize = sampleConfig.FontSize
}
}
if sampleConfig.AppButtons.Separator.Left.Color == "" { if sampleConfig.AppButtons.Separator.Left.Color == "" {
sampleConfig.AppButtons.Separator.Left.Color = sampleConfig.Separator.Left.Color sampleConfig.AppButtons.Separator.Left.Color = sampleConfig.Separator.Left.Color
@@ -1531,34 +1965,68 @@ func ReadConf(defaultConfig []byte) (*MyConfig, error) {
// app.Instance can be missing. // app.Instance can be missing.
// app.Class can be missing. // app.Class can be missing.
if app.Name == "" { if app.Name == "" {
app.Name = fmt.Sprintf("app%d", num) sampleConfig.Apps[num].Name = fmt.Sprintf("app%d", num)
} }
// app.Args can be empty slice. In that case command will be run without aruments. // app.Args can be empty slice. In that case command will be run without aruments.
if app.Cmd == "" { if app.Cmd == "" {
app.Cmd = "true" // If command omitted it will be just /usr/bin/true.
sampleConfig.Apps[num].Cmd = "true"
} }
if app.Color == "" { if app.Color == "" {
app.Color = sampleConfig.Color sampleConfig.Apps[num].Color = sampleConfig.AppButtons.Color
} }
if app.Background == "" { if app.Background == "" {
app.Background = sampleConfig.Background sampleConfig.Apps[num].Background = sampleConfig.AppButtons.Background
}
if app.Font == "" {
sampleConfig.Apps[num].Font = sampleConfig.AppButtons.Font
}
if app.FontSize == "" {
sampleConfig.Apps[num].FontSize = sampleConfig.AppButtons.FontSize
} else {
matched, err := regexp.MatchString(
`^(xx-small|x-small|small|medium|large|x-large|xx-large|smaller|larger)$`,
app.FontSize,
)
if err != nil {
log.Printf(
"Unable to set %s.FontSize: %s, fallback to %s",
app.Name,
err,
sampleConfig.AppButtons.FontSize,
)
sampleConfig.Apps[num].FontSize = sampleConfig.AppButtons.FontSize
}
if !matched {
log.Printf("Unable to set %s.FontSize, fallback to %s",
app.Name,
sampleConfig.AppButtons.FontSize,
)
sampleConfig.Apps[num].FontSize = sampleConfig.AppButtons.FontSize
}
} }
if app.Border == "" { if app.Border == "" {
app.Border = sampleConfig.Color sampleConfig.Apps[num].Border = sampleConfig.AppButtons.Color
} }
if app.BorderActive == "" { if app.BorderActive == "" {
app.BorderActive = sampleConfig.Color sampleConfig.Apps[num].BorderActive = sampleConfig.AppButtons.Color
} }
// app.Separator can be omitted, in that case it is false // app.Separator can be omitted, in that case it is false
// app.SeparatorBlockWidth can be missing // app.SeparatorBlockWidth can be missing
if app.FullText == "" { if app.FullText == "" {
app.FullText = fmt.Sprintf(" %d ", num) sampleConfig.Apps[num].FullText = fmt.Sprintf(" %d ", num)
} }
} }
} }

View File

@@ -51,7 +51,7 @@ func (c *MyConfig) UpdateMemStats() {
if c.Values.Memory.Usedpct != uint64(v.UsedPercent) || c.Values.Memory.Shared != v.Shared/1024/1024 || c.Values.Memory.Swap != v.SwapTotal-v.SwapFree { if c.Values.Memory.Usedpct != uint64(v.UsedPercent) || c.Values.Memory.Shared != v.Shared/1024/1024 || c.Values.Memory.Swap != v.SwapTotal-v.SwapFree {
c.Values.Memory.Usedpct = uint64(v.UsedPercent) c.Values.Memory.Usedpct = uint64(v.UsedPercent)
c.Values.Memory.Shared = v.Shared / 1024 / 1024 c.Values.Memory.Shared = v.Shared / 1024 / 1024
c.Values.Memory.Swap = sw.Used c.Values.Memory.Swap = sw.Used / 1024 / 1024
c.Channels.UpdateReady <- true c.Channels.UpdateReady <- true
} }
} else { } else {

View File

@@ -0,0 +1,41 @@
package lib
import (
"log"
"time"
)
// RunCommands gets list of commands runs them sequentally and conacatenates their output.
func (c *MyConfig) RunCommand() {
var (
InitialDelay = 100 * time.Millisecond
LoopIterationDelay = time.Duration(c.CmdRun.Delay) * time.Second
Delay = InitialDelay
ticker = time.NewTicker(Delay)
)
for range ticker.C {
if Delay == InitialDelay {
Delay = LoopIterationDelay
ticker.Reset(Delay)
}
var (
outputString string
command = []string{c.CmdRun.Cmd}
)
if len(c.CmdRun.Args) > 0 {
command = append(command, c.CmdRun.Args...)
}
outputString += RunProcess(command)
log.Println(c.Values.RunCommandOutput)
if c.Values.RunCommandOutput != outputString {
c.Values.RunCommandOutput = outputString
c.Channels.UpdateReady <- true
}
}
}

View File

@@ -1,9 +1,11 @@
package lib package lib
import ( import (
"bytes"
"log" "log"
"os" "os"
"os/exec" "os/exec"
"strings"
"syscall" "syscall"
"time" "time"
) )
@@ -11,6 +13,8 @@ import (
// Spawner forks and execs given program, and also detaches form its control tty. // Spawner forks and execs given program, and also detaches form its control tty.
func (c *MyConfig) Spawner() { func (c *MyConfig) Spawner() {
for prg := range c.Channels.RunChan { for prg := range c.Channels.RunChan {
<-time.After(25 * time.Millisecond)
devnullR, _ := os.Open(os.DevNull) devnullR, _ := os.Open(os.DevNull)
devnullW, _ := os.OpenFile(os.DevNull, os.O_WRONLY|os.O_APPEND, 0644) devnullW, _ := os.OpenFile(os.DevNull, os.O_WRONLY|os.O_APPEND, 0644)
@@ -54,3 +58,41 @@ func (c *MyConfig) CleanZombies() {
time.Sleep(1 * time.Minute) 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], "") //nolint: gosec
default:
cmd = exec.Command(command[0], command[1:]...) //nolint: gosec
}
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 ""
}
if stderr.String() != "" {
log.Printf("%s: %s", strings.Join(command, " "), stderr.String())
}
return strings.TrimRight(stdout.String(), "\n")
}

View File

@@ -70,7 +70,7 @@ func (c *MyConfig) UpdateVPNStatus() {
func (c *MyConfig) VPNTCPCheck() bool { func (c *MyConfig) VPNTCPCheck() bool {
conn, err := net.DialTimeout( conn, err := net.DialTimeout(
"tcp", "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), time.Second*time.Duration(c.Vpn.TCPCheck.Timeout),
) )