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 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"