display: refine monitor labels and names

parent ff5911c8
......@@ -6,5 +6,6 @@
<file alias="widgets/backdrop-color-widget.ui" preprocess="xml-stripblanks">ui/widgets/backdrop-color-widget.ui</file>
<file alias="widgets/config-include-widget.ui" preprocess="xml-stripblanks">ui/widgets/config-include-widget.ui</file>
<file alias="widgets/monitor-layout.ui" preprocess="xml-stripblanks">ui/widgets/monitor-layout.ui</file>
<file alias="style.css">style.css</file>
</gresource>
</gresources>
/* Matches GNOME Control Center display monitor labels. */
.monitor-label {
border-radius: 50%;
font-weight: bold;
min-width: 1.5em;
min-height: 1.5em;
color: #000;
background: #ddd;
font-feature-settings: "tnum";
}
......@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: tuner-displays\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-06-15 19:13+0300\n"
"POT-Creation-Date: 2026-06-15 21:31+0300\n"
"PO-Revision-Date: 2026-05-28 00:00+0000\n"
"Last-Translator: Automatically generated\n"
"Language-Team: Russian\n"
......@@ -199,7 +199,7 @@ msgstr "Добавьте monitors.conf в конфигурацию Hyprland."
msgid "hyprctl monitors all returned non-array JSON"
msgstr "hyprctl monitors all вернул JSON не в виде массива"
#: src/backends/hyprland-backend.vala:213
#: src/backends/hyprland-backend.vala:219
msgid "Unsupported virtual output backend"
msgstr "Неподдерживаемый бэкенд виртуального монитора"
......@@ -215,7 +215,7 @@ msgstr "Добавьте monitor.kdl в конфигурацию niri."
msgid "niri msg outputs returned non-object JSON"
msgstr "niri msg outputs вернул JSON не в виде объекта"
#: src/core/display-model.vala:75
#: src/core/display-model.vala:78
msgid "Built-in Display"
msgstr "Встроенный дисплей"
......@@ -333,14 +333,14 @@ msgstr "Нет"
msgid "Monitor configuration connected"
msgstr "Конфигурация мониторов подключена"
#: src/ui/widgets/monitor-row.vala:35
#: src/ui/widgets/monitor-row.vala:39
msgid "Remove"
msgstr "Удалить"
#: src/ui/widgets/monitor-row.vala:81
#: src/ui/widgets/monitor-row.vala:94
#, c-format
msgid "%dx%d@%.2f scale %.2f %dx%d"
msgstr "%dx%d@%.2f масштаб %.2f %dx%d"
msgid "%s %dx%d"
msgstr ""
#: src/ui/widgets/primary-display-widget.vala:37
msgid "Primary Display"
......@@ -354,14 +354,18 @@ msgstr "Режим только для чтения"
msgid "Applying monitor layouts is not supported by this backend."
msgstr "Применение раскладок мониторов не поддерживается этим бэкендом."
#: src/ui/widgets/virtual-outputs-widget.vala:35
#: src/ui/widgets/virtual-outputs-widget.vala:36
msgid "No Virtual Displays"
msgstr "Виртуальных мониторов нет"
#: src/ui/widgets/virtual-outputs-widget.vala:36
#: src/ui/widgets/virtual-outputs-widget.vala:37
msgid "Use the add button to create one."
msgstr "Используйте кнопку добавления, чтобы создать виртуальный монитор."
#, fuzzy, c-format
#~ msgid "%s %dx%d@%.2f scale %.2f %dx%d"
#~ msgstr "%dx%d@%.2f масштаб %.2f %dx%d"
#~ msgid "Details"
#~ msgstr "Параметры"
......
......@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: tuner-displays\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-06-15 19:13+0300\n"
"POT-Creation-Date: 2026-06-15 21:31+0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
......@@ -205,7 +205,7 @@ msgstr ""
msgid "hyprctl monitors all returned non-array JSON"
msgstr ""
#: src/backends/hyprland-backend.vala:213
#: src/backends/hyprland-backend.vala:219
msgid "Unsupported virtual output backend"
msgstr ""
......@@ -221,7 +221,7 @@ msgstr ""
msgid "niri msg outputs returned non-object JSON"
msgstr ""
#: src/core/display-model.vala:75
#: src/core/display-model.vala:78
msgid "Built-in Display"
msgstr ""
......@@ -339,13 +339,13 @@ msgstr ""
msgid "Monitor configuration connected"
msgstr ""
#: src/ui/widgets/monitor-row.vala:35
#: src/ui/widgets/monitor-row.vala:39
msgid "Remove"
msgstr ""
#: src/ui/widgets/monitor-row.vala:81
#: src/ui/widgets/monitor-row.vala:94
#, c-format
msgid "%dx%d@%.2f scale %.2f %dx%d"
msgid "%s %dx%d"
msgstr ""
#: src/ui/widgets/primary-display-widget.vala:37
......@@ -360,10 +360,10 @@ msgstr ""
msgid "Applying monitor layouts is not supported by this backend."
msgstr ""
#: src/ui/widgets/virtual-outputs-widget.vala:35
#: src/ui/widgets/virtual-outputs-widget.vala:36
msgid "No Virtual Displays"
msgstr ""
#: src/ui/widgets/virtual-outputs-widget.vala:36
#: src/ui/widgets/virtual-outputs-widget.vala:37
msgid "Use the add button to create one."
msgstr ""
......@@ -81,6 +81,12 @@ namespace TunerDisplays {
monitor.name = obj.get_string_member("name");
monitor.enabled = active.contains(monitor.name);
monitor.description = backend_get_string(obj, "description");
monitor.vendor = backend_get_string(obj, "make");
monitor.product = backend_get_string(obj, "model");
monitor.serial = backend_get_string(obj, "serial");
monitor.physical_width = (int) backend_get_double(obj, "physicalWidth", 0);
monitor.physical_height = (int) backend_get_double(obj, "physicalHeight", 0);
monitor.display_title = MonitorNames.make_title(monitor);
monitor.x = (int) backend_get_double(obj, "x", 0);
monitor.y = (int) backend_get_double(obj, "y", 0);
monitor.width = (int) backend_get_double(obj, "width", 0);
......
......@@ -89,6 +89,8 @@ namespace TunerDisplays {
monitor.product = backend_get_string(obj, "model");
monitor.serial = backend_get_string(obj, "serial");
monitor.description = build_description(monitor.vendor, monitor.product, monitor.serial);
read_physical_size(obj, monitor);
monitor.display_title = MonitorNames.make_title(monitor);
monitor.supports_variable_refresh_rate = backend_get_bool(obj, "vrr_supported", false);
monitor.vrr = backend_get_bool(obj, "vrr_enabled", false) ? 1 : 0;
apply_saved_monitor(saved, monitor);
......@@ -365,6 +367,18 @@ namespace TunerDisplays {
builder.append(value);
}
private static void read_physical_size(Json.Object obj, MonitorConfig monitor) {
if (!obj.has_member("physical_size") || obj.get_member("physical_size").is_null())
return;
var size = obj.get_array_member("physical_size");
if (size.get_length() < 2)
return;
monitor.physical_width = (int) size.get_int_element(0);
monitor.physical_height = (int) size.get_int_element(1);
}
private static string transform_from_niri(string transform) {
for (int i = 0; i < NIRI_TRANSFORMS.length; i++) {
if (NIRI_TRANSFORMS[i] == transform)
......
......@@ -28,6 +28,7 @@ namespace TunerDisplays {
public class MonitorConfig : Object {
public string name { get; set; default = ""; }
public string description { get; set; default = ""; }
public string display_title { get; set; default = ""; }
public string vendor { get; set; default = ""; }
public string product { get; set; default = ""; }
public string serial { get; set; default = ""; }
......@@ -37,6 +38,8 @@ namespace TunerDisplays {
public int y { get; set; }
public int width { get; set; }
public int height { get; set; }
public int physical_width { get; set; }
public int physical_height { get; set; }
public double refresh { get; set; }
public double scale { get; set; default = 1.0; }
public string transform { get; set; default = "normal"; }
......@@ -74,13 +77,10 @@ namespace TunerDisplays {
if (is_builtin())
return _("Built-in Display");
return description != "" ? description : name;
}
}
if (display_title != "")
return display_title;
public string preview_name {
owned get {
return name;
return description != "" ? description : name;
}
}
......@@ -108,6 +108,7 @@ namespace TunerDisplays {
public void copy_from(MonitorConfig source) {
description = source.description;
display_title = source.display_title;
vendor = source.vendor;
product = source.product;
serial = source.serial;
......@@ -117,6 +118,8 @@ namespace TunerDisplays {
y = source.y;
width = source.width;
height = source.height;
physical_width = source.physical_width;
physical_height = source.physical_height;
refresh = source.refresh;
scale = source.scale;
transform = source.transform;
......
namespace TunerDisplays {
public class MonitorNames {
private static UDev.Context? udev = null;
private static UDev.Hwdb? hwdb = null;
public static string make_title(MonitorConfig monitor) {
var vendor = expand_vendor(monitor.vendor);
var size = diagonal_label(monitor.physical_width, monitor.physical_height);
if (vendor != "" && size != "")
return "%s %s".printf(vendor, size);
if (vendor != "" && monitor.product != "")
return "%s %s".printf(vendor, monitor.product);
if (vendor != "")
return vendor;
return monitor.product;
}
public static string expand_vendor(string vendor) {
var code = vendor_code(vendor);
if (code == "")
return vendor;
var name = lookup_vendor(code);
return name != "" ? name : vendor;
}
private static string vendor_code(string vendor) {
var value = vendor.strip();
if (value.has_prefix("PNP(") && value.has_suffix(")") && value.length == 8)
value = value.substring(4, 3);
return value.length == 3 ? value.up() : "";
}
private static string diagonal_label(int width_mm, int height_mm) {
if (width_mm <= 0 || height_mm <= 0)
return "";
var inches = Math.sqrt(width_mm * width_mm + height_mm * height_mm) / 25.4;
return "%.0f\"".printf(inches);
}
private static string lookup_vendor(string code) {
ensure_hwdb();
if (hwdb == null)
return "";
unowned UDev.ListEntry? entry = hwdb.get_properties_list_entry("acpi:%s:".printf(code), 0);
unowned UDev.ListEntry? vendor = UDev.ListEntry.get_by_name(entry, "ID_VENDOR_FROM_DATABASE");
if (vendor == null)
return "";
var value = vendor.get_value();
return value != null ? value : "";
}
private static void ensure_hwdb() {
if (hwdb != null)
return;
udev = UDev.Context.create();
hwdb = new UDev.Hwdb(udev);
}
}
}
[CCode (cheader_filename = "libudev.h")]
namespace UDev {
[Compact]
[CCode (cname = "struct udev", free_function = "udev_unref")]
public class Context {
[CCode (cname = "udev_new")]
public static Context? create();
}
[Compact]
[CCode (cname = "struct udev_hwdb", free_function = "udev_hwdb_unref")]
public class Hwdb {
[CCode (cname = "udev_hwdb_new")]
public Hwdb(Context? udev);
[CCode (cname = "udev_hwdb_get_properties_list_entry")]
public unowned ListEntry? get_properties_list_entry(string modalias, uint flags);
}
[Compact]
[CCode (cname = "struct udev_list_entry", free_function = "")]
public class ListEntry {
[CCode (cname = "udev_list_entry_get_by_name")]
public static unowned ListEntry? get_by_name(ListEntry? list_entry, string name);
[CCode (cname = "udev_list_entry_get_value")]
public unowned string? get_value();
}
}
cc = meson.get_compiler('c')
deps = [
dependency('gee-0.8'),
dependency('json-glib-1.0'),
cc.find_library('udev'),
dependency('tuner-1'),
]
......@@ -11,6 +14,8 @@ sources = files(
'backends/hyprland-backend.vala',
'backends/niri-backend.vala',
'core/display-model.vala',
'libudev.vapi',
'core/monitor-names.vala',
'core/shell-command.vala',
'ui/common/displays-controller.vala',
'ui/common/displays-visibility.vala',
......
......@@ -9,6 +9,8 @@ namespace TunerDisplays {
construct {
Intl.bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR);
load_css();
DisplaysContext.controller = new DisplaysController();
typeof(ConfigIncludeBinding).ensure();
......@@ -72,6 +74,16 @@ namespace TunerDisplays {
DisplaysContext.controller.reload();
}
private void load_css() {
var provider = new Gtk.CssProvider();
provider.load_from_resource("/ru/ximperlinux/tuner/Displays/style.css");
Gtk.StyleContext.add_provider_for_display(
Gdk.Display.get_default(),
provider,
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
);
}
private void show_monitor_settings(MonitorConfig monitor) {
monitor_page.title = monitor.title;
monitor_page_title.label = monitor.title;
......
......@@ -101,7 +101,6 @@ namespace TunerDisplays {
var dark = Adw.StyleManager.get_default().dark;
var tile = dark ? 0.145 : 0.985;
var tile_disabled = dark ? 0.105 : 0.880;
var text = dark ? 0.925 : 0.145;
var line = dark ? 1.0 : 0.0;
var line_alpha = dark ? 0.105 : 0.110;
......@@ -126,18 +125,7 @@ namespace TunerDisplays {
rounded_rectangle(cr, x + 0.5, y + 0.5, w - 1, h - 1, 5);
cr.stroke();
draw_monitor_badge(cr, i + 1, x + 18, y + 18, dark, monitor.enabled);
cr.set_source_rgb(text, text, text);
cr.select_font_face("Sans", Cairo.FontSlant.NORMAL, Cairo.FontWeight.BOLD);
cr.set_font_size(13);
Cairo.TextExtents extents;
cr.text_extents(monitor.preview_name, out extents);
cr.move_to(
x + w / 2 - extents.width / 2 - extents.x_bearing,
y + h / 2 - extents.height / 2 - extents.y_bearing
);
cr.show_text(monitor.preview_name);
draw_monitor_badge(cr, i + 1, x + w / 2, y + h / 2, dark, monitor.enabled);
if (drag_active && dragged == i)
draw_position_overlay(cr, "%dx%d".printf(monitor.x, monitor.y), x + w / 2, y - 8, width, dark);
......
......@@ -18,11 +18,12 @@ namespace TunerDisplays {
return;
}
foreach (var monitor in controller.monitors) {
for (var i = 0; i < controller.monitors.size; i++) {
var monitor = controller.monitors[i];
if (controller.backend.is_virtual_output(monitor))
continue;
var row = new MonitorRow(monitor, monitor_settings_page_id(), controller.monitors, controller.backend);
var row = new MonitorRow(monitor, i + 1, 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);
......
......@@ -5,12 +5,13 @@ namespace TunerDisplays {
private Gee.ArrayList<MonitorConfig> all_monitors;
private string page_id;
private DisplayBackend backend;
private int number;
private Gtk.Switch enabled_switch;
public signal void monitor_changed();
public signal void monitor_selected(MonitorConfig monitor);
public MonitorRow(MonitorConfig monitor, string page_id, Gee.ArrayList<MonitorConfig> all_monitors, DisplayBackend backend) {
public MonitorRow(MonitorConfig monitor, int number, string page_id, Gee.ArrayList<MonitorConfig> all_monitors, DisplayBackend backend) {
Object(
title: monitor.title,
subtitle: subtitle_text(monitor)
......@@ -19,6 +20,7 @@ namespace TunerDisplays {
this.all_monitors = all_monitors;
this.page_id = page_id;
this.backend = backend;
this.number = number;
build();
}
......@@ -30,6 +32,8 @@ namespace TunerDisplays {
activate_action("navigation.push", "s", page_id);
});
add_prefix(create_number_badge());
if (backend.is_virtual_output(monitor)) {
var remove_button = new Gtk.Button.from_icon_name("user-trash-symbolic") {
tooltip_text = _("Remove"),
......@@ -70,6 +74,15 @@ namespace TunerDisplays {
add_suffix(arrow);
}
private Gtk.Widget create_number_badge() {
var badge = new Gtk.Label(number.to_string()) {
halign = Gtk.Align.CENTER,
valign = Gtk.Align.CENTER
};
badge.add_css_class("monitor-label");
return badge;
}
public void sync_from_monitor() {
if (enabled_switch.active != monitor.enabled)
enabled_switch.active = monitor.enabled;
......@@ -78,8 +91,8 @@ namespace TunerDisplays {
}
private static string subtitle_text(MonitorConfig monitor) {
return _("%dx%d@%.2f scale %.2f %dx%d").printf(
monitor.width, monitor.height, monitor.refresh, monitor.scale, monitor.x, monitor.y
return _("%s %dx%d").printf(
monitor.name, monitor.x, monitor.y
);
}
}
......
......@@ -20,9 +20,10 @@ namespace TunerDisplays {
var controller = DisplaysContext.controller;
var has_virtual_outputs = false;
foreach (var monitor in controller.monitors) {
for (var i = 0; i < controller.monitors.size; i++) {
var monitor = controller.monitors[i];
if (controller.backend.is_virtual_output(monitor)) {
var row = new MonitorRow(monitor, monitor_settings_page_id(), controller.monitors, controller.backend);
var row = new MonitorRow(monitor, i + 1, 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);
......
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