commit 9fe024867d7cda55491c833d71ee93380ea5e684
parent d1be34f62c14c63615e046ecd0000c00b7b21bd4
Author: Suraj N. Kurapati <sunaku@gmail.com>
Date: Sun, 10 Sep 2006 09:07:43 -0700
[project @ 43751eb0be7477e5ec6a7f9ef860317d2820ca19]
[project @ 44]
cleaned up IXP abstraction... now multiple levels of method_missing works, and so does self[path]
add Wmii#with_selection which works wit destructive ops
add new tagging rules handling in event loop
Diffstat:
IxpNode.rb | | | 155 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------- |
Wmii.rb | | | 191 | +++++++++++++++++++++++++++++++++++++++++++------------------------------------ |
wmiirc | | | 87 | +++++++++++++++++++++++++++++++++++++++++-------------------------------------- |
3 files changed, 260 insertions(+), 173 deletions(-)
diff --git a/IxpNode.rb b/IxpNode.rb
@@ -20,81 +20,148 @@
$:.unshift File.join(File.dirname(__FILE__), 'ruby-ixp', 'lib')
require 'ixp'
-# Encapsulates access to a file/directory in the IXP file system.
-class IxpNode
- attr_reader :path
-
- def initialize aPath
- @path = aPath
-
- unless defined? @@ixp
- begin
- @@ixp = IXP::Client.new
- rescue Errno::ECONNREFUSED
- retry
- end
- end
+# Encapsulates access to the IXP file system.
+module IxpFs
+ begin
+ @@ixp = IXP::Client.new
+ rescue Errno::ECONNREFUSED
+ retry
end
# Creates a file at the given path and returns it.
- def create aPath
+ def self.create aPath
begin
@@ixp.create aPath
- IxpNode.new aPath
rescue IXP::IXPException => e
- puts "#{e.backtrace.first}: #{e}"
+ puts e, e.backtrace
end
end
# Deletes the given path.
- def remove aPath
+ def self.remove aPath
begin
@@ixp.remove aPath
rescue IXP::IXPException => e
- puts "#{e.backtrace.first}: #{e}"
+ puts e, e.backtrace
end
end
# Writes the given content to the given path.
- def write aPath, aContent
- begin
- @@ixp.open(aPath) do |f|
- f.write aContent.to_s
- end
- rescue IXP::IXPException => e
- puts "#{e.backtrace.first}: #{e}"
+ def self.write aPath, aContent
+ open(aPath) do |f|
+ f.write aContent.to_s
+ # puts '', "#{self.class}.write #{aPath}, #{aContent.inspect}", caller # if $DEBUG
end
end
# Reads from the given path and returns the content. If the path is a directory, then the names of all files in that directory are returned.
- def read aPath
- begin
- @@ixp.open(aPath) do |f|
- if f.respond_to? :next # read file-names from directory
- names = ''
+ def self.read aPath
+ open(aPath) do |f|
+ if f.is_a? IXP::Directory
+ names = ''
- while i = f.next
- names << i.name << "\n"
- end
+ while i = f.next
+ names << i.name << "\n"
+ end
- names
- else # read file contents
- f.read_all
+ names
+
+ else # read file contents
+ f.read_all
+ end
+ end
+ end
+
+ def self.file? aPath
+ open(aPath) {|f| f.instance_of? IXP::File}
+ end
+
+ def self.directory? aPath
+ open(aPath) {|f| f.instance_of? IXP::Directory}
+ end
+
+ def self.exist? aPath
+ open(aPath) {true}
+ end
+
+
+ def self.open aPath # :yields: IXP stream
+ if block_given?
+ begin
+ @@ixp.open(aPath) do |f|
+ return yield(f)
end
+ rescue IXP::IXPException
end
- rescue IXP::IXPException => e
- puts "#{e.backtrace.first}: #{e}"
+ end
+
+ nil
+ end
+end
+
+# Encapsulates access to an entry in the IXP file system.
+class IxpNode
+ attr_reader :path
+
+ def initialize aPath, aCreateIt = false
+ @path = aPath.squeeze('/')
+ create! if aCreateIt && !exist?
+ end
+
+ def create!
+ IxpFs.create @path
+ end
+
+ def remove!
+ IxpFs.remove @path
+ end
+
+ def write! aContent
+ IxpFs.write @path, aContent
+ end
+
+ def read
+ IxpFs.read @path
+ end
+
+ def file?
+ IxpFs.file? @path
+ end
+
+ def directory?
+ IxpFs.directory? @path
+ end
+
+ def exist?
+ IxpFs.exist? @path
+ end
+
+ def [] aSubPath
+ child = IxpNode.new("#{@path}/#{aSubPath}")
+
+ if child.file?
+ child.read
+ else
+ child
+ end
+ end
+
+ def []= aSubPath, aContent
+ child = IxpNode.new("#{@path}/#{aSubPath}")
+
+ if child.file?
+ child.write! aContent
+ else
+ raise IOError, "cannot write to non-file: #{child.path}"
end
end
- # Provides easy access to files contained within this file.
+ # Provides easy access to sub-nodes.
def method_missing aMeth, *aArgs
if aMeth.to_s =~ /=$/
- write "#{@path}/#{$`}", *aArgs
- elsif content = read("#{@path}/#{aMeth}")
- content
+ self[$`] = *aArgs
else
- super
+ self[aMeth]
end
end
end
diff --git a/Wmii.rb b/Wmii.rb
@@ -19,16 +19,90 @@
require 'IxpNode'
require 'find'
+# Encapsulates a graphical region in the window manager.
+class Container < IxpNode
+ def initialize aParentClass, aChildClass, *aArgs
+ @parentClass = aParentClass
+ @childClass = aChildClass
+ super(*aArgs)
+ end
+
+ # Returns a child with the given sub-path.
+ def [] *args
+ child = super
+
+ if child.respond_to? :path
+ child = @childClass.new(child.path)
+ end
+
+ child
+ end
+
+ # Returns the parent of this region.
+ def parent
+ @parentClass.new File.dirname(@path)
+ end
+
+ # Returns the index of this region in the parent.
+ def index
+ File.basename(@path).to_i
+ end
+
+ # Returns the next region in the parent.
+ def next
+ parent[self.index + 1]
+ end
+
+ # Returns a list of indices of items in this region.
+ def indices
+ if list = self.read
+ list.split.grep(/^\d+$/)
+ else
+ []
+ end
+ end
+
+ # Returns a list of items in this region.
+ def children
+ indices.map {|i| @childClass.new "#{@path}/#{i}"}
+ end
+
+ # Adds all clients in this region to the selection.
+ def select!
+ children.each do |s|
+ s.select!
+ end
+ end
+
+ # Removes all clients in this region from the selection.
+ def unselect!
+ children.each do |s|
+ s.unselect!
+ end
+ end
+
+ # Inverts the selection of clients in this region.
+ def invert_selection!
+ children.each do |s|
+ s.invert_selection!
+ end
+ end
+
+ # Puts focus on this region.
+ def focus!
+ ['select', 'view'].each do |cmd|
+ parent.ctl = "#{cmd} #{File.basename @path}"
+ end
+ end
+end
+
# Ruby interface to WMII.
-class Wmii < IxpNode
+class Wmii < Container
SELECTION_TAG = 'SEL'
DETACHED_TAG = 'status'
- attr_reader :config
-
def initialize
- super "/"
- @config = IxpNode.new('/def')
+ super IxpNode, View, '/'
end
@@ -51,7 +125,7 @@ class Wmii < IxpNode
# Returns the current set of tags.
def tags
- read('/tags').split
+ IxpFs.read('/tags').split
end
# Returns the current set of views.
@@ -133,6 +207,25 @@ class Wmii < IxpNode
View.new("/#{SELECTION_TAG}").unselect!
end
+ # Invokes the given block for each client in the selection.
+ def with_selection # :yields: client
+ return unless block_given?
+
+ oldJobs = []
+
+ loop do
+ selection = selected_clients
+ curJobs = selection.map {|c| c.index}
+
+ pending = (curJobs - oldJobs)
+ break if pending.empty?
+
+ job = pending.shift
+ yield selection.detect {|i| i.index == job}
+ oldJobs << job
+ end
+ end
+
## wmii-2 style client detaching
@@ -185,82 +278,6 @@ class Wmii < IxpNode
## Subclasses for abstraction
- # Encapsulates a graphical region and its file system properties.
- class Container < IxpNode
- def initialize aParentClass, aChildClass, *aArgs
- @parentClass = aParentClass
- @childClass = aChildClass
- super(*aArgs)
- end
-
- # Returns a child with the given sub-path.
- def [] aSubPath
- @childClass.new "#{@path}/#{aSubPath}"
- end
-
- # Returns the parent of this region.
- def parent
- @parentClass.new File.dirname(@path)
- end
-
- # Returns the index of this region in the parent.
- def index
- File.basename(@path).to_i
- end
-
- # Returns the next region in the parent.
- def next
- parent[self.index + 1]
- end
-
- # Returns the currently focused item in this region.
- def foci
- self['sel']
- end
-
- # Returns a list of indices of items in this region.
- def indices
- if list = read(@path)
- list.split.grep(/^\d+$/)
- else
- []
- end
- end
-
- # Returns a list of items in this region.
- def children
- indices.map {|i| @childClass.new "#{@path}/#{i}"}
- end
-
- # Adds all clients in this region to the selection.
- def select!
- children.each do |s|
- s.select!
- end
- end
-
- # Removes all clients in this region from the selection.
- def unselect!
- children.each do |s|
- s.unselect!
- end
- end
-
- # Inverts the selection of clients in this region.
- def invert_selection!
- children.each do |s|
- s.invert_selection!
- end
- end
-
- # Puts focus on this region.
- def focus!
- ['select', 'view'].each do |cmd|
- return if write "#{@path}/../ctl", "#{cmd} #{File.basename @path}"
- end
- end
- end
-
# Represents a running, graphical program.
class Client < Container
def initialize *aArgs
@@ -271,13 +288,13 @@ class Wmii < IxpNode
# Returns the tags associated with this client.
def tags
- read("#{@path}/tags").split(TAG_DELIMITER)
+ self['tags'].split(TAG_DELIMITER)
end
# Modifies the tags associated with this client.
def tags= *aTags
t = aTags.flatten.uniq
- write "#{@path}/tags", t.join(TAG_DELIMITER) unless t.empty?
+ self['tags'] = t.join(TAG_DELIMITER) unless t.empty?
end
# Evaluates the given block within the context of this client's list of tags.
@@ -351,7 +368,7 @@ class Wmii < IxpNode
end
dstIdx = setup_for_insertion(aClients.shift)
- parent[dstIdx].foci.ctl = 'swap up'
+ parent[dstIdx].sel.ctl = 'swap up'
aClients.each do |c|
c.ctl = "sendto #{dstIdx}"
@@ -372,7 +389,7 @@ class Wmii < IxpNode
if dstIdx > maxIdx
aFirstClient.ctl = "sendto #{maxIdx}"
- parent[maxIdx].foci.ctl = "sendto next"
+ parent[maxIdx].sel.ctl = "sendto next"
dstIdx = maxIdx.next
else
aFirstClient.ctl = "sendto #{dstIdx}"
@@ -384,7 +401,7 @@ class Wmii < IxpNode
class View < Container
def initialize *aArgs
- super Container, Area, *aArgs
+ super IxpNode, Area, *aArgs
end
alias areas children
diff --git a/wmiirc b/wmiirc
@@ -43,45 +43,32 @@ system %{xsetroot -solid '#333333'}
## WM CONFIGURATION
-WM.config.border = 2
+WM.def.border = 2
-WM.config.font = ENV['WMII_FONT']
-WM.config.selcolors = ENV['WMII_SELCOLORS']
-WM.config.normcolors = ENV['WMII_NORMCOLORS']
+WM.def.font = ENV['WMII_FONT']
+WM.def.selcolors = ENV['WMII_SELCOLORS']
+WM.def.normcolors = ENV['WMII_NORMCOLORS']
-WM.config.colmode = 'default'
-WM.config.colwidth = 0
+WM.def.colmode = 'default'
+WM.def.colwidth = 0
-TAGGING_RULES = {
- '~' => [
- 'QEMU.*',
- 'MPlayer.*',
- 'xconsole.*',
- 'alsamixer.*',
- ],
+TAGGING_RULES = [
+ [:code, /jEdit/],
+ [:chat, /gaim/, /xchat/],
+ [:gimp, /gimp/],
+ ['~', /QEMU/, /^MPlayer/, /xconsole/],
+]
- :code => [
- 'jEdit.*',
- ],
-
- :chat => [
- 'Buddy List.*', # gaim
- 'XChat.*',
- ],
-
- :gimp => ['Gimp.*'],
-
- '!' => ['.*'],
- '1' => ['.*'],
-}
-
-WM.config.rules = TAGGING_RULES.inject('') do |memo, (tag, regexps)|
- regexps.each do |re|
- memo << "/#{re}/ -> #{tag}\n"
+WM.def.rules = TAGGING_RULES.inject('') do |memo, (tag, *regexps)|
+ regexps.each do |r|
+ memo << "/#{r.source}/ -> #{tag}\n"
end
memo
-end
+end << <<EOS
+/.*/ -> !
+/.*/ -> 1
+EOS
## KEY CONFIGURATION
@@ -151,7 +138,7 @@ SHORTCUTS = {
end,
"#{LAYOUT}z" => lambda do
- WM.focused_view[0].foci.geom = '0 0 east south'
+ WM.focused_view[0].sel.geom = '0 0 east south'
end,
@@ -192,7 +179,7 @@ SHORTCUTS = {
end,
"#{MENU}Shift-v" => lambda do
- WM.focus_view(WM.show_menu(WM.read('/tags')))
+ WM.focus_view(WM.show_menu(WM.tags))
end,
# focus any client by choosing from a menu
@@ -215,19 +202,19 @@ SHORTCUTS = {
"#{SEND}#{LEFT}" => lambda do
- WM.selected_clients.each do |c|
+ WM.with_selection do |c|
c.ctl = 'sendto prev'
end
end,
"#{SEND}#{RIGHT}" => lambda do
- WM.selected_clients.each do |c|
+ WM.with_selection do |c|
c.ctl = 'sendto next'
end
end,
"#{SEND}space" => lambda do
- WM.selected_clients.each do |c|
+ WM.with_selection do |c|
c.ctl = 'sendto toggle'
end
end,
@@ -298,10 +285,12 @@ SHORTCUTS = {
end
end,
+ # wmii-2 style detaching
"#{SEND}d" => lambda do
WM.detach_selection
end,
+ # wmii-2 style detaching
"#{SEND}Shift-d" => lambda do
WM.attach_last_client
end,
@@ -385,22 +374,23 @@ end
end
end
-WM.config.grabmod = MODKEY
-WM.config.keys = SHORTCUTS.keys.join("\n")
+WM.def.grabmod = MODKEY
+WM.def.keys = SHORTCUTS.keys.join("\n")
## MINI SCRIPTS
# display time and system status in the bar
Thread.new do
- status = WM.create("/bar/status")
+ status = IxpNode.new("/bar/status", true)
status.colors = ENV['WMII_NORMCOLORS']
loop do
- uptime = `uptime`.scan(/\d+\.\d+/).join(' ')
+ upTime = `uptime`.scan(/\d+\.\d+/).join(' ')
+ diskUsage = `df -h ~`.split[-3..-1].join(' ')
5.times do
- status.data = "#{Time.now.to_s} | #{uptime}"
+ status.data = "#{Time.now.to_s} | #{upTime} | #{diskUsage}"
sleep 1
end
end
@@ -447,6 +437,19 @@ begin
Wmii::Client.new("/client/#{client}").invert_selection!
end
+ when 'CreateClient'
+ client = Wmii::Client.new("/client/#{arg}")
+ info = client[:class]
+
+ TAGGING_RULES.each do |(tag, *regexps)|
+ regexps.each do |r|
+ if info =~ r
+ client.tags = tag
+ raise
+ end
+ end
+ end rescue nil
+
when 'Key'
SHORTCUTS[arg].call
end