refactor: move backend options into backend_config section
- Add BackendConfig map[string]any to Config struct - Deprecate top-level output_path (backward compatible via EffectiveBackendConfig()) - NewHyprland now accepts opts map[string]any instead of raw outputPath - Factory signature unified to func(*slog.Logger, map[string]any) for all backends - Update README with backend_config docs and fix Sway skeleton signature - Update example.yaml to show backend_config instead of output_path
This commit is contained in:
parent
15691c0918
commit
cc1137608c
22
README.md
22
README.md
@ -166,6 +166,25 @@ Two match modes in the `external` list:
|
|||||||
|
|
||||||
Run `hyprctl monitors all` to see your monitor names and descriptions.
|
Run `hyprctl monitors all` to see your monitor names and descriptions.
|
||||||
|
|
||||||
|
### Backend-specific configuration
|
||||||
|
|
||||||
|
Use the `backend_config` section for compositor-specific options:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
backend_config:
|
||||||
|
output_path: ~/.config/hypr/custom-monitors.conf # override generated config path
|
||||||
|
```
|
||||||
|
|
||||||
|
**Hyprland keys:**
|
||||||
|
|
||||||
|
| Key | Type | Default | Description |
|
||||||
|
|-----|------|---------|-------------|
|
||||||
|
| `output_path` | string | `~/.config/hypr/monitors.conf` | Path to the generated monitor config file. Supports `~` expansion. |
|
||||||
|
|
||||||
|
The deprecated top-level `output_path` key still works — `backend_config` takes priority if both are set.
|
||||||
|
|
||||||
|
When adding new compositor backends, their options go into the same `backend_config` section without changing the core config structure.
|
||||||
|
|
||||||
### Hook commands
|
### Hook commands
|
||||||
|
|
||||||
Hooks are shell commands executed via `sh -c`. Each command gets a **30-second timeout**. Commands run in parallel — one slow hook won't block others.
|
Hooks are shell commands executed via `sh -c`. Each command gets a **30-second timeout**. Commands run in parallel — one slow hook won't block others.
|
||||||
@ -273,8 +292,9 @@ package backend
|
|||||||
|
|
||||||
type swayBackend struct { logger *slog.Logger }
|
type swayBackend struct { logger *slog.Logger }
|
||||||
|
|
||||||
func NewSway(logger *slog.Logger) (Backend, error) {
|
func NewSway(logger *slog.Logger, opts map[string]any) (Backend, error) {
|
||||||
// Check if SWAYSOCK is set and swaymsg is available
|
// Check if SWAYSOCK is set and swaymsg is available
|
||||||
|
// opts carries backend_config from the config file
|
||||||
return &swayBackend{logger: logger}, nil
|
return &swayBackend{logger: logger}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -60,7 +60,9 @@ func run(logger *slog.Logger) error {
|
|||||||
logger.Info("config loaded", "path", configPath, "backend", cfg.Backend)
|
logger.Info("config loaded", "path", configPath, "backend", cfg.Backend)
|
||||||
|
|
||||||
// Resolve the backend (auto-detect or explicit).
|
// Resolve the backend (auto-detect or explicit).
|
||||||
b, err := resolveBackend(cfg.Backend, cfg.OutputPath, logger)
|
// EffectiveBackendConfig merges deprecated top-level keys
|
||||||
|
// into the backend_config map with proper priority.
|
||||||
|
b, err := resolveBackend(cfg.Backend, cfg.EffectiveBackendConfig(), logger)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("resolve backend: %w", err)
|
return fmt.Errorf("resolve backend: %w", err)
|
||||||
}
|
}
|
||||||
@ -86,11 +88,11 @@ func run(logger *slog.Logger) error {
|
|||||||
// resolveBackend returns the first available backend. When backendName is
|
// resolveBackend returns the first available backend. When backendName is
|
||||||
// "auto", backends are probed in priority order. To add a new backend,
|
// "auto", backends are probed in priority order. To add a new backend,
|
||||||
// register it in the backends slice below.
|
// register it in the backends slice below.
|
||||||
func resolveBackend(backendName, outputPath string, logger *slog.Logger) (backend.Backend, error) {
|
func resolveBackend(backendName string, backendConfig map[string]any, logger *slog.Logger) (backend.Backend, error) {
|
||||||
// Ordered by priority: preferred backends first.
|
// Ordered by priority: preferred backends first.
|
||||||
backends := []struct {
|
backends := []struct {
|
||||||
name string
|
name string
|
||||||
factory func(*slog.Logger, string) (backend.Backend, error)
|
factory func(*slog.Logger, map[string]any) (backend.Backend, error)
|
||||||
}{
|
}{
|
||||||
{"hyprland", backend.NewHyprland},
|
{"hyprland", backend.NewHyprland},
|
||||||
// Future: {"sway", backend.NewSway},
|
// Future: {"sway", backend.NewSway},
|
||||||
@ -99,7 +101,7 @@ func resolveBackend(backendName, outputPath string, logger *slog.Logger) (backen
|
|||||||
|
|
||||||
if backendName == "auto" {
|
if backendName == "auto" {
|
||||||
for _, entry := range backends {
|
for _, entry := range backends {
|
||||||
b, err := entry.factory(logger, outputPath)
|
b, err := entry.factory(logger, backendConfig)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
logger.Info("auto-detected backend", "name", b.Name())
|
logger.Info("auto-detected backend", "name", b.Name())
|
||||||
return b, nil
|
return b, nil
|
||||||
@ -112,7 +114,7 @@ func resolveBackend(backendName, outputPath string, logger *slog.Logger) (backen
|
|||||||
// Explicit backend selection.
|
// Explicit backend selection.
|
||||||
for _, entry := range backends {
|
for _, entry := range backends {
|
||||||
if entry.name == backendName {
|
if entry.name == backendName {
|
||||||
b, err := entry.factory(logger, outputPath)
|
b, err := entry.factory(logger, backendConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("backend %s: %w", backendName, err)
|
return nil, fmt.Errorf("backend %s: %w", backendName, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -30,6 +30,7 @@ type hyprlandBackend struct {
|
|||||||
|
|
||||||
// outputPath overrides the generated monitor config file path.
|
// outputPath overrides the generated monitor config file path.
|
||||||
// Empty means the default: ~/.config/hypr/monitors.conf.
|
// Empty means the default: ~/.config/hypr/monitors.conf.
|
||||||
|
// Read from backend_config.output_path at construction time.
|
||||||
outputPath string
|
outputPath string
|
||||||
|
|
||||||
// conn is the current socket2 connection; guarded by mu.
|
// conn is the current socket2 connection; guarded by mu.
|
||||||
@ -40,9 +41,12 @@ type hyprlandBackend struct {
|
|||||||
// NewHyprland creates a Hyprland backend. The backend auto-detects the
|
// NewHyprland creates a Hyprland backend. The backend auto-detects the
|
||||||
// Hyprland instance signature from the environment.
|
// Hyprland instance signature from the environment.
|
||||||
//
|
//
|
||||||
// outputPath overrides the generated monitor config file location.
|
// opts carries backend-specific configuration from the config file's
|
||||||
// When empty, defaults to ~/.config/hypr/monitors.conf.
|
// backend_config section. Hyprland supports:
|
||||||
func NewHyprland(logger *slog.Logger, outputPath string) (Backend, error) {
|
//
|
||||||
|
// output_path — path to the generated monitor config file
|
||||||
|
// (default: ~/.config/hypr/monitors.conf)
|
||||||
|
func NewHyprland(logger *slog.Logger, opts map[string]any) (Backend, error) {
|
||||||
sig := os.Getenv("HYPRLAND_INSTANCE_SIGNATURE")
|
sig := os.Getenv("HYPRLAND_INSTANCE_SIGNATURE")
|
||||||
if sig == "" {
|
if sig == "" {
|
||||||
return nil, errors.New("HYPRLAND_INSTANCE_SIGNATURE is not set; is Hyprland running?")
|
return nil, errors.New("HYPRLAND_INSTANCE_SIGNATURE is not set; is Hyprland running?")
|
||||||
@ -57,6 +61,13 @@ func NewHyprland(logger *slog.Logger, outputPath string) (Backend, error) {
|
|||||||
return nil, fmt.Errorf("socket2 not found at %s: %w", socketPath, err)
|
return nil, fmt.Errorf("socket2 not found at %s: %w", socketPath, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var outputPath string
|
||||||
|
if opts != nil {
|
||||||
|
if v, ok := opts["output_path"]; ok {
|
||||||
|
outputPath, _ = v.(string)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return &hyprlandBackend{logger: logger, outputPath: outputPath}, nil
|
return &hyprlandBackend{logger: logger, outputPath: outputPath}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -52,9 +52,16 @@ type Config struct {
|
|||||||
|
|
||||||
// OutputPath overrides the generated monitor config file path.
|
// OutputPath overrides the generated monitor config file path.
|
||||||
// Supports ~ for home directory expansion.
|
// Supports ~ for home directory expansion.
|
||||||
|
// Deprecated: use backend_config.output_path instead.
|
||||||
// Empty means the default: ~/.config/hypr/monitors.conf.
|
// Empty means the default: ~/.config/hypr/monitors.conf.
|
||||||
OutputPath string `yaml:"output_path"`
|
OutputPath string `yaml:"output_path"`
|
||||||
|
|
||||||
|
// BackendConfig holds backend-specific configuration options.
|
||||||
|
// Keys and values vary by the selected backend.
|
||||||
|
// Hyprland supports:
|
||||||
|
// output_path — path to the generated monitor config file
|
||||||
|
BackendConfig map[string]any `yaml:"backend_config"`
|
||||||
|
|
||||||
// External lists monitor identifiers that trigger docked mode.
|
// External lists monitor identifiers that trigger docked mode.
|
||||||
// Each entry is a name (DP-1) or a desc: prefix (desc:Dell U2723QE).
|
// Each entry is a name (DP-1) or a desc: prefix (desc:Dell U2723QE).
|
||||||
External []string `yaml:"external"`
|
External []string `yaml:"external"`
|
||||||
@ -158,6 +165,25 @@ func (c *Config) validate() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EffectiveBackendConfig merges top-level deprecated backend keys
|
||||||
|
// (OutputPath) into the BackendConfig map. backend_config values
|
||||||
|
// take priority over deprecated top-level keys.
|
||||||
|
//
|
||||||
|
// The caller receives a non-nil map even when no backend_config is
|
||||||
|
// set and no deprecated keys are present.
|
||||||
|
func (c *Config) EffectiveBackendConfig() map[string]any {
|
||||||
|
bc := c.BackendConfig
|
||||||
|
if bc == nil {
|
||||||
|
bc = make(map[string]any)
|
||||||
|
}
|
||||||
|
if c.OutputPath != "" {
|
||||||
|
if _, exists := bc["output_path"]; !exists {
|
||||||
|
bc["output_path"] = c.OutputPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bc
|
||||||
|
}
|
||||||
|
|
||||||
// countEnabled returns how many MonitorEntries are enabled.
|
// countEnabled returns how many MonitorEntries are enabled.
|
||||||
func countEnabled(entries []MonitorEntry) int {
|
func countEnabled(entries []MonitorEntry) int {
|
||||||
n := 0
|
n := 0
|
||||||
|
|||||||
@ -15,10 +15,12 @@ poll_interval: 5s
|
|||||||
# Whether to restore the portable layout when the daemon shuts down.
|
# Whether to restore the portable layout when the daemon shuts down.
|
||||||
restore_on_exit: true
|
restore_on_exit: true
|
||||||
|
|
||||||
# Optional: override where the generated Hyprland monitor config is written.
|
# Backend-specific options. Keys depend on the selected backend.
|
||||||
# Supports ~ for home directory expansion.
|
# Hyprland supports:
|
||||||
# Defaults to ~/.config/hypr/monitors.conf.
|
# output_path — path to the generated monitor config file
|
||||||
# output_path: ~/.config/hypr/custom-monitors.conf
|
# (default: ~/.config/hypr/monitors.conf)
|
||||||
|
# backend_config:
|
||||||
|
# output_path: ~/.config/hypr/custom-monitors.conf
|
||||||
|
|
||||||
# External monitors that trigger docked mode.
|
# External monitors that trigger docked mode.
|
||||||
# Plain name: matches the connector name (e.g. DP-1, HDMI-A-1).
|
# Plain name: matches the connector name (e.g. DP-1, HDMI-A-1).
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user