feat: add monitor picker to the GUI settings
Enumerate monitors via hyprctl (Hyprland) and present them as a dropdown in the settings window instead of a raw index. The picker is enabled only for the screen source and persists the selected monitor as stream_index. Falls back to a single indexed option when enumeration is unavailable (non-Hyprland).
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
// Monitor enumeration for the settings UI.
|
||||
//
|
||||
// The screen-capture backend itself selects a monitor by index; this file
|
||||
// provides a way to list the available monitors so the GUI can present a
|
||||
// friendly picker instead of a raw index.
|
||||
//
|
||||
// Hyprland exposes monitor info via the `hyprctl monitors` command. Other
|
||||
// Wayland compositors would need a portal-based enumeration; for now we only
|
||||
// implement the Hyprland path (the primary dev environment) and return a
|
||||
// clear error elsewhere.
|
||||
|
||||
package capture
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// Monitor describes one capturable output.
|
||||
type Monitor struct {
|
||||
Index int // capture StreamIndex to pass to OpenPipeWire
|
||||
Name string // compositor name, e.g. "eDP-1"
|
||||
Width int
|
||||
Height int
|
||||
Primary bool
|
||||
}
|
||||
|
||||
// ErrNoMonitors is returned when monitor enumeration is unsupported or fails.
|
||||
var ErrNoMonitors = errors.New("capture: monitor enumeration unavailable on this compositor")
|
||||
|
||||
// ListMonitors returns the available monitors for the GUI picker.
|
||||
func ListMonitors() ([]Monitor, error) {
|
||||
if hyprctlAvailable() {
|
||||
return hyprctlMonitors()
|
||||
}
|
||||
return nil, ErrNoMonitors
|
||||
}
|
||||
|
||||
// hyprctlAvailable reports whether the Hyprland monitor command exists.
|
||||
func hyprctlAvailable() bool {
|
||||
_, err := exec.LookPath("hyprctl")
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// hyprctlMonitor is the JSON shape emitted by `hyprctl monitors -j`.
|
||||
type hyprctlMonitor struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Width int `json:"width"`
|
||||
Height int `json:"height"`
|
||||
Description string `json:"description"`
|
||||
Focused bool `json:"focused"`
|
||||
}
|
||||
|
||||
// hyprctlMonitors lists monitors via the Hyprland IPC command.
|
||||
func hyprctlMonitors() ([]Monitor, error) {
|
||||
out, err := exec.Command("hyprctl", "monitors", "-j").Output()
|
||||
if err != nil {
|
||||
return nil, ErrNoMonitors
|
||||
}
|
||||
|
||||
var raw []hyprctlMonitor
|
||||
if err := json.Unmarshal(out, &raw); err != nil {
|
||||
return nil, ErrNoMonitors
|
||||
}
|
||||
|
||||
monitors := make([]Monitor, 0, len(raw))
|
||||
for _, m := range raw {
|
||||
monitors = append(monitors, Monitor{
|
||||
Index: m.ID,
|
||||
Name: m.Name,
|
||||
Width: m.Width,
|
||||
Height: m.Height,
|
||||
Primary: m.Focused,
|
||||
})
|
||||
}
|
||||
return monitors, nil
|
||||
}
|
||||
Reference in New Issue
Block a user