Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
X
ximper-shell-notification-center
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
ximper-shell-notification-center
Commits
a67ff66e
Verified
Commit
a67ff66e
authored
Mar 24, 2026
by
Kirill Unitsaev
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
volume: auto-show per-app and device selector, remove config options
parent
113863fd
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
274 additions
and
267 deletions
+274
-267
volume.scss
data/style/widgets/volume.scss
+11
-11
configSchema.json
src/configSchema.json
+0
-56
sinkInputRow.vala
src/controlCenter/widgets/volume/sinkInputRow.vala
+23
-36
volume.vala
src/controlCenter/widgets/volume/volume.vala
+240
-164
No files found.
data/style/widgets/volume.scss
View file @
a67ff66e
.widget-volume
{
padding
:
0
px
10px
;
padding
:
0
;
margin
:
5px
15px
;
border-radius
:
var
(
--
window-radius
);
...
...
@@ -8,26 +8,22 @@
box-shadow
:
0px
0px
10px
var
(
--
headerbar-shade-color
);
border
:
1px
solid
var
(
--
border-color
);
}
&
>
box
{
padding
:
0
10px
;
margin
:
0
0
0
3px
;
}
}
.widget-volume
.image-button
{
background
:
transparent
;
}
.widget-volume
>
box
{
margin
:
0
0
0
3px
;
}
.widget-volume
>
box
>
button
{}
.widget-volume
revealer
{
padding
:
0
;
margin
:
0
;
}
.per-app-volume
{
background
:
transparent
;
padding
:
0
;
padding
:
0
10px
;
margin
:
0
;
border-radius
:
var
(
--
border-radius
);
}
...
...
@@ -35,3 +31,7 @@
.per-app-volume
row
{
background
:
transparent
;
}
.sink-selector
{
padding
:
0
;
}
src/configSchema.json
View file @
a67ff66e
...
...
@@ -339,9 +339,6 @@
"^slider(#[a-zA-Z0-9_-]{1,}){0,1}?$"
:
{
"$ref"
:
"#/widgets/slider"
},
"^volume(#[a-zA-Z0-9_-]{1,}){0,1}?$"
:
{
"$ref"
:
"#/widgets/volume"
},
"^backlight(#[a-zA-Z0-9_-]{1,}){0,1}?$"
:
{
"$ref"
:
"#/widgets/backlight"
},
...
...
@@ -484,59 +481,6 @@
}
}
},
"volume"
:
{
"type"
:
"object"
,
"description"
:
"Slider to control pulse volume"
,
"additionalProperties"
:
false
,
"properties"
:
{
"show-per-app"
:
{
"type"
:
"boolean"
,
"default"
:
false
,
"description"
:
"Show per app volume control"
},
"show-per-app-icon"
:
{
"type"
:
"boolean"
,
"default"
:
true
,
"description"
:
"Show application icon in per app control"
},
"show-per-app-label"
:
{
"type"
:
"boolean"
,
"default"
:
false
,
"description"
:
"Show application name in per app control"
},
"expand-per-app"
:
{
"type"
:
"boolean"
,
"default"
:
false
,
"description"
:
"If the per app section should start expanded"
},
"empty-list-label"
:
{
"type"
:
"string"
,
"default"
:
"No active sink input"
,
"description"
:
"Text displayed when there are not active sink inputs"
},
"expand-button-label"
:
{
"type"
:
"string"
,
"default"
:
"⇧"
,
"description"
:
"Label displayed on button to show per app volume control"
},
"collapse-button-label"
:
{
"type"
:
"string"
,
"default"
:
"⇩"
,
"description"
:
"Label displayed on button to hide per app volume control"
},
"animation-type"
:
{
"type"
:
"string"
,
"default"
:
"slide_down"
,
"description"
:
"Animation type for menu"
,
"enum"
:
[
"slide_down"
,
"slide_up"
,
"none"
]
},
"animation-duration"
:
{
"type"
:
"integer"
,
"default"
:
250
,
"description"
:
"Duration of animation in milliseconds"
}
}
},
"backlight"
:
{
"type"
:
"object"
,
"description"
:
"Unified brightness control widget. Auto-detects display backlights (/sys/class/backlight), DDC/CI monitors, and keyboard backlights (/sys/class/leds/*kbd*)."
,
...
...
src/controlCenter/widgets/volume/sinkInputRow.vala
View file @
a67ff66e
...
...
@@ -2,21 +2,16 @@ namespace XimperShellNotificationCenter.Widgets {
public
class
SinkInputRow
:
Gtk
.
ListBoxRow
{
Gtk
.
Box
container
;
Gtk
.
Image
icon
=
new
Gtk
.
Image
();
Gtk
.
Label
label
=
new
Gtk
.
Label
(
null
);
Gtk
.
Scale
scale
=
new
Gtk
.
Scale
.
with_range
(
Gtk
.
Orientation
.
HORIZONTAL
,
0
,
100
,
1
);
Gtk
.
Scale
scale
=
new
Gtk
.
Scale
.
with_range
(
Gtk
.
Orientation
.
HORIZONTAL
,
0
,
100
,
1
);
public
unowned
PulseSinkInput
sink_input
;
private
unowned
PulseDaemon
client
;
private
bool
show_per_app_icon
;
private
bool
show_per_app_label
;
public
SinkInputRow
(
PulseSinkInput
sink_input
,
PulseDaemon
client
,
bool
show_per_app_icon
,
bool
show_per_app_label
)
{
public
SinkInputRow
(
PulseSinkInput
sink_input
,
PulseDaemon
client
)
{
this
.
client
=
client
;
this
.
show_per_app_icon
=
show_per_app_icon
;
this
.
show_per_app_label
=
show_per_app_label
;
set_activatable
(
false
);
...
...
@@ -25,49 +20,41 @@ namespace XimperShellNotificationCenter.Widgets {
scale
.
draw_value
=
false
;
scale
.
set_hexpand
(
true
);
container
=
new
Gtk
.
Box
(
Gtk
.
Orientation
.
HORIZONTAL
,
0
);
if
(
show_per_app_icon
)
{
container
.
append
(
icon
);
}
if
(
show_per_app_label
)
{
container
.
append
(
label
);
}
container
=
new
Gtk
.
Box
(
Gtk
.
Orientation
.
HORIZONTAL
,
0
);
container
.
append
(
icon
);
container
.
append
(
scale
);
set_child
(
container
);
scale
.
value_changed
.
connect
(()
=>
{
client
.
set_sink_input_volume
(
sink_input
,
(
float
)
scale
.
get_value
());
scale
.
tooltip_text
=
((
int
)
scale
.
get_value
()).
to_string
();
client
.
set_sink_input_volume
(
sink_input
,
(
float
)
scale
.
get_value
());
scale
.
tooltip_text
=
((
int
)
scale
.
get_value
()).
to_string
();
});
}
public
void
update
(
PulseSinkInput
sink_input
)
{
this
.
sink_input
=
sink_input
;
if
(
show_per_app_icon
)
{
string
icon_name
;
if
(
sink_input
.
application_icon_name
!=
null
)
{
icon_name
=
sink_input
.
application_icon_name
;
}
else
{
string
icon_name
=
"application-x-executable"
;
if
(
sink_input
.
application_icon_name
!=
null
)
{
icon_name
=
sink_input
.
application_icon_name
;
}
else
if
(
sink_input
.
application_binary
!=
null
)
{
var
theme
=
Gtk
.
IconTheme
.
get_for_display
(
Gdk
.
Display
.
get_default
());
if
(
theme
.
has_icon
(
sink_input
.
application_binary
))
{
icon_name
=
sink_input
.
application_binary
;
}
var
theme
=
Gtk
.
IconTheme
.
get_for_display
(
Gdk
.
Display
.
get_default
());
if
(
theme
.
has_icon
(
icon_name
))
{
icon
.
set_from_icon_name
(
icon_name
);
}
else
{
icon
.
set_from_icon_name
(
"application-x-executable"
);
}
}
if
(
show_per_app_label
)
{
label
.
set_text
(
this
.
sink_input
.
name
);
}
icon
.
set_from_icon_name
(
icon_name
);
icon
.
set_tooltip_text
(
sink_input
.
name
);
scale
.
set_value
(
sink_input
.
volume
);
scale
.
tooltip_text
=
((
int
)
scale
.
get_value
()).
to_string
();
scale
.
tooltip_text
=
((
int
)
scale
.
get_value
()).
to_string
();
}
}
}
src/controlCenter/widgets/volume/volume.vala
View file @
a67ff66e
...
...
@@ -6,44 +6,43 @@ namespace XimperShellNotificationCenter.Widgets {
}
}
Gtk
.
Box
main_volume_slider_container
=
new
Gtk
.
Box
(
Gtk
.
Orientation
.
HORIZONTAL
,
0
);
Gtk
.
Box
main_volume_slider_container
=
new
Gtk
.
Box
(
Gtk
.
Orientation
.
HORIZONTAL
,
0
);
Gtk
.
Image
icon_widget
=
new
Gtk
.
Image
();
Gtk
.
Scale
slider
=
new
Gtk
.
Scale
.
with_range
(
Gtk
.
Orientation
.
HORIZONTAL
,
0
,
100
,
1
);
Gtk
.
Scale
slider
=
new
Gtk
.
Scale
.
with_range
(
Gtk
.
Orientation
.
HORIZONTAL
,
0
,
100
,
1
);
// Per app volume control
Gtk
.
ListBox
levels_listbox
;
IterListBoxController
list_box_controller
;
// Expandable section
Gtk
.
ToggleButton
reveal_button
;
Gtk
.
Revealer
revealer
;
Gtk
.
Label
no_sink_inputs_label
;
string
empty_label
;
string
?
expand_label
=
null
;
string
?
collapse_label
=
null
;
Gtk
.
Box
revealer_content
;
// Per app volume
Gtk
.
ListBox
levels_listbox
;
IterListBoxController
list_box_controller
;
Gtk
.
RevealerTransitionType
revealer_type
=
Gtk
.
RevealerTransitionType
.
SLIDE_DOWN
;
int
revealer_duration
=
250
;
// Sink selector
Gtk
.
Separator
sink_separator
;
Gtk
.
Box
sink_list_box
;
uint
sink_rebuild_timeout
=
0
;
private
PulseDevice
?
default_sink
=
null
;
private
PulseDaemon
client
=
new
PulseDaemon
();
private
bool
show_per_app
=
false
;
private
bool
show_per_app_icon
=
true
;
private
bool
show_per_app_label
=
false
;
private
bool
expand_per_app
=
false
;
construct
{
this
.
client
.
change_default_device
.
connect
(
default_device_changed
);
this
.
client
.
change_default_device
.
connect
(
default_device_changed
);
icon_widget
.
set_from_icon_name
(
"audio-volume-high-symbolic"
);
icon_widget
.
set_from_icon_name
(
"audio-volume-high-symbolic"
);
slider
.
value_changed
.
connect
(()
=>
{
if
(
default_sink
!=
null
)
{
this
.
client
.
set_device_volume
(
default_sink
,
(
float
)
slider
.
get_value
());
slider
.
tooltip_text
=
((
int
)
slider
.
get_value
()).
to_string
();
slider
.
tooltip_text
=
((
int
)
slider
.
get_value
()).
to_string
();
}
update_volume_icon
((
int
)
slider
.
get_value
());
});
...
...
@@ -51,131 +50,93 @@ namespace XimperShellNotificationCenter.Widgets {
public
Volume
(
string
suffix
)
{
base
(
suffix
);
empty_label
=
_
(
"No active sink input"
);
Json
.
Object
?
config
=
get_config
(
this
);
if
(
config
!=
null
)
{
bool
show_per_app_found
;
bool
?
show_per_app
=
get_prop
<
bool
>
(
config
,
"show-per-app"
,
out
show_per_app_found
);
if
(
show_per_app_found
)
{
this
.
show_per_app
=
show_per_app
;
}
bool
show_per_app_icon_found
;
bool
?
show_per_app_icon
=
get_prop
<
bool
>
(
config
,
"show-per-app-icon"
,
out
show_per_app_icon_found
);
if
(
show_per_app_icon_found
)
{
this
.
show_per_app_icon
=
show_per_app_icon
;
}
bool
show_per_app_label_found
;
bool
?
show_per_app_label
=
get_prop
<
bool
>
(
config
,
"show-per-app-label"
,
out
show_per_app_label_found
);
if
(
show_per_app_label_found
)
{
this
.
show_per_app_label
=
show_per_app_label
;
}
bool
expand_per_app_found
;
bool
?
expand_per_app
=
get_prop
<
bool
>
(
config
,
"expand-per-app"
,
out
expand_per_app_found
);
if
(
expand_per_app_found
)
{
this
.
expand_per_app
=
expand_per_app
;
}
string
?
el
=
get_prop
<
string
>
(
config
,
"empty-list-label"
);
if
(
el
!=
null
)
{
empty_label
=
el
;
}
string
?
l1
=
get_prop
<
string
>
(
config
,
"expand-button-label"
);
if
(
l1
!=
null
)
{
expand_label
=
l1
;
}
string
?
l2
=
get_prop
<
string
>
(
config
,
"collapse-button-label"
);
if
(
l2
!=
null
)
{
collapse_label
=
l2
;
}
revealer_duration
=
int
.
max
(
0
,
get_prop
<
int
>
(
config
,
"animation-duration"
));
if
(
revealer_duration
==
0
)
{
revealer_duration
=
250
;
}
string
?
animation_type
=
get_prop
<
string
>
(
config
,
"animation-type"
);
if
(
animation_type
!=
null
)
{
switch
(
animation_type
)
{
default
:
case
"none"
:
revealer_type
=
Gtk
.
RevealerTransitionType
.
NONE
;
break
;
case
"slide_up"
:
revealer_type
=
Gtk
.
RevealerTransitionType
.
SLIDE_UP
;
break
;
case
"slide_down"
:
revealer_type
=
Gtk
.
RevealerTransitionType
.
SLIDE_DOWN
;
break
;
}
}
}
this
.
orientation
=
Gtk
.
Orientation
.
VERTICAL
;
// Main slider
slider
.
draw_value
=
false
;
slider
.
set_hexpand
(
true
);
main_volume_slider_container
.
append
(
icon_widget
);
main_volume_slider_container
.
append
(
slider
);
append
(
main_volume_slider_container
);
if
(
show_per_app
)
{
revealer
=
new
Gtk
.
Revealer
();
revealer
.
transition_type
=
revealer_type
;
revealer
.
transition_duration
=
revealer_duration
;
levels_listbox
=
new
Gtk
.
ListBox
();
levels_listbox
.
add_css_class
(
"per-app-volume"
);
levels_listbox
.
set_activate_on_single_click
(
true
);
levels_listbox
.
set_selection_mode
(
Gtk
.
SelectionMode
.
NONE
);
revealer
.
set_child
(
levels_listbox
);
list_box_controller
=
new
IterListBoxController
(
levels_listbox
);
if
(
this
.
client
.
active_sinks
.
size
==
0
)
{
no_sink_inputs_label
=
new
Gtk
.
Label
(
empty_label
);
list_box_controller
.
append
(
no_sink_inputs_label
);
}
foreach
(
var
item
in
this
.
client
.
active_sinks
.
values
)
{
SinkInputRow
row
=
new
SinkInputRow
(
item
,
client
,
show_per_app_icon
,
show_per_app_label
);
list_box_controller
.
append
(
row
);
}
this
.
client
.
change_active_sink
.
connect
(
active_sink_change
);
this
.
client
.
new_active_sink
.
connect
(
active_sink_added
);
this
.
client
.
remove_active_sink
.
connect
(
active_sink_removed
);
reveal_button
=
new
Gtk
.
ToggleButton
();
reveal_button
.
set_active
(
expand_per_app
);
revealer
.
set_reveal_child
(
expand_per_app
);
// Reveal button
reveal_button
=
new
Gtk
.
ToggleButton
();
set_button_icon
();
reveal_button
.
toggled
.
connect
(()
=>
{
set_button_icon
();
reveal_button
.
toggled
.
connect
(()
=>
{
set_button_icon
();
revealer
.
set_reveal_child
(
reveal_button
.
active
);
if
(
reveal_button
.
active
)
{
add_css_class
(
"expanded"
);
}
else
{
remove_css_class
(
"expanded"
);
}
});
if
(
expand_per_app
)
{
revealer
.
set_reveal_child
(
reveal_button
.
active
);
if
(
reveal_button
.
active
)
{
add_css_class
(
"expanded"
);
}
else
{
remove_css_class
(
"expanded"
);
}
main_volume_slider_container
.
append
(
reveal_button
);
append
(
revealer
);
});
main_volume_slider_container
.
append
(
reveal_button
);
append
(
main_volume_slider_container
);
// Revealer with per-app + sink selector
revealer
=
new
Gtk
.
Revealer
();
revealer
.
transition_type
=
Gtk
.
RevealerTransitionType
.
SLIDE_DOWN
;
revealer
.
transition_duration
=
200
;
revealer_content
=
new
Gtk
.
Box
(
Gtk
.
Orientation
.
VERTICAL
,
0
);
revealer
.
set_child
(
revealer_content
);
// Per-app volume list
levels_listbox
=
new
Gtk
.
ListBox
();
levels_listbox
.
add_css_class
(
"per-app-volume"
);
levels_listbox
.
set_activate_on_single_click
(
true
);
levels_listbox
.
set_selection_mode
(
Gtk
.
SelectionMode
.
NONE
);
list_box_controller
=
new
IterListBoxController
(
levels_listbox
);
foreach
(
var
item
in
this
.
client
.
active_sinks
.
values
)
{
SinkInputRow
row
=
new
SinkInputRow
(
item
,
client
);
list_box_controller
.
append
(
row
);
}
this
.
client
.
change_active_sink
.
connect
(
active_sink_change
);
this
.
client
.
new_active_sink
.
connect
(
active_sink_added
);
this
.
client
.
remove_active_sink
.
connect
(
active_sink_removed
);
revealer_content
.
append
(
levels_listbox
);
// Separator between per-app and sinks
sink_separator
=
new
Gtk
.
Separator
(
Gtk
.
Orientation
.
HORIZONTAL
);
sink_separator
.
set_margin_top
(
8
);
sink_separator
.
set_margin_bottom
(
8
);
sink_separator
.
set_visible
(
false
);
revealer_content
.
append
(
sink_separator
);
// Sink selector
sink_list_box
=
new
Gtk
.
Box
(
Gtk
.
Orientation
.
VERTICAL
,
0
);
sink_list_box
.
add_css_class
(
"sink-selector"
);
sink_list_box
.
set_margin_start
(
4
);
sink_list_box
.
set_margin_end
(
4
);
sink_list_box
.
set_margin_top
(
4
);
sink_list_box
.
set_margin_bottom
(
4
);
sink_list_box
.
set_visible
(
false
);
revealer_content
.
append
(
sink_list_box
);
append
(
revealer
);
// Device signals
this
.
client
.
new_device
.
connect
(
on_device_changed
);
this
.
client
.
change_device
.
connect
(
on_device_changed
);
this
.
client
.
remove_device
.
connect
(
on_device_changed
);
update_reveal_visibility
();
}
private
void
update_volume_icon
(
int
volume
)
{
...
...
@@ -194,18 +155,37 @@ namespace XimperShellNotificationCenter.Widgets {
private
void
set_button_icon
()
{
if
(!
reveal_button
.
active
)
{
if
(
expand_label
==
null
)
{
reveal_button
.
set_icon_name
(
"ximper-shell-notification-center-up-small-symbolic"
);
}
else
{
reveal_button
.
set_label
(
expand_label
);
}
reveal_button
.
set_icon_name
(
"ximper-shell-notification-center-up-small-symbolic"
);
}
else
{
if
(
collapse_label
==
null
)
{
reveal_button
.
set_icon_name
(
"ximper-shell-notification-center-down-small-symbolic"
);
}
else
{
reveal_button
.
set_label
(
collapse_label
);
reveal_button
.
set_icon_name
(
"ximper-shell-notification-center-down-small-symbolic"
);
}
}
private
void
update_reveal_visibility
()
{
bool
has_apps
=
client
.
active_sinks
.
size
>
0
;
bool
has_sinks
=
count_output_sinks
()
>
1
;
// Show/hide components
levels_listbox
.
set_visible
(
has_apps
);
sink_separator
.
set_visible
(
has_apps
&&
has_sinks
);
// Show reveal button only if there's something
reveal_button
.
set_visible
(
has_apps
||
has_sinks
);
}
private
int
count_output_sinks
()
{
int
count
=
0
;
foreach
(
var
device
in
client
.
sinks
.
values
)
{
if
(
device
.
removed
)
continue
;
if
(
device
.
direction
!=
PulseAudio
.
Direction
.
OUTPUT
)
{
continue
;
}
count
++;
}
return
count
;
}
public
override
void
on_cc_visibility_change
(
bool
val
)
{
...
...
@@ -213,24 +193,23 @@ namespace XimperShellNotificationCenter.Widgets {
this
.
client
.
start
();
}
else
{
this
.
client
.
close
();
}
if
(
show_per_app
)
{
reveal_button
.
set_active
(
expand_per_app
);
reveal_button
.
set_active
(
false
);
}
}
private
void
default_device_changed
(
PulseDevice
device
)
{
if
(
device
!=
null
&&
device
.
direction
==
PulseAudio
.
Direction
.
OUTPUT
)
{
if
(
device
!=
null
&&
device
.
direction
==
PulseAudio
.
Direction
.
OUTPUT
)
{
this
.
default_sink
=
device
;
slider
.
set_value
(
device
.
volume
);
}
}
private
void
active_sink_change
(
PulseSinkInput
sink
)
{
foreach
(
unowned
Gtk
.
Widget
row
in
list_box_controller
.
get_children
())
{
if
(!(
row
is
SinkInputRow
))
{
continue
;
}
foreach
(
unowned
Gtk
.
Widget
row
in
list_box_controller
.
get_children
())
{
if
(!(
row
is
SinkInputRow
))
continue
;
var
s
=
(
SinkInputRow
)
row
;
if
(
s
.
sink_input
.
cmp
(
sink
))
{
s
.
update
(
sink
);
...
...
@@ -240,31 +219,128 @@ namespace XimperShellNotificationCenter.Widgets {
}
private
void
active_sink_added
(
PulseSinkInput
sink
)
{
// one element added -> remove the empty label
if
(
this
.
client
.
active_sinks
.
size
==
1
)
{
var
label
=
levels_listbox
.
get_first_child
();
list_box_controller
.
remove
((
Gtk
.
Widget
)
label
);
}
SinkInputRow
row
=
new
SinkInputRow
(
sink
,
client
,
show_per_app_icon
,
show_per_app_label
);
SinkInputRow
row
=
new
SinkInputRow
(
sink
,
client
);
list_box_controller
.
append
(
row
);
update_reveal_visibility
();
}
private
void
active_sink_removed
(
PulseSinkInput
sink
)
{
foreach
(
unowned
Gtk
.
Widget
row
in
list_box_controller
.
get_children
())
{
if
(!(
row
is
SinkInputRow
))
{
continue
;
}
foreach
(
unowned
Gtk
.
Widget
row
in
list_box_controller
.
get_children
())
{
if
(!(
row
is
SinkInputRow
))
continue
;
var
s
=
(
SinkInputRow
)
row
;
if
(
s
.
sink_input
.
cmp
(
sink
))
{
list_box_controller
.
remove
(
row
);
break
;
}
}
if
(
levels_listbox
.
get_first_child
()
==
null
)
{
list_box_controller
.
append
(
no_sink_inputs_label
);
update_reveal_visibility
();
}
private
static
string
get_sink_icon
(
string
?
name
)
{
if
(
name
==
null
||
name
.
length
==
0
)
{
return
"audio-card-symbolic"
;
}
var
theme
=
Gtk
.
IconTheme
.
get_for_display
(
Gdk
.
Display
.
get_default
());
string
[]
candidates
=
{
name
,
name
+
"-symbolic"
};
string
shortened
=
name
;
while
(
shortened
.
last_index_of
(
"-"
)
>
0
)
{
shortened
=
shortened
.
substring
(
0
,
shortened
.
last_index_of
(
"-"
));
candidates
+=
shortened
;
candidates
+=
shortened
+
"-symbolic"
;
}
foreach
(
string
candidate
in
candidates
)
{
if
(
theme
.
has_icon
(
candidate
))
{
return
candidate
;
}
}
return
"audio-card-symbolic"
;
}
private
void
on_device_changed
(
PulseDevice
device
)
{
if
(
sink_rebuild_timeout
!=
0
)
{
Source
.
remove
(
sink_rebuild_timeout
);
}
sink_rebuild_timeout
=
Timeout
.
add
(
100
,
()
=>
{
sink_rebuild_timeout
=
0
;
rebuild_sink_list
();
return
Source
.
REMOVE
;
});
}
private
void
rebuild_sink_list
()
{
// Clear existing
for
(
var
child
=
sink_list_box
.
get_first_child
();
child
!=
null
;)
{
var
next
=
child
.
get_next_sibling
();
sink_list_box
.
remove
(
child
);
child
=
next
;
}
// Collect OUTPUT sinks
int
count
=
0
;
foreach
(
var
device
in
client
.
sinks
.
values
)
{
if
(
device
.
removed
)
continue
;
if
(
device
.
direction
!=
PulseAudio
.
Direction
.
OUTPUT
)
{
continue
;
}
count
++;
var
row
=
new
Gtk
.
Box
(
Gtk
.
Orientation
.
HORIZONTAL
,
8
);
row
.
add_css_class
(
"sink-row"
);
string
sink_icon
=
get_sink_icon
(
device
.
icon_name
);
var
row_icon
=
new
Gtk
.
Image
.
from_icon_name
(
sink_icon
);
string
?
name
=
device
.
get_display_name
();
if
(
name
==
null
||
name
==
""
)
{
name
=
device
.
device_description
??
"Unknown"
;
}
var
row_label
=
new
Gtk
.
Label
(
name
);
row_label
.
set_ellipsize
(
Pango
.
EllipsizeMode
.
END
);
row_label
.
set_hexpand
(
true
);
row_label
.
set_halign
(
Gtk
.
Align
.
START
);
row
.
append
(
row_icon
);
row
.
append
(
row_label
);
bool
is_active
=
default_sink
!=
null
&&
device
.
device_name
==
default_sink
.
device_name
&&
device
.
port_name
==
default_sink
.
port_name
;
if
(
is_active
)
{
var
check
=
new
Gtk
.
Image
.
from_icon_name
(
"object-select-symbolic"
);
row
.
append
(
check
);
}
var
btn
=
new
Gtk
.
Button
();
btn
.
set_child
(
row
);
btn
.
add_css_class
(
"flat"
);
btn
.
clicked
.
connect
(()
=>
{
client
.
set_default_device
.
begin
(
device
);
});
sink_list_box
.
append
(
btn
);
}
bool
show
=
count
>
1
;
sink_list_box
.
set_visible
(
show
);
update_reveal_visibility
();
}
}
}
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