From d4290ff5535f1d94bda926f61ec2bf5438ee52a8 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Thu, 11 Jan 2024 22:51:07 +0300 Subject: [PATCH] go rewrite --- .gitignore | 22 ++- Cargo.lock | 351 ------------------------------------ Cargo.toml | 10 - README.md | 70 +++++++ TODO | 4 - default.nix | 23 --- example.config.json | 8 + flake.lock | 26 --- flake.nix | 11 +- go.mod | 29 +++ go.sum | 62 +++++++ internal/battery/battery.go | 71 ++++++++ internal/config/config.go | 29 +++ internal/exec/exec.go | 21 +++ internal/logger/logger.go | 24 +++ internal/model/model.go | 11 ++ main.go | 60 ++++++ nix/package.nix | 11 ++ nix/shell.nix | 15 ++ shell.nix | 20 -- src/main.rs | 111 ------------ 21 files changed, 437 insertions(+), 552 deletions(-) delete mode 100644 Cargo.lock delete mode 100644 Cargo.toml create mode 100644 README.md delete mode 100644 TODO delete mode 100644 default.nix create mode 100644 example.config.json delete mode 100644 flake.lock create mode 100644 go.mod create mode 100644 go.sum create mode 100644 internal/battery/battery.go create mode 100644 internal/config/config.go create mode 100644 internal/exec/exec.go create mode 100644 internal/logger/logger.go create mode 100644 internal/model/model.go create mode 100644 main.go create mode 100644 nix/package.nix create mode 100644 nix/shell.nix delete mode 100644 shell.nix delete mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore index ea8c4bf..54b5d99 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/Cargo.lock b/Cargo.lock deleted file mode 100644 index ba2010f..0000000 --- a/Cargo.lock +++ /dev/null @@ -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" diff --git a/Cargo.toml b/Cargo.toml deleted file mode 100644 index b70b342..0000000 --- a/Cargo.toml +++ /dev/null @@ -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" diff --git a/README.md b/README.md new file mode 100644 index 0000000..1f4f48d --- /dev/null +++ b/README.md @@ -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. diff --git a/TODO b/TODO deleted file mode 100644 index f11bb88..0000000 --- a/TODO +++ /dev/null @@ -1,4 +0,0 @@ -- nix integration -- unit tests -- use the battery crate -- rewrite in Go as soon as rust frustrates me enough diff --git a/default.nix b/default.nix deleted file mode 100644 index fcf1885..0000000 --- a/default.nix +++ /dev/null @@ -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; - }; - } diff --git a/example.config.json b/example.config.json new file mode 100644 index 0000000..dbd4010 --- /dev/null +++ b/example.config.json @@ -0,0 +1,8 @@ +{ + "batPaths": [ + { + "path": "/sys/class/power_supply/BAT0", + "extraCommand": "notify-send 'hello'" + } + ] +} diff --git a/flake.lock b/flake.lock deleted file mode 100644 index 7c737b0..0000000 --- a/flake.lock +++ /dev/null @@ -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 -} diff --git a/flake.nix b/flake.nix index 21b4b61..a2c4450 100644 --- a/flake.nix +++ b/flake.nix @@ -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 {}; }); }; } diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..33e8d51 --- /dev/null +++ b/go.mod @@ -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 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..f4ba775 --- /dev/null +++ b/go.sum @@ -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= diff --git a/internal/battery/battery.go b/internal/battery/battery.go new file mode 100644 index 0000000..a0361e1 --- /dev/null +++ b/internal/battery/battery.go @@ -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) + } +} diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100644 index 0000000..6d307ce --- /dev/null +++ b/internal/config/config.go @@ -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 +} diff --git a/internal/exec/exec.go b/internal/exec/exec.go new file mode 100644 index 0000000..706be70 --- /dev/null +++ b/internal/exec/exec.go @@ -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 +} diff --git a/internal/logger/logger.go b/internal/logger/logger.go new file mode 100644 index 0000000..b351f17 --- /dev/null +++ b/internal/logger/logger.go @@ -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...) +} diff --git a/internal/model/model.go b/internal/model/model.go new file mode 100644 index 0000000..030737e --- /dev/null +++ b/internal/model/model.go @@ -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"` +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..85948cf --- /dev/null +++ b/main.go @@ -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") + } +} diff --git a/nix/package.nix b/nix/package.nix new file mode 100644 index 0000000..6984378 --- /dev/null +++ b/nix/package.nix @@ -0,0 +1,11 @@ +{buildGoModule}: +buildGoModule { + pname = "sample-go"; + version = "0.0.1"; + + src = ./.; + + vendorHash = null; + + ldflags = ["-s" "-w"]; +} diff --git a/nix/shell.nix b/nix/shell.nix new file mode 100644 index 0000000..52633eb --- /dev/null +++ b/nix/shell.nix @@ -0,0 +1,15 @@ +{ + callPackage, + gopls, + go, +}: let + mainPkg = callPackage ./default.nix {}; +in + mainPkg.overrideAttrs (oa: { + nativeBuildInputs = + (oa.nativeBuildInputs or []) + ++ [ + gopls + go + ]; + }) diff --git a/shell.nix b/shell.nix deleted file mode 100644 index 8150363..0000000 --- a/shell.nix +++ /dev/null @@ -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 []); - }) diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 9f456c6..0000000 --- a/src/main.rs +++ /dev/null @@ -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>(path: P) -> Result> { - let metadata = fs::metadata(path.as_ref())?; - let permissions = metadata.permissions(); - let mode = permissions.mode(); - Ok(mode & 0o444 != 0) -} - - - -fn main() -> Result<(), Box> { - 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::() { - 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), - } - } -} -