displays: add monitor config include prompt

parent 18cf5dd4
...@@ -7,6 +7,19 @@ template $TunerDisplaysDisplaysView : Adw.PreferencesPage { ...@@ -7,6 +7,19 @@ template $TunerDisplaysDisplaysView : Adw.PreferencesPage {
title: _("Displays"); title: _("Displays");
icon-name: "video-display-symbolic"; icon-name: "video-display-symbolic";
Adw.PreferencesGroup config_include_group {
Adw.ActionRow config_include_row {
visible: false;
title: _("Monitor configuration is not connected");
[suffix]
Button config_include_button {
valign: center;
label: _("Connect");
}
}
}
Adw.PreferencesGroup status_group {} Adw.PreferencesGroup status_group {}
Adw.PreferencesGroup layout_group {} Adw.PreferencesGroup layout_group {}
......
...@@ -2,7 +2,7 @@ msgid "" ...@@ -2,7 +2,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: tuner-displays\n" "Project-Id-Version: tuner-displays\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-05-31 21:48+0300\n" "POT-Creation-Date: 2026-06-15 11:58+0300\n"
"PO-Revision-Date: 2026-05-28 00:00+0000\n" "PO-Revision-Date: 2026-05-28 00:00+0000\n"
"Last-Translator: Automatically generated\n" "Last-Translator: Automatically generated\n"
"Language-Team: Russian\n" "Language-Team: Russian\n"
...@@ -15,7 +15,17 @@ msgstr "" ...@@ -15,7 +15,17 @@ msgstr ""
msgid "Displays" msgid "Displays"
msgstr "Мониторы" msgstr "Мониторы"
#: data/ui/displays-view.blp:15 #: data/ui/displays-view.blp:13 src/backends/hyprland-backend.vala:40
#: src/backends/hyprland-backend.vala:55 src/backends/niri-backend.vala:46
#: src/backends/niri-backend.vala:61
msgid "Monitor configuration is not connected"
msgstr "Конфигурация мониторов не подключена"
#: data/ui/displays-view.blp:18
msgid "Connect"
msgstr "Подключить"
#: data/ui/displays-view.blp:28
msgid "Details" msgid "Details"
msgstr "Параметры" msgstr "Параметры"
...@@ -55,11 +65,27 @@ msgstr "Нет включённых мониторов для зеркалиро ...@@ -55,11 +65,27 @@ msgstr "Нет включённых мониторов для зеркалиро
msgid "No common mirror mode is available" msgid "No common mirror mode is available"
msgstr "Нет общего режима для зеркалирования" msgstr "Нет общего режима для зеркалирования"
#: src/backends/hyprland-backend.vala:39 #: src/backends/hyprland-backend.vala:41
msgid "Hyprland configuration file was not found."
msgstr "Файл конфигурации Hyprland не найден."
#: src/backends/hyprland-backend.vala:56
msgid "Add monitors.conf to the Hyprland configuration."
msgstr "Добавьте monitors.conf в конфигурацию Hyprland."
#: src/backends/hyprland-backend.vala:80
msgid "hyprctl monitors all returned non-array JSON" msgid "hyprctl monitors all returned non-array JSON"
msgstr "hyprctl monitors all вернул JSON не в виде массива" msgstr "hyprctl monitors all вернул JSON не в виде массива"
#: src/backends/niri-backend.vala:44 #: src/backends/niri-backend.vala:47
msgid "niri configuration file was not found."
msgstr "Файл конфигурации niri не найден."
#: src/backends/niri-backend.vala:62
msgid "Add monitor.kdl to the niri configuration."
msgstr "Добавьте monitor.kdl в конфигурацию niri."
#: src/backends/niri-backend.vala:85
msgid "niri msg outputs returned non-object JSON" msgid "niri msg outputs returned non-object JSON"
msgstr "niri msg outputs вернул JSON не в виде объекта" msgstr "niri msg outputs вернул JSON не в виде объекта"
...@@ -67,73 +93,77 @@ msgstr "niri msg outputs вернул JSON не в виде объекта" ...@@ -67,73 +93,77 @@ msgstr "niri msg outputs вернул JSON не в виде объекта"
msgid "Built-in Display" msgid "Built-in Display"
msgstr "Встроенный дисплей" msgstr "Встроенный дисплей"
#: src/ui/displays-view.vala:68 #: src/ui/displays-view.vala:72
msgid "Failed to load monitors" msgid "Failed to load monitors"
msgstr "Не удалось загрузить мониторы" msgstr "Не удалось загрузить мониторы"
#: src/ui/displays-view.vala:77 #: src/ui/displays-view.vala:81
msgid "Monitor settings applied" msgid "Monitor settings applied"
msgstr "Настройки мониторов применены" msgstr "Настройки мониторов применены"
#: src/ui/displays-view.vala:160 #: src/ui/displays-view.vala:165
msgid "Read-only backend" msgid "Read-only backend"
msgstr "Режим только для чтения" msgstr "Режим только для чтения"
#: src/ui/displays-view.vala:161 #: src/ui/displays-view.vala:166
msgid "Applying monitor layouts is not supported by this backend." msgid "Applying monitor layouts is not supported by this backend."
msgstr "Применение раскладок мониторов не поддерживается этим бэкендом." msgstr "Применение раскладок мониторов не поддерживается этим бэкендом."
#: src/ui/displays-view.vala:171 #: src/ui/displays-view.vala:189
msgid "Monitor configuration connected"
msgstr "Конфигурация мониторов подключена"
#: src/ui/displays-view.vala:201
msgid "Mirror Displays" msgid "Mirror Displays"
msgstr "Зеркалировать мониторы" msgstr "Зеркалировать мониторы"
#: src/ui/displays-view.vala:231 src/ui/monitor-settings-content.vala:88 #: src/ui/displays-view.vala:261 src/ui/monitor-settings-content.vala:88
#: src/ui/monitor-settings-content.vala:127 #: src/ui/monitor-settings-content.vala:127
msgid "Resolution" msgid "Resolution"
msgstr "Разрешение" msgstr "Разрешение"
#: src/ui/displays-view.vala:277 src/ui/monitor-settings-content.vala:265 #: src/ui/displays-view.vala:307 src/ui/monitor-settings-content.vala:265
#: src/ui/monitor-settings-content.vala:291 #: src/ui/monitor-settings-content.vala:291
msgid "Scale" msgid "Scale"
msgstr "Масштаб" msgstr "Масштаб"
#: src/ui/displays-view.vala:295 src/ui/monitor-settings-content.vala:308 #: src/ui/displays-view.vala:325 src/ui/monitor-settings-content.vala:308
msgid "Normal" msgid "Normal"
msgstr "Обычный" msgstr "Обычный"
#: src/ui/displays-view.vala:295 src/ui/monitor-settings-content.vala:308 #: src/ui/displays-view.vala:325 src/ui/monitor-settings-content.vala:308
msgid "90 degrees" msgid "90 degrees"
msgstr "90 градусов" msgstr "90 градусов"
#: src/ui/displays-view.vala:295 src/ui/monitor-settings-content.vala:308 #: src/ui/displays-view.vala:325 src/ui/monitor-settings-content.vala:308
msgid "180 degrees" msgid "180 degrees"
msgstr "180 градусов" msgstr "180 градусов"
#: src/ui/displays-view.vala:295 src/ui/monitor-settings-content.vala:308 #: src/ui/displays-view.vala:325 src/ui/monitor-settings-content.vala:308
msgid "270 degrees" msgid "270 degrees"
msgstr "270 градусов" msgstr "270 градусов"
#: src/ui/displays-view.vala:296 src/ui/monitor-settings-content.vala:309 #: src/ui/displays-view.vala:326 src/ui/monitor-settings-content.vala:309
msgid "Flipped" msgid "Flipped"
msgstr "Отражённый" msgstr "Отражённый"
#: src/ui/displays-view.vala:296 src/ui/monitor-settings-content.vala:309 #: src/ui/displays-view.vala:326 src/ui/monitor-settings-content.vala:309
msgid "Flipped 90 degrees" msgid "Flipped 90 degrees"
msgstr "Отражённый 90 градусов" msgstr "Отражённый 90 градусов"
#: src/ui/displays-view.vala:296 src/ui/monitor-settings-content.vala:309 #: src/ui/displays-view.vala:326 src/ui/monitor-settings-content.vala:309
msgid "Flipped 180 degrees" msgid "Flipped 180 degrees"
msgstr "Отражённый 180 градусов" msgstr "Отражённый 180 градусов"
#: src/ui/displays-view.vala:296 src/ui/monitor-settings-content.vala:309 #: src/ui/displays-view.vala:326 src/ui/monitor-settings-content.vala:309
msgid "Flipped 270 degrees" msgid "Flipped 270 degrees"
msgstr "Отражённый 270 градусов" msgstr "Отражённый 270 градусов"
#: src/ui/displays-view.vala:302 src/ui/monitor-settings-content.vala:315 #: src/ui/displays-view.vala:332 src/ui/monitor-settings-content.vala:315
msgid "Rotation" msgid "Rotation"
msgstr "Поворот" msgstr "Поворот"
#: src/ui/displays-view.vala:335 #: src/ui/displays-view.vala:365
msgid "Primary Display" msgid "Primary Display"
msgstr "Основной дисплей" msgstr "Основной дисплей"
......
...@@ -8,7 +8,7 @@ msgid "" ...@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: tuner-displays\n" "Project-Id-Version: tuner-displays\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-05-31 21:48+0300\n" "POT-Creation-Date: 2026-06-15 11:58+0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
...@@ -21,7 +21,17 @@ msgstr "" ...@@ -21,7 +21,17 @@ msgstr ""
msgid "Displays" msgid "Displays"
msgstr "" msgstr ""
#: data/ui/displays-view.blp:15 #: data/ui/displays-view.blp:13 src/backends/hyprland-backend.vala:40
#: src/backends/hyprland-backend.vala:55 src/backends/niri-backend.vala:46
#: src/backends/niri-backend.vala:61
msgid "Monitor configuration is not connected"
msgstr ""
#: data/ui/displays-view.blp:18
msgid "Connect"
msgstr ""
#: data/ui/displays-view.blp:28
msgid "Details" msgid "Details"
msgstr "" msgstr ""
...@@ -61,11 +71,27 @@ msgstr "" ...@@ -61,11 +71,27 @@ msgstr ""
msgid "No common mirror mode is available" msgid "No common mirror mode is available"
msgstr "" msgstr ""
#: src/backends/hyprland-backend.vala:39 #: src/backends/hyprland-backend.vala:41
msgid "Hyprland configuration file was not found."
msgstr ""
#: src/backends/hyprland-backend.vala:56
msgid "Add monitors.conf to the Hyprland configuration."
msgstr ""
#: src/backends/hyprland-backend.vala:80
msgid "hyprctl monitors all returned non-array JSON" msgid "hyprctl monitors all returned non-array JSON"
msgstr "" msgstr ""
#: src/backends/niri-backend.vala:44 #: src/backends/niri-backend.vala:47
msgid "niri configuration file was not found."
msgstr ""
#: src/backends/niri-backend.vala:62
msgid "Add monitor.kdl to the niri configuration."
msgstr ""
#: src/backends/niri-backend.vala:85
msgid "niri msg outputs returned non-object JSON" msgid "niri msg outputs returned non-object JSON"
msgstr "" msgstr ""
...@@ -73,73 +99,77 @@ msgstr "" ...@@ -73,73 +99,77 @@ msgstr ""
msgid "Built-in Display" msgid "Built-in Display"
msgstr "" msgstr ""
#: src/ui/displays-view.vala:68 #: src/ui/displays-view.vala:72
msgid "Failed to load monitors" msgid "Failed to load monitors"
msgstr "" msgstr ""
#: src/ui/displays-view.vala:77 #: src/ui/displays-view.vala:81
msgid "Monitor settings applied" msgid "Monitor settings applied"
msgstr "" msgstr ""
#: src/ui/displays-view.vala:160 #: src/ui/displays-view.vala:165
msgid "Read-only backend" msgid "Read-only backend"
msgstr "" msgstr ""
#: src/ui/displays-view.vala:161 #: src/ui/displays-view.vala:166
msgid "Applying monitor layouts is not supported by this backend." msgid "Applying monitor layouts is not supported by this backend."
msgstr "" msgstr ""
#: src/ui/displays-view.vala:171 #: src/ui/displays-view.vala:189
msgid "Monitor configuration connected"
msgstr ""
#: src/ui/displays-view.vala:201
msgid "Mirror Displays" msgid "Mirror Displays"
msgstr "" msgstr ""
#: src/ui/displays-view.vala:231 src/ui/monitor-settings-content.vala:88 #: src/ui/displays-view.vala:261 src/ui/monitor-settings-content.vala:88
#: src/ui/monitor-settings-content.vala:127 #: src/ui/monitor-settings-content.vala:127
msgid "Resolution" msgid "Resolution"
msgstr "" msgstr ""
#: src/ui/displays-view.vala:277 src/ui/monitor-settings-content.vala:265 #: src/ui/displays-view.vala:307 src/ui/monitor-settings-content.vala:265
#: src/ui/monitor-settings-content.vala:291 #: src/ui/monitor-settings-content.vala:291
msgid "Scale" msgid "Scale"
msgstr "" msgstr ""
#: src/ui/displays-view.vala:295 src/ui/monitor-settings-content.vala:308 #: src/ui/displays-view.vala:325 src/ui/monitor-settings-content.vala:308
msgid "Normal" msgid "Normal"
msgstr "" msgstr ""
#: src/ui/displays-view.vala:295 src/ui/monitor-settings-content.vala:308 #: src/ui/displays-view.vala:325 src/ui/monitor-settings-content.vala:308
msgid "90 degrees" msgid "90 degrees"
msgstr "" msgstr ""
#: src/ui/displays-view.vala:295 src/ui/monitor-settings-content.vala:308 #: src/ui/displays-view.vala:325 src/ui/monitor-settings-content.vala:308
msgid "180 degrees" msgid "180 degrees"
msgstr "" msgstr ""
#: src/ui/displays-view.vala:295 src/ui/monitor-settings-content.vala:308 #: src/ui/displays-view.vala:325 src/ui/monitor-settings-content.vala:308
msgid "270 degrees" msgid "270 degrees"
msgstr "" msgstr ""
#: src/ui/displays-view.vala:296 src/ui/monitor-settings-content.vala:309 #: src/ui/displays-view.vala:326 src/ui/monitor-settings-content.vala:309
msgid "Flipped" msgid "Flipped"
msgstr "" msgstr ""
#: src/ui/displays-view.vala:296 src/ui/monitor-settings-content.vala:309 #: src/ui/displays-view.vala:326 src/ui/monitor-settings-content.vala:309
msgid "Flipped 90 degrees" msgid "Flipped 90 degrees"
msgstr "" msgstr ""
#: src/ui/displays-view.vala:296 src/ui/monitor-settings-content.vala:309 #: src/ui/displays-view.vala:326 src/ui/monitor-settings-content.vala:309
msgid "Flipped 180 degrees" msgid "Flipped 180 degrees"
msgstr "" msgstr ""
#: src/ui/displays-view.vala:296 src/ui/monitor-settings-content.vala:309 #: src/ui/displays-view.vala:326 src/ui/monitor-settings-content.vala:309
msgid "Flipped 270 degrees" msgid "Flipped 270 degrees"
msgstr "" msgstr ""
#: src/ui/displays-view.vala:302 src/ui/monitor-settings-content.vala:315 #: src/ui/displays-view.vala:332 src/ui/monitor-settings-content.vala:315
msgid "Rotation" msgid "Rotation"
msgstr "" msgstr ""
#: src/ui/displays-view.vala:335 #: src/ui/displays-view.vala:365
msgid "Primary Display" msgid "Primary Display"
msgstr "" msgstr ""
......
...@@ -7,7 +7,23 @@ namespace TunerDisplays { ...@@ -7,7 +7,23 @@ namespace TunerDisplays {
APPLY_FAILED APPLY_FAILED
} }
public enum ConfigIncludeState {
NOT_NEEDED,
INCLUDED,
NOT_INCLUDED,
MISSING_MAIN_CONFIG,
UNSUPPORTED
}
public class ConfigIncludeInfo : Object {
public ConfigIncludeState state { get; set; default = ConfigIncludeState.NOT_NEEDED; }
public string title { get; set; default = ""; }
public string subtitle { get; set; default = ""; }
}
public abstract class DisplayBackend : Object { public abstract class DisplayBackend : Object {
protected delegate bool ConfigIncludeMatcher(string content);
public abstract string id { get; } public abstract string id { get; }
public abstract string title { owned get; } public abstract string title { owned get; }
public abstract bool can_apply { get; } public abstract bool can_apply { get; }
...@@ -33,6 +49,14 @@ namespace TunerDisplays { ...@@ -33,6 +49,14 @@ namespace TunerDisplays {
public abstract Gee.ArrayList<MonitorConfig> load() throws Error; public abstract Gee.ArrayList<MonitorConfig> load() throws Error;
public abstract void apply(Gee.ArrayList<MonitorConfig> monitors) throws Error; public abstract void apply(Gee.ArrayList<MonitorConfig> monitors) throws Error;
public virtual ConfigIncludeInfo config_include_info() {
return new ConfigIncludeInfo();
}
public virtual void include_monitor_config() throws Error {
throw new BackendError.UNSUPPORTED("Including monitor config is not supported by this backend");
}
protected static Json.Node backend_parse_json(string text) throws Error { protected static Json.Node backend_parse_json(string text) throws Error {
var parser = new Json.Parser(); var parser = new Json.Parser();
parser.load_from_data(text); parser.load_from_data(text);
...@@ -78,6 +102,86 @@ namespace TunerDisplays { ...@@ -78,6 +102,86 @@ namespace TunerDisplays {
return value.replace("\\", "\\\\").replace("\"", "\\\""); return value.replace("\\", "\\\\").replace("\"", "\\\"");
} }
protected static void backend_include_config_file(
string main_path,
string included_path,
string include_line,
string comment_prefix,
ConfigIncludeMatcher matcher
) throws Error {
backend_ensure_config_file(included_path);
string content = "";
if (FileUtils.test(main_path, FileTest.EXISTS))
FileUtils.get_contents(main_path, out content);
if (matcher(content))
return;
string updated;
if (backend_uncomment_config_include(content, comment_prefix, matcher, out updated)) {
FileUtils.set_contents(main_path, updated);
return;
}
var separator = content == "" || content.has_suffix("\n") ? "" : "\n";
FileUtils.set_contents(main_path, content + separator + include_line);
}
private static void backend_ensure_config_file(string path) throws Error {
DirUtils.create_with_parents(Path.get_dirname(path), 0755);
if (!FileUtils.test(path, FileTest.EXISTS))
FileUtils.set_contents(path, "");
}
private static bool backend_uncomment_config_include(
string content,
string comment_prefix,
ConfigIncludeMatcher matcher,
out string updated
) {
var builder = new StringBuilder();
var lines = content.split("\n");
var changed = false;
for (var i = 0; i < lines.length; i++) {
var line = lines[i];
if (!changed && backend_uncomment_config_line(line, comment_prefix, matcher, out line))
changed = true;
if (i > 0)
builder.append_c('\n');
builder.append(line);
}
updated = builder.str;
return changed;
}
private static bool backend_uncomment_config_line(
string line,
string comment_prefix,
ConfigIncludeMatcher matcher,
out string uncommented
) {
uncommented = line;
var comment = line.index_of(comment_prefix);
if (comment < 0 || line.substring(0, comment).strip() != "")
return false;
var prefix = line.substring(0, comment);
var candidate = line.substring(comment + comment_prefix.length);
if (candidate.has_prefix(" "))
candidate = candidate.substring(1);
if (!matcher(candidate))
return false;
uncommented = prefix + candidate;
return true;
}
public static DisplayBackend create_for_session() { public static DisplayBackend create_for_session() {
var desktop = (Environment.get_variable("XDG_CURRENT_DESKTOP") ?? "").down(); var desktop = (Environment.get_variable("XDG_CURRENT_DESKTOP") ?? "").down();
var session = (Environment.get_variable("XDG_SESSION_DESKTOP") ?? "").down(); var session = (Environment.get_variable("XDG_SESSION_DESKTOP") ?? "").down();
......
...@@ -29,6 +29,41 @@ namespace TunerDisplays { ...@@ -29,6 +29,41 @@ namespace TunerDisplays {
public override bool supports_color_management { 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_hdr_metadata { get { return true; } }
public override ConfigIncludeInfo config_include_info() {
var main_path = main_config_path();
if (!FileUtils.test(main_path, FileTest.EXISTS)) {
return new ConfigIncludeInfo() {
state = ConfigIncludeState.MISSING_MAIN_CONFIG,
title = _("Monitor configuration is not connected"),
subtitle = _("Hyprland configuration file was not found.")
};
}
try {
string content;
if (FileUtils.get_contents(main_path, out content) && hyprland_config_includes_monitor_config(content))
return new ConfigIncludeInfo() { state = ConfigIncludeState.INCLUDED };
} catch (Error err) {
warning("Failed to check Hyprland monitor config include: %s", err.message);
}
return new ConfigIncludeInfo() {
state = ConfigIncludeState.NOT_INCLUDED,
title = _("Monitor configuration is not connected"),
subtitle = _("Add monitors.conf to the Hyprland configuration.")
};
}
public override void include_monitor_config() throws Error {
backend_include_config_file(
main_config_path(),
monitors_path(),
"source = ~/.config/hypr/monitors.conf\n",
"#",
hyprland_config_includes_monitor_config
);
}
public override Gee.ArrayList<MonitorConfig> load() throws Error { public override Gee.ArrayList<MonitorConfig> load() throws Error {
var monitors = new Gee.ArrayList<MonitorConfig>(); var monitors = new Gee.ArrayList<MonitorConfig>();
var active = read_active_names(); var active = read_active_names();
...@@ -316,6 +351,33 @@ namespace TunerDisplays { ...@@ -316,6 +351,33 @@ namespace TunerDisplays {
return Path.build_filename(Environment.get_user_config_dir(), "hypr", "monitors.conf"); return Path.build_filename(Environment.get_user_config_dir(), "hypr", "monitors.conf");
} }
private static string main_config_path() {
return Path.build_filename(Environment.get_user_config_dir(), "hypr", "hyprland.conf");
}
private static bool hyprland_config_includes_monitor_config(string content) {
foreach (var line in content.split("\n")) {
var trimmed = strip_hyprland_comment(line).strip();
if (!trimmed.has_prefix("source"))
continue;
var equals = trimmed.index_of("=");
if (equals < 0)
continue;
var path = trimmed.substring(equals + 1).strip();
if (path == "~/.config/hypr/monitors.conf" || path == monitors_path())
return true;
}
return false;
}
private static string strip_hyprland_comment(string line) {
var index = line.index_of("#");
return index >= 0 ? line.substring(0, index) : line;
}
private static DisplayMode? parse_mode(string value) { private static DisplayMode? parse_mode(string value) {
var cleaned = value.replace("Hz", ""); var cleaned = value.replace("Hz", "");
var parts = cleaned.split("@"); var parts = cleaned.split("@");
......
...@@ -35,6 +35,41 @@ namespace TunerDisplays { ...@@ -35,6 +35,41 @@ namespace TunerDisplays {
public override bool supports_backdrop_color { get { return true; } } public override bool supports_backdrop_color { get { return true; } }
public override bool supports_hot_corners { get { return true; } } public override bool supports_hot_corners { get { return true; } }
public override ConfigIncludeInfo config_include_info() {
var main_path = main_config_path();
if (!FileUtils.test(main_path, FileTest.EXISTS)) {
return new ConfigIncludeInfo() {
state = ConfigIncludeState.MISSING_MAIN_CONFIG,
title = _("Monitor configuration is not connected"),
subtitle = _("niri configuration file was not found.")
};
}
try {
string content;
if (FileUtils.get_contents(main_path, out content) && niri_config_includes_monitor_config(content))
return new ConfigIncludeInfo() { state = ConfigIncludeState.INCLUDED };
} catch (Error err) {
warning("Failed to check niri monitor config include: %s", err.message);
}
return new ConfigIncludeInfo() {
state = ConfigIncludeState.NOT_INCLUDED,
title = _("Monitor configuration is not connected"),
subtitle = _("Add monitor.kdl to the niri configuration.")
};
}
public override void include_monitor_config() throws Error {
backend_include_config_file(
main_config_path(),
monitors_path(),
"include \"monitor.kdl\"\n",
"//",
niri_config_includes_monitor_config
);
}
public override Gee.ArrayList<MonitorConfig> load() throws Error { public override Gee.ArrayList<MonitorConfig> load() throws Error {
var monitors = new Gee.ArrayList<MonitorConfig>(); var monitors = new Gee.ArrayList<MonitorConfig>();
var saved = read_saved_monitors(); var saved = read_saved_monitors();
...@@ -273,6 +308,28 @@ namespace TunerDisplays { ...@@ -273,6 +308,28 @@ namespace TunerDisplays {
return Path.build_filename(Environment.get_user_config_dir(), "niri", "monitor.kdl"); return Path.build_filename(Environment.get_user_config_dir(), "niri", "monitor.kdl");
} }
private static string main_config_path() {
return Path.build_filename(Environment.get_user_config_dir(), "niri", "config.kdl");
}
private static bool niri_config_includes_monitor_config(string content) {
foreach (var line in content.split("\n")) {
var trimmed = strip_comment(line).strip();
if (!trimmed.has_prefix("include "))
continue;
var path = parse_quoted(trimmed);
if (path == "monitor.kdl"
|| path == "./monitor.kdl"
|| path == "~/.config/niri/monitor.kdl"
|| path == monitors_path()) {
return true;
}
}
return false;
}
private static void append_hot_corners(StringBuilder builder, string value) { private static void append_hot_corners(StringBuilder builder, string value) {
if (value == "") if (value == "")
return; return;
......
...@@ -12,6 +12,7 @@ sources = files( ...@@ -12,6 +12,7 @@ sources = files(
'backends/niri-backend.vala', 'backends/niri-backend.vala',
'core/display-model.vala', 'core/display-model.vala',
'core/shell-command.vala', 'core/shell-command.vala',
'ui/config-include-validator.vala',
'ui/displays-view.vala', 'ui/displays-view.vala',
'ui/monitor-layout.vala', 'ui/monitor-layout.vala',
'ui/monitor-row.vala', 'ui/monitor-row.vala',
......
namespace TunerDisplays {
public class ConfigIncludeBinding : Tuner.Binding {
private DisplayBackend backend;
public ConfigIncludeBinding(DisplayBackend backend) {
this.backend = backend;
}
public ConfigIncludeInfo info() {
return backend.config_include_info();
}
public void connect_config() throws Error {
backend.include_monitor_config();
emit_changed();
}
public override Type expected_type { get { return typeof(bool); } }
public override bool get_value(ref Value value) {
value = info().state == ConfigIncludeState.NOT_INCLUDED;
return true;
}
public override void set_value(Value value) {
}
}
public class ConfigIncludeValidator : Tuner.Validator {
public override void apply(Tuner.Binding binding, Gtk.Widget native_widget) {
var include_binding = binding as ConfigIncludeBinding;
var row = native_widget as Adw.ActionRow;
if (include_binding == null || row == null)
return;
update(include_binding, row);
include_binding.changed.connect(() => update(include_binding, row));
}
private static void update(ConfigIncludeBinding binding, Adw.ActionRow row) {
var info = binding.info();
var visible = info.state == ConfigIncludeState.NOT_INCLUDED
|| info.state == ConfigIncludeState.MISSING_MAIN_CONFIG;
row.visible = visible;
var group = row.get_ancestor(typeof(Adw.PreferencesGroup));
if (group != null)
group.visible = visible;
if (!visible)
return;
row.title = info.title;
row.subtitle = info.subtitle;
}
}
}
...@@ -11,8 +11,11 @@ namespace TunerDisplays { ...@@ -11,8 +11,11 @@ namespace TunerDisplays {
private MonitorSettingsContent? single_monitor_content; private MonitorSettingsContent? single_monitor_content;
private MonitorLayout layout; private MonitorLayout layout;
private Adw.PreferencesRow layout_row; private Adw.PreferencesRow layout_row;
private ConfigIncludeBinding config_include_binding;
private DBusConnection? session_bus; private DBusConnection? session_bus;
private uint monitors_changed_id; private uint monitors_changed_id;
[GtkChild] private unowned Adw.ActionRow config_include_row;
[GtkChild] private unowned Gtk.Button config_include_button;
[GtkChild] private unowned Adw.PreferencesGroup monitors_group; [GtkChild] private unowned Adw.PreferencesGroup monitors_group;
[GtkChild] private unowned Adw.PreferencesGroup layout_group; [GtkChild] private unowned Adw.PreferencesGroup layout_group;
[GtkChild] private unowned Adw.PreferencesGroup status_group; [GtkChild] private unowned Adw.PreferencesGroup status_group;
...@@ -23,6 +26,7 @@ namespace TunerDisplays { ...@@ -23,6 +26,7 @@ namespace TunerDisplays {
construct { construct {
backend = DisplayBackend.create_for_session(); backend = DisplayBackend.create_for_session();
config_include_binding = new ConfigIncludeBinding(backend);
layout_row = new Adw.PreferencesRow() { layout_row = new Adw.PreferencesRow() {
activatable = false, activatable = false,
...@@ -40,6 +44,9 @@ namespace TunerDisplays { ...@@ -40,6 +44,9 @@ namespace TunerDisplays {
layout.layout_changed.connect(sync_rows); layout.layout_changed.connect(sync_rows);
layout_row.child = layout; layout_row.child = layout;
layout_group.add(layout_row); layout_group.add(layout_row);
config_include_button.clicked.connect(connect_monitor_config);
new ConfigIncludeValidator().apply(config_include_binding, config_include_row);
config_include_binding.changed.connect(sync_config_include_button);
subscribe_monitor_changes(); subscribe_monitor_changes();
reload(); reload();
...@@ -133,6 +140,7 @@ namespace TunerDisplays { ...@@ -133,6 +140,7 @@ namespace TunerDisplays {
clear_status_rows(); clear_status_rows();
clear_mirror_settings_rows(); clear_mirror_settings_rows();
clear_single_monitor_settings(); clear_single_monitor_settings();
config_include_binding.emit_changed();
sync_layout_visibility(); sync_layout_visibility();
sync_group_visibility(); sync_group_visibility();
...@@ -163,6 +171,20 @@ namespace TunerDisplays { ...@@ -163,6 +171,20 @@ namespace TunerDisplays {
} }
} }
private void sync_config_include_button() {
config_include_button.visible = config_include_binding.info().state == ConfigIncludeState.NOT_INCLUDED;
}
private void connect_monitor_config() {
try {
config_include_binding.connect_config();
Tuner.toast(_("Monitor configuration connected"));
reload();
} catch (Error err) {
Tuner.toast(err.message);
}
}
private void add_gnome_mirror_row() { private void add_gnome_mirror_row() {
if (!backend.supports_global_mirroring || monitors.size <= 1) if (!backend.supports_global_mirroring || monitors.size <= 1)
return; return;
......
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