pulseaudio 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/VERSION +1 -1
- data/gui/choose_active_sink/choose_active_sink.rb +3 -0
- data/include/pulseaudio_events.rb +110 -0
- data/include/pulseaudio_sink.rb +37 -0
- data/include/pulseaudio_sink_input.rb +43 -1
- data/include/pulseaudio_source.rb +35 -0
- data/include/pulseaudio_source_output.rb +25 -0
- data/lib/pulseaudio.rb +5 -10
- data/pulseaudio.gemspec +3 -2
- data/spec/pulseaudio_spec.rb +13 -0
- metadata +4 -3
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.4
|
@@ -17,6 +17,9 @@ class PulseAudio::Gui::Choose_active_sink
|
|
17
17
|
@ui["tvSources"].columns[0].visible = false
|
18
18
|
self.reload_sources
|
19
19
|
|
20
|
+
PulseAudio::Sink::Input.auto_redirect_new_inputs_to_default_sink
|
21
|
+
PulseAudio::Source::Output.auto_redirect_new_outputs_to_default_source
|
22
|
+
|
20
23
|
@ui["window"].show_all
|
21
24
|
end
|
22
25
|
|
@@ -0,0 +1,110 @@
|
|
1
|
+
#This class listens for PulseAudio events, which you are able to connect to.
|
2
|
+
#===Examples
|
3
|
+
# events = PulseAudio::Events.instance
|
4
|
+
# events.connect(:event => :new, :element => "sink-input") do |data|
|
5
|
+
# print "New event: #{data}\n"
|
6
|
+
# end
|
7
|
+
class PulseAudio::Events
|
8
|
+
@@events_instance = nil
|
9
|
+
|
10
|
+
#Returns the default instance of events - only one can exist!
|
11
|
+
def self.instance
|
12
|
+
@@events_instance = PulseAudio::Events.new if !@@events_instance
|
13
|
+
return @@events_instance
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(args = {})
|
17
|
+
raise "An instance already exists." if @@events_instance
|
18
|
+
|
19
|
+
@args = args
|
20
|
+
@connects = {}
|
21
|
+
@connects_count = 0
|
22
|
+
@connects_mutex = Mutex.new
|
23
|
+
require "open3"
|
24
|
+
|
25
|
+
@thread = Thread.new do
|
26
|
+
begin
|
27
|
+
#Has to be done via PTY, since "pactl subscribe" requires a tty to write to...
|
28
|
+
require "pty"
|
29
|
+
PTY.spawn("pactl subscribe") do |stdout, stdin, pid|
|
30
|
+
@pid = pid
|
31
|
+
|
32
|
+
#Make sure destroy is called, when the process stops - else it might freeze.
|
33
|
+
Kernel.at_exit do
|
34
|
+
self.destroy
|
35
|
+
end
|
36
|
+
|
37
|
+
stdout.sync = true
|
38
|
+
stdout.each_line do |line|
|
39
|
+
if match = line.match(/^Event '(.+)' on (.+) #(\d+)/)
|
40
|
+
event = match[1].to_sym
|
41
|
+
element = match[2]
|
42
|
+
element_id = match[3].to_i
|
43
|
+
|
44
|
+
self.call(:event => event, :element => element, :element_id => element_id)
|
45
|
+
else
|
46
|
+
$stderr.puts "PulseAudio::Events thread could not unstand: '#{line}'."
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
rescue => e
|
51
|
+
if !@args
|
52
|
+
#ignore - this means the process has been killed.
|
53
|
+
else
|
54
|
+
$stderr.puts "An error occurred in the PulseAudio::Events thread!"
|
55
|
+
$stderr.puts e.inspect
|
56
|
+
$stderr.puts e.backtrace
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def destroy
|
63
|
+
@args = nil
|
64
|
+
@connects = nil
|
65
|
+
Process.kill("HUP", @pid) if @pid
|
66
|
+
@@events_instance = nil
|
67
|
+
end
|
68
|
+
|
69
|
+
def connect(args, &block)
|
70
|
+
raise "'args' wasnt a hash." if !args.is_a?(Hash)
|
71
|
+
|
72
|
+
@connects_mutex.synchronize do
|
73
|
+
id = @connects_count
|
74
|
+
@connects_count += 1
|
75
|
+
@connects[id] = {:args => args, :block => block}
|
76
|
+
|
77
|
+
return {:connect_id => id}
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def unconnect(args)
|
82
|
+
@connects_mutex.synchronize do
|
83
|
+
raise "No connection by that ID: '#{args[:connect_id]}'." if !@connects.key?(args[:connect_id])
|
84
|
+
@connects.delete(args[:connect_id])
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def call(args)
|
89
|
+
@connects_mutex.synchronize do
|
90
|
+
@connects.each do |id, connect_data|
|
91
|
+
call = true
|
92
|
+
|
93
|
+
connect_data[:args].each do |key, val|
|
94
|
+
if !args.key?(key) or args[key] != val
|
95
|
+
call = false
|
96
|
+
break
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
if call
|
101
|
+
connect_data[:block].call(:args => args)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def join
|
108
|
+
@thread.join if @thread
|
109
|
+
end
|
110
|
+
end
|
data/include/pulseaudio_sink.rb
CHANGED
@@ -10,6 +10,15 @@ class PulseAudio::Sink
|
|
10
10
|
|
11
11
|
@@sinks = Wref_map.new
|
12
12
|
|
13
|
+
#Used to look up IDs from names (like when getting default sink).
|
14
|
+
@@sink_name_to_id_ref = {}
|
15
|
+
|
16
|
+
#Autoloader for subclasses.
|
17
|
+
def self.const_missing(name)
|
18
|
+
require "#{File.realpath(File.dirname(__FILE__))}/pulseaudio_sink_#{name.to_s.downcase}.rb"
|
19
|
+
return PulseAudio::Sink.const_get(name)
|
20
|
+
end
|
21
|
+
|
13
22
|
#Returns a list of sinks on the system. It also reloads information for all sinks if the information has been changed.
|
14
23
|
#===Examples
|
15
24
|
# sinks = PulseAudio::Sink.list
|
@@ -33,6 +42,7 @@ class PulseAudio::Sink
|
|
33
42
|
if !sink
|
34
43
|
sink = PulseAudio::Sink.new
|
35
44
|
@@sinks[sink_id] = sink
|
45
|
+
@@sink_name_to_id_ref[props["name"]] = sink_id
|
36
46
|
end
|
37
47
|
|
38
48
|
sink.update(args)
|
@@ -51,6 +61,33 @@ class PulseAudio::Sink
|
|
51
61
|
end
|
52
62
|
end
|
53
63
|
|
64
|
+
#Returns the default sink by doing a smart lookup and using the 'name-to-id-ref'-cache.
|
65
|
+
def self.by_default
|
66
|
+
def_str = %x[pacmd info | grep "Default sink name"]
|
67
|
+
raise "Could not match default sink." if !match = def_str.match(/^Default sink name: (.+?)\s*$/)
|
68
|
+
sink_id = @@sink_name_to_id_ref[match[1]]
|
69
|
+
raise "Could not figure out sink-ID." if !sink_id
|
70
|
+
return PulseAudio::Sink.by_id(sink_id.to_i)
|
71
|
+
end
|
72
|
+
|
73
|
+
#Returns a sink by its sink-ID.
|
74
|
+
#===Examples
|
75
|
+
# sink = PulseAudio::Sink.by_id(3)
|
76
|
+
def self.by_id(id)
|
77
|
+
#Return it from the weak-reference-map, if it already exists there.
|
78
|
+
if sink = @@sinks.get!(id)
|
79
|
+
return sink
|
80
|
+
end
|
81
|
+
|
82
|
+
#Read the sinks one-by-one and return it when found.
|
83
|
+
PulseAudio::Sink.list do |sink|
|
84
|
+
return sink if sink.sink_id == id
|
85
|
+
end
|
86
|
+
|
87
|
+
#Sink could not be found by the given ID - raise error.
|
88
|
+
raise NameError, "No sink by that ID: '#{id}' (#{id.class.name})."
|
89
|
+
end
|
90
|
+
|
54
91
|
#Updates the data on the object. This should not be called.
|
55
92
|
def update(args)
|
56
93
|
@args = args
|
@@ -2,10 +2,34 @@
|
|
2
2
|
class PulseAudio::Sink::Input
|
3
3
|
@@inputs = Wref_map.new
|
4
4
|
|
5
|
+
#Starts automatically redirect new opened inputs to the default sink.
|
6
|
+
#===Examples
|
7
|
+
# PulseAudio::Sink::Input.auto_redirect_new_inputs_to_default_sink
|
8
|
+
def self.auto_redirect_new_inputs_to_default_sink
|
9
|
+
raise "Already redirecting!" if @auto_redirect_connect_id
|
10
|
+
|
11
|
+
@auto_redirect_connect_id = PulseAudio::Events.instance.connect(:event => :new, :element => "sink-input") do |data|
|
12
|
+
begin
|
13
|
+
sink_input = PulseAudio::Sink::Input.by_id(data[:args][:element_id])
|
14
|
+
sink_input.sink = PulseAudio::Sink.by_default
|
15
|
+
rescue NameError
|
16
|
+
#sometimes sinks are killed instantly and we cant find them before that happens.
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
#Stops automatically redirecting new opened inputs to the default sink.
|
22
|
+
#===Examples
|
23
|
+
# PulseAudio::Sink::Input.stop_auto_redirect_new_inputs_to_default_sink
|
24
|
+
def self.stop_auto_redirect_new_inputs_to_default_sink
|
25
|
+
raise "Not redirecting at the moment." if !@auto_redirect_connect_id
|
26
|
+
PulseAudio::Events.instance.unconnect(@auto_redirect_connect_id)
|
27
|
+
@auto_redirect_connect_id = nil
|
28
|
+
end
|
29
|
+
|
5
30
|
#Returns a list of sink-inputs.
|
6
31
|
def self.list
|
7
32
|
list = %x[pacmd list-sink-inputs]
|
8
|
-
|
9
33
|
inputs = [] unless block_given?
|
10
34
|
|
11
35
|
list.scan(/index: (\d+)/) do |match|
|
@@ -34,6 +58,24 @@ class PulseAudio::Sink::Input
|
|
34
58
|
end
|
35
59
|
end
|
36
60
|
|
61
|
+
#Returns a sink-input by its input-ID.
|
62
|
+
#===Examples
|
63
|
+
# sink_input = PulseAudio::Sink::Input.by_id(53)
|
64
|
+
def self.by_id(id)
|
65
|
+
#Return it from the weak-reference-map, if it already exists there.
|
66
|
+
if input = @@inputs.get!(id)
|
67
|
+
return input
|
68
|
+
end
|
69
|
+
|
70
|
+
#Read the inputs one-by-one and return it when found.
|
71
|
+
PulseAudio::Sink::Input.list do |input|
|
72
|
+
return input if input.input_id == id
|
73
|
+
end
|
74
|
+
|
75
|
+
#Input could not be found by the given ID - raise error.
|
76
|
+
raise NameError, "No sink-input by that ID: '#{id}' (#{id.class.name})."
|
77
|
+
end
|
78
|
+
|
37
79
|
#Should not be called manually but through 'list'.
|
38
80
|
def update(args)
|
39
81
|
@args = args
|
@@ -3,6 +3,13 @@ class PulseAudio::Source
|
|
3
3
|
attr_reader :args
|
4
4
|
|
5
5
|
@@sources = Wref_map.new
|
6
|
+
@@sources_name_to_id_ref = {}
|
7
|
+
|
8
|
+
#Autoloader for subclasses.
|
9
|
+
def self.const_missing(name)
|
10
|
+
require "#{File.realpath(File.dirname(__FILE__))}/pulseaudio_source_#{name.to_s.downcase}.rb"
|
11
|
+
return PulseAudio::Source.const_get(name)
|
12
|
+
end
|
6
13
|
|
7
14
|
def self.list
|
8
15
|
list = %x[pactl list sources]
|
@@ -21,6 +28,7 @@ class PulseAudio::Source
|
|
21
28
|
if !source
|
22
29
|
source = PulseAudio::Source.new
|
23
30
|
@@sources[source_id] = source
|
31
|
+
@@sources_name_to_id_ref[props["name"]] = source_id
|
24
32
|
end
|
25
33
|
|
26
34
|
source.update(args)
|
@@ -39,6 +47,33 @@ class PulseAudio::Source
|
|
39
47
|
end
|
40
48
|
end
|
41
49
|
|
50
|
+
#Returns the default source by doing a smart lookup and using the 'name-to-id-ref'-cache.
|
51
|
+
def self.by_default
|
52
|
+
def_str = %x[pacmd info | grep "Default source name"]
|
53
|
+
raise "Could not match default source." if !match = def_str.match(/^Default source name: (.+?)\s*$/)
|
54
|
+
source_id = @@sources_name_to_id_ref[match[1]]
|
55
|
+
raise "Could not figure out source-ID." if !source_id
|
56
|
+
return PulseAudio::Source.by_id(source_id.to_i)
|
57
|
+
end
|
58
|
+
|
59
|
+
#Returns a source by its source-ID.
|
60
|
+
#===Examples
|
61
|
+
# source = PulseAudio::Source.by_id(3)
|
62
|
+
def self.by_id(id)
|
63
|
+
#Return it from the weak-reference-map, if it already exists there.
|
64
|
+
if source = @@sources.get!(id)
|
65
|
+
return source
|
66
|
+
end
|
67
|
+
|
68
|
+
#Read the sources one-by-one and return it when found.
|
69
|
+
PulseAudio::Source.list do |source|
|
70
|
+
return source if source.source_id == id
|
71
|
+
end
|
72
|
+
|
73
|
+
#Source could not be found by the given ID - raise error.
|
74
|
+
raise NameError, "No source by that ID: '#{id}' (#{id.class.name})."
|
75
|
+
end
|
76
|
+
|
42
77
|
#Updates the data on the object. This should not be called.
|
43
78
|
def update(args)
|
44
79
|
@args = args
|
@@ -2,6 +2,31 @@
|
|
2
2
|
class PulseAudio::Source::Output
|
3
3
|
@@outputs = Wref_map.new
|
4
4
|
|
5
|
+
#Starts automatically redirect new opened outputs to the default source.
|
6
|
+
#===Examples
|
7
|
+
# PulseAudio::Source::Output.auto_redirect_new_outputs_to_default_source
|
8
|
+
def self.auto_redirect_new_outputs_to_default_source
|
9
|
+
raise "Already redirecting!" if @auto_redirect_connect_id
|
10
|
+
|
11
|
+
@auto_redirect_connect_id = PulseAudio::Events.instance.connect(:event => :new, :element => "source-output") do |data|
|
12
|
+
begin
|
13
|
+
source_output = PulseAudio::Source::Output.by_id(data[:args][:element_id])
|
14
|
+
source_output.source = PulseAudio::Source.by_default
|
15
|
+
rescue NameError
|
16
|
+
#sometimes sources are killed instantly and we cant find them before that happens.
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
#Stops automatically redirecting new opened outputs to the default source.
|
22
|
+
#===Examples
|
23
|
+
# PulseAudio::Source::Output.stop_auto_redirect_new_outputs_to_default_source
|
24
|
+
def self.stop_auto_redirect_new_outputs_to_default_source
|
25
|
+
raise "Not redirecting at the moment." if !@auto_redirect_connect_id
|
26
|
+
PulseAudio::Events.instance.unconnect(@auto_redirect_connect_id)
|
27
|
+
@auto_redirect_connect_id = nil
|
28
|
+
end
|
29
|
+
|
5
30
|
#Returns a list of source-outputs.
|
6
31
|
def self.list
|
7
32
|
list = %x[pacmd list-source-outputs]
|
data/lib/pulseaudio.rb
CHANGED
@@ -2,14 +2,9 @@ require "wref"
|
|
2
2
|
|
3
3
|
#A framework for controlling various elements of PulseAudio in Ruby.
|
4
4
|
class PulseAudio
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
files << "#{dir}/#{file}" if file.match(/\.rb$/)
|
11
|
-
end
|
12
|
-
|
13
|
-
files.sort.each do |file|
|
14
|
-
require file
|
5
|
+
#Autoloader for subclasses.
|
6
|
+
def self.const_missing(name)
|
7
|
+
require "#{File.realpath("#{File.dirname(__FILE__)}/../include")}/pulseaudio_#{name.to_s.downcase}.rb"
|
8
|
+
return PulseAudio.const_get(name)
|
9
|
+
end
|
15
10
|
end
|
data/pulseaudio.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{pulseaudio}
|
8
|
-
s.version = "0.0.
|
8
|
+
s.version = "0.0.4"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Kasper Johansen"]
|
12
|
-
s.date = %q{2012-06-
|
12
|
+
s.date = %q{2012-06-27}
|
13
13
|
s.default_executable = %q{pulseaudio_volume.rb}
|
14
14
|
s.description = %q{Ruby-library for controlling PulseAudio via 'pactl'.}
|
15
15
|
s.email = %q{k@spernj.org}
|
@@ -30,6 +30,7 @@ Gem::Specification.new do |s|
|
|
30
30
|
"bin/pulseaudio_volume.rb",
|
31
31
|
"gui/choose_active_sink/choose_active_sink.glade",
|
32
32
|
"gui/choose_active_sink/choose_active_sink.rb",
|
33
|
+
"include/pulseaudio_events.rb",
|
33
34
|
"include/pulseaudio_gui.rb",
|
34
35
|
"include/pulseaudio_sink.rb",
|
35
36
|
"include/pulseaudio_sink_input.rb",
|
data/spec/pulseaudio_spec.rb
CHANGED
@@ -9,4 +9,17 @@ describe "Pulseaudio" do
|
|
9
9
|
sink.mute_toggle
|
10
10
|
end
|
11
11
|
end
|
12
|
+
|
13
|
+
it "should be able to listen for events and redirect all new inputs to the default sink" do
|
14
|
+
def_sink = nil
|
15
|
+
PulseAudio::Sink.list do |sink|
|
16
|
+
if sink.default?
|
17
|
+
def_sink = sink
|
18
|
+
break
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
PulseAudio::Sink::Input.auto_redirect_new_inputs_to_default_sink
|
23
|
+
PulseAudio::Events.instance.join
|
24
|
+
end
|
12
25
|
end
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: pulseaudio
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.0.
|
5
|
+
version: 0.0.4
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Kasper Johansen
|
@@ -10,7 +10,7 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2012-06-
|
13
|
+
date: 2012-06-27 00:00:00 +02:00
|
14
14
|
default_executable: pulseaudio_volume.rb
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
@@ -100,6 +100,7 @@ files:
|
|
100
100
|
- bin/pulseaudio_volume.rb
|
101
101
|
- gui/choose_active_sink/choose_active_sink.glade
|
102
102
|
- gui/choose_active_sink/choose_active_sink.rb
|
103
|
+
- include/pulseaudio_events.rb
|
103
104
|
- include/pulseaudio_gui.rb
|
104
105
|
- include/pulseaudio_sink.rb
|
105
106
|
- include/pulseaudio_sink_input.rb
|
@@ -125,7 +126,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
125
126
|
requirements:
|
126
127
|
- - ">="
|
127
128
|
- !ruby/object:Gem::Version
|
128
|
-
hash: -
|
129
|
+
hash: -248938999908362566
|
129
130
|
segments:
|
130
131
|
- 0
|
131
132
|
version: "0"
|