volay 1.2.0 → 2.0.0

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