hyprland: add virtual output controls

parent ba26fb3d
......@@ -56,7 +56,7 @@ Tuner.Page displays_page {
}
Tuner.Group {
title: _("Details");
title: _("Displays");
id: "monitors";
$TunerDisplaysPrimaryDisplayWidget primary_display_widget {}
......@@ -70,6 +70,28 @@ Tuner.Page displays_page {
}
Tuner.Group {
title: _("Virtual Displays");
id: "virtual-outputs";
show-empty: true;
header-suffix: Button add_virtual_output_button {
icon-name: "list-add-symbolic";
tooltip-text: _("Add Virtual Display");
has-frame: false;
styles ["flat"]
};
$TunerDisplaysVirtualOutputsWidget virtual_outputs_widget {
binding: $TunerDisplaysDisplaysVisibilityBinding {
mode: "virtual-outputs";
validator: $TunerDisplaysDisplaysVisibilityValidator {
group-mode: "virtual-outputs";
};
};
}
}
Tuner.Group {
id: "single-monitor";
$TunerDisplaysSingleMonitorWidget single_monitor_widget {
......
......@@ -17,3 +17,4 @@ src/ui/widgets/mirror-settings-widget.vala
src/ui/widgets/monitor-row.vala
src/ui/widgets/primary-display-widget.vala
src/ui/widgets/status-widget.vala
src/ui/widgets/virtual-outputs-widget.vala
......@@ -45,6 +45,7 @@ namespace TunerDisplays {
public virtual bool supports_hot_corners { get { return false; } }
public virtual bool supports_underscanning { get { return false; } }
public virtual bool supports_hdr_toggle { get { return false; } }
public virtual bool supports_virtual_outputs { get { return false; } }
public abstract Gee.ArrayList<MonitorConfig> load() throws Error;
public abstract void apply(Gee.ArrayList<MonitorConfig> monitors) throws Error;
......@@ -57,6 +58,18 @@ namespace TunerDisplays {
throw new BackendError.UNSUPPORTED("Including monitor config is not supported by this backend");
}
public virtual void create_virtual_output(string backend) throws Error {
throw new BackendError.UNSUPPORTED("Virtual outputs are not supported by this backend");
}
public virtual void remove_virtual_output(string name) throws Error {
throw new BackendError.UNSUPPORTED("Virtual outputs are not supported by this backend");
}
public virtual bool is_virtual_output(MonitorConfig monitor) {
return false;
}
protected static Json.Node backend_parse_json(string text) throws Error {
var parser = new Json.Parser();
parser.load_from_data(text);
......
......@@ -28,6 +28,7 @@ namespace TunerDisplays {
public override bool supports_vrr_modes { get { return true; } }
public override bool supports_color_management { get { return true; } }
public override bool supports_hdr_metadata { get { return true; } }
public override bool supports_virtual_outputs { get { return true; } }
public override ConfigIncludeInfo config_include_info() {
var main_path = main_config_path();
......@@ -207,6 +208,23 @@ namespace TunerDisplays {
}
}
public override void create_virtual_output(string backend) throws Error {
if (!valid_virtual_backend(backend))
throw new BackendError.APPLY_FAILED(_("Unsupported virtual output backend"));
ShellCommand.run("hyprctl output create %s".printf(backend));
}
public override void remove_virtual_output(string name) throws Error {
ShellCommand.run("hyprctl output remove %s".printf(ShellCommand.quote(name)));
}
public override bool is_virtual_output(MonitorConfig monitor) {
return monitor.name.has_prefix("HEADLESS-")
|| monitor.name.has_prefix("WAYLAND-")
|| monitor.name.has_prefix("X11-");
}
private static Gee.HashSet<string> read_active_names() throws Error {
var names = new Gee.HashSet<string>();
var root = backend_parse_json(ShellCommand.run("hyprctl -j monitors"));
......@@ -410,6 +428,13 @@ namespace TunerDisplays {
return format == "XRGB2101010" || format == "XBGR2101010";
}
private static bool valid_virtual_backend(string backend) {
return backend == "auto"
|| backend == "headless"
|| backend == "wayland"
|| backend == "x11";
}
private static string output_identifier(MonitorConfig monitor) {
if (!monitor.use_description)
return monitor.name;
......
......@@ -25,6 +25,7 @@ sources = files(
'ui/widgets/primary-display-widget.vala',
'ui/widgets/single-monitor-widget.vala',
'ui/widgets/status-widget.vala',
'ui/widgets/virtual-outputs-widget.vala',
'ui/pages/monitor-settings-content.vala',
'ui/settings/monitor-choice-loader.vala',
'ui/settings/monitor-setting-binding.vala',
......
......@@ -25,6 +25,7 @@ namespace TunerDisplays {
typeof(PrimaryDisplayWidget).ensure();
typeof(MonitorListWidget).ensure();
typeof(SingleMonitorWidget).ensure();
typeof(VirtualOutputsWidget).ensure();
typeof(MonitorSettingBinding).ensure();
typeof(MonitorSettingValidator).ensure();
typeof(MonitorChoiceLoader).ensure();
......@@ -43,6 +44,16 @@ namespace TunerDisplays {
apply.clicked.connect(DisplaysContext.controller.apply_changes);
page.pack_end(apply);
var add_virtual_output = builder.get_object("add_virtual_output_button") as Gtk.Button;
add_virtual_output.clicked.connect(() => {
try {
DisplaysContext.controller.backend.create_virtual_output("auto");
DisplaysContext.controller.reload();
} catch (Error err) {
Tuner.toast(err.message);
}
});
monitor_page = builder.get_object("monitor_settings_page") as Tuner.Page;
monitor_page_content = builder.get_object("monitor_page_content") as Gtk.Box;
monitor_page_title = builder.get_object("monitor_page_title") as Gtk.Label;
......
......@@ -81,6 +81,8 @@ namespace TunerDisplays {
return !controller.mirror_enabled() && !controller.single_monitor_mode();
case "single-monitor":
return controller.single_monitor_mode();
case "virtual-outputs":
return controller.backend.supports_virtual_outputs;
default:
return true;
}
......
......@@ -19,11 +19,15 @@ namespace TunerDisplays {
}
foreach (var monitor in controller.monitors) {
if (controller.backend.is_virtual_output(monitor))
continue;
var row = new MonitorRow(monitor, monitor_settings_page_id(), controller.monitors, controller.backend);
row.monitor_selected.connect(controller.open_monitor_settings);
row.monitor_changed.connect(controller.refresh_from_monitors);
list.append(row);
}
list.visible = list_has_children(list);
}
}
......
......@@ -30,6 +30,25 @@ namespace TunerDisplays {
activate_action("navigation.push", "s", page_id);
});
if (backend.is_virtual_output(monitor)) {
var remove_button = new Gtk.Button.from_icon_name("user-trash-symbolic") {
tooltip_text = _("Remove"),
valign = Gtk.Align.CENTER
};
remove_button.has_frame = false;
remove_button.add_css_class("flat");
remove_button.add_css_class("destructive-action");
remove_button.clicked.connect(() => {
try {
backend.remove_virtual_output(monitor.name);
DisplaysContext.controller.reload();
} catch (Error err) {
Tuner.toast(err.message);
}
});
add_suffix(remove_button);
}
enabled_switch = new Gtk.Switch() {
valign = Gtk.Align.CENTER,
active = monitor.enabled,
......
namespace TunerDisplays {
public class VirtualOutputsWidget : Tuner.Widget {
private Gtk.ListBox list;
public override Gtk.Widget? create() {
var box = new Gtk.Box(Gtk.Orientation.VERTICAL, 0);
list = create_boxed_list();
box.append(list);
DisplaysContext.controller.changed.connect(rebuild);
rebuild();
if (binding != null && binding.validator != null)
binding.validator.apply(binding, box);
return box;
}
private void rebuild() {
clear_list(list);
var controller = DisplaysContext.controller;
var has_virtual_outputs = false;
foreach (var monitor in controller.monitors) {
if (controller.backend.is_virtual_output(monitor)) {
var row = new MonitorRow(monitor, monitor_settings_page_id(), controller.monitors, controller.backend);
row.monitor_selected.connect(controller.open_monitor_settings);
row.monitor_changed.connect(controller.refresh_from_monitors);
list.append(row);
has_virtual_outputs = true;
}
}
if (!has_virtual_outputs) {
list.append(new Adw.ActionRow() {
title = _("No Virtual Displays"),
subtitle = _("Use the add button to create one.")
});
}
list.visible = true;
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment