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 CHANGED
@@ -1 +1 @@
1
- 0.0.3
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
@@ -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
- end
6
-
7
- dir = "#{File.dirname(__FILE__)}/../include"
8
- files = []
9
- Dir.foreach(dir) do |file|
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.3"
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-07}
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",
@@ -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.3
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-07 00:00:00 +02:00
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: -1013676805268999016
129
+ hash: -248938999908362566
129
130
  segments:
130
131
  - 0
131
132
  version: "0"