Per app volume control (#235)
This commit is contained in:
@@ -314,13 +314,13 @@ config file to be able to detect config errors
|
|||||||
default: "right" ++
|
default: "right" ++
|
||||||
description: Horizontal position of the button in the bar ++
|
description: Horizontal position of the button in the bar ++
|
||||||
enum: ["right", "left"] ++
|
enum: ["right", "left"] ++
|
||||||
animation_type: ++
|
animation-type: ++
|
||||||
type: string ++
|
type: string ++
|
||||||
optional: true ++
|
optional: true ++
|
||||||
default: "slide_down" ++
|
default: "slide_down" ++
|
||||||
description: Animation type for menu++
|
description: Animation type for menu++
|
||||||
enum: ["slide_down", "slide_up", "none"] ++
|
enum: ["slide_down", "slide_up", "none"] ++
|
||||||
animation_duration: ++
|
animation-duration: ++
|
||||||
type: integer ++
|
type: integer ++
|
||||||
optional: true ++
|
optional: true ++
|
||||||
default: 250 ++
|
default: 250 ++
|
||||||
@@ -388,13 +388,51 @@ config file to be able to detect config errors
|
|||||||
description: A grid of buttons that execute shell commands ++
|
description: A grid of buttons that execute shell commands ++
|
||||||
*volume*++
|
*volume*++
|
||||||
type: object ++
|
type: object ++
|
||||||
css class: widget-volume ++
|
css class: ++
|
||||||
|
widget-volume ++
|
||||||
|
per-app-volume ++
|
||||||
properties: ++
|
properties: ++
|
||||||
label: ++
|
label: ++
|
||||||
type: string ++
|
type: string ++
|
||||||
optional: true ++
|
optional: true ++
|
||||||
default: "Volume" ++
|
default: "Volume" ++
|
||||||
description: Text displayed in front of the volume slider ++
|
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 ++
|
description: Slider to control pulse volume ++
|
||||||
*backlight*++
|
*backlight*++
|
||||||
type: object ++
|
type: object ++
|
||||||
|
@@ -423,13 +423,13 @@
|
|||||||
"default": "right",
|
"default": "right",
|
||||||
"enum": ["right", "left"]
|
"enum": ["right", "left"]
|
||||||
},
|
},
|
||||||
"animation_type": {
|
"animation-type": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"default": "slide_down",
|
"default": "slide_down",
|
||||||
"description": "Animation type for menu",
|
"description": "Animation type for menu",
|
||||||
"enum": ["slide_down", "slide_up", "none"]
|
"enum": ["slide_down", "slide_up", "none"]
|
||||||
},
|
},
|
||||||
"animation_duration":{
|
"animation-duration":{
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"default": 250,
|
"default": 250,
|
||||||
"description": "Duration of animation in milliseconds"
|
"description": "Duration of animation in milliseconds"
|
||||||
@@ -466,6 +466,42 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Text displayed in front of the volume slider",
|
"description": "Text displayed in front of the volume slider",
|
||||||
"default": "Volume"
|
"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");
|
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;
|
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) {
|
if (animation_type == null) {
|
||||||
animation_type = "slide_down";
|
animation_type = "slide_down";
|
||||||
info ("No animation-type for menu-object given using default");
|
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
|
* https://github.com/elementary/switchboard-plug-sound
|
||||||
*/
|
*/
|
||||||
public class PulseDaemon : Object {
|
public class PulseDaemon : Object {
|
||||||
private Context? context;
|
private Context ? context;
|
||||||
private GLibMainLoop mainloop;
|
private GLibMainLoop mainloop;
|
||||||
private bool quitting = false;
|
private bool quitting = false;
|
||||||
|
|
||||||
@@ -21,10 +21,14 @@ namespace SwayNotificationCenter.Widgets {
|
|||||||
|
|
||||||
public HashMap<string, PulseDevice> sinks { get; private set; }
|
public HashMap<string, PulseDevice> sinks { get; private set; }
|
||||||
|
|
||||||
|
public HashMap<uint32, PulseSinkInput> active_sinks { get; private set; }
|
||||||
|
|
||||||
construct {
|
construct {
|
||||||
mainloop = new GLibMainLoop ();
|
mainloop = new GLibMainLoop ();
|
||||||
|
|
||||||
sinks = new HashMap<string, PulseDevice> ();
|
sinks = new HashMap<string, PulseDevice> ();
|
||||||
|
|
||||||
|
active_sinks = new HashMap<uint32, PulseSinkInput> ();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void start () {
|
public void start () {
|
||||||
@@ -45,6 +49,10 @@ namespace SwayNotificationCenter.Widgets {
|
|||||||
|
|
||||||
public signal void change_default_device (PulseDevice device);
|
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 new_device (PulseDevice device);
|
||||||
public signal void change_device (PulseDevice device);
|
public signal void change_device (PulseDevice device);
|
||||||
public signal void remove_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 type = t & Context.SubscriptionEventType.FACILITY_MASK;
|
||||||
var event = t & Context.SubscriptionEventType.TYPE_MASK;
|
var event = t & Context.SubscriptionEventType.TYPE_MASK;
|
||||||
switch (type) {
|
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:
|
case Context.SubscriptionEventType.SINK:
|
||||||
switch (event) {
|
switch (event) {
|
||||||
default: break;
|
default: break;
|
||||||
@@ -165,6 +193,68 @@ namespace SwayNotificationCenter.Widgets {
|
|||||||
|
|
||||||
ctx.get_card_info_list (this.get_card_info);
|
ctx.get_card_info_list (this.get_card_info);
|
||||||
ctx.get_sink_info_list (this.get_sink_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) {
|
private void get_card_info (Context ctx, CardInfo ? info, int eol) {
|
||||||
@@ -368,6 +458,26 @@ namespace SwayNotificationCenter.Widgets {
|
|||||||
/*
|
/*
|
||||||
* Setters
|
* 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) {
|
public void set_device_volume (PulseDevice device, double volume) {
|
||||||
device.volume_operations.foreach ((operation) => {
|
device.volume_operations.foreach ((operation) => {
|
||||||
if (operation.get_state () == Operation.State.RUNNING) {
|
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) {
|
public void set_sink_input_mute (bool state, PulseSinkInput sink_input) {
|
||||||
// if (sink_input.is_muted == state) return;
|
if (sink_input.is_muted == state) return;
|
||||||
// context.set_sink_input_mute (sink_input.index, state);
|
context.set_sink_input_mute (sink_input.index, state);
|
||||||
// }
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Volume utils
|
* 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.Label label_widget = new Gtk.Label (null);
|
||||||
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 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 PulseDevice ? default_sink = null;
|
||||||
private PulseDaemon client = new PulseDaemon ();
|
private PulseDaemon client = new PulseDaemon ();
|
||||||
|
|
||||||
|
private bool show_per_app;
|
||||||
|
|
||||||
construct {
|
construct {
|
||||||
this.client.change_default_device.connect (default_device_changed);
|
this.client.change_default_device.connect (default_device_changed);
|
||||||
|
|
||||||
@@ -32,12 +49,83 @@ namespace SwayNotificationCenter.Widgets {
|
|||||||
if (config != null) {
|
if (config != null) {
|
||||||
string ? label = get_prop<string> (config, "label");
|
string ? label = get_prop<string> (config, "label");
|
||||||
label_widget.set_label (label ?? "Volume");
|
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;
|
slider.draw_value = false;
|
||||||
|
|
||||||
add (label_widget);
|
main_volume_slider_container.add (label_widget);
|
||||||
pack_start (slider, true, true, 0);
|
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 ();
|
show_all ();
|
||||||
}
|
}
|
||||||
@@ -47,6 +135,7 @@ namespace SwayNotificationCenter.Widgets {
|
|||||||
this.client.start ();
|
this.client.start ();
|
||||||
} else {
|
} else {
|
||||||
this.client.close ();
|
this.client.close ();
|
||||||
|
if (show_per_app) revealer.set_reveal_child (false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,5 +145,41 @@ namespace SwayNotificationCenter.Widgets {
|
|||||||
slider.set_value (device.volume);
|
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/volume.vala',
|
||||||
'controlCenter/widgets/volume/pulseDaemon.vala',
|
'controlCenter/widgets/volume/pulseDaemon.vala',
|
||||||
'controlCenter/widgets/volume/pulseDevice.vala',
|
'controlCenter/widgets/volume/pulseDevice.vala',
|
||||||
|
'controlCenter/widgets/volume/pulseSinkInput.vala',
|
||||||
|
'controlCenter/widgets/volume/sinkInputRow.vala',
|
||||||
# Widget: Backlight Slider
|
# Widget: Backlight Slider
|
||||||
'controlCenter/widgets/backlight/backlight.vala',
|
'controlCenter/widgets/backlight/backlight.vala',
|
||||||
'controlCenter/widgets/backlight/backlightUtil.vala',
|
'controlCenter/widgets/backlight/backlightUtil.vala',
|
||||||
|
@@ -291,6 +291,18 @@
|
|||||||
border-radius: 12px;
|
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 */
|
/* Backlight widget */
|
||||||
.widget-backlight {
|
.widget-backlight {
|
||||||
background-color: @noti-bg;
|
background-color: @noti-bg;
|
||||||
|
Reference in New Issue
Block a user