dock_driver 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,168 +1,119 @@
1
- # -*- ruby -*-
2
- # vim: set nosta noet ts=4 sw=4:
3
- # encoding: utf-8
4
1
 
5
- require 'erb'
6
- require 'yaml'
2
+ require 'thread'
7
3
  require 'ostruct'
8
- require 'logger'
9
-
10
- require 'dock_driver' unless defined? DockDriver
11
- require 'dock_driver/hashutils'
12
- require 'dock_driver/poll'
13
- require 'dock_driver/data_rate_poll'
14
-
15
- # An object that represents and drives a dock program, periodically
16
- # updating what it displays.
17
- #
18
- class DockDriver::Dock < OpenStruct
19
-
20
- include HashUtilities
21
-
22
- # The *normal* place people keep user-specific configuration data for this program.
23
- DEFAULT_CONFIG_FILE = '~/.dock_driver.yml'.freeze
24
-
25
- # The usual place to put user-specific diagnostic information pertaining to this program.
26
- DEFAULT_LOG_FILE = '~/.dock_driver.log'.freeze
27
-
28
- # Default values to populate this OpenStruct with.
29
- #
30
- DEFAULTS = {
31
- :polls => {},
32
- :time_format => '%Y-%m-%d %H:%M:%S %Z',
33
- }.freeze
34
-
35
- # A map of poll Classes/types to kewords for config.
36
- #
37
- POLL_TYPES = {
38
- nil => DockDriver::Poll,
39
- 'text' => DockDriver::Poll,
40
- 'rate' => DockDriver::DataRatePoll,
41
- }.freeze
42
-
43
- # Create and start a new Dock.
44
- #
45
- # === Arguments:
46
- #
47
- # +:config_file+ - Provide a path to a config file different from
48
- # the default: ~/.dock_driver.yml
49
- #
50
- # +:dock_command+ - Override the dock command from the config file.
51
- #
52
- def initialize( config_file = nil, log_file = nil, dock_command = nil )
53
- super DEFAULTS
54
-
55
- @log_file = File.expand_path( log_file || DEFAULT_LOG_FILE )
56
- @log = Logger.new( @log_file )
57
- @log.level = $DEBUG ? Logger::DEBUG : Logger::WARN
58
-
59
- @config_file_mtime = nil
60
- @config_file = File.expand_path( config_file || DEFAULT_CONFIG_FILE )
61
-
62
- load_config
63
-
64
- @running = false
65
- if dock_command
66
- self.dock_command = dock_command
67
- end
68
- end
69
-
70
- # Start the dock. Repeatedly calls Dock#drive until it's time to stop. See Dock#stop.
71
- #
72
- def start
73
- return if @running
74
- @running = true
75
- while @running
76
- drive
77
- end
78
- end
79
-
80
- # Signal that it's time to stop the dock.
81
- #
82
- def stop
83
- @running = false
84
- end
85
-
86
- #########
87
- protected
88
- #########
89
-
90
- # Drives the dock program by IO#popen. This method provides a main
91
- # loop that repeatedly checks the mtime of the config file, polls all of
92
- # the polls, updates and delivers the template or any errors that
93
- # happened along the way.
94
- #
95
- def drive
96
- pipe = File.popen( self.dock_command, 'w' )
97
- pipe.sync = true
98
-
99
- while true
100
-
101
- begin
102
- break if load_config
103
- rescue Exception => e
104
- @log.error e
105
- pipe.puts "Couldn't load your config: %s " % e
106
- sleep 10
107
- next
108
- end
109
-
110
- self.polls.each do |name,p|
111
- begin
112
- p.poll
113
- rescue Exception => e
114
- @log.error e
115
- end
116
- end
117
-
118
- begin
119
- vbind = OpenStruct.new( self.polls ).send( :binding )
120
- pipe.puts @template.result( vbind ).gsub( /\s+/, ' ' )
121
- rescue SyntaxError => e
122
- @log.error e
123
- pipe.puts "ERB didn't like your template. Check #{@log_file} to see why."
124
- sleep 30
125
- rescue Exception
126
- break
127
- end
128
-
129
- sleep 1
130
- end
131
-
132
- pipe.close
133
- rescue => e
134
- puts e
135
- puts e.backtrace
136
- exit
137
- end
138
-
139
- # Loads the configuration file defined by the option +:config_file+ if it has
140
- # changed since this method was last called. If not, returns false.
141
- #
142
- def load_config
143
- mtime = File.mtime @config_file
144
- return false if mtime == @config_file_mtime
145
-
146
- @table.merge! symbolify_keys YAML.load_file @config_file
147
- @config_file_mtime = mtime
148
-
149
- self.polls = {}
150
- if self.commands and self.commands.is_a? Hash
151
- self.commands.each do |name,hash|
152
- hash[:name] = name
153
- self.polls[name] = POLL_TYPES[hash[:type]].new( hash )
154
- end
155
- end
156
-
157
- if self.polls[:time].nil?
158
- self.polls[:time] = DockDriver::Poll.new( {
159
- :cmd => Proc.new { Time.now.strftime( self.time_format ) },
160
- } )
161
- end
162
-
163
- @template = ERB.new( self.template )
164
- true
165
- end
166
-
167
- end
4
+ require 'erb'
168
5
 
6
+ module DockDriver
7
+
8
+ # Manages the dock process and templating.
9
+ class Dock
10
+
11
+ extend Loggability
12
+ log_to :dock_driver
13
+
14
+ #####################################################################
15
+ ### C O N F I G U R A B I L I T Y A P I
16
+ #####################################################################
17
+
18
+ include Configurability
19
+ config_key :dock
20
+
21
+ class << self
22
+ # The dock command.
23
+ attr_accessor :command
24
+ # The configuration for each DockItem.
25
+ attr_accessor :items
26
+ # The dock's output template.
27
+ attr_accessor :template
28
+ end
29
+
30
+ ### The default configuration for this class.
31
+ CONFIG_DEFAULTS = {
32
+ :items => [
33
+ { 'name' => 'date', 'command' => 'date', 'frequency' => 1 }
34
+ ],
35
+ :command => 'dzen2',
36
+ :template => '<% date %>'
37
+ }
38
+
39
+ ### Configure the class.
40
+ def self::configure( section )
41
+ merged = CONFIG_DEFAULTS.merge( section )
42
+ @items = merged[:items]
43
+ @command = merged[:command]
44
+ @template = ERB.new( merged[:template] )
45
+ end
46
+
47
+ #####################################################################
48
+ ### D O C K A P I
49
+ #####################################################################
50
+
51
+ # Set to true after the dock starts.
52
+ attr_accessor :running
53
+ # A hash of DockItems.
54
+ attr_accessor :items
55
+ # A hash of threads that have been created to run each DockItem.
56
+ attr_accessor :threads
57
+ # A mutex to ensure sane output.
58
+ attr_accessor :render_lock
59
+ # An IO pipe that will be or is connected to the dock program.
60
+ attr_writer :pipe
61
+
62
+ ### A bog-standard constructor.
63
+ def initialize #:nodoc:
64
+ @items = {}
65
+ @threads = {}
66
+ @render_lock = Mutex.new
67
+ end
68
+
69
+ ### Lazily execute the dock command.
70
+ def pipe
71
+ return @pipe ||= IO.popen( self.class.command, 'r+' )
72
+ end
73
+
74
+ ### Listen to this dock's items, printing to the dock command when
75
+ ### appropriate.
76
+ def notify( obj )
77
+ self.render_lock.synchronize do
78
+ self.pipe.puts self.to_s if self.pipe
79
+ end
80
+ end
81
+
82
+ ### Start or restart the dock.
83
+ def run
84
+
85
+ self.log.debug "Template: %p" % [self.class.template]
86
+
87
+ if self.running
88
+ self.log.debug "Restarting the dock."
89
+ self.threads.values.map( &:kill )
90
+ self.items.clear
91
+ self.pipe.close
92
+ self.pipe = nil
93
+ else
94
+ self.log.debug "Starting the dock."
95
+ self.running = true
96
+ end
97
+
98
+ self.log.debug "Building items."
99
+ self.class.items.map do |item_opts|
100
+ item = DockItem.new( item_opts )
101
+ item.add_observer self
102
+ self.items[item.name] = item
103
+ self.threads[item.name] = Thread.new { item.run }
104
+ end
105
+
106
+ self.log.debug "Starting items."
107
+ self.threads.values.map( &:run )
108
+
109
+ end
110
+
111
+ ### Provide output for the dock.
112
+ def to_s
113
+ vbind = OpenStruct.new( self.items ).send( :binding )
114
+ return self.class.template.result( vbind ).gsub( /\s+/, ' ' )
115
+ end
116
+
117
+ end
118
+
119
+ end
@@ -0,0 +1,47 @@
1
+ module DockDriver
2
+
3
+ # Runs a command periodically and notifies an observer periodically, and
4
+ # provides output from the command.
5
+ class DockItem
6
+
7
+ extend Loggability
8
+ log_to :dock_driver
9
+
10
+ include Subject
11
+
12
+ # This item's name.
13
+ attr_accessor :name
14
+ # How many seconds to wait between command executions.
15
+ attr_accessor :frequency
16
+ # The last output from the command.
17
+ attr_accessor :output
18
+ # The command to be run.
19
+ attr_accessor :command
20
+
21
+ # A standard contstructor.
22
+ def initialize( opts = {} ) #:nodoc:
23
+ self.log.debug "DockItem constructor options: %p" % [opts]
24
+ @name = opts['name']
25
+ @frequency = opts['frequency'] || 1
26
+ @command = opts['command']
27
+ @output = 0
28
+ end
29
+
30
+ # Work for a thread to do.
31
+ def run
32
+ self.log.debug "%s is running." % self.name
33
+ loop do
34
+ sleep self.frequency
35
+ # TODO output scanning
36
+ self.output = `#{self.command}`
37
+ self.notify_observers
38
+ end
39
+ end
40
+
41
+ # Returns the formatted output from the command.
42
+ def to_s
43
+ return self.output
44
+ end
45
+
46
+ end
47
+ end
@@ -0,0 +1,5 @@
1
+ module DockDriver
2
+ # A class to provide an api for formatting.
3
+ class Dzen2
4
+ end
5
+ end
@@ -0,0 +1,80 @@
1
+
2
+ require 'socket'
3
+ require 'pathname'
4
+ require 'json'
5
+
6
+ module DockDriver
7
+ # A class to provide IPC communication with i3wm.
8
+ class I3
9
+ COMMAND = 0
10
+ GET_WORKSPACES = 1
11
+ SUBSCRIBE = 2
12
+ GET_OUTPUTS = 3
13
+ MAGIC = 'i3-ipc'
14
+
15
+ # ripped from include/i3/ipc.h
16
+ #
17
+ EVENT_MASK = (1 << 31)
18
+ EVENT_WORKSPACE = (EVENT_MASK | 0)
19
+
20
+ def initialize( socket='~/.i3/ipc.sock' )
21
+ @ipc = UNIXSocket.open( Pathname( socket ).expand_path )
22
+ end
23
+
24
+ attr_reader :ipc
25
+
26
+ ### Send a command to i3.
27
+ def command( cmd )
28
+ self.send_data( COMMAND, cmd )
29
+ end
30
+
31
+ ### Return an array of current workspace state.
32
+ def get_workspaces
33
+ self.send_data( GET_WORKSPACES )
34
+ return self.read_data( GET_WORKSPACES )
35
+ end
36
+
37
+ ### Register interest in workspace change events.
38
+ def subscribe_workspaces
39
+ self.send_data( SUBSCRIBE, JSON.generate(['workspace']))
40
+ result = self.read_data( SUBSCRIBE )
41
+ return result[ 'success' ]
42
+ end
43
+
44
+ ### Do a blocking select, yielding an event when one is seen.
45
+ def wait_for_event( type=nil, timeout=nil )
46
+ ready = select( [self.ipc], nil, nil, timeout )
47
+ return if ready.nil? # timeout
48
+
49
+ msg = self.read_data( type )
50
+ return if msg.nil? && ! type.nil?
51
+
52
+ yield msg if block_given?
53
+ return msg
54
+ end
55
+
56
+ #########
57
+ protected
58
+ #########
59
+
60
+ ### Format a message payload and send it to i3.
61
+ def send_data( type, data=nil )
62
+ len = data.nil? ? 0 : data.to_s.bytes.count
63
+ body = MAGIC + [ len, type ].pack( 'LL' )
64
+ body << data.to_s unless data.nil?
65
+
66
+ self.ipc.write( body )
67
+ end
68
+
69
+ ### Receive a message payload from i3, returning it as a data structure.
70
+ ### Returns nil if a +type+ is specified, and the message doesn't match.
71
+ def read_data( expected_type=nil )
72
+ buf = self.ipc.read( MAGIC.bytes.count + 4 + 4 )
73
+ len, type = buf[6..-1].unpack( 'LL' )
74
+ msg = self.ipc.read( len )
75
+
76
+ return nil if ! expected_type.nil? && expected_type != type
77
+ return JSON.parse( msg )
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,33 @@
1
+ module DockDriver
2
+
3
+ # A mixin providing a simple implementation of the listener pattern.
4
+ module Subject
5
+
6
+ extend Loggability
7
+ log_to :dock_driver
8
+
9
+ # Lazily create a list of interested objects.
10
+ def observers
11
+ return @observers ||= []
12
+ end
13
+
14
+ # Register an object's interest in this Subject.
15
+ def add_observer( obj )
16
+ observers ||= []
17
+ if obj.respond_to? :notify
18
+ self.observers << obj
19
+ else
20
+ raise "Object must respond to :notify: %p" % [obj]
21
+ end
22
+ end
23
+
24
+ # Dispatch notification - call +notify+ on each observer.
25
+ def notify_observers
26
+ self.observers.each do |observer|
27
+ observer.notify( self )
28
+ end
29
+ end
30
+
31
+ end
32
+
33
+ end
@@ -0,0 +1,5 @@
1
+ module DockDriver
2
+ # A DockItem to show and switch workspaces.
3
+ class WorkspacePager < DockItem
4
+ end
5
+ end
data/lib/dock_driver.rb CHANGED
@@ -1,20 +1,86 @@
1
- # -*- ruby -*-
2
- # vim: set nosta noet ts=4 sw=4:
3
- # encoding: utf-8
4
- #
1
+ require 'rubygems'
2
+ require 'loggability'
5
3
 
6
- # A namespace for the project.
7
- #
4
+ require 'dock_driver/mixins'
5
+
6
+ require 'dock_driver/i3'
7
+ require 'dock_driver/dock'
8
+ require 'dock_driver/dock_item'
9
+ require 'dock_driver/workspace_pager'
10
+
11
+ # A namespace for the project & singleton for driving the dock.
8
12
  module DockDriver
9
13
 
10
- # Version Constant
11
- VERSION = '0.1.2'
14
+ include Configurability
15
+ extend Loggability
16
+
17
+ config_key :dock_driver
18
+ log_as :dock_driver
19
+
20
+ # Project version.
21
+ VERSION = '0.2.0'
22
+
23
+ # Project revision.
24
+ REVISION = %$Revision: aa11bde4afd8 $
25
+
26
+ # The default location of a user's config file.
27
+ USER_CONFIG_FILE = Pathname( '~/.dock_driver.yml' )
28
+
29
+ # Configurability defaults.
30
+ CONFIG_DEFAULTS = {
31
+ :dock_driver => {},
32
+ :dock => {
33
+ :command => 'dzen2 -dock',
34
+ :items => []
35
+ }
36
+ }
37
+
38
+ # The loaded config
39
+ @config = {}
40
+
41
+ # The dock instance for the app.
42
+ @dock = Dock.new
43
+
44
+ class << self
45
+ # The loaded config.
46
+ attr_accessor :config
47
+ # When the config was last loaded.
48
+ attr_accessor :config_loaded_at
49
+ # The dock object.
50
+ attr_accessor :dock
51
+ end
52
+
53
+ ### Configurability API
54
+ def self::load_config( config_file )
55
+ self.config = Configurability::Config.load( config_file, CONFIG_DEFAULTS )
56
+ self.log.info "Installing config (%p)" % [self.config]
57
+ self.config.install
58
+ self.config_loaded_at = Time.now
59
+ return self.config
60
+ end
12
61
 
13
- # Revision Constant
14
- REVISION = %q$Revision: 196ad604d0de $
62
+ # Watch the config; initate a reload if it has been touched.
63
+ def self::run( opts )
64
+ loop do
65
+ if self.config_loaded_at.nil? or
66
+ self.config_loaded_at < File.mtime( opts[:config] )
67
+ self.log.debug "The config has been touched."
68
+ self.reload( opts )
69
+ end
70
+ sleep 1
71
+ end
72
+ end
15
73
 
16
- # Behold the author(s) responsible for this mess.
17
- AUTHORS = [ 'Mike Hix <mike@musl.org>' ]
74
+ # Reload the config
75
+ def self::reload( opts )
76
+ self.log.debug "Reloading."
77
+ self.load_config( opts[:config] )
78
+ self.dock.run
79
+ end
18
80
 
19
- end
81
+ # Cleanly kill the dock.
82
+ def self::kill
83
+ self.dock.pipe.close
84
+ end
20
85
 
86
+ end
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dock_driver
3
3
  version: !ruby/object:Gem::Version
4
- hash: 31
4
+ hash: 23
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 1
9
8
  - 2
10
- version: 0.1.2
9
+ - 0
10
+ version: 0.2.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Michael Hix
@@ -35,7 +35,7 @@ cert_chain:
35
35
  Edc7ShBTME9+u6K/HFwVxTOH8WJVxkwGTmxKgCevr5mZzbINH/C6LbkmfEA=
36
36
  -----END CERTIFICATE-----
37
37
 
38
- date: 2012-08-22 00:00:00 Z
38
+ date: 2012-11-22 00:00:00 Z
39
39
  dependencies:
40
40
  - !ruby/object:Gem::Dependency
41
41
  prerelease: false
@@ -59,11 +59,11 @@ dependencies:
59
59
  requirements:
60
60
  - - ~>
61
61
  - !ruby/object:Gem::Version
62
- hash: 7
62
+ hash: 5
63
63
  segments:
64
64
  - 3
65
- - 0
66
- version: "3.0"
65
+ - 1
66
+ version: "3.1"
67
67
  type: :development
68
68
  name: hoe
69
69
  version_requirements: *id002
@@ -81,28 +81,27 @@ executables:
81
81
  extensions: []
82
82
 
83
83
  extra_rdoc_files:
84
- - History.txt
85
- - Ideas.txt
84
+ - History.rdoc
86
85
  - Manifest.txt
87
- - README.txt
86
+ - README.rdoc
87
+ - TODO.rdoc
88
88
  files:
89
89
  - .autotest
90
- - .hgignore
91
- - History.txt
92
- - Ideas.txt
90
+ - .pryrc
91
+ - History.rdoc
93
92
  - Manifest.txt
94
- - README.txt
93
+ - README.rdoc
95
94
  - Rakefile
95
+ - TODO.rdoc
96
96
  - bin/dock_driver
97
97
  - data/example.dock_driver.yml
98
98
  - lib/dock_driver.rb
99
- - lib/dock_driver/hashutils.rb
100
- - lib/dock_driver/poll.rb
101
- - lib/dock_driver/data_rate_poll.rb
102
99
  - lib/dock_driver/dock.rb
103
- - spec/dock_driver/dock_spec.rb
104
- - spec/dock_driver/poll_spec.rb
105
- - spec/dock_driver_spec.rb
100
+ - lib/dock_driver/dock_item.rb
101
+ - lib/dock_driver/dzen2.rb
102
+ - lib/dock_driver/i3.rb
103
+ - lib/dock_driver/mixins.rb
104
+ - lib/dock_driver/workspace_pager.rb
106
105
  - .gemtest
107
106
  homepage: http://hg.musl.org/projects/dock_driver/
108
107
  licenses: []
@@ -110,7 +109,7 @@ licenses: []
110
109
  post_install_message:
111
110
  rdoc_options:
112
111
  - --main
113
- - README.txt
112
+ - README.rdoc
114
113
  require_paths:
115
114
  - lib
116
115
  required_ruby_version: !ruby/object:Gem::Requirement
metadata.gz.sig CHANGED
Binary file
data/.hgignore DELETED
@@ -1,2 +0,0 @@
1
- doc/.*
2
- pkg/.*
data/History.txt DELETED
@@ -1,37 +0,0 @@
1
- === 0.1.2 / 2012-08-22
2
-
3
- * 1 minor enhancement
4
- * the signal handler for INT now exits.
5
-
6
- === 0.1.1 / 2012-05-20
7
-
8
- * 2 minor enhancements
9
- * logging
10
- * bug fixes
11
-
12
- === 0.1.0 / 2012-05-15
13
-
14
- * 3 minor enhancements
15
-
16
- * bin/dock_driver completed
17
- * RSpec tests completed & passing
18
- * Several bugs fixed
19
-
20
- === 0.0.3 / 2012-05-15
21
-
22
- * 1 minor enhancement
23
-
24
- * Doc & Manifest Updates
25
-
26
- === 0.0.2 / 2012-05-15
27
-
28
- * 1 minor enhancement
29
-
30
- * 100% documentation coverage
31
-
32
- === 0.0.1 / 2012-05-15
33
-
34
- * 1 major enhancement
35
-
36
- * Birthday!
37
-