wmiirc-rumai

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

commit 7a47d7d7e49158d454bffce0a08fdbc43134e794
parent 9d93f7a40d8462a4e5b0576f4745aed73e10af69
Author: Suraj N. Kurapati <sunaku@gmail.com>
Date:   Sun, 10 Sep 2006 18:21:43 -0700

[project @ 0016c560d74f34ce222fb43089de0a9a8431a1a1]

[project @ 49]
mv  util methods into rc.rb
fix PROGRAM_MENU was scanning home directory!! (time waste)

Diffstat:
fs.rb | 3++-
rc.rb | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
wm.rb | 450+++++++++++++++++++++++++++++++++++++------------------------------------------
wmiirc | 15++++++++-------
4 files changed, 270 insertions(+), 249 deletions(-)

diff --git a/fs.rb b/fs.rb @@ -152,7 +152,8 @@ class IxpNode if child.file? child.write! aContent else - raise IOError, "cannot write to non-file: #{child.path}" + #raise IOError, + warn "cannot write to non-file: #{child.path}" end end diff --git a/rc.rb b/rc.rb @@ -0,0 +1,51 @@ +=begin + Copyright 2006 Suraj N. Kurapati + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +=end + +## Utility methods + +require 'find' + +# Returns a list of program names available in the given paths. +def find_programs *aPaths + aPaths.flatten! + aPaths.map! {|p| File.expand_path p} + list = [] + + Find.find(*aPaths) do |f| + if File.file?(f) && File.executable?(f) + list << File.basename(f) + end + end + + list.uniq.sort +end + +# Shows a WM menu with the given content and returns its output. +def show_menu *aContent + aContent.flatten! + output = nil + + IO.popen('wmiimenu', 'r+') do |menu| + menu.write aContent.join("\n") + menu.close_write + + output = menu.read + end + + output +end diff --git a/wm.rb b/wm.rb @@ -17,7 +17,6 @@ =end require 'fs' -require 'find' # Encapsulates a graphical region in the window manager. class Container < IxpNode @@ -96,6 +95,215 @@ class Container < IxpNode end end +# Represents a running, graphical program. +class Client < Container + def initialize *aArgs + super Area, IxpNode, *aArgs + end + + TAG_DELIMITER = "+" + + # Returns the tags associated with this client. + def tags + self['tags'].split(TAG_DELIMITER) + end + + # Modifies the tags associated with this client. + def tags= *aTags + t = aTags.flatten.uniq + self['tags'] = t.join(TAG_DELIMITER) unless t.empty? + end + + # Evaluates the given block within the context of this client's list of tags. + def with_tags &aBlock + t = self.tags + t.instance_eval(&aBlock) + self.tags = t + end + + # Checks if this client is included in the current selection. + def selected? + tags.include? Wmii::SELECTION_TAG + end + + def select! + with_tags do + unshift Wmii::SELECTION_TAG + end + end + + def unselect! + with_tags do + delete Wmii::SELECTION_TAG + end + end + + def invert_selection! + if selected? + unselect! + else + select! + end + end +end + +class Area < Container + def initialize *aArgs + super View, Client, *aArgs + end + + alias clients children + + # Inserts the given clients at the bottom of this area. + def push! *aClients + aClients.flatten! + return if aClients.empty? + + unless (list = clients).empty? + list.last.focus! + end + + insert! aClients + end + + # Inserts the given clients after the currently focused client in this area. + def insert! *aClients + aClients.flatten! + return if aClients.empty? + + dstIdx = setup_for_insertion(aClients.shift) + + aClients.each do |c| + c.ctl = "sendto #{dstIdx}" + end + end + + # Inserts the given clients at the top of this area. + def unshift! *aClients + aClients.flatten! + return if aClients.empty? + + unless (list = clients).empty? + list.first.focus! + end + + dstIdx = setup_for_insertion(aClients.shift) + parent[dstIdx].sel.ctl = 'swap up' + + aClients.each do |c| + c.ctl = "sendto #{dstIdx}" + end + end + + # Concatenates the given area to the bottom of this area. + def concat! aArea + push! aArea.clients + end + + private + # Sets up this area for insertion and returns the area ID into which insertion is performed. + def setup_for_insertion aFirstClient + dstIdx = self.index + maxIdx = parent.indices.length - 1 + + if dstIdx > maxIdx + aFirstClient.ctl = "sendto #{maxIdx}" + + parent[maxIdx].sel.ctl = "sendto next" + dstIdx = maxIdx.next + else + aFirstClient.ctl = "sendto #{dstIdx}" + end + + dstIdx + end +end + +class View < Container + def initialize *aArgs + super IxpNode, Area, *aArgs + end + + alias areas children + + # Applies wmii-2 style tiling layout to this view while maintaining its order of clients. Only the first client in the primary column is kept; all others are evicted to the *top* of the secondary column. Any subsequent columns are squeezed into the *bottom* of the secondary column. + def tile! + numAreas = self.indices.length + + if numAreas > 1 + priCol, secCol, extCol = self[1], self[2], self[3] + + # keep only the first client in primary column + priClient, *rest = priCol.clients + secCol.unshift! rest + + # squeeze extra columns into secondary column + if numAreas > 3 + (numAreas - 2).times do + secCol.concat! extCol + end + end + + secCol.mode = 'default' + # priCol.mode = 'max' + priClient.focus! + end + end + + # Applies wmii-2 style grid layout to this view while maintaining its order of clients. If the maximum number of clients per column, the distribution of clients among the columns is calculated according to wmii-2 style. Only the first client in the primary column is kept; all others are evicted to the *top* of the secondary column. Any teritiary, quaternary, etc. columns are squeezed into the *bottom* of the secondary column. + def grid! aMaxClientsPerColumn = nil + # determine client distribution + unless aMaxClientsPerColumn + numClients = self.areas[1..-1].inject(0) do |count, area| + count + area.clients.length + end + + return unless numClients > 1 + + numColumns = Math.sqrt(numClients) + aMaxClientsPerColumn = (numClients / numColumns).round + end + + # distribute the clients + if aMaxClientsPerColumn <= 0 + # squeeze all clients into a single column + areaList = self.areas + + (areaList.length - 2).times do + areaList[1].concat! areaList[2] + end + + else + i = 1 + + until i >= (areaList = self.areas).length + a = areaList[i] + + + a.mode = 'default' + clientList = a.clients + + if clientList.length > aMaxClientsPerColumn + # evict excess clients to next column + a.next.unshift! clientList[aMaxClientsPerColumn..-1] + + elsif clientList.length < aMaxClientsPerColumn + # import clients from next column + until (diff = aMaxClientsPerColumn - a.clients.length) == 0 + immigrants = a.next.clients[0...diff] + break if immigrants.empty? + + a.push! immigrants + end + end + + + i += 1 + end + end + end +end + # Ruby interface to WMII. class Wmii < Container SELECTION_TAG = 'SEL' @@ -244,244 +452,4 @@ class Wmii < Container end end end - - - ## Utility methods - - # Shows a WM menu with the given content and returns its output. - def show_menu aContent - output = nil - - IO.popen('wmiimenu', 'r+') do |menu| - menu.write aContent - menu.close_write - - output = menu.read - end - - output - end - - # Returns a list of program names available in the given paths. - def find_programs *aPaths - aPaths.map! {|p| File.expand_path p} - list = [] - - Find.find(*aPaths) do |f| - if File.executable?(f) && !File.directory?(f) - list << File.basename(f) - end - end - - list.uniq.sort - end - - - ## Subclasses for abstraction - - # Represents a running, graphical program. - class Client < Container - def initialize *aArgs - super Area, IxpNode, *aArgs - end - - TAG_DELIMITER = "+" - - # Returns the tags associated with this client. - def tags - self['tags'].split(TAG_DELIMITER) - end - - # Modifies the tags associated with this client. - def tags= *aTags - t = aTags.flatten.uniq - self['tags'] = t.join(TAG_DELIMITER) unless t.empty? - end - - # Evaluates the given block within the context of this client's list of tags. - def with_tags &aBlock - t = self.tags - t.instance_eval(&aBlock) - self.tags = t - end - - # Checks if this client is included in the current selection. - def selected? - tags.include? SELECTION_TAG - end - - def select! - with_tags do - unshift SELECTION_TAG - end - end - - def unselect! - with_tags do - delete SELECTION_TAG - end - end - - def invert_selection! - if selected? - unselect! - else - select! - end - end - end - - class Area < Container - def initialize *aArgs - super View, Client, *aArgs - end - - alias clients children - - # Inserts the given clients at the bottom of this area. - def push! *aClients - return if aClients.empty? - - unless (list = clients).empty? - list.last.focus! - end - - insert!(*aClients) - end - - # Inserts the given clients after the currently focused client in this area. - def insert! *aClients - return if aClients.empty? - - dstIdx = setup_for_insertion(aClients.shift) - - aClients.each do |c| - c.ctl = "sendto #{dstIdx}" - end - end - - # Inserts the given clients at the top of this area. - def unshift! *aClients - return if aClients.empty? - - unless (list = clients).empty? - list.first.focus! - end - - dstIdx = setup_for_insertion(aClients.shift) - parent[dstIdx].sel.ctl = 'swap up' - - aClients.each do |c| - c.ctl = "sendto #{dstIdx}" - end - end - - # Concatenates the given area to the bottom of this area. - def concat! aArea - push!(*aArea.clients) - end - - private - # Sets up this area for insertion and returns the area ID into which insertion is performed. - def setup_for_insertion aFirstClient - dstIdx = self.index - maxIdx = parent.indices.length - 1 - - if dstIdx > maxIdx - aFirstClient.ctl = "sendto #{maxIdx}" - - parent[maxIdx].sel.ctl = "sendto next" - dstIdx = maxIdx.next - else - aFirstClient.ctl = "sendto #{dstIdx}" - end - - dstIdx - end - end - - class View < Container - def initialize *aArgs - super IxpNode, Area, *aArgs - end - - alias areas children - - # Applies wmii-2 style tiling layout to this view while maintaining its order of clients. Only the first client in the primary column is kept; all others are evicted to the *top* of the secondary column. Any subsequent columns are squeezed into the *bottom* of the secondary column. - def tile! - numAreas = self.indices.length - - if numAreas > 1 - priCol, secCol, extCol = self[1], self[2], self[3] - - # keep only the first client in primary column - priClient, *rest = priCol.clients - secCol.unshift!(*rest) - - # squeeze extra columns into secondary column - if numAreas > 3 - (numAreas - 2).times do - secCol.concat! extCol - end - end - - secCol.mode = 'default' - # priCol.mode = 'max' - priClient.focus! - end - end - - # Applies wmii-2 style grid layout to this view while maintaining its order of clients. If the maximum number of clients per column, the distribution of clients among the columns is calculated according to wmii-2 style. Only the first client in the primary column is kept; all others are evicted to the *top* of the secondary column. Any teritiary, quaternary, etc. columns are squeezed into the *bottom* of the secondary column. - def grid! aMaxClientsPerColumn = nil - # determine client distribution - unless aMaxClientsPerColumn - numClients = self.areas[1..-1].inject(0) do |count, area| - count + area.clients.length - end - - return unless numClients > 1 - - numColumns = Math.sqrt(numClients) - aMaxClientsPerColumn = (numClients / numColumns).round - end - - # distribute the clients - if aMaxClientsPerColumn <= 0 - # squeeze all clients into a single column - areaList = self.areas - - (areaList.length - 2).times do - areaList[1].concat! areaList[2] - end - - else - i = 1 - - until i >= (areaList = self.areas).length - a = areaList[i] - - - a.mode = 'default' - clientList = a.clients - - if clientList.length > aMaxClientsPerColumn - # evict excess clients to next column - a.next.unshift!(*clientList[aMaxClientsPerColumn..-1]) - - elsif clientList.length < aMaxClientsPerColumn - # import clients from next column - until (diff = aMaxClientsPerColumn - a.clients.length) == 0 - pool = a.next.clients[0...diff] - break if pool.empty? - - a.push!(*pool) - end - end - - - i += 1 - end - end - end - end end diff --git a/wmiirc b/wmiirc @@ -20,13 +20,14 @@ $: << File.dirname(__FILE__) require 'wm' +require 'rc' ## WM STARTUP WM = Wmii.new -PROGRAM_MENU = WM.find_programs(*ENV['PATH'].split(':')).join("\n") -ACTION_MENU = WM.find_programs('~/dry/apps/wmii/etc/wmii-3', '~/.wmii-3').join("\n") +PROGRAM_MENU = find_programs( ENV['PATH'].squeeze(':').split(':') ) +ACTION_MENU = find_programs('~/dry/apps/wmii/etc/wmii-3', '~/.wmii-3') # terminate existing wmiirc processes sleep 1 until WM.event = "Start wmiirc\n" @@ -165,17 +166,17 @@ SHORTCUTS = { "#{MENU}i" => lambda do - action = WM.show_menu(ACTION_MENU) + action = show_menu(ACTION_MENU) system(action << '&') unless action.empty? end, "#{MENU}e" => lambda do - program = WM.show_menu(PROGRAM_MENU) + program = show_menu(PROGRAM_MENU) system(program << '&') unless program.empty? end, "#{MENU}Shift-v" => lambda do - WM.focus_view(WM.show_menu(WM.tags)) + WM.focus_view(show_menu(WM.tags)) end, # focus any client by choosing from a menu @@ -184,7 +185,7 @@ SHORTCUTS = { format "%d. [%s] %s", c.index, c.tags, c.name.downcase end - target = WM.show_menu(choices.join("\n")) + target = show_menu(choices) unless target.empty? WM.focus_client target.scan(/\d+/).first @@ -225,7 +226,7 @@ SHORTCUTS = { # +tag -tag idea from Jonas Pfenniger <http://zimbatm.oree.ch/articles/2006/06/15/wmii-3-and-ruby> "#{SEND}t" => lambda do choices = WM.tags.map {|t| [t, "+#{t}", "-#{t}"]}.flatten - target = WM.show_menu(choices.join("\n")) + target = show_menu(choices) WM.selected_clients.each do |c| c.with_tags do