Add a xdg-desktop-portal ScreenCast fallback to monitor enumeration so the GUI picker works on compositors without hyprctl (GNOME/KDE etc.). ListMonitors now tries hyprctl first, then creates a portal session, selects monitor sources, starts capture and parses the stream list. Verified on Hyprland: the portal path returns the 1920x1200 monitor, confirming the compositor-agnostic mechanism works.
84 lines
2.3 KiB
Go
84 lines
2.3 KiB
Go
// 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. It prefers
|
|
// the Hyprland IPC when available, falling back to the xdg-desktop-portal
|
|
// ScreenCast API for other compositors.
|
|
func ListMonitors() ([]Monitor, error) {
|
|
if hyprctlAvailable() {
|
|
if mons, err := hyprctlMonitors(); err == nil {
|
|
return mons, nil
|
|
}
|
|
}
|
|
return portalMonitors()
|
|
}
|
|
|
|
// 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
|
|
}
|