wmiirc-rumai

git clone git://oldgit.suckless.org/wmiirc-rumai/
Log | Files | Refs | README | LICENSE

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