volay 1.2.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '0786e6be35e259e88f2c72bbd57b2b1014ec2e8e0928000c9162a2ca172cb1c0'
4
- data.tar.gz: a4131cbfad89dfc19a8614c9c5a94313bd544a4c06bbdd89e443b0cd99935317
3
+ metadata.gz: 688ccbdc75ceb9062fb138563fa72504509a2afaffbc2b1aae9e8f9f24a3dd32
4
+ data.tar.gz: 2e2c26689b143f3011d04a3d522eb9bbae829254d36618b5e853b9ecc8d64006
5
5
  SHA512:
6
- metadata.gz: 5e5220cba4ce083166566bcddaf825ddc77e12e78774b2fafc30f9bd6b93aa6778c9accaf18f032122dee0d36976c5a867d82e1ec8107b68530aafaf67da8272
7
- data.tar.gz: e6c2f0e812fac1c0589ccc53b83229647e7edf36fa1dd0872a055f7a1dcd43cc41af6bad8f9b883b842eaffb6d5e313ba010ed3bcdeb83d0814a18dbf82e10ba
6
+ metadata.gz: a13b3fb7690cf4bf04bbc210deaf6317d18fbb13743877dc330b345a084aeaae1242865bf72e2cbfe8319525fbbba09eeae4be0d66e1c05609f4f270a4a81dd7
7
+ data.tar.gz: 6cd5dcc83ccd294308644e5b45c9871a02447ec28c13fa14bc60c2fe44403996a58490128155ef4b523fb58998f89e117f87c7874b1bc3b98a71245f05164e7f
@@ -1,4 +1,7 @@
1
- Style/FileName:
1
+ AllCops:
2
+ TargetRubyVersion: 2.3
3
+
4
+ Naming:
2
5
  Enabled: false
3
6
  Metrics/AbcSize:
4
7
  Enabled: false
@@ -18,3 +21,7 @@ Metrics/BlockLength:
18
21
  Style/FrozenStringLiteralComment:
19
22
  Exclude:
20
23
  - lib/volay/cli.rb
24
+ - lib/volay/version.rb
25
+
26
+ Performance/RegexpMatch:
27
+ Enabled: false
@@ -1,7 +1,7 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 2.2.5
4
- - 2.3.1
3
+ - 2.3
4
+ - 2.4
5
5
 
6
6
  env:
7
7
  global:
data/MANIFEST CHANGED
@@ -17,8 +17,8 @@ lib/volay/app.rb
17
17
  lib/volay/cli.rb
18
18
  lib/volay/config.rb
19
19
  lib/volay/exceptions.rb
20
- lib/volay/mixer/alsa.rb
21
20
  lib/volay/mixer/default.rb
21
+ lib/volay/mixer/pulse.rb
22
22
  lib/volay/utils.rb
23
23
  lib/volay/version.rb
24
24
  lib/volay/volay.rb
@@ -28,11 +28,11 @@ lib/volay/widget/volume_control.rb
28
28
  spec/spec_helper.rb
29
29
  spec/volay/app_spec.rb
30
30
  spec/volay/config_spec.rb
31
- spec/volay/mixer/alsa_spec.rb
32
31
  spec/volay/mixer/default_spec.rb
32
+ spec/volay/mixer/pulse_spec.rb
33
33
  spec/volay/utils_spec.rb
34
34
  spec/volay/widget/volume_control_spec.rb
35
35
  task/manifest.rake
36
36
  task/rubocop.rake
37
37
  task/spec.rake
38
- volay.gemspec
38
+ volay.gemspec
data/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  ## Requirements
6
6
 
7
- * Ruby 2.0 or newer
7
+ * Ruby 2.3 or newer
8
8
 
9
9
  ## Installation
10
10
 
@@ -1,5 +1,5 @@
1
1
  <?xml version="1.0" encoding="UTF-8"?>
2
- <!-- Generated with glade 3.18.3 -->
2
+ <!-- Generated with glade 3.20.0 -->
3
3
  <interface>
4
4
  <requires lib="gtk+" version="3.6"/>
5
5
  <object class="GtkIconFactory" id="icon_factory">
@@ -19,4 +19,4 @@ require 'volay/widget/events'
19
19
  require 'volay/widget/system_tray'
20
20
  require 'volay/widget/volume_control'
21
21
  require 'volay/mixer/default'
22
- require 'volay/mixer/alsa'
22
+ require 'volay/mixer/pulse'
@@ -59,9 +59,8 @@ module Volay
59
59
  # Initialize mixer for controlling volume
60
60
  #
61
61
  def self.mixer
62
- raise MixerNotFound unless which('amixer')
63
-
64
- @mixer ||= Volay::Mixer::Alsa.new
62
+ raise MixerNotFound unless which('pacmd')
63
+ @mixer ||= Volay::Mixer::Pulse.new
65
64
  end
66
65
 
67
66
  ##
@@ -0,0 +1,197 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Volay module
4
+ module Volay
5
+ # Mixer module
6
+ module Mixer
7
+ # Pulse class for playing with pacmd command
8
+ class Pulse < Default
9
+ DEFAULT_VALUE = 2
10
+ MAX_VALUE = 65_536
11
+
12
+ attr_reader :default_sink_id
13
+ attr_accessor :cards
14
+
15
+ def initialize
16
+ @cards = {}
17
+ refresh
18
+ end
19
+
20
+ def up(value = DEFAULT_VALUE)
21
+ set_volume(
22
+ volume_percent(@cards[@default_sink_id]['volume']) + value
23
+ )
24
+ end
25
+
26
+ def down(value = DEFAULT_VALUE)
27
+ set_volume(
28
+ volume_percent(@cards[@default_sink_id]['volume']) - value
29
+ )
30
+ end
31
+
32
+ def value=(value)
33
+ set_volume(value.to_i)
34
+ end
35
+
36
+ def toggle
37
+ value = if current_sink['muted'] == 'yes'
38
+ 'no'
39
+ else
40
+ 'yes'
41
+ end
42
+ current_sink['muted'] = value
43
+ command("set-sink-mute #{@cards[@default_sink_id]['sink']} #{value}")
44
+ end
45
+
46
+ def current
47
+ {
48
+ value: current_sink['volume'],
49
+ max_value: MAX_VALUE,
50
+ percent: volume_percent(current_sink['volume']),
51
+ muted: current_sink['muted'] == 'yes'
52
+ }
53
+ end
54
+
55
+ ##
56
+ # Change card
57
+ #
58
+ # @param [Integer] newdefault New default index
59
+ #
60
+ def change_card(newdefault)
61
+ return unless @cards.key?(newdefault)
62
+
63
+ # Change default sink
64
+ command("set-default-sink #{@cards[newdefault]['sink']}")
65
+
66
+ # Now to move all current playing stuff to the new sink....
67
+ dump = command('list-sink-inputs').lines
68
+ dump.each do |line|
69
+ args = line.split
70
+ # We need to find the item index for each playing stream
71
+ if args[0] == 'index:'
72
+ # And now to shift them all to the new sink.
73
+ command("move-sink-input #{args[1]} #{@cards[newdefault]['sink']}")
74
+ end
75
+ end
76
+
77
+ refresh
78
+ end
79
+
80
+ private
81
+
82
+ ##
83
+ # Get volume percent
84
+ #
85
+ # @return Integer
86
+ #
87
+ def volume_percent(value)
88
+ value * 100 / MAX_VALUE
89
+ end
90
+
91
+ ##
92
+ # Refresh cards data
93
+ #
94
+ def refresh
95
+ dump = command('dump').lines
96
+ dump.each do |line|
97
+ args = line.split
98
+
99
+ # We are using the id to actually enumerate all cards.
100
+ if args[1] == 'module-alsa-card'
101
+ number = args[2].sub('device_id="', '').sub('"', '').to_i
102
+ @cards[number] = {}
103
+ @cards[number]['name'] = args[3].sub('name="', '').sub('"', '')
104
+ # we already have something in the array
105
+ elsif @cards.keys.length.positive?
106
+ set_data(args)
107
+ end
108
+ end
109
+
110
+ set_long_names
111
+ end
112
+
113
+ ##
114
+ # Set cards data such as default sink
115
+ # volume and if card is muted
116
+ #
117
+ # @param [Hash] args Parameters
118
+ #
119
+ def set_data(args)
120
+ @cards.each do |id, card|
121
+ next if args[1].nil? || !args[1].include?(card['name'])
122
+ case args[0]
123
+ when 'set-default-sink'
124
+ @default_sink_id = id
125
+ when 'set-sink-volume'
126
+ @cards[id]['volume'] = args[2].hex
127
+ @cards[id]['sink'] = args[1]
128
+ when 'set-sink-mute'
129
+ @cards[id]['muted'] = args[2]
130
+ end
131
+ end
132
+ end
133
+
134
+ ##
135
+ # Get long name from list-cards command
136
+ #
137
+ def set_long_names
138
+ dump = command(
139
+ "list-cards | grep -e device.string -e 'name:' -e 'card_name'"
140
+ )
141
+ .split('name: ')
142
+ .reject { |x| x.strip.empty? }
143
+
144
+ dump.each do |card|
145
+ id = nil
146
+ name = nil
147
+ card.lines.each do |line|
148
+ args = line.strip.split(' = ')
149
+ if args[0] == 'device.string'
150
+ id = args[1].delete('"').to_i
151
+ elsif args[0] == 'alsa.card_name'
152
+ name = args[1].delete('"')
153
+ end
154
+ end
155
+
156
+ @cards[id]['long_name'] = name
157
+ end
158
+ end
159
+
160
+ ##
161
+ # Get current sink
162
+ #
163
+ # @return [Hash]
164
+ #
165
+ def current_sink
166
+ @cards[@default_sink_id]
167
+ end
168
+
169
+ ##
170
+ # Set up volume
171
+ #
172
+ # @param [Integer] volume Volume to set up, must be a percentage
173
+ #
174
+ def set_volume(volume)
175
+ volume = [[0, (volume * MAX_VALUE / 100)].max, MAX_VALUE].min
176
+ @cards[@default_sink_id]['volume'] = volume
177
+ sink = @cards[@default_sink_id]['sink']
178
+ command("set-sink-volume #{sink} #{format('0x%<v>x', v: volume)}")
179
+ end
180
+
181
+ ##
182
+ # Run command
183
+ #
184
+ # @param [String] cmd Command to run
185
+ #
186
+ def command(cmd)
187
+ command = "pacmd #{cmd}"
188
+ result = Mixlib::ShellOut.new(command)
189
+ Volay::Config.logger.debug(command)
190
+ result.run_command
191
+ Volay::Config.logger.error(result.stderr) unless result.stderr.empty?
192
+ Volay::Config.logger.debug(result.stdout) unless result.stdout.empty?
193
+ result.stdout
194
+ end
195
+ end
196
+ end
197
+ end
@@ -1,6 +1,4 @@
1
- # frozen_string_literal: true
2
-
3
1
  # Volay module
4
2
  module Volay
5
- VERSION = '1.2.0'
3
+ VERSION = '2.0.0'.freeze
6
4
  end
@@ -15,9 +15,11 @@ module Volay
15
15
  @app = app
16
16
 
17
17
  methods.each do |name|
18
- next unless name.match?(/^on_/)
18
+ next unless name =~ /^on_/
19
19
  @app.signals_list[name.to_s] = method(name)
20
20
  end
21
+
22
+ init if respond_to?('init')
21
23
  end
22
24
  end
23
25
  end
@@ -10,6 +10,34 @@ module Volay
10
10
  RIGHT_CLICK = 3
11
11
  M_KEYCODE = 47
12
12
 
13
+ ##
14
+ # When popup window menu is draw
15
+ #
16
+ def init
17
+ return unless @app&.mixer&.cards
18
+
19
+ cards_menu = Gtk::MenuItem.new(label: 'Cards')
20
+ menu = Gtk::Menu.new
21
+ last_item = nil
22
+ @app.mixer.cards.each do |id, card|
23
+ radio_menu_item = Gtk::RadioMenuItem.new(nil, card['long_name'])
24
+ radio_menu_item.join_group(last_item) if last_item
25
+ radio_menu_item.name = "card_menu_#{id}"
26
+ radio_menu_item.active = @app.mixer.default_sink_id == id
27
+ last_item = radio_menu_item
28
+ menu.append(radio_menu_item)
29
+
30
+ radio_menu_item.signal_connect('activate') do |widget|
31
+ next unless widget.active?
32
+ @app.mixer.change_card(id)
33
+ @app.utils.update_status_icon
34
+ end
35
+ end
36
+
37
+ cards_menu.submenu = menu
38
+ @app.get_object('popup_menu').prepend(cards_menu)
39
+ end
40
+
13
41
  ##
14
42
  # When left click on the status icon, popup the window menu
15
43
  #
@@ -9,14 +9,14 @@ require 'volay/widget/events'
9
9
  require 'volay/widget/system_tray'
10
10
  require 'volay/widget/volume_control'
11
11
  require 'volay/mixer/default'
12
- require 'volay/mixer/alsa'
12
+ require 'volay/mixer/pulse'
13
13
  require 'volay/exceptions'
14
14
 
15
15
  describe 'Volay::App' do
16
16
  include FakeFS::SpecHelpers
17
17
 
18
18
  context '#events' do
19
- it 'should return alsa' do
19
+ it 'should initiliaze events' do
20
20
  allow_any_instance_of(Volay::App).to receive(:initialize_mixer)
21
21
  allow_any_instance_of(Volay::App).to receive(:initialize_ui)
22
22
  allow_any_instance_of(Volay::Utils).to receive(:update_status_icon)
@@ -30,7 +30,7 @@ describe 'Volay::App' do
30
30
  end
31
31
 
32
32
  context '#ui' do
33
- it 'should return alsa' do
33
+ it 'should initialize events' do
34
34
  allow_any_instance_of(Volay::App).to receive(:initialize_mixer)
35
35
  allow_any_instance_of(Volay::App).to receive(:initialize_events)
36
36
 
@@ -4,7 +4,7 @@ require 'spec_helper'
4
4
  require 'logger'
5
5
  require 'volay/config'
6
6
  require 'volay/mixer/default'
7
- require 'volay/mixer/alsa'
7
+ require 'volay/mixer/pulse'
8
8
  require 'volay/exceptions'
9
9
 
10
10
  describe 'Volay::Config' do
@@ -39,9 +39,9 @@ describe 'Volay::Config' do
39
39
  expect(Volay::Config.which('ruby')).to eq('/usr/bin/ruby')
40
40
  end
41
41
 
42
- it "shouldn't find amixer executable" do
42
+ it "shouldn't find pacmd executable" do
43
43
  config
44
- expect(Volay::Config.which('amixer')).to be_nil
44
+ expect(Volay::Config.which('pacmd')).to be_nil
45
45
  end
46
46
  end
47
47
 
@@ -53,14 +53,16 @@ describe 'Volay::Config' do
53
53
  end
54
54
 
55
55
  it 'should not return pulseaudio' do
56
- app_mixer('pulseaudio-ctl')
57
56
  expect { Volay::Config.mixer }
58
57
  .to raise_error(Volay::MixerNotFound)
59
58
  end
60
59
 
61
- it 'should return alsa' do
62
- app_mixer('amixer')
63
- expect(Volay::Config.mixer).to be_a(Volay::Mixer::Alsa)
60
+ it 'should return a mixer' do
61
+ Volay::Config.logger.level = :info
62
+ allow_any_instance_of(Mixlib::ShellOut).to receive(:new).twice
63
+ allow_any_instance_of(Mixlib::ShellOut).to receive(:run_command)
64
+ app_mixer('pacmd')
65
+ expect(Volay::Config.mixer).to be_a(Volay::Mixer::Pulse)
64
66
  end
65
67
  end
66
68
  end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'mixlib/shellout'
5
+ require 'volay/mixer/default'
6
+
7
+ describe 'Volay::Mixer::Pulse' do
8
+ let(:mixer) do
9
+ Volay::Mixer::Pulse.new
10
+ end
11
+
12
+ def stub_shellout(cmd, stdout = '', stderr = '')
13
+ shellout = double(
14
+ run_command: nil,
15
+ error!: nil,
16
+ stdout: stdout,
17
+ stderr: stderr
18
+ )
19
+
20
+ expect(Mixlib::ShellOut).to receive(:new)
21
+ .with("pacmd #{cmd}")
22
+ .and_return(shellout)
23
+ end
24
+
25
+ before(:each) do
26
+ stub_shellout(
27
+ 'dump',
28
+ File.read(
29
+ File.expand_path('../stubs/pulse_dump.stub', __FILE__)
30
+ )
31
+ )
32
+ stub_shellout(
33
+ "list-cards | grep -e device.string -e 'name:' -e 'card_name'",
34
+ File.read(
35
+ File.expand_path('../stubs/pulse_list-cards.stub', __FILE__)
36
+ )
37
+ )
38
+ end
39
+
40
+ it 'should raise error when calling up' do
41
+ stub_shellout(
42
+ 'set-sink-volume alsa_output.pci-0000_01_00.1.hdmi-stereo 0x10000',
43
+ 'Yes'
44
+ )
45
+ expect(mixer.up).to eq('Yes')
46
+ end
47
+
48
+ it 'should raise error when calling down' do
49
+ stub_shellout(
50
+ 'set-sink-volume alsa_output.pci-0000_01_00.1.hdmi-stereo 0xfae1',
51
+ 'Yes'
52
+ )
53
+ expect(mixer.down).to eq('Yes')
54
+ end
55
+
56
+ it 'should raise error when calling value=' do
57
+ stub_shellout(
58
+ 'set-sink-volume alsa_output.pci-0000_01_00.1.hdmi-stereo 0x8000',
59
+ 'Yes'
60
+ )
61
+ expect(mixer.value = '50').to eq('50')
62
+ end
63
+
64
+ it 'should raise error when calling toggle' do
65
+ stub_shellout(
66
+ 'set-sink-mute alsa_output.pci-0000_01_00.1.hdmi-stereo yes',
67
+ 'Yes'
68
+ )
69
+ expect(mixer.toggle).to eq('Yes')
70
+ end
71
+
72
+ it 'should raise error when calling current' do
73
+ expect(mixer.current).to eq(value: 65_536,
74
+ max_value: 65_536,
75
+ percent: 100,
76
+ muted: false)
77
+ end
78
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: volay
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pierre Rambaud
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-01-17 00:00:00.000000000 Z
11
+ date: 2018-01-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: glib2
@@ -190,8 +190,8 @@ files:
190
190
  - lib/volay/cli.rb
191
191
  - lib/volay/config.rb
192
192
  - lib/volay/exceptions.rb
193
- - lib/volay/mixer/alsa.rb
194
193
  - lib/volay/mixer/default.rb
194
+ - lib/volay/mixer/pulse.rb
195
195
  - lib/volay/utils.rb
196
196
  - lib/volay/version.rb
197
197
  - lib/volay/volay.rb
@@ -201,8 +201,8 @@ files:
201
201
  - spec/spec_helper.rb
202
202
  - spec/volay/app_spec.rb
203
203
  - spec/volay/config_spec.rb
204
- - spec/volay/mixer/alsa_spec.rb
205
204
  - spec/volay/mixer/default_spec.rb
205
+ - spec/volay/mixer/pulse_spec.rb
206
206
  - spec/volay/utils_spec.rb
207
207
  - spec/volay/widget/volume_control_spec.rb
208
208
  - task/manifest.rake
@@ -1,50 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Volay module
4
- module Volay
5
- # Mixer module
6
- module Mixer
7
- # Alsa class for playing with amixer
8
- class Alsa < Default
9
- DEFAULT_VALUE = 2
10
-
11
- def up(value = DEFAULT_VALUE)
12
- command("-q set Master #{value}%+")
13
- end
14
-
15
- def down(value = DEFAULT_VALUE)
16
- command("-q set Master #{value}%-")
17
- end
18
-
19
- def value=(value)
20
- command("-q set Master #{value}%")
21
- end
22
-
23
- def toggle
24
- command('-q set Master toggle')
25
- end
26
-
27
- def current
28
- result = command('get Master')
29
- max_value = result.match(/ 0 - (\d+)/)[1].to_i
30
- current_percent = result.match(/\[([0-9]+)%\]\s+\[o(?:n|ff)\]/)[1].to_i
31
- current_value = ((current_percent * max_value) / 100).to_i
32
- current_state = result.match(/\[([a-z]+)\]/)[1].to_s
33
-
34
- { value: current_value,
35
- max_value: max_value,
36
- percent: current_percent,
37
- muted: current_state != 'on' }
38
- end
39
-
40
- private
41
-
42
- def command(cmd)
43
- result = Mixlib::ShellOut.new("amixer #{cmd}")
44
- result.run_command
45
- Volay::Config.logger.error(result.stderr) unless result.stderr.empty?
46
- result.stdout
47
- end
48
- end
49
- end
50
- end
@@ -1,58 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'spec_helper'
4
- require 'mixlib/shellout'
5
- require 'volay/mixer/default'
6
-
7
- describe 'Volay::Mixer::Alsa' do
8
- let(:mixer) do
9
- Volay::Mixer::Alsa.new
10
- end
11
-
12
- def stub_shellout(cmd, stdout = '', stderr = '')
13
- allow_any_instance_of(Mixlib::ShellOut).to receive(:run_command)
14
- allow_any_instance_of(Mixlib::ShellOut).to receive(:new)
15
- .with("amixer #{cmd}")
16
- allow_any_instance_of(Mixlib::ShellOut).to receive(:stdout)
17
- .and_return(stdout)
18
- allow_any_instance_of(Mixlib::ShellOut).to receive(:stderr)
19
- .and_return(stderr)
20
- end
21
-
22
- it 'should raise error when calling up' do
23
- stub_shellout('-q set Master 2%+', 'Yes')
24
- expect(mixer.up).to eq('Yes')
25
- end
26
-
27
- it 'should raise error when calling down' do
28
- stub_shellout('-q set Master 2%-', 'Yes')
29
- expect(mixer.down).to eq('Yes')
30
- end
31
-
32
- it 'should raise error when calling value=' do
33
- stub_shellout('-q set Master 50%', 'Yes')
34
- expect(mixer.value = '50').to eq('50')
35
- end
36
-
37
- it 'should raise error when calling toggle' do
38
- stub_shellout('-q set Master toggle', 'Yes')
39
- expect(mixer.toggle).to eq('Yes')
40
- end
41
-
42
- it 'should raise error when calling current' do
43
- stdout = <<-OUTPUT
44
- Simple mixer control 'Master',0
45
- Capabilities: pvolume pswitch pswitch-joined
46
- Playback channels: Front Left - Front Right
47
- Limits: Playback 0 - 65536
48
- Mono:
49
- Front Left: Playback 13108 [20%] [off]
50
- Front Right: Playback 13108 [20%] [off]
51
- OUTPUT
52
- stub_shellout('get Master', stdout)
53
- expect(mixer.current).to eq(value: 13_107,
54
- max_value: 65_536,
55
- percent: 20,
56
- muted: true)
57
- end
58
- end