Skip to content

Commit e1c35f6

Browse files
authored
feat: detect running on desktop, disable power/lid events for it (#80)
## What does this PR do? Disables power events on desktops by default (still enabled on laptops). ## Why is this change important? User requested in #79 ## How to test this PR locally? `make pre-push` ## Related issues Closes #79
2 parents 907fbe8 + 607ec72 commit e1c35f6

File tree

11 files changed

+354
-7
lines changed

11 files changed

+354
-7
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ See the [`examples/`](https://github.com/fiffeek/hyprdynamicmonitors/tree/main/e
143143
## Runtime Requirements
144144

145145
- Hyprland with IPC support
146-
- UPower (required, unless `--disable-power-events` is passed, for power state monitoring)
146+
- UPower (required on laptops, unless `--disable-power-events` is passed, for power state monitoring)
147147
- D-Bus access (required if power events, lid state or notifications are enabled)
148148

149149
## Alternative Software

cmd/run.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66

77
"github.com/fiffeek/hyprdynamicmonitors/internal/app"
8+
"github.com/fiffeek/hyprdynamicmonitors/internal/utils"
89
"github.com/sirupsen/logrus"
910
"github.com/spf13/cobra"
1011
)
@@ -24,6 +25,16 @@ var runCmd = &cobra.Command{
2425
Long: `Run the HyprDynamicMonitors service to continuously monitor for display changes and automatically apply matching configuration profiles.`,
2526
RunE: func(cmd *cobra.Command, args []string) error {
2627
logrus.WithField("version", Version).Debug("Starting Hyprland Dynamic Monitor Manager")
28+
29+
// If the flag wasn't provided, power events are enabled, and not running on a laptop, then default to --disable-power-events
30+
if !cmd.Flags().Changed("disable-power-events") && !disablePowerEvents && !utils.IsLaptop() {
31+
disablePowerEvents = true
32+
logrus.WithFields(utils.NewLogrusCustomFields(logrus.Fields{
33+
"disable-power-events": disablePowerEvents,
34+
}).WithLogID(utils.DisablingPowerEventsLogID)).Info(
35+
"Running on desktop. Disabling power events. If this is not what you want, run with `--disable-power-events=false`.")
36+
}
37+
2738
ctx, cancel := context.WithCancelCause(context.Background())
2839
app, err := app.NewApplication(&configPath, &dryRun, ctx, cancel, &disablePowerEvents,
2940
&disableAutoHotReload, &connectToSessionBus, &enableLidEvents)
@@ -60,7 +71,7 @@ func init() {
6071
&disablePowerEvents,
6172
"disable-power-events",
6273
false,
63-
"Disable power events (dbus)",
74+
"Disable power events (dbus). Defaults to true if running on desktop, to false otherwise",
6475
)
6576
runCmd.Flags().BoolVar(
6677
&connectToSessionBus,

cmd/tui.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
tea "github.com/charmbracelet/bubbletea"
99
"github.com/charmbracelet/lipgloss"
1010
"github.com/fiffeek/hyprdynamicmonitors/internal/app"
11+
"github.com/fiffeek/hyprdynamicmonitors/internal/utils"
1112
"github.com/muesli/termenv"
1213
"github.com/sirupsen/logrus"
1314
"github.com/spf13/cobra"
@@ -23,6 +24,15 @@ var tuiCmd = &cobra.Command{
2324
Short: "Launch interactive TUI for monitor configuration",
2425
Long: `Launch an interactive terminal-based TUI for managing monitor configurations.`,
2526
RunE: func(cmd *cobra.Command, args []string) error {
27+
// If the flag wasn't provided, power events are enabled, and not running on a laptop, then default to --disable-power-events
28+
if !cmd.Flags().Changed("disable-power-events") && !disablePowerEvents && !utils.IsLaptop() {
29+
disablePowerEvents = true
30+
logrus.WithFields(utils.NewLogrusCustomFields(logrus.Fields{
31+
"disable-power-events": disablePowerEvents,
32+
}).WithLogID(utils.DisablingPowerEventsLogID)).Info(
33+
"Running on desktop. Disabling power events. If this is not what you want, run with `--disable-power-events=false`.")
34+
}
35+
2636
if debug {
2737
f, err := tea.LogToFile("debug.log", "debug")
2838
if err != nil {
@@ -67,7 +77,7 @@ func init() {
6777
&disablePowerEvents,
6878
"disable-power-events",
6979
false,
70-
"Disable power events (dbus)",
80+
"Disable power events (dbus). Defaults to true if running on desktop, to false otherwise",
7181
)
7282

7383
tuiCmd.Flags().BoolVar(

docs/docs/configuration/power-events.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,15 @@ When disabled:
2121
- No power events will be delivered
2222
- No D-Bus connection will be made
2323

24+
## Enabling Power Events
25+
26+
Power events are enabled by default on laptops. They are disabled on desktops (the current chassis type is pulled from `/sys/class/dmi/id/chassis_type`).
27+
If you want to enable them anyway, run, e.g.:
28+
29+
```bash
30+
hyprdynamicmonitors run --disable-power-events=false
31+
```
32+
2433
## Default Configuration
2534

2635
By default, the service listens for D-Bus signals:

docs/docs/faq.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,16 @@ Yes! HyprDynamicMonitors can work alongside `nwg-displays`:
214214

215215
This combines the visual configuration of `nwg-displays` with the automatic profile management of `hyprdynamicmonitors`.
216216

217+
## Is UPower Running? UPower misconfigured or not running: failed to get property from UPower: Object does not exist at path “/org/freedesktop/UPower/devices/line_power_ACAD”
218+
219+
If you got this error, you either want to:
220+
1. If you do not want to use power events (e.g., on a desktop), pass the `--disable-power-events` CLI argument
221+
2. Tweak your [Power Events configuration](./configuration/power-events.md) to use the proper device path (`upower -e` to find your `line_power` device; additionally, it would be helpful if you added it [here](https://github.com/fiffeek/hyprdynamicmonitors/blob/main/internal/utils/power.go#L14) so that others do not encounter this issue going forward)
222+
223+
If you are running on a desktop with a version of `hyprdynamicmonitors` that includes [this patch](https://github.com/fiffeek/hyprdynamicmonitors/pull/80) (`v1.3.5+`),
224+
this error should not occur unless `--disable-power-events=false` is explicitly passed, since power events are disabled
225+
for desktops by default. Please [open an issue](https://github.com/fiffeek/hyprdynamicmonitors/issues) and include the output of `cat /sys/class/dmi/id/chassis_type`.
226+
217227
## See Also
218228

219229
- [Examples](https://github.com/fiffeek/hyprdynamicmonitors/tree/main/examples) - Complete configuration examples

docs/docs/quickstart/setup-approaches.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,14 @@ Choose your preferred setup approach based on whether you want to run the daemon
99
## Choose Your Approach
1010

1111
:::warning
12-
When running the daemon with power or lid events enabled, make sure that `UPower` is installed and its service is running.
12+
The app will try to detect your chassis type from `/sys/class/dmi/id/chassis_type` on startup.
13+
If a desktop is detected, it will disable power events unless explicitly enabled (by passing `--disable-power-events=false`).
14+
On laptops, power events are enabled by default (pass `--disable-power-events` to disable them).
15+
:::
16+
17+
:::warning
18+
When running the daemon with power (default) or lid events enabled, make sure that `UPower` is installed and its service is running.
19+
If you're running on a desktop you likely want both disabled (pass `--disable-power-events`).
1320
:::
1421

1522
### Option A: TUI + Daemon (Recommended for Most Users)

docs/docs/usage/commands.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ Usage:
5555
Flags:
5656
--connect-to-session-bus Connect to session bus instead of system bus for power events: https://wiki.archlinux.org/title/D-Bus. You can switch as long as you expose power line events in your user session bus.
5757
--disable-auto-hot-reload Disable automatic hot reload (no file watchers)
58-
--disable-power-events Disable power events (dbus)
58+
--disable-power-events Disable power events (dbus). Defaults to true if running on desktop, to false otherwise
5959
--dry-run Show what would be done without making changes
6060
--enable-lid-events Enable listening to dbus lid events
6161
-h, --help help for run
@@ -221,7 +221,7 @@ Usage:
221221
222222
Flags:
223223
--connect-to-session-bus Connect to session bus instead of system bus for power events: https://wiki.archlinux.org/title/D-Bus. You can switch as long as you expose power line events in your user session bus.
224-
--disable-power-events Disable power events (dbus)
224+
--disable-power-events Disable power events (dbus). Defaults to true if running on desktop, to false otherwise
225225
--enable-lid-events Enable listening to dbus lid events
226226
-h, --help help for tui
227227
--hypr-monitors-override string When used it fill parse the given file as hyprland monitors spec, used for testing.

internal/utils/dmi.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package utils
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"strconv"
7+
"strings"
8+
9+
"github.com/sirupsen/logrus"
10+
)
11+
12+
var (
13+
dmiPathEnvVarOverride = "HYPRDYNAMICMONITORS_DMI_PATH_OVERRIDE"
14+
dmiPath = "/sys/class/dmi/id/chassis_type"
15+
)
16+
17+
// laptopLikeCodes are the SMBIOS/ DMI chassis_type numeric codes that indicate "laptop-like".
18+
var laptopLikeCodes = map[int]struct{}{
19+
8: {}, // Portable
20+
9: {}, // Laptop
21+
10: {}, // Notebook
22+
14: {}, // Sub Notebook
23+
31: {}, // Convertible
24+
32: {}, // Detachable
25+
}
26+
27+
func readChassisType() (int, error) {
28+
path, ok := os.LookupEnv(dmiPathEnvVarOverride)
29+
if !ok {
30+
path = dmiPath
31+
}
32+
33+
//nolint:gosec
34+
dmiContents, err := os.ReadFile(path)
35+
if err != nil {
36+
return 0, fmt.Errorf("can't read chassis type: %w", err)
37+
}
38+
s := strings.TrimSpace(string(dmiContents))
39+
40+
// Some kernels expose numeric; some distros might echo names (rare). Try parsing int first.
41+
if num, err := strconv.Atoi(s); err == nil {
42+
return num, nil
43+
}
44+
45+
// fallback: map some textual values to numbers (if present)
46+
// common textual outputs: "Notebook", "Laptop", "Desktop"
47+
switch strings.ToLower(s) {
48+
case "portable":
49+
return 8, nil
50+
case "laptop", "notebook":
51+
return 10, nil
52+
case "sub-notebook", "sub notebook":
53+
return 14, nil
54+
case "desktop":
55+
return 3, nil
56+
case "server":
57+
return 17, nil
58+
default:
59+
return 0, nil
60+
}
61+
}
62+
63+
func IsLaptop() bool {
64+
dmiCode, err := readChassisType()
65+
if err != nil {
66+
logrus.WithError(err).Warn("can't read DMI code")
67+
// assume laptop for backwards compatibility
68+
return true
69+
}
70+
71+
_, ok := laptopLikeCodes[dmiCode]
72+
return ok
73+
}

0 commit comments

Comments
 (0)