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.
data/MANIFEST ADDED
@@ -0,0 +1,38 @@
1
+ .gitignore
2
+ .rubocop.yml
3
+ .travis.yml
4
+ Gemfile
5
+ LICENSE
6
+ MANIFEST
7
+ README.md
8
+ Rakefile
9
+ bin/volay
10
+ glade/icons/volume-high.png
11
+ glade/icons/volume-low.png
12
+ glade/icons/volume-medium.png
13
+ glade/icons/volume-muted.png
14
+ glade/volay.glade
15
+ lib/volay.rb
16
+ lib/volay/app.rb
17
+ lib/volay/cli.rb
18
+ lib/volay/config.rb
19
+ lib/volay/exceptions.rb
20
+ lib/volay/mixer/alsa.rb
21
+ lib/volay/mixer/default.rb
22
+ lib/volay/utils.rb
23
+ lib/volay/version.rb
24
+ lib/volay/volay.rb
25
+ lib/volay/widget/events.rb
26
+ lib/volay/widget/system_tray.rb
27
+ lib/volay/widget/volume_control.rb
28
+ spec/spec_helper.rb
29
+ spec/volay/app_spec.rb
30
+ spec/volay/config_spec.rb
31
+ spec/volay/mixer/alsa_spec.rb
32
+ spec/volay/mixer/default_spec.rb
33
+ spec/volay/utils_spec.rb
34
+ spec/volay/widget/volume_control_spec.rb
35
+ task/manifest.rake
36
+ task/rubocop.rake
37
+ task/spec.rake
38
+ volay.gemspec
data/README.md ADDED
@@ -0,0 +1,52 @@
1
+ #Volay, the Volume System Tray
2
+
3
+ [![Build Status](https://travis-ci.org/PierreRambaud/volay.svg)](https://travis-ci.org/PierreRambaud/volay)
4
+
5
+ ##Requirements
6
+
7
+ * Ruby 1.9.3 or newer
8
+
9
+ ##Installation
10
+
11
+ From Rubygems:
12
+
13
+ ```
14
+ $ gem install volay
15
+ ```
16
+
17
+ From Github:
18
+
19
+ ```
20
+ $ git clone https://github.com/PierreRambaud/volay.git
21
+ $ cd volay
22
+ $ bundle install
23
+ $ bundle exec rake install
24
+ ```
25
+
26
+ ## Usage
27
+
28
+ ```bash
29
+ $ volay -h
30
+ Volay, the Volume System Tray.
31
+ -d, --down PERCENT Down volume
32
+ -l, --log_level LEVEL Set the log level (debug, info, warn, error, fatal)
33
+ -m, --toggle-mute Toggle mute
34
+ -u, --up PERCENT Up volume
35
+ -h, --help Show this message
36
+ ```
37
+
38
+ If no arguments are passed, the Gtk app is launched.
39
+
40
+ ## Running tests
41
+
42
+ To run unit tests:
43
+ `$ bundle exec rake spec`
44
+
45
+ To check code style:
46
+ `$ bundle exec rake rubocop`
47
+
48
+ To run all tests:
49
+ `$ bundle exec rake`
50
+
51
+ ## License
52
+ See [LICENSE](LICENSE) file
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+
2
+ require 'bundler/gem_tasks'
3
+ GEMSPEC = Gem::Specification.load('gemirro.gemspec')
4
+
5
+ Dir['./task/*.rake'].each do |task|
6
+ import(task)
7
+ end
8
+
9
+ task default: [:rubocop, :spec]
data/bin/volay ADDED
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env ruby
2
+ require File.expand_path('../../lib/volay', __FILE__)
3
+
4
+ # Module volay
5
+ module Volay
6
+ # Main class to display app or control volume
7
+ class Main
8
+ def initialize
9
+ cli = Volay::CLI.new
10
+ cli.parse_options
11
+ Volay::Config.set(:log_level, cli.config[:log_level])
12
+ Volay::Config.init_config
13
+ if [:up, :down, :mute].any? { |k| cli.config.key?(k) }
14
+ sound(cli.config)
15
+ else
16
+ app
17
+ end
18
+ end
19
+
20
+ def sound(config)
21
+ Volay::Config.mixer.up(config[:up]) unless config[:up].nil?
22
+ Volay::Config.mixer.down(config[:down]) unless config[:down].nil?
23
+ Volay::Config.mixer.toggle if config[:mute]
24
+ end
25
+
26
+ def app
27
+ Gtk.init
28
+ Volay::App.new('glade/volay.glade')
29
+ Gtk.main
30
+ end
31
+ end
32
+ end
33
+
34
+ Volay::Main.new
Binary file
Binary file
Binary file
Binary file
data/glade/volay.glade ADDED
@@ -0,0 +1,99 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!-- Generated with glade 3.18.3 -->
3
+ <interface>
4
+ <requires lib="gtk+" version="3.6"/>
5
+ <object class="GtkIconFactory" id="icon_factory">
6
+ <sources>
7
+ <source stock-id="volume-high" filename="/glade/icons/volume-high.png"/>
8
+ <source stock-id="volume-medium" filename="/glade/icons/volume-medium.png"/>
9
+ <source stock-id="volume-muted" filename="/glade/icons/volume-muted.png"/>
10
+ <source stock-id="volume-low" filename="/glade/icons/volume-low.png"/>
11
+ </sources>
12
+ </object>
13
+ <object class="GtkMenu" id="popup_menu">
14
+ <property name="visible">True</property>
15
+ <property name="can_focus">False</property>
16
+ <child>
17
+ <object class="GtkImageMenuItem" id="popup_menu_quit">
18
+ <property name="label">gtk-quit</property>
19
+ <property name="visible">True</property>
20
+ <property name="can_focus">False</property>
21
+ <property name="use_underline">True</property>
22
+ <property name="use_stock">True</property>
23
+ <property name="always_show_image">True</property>
24
+ <signal name="activate" handler="on_popup_menu_quit_activate" swapped="no"/>
25
+ </object>
26
+ </child>
27
+ </object>
28
+ <object class="GtkStatusIcon" id="status_icon">
29
+ <property name="stock">volume-muted</property>
30
+ <signal name="button-press-event" handler="on_status_icon_button_press_event" swapped="no"/>
31
+ <signal name="popup-menu" handler="on_status_icon_popup_menu" swapped="no"/>
32
+ </object>
33
+ <object class="GtkAdjustment" id="volume_adjustement">
34
+ <property name="upper">100</property>
35
+ <property name="step_increment">1</property>
36
+ <property name="page_increment">10</property>
37
+ <signal name="value-changed" handler="on_volume_adjustement_value_changed" swapped="no"/>
38
+ </object>
39
+ <object class="GtkWindow" id="system_tray_window">
40
+ <property name="width_request">25</property>
41
+ <property name="height_request">200</property>
42
+ <property name="can_focus">True</property>
43
+ <property name="events">GDK_FOCUS_CHANGE_MASK</property>
44
+ <property name="resizable">False</property>
45
+ <property name="modal">True</property>
46
+ <property name="window_position">mouse</property>
47
+ <property name="type_hint">popup-menu</property>
48
+ <property name="focus_on_map">False</property>
49
+ <property name="decorated">False</property>
50
+ <property name="deletable">False</property>
51
+ <signal name="focus-out-event" handler="on_system_tray_window_focus_out_event" swapped="no"/>
52
+ <signal name="show" handler="on_system_tray_window_show" swapped="no"/>
53
+ <child>
54
+ <object class="GtkBox" id="volume_control_box">
55
+ <property name="visible">True</property>
56
+ <property name="can_focus">False</property>
57
+ <property name="orientation">vertical</property>
58
+ <child>
59
+ <object class="GtkScale" id="volume_scale">
60
+ <property name="visible">True</property>
61
+ <property name="can_focus">True</property>
62
+ <property name="orientation">vertical</property>
63
+ <property name="adjustment">volume_adjustement</property>
64
+ <property name="inverted">True</property>
65
+ <property name="round_digits">0</property>
66
+ <property name="digits">0</property>
67
+ <signal name="realize" handler="on_volume_scale_realize" swapped="no"/>
68
+ </object>
69
+ <packing>
70
+ <property name="expand">True</property>
71
+ <property name="fill">True</property>
72
+ <property name="position">0</property>
73
+ </packing>
74
+ </child>
75
+ <child>
76
+ <object class="GtkToggleButton" id="toggle_mute">
77
+ <property name="visible">True</property>
78
+ <property name="can_focus">False</property>
79
+ <property name="receives_default">True</property>
80
+ <property name="always_show_image">True</property>
81
+ <signal name="toggled" handler="on_toggle_mute_toggled" swapped="no"/>
82
+ <child>
83
+ <object class="GtkImage" id="toggle_mute_image">
84
+ <property name="visible">True</property>
85
+ <property name="can_focus">False</property>
86
+ <property name="stock">volume-muted</property>
87
+ </object>
88
+ </child>
89
+ </object>
90
+ <packing>
91
+ <property name="expand">False</property>
92
+ <property name="fill">True</property>
93
+ <property name="position">1</property>
94
+ </packing>
95
+ </child>
96
+ </object>
97
+ </child>
98
+ </object>
99
+ </interface>
data/lib/volay/app.rb ADDED
@@ -0,0 +1,62 @@
1
+ # Volay module
2
+ module Volay
3
+ # App class
4
+ class App < Gtk::Builder
5
+ attr_accessor :signals_list
6
+ attr_reader :mixer, :utils
7
+
8
+ ##
9
+ # Initialize application
10
+ #
11
+ # @param [String] path Path to glade file
12
+ #
13
+ def initialize(path)
14
+ super()
15
+
16
+ Gtk::Settings.default.gtk_button_images = true
17
+
18
+ initialize_ui(path)
19
+ initialize_mixer
20
+ initialize_events
21
+ end
22
+
23
+ private
24
+
25
+ ##
26
+ # Initialize events
27
+ #
28
+ def initialize_events
29
+ @signals_list = {}
30
+ @utils = Volay::Utils.new(self)
31
+ @utils.update_status_icon
32
+ Volay::Widget::SystemTray.new(self)
33
+ Volay::Widget::VolumeControl.new(self)
34
+
35
+ connect_signals do |handler|
36
+ @signals_list[handler] if @signals_list.key?(handler)
37
+ end
38
+ end
39
+
40
+ ##
41
+ # Initialize mixer for controlling volume
42
+ #
43
+ def initialize_mixer
44
+ @mixer ||= Volay::Config.mixer
45
+ end
46
+
47
+ ##
48
+ # Intiailize ui by overriding icons path
49
+ #
50
+ # @param [String] path Path to glade file
51
+ #
52
+ def initialize_ui(path)
53
+ # Override Icon path to prevent
54
+ # Gtk-CRITICAL **:gtk_icon_source_set_filename:
55
+ ui = File.read(path)
56
+ ui.gsub!(%r{/glade/icons},
57
+ File.expand_path('../../glade/icons',
58
+ File.dirname(__FILE__)))
59
+ add_from_string(ui)
60
+ end
61
+ end
62
+ end
data/lib/volay/cli.rb ADDED
@@ -0,0 +1,44 @@
1
+ # Volay module
2
+ module Volay
3
+ # CLI implementation
4
+ class CLI
5
+ include Mixlib::CLI
6
+
7
+ banner('Volay, the Volume System Tray.')
8
+
9
+ option(:help,
10
+ short: '-h',
11
+ long: '--help',
12
+ description: 'Show this message',
13
+ on: :tail,
14
+ boolean: true,
15
+ show_options: true,
16
+ exit: 0)
17
+
18
+ option(:log_level,
19
+ short: '-l LEVEL',
20
+ long: '--log_level LEVEL',
21
+ description: 'Set the log level (debug, info, warn, error, fatal)',
22
+ in: %w(debug info warn error fatal),
23
+ default: :info,
24
+ proc: proc { |l| l.to_sym })
25
+
26
+ option(:up,
27
+ short: '-u PERCENT',
28
+ long: '--up PERCENT',
29
+ description: 'Up volume',
30
+ proc: proc { |l| l.to_i })
31
+
32
+ option(:down,
33
+ short: '-d PERCENT',
34
+ long: '--down PERCENT',
35
+ description: 'Down volume',
36
+ proc: proc { |l| l.to_i })
37
+
38
+ option(:mute,
39
+ short: '-m',
40
+ long: '--toggle-mute',
41
+ boolean: true,
42
+ description: 'Toggle mute')
43
+ end
44
+ end
@@ -0,0 +1,91 @@
1
+ # Volay module
2
+ module Volay
3
+ # Config class
4
+ class Config
5
+ attr_reader :logger, :mixer, :options
6
+
7
+ ##
8
+ # Get option
9
+ #
10
+ # @return [Mixed]
11
+ #
12
+ def self.init_config
13
+ File.write(config_file, '') unless File.exist?(config_file)
14
+ logger.level = get(:log_level)
15
+ end
16
+
17
+ ##
18
+ # Get option
19
+ #
20
+ # @return [Mixed]
21
+ #
22
+ def self.get(option)
23
+ @options ||= {}
24
+ @options[option.to_sym] if @options.key?(option.to_sym)
25
+ end
26
+
27
+ ##
28
+ # Set option
29
+ #
30
+ # @param [String|Symbol] option Option key
31
+ # @param [Mixed] value Option value
32
+ #
33
+ def self.set(option, value)
34
+ @options ||= {}
35
+ @options[option.to_sym] = value
36
+ end
37
+
38
+ ##
39
+ # Get config file
40
+ #
41
+ # @return [String]
42
+ #
43
+ def self.config_file
44
+ File.expand_path('~/.volay')
45
+ end
46
+
47
+ ##
48
+ # Get logger
49
+ #
50
+ # @return [Logger]
51
+ #
52
+ def self.logger
53
+ @logger ||= Logger.new(STDOUT)
54
+ end
55
+
56
+ ##
57
+ # Initialize mixer for controlling volume
58
+ #
59
+ def self.mixer
60
+ @mixer ||= begin
61
+ if which('amixer')
62
+ Volay::Mixer::Alsa.new
63
+ else
64
+ fail MixerNotFound
65
+ end
66
+ end
67
+ end
68
+
69
+ ##
70
+ # Cross-platform way of finding an executable in the $PATH.
71
+ #
72
+ # Example:
73
+ # which('ruby') #=> /usr/bin/ruby
74
+ # which('foo') #=> nil
75
+ #
76
+ # @param [String] cmd Which command
77
+ # @return [String|NilClass]
78
+ #
79
+ def self.which(cmd)
80
+ exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
81
+ ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
82
+ exts.each do |ext|
83
+ exe = File.join(path, "#{cmd}#{ext}")
84
+ return exe if File.executable?(exe) && !File.directory?(exe)
85
+ end
86
+ end
87
+
88
+ nil
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,6 @@
1
+ # Volay Module
2
+ module Volay
3
+ # Mixer not found
4
+ class MixerNotFound < StandardError
5
+ end
6
+ end
@@ -0,0 +1,48 @@
1
+ # Volay module
2
+ module Volay
3
+ # Mixer module
4
+ module Mixer
5
+ # Alsa class for playing with amixer
6
+ class Alsa < Default
7
+ DEFAULT_VALUE = 2
8
+
9
+ def up(value = DEFAULT_VALUE)
10
+ command("-q set Master #{value}%+")
11
+ end
12
+
13
+ def down(value = DEFAULT_VALUE)
14
+ command("-q set Master #{value}%-")
15
+ end
16
+
17
+ def value=(value)
18
+ command("-q set Master #{value}%")
19
+ end
20
+
21
+ def toggle
22
+ command('-q set Master toggle')
23
+ end
24
+
25
+ def current
26
+ result = command('get Master')
27
+ max_value = result.match(/ 0 - (\d+)/)[1].to_i
28
+ current_percent = result.match(/\[([0-9]+)%\]\s+\[o(?:n|ff)\]/)[1].to_i
29
+ current_value = ((current_percent * max_value) / 100).to_i
30
+ current_state = result.match(/\[([a-z]+)\]/)[1].to_s
31
+
32
+ { value: current_value,
33
+ max_value: max_value,
34
+ percent: current_percent,
35
+ muted: current_state == 'on' ? false : true }
36
+ end
37
+
38
+ private
39
+
40
+ def command(cmd)
41
+ result = Mixlib::ShellOut.new("amixer #{cmd}")
42
+ result.run_command
43
+ Volay::Config.logger.error(result.stderr) unless result.stderr.empty?
44
+ result.stdout
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,72 @@
1
+ # Volay module
2
+ module Volay
3
+ # Mixer module
4
+ module Mixer
5
+ # Default class
6
+ class Default
7
+ ##
8
+ # Up the volume
9
+ #
10
+ def up(*)
11
+ fail NotImplementedError
12
+ end
13
+
14
+ ##
15
+ # Down the volume
16
+ #
17
+ def down(*)
18
+ fail NotImplementedError
19
+ end
20
+
21
+ ##
22
+ # Set the volume
23
+ #
24
+ def value=(*)
25
+ fail NotImplementedError
26
+ end
27
+
28
+ ##
29
+ # Toggle mute
30
+ #
31
+ def toggle
32
+ fail NotImplementedError
33
+ end
34
+
35
+ ##
36
+ # Current data, value, max_value,
37
+ # percent and muted
38
+ #
39
+ def current
40
+ fail NotImplementedError
41
+ end
42
+
43
+ ##
44
+ # Current value
45
+ #
46
+ def max_value
47
+ current[:value]
48
+ end
49
+
50
+ ##
51
+ # Current value
52
+ #
53
+ def value
54
+ current[:value]
55
+ end
56
+
57
+ ##
58
+ # Current percent
59
+ #
60
+ def percent
61
+ current[:percent]
62
+ end
63
+
64
+ ##
65
+ # Is muted
66
+ #
67
+ def muted?
68
+ current[:muted]
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,40 @@
1
+ # Volay module
2
+ module Volay
3
+ # Utils class
4
+ class Utils
5
+ attr_reader :app
6
+
7
+ ##
8
+ # Initialize utils class
9
+ #
10
+ # @param [Gtk::Builder] app Application
11
+ #
12
+ def initialize(app)
13
+ @app = app
14
+ end
15
+
16
+ ##
17
+ # Change status icon stock
18
+ #
19
+ def update_status_icon
20
+ icon = status_icon
21
+ @app.get_object('status_icon')
22
+ .set_stock(icon)
23
+ @app.get_object('toggle_mute_image')
24
+ .set_stock(icon)
25
+ end
26
+
27
+ def status_icon
28
+ case @app.mixer.percent
29
+ when 66..100
30
+ icon = 'volume-high'
31
+ when 33..65
32
+ icon = 'volume-medium'
33
+ when 0..32
34
+ icon = 'volume-low'
35
+ end unless @app.mixer.muted?
36
+
37
+ icon.nil? ? 'volume-muted' : icon
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,4 @@
1
+ # Volay module
2
+ module Volay
3
+ VERSION = '0.0.1'
4
+ end
@@ -0,0 +1,11 @@
1
+ require 'gtk3'
2
+ require 'mixlib-cli'
3
+
4
+ unless $LOAD_PATH.include?(File.expand_path('../', __FILE__))
5
+ $LOAD_PATH.unshift(File.expand_path('../', __FILE__))
6
+ end
7
+
8
+ require 'volay/version'
9
+ require 'volay/app'
10
+ require 'volay/cli'
11
+ require 'volay/widget/events'
@@ -0,0 +1,22 @@
1
+ # Volay module
2
+ module Volay
3
+ # Widgets components
4
+ module Widget
5
+ # Events class
6
+ class Events
7
+ attr_reader :app
8
+
9
+ ##
10
+ # Initialize signals events
11
+ #
12
+ def initialize(app)
13
+ @app = app
14
+
15
+ methods.each do |name|
16
+ next unless name.match(/^on_/)
17
+ @app.signals_list[name.to_s] = method(name)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end