Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
T
tuner-displays
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Ximper Linux
tuner-displays
Commits
ff5911c8
Verified
Commit
ff5911c8
authored
Jun 15, 2026
by
Kirill Unitsaev
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
hyprland: add virtual output controls
parent
ba26fb3d
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
142 additions
and
1 deletion
+142
-1
displays-page.blp
data/ui/pages/displays-page.blp
+23
-1
POTFILES
po/POTFILES
+1
-0
ru.po
po/ru.po
+0
-0
tuner-displays.pot
po/tuner-displays.pot
+0
-0
display-backend.vala
src/backends/display-backend.vala
+13
-0
hyprland-backend.vala
src/backends/hyprland-backend.vala
+25
-0
meson.build
src/meson.build
+1
-0
plugin.vala
src/plugin.vala
+11
-0
displays-visibility.vala
src/ui/common/displays-visibility.vala
+2
-0
monitor-list-widget.vala
src/ui/widgets/monitor-list-widget.vala
+4
-0
monitor-row.vala
src/ui/widgets/monitor-row.vala
+19
-0
virtual-outputs-widget.vala
src/ui/widgets/virtual-outputs-widget.vala
+43
-0
No files found.
data/ui/pages/displays-page.blp
View file @
ff5911c8
...
...
@@ -56,7 +56,7 @@ Tuner.Page displays_page {
}
Tuner.Group {
title: _("D
etail
s");
title: _("D
isplay
s");
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 {
...
...
po/POTFILES
View file @
ff5911c8
...
...
@@ -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
po/ru.po
View file @
ff5911c8
This diff is collapsed.
Click to expand it.
po/tuner-displays.pot
View file @
ff5911c8
This diff is collapsed.
Click to expand it.
src/backends/display-backend.vala
View file @
ff5911c8
...
...
@@ -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
);
...
...
src/backends/hyprland-backend.vala
View file @
ff5911c8
...
...
@@ -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
;
...
...
src/meson.build
View file @
ff5911c8
...
...
@@ -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',
...
...
src/plugin.vala
View file @
ff5911c8
...
...
@@ -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
;
...
...
src/ui/common/displays-visibility.vala
View file @
ff5911c8
...
...
@@ -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
;
}
...
...
src/ui/widgets/monitor-list-widget.vala
View file @
ff5911c8
...
...
@@ -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
);
}
}
...
...
src/ui/widgets/monitor-row.vala
View file @
ff5911c8
...
...
@@ -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
,
...
...
src/ui/widgets/virtual-outputs-widget.vala
0 → 100644
View file @
ff5911c8
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
;
}
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment