Merge pull request #1 from NotAShelf/v2

go rewrite
This commit is contained in:
raf 2024-01-11 19:51:43 +00:00 committed by GitHub
commit 2b94d2eda1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 437 additions and 552 deletions

22
.gitignore vendored
View file

@ -1 +1,21 @@
/target
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
# vendor/
# Go workspace file
go.work
# Config file
config.json

351
Cargo.lock generated
View file

@ -1,351 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "batmon"
version = "0.1.0"
dependencies = [
"glob",
"notify",
]
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "crossbeam-channel"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "176dc175b78f56c0f321911d9c8eb2b77a78a4860b9c19db83835fea1a46649b"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
[[package]]
name = "filetime"
version = "0.2.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"windows-sys 0.52.0",
]
[[package]]
name = "fsevent-sys"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2"
dependencies = [
"libc",
]
[[package]]
name = "glob"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]]
name = "inotify"
version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff"
dependencies = [
"bitflags 1.3.2",
"inotify-sys",
"libc",
]
[[package]]
name = "inotify-sys"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb"
dependencies = [
"libc",
]
[[package]]
name = "kqueue"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c"
dependencies = [
"kqueue-sys",
"libc",
]
[[package]]
name = "kqueue-sys"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b"
dependencies = [
"bitflags 1.3.2",
"libc",
]
[[package]]
name = "libc"
version = "0.2.152"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7"
[[package]]
name = "log"
version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
[[package]]
name = "mio"
version = "0.8.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09"
dependencies = [
"libc",
"log",
"wasi",
"windows-sys 0.48.0",
]
[[package]]
name = "notify"
version = "6.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d"
dependencies = [
"bitflags 2.4.1",
"crossbeam-channel",
"filetime",
"fsevent-sys",
"inotify",
"kqueue",
"libc",
"log",
"mio",
"walkdir",
"windows-sys 0.48.0",
]
[[package]]
name = "redox_syscall"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
dependencies = [
"bitflags 1.3.2",
]
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]
[[package]]
name = "walkdir"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee"
dependencies = [
"same-file",
"winapi-util",
]
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets 0.48.5",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets 0.52.0",
]
[[package]]
name = "windows-targets"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
dependencies = [
"windows_aarch64_gnullvm 0.48.5",
"windows_aarch64_msvc 0.48.5",
"windows_i686_gnu 0.48.5",
"windows_i686_msvc 0.48.5",
"windows_x86_64_gnu 0.48.5",
"windows_x86_64_gnullvm 0.48.5",
"windows_x86_64_msvc 0.48.5",
]
[[package]]
name = "windows-targets"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
dependencies = [
"windows_aarch64_gnullvm 0.52.0",
"windows_aarch64_msvc 0.52.0",
"windows_i686_gnu 0.52.0",
"windows_i686_msvc 0.52.0",
"windows_x86_64_gnu 0.52.0",
"windows_x86_64_gnullvm 0.52.0",
"windows_x86_64_msvc 0.52.0",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]]
name = "windows_i686_gnu"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]]
name = "windows_i686_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"

View file

@ -1,10 +0,0 @@
[package]
name = "batmon"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
notify = "6.1"
glob = "0.3"

70
README.md Normal file
View file

@ -0,0 +1,70 @@
# 🔋 Batmon
> **Batmon** is a dead-simple battery monitor for Linux written in Go. It
> provides real-time monitoring of battery status and adjusts the power profile
> accordingly to optimize battery life.
## Features
- Real-time monitoring of battery status
- Adjustment of power profile based on battery status
- Support for custom commands and extra commands
- Configuration via a JSON file
- Not written in Rust (the codebase is _readable_)
## Installation
## Prerequisites
- Upower
- powerprofilesctl
- Nix or Go
### Nix
**Batmon** is primarily distributed through a Nix flake. You may install it
manually using `nix profile install github:NotAShelf/batmon`
### Manually
```console
go install . # this will install Batmon in your $GOPATH
```
## Usage
To start using Batmon, use the following command:
```
batmon
```
By default, Gomon will load the configuration from config.json in the current
directory. You can specify a different configuration file using the `--config`
flag:
```console
gomon -c /path/to/config.json
```
The configuration file should contain a list of batteries to monitor, along
with any custom commands or extra commands to execute. Here's an example of
a configuration file:
```json
{
"batPaths": [
{
"path": "/sys/class/power_supply/BAT0",
"command": "powerprofilesctl set performance",
"extraCommand": "echo 'Battery is charging' | wall"
}
]
}
```
- You can leave `command` empty to use the default behaviour - which will
switch active powerprofile using `powerprofiles set performance | balanced`
- `extraCommand`, if provided, will be executed in addition to the `command`
value.

4
TODO
View file

@ -1,4 +0,0 @@
- nix integration
- unit tests
- use the battery crate
- rewrite in Go as soon as rust frustrates me enough

View file

@ -1,23 +0,0 @@
{
lib,
rustPlatform,
...
}: let
pname = "batmon";
version = "unstable-2024-01-08";
in
rustPlatform.buildRustPackage {
inherit pname version;
src = lib.cleanSource ./.;
cargoHash = "sha256-d9wWr17BnlRwa3CLcfDeby60a2BPwpBy1xjY6oTgyG0=";
meta = {
description = "Nananananananana batmon";
homepage = "https://github.com/NotAShelf/batmon.git";
license = lib.licenses.gpl3Only;
maintainers = with lib.maintainers; [NotAShelf];
mainProgram = "batmon";
platforms = lib.platforms.linux;
};
}

8
example.config.json Normal file
View file

@ -0,0 +1,8 @@
{
"batPaths": [
{
"path": "/sys/class/power_supply/BAT0",
"extraCommand": "notify-send 'hello'"
}
]
}

View file

@ -1,26 +0,0 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1704796538,
"narHash": "sha256-uRMWOijfPGHzLXMqiYGoAitkq1beLfg2JAmGLdTjmvQ=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "792a7ddec9e0fe05aed934160eb75f1a821eb69b",
"type": "github"
},
"original": {
"owner": "NixOS",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}

View file

@ -1,8 +1,6 @@
{
description = "Batmon - battery monitor service";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs";
};
description = "Batmon - battery monitor";
inputs.nixpkgs.url = "github:NixOS/nixpkgs";
outputs = {
self,
@ -13,11 +11,12 @@
pkgsForEach = nixpkgs.legacyPackages;
in {
packages = forEachSystem (system: {
default = pkgsForEach.${system}.callPackage ./default.nix {};
batmon = pkgsForEach.${system}.callPackage ./nix/package.nix {};
default = self.${system}.batmon;
});
devShells = forEachSystem (system: {
default = pkgsForEach.${system}.callPackage ./shell.nix {};
default = pkgsForEach.${system}.callPackage ./nix/shell.nix {};
});
};
}

29
go.mod Normal file
View file

@ -0,0 +1,29 @@
module gomon
go 1.21.5
require (
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.6.0 // indirect
github.com/spf13/cobra v1.8.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.18.2 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.9.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/sys v0.16.0 // indirect
golang.org/x/text v0.14.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

62
go.sum Normal file
View file

@ -0,0 +1,62 @@
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ=
github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View file

@ -0,0 +1,71 @@
package battery
import (
"os"
"strings"
"time"
"gomon/internal/exec"
"gomon/internal/logger"
"gomon/internal/model"
)
// battery monitor service
func Monitor(bat model.Battery, profiles ...string) error {
// wait a while if needed
startupWait := os.Getenv("STARTUP_WAIT")
if startupWait != "" {
duration, err := time.ParseDuration(startupWait)
if err != nil {
logger.Error("Invalid STARTUP_WAIT duration")
return nil
}
time.Sleep(duration)
}
// start the monitor loop
var prevProfile string
for {
// read the current state
batteryStatus, err := os.ReadFile(bat.Path + "/status")
if err != nil {
logger.Error("Failed to read battery status")
return nil
}
currentProfile := "performance"
if strings.TrimSpace(string(batteryStatus)) == "Discharging" {
currentProfile = "balanced"
}
// set the new profile
if currentProfile != prevProfile {
logger.Info("Setting power profile to %s for battery %s", currentProfile, bat.Path)
var commandArgs []string
if bat.Command != "" {
commandArgs = strings.Split(bat.Command, " ")
} else {
commandArgs = []string{"powerprofilesctl", "set", currentProfile}
}
err = exec.ExecCommand(commandArgs[0], commandArgs[1:]...)
if err != nil {
logger.Error("Failed to execute command")
return nil
}
// execute the extra command
// if any
if bat.ExtraCommand != "" {
extraCommandArgs := strings.Split(bat.ExtraCommand, " ")
err = exec.ExecCommand(extraCommandArgs[0], extraCommandArgs[1:]...)
if err != nil {
logger.Error("Failed to execute extra command")
return nil
}
}
}
prevProfile = currentProfile
// wait for the next power change event
time.Sleep(1 * time.Second)
}
}

29
internal/config/config.go Normal file
View file

@ -0,0 +1,29 @@
package config
import (
"encoding/json"
"os"
"gomon/internal/logger"
"gomon/internal/model"
)
// Load loads the battery configuration from a file.
func Load(filename string) (model.BatteryConfig, error) {
file, err := os.Open(filename)
if err != nil {
logger.Error("Failed to open file")
return model.BatteryConfig{}, err
}
defer file.Close()
var config model.BatteryConfig
decoder := json.NewDecoder(file)
err = decoder.Decode(&config)
if err != nil {
logger.Error("Failed to decode JSON")
return model.BatteryConfig{}, err
}
return config, nil
}

21
internal/exec/exec.go Normal file
View file

@ -0,0 +1,21 @@
package exec
import (
"os/exec"
"gomon/internal/logger"
)
// execute a command with args and returns its output
// this will be used primarily for executing external commands if configured
// or executing bash commands verbatim
func ExecCommand(name string, arg ...string) error {
cmd := exec.Command(name, arg...)
err := cmd.Run()
if err != nil {
logger.Error("Failed to execute command")
return err
}
return nil
}

24
internal/logger/logger.go Normal file
View file

@ -0,0 +1,24 @@
package logger
import (
"github.com/sirupsen/logrus"
)
var Log *logrus.Logger
func init() {
Log = logrus.New()
Log.SetFormatter(&logrus.JSONFormatter{})
}
func Info(format string, v ...interface{}) {
Log.Infof(format, v...)
}
func Error(format string, v ...interface{}) {
Log.Errorf(format, v...)
}
func Fatal(format string, v ...interface{}) {
Log.Fatalf(format, v...)
}

11
internal/model/model.go Normal file
View file

@ -0,0 +1,11 @@
package model
type Battery struct {
Path string `json:"path"`
Command string `json:"command"`
ExtraCommand string `json:"extraCommand"`
}
type BatteryConfig struct {
BatPaths []Battery `json:"batPaths"`
}

60
main.go Normal file
View file

@ -0,0 +1,60 @@
package main
import (
"sync"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"gomon/internal/battery"
"gomon/internal/config"
"gomon/internal/model"
)
func main() {
var rootCmd = &cobra.Command{
Use: "gomon",
Short: "TODO",
Long: `TODO`,
Run: func(cmd *cobra.Command, args []string) {
// load the battery configuration
config, err := config.Load(viper.GetString("config"))
if err != nil {
logrus.WithFields(logrus.Fields{
"error": err,
}).Fatal("Failed to load configuration")
}
// create a WaitGroup to wait for all goroutines to finish
var wg sync.WaitGroup
wg.Add(len(config.BatPaths))
// start a goroutine for each battery
for _, bat := range config.BatPaths {
go func(bat model.Battery) {
defer wg.Done()
err := battery.Monitor(bat)
if err != nil {
logrus.WithFields(logrus.Fields{
"error": err,
"battery": bat.Path,
}).Error("Failed to monitor battery")
}
}(bat)
}
// wait for all goroutines to finish
wg.Wait()
},
}
rootCmd.PersistentFlags().StringP("config", "c", "config.json", "Path to the configuration file")
viper.BindPFlag("config", rootCmd.PersistentFlags().Lookup("config"))
if err := rootCmd.Execute(); err != nil {
logrus.WithFields(logrus.Fields{
"error": err,
}).Fatal("Failed to execute root command")
}
}

11
nix/package.nix Normal file
View file

@ -0,0 +1,11 @@
{buildGoModule}:
buildGoModule {
pname = "sample-go";
version = "0.0.1";
src = ./.;
vendorHash = null;
ldflags = ["-s" "-w"];
}

15
nix/shell.nix Normal file
View file

@ -0,0 +1,15 @@
{
callPackage,
gopls,
go,
}: let
mainPkg = callPackage ./default.nix {};
in
mainPkg.overrideAttrs (oa: {
nativeBuildInputs =
(oa.nativeBuildInputs or [])
++ [
gopls
go
];
})

View file

@ -1,20 +0,0 @@
{
callPackage,
rust-analyzer,
rustfmt,
clippy,
cargo,
}: let
mainPkg = callPackage ./default.nix {};
in
mainPkg.overrideAttrs (oa: {
nativeBuildInputs =
[
# Additional rust tooling
rust-analyzer
rustfmt
clippy
cargo
]
++ (oa.nativeBuildInputs or []);
})

View file

@ -1,111 +0,0 @@
use std::fs;
use std::path::Path;
use std::thread;
use std::time::Duration;
use std::sync::mpsc::channel;
use std::process::Command;
use std::error::Error;
use notify::{RecommendedWatcher, RecursiveMode, Watcher, Config};
use glob::glob;
use std::os::unix::fs::PermissionsExt;
// check if the current user has read permissions for a given path
fn has_read_permission<P: AsRef<Path>>(path: P) -> Result<bool, Box<dyn Error>> {
let metadata = fs::metadata(path.as_ref())?;
let permissions = metadata.permissions();
let mode = permissions.mode();
Ok(mode & 0o444 != 0)
}
fn main() -> Result<(), Box<dyn Error>> {
println!("Starting the battery monitor...");
let bat_base_paths = glob("/sys/class/power_supply/BAT*")?.filter_map(Result::ok);
let mut bat_status_path = String::new();
let mut bat_capacity_path = String::new();
for path in bat_base_paths {
println!("Found battery path: {:?}", path.display());
bat_status_path = format!("{}/status", path.display());
bat_capacity_path = format!("{}/capacity", path.display());
// break after finding the first match.
// FIXME: this is assuming there's only one battery, account for multiple?
break;
}
if bat_status_path.is_empty() || bat_capacity_path.is_empty() {
return Err("Battery path not found".into());
}
// check if we have read permissions for the battery status and capacity paths
// TODO: I'm pretty sure there's a better and faster way to do this
if !has_read_permission(&bat_status_path)? {
println!("No read permission for the battery status path");
return Err("Permission denied".into());
}
if !has_read_permission(&bat_capacity_path)? {
println!("No read permission for the battery capacity path");
return Err("Permission denied".into());
}
println!("Battery status path: {}", bat_status_path);
println!("Battery capacity path: {}", bat_capacity_path);
let ac_profile = "performance";
let bat_profile = "balanced";
if let Ok(wait_time_str) = std::env::var("STARTUP_WAIT") {
println!("STARTUP_WAIT is set, waiting for {} seconds", wait_time_str);
if let Ok(wait_time) = wait_time_str.parse::<u64>() {
thread::sleep(Duration::from_secs(wait_time));
} else {
println!("Invalid STARTUP_WAIT value: {}", wait_time_str);
}
}
let mut prev_profile = String::new();
let (tx, rx) = channel();
let mut watcher = RecommendedWatcher::new(tx, Config::default())?;
println!("Watching battery status and capacity paths for changes...");
watcher.watch(Path::new(&bat_status_path), RecursiveMode::NonRecursive)?;
watcher.watch(Path::new(&bat_capacity_path), RecursiveMode::NonRecursive)?;
loop {
match rx.recv() {
Ok(_) => {
println!("Change detected in battery status or capacity.");
let current_status = fs::read_to_string(&bat_status_path)?.trim().to_string();
println!("Current battery status: {}", current_status);
let profile = if current_status == "Discharging" {
bat_profile
} else {
ac_profile
};
if prev_profile != profile {
println!("Setting power profile to {}", profile);
let output = Command::new("powerprofilesctl")
.arg("set")
.arg(profile)
.output()?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
println!("Failed to set power profile: {}", stderr);
}
}
prev_profile = profile.to_string();
},
Err(e) => println!("Watch error: {:?}", e),
}
}
}