volay 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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