volay 0.0.1
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 +7 -0
- data/.gitignore +7 -0
- data/.rubocop.yml +10 -0
- data/.travis.yml +11 -0
- data/Gemfile +4 -0
- data/LICENSE +674 -0
- data/MANIFEST +38 -0
- data/README.md +52 -0
- data/Rakefile +9 -0
- data/bin/volay +34 -0
- data/glade/icons/volume-high.png +0 -0
- data/glade/icons/volume-low.png +0 -0
- data/glade/icons/volume-medium.png +0 -0
- data/glade/icons/volume-muted.png +0 -0
- data/glade/volay.glade +99 -0
- data/lib/volay/app.rb +62 -0
- data/lib/volay/cli.rb +44 -0
- data/lib/volay/config.rb +91 -0
- data/lib/volay/exceptions.rb +6 -0
- data/lib/volay/mixer/alsa.rb +48 -0
- data/lib/volay/mixer/default.rb +72 -0
- data/lib/volay/utils.rb +40 -0
- data/lib/volay/version.rb +4 -0
- data/lib/volay/volay.rb +11 -0
- data/lib/volay/widget/events.rb +22 -0
- data/lib/volay/widget/system_tray.rb +91 -0
- data/lib/volay/widget/volume_control.rb +37 -0
- data/lib/volay.rb +20 -0
- data/spec/spec_helper.rb +7 -0
- data/spec/volay/app_spec.rb +56 -0
- data/spec/volay/config_spec.rb +64 -0
- data/spec/volay/mixer/alsa_spec.rb +56 -0
- data/spec/volay/mixer/default_spec.rb +44 -0
- data/spec/volay/utils_spec.rb +52 -0
- data/spec/volay/widget/volume_control_spec.rb +27 -0
- data/task/manifest.rake +9 -0
- data/task/rubocop.rake +5 -0
- data/task/spec.rake +6 -0
- data/volay.gemspec +27 -0
- metadata +208 -0
@@ -0,0 +1,91 @@
|
|
1
|
+
# Volay module
|
2
|
+
module Volay
|
3
|
+
# Widgets components
|
4
|
+
module Widget
|
5
|
+
# Events class
|
6
|
+
class SystemTray < Events
|
7
|
+
LEFT_CLICK = 1
|
8
|
+
RIGHT_CLICK = 3
|
9
|
+
|
10
|
+
##
|
11
|
+
# When left click on the status icon, popup the window menu
|
12
|
+
#
|
13
|
+
# @param [Gtk::Widget] widget Widget
|
14
|
+
# @param [Gtk::Event] event Event
|
15
|
+
#
|
16
|
+
def on_status_icon_button_press_event(_widget, event)
|
17
|
+
return unless event.is_a?(Gdk::EventButton) &&
|
18
|
+
event.button == LEFT_CLICK
|
19
|
+
window = @app.get_object('system_tray_window')
|
20
|
+
posx, posy = get_position(window)
|
21
|
+
window.move(posx, posy)
|
22
|
+
@app.get_object('system_tray_window').show_all
|
23
|
+
end
|
24
|
+
|
25
|
+
##
|
26
|
+
# When click outside the window
|
27
|
+
#
|
28
|
+
def on_system_tray_window_focus_out_event
|
29
|
+
@app.get_object('system_tray_window').hide
|
30
|
+
end
|
31
|
+
|
32
|
+
##
|
33
|
+
# When right click on the status icon
|
34
|
+
#
|
35
|
+
# @param [Gtk::Widget] widget Widget
|
36
|
+
# @param [Gtk::Event] event Event
|
37
|
+
# @param [Integer] time Time
|
38
|
+
def on_status_icon_popup_menu(_widget, _event, time)
|
39
|
+
popup_menu = @app.get_object('popup_menu')
|
40
|
+
popup_menu.show_all
|
41
|
+
popup_menu.popup(nil, nil, 0, time)
|
42
|
+
end
|
43
|
+
|
44
|
+
##
|
45
|
+
# When quit button is clicked
|
46
|
+
#
|
47
|
+
def on_popup_menu_quit_activate
|
48
|
+
Gtk.main_quit
|
49
|
+
end
|
50
|
+
|
51
|
+
##
|
52
|
+
# Retrieve the good position to be above
|
53
|
+
# the status icon
|
54
|
+
#
|
55
|
+
# @param [Gtk::Window] window Window
|
56
|
+
#
|
57
|
+
def get_position(window)
|
58
|
+
screen, rectangle, orientation = @app.get_object('status_icon').geometry
|
59
|
+
window.set_screen(screen)
|
60
|
+
monitor_num = screen.get_monitor_at_point(rectangle.x, rectangle.y)
|
61
|
+
monitor = screen.get_monitor_geometry(monitor_num)
|
62
|
+
window_width, window_height = window.size
|
63
|
+
|
64
|
+
if orientation == Gtk::Orientation::VERTICAL
|
65
|
+
if monitor.width - rectangle.x == rectangle.width
|
66
|
+
# right panel
|
67
|
+
posx = monitor.x + monitor.width - window_width - rectangle.width
|
68
|
+
else
|
69
|
+
# left panel
|
70
|
+
posx = rectangle.x + rectangle.width
|
71
|
+
posy = rectangle.y
|
72
|
+
end
|
73
|
+
else
|
74
|
+
if (rectangle.y + rectangle.height + window_height <=
|
75
|
+
monitor.y + monitor.height)
|
76
|
+
posy = rectangle.y + rectangle.height
|
77
|
+
else
|
78
|
+
posy = rectangle.y - window_height
|
79
|
+
if (rectangle.x + window_width <= monitor.x + monitor.width)
|
80
|
+
posx = rectangle.x
|
81
|
+
else
|
82
|
+
posx = monitor.x + monitor.width - window_width
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
[posx, posy]
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# Volay module
|
2
|
+
module Volay
|
3
|
+
# Widgets components
|
4
|
+
module Widget
|
5
|
+
# Events class
|
6
|
+
class VolumeControl < Events
|
7
|
+
##
|
8
|
+
# When system tray window is showed
|
9
|
+
#
|
10
|
+
def on_system_tray_window_show
|
11
|
+
@app.get_object('volume_adjustement')
|
12
|
+
.value = @app.mixer.percent
|
13
|
+
@app.utils.update_status_icon
|
14
|
+
end
|
15
|
+
|
16
|
+
##
|
17
|
+
# When slider have its adjustement value changed
|
18
|
+
#
|
19
|
+
def on_volume_adjustement_value_changed(widget)
|
20
|
+
Thread.new do
|
21
|
+
@app.mixer.value = widget.value
|
22
|
+
@app.utils.update_status_icon
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
##
|
27
|
+
# When mute toggle image is clicked
|
28
|
+
#
|
29
|
+
def on_toggle_mute_toggled
|
30
|
+
Thread.new do
|
31
|
+
@app.mixer.toggle
|
32
|
+
@app.utils.update_status_icon
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/lib/volay.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'gtk3'
|
2
|
+
require 'logger'
|
3
|
+
require 'mixlib/cli'
|
4
|
+
require 'mixlib/shellout'
|
5
|
+
|
6
|
+
unless $LOAD_PATH.include?(File.expand_path('../', __FILE__))
|
7
|
+
$LOAD_PATH.unshift(File.expand_path('../', __FILE__))
|
8
|
+
end
|
9
|
+
|
10
|
+
require 'volay/version'
|
11
|
+
require 'volay/config'
|
12
|
+
require 'volay/exceptions'
|
13
|
+
require 'volay/app'
|
14
|
+
require 'volay/cli'
|
15
|
+
require 'volay/utils'
|
16
|
+
require 'volay/widget/events'
|
17
|
+
require 'volay/widget/system_tray'
|
18
|
+
require 'volay/widget/volume_control'
|
19
|
+
require 'volay/mixer/default'
|
20
|
+
require 'volay/mixer/alsa'
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'fakefs/spec_helpers'
|
3
|
+
require 'gtk3'
|
4
|
+
require 'volay/app'
|
5
|
+
require 'volay/utils'
|
6
|
+
require 'volay/widget/events'
|
7
|
+
require 'volay/widget/system_tray'
|
8
|
+
require 'volay/widget/volume_control'
|
9
|
+
require 'volay/mixer/default'
|
10
|
+
require 'volay/mixer/alsa'
|
11
|
+
require 'volay/exceptions'
|
12
|
+
|
13
|
+
describe 'Volay::App' do
|
14
|
+
include FakeFS::SpecHelpers
|
15
|
+
|
16
|
+
context '#events' do
|
17
|
+
def app_events
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'should return alsa' do
|
21
|
+
allow_any_instance_of(Volay::App).to receive(:initialize_mixer)
|
22
|
+
allow_any_instance_of(Volay::App).to receive(:initialize_ui)
|
23
|
+
allow_any_instance_of(Volay::Utils).to receive(:update_status_icon)
|
24
|
+
allow_any_instance_of(Volay::App).to receive(:connect_signals)
|
25
|
+
.and_yield('on_status_icon_button_press_event')
|
26
|
+
|
27
|
+
File.write('something', '')
|
28
|
+
app = Volay::App.new('something')
|
29
|
+
expect(app.signals_list).to be_a(Hash)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context '#ui' do
|
34
|
+
it 'should return alsa' do
|
35
|
+
allow_any_instance_of(Volay::App).to receive(:initialize_mixer)
|
36
|
+
allow_any_instance_of(Volay::App).to receive(:initialize_events)
|
37
|
+
|
38
|
+
xml = <<-EOF
|
39
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
40
|
+
<!-- Generated with glade 3.18.3 -->
|
41
|
+
<interface>
|
42
|
+
<requires lib="gtk+" version="3.2"/>
|
43
|
+
<object class="GtkIconFactory" id="icon_factory">
|
44
|
+
<sources>
|
45
|
+
<source stock-id="volume-muted" filename="/glade/icons/volume-muted.png"/>
|
46
|
+
</sources>
|
47
|
+
</object>
|
48
|
+
</interface>
|
49
|
+
EOF
|
50
|
+
|
51
|
+
File.write('something', xml)
|
52
|
+
app = Volay::App.new('something')
|
53
|
+
expect(app.get_object('icon_factory')).to be_a(Gtk::IconFactory)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'logger'
|
3
|
+
require 'volay/config'
|
4
|
+
require 'volay/mixer/default'
|
5
|
+
require 'volay/mixer/alsa'
|
6
|
+
require 'volay/exceptions'
|
7
|
+
|
8
|
+
describe 'Volay::Config' do
|
9
|
+
include FakeFS::SpecHelpers
|
10
|
+
|
11
|
+
it 'should return config file' do
|
12
|
+
config_file = Volay::Config.config_file
|
13
|
+
expect(config_file).to be_a(String)
|
14
|
+
expect(config_file).to match(/.volay$/)
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'should return logger' do
|
18
|
+
expect(Volay::Config.logger).to be_a(Logger)
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'should set and get config' do
|
22
|
+
Volay::Config.set('test', 'something')
|
23
|
+
expect(Volay::Config.get('test')).to eq('something')
|
24
|
+
expect(Volay::Config.get(:test)).to eq('something')
|
25
|
+
expect(Volay::Config.get(:something)).to be_nil
|
26
|
+
end
|
27
|
+
|
28
|
+
context '#which' do
|
29
|
+
let(:config) do
|
30
|
+
FileUtils.mkdir_p('/usr/bin')
|
31
|
+
File.write('/usr/bin/ruby', '')
|
32
|
+
File.chmod(0777, '/usr/bin/ruby')
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'should find ruby executable' do
|
36
|
+
config
|
37
|
+
expect(Volay::Config.which('ruby')).to eq('/usr/bin/ruby')
|
38
|
+
end
|
39
|
+
|
40
|
+
it "shouldn't find amixer executable" do
|
41
|
+
config
|
42
|
+
expect(Volay::Config.which('amixer')).to be_nil
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context '#mixer' do
|
47
|
+
def app_mixer(prog)
|
48
|
+
FileUtils.mkdir_p('/usr/bin')
|
49
|
+
File.write("/usr/bin/#{prog}", '')
|
50
|
+
File.chmod(0777, "/usr/bin/#{prog}")
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'should not return pulseaudio' do
|
54
|
+
app_mixer('pulseaudio-ctl')
|
55
|
+
expect { Volay::Config.mixer }
|
56
|
+
.to raise_error(Volay::MixerNotFound)
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'should return alsa' do
|
60
|
+
app_mixer('amixer')
|
61
|
+
expect(Volay::Config.mixer).to be_a(Volay::Mixer::Alsa)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'mixlib/shellout'
|
3
|
+
require 'volay/mixer/default'
|
4
|
+
|
5
|
+
describe 'Volay::Mixer::Alsa' do
|
6
|
+
let(:mixer) do
|
7
|
+
Volay::Mixer::Alsa.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def stub_shellout(cmd, stdout = '', stderr = '')
|
11
|
+
allow_any_instance_of(Mixlib::ShellOut).to receive(:run_command)
|
12
|
+
allow_any_instance_of(Mixlib::ShellOut).to receive(:new)
|
13
|
+
.with("amixer #{cmd}")
|
14
|
+
allow_any_instance_of(Mixlib::ShellOut).to receive(:stdout)
|
15
|
+
.and_return(stdout)
|
16
|
+
allow_any_instance_of(Mixlib::ShellOut).to receive(:stderr)
|
17
|
+
.and_return(stderr)
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'should raise error when calling up' do
|
21
|
+
stub_shellout('-q set Master 2%+', 'Yes')
|
22
|
+
expect(mixer.up).to eq('Yes')
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'should raise error when calling down' do
|
26
|
+
stub_shellout('-q set Master 2%-', 'Yes')
|
27
|
+
expect(mixer.down).to eq('Yes')
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'should raise error when calling value=' do
|
31
|
+
stub_shellout('-q set Master 50%', 'Yes')
|
32
|
+
expect(mixer.value = '50').to eq('50')
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'should raise error when calling toggle' do
|
36
|
+
stub_shellout('-q set Master toggle', 'Yes')
|
37
|
+
expect(mixer.toggle).to eq('Yes')
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'should raise error when calling current' do
|
41
|
+
stdout = <<-EOF
|
42
|
+
Simple mixer control 'Master',0
|
43
|
+
Capabilities: pvolume pswitch pswitch-joined
|
44
|
+
Playback channels: Front Left - Front Right
|
45
|
+
Limits: Playback 0 - 65536
|
46
|
+
Mono:
|
47
|
+
Front Left: Playback 13108 [20%] [off]
|
48
|
+
Front Right: Playback 13108 [20%] [off]
|
49
|
+
EOF
|
50
|
+
stub_shellout('get Master', stdout)
|
51
|
+
expect(mixer.current).to eq(value: 13_107,
|
52
|
+
max_value: 65_536,
|
53
|
+
percent: 20,
|
54
|
+
muted: true)
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'volay/mixer/default'
|
3
|
+
|
4
|
+
describe 'Volay::Mixer::Default' do
|
5
|
+
let(:mixer) do
|
6
|
+
Volay::Mixer::Default.new
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'should raise error when calling up' do
|
10
|
+
expect { mixer.up }.to raise_error(NotImplementedError)
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'should raise error when calling down' do
|
14
|
+
expect { mixer.down }.to raise_error(NotImplementedError)
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'should raise error when calling value=' do
|
18
|
+
expect { mixer.value = '50' }.to raise_error(NotImplementedError)
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'should raise error when calling toggle' do
|
22
|
+
expect { mixer.toggle }.to raise_error(NotImplementedError)
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'should raise error when calling current' do
|
26
|
+
expect { mixer.current }.to raise_error(NotImplementedError)
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'should raise error when calling value' do
|
30
|
+
expect { mixer.value }.to raise_error(NotImplementedError)
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should raise error when calling max_value' do
|
34
|
+
expect { mixer.max_value }.to raise_error(NotImplementedError)
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'should raise error when calling percent' do
|
38
|
+
expect { mixer.percent }.to raise_error(NotImplementedError)
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'should raise error when calling muted' do
|
42
|
+
expect { mixer.muted? }.to raise_error(NotImplementedError)
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'volay/utils'
|
3
|
+
|
4
|
+
describe 'Volay::Utils' do
|
5
|
+
context 'status_icon' do
|
6
|
+
def prepare(image, muted, volume_percent)
|
7
|
+
app = double
|
8
|
+
toggle_image = double
|
9
|
+
status_icon = double
|
10
|
+
mixer = double
|
11
|
+
allow(app).to receive(:mixer).and_return(mixer)
|
12
|
+
allow(app).to receive(:get_object)
|
13
|
+
.with('status_icon').and_return(status_icon)
|
14
|
+
allow(app).to receive(:get_object)
|
15
|
+
.with('toggle_mute_image').and_return(toggle_image)
|
16
|
+
allow(mixer).to receive(:muted?)
|
17
|
+
.and_return(muted)
|
18
|
+
allow(mixer).to receive(:percent)
|
19
|
+
.and_return(volume_percent)
|
20
|
+
[status_icon, toggle_image].each do |element|
|
21
|
+
allow(element).to receive(:set_stock)
|
22
|
+
.with(image).once.and_return(true)
|
23
|
+
end
|
24
|
+
|
25
|
+
app
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'should change icon to low' do
|
29
|
+
app = prepare('volume-low', false, 20)
|
30
|
+
utils = Volay::Utils.new(app)
|
31
|
+
expect(utils.update_status_icon).to be_truthy
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'should change icon to medium' do
|
35
|
+
app = prepare('volume-medium', false, 40)
|
36
|
+
utils = Volay::Utils.new(app)
|
37
|
+
expect(utils.update_status_icon).to be_truthy
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'should change icon to high' do
|
41
|
+
app = prepare('volume-high', false, 80)
|
42
|
+
utils = Volay::Utils.new(app)
|
43
|
+
expect(utils.update_status_icon).to be_truthy
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'should change icon to muted' do
|
47
|
+
app = prepare('volume-muted', true, 0)
|
48
|
+
utils = Volay::Utils.new(app)
|
49
|
+
expect(utils.update_status_icon).to be_truthy
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'volay/widget/volume_control'
|
3
|
+
|
4
|
+
describe 'Volay::Widget::VolumeControl' do
|
5
|
+
let(:app) do
|
6
|
+
app = double
|
7
|
+
allow(app).to receive(:mixer).and_return(double)
|
8
|
+
allow(app).to receive(:signals_list).and_return({})
|
9
|
+
app
|
10
|
+
end
|
11
|
+
|
12
|
+
let(:vc) do
|
13
|
+
Volay::Widget::VolumeControl.new(app)
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'on volume scale' do
|
17
|
+
utils = double
|
18
|
+
volume = double
|
19
|
+
allow(volume).to receive(:value=).and_return(20)
|
20
|
+
allow(app).to receive(:get_object).once.with('volume_adjustement')
|
21
|
+
.and_return(volume)
|
22
|
+
allow(app).to receive(:utils).once.and_return(utils)
|
23
|
+
allow(utils).to receive(:update_status_icon).once.and_return(true)
|
24
|
+
allow(app.mixer).to receive(:percent).and_return(20)
|
25
|
+
expect(vc.on_system_tray_window_show).to be_truthy
|
26
|
+
end
|
27
|
+
end
|
data/task/manifest.rake
ADDED
data/task/rubocop.rake
ADDED
data/task/spec.rake
ADDED
data/volay.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require File.expand_path('../lib/volay/version', __FILE__)
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = 'volay'
|
5
|
+
s.version = Volay::VERSION
|
6
|
+
s.authors = ['Pierre Rambaud']
|
7
|
+
s.email = 'pierre.rambaud86@gmail.com'
|
8
|
+
s.license = 'GPL-3.0'
|
9
|
+
s.summary = 'The Volume system tray'
|
10
|
+
s.homepage = 'http://github.com/PierreRambaud/volay'
|
11
|
+
s.description = 'Really simple volume system tray written in ruby.'
|
12
|
+
s.executables = ['volay']
|
13
|
+
s.files = File.read(File.expand_path('../MANIFEST', __FILE__)).split("\n")
|
14
|
+
|
15
|
+
s.required_ruby_version = '>= 1.9.3'
|
16
|
+
|
17
|
+
s.add_dependency 'mixlib-cli', '~>1.5.0'
|
18
|
+
s.add_dependency 'mixlib-shellout', '~>2.0.1'
|
19
|
+
s.add_dependency 'gtk3', '~>2.2'
|
20
|
+
|
21
|
+
s.add_development_dependency 'fakefs', '~>0.6.0'
|
22
|
+
s.add_development_dependency 'rake', '~>10.0'
|
23
|
+
s.add_development_dependency 'rack-test', '~>0.6'
|
24
|
+
s.add_development_dependency 'rspec', '~>3.0'
|
25
|
+
s.add_development_dependency 'simplecov', '~>0.9'
|
26
|
+
s.add_development_dependency 'rubocop', '~>0.25'
|
27
|
+
end
|