dock_driver 0.1.2 → 0.2.0

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.
@@ -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
-