Mike Hix avatar Mike Hix committed 4f7d1eb

Rewrite complete.

Comments (0)

Files changed (17)

+= History
+
+=== 0.2.0 / 2012-11-21
+
+* 1 major enhancement
+  * A complete rewrite.
+    * Basic functionality is complete.
+    * The namespace has been re-arranged and made more general.
+    * Uses Configurability and Loggability gems.
+    * OptionParser has been replaced by Trollop.
+    * Support for INT and HUP signals.
+    * Dock now waits for DockItems to notify it before creating output.
+    * Dock now lazily executes the dock command.
+    * Dock no longer sets sync on the pipe which sometimes made dzen2 exit uncleanly.
+
+=== 0.1.2 / 2012-08-22
+
+* 1 minor enhancement
+  * the signal handler for INT now exits.
+
+=== 0.1.1 / 2012-05-20
+
+* 2 minor enhancements
+  * logging
+  * bug fixes
+
+=== 0.1.0 / 2012-05-15
+
+* 3 minor enhancements
+
+  * bin/dock_driver completed
+  * RSpec tests completed & passing
+  * Several bugs fixed
+
+=== 0.0.3 / 2012-05-15
+
+* 1 minor enhancement
+
+  * Doc & Manifest Updates
+
+=== 0.0.2 / 2012-05-15
+
+* 1 minor enhancement
+
+  * 100% documentation coverage
+
+=== 0.0.1 / 2012-05-15
+
+* 1 major enhancement
+
+  * Birthday!
+

History.txt

-=== 0.1.2 / 2012-08-22
-
-* 1 minor enhancement
-  * the signal handler for INT now exits.
-
-=== 0.1.1 / 2012-05-20
-
-* 2 minor enhancements
-  * logging
-  * bug fixes
-
-=== 0.1.0 / 2012-05-15
-
-* 3 minor enhancements
-
-  * bin/dock_driver completed
-  * RSpec tests completed & passing
-  * Several bugs fixed
-
-=== 0.0.3 / 2012-05-15
-
-* 1 minor enhancement
-
-  * Doc & Manifest Updates
-
-=== 0.0.2 / 2012-05-15
-
-* 1 minor enhancement
-
-  * 100% documentation coverage
-
-=== 0.0.1 / 2012-05-15
-
-* 1 major enhancement
-
-  * Birthday!
-

Empty file removed.

+.autotest
+.pryrc
+History.rdoc
+Manifest.txt
+README.rdoc
+Rakefile
+TODO.rdoc
+bin/dock_driver
+data/example.dock_driver.yml
+lib/dock_driver.rb
+lib/dock_driver/dock.rb
+lib/dock_driver/dock_item.rb
+lib/dock_driver/dzen2.rb
+lib/dock_driver/i3.rb
+lib/dock_driver/mixins.rb
+lib/dock_driver/workspace_pager.rb
+= dock_driver
+
+* http://hg.musl.org/projects/dock_driver/
+
+== DESCRIPTION:
+
+Provides a simple executable to drive a dzen2 (or similar) dock with a
+single YAML config file.
+
+Read +example.dock_driver.yml+ for more information about how to get
+started. It's located in this gem's data directory, which is usually:
+	$GEM_HOME/gems/dock_driver-x.y.z/data/
+
+== FEATURES/PROBLEMS:
+
+=== Features:
+
+* easy, flexible configuration
+* sholud the configuration change, the dock will be automatically restarted
+
+See 'Ideas' for more info on what's planned for the future.
+
+=== Problems:
+
+* undiscovered bugs
+* more RSpec examples needed
+* cleanup
+
+== SYNOPSIS:
+
+There really isn't an API or interface other than the YAML config file.
+
+== REQUIREMENTS:
+
+* This gem proudly has zero dependencies.
+
+== INSTALL:
+
+	sudo gem install dock_driver
+
+After that you might want to copy +example.dock_driver.yml+ from the
+gem's data directory to ~/.dock_driver.yml and edit it to taste. You can
+test your config by running +dock_driver+ directly without arguments.
+
+Finally, you'll probably want run dock_driver from your .xsession or
+~/.i3/config file. See the documentation for either on how to do that.
+
+You'll find a log file called ~/.dock_driver.log that can give you
+more information should something go horribly wrong.
+
+== DEVELOPERS:
+
+After checking out the source, run:
+
+  $ rake newb
+
+This task will install any missing dependencies, run the tests/specs,
+and generate the RDoc.
+
+== LICENSE:
+
+(The BSD 2-Clause License)
+
+Copyright (c) 2012, Michael B. Hix
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+* Redistributions of source code must retain the above copyright notice,
+  this list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+
+* Neither the name of the author/s, nor the names of the project's
+  contributors may be used to endorse or promote products derived from this
+  software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+This software includes one or more mixins under the following license:
+
+	Copyright (c) 2008-2011, Michael Granger and Mahlon E. Smith
+	All rights reserved.
+
+	Redistribution and use in source and binary forms, with or without
+	modification, are permitted provided that the following conditions are met:
+
+	* Redistributions of source code must retain the above copyright notice,
+	  this list of conditions and the following disclaimer.
+
+	* Redistributions in binary form must reproduce the above copyright notice,
+	  this list of conditions and the following disclaimer in the documentation
+	  and/or other materials provided with the distribution.
+
+	* Neither the name of the author/s, nor the names of the project's
+	  contributors may be used to endorse or promote products derived from this
+	  software without specific prior written permission.
+
+	THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+	AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+	IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+	DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+	FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+	DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+	SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+	CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+	OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+	OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+=== Authors
+
+* Michael Hix <mike@musl.org>
+

README.txt

-= dock_driver
-
-* http://hg.musl.org/projects/dock_driver/
-
-== DESCRIPTION:
-
-Provides a simple executable to drive a dzen2 (or similar) dock with a
-single YAML config file.
-
-Read +example.dock_driver.yml+ for more information about how to get
-started. It's located in this gem's data directory, which is usually:
-	$GEM_HOME/gems/dock_driver-x.y.z/data/
-
-== FEATURES/PROBLEMS:
-
-=== Features:
-
-* easy, flexible configuration
-* sholud the configuration change, the dock will be automatically restarted
-* field extraction with +scan_regex+ (no grep or pipes necessary)
-* automatic running average of data rates
-
-See 'Ideas' for more info on what's planned for the future.
-
-=== Problems:
-
-* undiscovered bugs
-* more RSpec examples needed
-* cleanup
-
-== SYNOPSIS:
-
-There really isn't an API or interface other than the YAML config file.
-
-See the +--help+ option for more information on how to use the
-+dock_config+ binary.
-
-== REQUIREMENTS:
-
-* This gem proudly has zero dependencies.
-
-== INSTALL:
-
-	sudo gem install dock_driver
-
-After that you might want to copy +example.dock_driver.yml+ from the
-gem's data directory to ~/.dock_driver.yml and edit it to taste. You can
-test your config by running +dock_driver+ directly without arguments.
-
-Finally, you'll probably want run dock_driver from your .xsession or
-~/.i3/config file. See the documentation for either on how to do that.
-
-You'll find a log file called ~/.dock_driver.log that can give you
-more information should something go horribly wrong.
-
-== DEVELOPERS:
-
-After checking out the source, run:
-
-  $ rake newb
-
-This task will install any missing dependencies, run the tests/specs,
-and generate the RDoc.
-
-== LICENSE:
-
-(The BSD 2-Clause License)
-
-Copyright (c) 2012, Michael B. Hix
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are
-met:
-
-* Redistributions of source code must retain the above copyright notice,
-  this list of conditions and the following disclaimer.
-
-* Redistributions in binary form must reproduce the above copyright notice,
-  this list of conditions and the following disclaimer in the documentation
-  and/or other materials provided with the distribution.
-
-* Neither the name of the author/s, nor the names of the project's
-  contributors may be used to endorse or promote products derived from this
-  software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
-FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-This software includes one or more mixins under the following license:
-
-	Copyright (c) 2008-2011, Michael Granger and Mahlon E. Smith
-	All rights reserved.
-
-	Redistribution and use in source and binary forms, with or without
-	modification, are permitted provided that the following conditions are met:
-
-	* Redistributions of source code must retain the above copyright notice,
-	  this list of conditions and the following disclaimer.
-
-	* Redistributions in binary form must reproduce the above copyright notice,
-	  this list of conditions and the following disclaimer in the documentation
-	  and/or other materials provided with the distribution.
-
-	* Neither the name of the author/s, nor the names of the project's
-	  contributors may be used to endorse or promote products derived from this
-	  software without specific prior written permission.
-
-	THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-	AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-	IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-	DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
-	FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-	DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-	SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-	CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-	OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-	OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-=== Authors
-
-* Michael Hix <mike@musl.org>
-
 Hoe.plugin :mercurial
 
 Hoe.spec 'dock_driver' do
-	developer 'Michael Hix', 'mike@musl.org'
-	require_ruby_version '>= 1.8.7'
-	require_rubygems_version '>= 1.8'
+    self.readme_file = 'README.rdoc'
+    self.history_file = 'History.rdoc'
+    self.extra_rdoc_files = FileList[ '*.rdoc' ]
+	self.developer 'Michael Hix', 'mike@musl.org'
+	self.require_ruby_version '>= 1.8.7'
+	self.require_rubygems_version '>= 1.8'
 end
 
+# This task is probably a bad idea but I'm doing it until the project settles
+# down.
+desc "Build Manifest.txt automatically, respecting .hgignore."
+task :manifest do
+    return unless File.exist?( '.hgignore' )
+
+    ignore = Regexp.union(
+        /^\.(hg|rvm)/,
+        *File.readlines( '.hgignore' ).map { |l| l.chomp }
+    )
+
+    manifest = []
+
+    Dir.glob( [ '**/*', '**/.*' ] ).sort.each do |file|
+        next if File.directory?( file )
+        next if file =~ ignore
+        manifest << file
+    end
+
+    File.write( 'Manifest.txt', manifest.join( "\n" ) + "\n" )
+end
+= TODO
+== Important Features
+* Color & full Dzen2 escape support. (DockDriver::Dzen2)
+* DockDriver::WorkspacePager
+* DockItem output scanning
+* DockItem data rate support
+== Ideas
+* DockItem plugins
+* Multiple Dock support
+* DockItems with rotating output
+* Notifications / Slave window support
         :default => DockDriver::USER_CONFIG_FILE.expand_path.to_s
 end
 
-self.log.debug "DockDriver command line options: %p" % [opts]
+self.log.debug "DockDriver opts: %p" % [opts]
 
 trap 'INT' do
-    self.log.debug "Recieved SIGINT. Bye."
+    self.log.debug "Recieved SIGINT."
+    DockDriver.kill
     exit 0
 end
 
 trap 'HUP' do
     self.log.debug "Recieved SIGHUP."
-    DockDriver.reload opts 
+    DockDriver.reload opts
 end
 
 DockDriver.run opts

lib/dock_driver.rb

 require 'rubygems'
 require 'loggability'
 
-require 'dock_driver/constants'    
 require 'dock_driver/mixins'
 
 require 'dock_driver/i3'
 # A namespace for the project & singleton for driving the dock.
 module DockDriver
 
-    include Constants,
-        Configurability
+    include Configurability
     extend Loggability
 
     config_key :dock_driver
     log_as :dock_driver
 
+    # Project version.
+    VERSION = '0.2.0'
+
+    # Project revision.
+    REVISION = %$Revision$
+
+    # The default location of a user's config file.
+    USER_CONFIG_FILE = Pathname( '~/.dock_driver.yml' )
+
+    # Configurability defaults.
+    CONFIG_DEFAULTS = {
+        :dock_driver => {},
+        :dock => {
+            :command => 'dzen2 -dock',
+            :items => []
+        }
+    }
+
     # The loaded config
     @config = {}
 
     @dock = Dock.new
 
     class << self
+        # The loaded config.
         attr_accessor :config
+        # When the config was last loaded.
         attr_accessor :config_loaded_at
-        attr_accessor :log_file
+        # The dock object.
         attr_accessor :dock
     end
 
     ### Configurability API
     def self::load_config( config_file )
-        self.config = Configurability::Config.load config_file, CONFIG_DEFAULTS
+        self.config = Configurability::Config.load( config_file, CONFIG_DEFAULTS )
         self.log.info "Installing config (%p)" % [self.config]
         self.config.install
         self.config_loaded_at = Time.now
         self.dock.run
     end
 
+    # Cleanly kill the dock.
+    def self::kill
+        self.dock.pipe.close
+    end
+
 end

lib/dock_driver/constants.rb

-
-require 'pathname'
-
-module DockDriver
-    module Constants
-        VERSION = '0.2.0'
-        REVISION = %$Revision$
-        USER_CONFIG_FILE = Pathname( '~/.dock_driver.yml' )
-        CONFIG_DEFAULTS = {}
-    end
-    self.extend Constants
-end

lib/dock_driver/dock.rb

 
 require 'thread'
+require 'ostruct'
+require 'erb'
 
 module DockDriver
 
+    # Manages the dock process and templating.
     class Dock
 
         extend Loggability
         log_to :dock_driver
 
+        #####################################################################
+        ### C O N F I G U R A B I L I T Y   A P I
+        #####################################################################
+
         include Configurability
-
         config_key :dock
 
-        ### Configurability
+        class << self
+            # The dock command.
+            attr_accessor :command
+            # The configuration for each DockItem.
+            attr_accessor :items
+            # The dock's output template.
+            attr_accessor :template
+        end
 
-        @items = []
-        class << self; attr_accessor :items; end
-
+        ### The default configuration for this class.
         CONFIG_DEFAULTS = {
-            :items => []
+            :items => [
+                { 'name' => 'date', 'command' => 'date', 'frequency' => 1 }
+                ],
+            :command => 'dzen2',
+            :template => '<% date %>'
         }
 
+        ### Configure the class.
         def self::configure( section )
-            if section
-                @items = section[:items] || CONFIG_DEFAULTS[:items]
+            merged    = CONFIG_DEFAULTS.merge( section )
+            @items    = merged[:items]
+            @command  = merged[:command]
+            @template = ERB.new( merged[:template] )
+        end
+
+        #####################################################################
+        ### D O C K   A P I
+        #####################################################################
+
+        # Set to true after the dock starts.
+        attr_accessor :running
+        # A hash of DockItems.
+        attr_accessor :items
+        # A hash of threads that have been created to run each DockItem.
+        attr_accessor :threads
+        # A mutex to ensure sane output.
+        attr_accessor :render_lock
+        # An IO pipe that will be or is connected to the dock program.
+        attr_writer   :pipe
+
+        ### A bog-standard constructor.
+        def initialize #:nodoc:
+            @items = {}
+            @threads = {}
+            @render_lock = Mutex.new
+        end
+
+        ### Lazily execute the dock command.
+        def pipe
+            return @pipe ||= IO.popen( self.class.command, 'r+' )
+        end
+
+        ### Listen to this dock's items, printing to the dock command when 
+        ### appropriate. 
+        def notify( obj )
+            self.render_lock.synchronize do
+                self.pipe.puts self.to_s if self.pipe
             end
         end
 
-        attr_reader :running
-        attr_accessor :threads
-        attr_accessor :render_lock
-
-        def initialize
-            @threads = {}
-            @render_lock = Mutex.new
-            # TODO - start the dock process
-        end
-
-        # Be a good listener. 
-        def observe
-            self.render_lock.synchronize do
-                # TODO - output to the dock process
-                puts self.to_s
-            end
-        end
-
-        # TODO can the run/running code be factored out to a module?
+        ### Start or restart the dock.
         def run
 
-            # If already running, kill and clear out the threads and restart
-            # the dock process
+            self.log.debug "Template: %p" % [self.class.template]
+
             if self.running
                 self.log.debug "Restarting the dock."
                 self.threads.values.map( &:kill )
-                self.threads.clear
-                # TODO restart the dock process
+                self.items.clear
+                self.pipe.close
+                self.pipe = nil
             else
                 self.log.debug "Starting the dock."
-                @running = true
+                self.running = true
             end
 
             self.log.debug "Building items."
             self.class.items.map do |item_opts|
                 item = DockItem.new( item_opts )
                 item.add_observer self
-                threads[item] = Thread.new { item.run }
+                self.items[item.name] = item
+                self.threads[item.name] = Thread.new { item.run }
             end
 
-            self.log.debug "Starting threads."
+            self.log.debug "Starting items."
             self.threads.values.map( &:run )
 
-            self.log.debug "Thread count: %d" % [Thread.list.count]
         end
 
+        ### Provide output for the dock.
         def to_s
-            # TODO - templating engine
-            # TODO - cache the template to avoid rendering unnecessarily
-            return self.threads.keys.map( &:to_s ).join( ' ' )
+            vbind = OpenStruct.new( self.items ).send( :binding )
+            return self.class.template.result( vbind ).gsub( /\s+/, ' ' )
         end
 
     end

lib/dock_driver/dock_item.rb

 module DockDriver
+
+    # Runs a command periodically and notifies an observer periodically, and
+    # provides output from the command.
     class DockItem
 
         extend Loggability
 
         include Subject
 
+        # This item's name.
         attr_accessor :name
+        # How many seconds to wait between command executions.
         attr_accessor :frequency
-        attr_accessor :var
+        # The last output from the command.
+        attr_accessor :output
+        # The command to be run.
+        attr_accessor :command
 
-        def initialize( opts = {} )
+        # A standard contstructor.
+        def initialize( opts = {} ) #:nodoc:
             self.log.debug "DockItem constructor options: %p" % [opts]
             @name = opts['name']
-            @frequency = opts['frequency'] || 1000
-            @var = 0
+            @frequency = opts['frequency'] || 1
+            @command = opts['command']
+            @output = 0
         end
 
-        # TODO can the run/running code be factored out to a module?
+        # Work for a thread to do.
         def run
             self.log.debug "%s is running." % self.name
             loop do
                 sleep self.frequency
-                self.var += 1
+                # TODO output scanning
+                self.output = `#{self.command}`
                 self.notify_observers
             end
         end
 
+        # Returns the formatted output from the command.
         def to_s
-            # TODO - templating engine
-            # TODO - cache the template to avoid rendering unnecessarily
-            return "%s: %s" % [self.name, self.var]
+            return self.output
         end
 
     end

lib/dock_driver/dzen2.rb

+module DockDriver
+    # A class to provide an api for formatting.
+    class Dzen2
+    end
+end

lib/dock_driver/i3.rb

+
+require 'socket'
+require 'pathname'
+require 'json'
+
 module DockDriver
+    # A class to provide IPC communication with i3wm.
     class I3
+        COMMAND        = 0
+        GET_WORKSPACES = 1
+        SUBSCRIBE      = 2
+        GET_OUTPUTS    = 3
+        MAGIC          = 'i3-ipc'
+
+        # ripped from include/i3/ipc.h
+        #
+        EVENT_MASK      = (1 << 31)
+        EVENT_WORKSPACE = (EVENT_MASK | 0)
+
+        def initialize( socket='~/.i3/ipc.sock' )
+            @ipc = UNIXSocket.open( Pathname( socket ).expand_path )
+        end
+
+        attr_reader :ipc
+
+        ### Send a command to i3.
+        def command( cmd )
+            self.send_data( COMMAND, cmd )
+        end
+
+        ### Return an array of current workspace state.
+        def get_workspaces
+            self.send_data( GET_WORKSPACES )
+            return self.read_data( GET_WORKSPACES )
+        end
+
+        ### Register interest in workspace change events.
+        def subscribe_workspaces
+            self.send_data( SUBSCRIBE, JSON.generate(['workspace']))
+            result = self.read_data( SUBSCRIBE )
+            return result[ 'success' ]
+        end
+
+        ### Do a blocking select, yielding an event when one is seen.
+        def wait_for_event( type=nil, timeout=nil )
+            ready = select( [self.ipc], nil, nil, timeout )
+            return if ready.nil? # timeout
+
+            msg = self.read_data( type )
+            return if msg.nil? && ! type.nil?
+
+            yield msg if block_given?
+            return msg
+        end
+
+        #########
+        protected
+        #########
+
+        ### Format a message payload and send it to i3.
+        def send_data( type, data=nil )
+            len = data.nil? ? 0 : data.to_s.bytes.count
+            body = MAGIC + [ len, type ].pack( 'LL' )
+            body << data.to_s unless data.nil?
+
+            self.ipc.write( body )
+        end
+
+        ### Receive a message payload from i3, returning it as a data structure.
+        ### Returns nil if a +type+ is specified, and the message doesn't match.
+        def read_data( expected_type=nil )
+            buf = self.ipc.read( MAGIC.bytes.count + 4 + 4 )
+            len, type = buf[6..-1].unpack( 'LL' )
+            msg = self.ipc.read( len )
+
+            return nil if ! expected_type.nil? && expected_type != type
+            return JSON.parse( msg )
+        end
     end
 end

lib/dock_driver/mixins.rb

 module DockDriver
 
-    # The Listened to.
+    # A mixin providing a simple implementation of the listener pattern.
     module Subject
 
         extend Loggability
         log_to :dock_driver
 
+        # Lazily create a list of interested objects.
         def observers
             return @observers ||= []
         end
 
+        # Register an object's interest in this Subject.
         def add_observer( obj )
             observers ||= []
-            if obj.respond_to? :observe
+            if obj.respond_to? :notify
                 self.observers << obj
             else
-                raise "Object must respond to :observe: %p" % [obj]
+                raise "Object must respond to :notify: %p" % [obj]
             end
         end
 
+        # Dispatch notification - call +notify+ on each observer.
         def notify_observers
-            return unless self.observers
             self.observers.each do |observer|
-                observer.observe
+                observer.notify( self )
             end
         end
 
     end
-    
+
 end

lib/dock_driver/workspace_pager.rb

 module DockDriver
-    class DockDriver::WorkspacePager < DockDriver::DockItem
+    # A DockItem to show and switch workspaces.
+    class WorkspacePager < DockItem
     end
 end
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.