12 Commits

Author SHA1 Message Date
eleksir
3f5704237a [WIP] Update linter config + speed optimizations 2025-11-23 16:41:20 +03:00
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
19 changed files with 1960 additions and 470 deletions

View File

@@ -14,23 +14,29 @@ linters:
- contextcheck - contextcheck
- decorder - decorder
- durationcheck - durationcheck
- err113
- errcheck - errcheck
- errchkjson - errchkjson
- errorlint - errorlint
- exptostd
- ginkgolinter - ginkgolinter
- gocheckcompilerdirectives - gocheckcompilerdirectives
- gocritic - gocritic
- godoclint
- godot - godot
- godox
- goprintffuncname
- gosec - gosec
- gosmopolitan - gosmopolitan
- govet - govet
- grouper - grouper
- importas - importas
- ineffassign - ineffassign
- intrange
- ireturn - ireturn
- makezero - makezero
- mirror - mirror
- misspell
- modernize
- nakedret - nakedret
- nilerr - nilerr
- nilnil - nilnil
@@ -38,6 +44,7 @@ linters:
- noctx - noctx
- nolintlint - nolintlint
- nonamedreturns - nonamedreturns
- perfsprint
- predeclared - predeclared
- promlinter - promlinter
- reassign - reassign
@@ -46,14 +53,731 @@ linters:
- staticcheck - staticcheck
- unconvert - unconvert
- unused - unused
- usestdlibvars
- wastedassign - wastedassign
- whitespace - wsl_v5
- wsl
settings: settings:
gosec: decorder:
dec-order:
- type
- const
- var
- func
gocritic:
disable-all: false
enable-all: true
disabled-checks:
- singleCaseSwitch
- octalLiteral
- regexpSimplify
- paramTypeCombine
- whyNoLint
- hugeParam
godoclint:
default: all
ignore-unexported: false
ignore-exported: false
include-unexported: true
disable:
- max-len
godot:
scope: all
exclude:
# Exclude todo and fixme comments.
- "^fixme:"
- "^todo:"
period: true
capital: false
godox:
keywords:
- FIXME
gosec: # go-fucking-sic
excludes: excludes:
- G404 # Боже, как же вы затрахали своей крипто-ебучкой, наркоманы хреновы!
- G306 - G405 # Detect the usage of DES or RC4
- G406 # Detect the usage of MD4 or RIPEMD160
# Оно не используется для повышения безопасности или каких-то таких секъюрных вещей, оно используется для выборки
# произвольного элемента из массива, так что ИДИ НАХУЙ с этой проверкой и не еби мозги липовой безопасностью.
- G404 # Insecure random number source (rand)
- G501 # Import blocklist: crypto/md5
- G502 # Import blocklist: crypto/des
- G503 # Import blocklist: crypto/rc4
- G504 # Import blocklist: net/http/cgi
- G505 # Import blocklist: crypto/sha1
- G506 # Import blocklist: golang.org/x/crypto/md4
- G507 # Import blocklist: golang.org/x/crypto/ripemd160
config:
global:
audit: true
# Ваша сраная паранойя при создании файлов и каталогов мешает нормальной работе сервисов. Группы придуманы для
# удобства, группе удобно давать право чтения. Поэтому идите нахуй со своими ограничениями на чтение для
# группы, "безопасные" клоуны.
G301: "0755"
G306: "0644"
govet:
disable-all: false
enable-all: true
disable:
- shadow
- fieldalignment
grouper:
# Require the use of a single global 'const' declaration only.
# Default: false
const-require-single-const: true
# Require the use of grouped global 'const' declarations.
# Default: false
const-require-grouping: true
# Require the use of a single 'import' declaration only.
# Default: false
import-require-single-import: true
# Require the use of grouped 'import' declarations.
# Default: false
import-require-grouping: true
# Require the use of a single global 'type' declaration only.
# Default: false
type-require-single-type: true
# Require the use of grouped global 'type' declarations.
# Default: false
type-require-grouping: true
# Require the use of a single global 'var' declaration only.
# Default: false
var-require-single-var: true
# Require the use of grouped global 'var' declarations.
# Default: false
var-require-grouping: true
misspell:
# Correct spellings using locale preferences for US or UK.
# Setting locale to US will correct the British spelling of 'colour' to 'color'.
# Default is to use a neutral variety of English.
locale: US
# Typos to ignore.
# Should be in lower case.
# Default: []
ignore-rules:
- someword
# Extra word corrections.
# `typo` and `correction` should only contain letters.
# The words are case-insensitive.
# Default: []
extra-words:
- typo: "iff"
correction: "if"
- typo: "cancelation"
correction: "cancellation"
# Mode of the analysis:
# - default: checks all the file content.
# - restricted: checks only comments.
# Default: ""
mode: restricted
nonamedreturns:
# Report named error if it is assigned inside defer.
# Default: false
report-error-in-defer: true
perfsprint:
integer-format: true
int-conversion: true
error-format: true
err-error: false
errorf: true
string-format: true
sprintf1: true
strconcat: true
bool-format: true
hex-format: true
concat-loop: true
loop-other-ops: false
revive:
enable-all-rules: false
staticcheck:
# https://staticcheck.dev/docs/configuration/options/#dot_import_whitelist
# Default: ["github.com/mmcloughlin/avo/build", "github.com/mmcloughlin/avo/operand", "github.com/mmcloughlin/avo/reg"]
dot-import-whitelist:
- fmt
# https://staticcheck.dev/docs/configuration/options/#initialisms
# Default: ["ACL", "API", "ASCII", "CPU", "CSS", "DNS", "EOF", "GUID", "HTML", "HTTP", "HTTPS", "ID", "IP", "JSON", "QPS", "RAM", "RPC", "SLA", "SMTP", "SQL", "SSH", "TCP", "TLS", "TTL", "UDP", "UI", "GID", "UID", "UUID", "URI", "URL", "UTF8", "VM", "XML", "XMPP", "XSRF", "XSS", "SIP", "RTP", "AMQP", "DB", "TS"]
initialisms:
[
"ACL",
"API",
"ASCII",
"CPU",
"CSS",
"DNS",
"EOF",
"GUID",
"HTML",
"HTTP",
"HTTPS",
"ID",
"IP",
"JSON",
"QPS",
"RAM",
"RPC",
"SLA",
"SMTP",
"SQL",
"SSH",
"TCP",
"TLS",
"TTL",
"UDP",
"UI",
"GID",
"UID",
"UUID",
"URI",
"URL",
"UTF8",
"VM",
"XML",
"XMPP",
"XSRF",
"XSS",
"SIP",
"RTP",
"AMQP",
"DB",
"TS",
]
# https://staticcheck.dev/docs/configuration/options/#http_status_code_whitelist
# Default: ["200", "400", "404", "500"]
http-status-code-whitelist: ["200", "400", "404", "500"]
# SAxxxx checks in https://staticcheck.dev/docs/configuration/options/#checks
# Example (to disable some checks): [ "all", "-SA1000", "-SA1001"]
# Run `GL_DEBUG=staticcheck golangci-lint run --enable=staticcheck` to see all available checks and enabled by config checks.
# Default: ["all", "-ST1000", "-ST1003", "-ST1016", "-ST1020", "-ST1021", "-ST1022"]
checks:
# Invalid regular expression.
# https://staticcheck.dev/docs/checks/#SA1000
- SA1000
# Invalid template.
# https://staticcheck.dev/docs/checks/#SA1001
- SA1001
# Invalid format in 'time.Parse'.
# https://staticcheck.dev/docs/checks/#SA1002
- SA1002
# Unsupported argument to functions in 'encoding/binary'.
# https://staticcheck.dev/docs/checks/#SA1003
- SA1003
# Suspiciously small untyped constant in 'time.Sleep'.
# https://staticcheck.dev/docs/checks/#SA1004
- SA1004
# Invalid first argument to 'exec.Command'.
# https://staticcheck.dev/docs/checks/#SA1005
- SA1005
# 'Printf' with dynamic first argument and no further arguments.
# https://staticcheck.dev/docs/checks/#SA1006
- SA1006
# Invalid URL in 'net/url.Parse'.
# https://staticcheck.dev/docs/checks/#SA1007
- SA1007
# Non-canonical key in 'http.Header' map.
# https://staticcheck.dev/docs/checks/#SA1008
- SA1008
# '(*regexp.Regexp).FindAll' called with 'n == 0', which will always return zero results.
# https://staticcheck.dev/docs/checks/#SA1010
- SA1010
# Various methods in the "strings" package expect valid UTF-8, but invalid input is provided.
# https://staticcheck.dev/docs/checks/#SA1011
- SA1011
# A nil 'context.Context' is being passed to a function, consider using 'context.TODO' instead.
# https://staticcheck.dev/docs/checks/#SA1012
- SA1012
# 'io.Seeker.Seek' is being called with the whence constant as the first argument, but it should be the second.
# https://staticcheck.dev/docs/checks/#SA1013
- SA1013
# Non-pointer value passed to 'Unmarshal' or 'Decode'.
# https://staticcheck.dev/docs/checks/#SA1014
- SA1014
# Using 'time.Tick' in a way that will leak. Consider using 'time.NewTicker', and only use 'time.Tick' in tests, commands and endless functions.
# https://staticcheck.dev/docs/checks/#SA1015
- SA1015
# Trapping a signal that cannot be trapped.
# https://staticcheck.dev/docs/checks/#SA1016
- SA1016
# Channels used with 'os/signal.Notify' should be buffered.
# https://staticcheck.dev/docs/checks/#SA1017
- SA1017
# 'strings.Replace' called with 'n == 0', which does nothing.
# https://staticcheck.dev/docs/checks/#SA1018
- SA1018
# Using a deprecated function, variable, constant or field.
# https://staticcheck.dev/docs/checks/#SA1019
- SA1019
# Using an invalid host:port pair with a 'net.Listen'-related function.
# https://staticcheck.dev/docs/checks/#SA1020
- SA1020
# Using 'bytes.Equal' to compare two 'net.IP'.
# https://staticcheck.dev/docs/checks/#SA1021
- SA1021
# Modifying the buffer in an 'io.Writer' implementation.
# https://staticcheck.dev/docs/checks/#SA1023
- SA1023
# A string cutset contains duplicate characters.
# https://staticcheck.dev/docs/checks/#SA1024
- SA1024
# It is not possible to use '(*time.Timer).Reset''s return value correctly.
# https://staticcheck.dev/docs/checks/#SA1025
- SA1025
# Cannot marshal channels or functions.
# https://staticcheck.dev/docs/checks/#SA1026
- SA1026
# Atomic access to 64-bit variable must be 64-bit aligned.
# https://staticcheck.dev/docs/checks/#SA1027
- SA1027
# 'sort.Slice' can only be used on slices.
# https://staticcheck.dev/docs/checks/#SA1028
- SA1028
# Inappropriate key in call to 'context.WithValue'.
# https://staticcheck.dev/docs/checks/#SA1029
- SA1029
# Invalid argument in call to a 'strconv' function.
# https://staticcheck.dev/docs/checks/#SA1030
- SA1030
# Overlapping byte slices passed to an encoder.
# https://staticcheck.dev/docs/checks/#SA1031
- SA1031
# Wrong order of arguments to 'errors.Is'.
# https://staticcheck.dev/docs/checks/#SA1032
- SA1032
# 'sync.WaitGroup.Add' called inside the goroutine, leading to a race condition.
# https://staticcheck.dev/docs/checks/#SA2000
- SA2000
# Empty critical section, did you mean to defer the unlock?.
# https://staticcheck.dev/docs/checks/#SA2001
- SA2001
# Called 'testing.T.FailNow' or 'SkipNow' in a goroutine, which isn't allowed.
# https://staticcheck.dev/docs/checks/#SA2002
- SA2002
# Deferred 'Lock' right after locking, likely meant to defer 'Unlock' instead.
# https://staticcheck.dev/docs/checks/#SA2003
- SA2003
# 'TestMain' doesn't call 'os.Exit', hiding test failures.
# https://staticcheck.dev/docs/checks/#SA3000
- SA3000
# Assigning to 'b.N' in benchmarks distorts the results.
# https://staticcheck.dev/docs/checks/#SA3001
- SA3001
# Binary operator has identical expressions on both sides.
# https://staticcheck.dev/docs/checks/#SA4000
- SA4000
# '&*x' gets simplified to 'x', it does not copy 'x'.
# https://staticcheck.dev/docs/checks/#SA4001
- SA4001
# Comparing unsigned values against negative values is pointless.
# https://staticcheck.dev/docs/checks/#SA4003
- SA4003
# The loop exits unconditionally after one iteration.
# https://staticcheck.dev/docs/checks/#SA4004
- SA4004
# Field assignment that will never be observed. Did you mean to use a pointer receiver?.
# https://staticcheck.dev/docs/checks/#SA4005
- SA4005
# A value assigned to a variable is never read before being overwritten. Forgotten error check or dead code?.
# https://staticcheck.dev/docs/checks/#SA4006
- SA4006
# The variable in the loop condition never changes, are you incrementing the wrong variable?.
# https://staticcheck.dev/docs/checks/#SA4008
- SA4008
# A function argument is overwritten before its first use.
# https://staticcheck.dev/docs/checks/#SA4009
- SA4009
# The result of 'append' will never be observed anywhere.
# https://staticcheck.dev/docs/checks/#SA4010
- SA4010
# Break statement with no effect. Did you mean to break out of an outer loop?.
# https://staticcheck.dev/docs/checks/#SA4011
- SA4011
# Comparing a value against NaN even though no value is equal to NaN.
# https://staticcheck.dev/docs/checks/#SA4012
- SA4012
# Negating a boolean twice ('!!b') is the same as writing 'b'. This is either redundant, or a typo.
# https://staticcheck.dev/docs/checks/#SA4013
- SA4013
# An if/else if chain has repeated conditions and no side-effects; if the condition didn't match the first time, it won't match the second time, either.
# https://staticcheck.dev/docs/checks/#SA4014
- SA4014
# Calling functions like 'math.Ceil' on floats converted from integers doesn't do anything useful.
# https://staticcheck.dev/docs/checks/#SA4015
- SA4015
# Certain bitwise operations, such as 'x ^ 0', do not do anything useful.
# https://staticcheck.dev/docs/checks/#SA4016
- SA4016
# Discarding the return values of a function without side effects, making the call pointless.
# https://staticcheck.dev/docs/checks/#SA4017
- SA4017
# Self-assignment of variables.
# https://staticcheck.dev/docs/checks/#SA4018
- SA4018
# Multiple, identical build constraints in the same file.
# https://staticcheck.dev/docs/checks/#SA4019
- SA4019
# Unreachable case clause in a type switch.
# https://staticcheck.dev/docs/checks/#SA4020
- SA4020
# "x = append(y)" is equivalent to "x = y".
# https://staticcheck.dev/docs/checks/#SA4021
- SA4021
# Comparing the address of a variable against nil.
# https://staticcheck.dev/docs/checks/#SA4022
- SA4022
# Impossible comparison of interface value with untyped nil.
# https://staticcheck.dev/docs/checks/#SA4023
- SA4023
# Checking for impossible return value from a builtin function.
# https://staticcheck.dev/docs/checks/#SA4024
- SA4024
# Integer division of literals that results in zero.
# https://staticcheck.dev/docs/checks/#SA4025
- SA4025
# Go constants cannot express negative zero.
# https://staticcheck.dev/docs/checks/#SA4026
- SA4026
# '(*net/url.URL).Query' returns a copy, modifying it doesn't change the URL.
# https://staticcheck.dev/docs/checks/#SA4027
- SA4027
# 'x % 1' is always zero.
# https://staticcheck.dev/docs/checks/#SA4028
- SA4028
# Ineffective attempt at sorting slice.
# https://staticcheck.dev/docs/checks/#SA4029
- SA4029
# Ineffective attempt at generating random number.
# https://staticcheck.dev/docs/checks/#SA4030
- SA4030
# Checking never-nil value against nil.
# https://staticcheck.dev/docs/checks/#SA4031
- SA4031
# Comparing 'runtime.GOOS' or 'runtime.GOARCH' against impossible value.
# https://staticcheck.dev/docs/checks/#SA4032
- SA4032
# Assignment to nil map.
# https://staticcheck.dev/docs/checks/#SA5000
- SA5000
# Deferring 'Close' before checking for a possible error.
# https://staticcheck.dev/docs/checks/#SA5001
- SA5001
# The empty for loop ("for {}") spins and can block the scheduler.
# https://staticcheck.dev/docs/checks/#SA5002
- SA5002
# Defers in infinite loops will never execute.
# https://staticcheck.dev/docs/checks/#SA5003
- SA5003
# "for { select { ..." with an empty default branch spins.
# https://staticcheck.dev/docs/checks/#SA5004
- SA5004
# The finalizer references the finalized object, preventing garbage collection.
# https://staticcheck.dev/docs/checks/#SA5005
- SA5005
# Infinite recursive call.
# https://staticcheck.dev/docs/checks/#SA5007
- SA5007
# Invalid struct tag.
# https://staticcheck.dev/docs/checks/#SA5008
- SA5008
# Invalid Printf call.
# https://staticcheck.dev/docs/checks/#SA5009
- SA5009
# Impossible type assertion.
# https://staticcheck.dev/docs/checks/#SA5010
- SA5010
# Possible nil pointer dereference.
# https://staticcheck.dev/docs/checks/#SA5011
- SA5011
# Passing odd-sized slice to function expecting even size.
# https://staticcheck.dev/docs/checks/#SA5012
- SA5012
# Using 'regexp.Match' or related in a loop, should use 'regexp.Compile'.
# https://staticcheck.dev/docs/checks/#SA6000
- SA6000
# Missing an optimization opportunity when indexing maps by byte slices.
# https://staticcheck.dev/docs/checks/#SA6001
- SA6001
# Storing non-pointer values in 'sync.Pool' allocates memory.
# https://staticcheck.dev/docs/checks/#SA6002
- SA6002
# Converting a string to a slice of runes before ranging over it.
# https://staticcheck.dev/docs/checks/#SA6003
- SA6003
# Inefficient string comparison with 'strings.ToLower' or 'strings.ToUpper'.
# https://staticcheck.dev/docs/checks/#SA6005
- SA6005
# Using io.WriteString to write '[]byte'.
# https://staticcheck.dev/docs/checks/#SA6006
- SA6006
# Defers in range loops may not run when you expect them to.
# https://staticcheck.dev/docs/checks/#SA9001
- SA9001
# Using a non-octal 'os.FileMode' that looks like it was meant to be in octal.
# https://staticcheck.dev/docs/checks/#SA9002
- SA9002
# Empty body in an if or else branch.
# https://staticcheck.dev/docs/checks/#SA9003
- SA9003
# Only the first constant has an explicit type.
# https://staticcheck.dev/docs/checks/#SA9004
- SA9004
# Trying to marshal a struct with no public fields nor custom marshaling.
# https://staticcheck.dev/docs/checks/#SA9005
- SA9005
# Dubious bit shifting of a fixed size integer value.
# https://staticcheck.dev/docs/checks/#SA9006
- SA9006
# Deleting a directory that shouldn't be deleted.
# https://staticcheck.dev/docs/checks/#SA9007
- SA9007
# 'else' branch of a type assertion is probably not reading the right value.
# https://staticcheck.dev/docs/checks/#SA9008
- SA9008
# Ineffectual Go compiler directive.
# https://staticcheck.dev/docs/checks/#SA9009
- SA9009
# Incorrect or missing package comment.
# https://staticcheck.dev/docs/checks/#ST1000
- ST1000
# Dot imports are discouraged.
# https://staticcheck.dev/docs/checks/#ST1001
- ST1001
# Poorly chosen identifier.
# https://staticcheck.dev/docs/checks/#ST1003
- ST1003
# Incorrectly formatted error string.
# https://staticcheck.dev/docs/checks/#ST1005
- ST1005
# Poorly chosen receiver name.
# https://staticcheck.dev/docs/checks/#ST1006
- ST1006
# A function's error value should be its last return value.
# https://staticcheck.dev/docs/checks/#ST1008
- ST1008
# Poorly chosen name for variable of type 'time.Duration'.
# https://staticcheck.dev/docs/checks/#ST1011
- ST1011
# Poorly chosen name for error variable.
# https://staticcheck.dev/docs/checks/#ST1012
- ST1012
# Should use constants for HTTP error codes, not magic numbers.
# https://staticcheck.dev/docs/checks/#ST1013
- ST1013
# A switch's default case should be the first or last case.
# https://staticcheck.dev/docs/checks/#ST1015
- ST1015
# Use consistent method receiver names.
# https://staticcheck.dev/docs/checks/#ST1016
- ST1016
# Don't use Yoda conditions.
# https://staticcheck.dev/docs/checks/#ST1017
- ST1017
# Avoid zero-width and control characters in string literals.
# https://staticcheck.dev/docs/checks/#ST1018
- ST1018
# Importing the same package multiple times.
# https://staticcheck.dev/docs/checks/#ST1019
- ST1019
# The documentation of an exported function should start with the function's name.
# https://staticcheck.dev/docs/checks/#ST1020
- ST1020
# The documentation of an exported type should start with type's name.
# https://staticcheck.dev/docs/checks/#ST1021
- ST1021
# The documentation of an exported variable or constant should start with variable's name.
# https://staticcheck.dev/docs/checks/#ST1022
- ST1022
# Redundant type in variable declaration.
# https://staticcheck.dev/docs/checks/#ST1023
- ST1023
# Use plain channel send or receive instead of single-case select.
# https://staticcheck.dev/docs/checks/#S1000
- S1000
# Replace for loop with call to copy.
# https://staticcheck.dev/docs/checks/#S1001
- S1001
# Omit comparison with boolean constant.
# https://staticcheck.dev/docs/checks/#S1002
- S1002
# Replace call to 'strings.Index' with 'strings.Contains'.
# https://staticcheck.dev/docs/checks/#S1003
- S1003
# Replace call to 'bytes.Compare' with 'bytes.Equal'.
# https://staticcheck.dev/docs/checks/#S1004
- S1004
# Drop unnecessary use of the blank identifier.
# https://staticcheck.dev/docs/checks/#S1005
- S1005
# Use "for { ... }" for infinite loops.
# https://staticcheck.dev/docs/checks/#S1006
- S1006
# Simplify regular expression by using raw string literal.
# https://staticcheck.dev/docs/checks/#S1007
- S1007
# Simplify returning boolean expression.
# https://staticcheck.dev/docs/checks/#S1008
- S1008
# Omit redundant nil check on slices, maps, and channels.
# https://staticcheck.dev/docs/checks/#S1009
- S1009
# Omit default slice index.
# https://staticcheck.dev/docs/checks/#S1010
- S1010
# Use a single 'append' to concatenate two slices.
# https://staticcheck.dev/docs/checks/#S1011
- S1011
# Replace 'time.Now().Sub(x)' with 'time.Since(x)'.
# https://staticcheck.dev/docs/checks/#S1012
- S1012
# Use a type conversion instead of manually copying struct fields.
# https://staticcheck.dev/docs/checks/#S1016
- S1016
# Replace manual trimming with 'strings.TrimPrefix'.
# https://staticcheck.dev/docs/checks/#S1017
- S1017
# Use "copy" for sliding elements.
# https://staticcheck.dev/docs/checks/#S1018
- S1018
# Simplify "make" call by omitting redundant arguments.
# https://staticcheck.dev/docs/checks/#S1019
- S1019
# Omit redundant nil check in type assertion.
# https://staticcheck.dev/docs/checks/#S1020
- S1020
# Merge variable declaration and assignment.
# https://staticcheck.dev/docs/checks/#S1021
- S1021
# Omit redundant control flow.
# https://staticcheck.dev/docs/checks/#S1023
- S1023
# Replace 'x.Sub(time.Now())' with 'time.Until(x)'.
# https://staticcheck.dev/docs/checks/#S1024
- S1024
# Don't use 'fmt.Sprintf("%s", x)' unnecessarily.
# https://staticcheck.dev/docs/checks/#S1025
- S1025
# Simplify error construction with 'fmt.Errorf'.
# https://staticcheck.dev/docs/checks/#S1028
- S1028
# Range over the string directly.
# https://staticcheck.dev/docs/checks/#S1029
- S1029
# Use 'bytes.Buffer.String' or 'bytes.Buffer.Bytes'.
# https://staticcheck.dev/docs/checks/#S1030
- S1030
# Omit redundant nil check around loop.
# https://staticcheck.dev/docs/checks/#S1031
- S1031
# Use 'sort.Ints(x)', 'sort.Float64s(x)', and 'sort.Strings(x)'.
# https://staticcheck.dev/docs/checks/#S1032
- S1032
# Unnecessary guard around call to "delete".
# https://staticcheck.dev/docs/checks/#S1033
- S1033
# Use result of type assertion to simplify cases.
# https://staticcheck.dev/docs/checks/#S1034
- S1034
# Redundant call to 'net/http.CanonicalHeaderKey' in method call on 'net/http.Header'.
# https://staticcheck.dev/docs/checks/#S1035
- S1035
# Unnecessary guard around map access.
# https://staticcheck.dev/docs/checks/#S1036
- S1036
# Elaborate way of sleeping.
# https://staticcheck.dev/docs/checks/#S1037
- S1037
# Unnecessarily complex way of printing formatted string.
# https://staticcheck.dev/docs/checks/#S1038
- S1038
# Unnecessary use of 'fmt.Sprint'.
# https://staticcheck.dev/docs/checks/#S1039
- S1039
# Type assertion to current type.
# https://staticcheck.dev/docs/checks/#S1040
- S1040
# Apply De Morgan's law.
# https://staticcheck.dev/docs/checks/#QF1001
- QF1001
# Convert untagged switch to tagged switch.
# https://staticcheck.dev/docs/checks/#QF1002
- QF1002
# Convert if/else-if chain to tagged switch.
# https://staticcheck.dev/docs/checks/#QF1003
- QF1003
# Use 'strings.ReplaceAll' instead of 'strings.Replace' with 'n == -1'.
# https://staticcheck.dev/docs/checks/#QF1004
- QF1004
# Expand call to 'math.Pow'.
# https://staticcheck.dev/docs/checks/#QF1005
- QF1005
# Lift 'if'+'break' into loop condition.
# https://staticcheck.dev/docs/checks/#QF1006
- QF1006
# Merge conditional assignment into variable declaration.
# https://staticcheck.dev/docs/checks/#QF1007
- QF1007
# Omit embedded fields from selector expression.
# https://staticcheck.dev/docs/checks/#QF1008
- QF1008
# Use 'time.Time.Equal' instead of '==' operator.
# https://staticcheck.dev/docs/checks/#QF1009
- QF1009
# Convert slice of bytes to string when printing it.
# https://staticcheck.dev/docs/checks/#QF1010
- QF1010
# Omit redundant type from variable declaration.
# https://staticcheck.dev/docs/checks/#QF1011
- QF1011
# Use 'fmt.Fprintf(x, ...)' instead of 'x.Write(fmt.Sprintf(...))'.
# https://staticcheck.dev/docs/checks/#QF1012
- QF1012
unconvert:
# Remove conversions that force intermediate rounding.
# Default: false
fast-math: true
# Be more conservative (experimental).
# Default: false
safe: true
usestdlibvars:
# Suggest the use of http.MethodXX.
# Default: true
http-method: false
# Suggest the use of http.StatusXX.
# Default: true
http-status-code: false
# Suggest the use of time.Month in time.Date.
# Default: false
time-date-month: true
# Suggest the use of time.Weekday.String().
# Default: true
time-weekday: true
# Suggest the use of time.Month.String().
# Default: false
time-month: true
# Suggest the use of time.Layout.
# Default: false
time-layout: true
# Suggest the use of crypto.Hash.String().
# Default: false
crypto-hash: true
# Suggest the use of rpc.DefaultXXPath.
# Default: false
default-rpc-path: true
# Suggest the use of sql.LevelXX.String().
# Default: false
sql-isolation-level: true
# Suggest the use of tls.SignatureScheme.String().
# Default: false
tls-signature-scheme: true
# Suggest the use of constant.Kind.String().
# Default: false
constant-kind: true
wsl_v5:
allow-first-in-block: true
allow-whole-block: true
branch-max-lines: 2
case-max-lines: 2
default: all
disable:
- if
- err
exclusions: exclusions:
generated: lax generated: lax
presets: presets:
@@ -62,8 +786,6 @@ linters:
- legacy - legacy
- std-error-handling - std-error-handling
paths: paths:
- data
- vendor
- third_party$ - third_party$
- builtin$ - builtin$
- examples$ - examples$
@@ -74,8 +796,6 @@ formatters:
exclusions: exclusions:
generated: lax generated: lax
paths: paths:
- data
- vendor
- third_party$ - third_party$
- builtin$ - builtin$
- examples$ - examples$

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

@@ -1,3 +1,4 @@
// Package main simple test for battery funtionality.
package main package main
import ( import (

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,
@@ -702,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": {
@@ -749,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

@@ -1,3 +1,4 @@
// Package main status string generator for i3bar.
package main package main
import ( import (
@@ -8,6 +9,7 @@ import (
"log" "log"
"os" "os"
"os/signal" "os/signal"
"strconv"
"strings" "strings"
"syscall" "syscall"
@@ -15,7 +17,7 @@ import (
) )
//go:embed default-config.json //go:embed default-config.json
var DefaultConfig []byte var DefaultConfig []byte //nolint: godoclint, grouper, nolintlint
// Program entry point. // Program entry point.
func main() { func main() {
@@ -43,14 +45,11 @@ func main() {
Conf.Values.SoundVolume = "🔊:0%" Conf.Values.SoundVolume = "🔊:0%"
// TODO: Проставить дефолтные значения для глобальных переменных модулей. // TODO: Проставить дефолтные значения для глобальных переменных модулей.
Conf.Values.BatteryString = fmt.Sprintf( Conf.Values.BatteryString = "<span color='" + Conf.Battery.Color +
"<span color='%s' background='%s' font='%s' size='%s'>%s</span>", "' background='" + Conf.Battery.Background +
Conf.Battery.Color, "' font='" + Conf.Battery.SymbolFont +
Conf.Battery.Background, "' size='" + Conf.Battery.SymbolFontSize +
Conf.Battery.SymbolFont, "'>" + Conf.Battery.Symbol + "</span>"
Conf.Battery.SymbolFontSize,
Conf.Battery.Symbol,
)
Conf.Values.BatteryString += fmt.Sprintf( Conf.Values.BatteryString += fmt.Sprintf(
"<span color='%s' background='%s' font='%s' size='%s'> ??%% •</span>", "<span color='%s' background='%s' font='%s' size='%s'> ??%% •</span>",
@@ -84,6 +83,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()
@@ -96,6 +103,7 @@ func main() {
// Kick signal handler // Kick signal handler
go Conf.SigHandler() go Conf.SigHandler()
signal.Notify(Conf.Channels.SigChan, signal.Notify(Conf.Channels.SigChan,
syscall.SIGUSR1, syscall.SIGUSR1,
syscall.SIGUSR2, syscall.SIGUSR2,
@@ -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
@@ -166,35 +178,25 @@ func main() {
// TODO: впилить для первого и последнего батона separator // TODO: впилить для первого и последнего батона separator
if Conf.AppButtons.Enabled { if Conf.AppButtons.Enabled {
for num, app := range Conf.Apps { for _, app := range Conf.Apps {
var b lib.I3BarOutBlock var (
b lib.I3BarOutBlock
sb strings.Builder
)
if num == 0 { sb.WriteString("<span color='")
b.FullText = fmt.Sprintf( sb.WriteString(app.Color)
"<span color='%s' background='%s' font='%s' size='%s'>%s</span>", sb.WriteString("' background='")
Conf.AppButtons.Separator.Left.Color, sb.WriteString(app.Background)
Conf.AppButtons.Separator.Left.Background, sb.WriteString("' font='")
Conf.AppButtons.Separator.Left.Font, sb.WriteString(app.Font)
Conf.AppButtons.Separator.Left.FontSize, sb.WriteString("' size='")
Conf.AppButtons.Separator.Left.Symbol, sb.WriteString(app.FontSize)
) sb.WriteString("'>")
} sb.WriteString(app.FullText)
sb.WriteString("</span>")
b.FullText = app.FullText b.FullText = sb.String()
if num == len(Conf.Apps) {
b.FullText = fmt.Sprintf(
"<span color='%s' background='%s' font='%s' size='%s'>%s</span>",
Conf.AppButtons.Separator.Right.Color,
Conf.AppButtons.Separator.Right.Background,
Conf.AppButtons.Separator.Right.Font,
Conf.AppButtons.Separator.Right.FontSize,
Conf.AppButtons.Separator.Right.Symbol,
)
}
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
@@ -217,42 +219,55 @@ func main() {
} }
if Conf.CPUTemp.Enabled { if Conf.CPUTemp.Enabled {
var b lib.I3BarOutBlock var (
b lib.I3BarOutBlock
sb strings.Builder
)
b.Color = Conf.CPUTemp.Color b.Color = Conf.CPUTemp.Color
b.Background = Conf.CPUTemp.Background b.Background = Conf.CPUTemp.Background
if Conf.CPUTemp.Separator.Left.Enabled { if Conf.CPUTemp.Separator.Left.Enabled {
b.FullText = fmt.Sprintf( sb.WriteString("<span color='")
"<span color='%s' background='%s' font='%s' size='%s'>%s</span>", sb.WriteString(Conf.CPUTemp.Separator.Left.Color)
Conf.CPUTemp.Separator.Left.Color, sb.WriteString("' background='")
Conf.CPUTemp.Separator.Left.Background, sb.WriteString(Conf.CPUTemp.Separator.Left.Background)
Conf.CPUTemp.Separator.Left.Font, sb.WriteString("' font='")
Conf.CPUTemp.Separator.Left.FontSize, sb.WriteString(Conf.CPUTemp.Separator.Left.Font)
Conf.CPUTemp.Separator.Left.Symbol, sb.WriteString("' size='")
) sb.WriteString(Conf.CPUTemp.Separator.Left.FontSize)
sb.WriteString("'>")
sb.WriteString(Conf.CPUTemp.Separator.Left.Symbol)
sb.WriteString("</span>")
} }
b.FullText += fmt.Sprintf( sb.WriteString("<span color='")
"<span color='%s' background='%s' font='%s' size='%s'>CPU: %d°</span>", sb.WriteString(Conf.CPUTemp.Color)
Conf.CPUTemp.Color, sb.WriteString("' background='")
Conf.CPUTemp.Background, sb.WriteString(Conf.CPUTemp.Background)
Conf.CPUTemp.Font, sb.WriteString("' font='")
Conf.CPUTemp.FontSize, sb.WriteString(Conf.CPUTemp.Font)
Conf.Values.CPUTemperature, sb.WriteString("' size='")
) sb.WriteString(Conf.CPUTemp.FontSize)
sb.WriteString("'>CPU: ")
sb.WriteString(strconv.FormatInt(Conf.Values.CPUTemperature, 10))
sb.WriteString("°</span>")
if Conf.CPUTemp.Separator.Right.Enabled { if Conf.CPUTemp.Separator.Right.Enabled {
b.FullText += fmt.Sprintf( sb.WriteString("<span color='")
"<span color='%s' background='%s' font='%s' size='%s'>%s</span>", sb.WriteString(Conf.CPUTemp.Separator.Right.Color)
Conf.CPUTemp.Separator.Right.Color, sb.WriteString("' background='")
Conf.CPUTemp.Separator.Right.Background, sb.WriteString(Conf.CPUTemp.Separator.Right.Background)
Conf.CPUTemp.Separator.Right.Font, sb.WriteString("' font='")
Conf.CPUTemp.Separator.Right.FontSize, sb.WriteString(Conf.CPUTemp.Separator.Right.Font)
Conf.CPUTemp.Separator.Right.Symbol, sb.WriteString("' size='")
) sb.WriteString(Conf.CPUTemp.Separator.Right.FontSize)
sb.WriteString("'>")
sb.WriteString(Conf.CPUTemp.Separator.Right.Symbol)
sb.WriteString("</span>")
} }
b.FullText = sb.String()
b.Markup = "pango" b.Markup = "pango"
b.Separator = false b.Separator = false
@@ -260,56 +275,66 @@ func main() {
} }
if Conf.Mem.Enabled { if Conf.Mem.Enabled {
var b lib.I3BarOutBlock var (
b lib.I3BarOutBlock
sb strings.Builder
)
b.Color = Conf.Mem.Color b.Color = Conf.Mem.Color
b.Background = Conf.Mem.Background b.Background = Conf.Mem.Background
if Conf.Mem.Separator.Left.Enabled { if Conf.Mem.Separator.Left.Enabled {
b.FullText = fmt.Sprintf( sb.WriteString("<span color='")
"<span color='%s' background='%s' font='%s' size='%s'>%s</span>", sb.WriteString(Conf.Mem.Separator.Left.Color)
Conf.Mem.Separator.Left.Color, sb.WriteString("' background='")
Conf.Mem.Separator.Left.Background, sb.WriteString(Conf.Mem.Separator.Left.Background)
Conf.Mem.Separator.Left.Font, sb.WriteString("' font='")
Conf.Mem.Separator.Left.FontSize, sb.WriteString(Conf.Mem.Separator.Left.Font)
Conf.Mem.Separator.Left.Symbol, sb.WriteString("' size='")
) sb.WriteString(Conf.Mem.Separator.Left.FontSize)
sb.WriteString("'>")
sb.WriteString(Conf.Mem.Separator.Left.Symbol)
sb.WriteString("</span>")
} }
sb.WriteString("<span color='")
sb.WriteString(Conf.Mem.Color)
sb.WriteString("' background='")
sb.WriteString(Conf.Mem.Background)
sb.WriteString("' font='")
sb.WriteString(Conf.Mem.Font)
sb.WriteString("' size='")
sb.WriteString(Conf.Mem.FontSize)
sb.WriteString("'>M:")
sb.WriteString(strconv.FormatUint(Conf.Values.Memory.Usedpct, 10))
sb.WriteString("% SHM:")
sb.WriteString(strconv.FormatUint(Conf.Values.Memory.Shared, 10))
if Conf.Mem.ShowSwap { if Conf.Mem.ShowSwap {
b.FullText += fmt.Sprintf( sb.WriteString("% SW:")
"<span color='%s' background='%s' font='%s' size='%s'>M:%d%% SHM:%dM SW:%dM</span>", sb.WriteString(strconv.FormatUint(Conf.Values.Memory.Swap, 10))
Conf.Mem.Color, sb.WriteString("M")
Conf.Mem.Background,
Conf.Mem.Font,
Conf.Mem.FontSize,
Conf.Values.Memory.Usedpct,
Conf.Values.Memory.Shared,
Conf.Values.Memory.Swap,
)
} else { } else {
b.FullText += fmt.Sprintf( sb.WriteString("%")
"<span color='%s' background='%s' font='%s' size='%s'>M:%d%% SHM:%dM</span>",
Conf.Mem.Color,
Conf.Mem.Background,
Conf.Mem.Font,
Conf.Mem.FontSize,
Conf.Values.Memory.Usedpct,
Conf.Values.Memory.Shared,
)
} }
sb.WriteString("</span>")
if Conf.Mem.Separator.Right.Enabled { if Conf.Mem.Separator.Right.Enabled {
b.FullText += fmt.Sprintf( sb.WriteString("<span color='")
"<span color='%s' background='%s' font='%s' size='%s'>%s</span>", sb.WriteString(Conf.Mem.Separator.Right.Color)
Conf.Mem.Separator.Right.Color, sb.WriteString("' background='")
Conf.Mem.Separator.Right.Background, sb.WriteString(Conf.Mem.Separator.Right.Background)
Conf.Mem.Separator.Right.Font, sb.WriteString("' font='")
Conf.Mem.Separator.Right.FontSize, sb.WriteString(Conf.Mem.Separator.Right.Font)
Conf.Mem.Separator.Right.Symbol, sb.WriteString("' size='")
) sb.WriteString(Conf.Mem.Separator.Right.FontSize)
sb.WriteString("'>")
sb.WriteString(Conf.Mem.Separator.Right.Symbol)
sb.WriteString("</span>")
} }
b.FullText = sb.String()
b.Markup = "pango" b.Markup = "pango"
b.Separator = false b.Separator = false
@@ -317,42 +342,55 @@ func main() {
} }
if Conf.LA.Enabled { if Conf.LA.Enabled {
var b lib.I3BarOutBlock var (
b lib.I3BarOutBlock
sb strings.Builder
)
b.Color = Conf.LA.Color b.Color = Conf.LA.Color
b.Background = Conf.LA.Background b.Background = Conf.LA.Background
if Conf.LA.Separator.Left.Enabled { if Conf.LA.Separator.Left.Enabled {
b.FullText = fmt.Sprintf( sb.WriteString("<span color='")
"<span color='%s' background='%s' font='%s' size='%s'>%s</span>", sb.WriteString(Conf.LA.Separator.Left.Color)
Conf.LA.Separator.Left.Color, sb.WriteString("' background='")
Conf.LA.Separator.Left.Background, sb.WriteString(Conf.LA.Separator.Left.Background)
Conf.LA.Separator.Left.Font, sb.WriteString("' font='")
Conf.LA.Separator.Left.FontSize, sb.WriteString(Conf.LA.Separator.Left.Font)
Conf.LA.Separator.Left.Symbol, sb.WriteString("' size='")
) sb.WriteString(Conf.LA.Separator.Left.FontSize)
sb.WriteString("'>")
sb.WriteString(Conf.LA.Separator.Left.Symbol)
sb.WriteString("</span>")
} }
b.FullText += fmt.Sprintf( sb.WriteString("<span color='")
"<span color='%s' background='%s' font='%s' size='%s'>LA:%s</span>", sb.WriteString(Conf.LA.Color)
Conf.LA.Color, sb.WriteString("' background='")
Conf.LA.Background, sb.WriteString(Conf.LA.Background)
Conf.LA.Font, sb.WriteString("' font='")
Conf.LA.FontSize, sb.WriteString(Conf.LA.Font)
Conf.Values.La, sb.WriteString("' size='")
) sb.WriteString(Conf.LA.FontSize)
sb.WriteString("'>LA:")
sb.WriteString(Conf.Values.La)
sb.WriteString("</span>")
if Conf.LA.Separator.Right.Enabled { if Conf.LA.Separator.Right.Enabled {
b.FullText += fmt.Sprintf( sb.WriteString("<span color='")
"<span color='%s' background='%s' font='%s' size='%s'>%s</span>", sb.WriteString(Conf.LA.Separator.Right.Color)
Conf.LA.Separator.Right.Color, sb.WriteString("' background='")
Conf.LA.Separator.Right.Background, sb.WriteString(Conf.LA.Separator.Right.Background)
Conf.LA.Separator.Right.Font, sb.WriteString("' font='")
Conf.LA.Separator.Right.FontSize, sb.WriteString(Conf.LA.Separator.Right.Font)
Conf.LA.Separator.Right.Symbol, sb.WriteString("' size='")
) sb.WriteString(Conf.LA.Separator.Right.FontSize)
sb.WriteString("'>")
sb.WriteString(Conf.LA.Separator.Right.Symbol)
sb.WriteString("</span>")
} }
b.FullText = sb.String()
b.Markup = "pango" b.Markup = "pango"
b.Separator = false b.Separator = false
@@ -519,44 +557,101 @@ func main() {
j = append(j, b) j = append(j, b)
} }
if Conf.Clock.Enabled { if Conf.CmdRun.Enabled {
var b lib.I3BarOutBlock 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 {
var (
b lib.I3BarOutBlock
sb strings.Builder
)
b.Name = `wallclock` b.Name = `wallclock`
b.Color = Conf.Clock.Color b.Color = Conf.Clock.Color
b.Background = Conf.Clock.Background b.Background = Conf.Clock.Background
if Conf.Clock.Separator.Left.Enabled { if Conf.Clock.Separator.Left.Enabled {
b.FullText = fmt.Sprintf( sb.WriteString("<span color='")
"<span color='%s' background='%s' font='%s' size='%s'>%s</span>", sb.WriteString(Conf.Clock.Separator.Left.Color)
Conf.Clock.Separator.Left.Color, sb.WriteString("' background='")
Conf.Clock.Separator.Left.Background, sb.WriteString(Conf.Clock.Separator.Left.Background)
Conf.Clock.Separator.Left.Font, sb.WriteString("' font='")
Conf.Clock.Separator.Left.FontSize, sb.WriteString(Conf.Clock.Separator.Left.Font)
Conf.Clock.Separator.Left.Symbol, sb.WriteString("' size='")
) sb.WriteString(Conf.Clock.Separator.Left.FontSize)
sb.WriteString("'>")
sb.WriteString(Conf.Clock.Separator.Left.Symbol)
sb.WriteString("</span>")
} }
b.FullText += fmt.Sprintf( sb.WriteString("<span color='")
"<span color='%s' background='%s' font='%s' size='%s'>%s</span>", sb.WriteString(Conf.Clock.Color)
Conf.Clock.Color, sb.WriteString("' background='")
Conf.Clock.Background, sb.WriteString(Conf.Clock.Background)
Conf.Clock.Font, sb.WriteString("' font='")
Conf.Clock.FontSize, sb.WriteString(Conf.Clock.Font)
Conf.Values.ClockTime, sb.WriteString("' size='")
) sb.WriteString(Conf.Clock.FontSize)
sb.WriteString("'>")
sb.WriteString(Conf.Values.ClockTime)
sb.WriteString("</span>")
if Conf.Clock.Separator.Right.Enabled { if Conf.Clock.Separator.Right.Enabled {
b.FullText += fmt.Sprintf( sb.WriteString("<span color='")
"<span color='%s' background='%s' font='%s' size='%s'>%s</span>", sb.WriteString(Conf.Clock.Separator.Right.Color)
Conf.Clock.Separator.Right.Color, sb.WriteString("' background='")
Conf.Clock.Separator.Right.Background, sb.WriteString(Conf.Clock.Separator.Right.Background)
Conf.Clock.Separator.Right.Font, sb.WriteString("' font='")
Conf.Clock.Separator.Right.FontSize, sb.WriteString(Conf.Clock.Separator.Right.Font)
Conf.Clock.Separator.Right.Symbol, sb.WriteString("' size='")
) sb.WriteString(Conf.Clock.Separator.Right.FontSize)
sb.WriteString("'>")
sb.WriteString(Conf.Clock.Separator.Right.Symbol)
sb.WriteString("</span>")
} }
b.FullText = sb.String()
b.Markup = "pango" b.Markup = "pango"
b.Separator = false b.Separator = false

View File

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

View File

@@ -1,3 +1,4 @@
// Package lib represents guts of i3status-go.
package lib package lib
import ( import (
@@ -29,7 +30,7 @@ func (c *MyConfig) UpdateBatteryInfo() {
} }
var ( var (
batteries []*battery.Battery = []*battery.Battery{} batteries = []*battery.Battery{}
Batts string Batts string
ch int ch int
status string status string
@@ -52,6 +53,7 @@ func (c *MyConfig) UpdateBatteryInfo() {
if err != nil { if err != nil {
log.Printf("Unable to read file %s: %s", file, err) log.Printf("Unable to read file %s: %s", file, err)
continue continue
} }
@@ -65,6 +67,7 @@ func (c *MyConfig) UpdateBatteryInfo() {
if err != nil { if err != nil {
log.Printf("Unable to read file %s: %s", statusFile, err) log.Printf("Unable to read file %s: %s", statusFile, err)
continue continue
} }
@@ -91,6 +94,7 @@ func (c *MyConfig) UpdateBatteryInfo() {
if err != nil { if err != nil {
log.Printf("Unable to convert string from file %s to integer", file) log.Printf("Unable to convert string from file %s to integer", file)
continue continue
} }
@@ -100,9 +104,9 @@ func (c *MyConfig) UpdateBatteryInfo() {
} }
} }
} else { } else {
batteries, _ = battery.GetAll()
// In theory, this module should give for each entry its separate err, but in practice it gives // 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. // one single err for all entries, so we cannot detemine whist exatly entry errored.
batteries, _ = battery.GetAll()
} }
var ( var (

View File

@@ -4,16 +4,18 @@ import (
"sync" "sync"
) )
// Collection is sync.Map, that stores arbitrary data. type (
type Collection struct { // Collection is sync.Map, that stores arbitrary data.
items sync.Map Collection struct {
close chan struct{} close chan struct{}
} items sync.Map
}
// Item just interface for stoing any data. // Item just interface for stoing any data.
type item struct { item struct {
data any data any
} }
)
// NewCollection creates new instance of "collection". // NewCollection creates new instance of "collection".
func NewCollection() *Collection { func NewCollection() *Collection {
@@ -44,7 +46,7 @@ func (collection *Collection) Set(key any, value any) {
}) })
} }
// Range apply function f to all keys/values in collection. Retuns immediatly on first error. // Range apply function f to all keys/values in collection. Returns immediately on first error.
func (collection *Collection) Range(f func(key, value any) bool) { func (collection *Collection) Range(f func(key, value any) bool) {
fn := func(key, value any) bool { fn := func(key, value any) bool {
item := value.(item) item := value.(item)
@@ -63,6 +65,7 @@ func (collection *Collection) Delete(key any) {
// Close frees memory occupied by collection and deletes collection itself. // Close frees memory occupied by collection and deletes collection itself.
func (collection *Collection) Close() { func (collection *Collection) Close() {
collection.close <- struct{}{} collection.close <- struct{}{}
collection.items = sync.Map{} collection.items = sync.Map{}
} }

File diff suppressed because it is too large Load Diff

View File

@@ -41,6 +41,7 @@ func (c *MyConfig) UpdateCPUTemperature() {
if err != nil { if err != nil {
log.Printf("Unable to read from %s: %s", filename, err) log.Printf("Unable to read from %s: %s", filename, err)
err = file.Close() err = file.Close()
if err != nil { if err != nil {

View File

@@ -7,7 +7,9 @@ import (
) )
// myCron contains cron handler. // myCron contains cron handler.
var myCron gocron.Scheduler var (
myCron gocron.Scheduler
)
// RunCron parses config part, related to cron tasks and kicks cron to run. // RunCron parses config part, related to cron tasks and kicks cron to run.
func (c MyConfig) RunCron() { func (c MyConfig) RunCron() {

View File

@@ -105,9 +105,9 @@ func (c *MyConfig) I3EventParser(e *i3.WindowEvent) {
// interfaceToUint превращает данный интерфейс в Uint64. // interfaceToUint превращает данный интерфейс в Uint64.
// Если может, конечно :) . // Если может, конечно :) .
func interfaceToUint64(iface interface{}) uint64 { func interfaceToUint64(iface any) uint64 {
// А теперь мы начинаем дурдом, нам надо превратить ёбанный interface{} в []string // А теперь мы начинаем дурдом, нам надо превратить ёбанный any в []string.
// Поскольку interface{} может быть чем угодно, перестрахуемся // Поскольку any может быть чем угодно, перестрахуемся.
if reflect.TypeOf(iface).Kind() == reflect.Uint64 { if reflect.TypeOf(iface).Kind() == reflect.Uint64 {
return reflect.ValueOf(iface).Uint() return reflect.ValueOf(iface).Uint()
} }

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

@@ -113,12 +113,12 @@ func (c *MyConfig) PaReinit() error {
var err error var err error
// If we out of updates, seems someone killed pulseaudio server. Restart it. // If we out of updates, seems someone killed pulseaudio server. Restart it.
cmd := exec.Command("pulseaudio", "--check") cmd := exec.Command("pulseaudio", "--check") //nolint: noctx
if err := cmd.Run(); err == nil { if err := cmd.Run(); err == nil {
log.Printf("pulseaudio already running, but seems not responding, try gracefully kill it") log.Printf("pulseaudio already running, but seems not responding, try gracefully kill it")
cmd = exec.Command("pulseaudio", "--kill") cmd = exec.Command("pulseaudio", "--kill") //nolint: noctx
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
return fmt.Errorf("unable to kill pulseaudio: %w", err) return fmt.Errorf("unable to kill pulseaudio: %w", err)
@@ -127,7 +127,7 @@ func (c *MyConfig) PaReinit() error {
cnt := 0 cnt := 0
for { for {
cmd := exec.Command("pulseaudio", "--check") cmd := exec.Command("pulseaudio", "--check") //nolint: noctx
// PA not running. Breakout of loop. // PA not running. Breakout of loop.
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
@@ -154,10 +154,10 @@ func (c *MyConfig) PaReinit() error {
// audio. // audio.
if c.SimpleVolumePa.DontExitOnLogin { if c.SimpleVolumePa.DontExitOnLogin {
// Do not exit on any kind of login/logout events. // Do not exit on any kind of login/logout events.
cmd = exec.Command("pulseaudio", "--exit-idle-time=-1", "--start") cmd = exec.Command("pulseaudio", "--exit-idle-time=-1", "--start") //nolint: noctx
} else { } else {
// Exit immediately on logout(?). // Exit immediately on logout(?).
cmd = exec.Command("pulseaudio", "--exit-idle-time=0", "--start") cmd = exec.Command("pulseaudio", "--exit-idle-time=0", "--start") //nolint: noctx
} }
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
@@ -173,6 +173,8 @@ func (c *MyConfig) PaReinit() error {
return err return err
} }
// SVPAHandler simple volume pulse audio handler works with events sent to SVPAHandlerChan cahnnel. Left/right clicks,
// wheel turning events etc.
func (c *MyConfig) SVPAHandler() { func (c *MyConfig) SVPAHandler() {
for e := range c.Channels.SVPAHandlerChan { for e := range c.Channels.SVPAHandlerChan {
if e.Button == 3 { if e.Button == 3 {

View File

@@ -0,0 +1,41 @@
package lib
import (
"log"
"time"
)
// RunCommand 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"
) )
@@ -12,6 +14,7 @@ import (
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) <-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)
@@ -55,3 +58,41 @@ func (c *MyConfig) CleanZombies() {
time.Sleep(1 * time.Minute) time.Sleep(1 * time.Minute)
} }
} }
// RunProcess runs given command and returns contents 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

@@ -116,6 +116,7 @@ func (c *MyConfig) ParseStdin() {
break break
} }
case app.Name != "" && e.Name == app.Name: case app.Name != "" && e.Name == app.Name:
prg := append([]string{}, app.Cmd) prg := append([]string{}, app.Cmd)
prg = append(prg, app.Args...) prg = append(prg, app.Args...)

View File

@@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"net" "net"
"os" "os"
"strings"
"time" "time"
) )
@@ -11,8 +12,8 @@ import (
func (c *MyConfig) UpdateVPNStatus() { func (c *MyConfig) UpdateVPNStatus() {
var ( var (
vpnCStatus string vpnCStatus string
tcpCheck string
vpnCheck string vpnCheck string
b strings.Builder
InitialDelay = 100 * time.Millisecond InitialDelay = 100 * time.Millisecond
LoopIterationDelay = 3 * time.Second LoopIterationDelay = 3 * time.Second
Delay = InitialDelay Delay = InitialDelay
@@ -27,36 +28,47 @@ func (c *MyConfig) UpdateVPNStatus() {
if c.VPNFileCheck() { if c.VPNFileCheck() {
if c.Vpn.UpColor == "" { if c.Vpn.UpColor == "" {
vpnCheck = `⍋` b.WriteRune('⍋')
} else { } else {
vpnCheck = fmt.Sprintf(`<span foreground="%s">⍋</span>`, c.Vpn.UpColor) b.WriteString("<span foreground=\"")
b.WriteString(c.Vpn.UpColor)
b.WriteString("\">⍋</span>")
} }
} else { } else {
if c.Vpn.DownColor == "" { if c.Vpn.DownColor == "" {
vpnCheck = `⍒` b.WriteRune('⍒')
} else { } else {
vpnCheck = fmt.Sprintf(`<span foreground="%s">⍒</span>`, c.Vpn.DownColor) b.WriteString("<span foreground=\"")
b.WriteString(c.Vpn.DownColor)
b.WriteString("\">⍒</span>")
} }
} }
vpnCheck = b.String()
b.Reset()
if c.Vpn.TCPCheck.Enabled { if c.Vpn.TCPCheck.Enabled {
if c.VPNTCPCheck() { if c.VPNTCPCheck() {
if c.Vpn.UpColor == "" { if c.Vpn.UpColor == "" {
tcpCheck = `✔` b.WriteRune('✔')
} else { } else {
tcpCheck = fmt.Sprintf(`<span foreground="%s">✔</span>`, c.Vpn.UpColor) b.WriteString("<span foreground=\"")
b.WriteString(c.Vpn.UpColor)
b.WriteString("\">✔</span>")
} }
} else { } else {
if c.Vpn.DownColor == "" { if c.Vpn.DownColor == "" {
tcpCheck = `✘` b.WriteRune('✘')
} else { } else {
tcpCheck = fmt.Sprintf(`<span foreground="%s">✘</span>`, c.Vpn.DownColor) b.WriteString("<span foreground=\"")
b.WriteString(c.Vpn.DownColor)
b.WriteString("\">✘</span>")
} }
} }
vpnCStatus = fmt.Sprintf("VPN:%s:%s", vpnCheck, tcpCheck) vpnCStatus = "VPN:" + vpnCheck + ":" + b.String()
} else { } else {
vpnCStatus = fmt.Sprintf("VPN:%s", vpnCheck) vpnCStatus = "VPN:" + vpnCheck
} }
if c.Values.VPNStatus != vpnCStatus { if c.Values.VPNStatus != vpnCStatus {
@@ -66,11 +78,11 @@ func (c *MyConfig) UpdateVPNStatus() {
} }
} }
// VPNTCPCheck intended to check arbitrary service inside vpn segment, to indicate that openvpn sevice not stoned. // VPNTCPCheck intended to check arbitrary service inside vpn segment, to indicate that openvpn service not stoned.
func (c *MyConfig) VPNTCPCheck() bool { func (c *MyConfig) VPNTCPCheck() bool {
conn, err := net.DialTimeout( conn, err := net.DialTimeout( //nolint: noctx
"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),
) )