mirror of
https://github.com/ErikReider/SwayNotificationCenter.git
synced 2024-11-25 06:27:08 +03:00
Per app volume control (#235)
This commit is contained in:
parent
a2397da06a
commit
91f19dfb4e
@ -314,13 +314,13 @@ config file to be able to detect config errors
|
||||
default: "right" ++
|
||||
description: Horizontal position of the button in the bar ++
|
||||
enum: ["right", "left"] ++
|
||||
animation_type: ++
|
||||
animation-type: ++
|
||||
type: string ++
|
||||
optional: true ++
|
||||
default: "slide_down" ++
|
||||
description: Animation type for menu++
|
||||
enum: ["slide_down", "slide_up", "none"] ++
|
||||
animation_duration: ++
|
||||
animation-duration: ++
|
||||
type: integer ++
|
||||
optional: true ++
|
||||
default: 250 ++
|
||||
@ -388,13 +388,51 @@ config file to be able to detect config errors
|
||||
description: A grid of buttons that execute shell commands ++
|
||||
*volume*++
|
||||
type: object ++
|
||||
css class: widget-volume ++
|
||||
css class: ++
|
||||
widget-volume ++
|
||||
per-app-volume ++
|
||||
properties: ++
|
||||
label: ++
|
||||
type: string ++
|
||||
optional: true ++
|
||||
default: "Volume" ++
|
||||
description: Text displayed in front of the volume slider ++
|
||||
show-per-app: ++
|
||||
type: bool ++
|
||||
optional: true ++
|
||||
default: false ++
|
||||
description: Show per app volume control ++
|
||||
empty-list-label: ++
|
||||
type: string ++
|
||||
optional: true ++
|
||||
default: "No active sink input" ++
|
||||
description: Text displayed when there are not active sink inputs ++
|
||||
expand-button-label: ++
|
||||
type: string ++
|
||||
optional: true ++
|
||||
default: "⇧" ++
|
||||
description: Label displayed on button to show per app volume control ++
|
||||
collapse-button-label: ++
|
||||
type: string ++
|
||||
optional: true ++
|
||||
default: "⇩" ++
|
||||
description: Label displayed on button to hide per app volume control ++
|
||||
icon-size: ++
|
||||
type: integer ++
|
||||
optional: true ++
|
||||
default: 24 ++
|
||||
description: Size of the application icon in per app volume control ++
|
||||
animation-type: ++
|
||||
type: string ++
|
||||
optional: true ++
|
||||
default: "slide_down" ++
|
||||
description: Animation type for the per app volume control ++
|
||||
enum: ["slide_down", "slide_up", "none"] ++
|
||||
animation-duration: ++
|
||||
type: integer ++
|
||||
optional: true ++
|
||||
default: 250 ++
|
||||
description: Duration of animation in milliseconds ++
|
||||
description: Slider to control pulse volume ++
|
||||
*backlight*++
|
||||
type: object ++
|
||||
|
@ -423,13 +423,13 @@
|
||||
"default": "right",
|
||||
"enum": ["right", "left"]
|
||||
},
|
||||
"animation_type": {
|
||||
"animation-type": {
|
||||
"type": "string",
|
||||
"default": "slide_down",
|
||||
"description": "Animation type for menu",
|
||||
"enum": ["slide_down", "slide_up", "none"]
|
||||
},
|
||||
"animation_duration":{
|
||||
"animation-duration":{
|
||||
"type": "integer",
|
||||
"default": 250,
|
||||
"description": "Duration of animation in milliseconds"
|
||||
@ -466,6 +466,42 @@
|
||||
"type": "string",
|
||||
"description": "Text displayed in front of the volume slider",
|
||||
"default": "Volume"
|
||||
},
|
||||
"show-per-app": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Show per app volume control"
|
||||
},
|
||||
"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"
|
||||
},
|
||||
"icon-size": {
|
||||
"type": "integer",
|
||||
"default": 24,
|
||||
"description": "Size of the application icon in 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"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -167,10 +167,10 @@ namespace SwayNotificationCenter.Widgets {
|
||||
info ("No label for menu-object given using default");
|
||||
}
|
||||
|
||||
int duration = int.max (0, get_prop<int> (obj, "animation_duration"));
|
||||
int duration = int.max (0, get_prop<int> (obj, "animation-duration"));
|
||||
if (duration == 0) duration = 250;
|
||||
|
||||
string ? animation_type = get_prop<string> (obj, "animation_type");
|
||||
string ? animation_type = get_prop<string> (obj, "animation-type");
|
||||
if (animation_type == null) {
|
||||
animation_type = "slide_down";
|
||||
info ("No animation-type for menu-object given using default");
|
||||
@ -204,5 +204,13 @@ namespace SwayNotificationCenter.Widgets {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public override void on_cc_visibility_change (bool val) {
|
||||
if (!val) {
|
||||
foreach (var obj in menu_objects) {
|
||||
obj.revealer ?.set_reveal_child (false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ namespace SwayNotificationCenter.Widgets {
|
||||
* https://github.com/elementary/switchboard-plug-sound
|
||||
*/
|
||||
public class PulseDaemon : Object {
|
||||
private Context? context;
|
||||
private Context ? context;
|
||||
private GLibMainLoop mainloop;
|
||||
private bool quitting = false;
|
||||
|
||||
@ -21,10 +21,14 @@ namespace SwayNotificationCenter.Widgets {
|
||||
|
||||
public HashMap<string, PulseDevice> sinks { get; private set; }
|
||||
|
||||
public HashMap<uint32, PulseSinkInput> active_sinks { get; private set; }
|
||||
|
||||
construct {
|
||||
mainloop = new GLibMainLoop ();
|
||||
|
||||
sinks = new HashMap<string, PulseDevice> ();
|
||||
|
||||
active_sinks = new HashMap<uint32, PulseSinkInput> ();
|
||||
}
|
||||
|
||||
public void start () {
|
||||
@ -45,6 +49,10 @@ namespace SwayNotificationCenter.Widgets {
|
||||
|
||||
public signal void change_default_device (PulseDevice device);
|
||||
|
||||
public signal void new_active_sink (PulseSinkInput device);
|
||||
public signal void change_active_sink (PulseSinkInput device);
|
||||
public signal void remove_active_sink (PulseSinkInput device);
|
||||
|
||||
public signal void new_device (PulseDevice device);
|
||||
public signal void change_device (PulseDevice device);
|
||||
public signal void remove_device (PulseDevice device);
|
||||
@ -100,6 +108,26 @@ namespace SwayNotificationCenter.Widgets {
|
||||
var type = t & Context.SubscriptionEventType.FACILITY_MASK;
|
||||
var event = t & Context.SubscriptionEventType.TYPE_MASK;
|
||||
switch (type) {
|
||||
case Context.SubscriptionEventType.SINK_INPUT:
|
||||
switch (event) {
|
||||
default: break;
|
||||
case Context.SubscriptionEventType.NEW:
|
||||
case Context.SubscriptionEventType.CHANGE:
|
||||
ctx.get_sink_input_info_list (this.get_sink_input_info);
|
||||
break;
|
||||
case Context.SubscriptionEventType.REMOVE:
|
||||
// A safe way of removing the sink_input
|
||||
var iter = active_sinks.map_iterator ();
|
||||
while (iter.next ()) {
|
||||
var sink_input = iter.get_value ();
|
||||
if (sink_input.index != index) continue;
|
||||
this.remove_active_sink (sink_input);
|
||||
iter.unset ();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case Context.SubscriptionEventType.SINK:
|
||||
switch (event) {
|
||||
default: break;
|
||||
@ -165,6 +193,68 @@ namespace SwayNotificationCenter.Widgets {
|
||||
|
||||
ctx.get_card_info_list (this.get_card_info);
|
||||
ctx.get_sink_info_list (this.get_sink_info);
|
||||
|
||||
foreach (var sink_input in active_sinks) {
|
||||
sink_input.value.active = false;
|
||||
}
|
||||
ctx.get_sink_input_info_list (this.get_sink_input_info);
|
||||
var iter = active_sinks.map_iterator ();
|
||||
while (iter.next ()) {
|
||||
var sink_input = iter.get_value ();
|
||||
if (!sink_input.active) {
|
||||
this.remove_active_sink (sink_input);
|
||||
iter.unset ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void get_sink_input_info (Context ctx, SinkInputInfo ? info, int eol) {
|
||||
if (info == null || eol != 0) return;
|
||||
|
||||
uint32 id = PulseSinkInput.get_hash_map_key (info.index);
|
||||
PulseSinkInput sink_input = null;
|
||||
bool has_sink_input = active_sinks.has_key (id);
|
||||
if (has_sink_input) {
|
||||
sink_input = active_sinks.get (id);
|
||||
} else {
|
||||
sink_input = new PulseSinkInput ();
|
||||
}
|
||||
|
||||
sink_input.index = info.index;
|
||||
sink_input.sink_index = info.sink;
|
||||
sink_input.client_index = info.client;
|
||||
|
||||
sink_input.name = info.proplist.gets ("application.name");
|
||||
sink_input.application_binary = info.proplist
|
||||
.gets ("application.process.binary");
|
||||
sink_input.application_icon_name = info.proplist
|
||||
.gets ("application.icon_name");
|
||||
sink_input.media_name = info.proplist.gets ("media.name");
|
||||
|
||||
sink_input.is_muted = info.mute == 1;
|
||||
|
||||
sink_input.cvolume = info.volume;
|
||||
sink_input.channel_map = info.channel_map;
|
||||
sink_input.balance = sink_input.cvolume
|
||||
.get_balance (sink_input.channel_map);
|
||||
sink_input.volume_operations.foreach ((op) => {
|
||||
if (op.get_state () != Operation.State.RUNNING) {
|
||||
sink_input.volume_operations.remove (op);
|
||||
}
|
||||
return Source.CONTINUE;
|
||||
});
|
||||
if (sink_input.volume_operations.is_empty) {
|
||||
sink_input.volume = volume_to_double (
|
||||
sink_input.cvolume.max ());
|
||||
}
|
||||
sink_input.active = true;
|
||||
|
||||
if (!has_sink_input) {
|
||||
active_sinks.set (id, sink_input);
|
||||
this.new_active_sink (sink_input);
|
||||
} else {
|
||||
this.change_active_sink (sink_input);
|
||||
}
|
||||
}
|
||||
|
||||
private void get_card_info (Context ctx, CardInfo ? info, int eol) {
|
||||
@ -368,6 +458,26 @@ namespace SwayNotificationCenter.Widgets {
|
||||
/*
|
||||
* Setters
|
||||
*/
|
||||
public void set_sink_input_volume (PulseSinkInput sink_input, double volume) {
|
||||
sink_input.volume_operations.foreach ((operation) => {
|
||||
if (operation.get_state () == Operation.State.RUNNING) {
|
||||
operation.cancel ();
|
||||
}
|
||||
|
||||
sink_input.volume_operations.remove (operation);
|
||||
return GLib.Source.CONTINUE;
|
||||
});
|
||||
|
||||
var cvol = sink_input.cvolume;
|
||||
cvol.scale (double_to_volume (volume));
|
||||
Operation ? operation = null;
|
||||
operation = context.set_sink_input_volume (
|
||||
sink_input.index, cvol);
|
||||
if (operation != null) {
|
||||
sink_input.volume_operations.add (operation);
|
||||
}
|
||||
}
|
||||
|
||||
public void set_device_volume (PulseDevice device, double volume) {
|
||||
device.volume_operations.foreach ((operation) => {
|
||||
if (operation.get_state () == Operation.State.RUNNING) {
|
||||
@ -515,10 +625,10 @@ namespace SwayNotificationCenter.Widgets {
|
||||
}
|
||||
}
|
||||
|
||||
// public void set_sink_input_mute (bool state, PulseSinkInput sink_input) {
|
||||
// if (sink_input.is_muted == state) return;
|
||||
// context.set_sink_input_mute (sink_input.index, state);
|
||||
// }
|
||||
public void set_sink_input_mute (bool state, PulseSinkInput sink_input) {
|
||||
if (sink_input.is_muted == state) return;
|
||||
context.set_sink_input_mute (sink_input.index, state);
|
||||
}
|
||||
|
||||
/*
|
||||
* Volume utils
|
||||
|
62
src/controlCenter/widgets/volume/pulseSinkInput.vala
Normal file
62
src/controlCenter/widgets/volume/pulseSinkInput.vala
Normal file
@ -0,0 +1,62 @@
|
||||
// From SwaySettings PulseAudio page: https://github.com/ErikReider/SwaySettings/blob/2b05776bce2fd55933a7fbdec995f54849e39e7d/src/Pages/Pulse/PulseSinkInput.vala
|
||||
using PulseAudio;
|
||||
using Gee;
|
||||
|
||||
namespace SwayNotificationCenter.Widgets {
|
||||
public class PulseSinkInput : Object {
|
||||
/** The card index: ex. `Sink Input #227` */
|
||||
public uint32 index;
|
||||
/** The sink index: ex. `55` */
|
||||
public uint32 sink_index;
|
||||
/** The client index: ex. `266` */
|
||||
public uint32 client_index;
|
||||
|
||||
/** The name of the application: `application.name` */
|
||||
public string name;
|
||||
/** The name of the application binary: `application.process.binary` */
|
||||
public string application_binary;
|
||||
/** The application icon. Can be null: `application.icon_name` */
|
||||
public string ? application_icon_name;
|
||||
/** The name of the media: `media.name` */
|
||||
public string media_name;
|
||||
|
||||
/** The mute state: `Mute` */
|
||||
public bool is_muted;
|
||||
|
||||
public double volume;
|
||||
public float balance { get; set; default = 0; }
|
||||
public CVolume cvolume;
|
||||
public ChannelMap channel_map;
|
||||
public LinkedList<Operation> volume_operations;
|
||||
|
||||
public bool active;
|
||||
|
||||
/** Gets the name to be shown to the user:
|
||||
* "application_name"
|
||||
*/
|
||||
public string ? get_display_name () {
|
||||
return name;
|
||||
}
|
||||
|
||||
public bool cmp (PulseSinkInput sink_input) {
|
||||
return sink_input.index == index
|
||||
&& sink_input.sink_index == sink_index
|
||||
&& sink_input.client_index == client_index
|
||||
&& sink_input.name == name
|
||||
&& sink_input.application_binary == application_binary
|
||||
&& sink_input.is_muted == is_muted
|
||||
&& sink_input.volume == volume;
|
||||
}
|
||||
|
||||
/** Gets the name to be shown to the user:
|
||||
* "index:application_name"
|
||||
*/
|
||||
public static uint32 get_hash_map_key (uint32 i) {
|
||||
return i;
|
||||
}
|
||||
|
||||
construct {
|
||||
volume_operations = new LinkedList<Operation> ();
|
||||
}
|
||||
}
|
||||
}
|
49
src/controlCenter/widgets/volume/sinkInputRow.vala
Normal file
49
src/controlCenter/widgets/volume/sinkInputRow.vala
Normal file
@ -0,0 +1,49 @@
|
||||
namespace SwayNotificationCenter.Widgets {
|
||||
public class SinkInputRow : Gtk.ListBoxRow {
|
||||
|
||||
Gtk.Box container;
|
||||
Gtk.Image icon = new Gtk.Image ();
|
||||
Gtk.Scale scale = new Gtk.Scale.with_range (Gtk.Orientation.HORIZONTAL, 0, 100, 1);
|
||||
|
||||
public unowned PulseSinkInput sink_input;
|
||||
|
||||
private unowned PulseDaemon client;
|
||||
|
||||
public SinkInputRow (PulseSinkInput sink_input, PulseDaemon client, int icon_size) {
|
||||
this.client = client;
|
||||
|
||||
update (sink_input);
|
||||
|
||||
scale.draw_value = false;
|
||||
|
||||
icon.pixel_size = icon_size;
|
||||
|
||||
container = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0);
|
||||
|
||||
container.add (icon);
|
||||
|
||||
container.pack_start (scale);
|
||||
|
||||
add (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 ();
|
||||
});
|
||||
}
|
||||
|
||||
public void update (PulseSinkInput sink_input) {
|
||||
this.sink_input = sink_input;
|
||||
|
||||
icon.set_from_icon_name (
|
||||
sink_input.application_icon_name ?? "application-x-executable",
|
||||
Gtk.IconSize.DIALOG
|
||||
);
|
||||
|
||||
scale.set_value (sink_input.volume);
|
||||
scale.tooltip_text = ((int) scale.get_value ()).to_string ();
|
||||
|
||||
this.show_all ();
|
||||
}
|
||||
}
|
||||
}
|
@ -6,12 +6,29 @@ namespace SwayNotificationCenter.Widgets {
|
||||
}
|
||||
}
|
||||
|
||||
Gtk.Box main_volume_slider_container = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0);
|
||||
Gtk.Label label_widget = new Gtk.Label (null);
|
||||
Gtk.Scale slider = new Gtk.Scale.with_range (Gtk.Orientation.HORIZONTAL, 0, 100, 1);
|
||||
|
||||
// Per app volume controll
|
||||
Gtk.ListBox levels_listbox;
|
||||
Gtk.Button reveal_button;
|
||||
Gtk.Revealer revealer;
|
||||
Gtk.Label no_sink_inputs_label;
|
||||
string empty_label = "No active sink input";
|
||||
|
||||
string expand_label = "⇧";
|
||||
string collapse_label = "⇩";
|
||||
int icon_size = 24;
|
||||
|
||||
Gtk.RevealerTransitionType revealer_type = Gtk.RevealerTransitionType.SLIDE_DOWN;
|
||||
int revealer_duration = 250;
|
||||
|
||||
private PulseDevice ? default_sink = null;
|
||||
private PulseDaemon client = new PulseDaemon ();
|
||||
|
||||
private bool show_per_app;
|
||||
|
||||
construct {
|
||||
this.client.change_default_device.connect (default_device_changed);
|
||||
|
||||
@ -32,12 +49,83 @@ namespace SwayNotificationCenter.Widgets {
|
||||
if (config != null) {
|
||||
string ? label = get_prop<string> (config, "label");
|
||||
label_widget.set_label (label ?? "Volume");
|
||||
|
||||
show_per_app = get_prop<bool> (config, "show-per-app") ? true : false;
|
||||
|
||||
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;
|
||||
|
||||
int i = int.max (get_prop<int> (config, "icon-size"), 0);
|
||||
if (i != 0) icon_size = i;
|
||||
|
||||
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;
|
||||
|
||||
slider.draw_value = false;
|
||||
|
||||
add (label_widget);
|
||||
pack_start (slider, true, true, 0);
|
||||
main_volume_slider_container.add (label_widget);
|
||||
main_volume_slider_container.pack_start (slider, true, true, 0);
|
||||
add (main_volume_slider_container);
|
||||
|
||||
if (show_per_app) {
|
||||
reveal_button = new Gtk.Button.with_label (expand_label);
|
||||
revealer = new Gtk.Revealer ();
|
||||
revealer.transition_type = revealer_type;
|
||||
revealer.transition_duration = revealer_duration;
|
||||
levels_listbox = new Gtk.ListBox ();
|
||||
levels_listbox.get_style_context ().add_class ("per-app-volume");
|
||||
revealer.add (levels_listbox);
|
||||
|
||||
if (this.client.active_sinks.size == 0) {
|
||||
no_sink_inputs_label = new Gtk.Label (empty_label);
|
||||
levels_listbox.add (no_sink_inputs_label);
|
||||
}
|
||||
|
||||
foreach (var item in this.client.active_sinks.values) {
|
||||
levels_listbox.add (new SinkInputRow (item, client, icon_size));
|
||||
}
|
||||
|
||||
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.clicked.connect (() => {
|
||||
bool show = revealer.reveal_child;
|
||||
revealer.set_reveal_child (!show);
|
||||
if (show) {
|
||||
reveal_button.label = expand_label;
|
||||
} else {
|
||||
reveal_button.label = collapse_label;
|
||||
}
|
||||
});
|
||||
|
||||
main_volume_slider_container.pack_end (reveal_button, false, false, 0);
|
||||
add (revealer);
|
||||
}
|
||||
|
||||
show_all ();
|
||||
}
|
||||
@ -47,6 +135,7 @@ namespace SwayNotificationCenter.Widgets {
|
||||
this.client.start ();
|
||||
} else {
|
||||
this.client.close ();
|
||||
if (show_per_app) revealer.set_reveal_child (false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -56,5 +145,41 @@ namespace SwayNotificationCenter.Widgets {
|
||||
slider.set_value (device.volume);
|
||||
}
|
||||
}
|
||||
|
||||
private void active_sink_change (PulseSinkInput sink) {
|
||||
foreach (var row in levels_listbox.get_children ()) {
|
||||
if (row == null) continue;
|
||||
var s = (SinkInputRow) row;
|
||||
if (s.sink_input.cmp (sink)) {
|
||||
s.update (sink);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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_children ().first ().data;
|
||||
levels_listbox.remove ((Gtk.Widget) label);
|
||||
}
|
||||
levels_listbox.add (new SinkInputRow (sink, client, icon_size));
|
||||
show_all ();
|
||||
}
|
||||
|
||||
private void active_sink_removed (PulseSinkInput sink) {
|
||||
foreach (var row in levels_listbox.get_children ()) {
|
||||
if (row == null) continue;
|
||||
var s = (SinkInputRow) row;
|
||||
if (s.sink_input.cmp (sink)) {
|
||||
levels_listbox.remove (row);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (levels_listbox.get_children ().length () == 0) {
|
||||
levels_listbox.add (no_sink_inputs_label);
|
||||
show_all ();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -44,6 +44,8 @@ widget_sources = [
|
||||
'controlCenter/widgets/volume/volume.vala',
|
||||
'controlCenter/widgets/volume/pulseDaemon.vala',
|
||||
'controlCenter/widgets/volume/pulseDevice.vala',
|
||||
'controlCenter/widgets/volume/pulseSinkInput.vala',
|
||||
'controlCenter/widgets/volume/sinkInputRow.vala',
|
||||
# Widget: Backlight Slider
|
||||
'controlCenter/widgets/backlight/backlight.vala',
|
||||
'controlCenter/widgets/backlight/backlightUtil.vala',
|
||||
|
@ -291,6 +291,18 @@
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.widget-volume>box>button {
|
||||
background: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.per-app-volume {
|
||||
background-color: @noti-bg-alt;
|
||||
padding: 4px 8px 8px 8px;
|
||||
margin: 0px 8px 8px 8px;
|
||||
border-radius: 12px
|
||||
}
|
||||
|
||||
/* Backlight widget */
|
||||
.widget-backlight {
|
||||
background-color: @noti-bg;
|
||||
|
Loading…
Reference in New Issue
Block a user