pulseaudio 0.0.3 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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"
|