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.
@@ -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'
@@ -0,0 +1,7 @@
1
+ $LOAD_PATH << File.expand_path('../lib', __FILE__)
2
+
3
+ require 'simplecov'
4
+
5
+ SimpleCov.start do
6
+ add_filter '/spec/'
7
+ end
@@ -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
@@ -0,0 +1,9 @@
1
+ # -*- coding: utf-8 -*-
2
+ desc 'Generates the MANIFEST file'
3
+ task :manifest do
4
+ files = `git ls-files`.split("\n").sort
5
+ handle = File.open(File.expand_path('../../MANIFEST', __FILE__), 'w')
6
+
7
+ handle.write(files.join("\n"))
8
+ handle.close
9
+ end
data/task/rubocop.rake ADDED
@@ -0,0 +1,5 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'rubocop/rake_task'
3
+
4
+ desc 'Run RuboCop'
5
+ RuboCop::RakeTask.new(:rubocop)
data/task/spec.rake ADDED
@@ -0,0 +1,6 @@
1
+ require 'rspec/core/rake_task'
2
+
3
+ desc 'Run Rspec tests'
4
+ RSpec::Core::RakeTask.new(:spec) do |t|
5
+ t.rspec_opts = '--color --format documentation'
6
+ end
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