gui/choose_active_sink_gtk3/choose_active_sink_gtk3.rb
class PulseAudio::Gui::Choose_active_sink_gtk3
attr_reader :ui
def initialize
@ui = Gtk3assist::Builder.new.add_from_file("#{File.dirname(__FILE__)}/choose_active_sink.glade")
@ui.connect_signals{|h| method(h)}
#Init treeviews.
@tv_sinks = Gtk3assist::Treeview.new(
:tv => @ui["tvSinks"],
:model => :liststore,
:sort_col => :name,
:cols => [
{:id => :id, :title => _("ID")},
{:id => :name, :title => _("Name")}
]
)
@ui["tvSinks"].get_column(0).visible = false
self.reload_sinks
@tv_sources = Gtk3assist::Treeview.new(
:tv => @ui["tvSources"],
:model => :liststore,
:sort_col => :name,
:cols => [
{:id => :id, :title => _("ID")},
{:id => :name, :title => _("Name")}
]
)
@ui["tvSources"].get_column(0).visible = false
self.reload_sources
@tv_applications = Gtk3assist::Treeview.new(
:tv => @ui["tvApplications"],
:model => :liststore,
:sort_col => :name,
:cols => [
{:id => :id, :title => _("ID")},
{:id => :name, :title => _("Name")}
]
)
@ui["tvApplications"].get_column(0).visible = false
@ui["tvApplications"].selection.signal_connect("changed", &self.method(:on_tvApplications_selection_changed))
self.reload_applications
self.update_application_info
@ui["frameApplicationSettings"].hide
PulseAudio::Sink::Input.auto_redirect_new_inputs_to_default_sink
PulseAudio::Source::Output.auto_redirect_new_outputs_to_default_source
events = PulseAudio::Events.instance
events.connect(:event => :remove, &self.method(:on_remove))
events.connect(:event => :new, &self.method(:on_new))
events.connect(:event => :change, &self.method(:on_change))
@sicon = Gtk::StatusIcon.new
@sicon.signal_connect("activate", &self.method(:on_sicon_activate))
@sicon.signal_connect("popup-menu", &self.method(:on_sicon_popupmenu))
@sicon.signal_connect("scroll-event", &self.method(:on_sicon_scroll))
self.update_icon
self.update_sink_info
self.update_source_info
end
#Called when the window-state is changed to close window instead of minimize.
def on_window_window_state_event(win, win_state)
if win_state.new_window_state == 130
@ui["window"].hide
@ui["window"].deiconify
end
end
#Called when something is removed from PulseAudio. Removes items from the treeviews automatically.
def on_remove(args)
event, ele, ele_id = args[:args][:event].to_sym, args[:args][:element].to_s, args[:args][:element_id].to_i
if ele == "sink"
tv = @tv_sinks
elsif ele == "source"
tv = @tv_sources
else
return nil
end
tv.rows_remove do |data|
if data[:data][:id].to_i == ele_id
true
else
false
end
end
end
#Called when something is added to PulseAudio. Adds new items to the treeviews automatically.
def on_new(args)
event, ele, ele_id = args[:args][:event].to_sym, args[:args][:element].to_s, args[:args][:element_id].to_i
if ele == "sink"
sink = PulseAudio::Sink.by_id(ele_id.to_i)
self.add_sink(sink)
elsif ele == "source"
source = PulseAudio::Source.by_id(ele_id.to_i)
self.add_source(source)
else
return nil
end
end
#Called when something is changed. Updates mute-toggle-values, adjustment-values and more automatically.
def on_change(args)
event, ele, ele_id = args[:args][:event].to_sym, args[:args][:element].to_s, args[:args][:element_id].to_i
if ele == "sink"
self.update_sink_info
elsif ele == "source"
self.update_source_info
else
return nil
end
end
#Updates the statusicons icon based on the current sinks volume.
def update_icon
#Get the current active sink which should be manipulated.
return nil if !@sink
#Evaluate which icon is the closest to the current volume.
vol_perc = @sink.vol_perc
levels = [0, 33, 66, 100]
vol_closest = levels.first
vol_closest_dif = 100
levels.each do |level|
if !vol_closest or (vol_perc < level and diff = (level - vol_perc) and diff < vol_closest_dif) or (vol_perc >= level and diff = (vol_perc - level) and diff < vol_closest_dif)
vol_closest = level
vol_closest_dif = diff
end
end
#Set icon.
icon_filepath = File.realpath("#{File.dirname(__FILE__)}/../../gfx/volume_#{vol_closest}.png")
@sicon.set_from_file(icon_filepath)
end
#Runs the given block and doesnt toggle the mute-functionality while it runs.
def no_mute_toggle
@no_toggle_event_count = 0 if @no_toggle_event_count == nil
@no_toggle_event_count += 1
begin
yield
ensure
@no_toggle_event_count -= 1
end
end
def update_sink_info
self.no_mute_toggle do
if @sink.muted?
puts "Mute is active - set."
@ui["cbSinkMute"].active = true
else
puts "Mute is not active - unset."
@ui["cbSinkMute"].active = false
end
@ui["adjustmentSinkVolume"].value = @sink.vol_perc.to_f
end
end
def update_source_info
if @source.muted?
@ui["cbSourceMute"].active = true
else
@ui["cbSourceMute"].active = false
end
@ui["adjustmentSourceVolume"].value = @source.vol_perc.to_f
end
def update_application_info
return nil if !@input
@ui["adjustmentApplicationVolume"].value = @input.vol_perc.to_f
end
def on_hscaleSinkVolume_change_value
GLib.source_remove(@timeout_sink_volume_change) if @timeout_sink_volume_change
@timeout_sink_volume_change = GLib.timeout_add(GLib::PRIORITY_DEFAULT_IDLE, 50, proc{
@timeout_sink_volume_change = nil
@sink.vol_perc = @ui["adjustmentSinkVolume"].value
}, nil, nil)
end
def on_hscaleSourceVolume_change_value
GLib.source_remove(@timeout_source_volume_change) if @timeout_source_volume_change
@timeout_source_volume_change = GLib.timeout_add(GLib::PRIORITY_DEFAULT_IDLE, 50, proc{
@timeout_source_volume_change = nil
@source.vol_perc = @ui["adjustmentSourceVolume"].value
}, nil, nil)
end
def on_cbSinkMute_toggled
if !@no_toggle_event
puts "Mute toggle."
@sink.mute = @ui["cbSinkMute"].active
end
end
def on_cbSourceMute_toggled
@source.mute = @ui["cbSourceMute"].active
end
def on_sicon_activate(*args)
if !@ui["window"].get_visible
@ui["window"].show
else
@ui["window"].hide
end
end
def on_sicon_scroll(sicon, scroll_e, temp)
direction = scroll_e.direction.to_s
return nil if direction == "smooth"
if direction == "up"
@sink.vol_incr
elsif direction == "down"
@sink.vol_decr
end
self.update_icon
end
def on_sicon_popupmenu(*args)
Gtk.main_quit
end
def add_sink(sink)
append_data = @tv_sinks.add_row(:data => {:id => sink.sink_id, :name => sink.args[:props]["description"]})
return append_data
end
def reload_sinks
@reloading = true
@tv_sinks.model.clear
PulseAudio::Sink.list do |sink|
append_data = self.add_sink(sink)
if sink.default?
@ui["tvSinks"].selection.select_iter(append_data[:iter])
@sink = sink
end
end
@reloading = false
end
def add_source(source)
append_data = @tv_sources.add_row(:data => {:id => source.source_id, :name => source.args[:props]["description"]})
return append_data
end
def reload_sources
@reloading = true
@tv_sources.model.clear
PulseAudio::Source.list do |source|
append_data = self.add_source(source)
if source.default?
@ui["tvSources"].selection.select_iter(append_data[:iter])
@source = source
end
end
@reloading = false
end
def on_tvSinks_cursor_changed(*args)
begin
return nil if @reloading or !@tv_sinks
sel = @tv_sinks.sel
return nil if !sel
sink = nil
PulseAudio::Sink.list do |sink_i|
if sink_i.sink_id.to_i == sel[:data][:id].to_i
sink = sink_i
break
end
end
raise "Could not find sink." if !sink
@sink = sink
sink.default!
self.update_icon
self.update_sink_info
rescue => e
Gtk3assist::Msgbox.error(e)
end
end
def on_tvSources_cursor_changed(*args)
begin
return nil if @reloading or !@tv_sources
sel = @tv_sources.sel
return nil if !sel
@source = PulseAudio::Source.by_id(sel[:data][:id].to_i)
@source.default!
self.update_icon
self.update_source_info
rescue => e
Gtk3assist::Msgbox.error(e)
end
end
def add_input(input)
append_data = @tv_applications.add_row(:data => {:id => input.input_id, :name => input.name})
return append_data
end
def reload_applications
begin
@reloading = true
@tv_applications.model.clear
PulseAudio::Sink::Input.list do |input|
append_data = self.add_input(input)
end
ensure
@reloading = false
end
end
def on_tvApplications_selection_changed(*args)
begin
return nil if @reloading or !@tv_applications
sel = @tv_applications.sel
if !sel
@ui["frameApplicationSettings"].hide
@input = nil
else
@ui["frameApplicationSettings"].show
@input = PulseAudio::Sink::Input.by_id(sel[:data][:id].to_i)
self.update_application_info
end
rescue => e
raise e
end
end
def on_hscaleApplicationVolume_change_value
return nil if !@input
GLib.source_remove(@timeout_input_volume_change) if @timeout_input_volume_change
@timeout_input_volume_change = GLib.timeout_add(GLib::PRIORITY_DEFAULT_IDLE, 50, proc{
@timeout_input_volume_change = nil
@input.vol_perc = @ui["adjustmentApplicationVolume"].value
false
}, nil, nil)
end
def on_window_delete_event(*args)
@ui["window"].hide
return true
end
end