commit 75fb28c290e3d318a028e0c01545995eeb82747f
parent 6fdafbe03c6cdc15f8f4cd1f70b2003f846f5df4
Author: Suraj N. Kurapati <sunaku@gmail.com>
Date: Sun, 25 Feb 2007 16:13:56 -0800
move bare wmii-ruby interface into wmii-irb project
Diffstat:
.hgignore | | | 2 | -- |
fs.rb | | | 130 | ------------------------------------------------------------------------------- |
rc.rb | | | 191 | ------------------------------------------------------------------------------- |
wm.rb | | | 579 | ------------------------------------------------------------------------------- |
wmiish | | | 72 | ------------------------------------------------------------------------ |
5 files changed, 0 insertions(+), 974 deletions(-)
diff --git a/.hgignore b/.hgignore
@@ -1,2 +0,0 @@
-(^|/)\.svn($|/)
-(^|/)\.hg($|/)
diff --git a/fs.rb b/fs.rb
@@ -1,130 +0,0 @@
-# Abstractions for wmii's {IXP file system}[http://wmii.de/contrib/guide/wmii-3/guide-en/guide_en/node9.html] interface.
-=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
-
-$:.unshift File.join(File.dirname(__FILE__), 'ruby-ixp', 'lib')
-require 'ixp'
-
-# Encapsulates access to the IXP file system.
-module Ixp
- Client = IXP::Client.new
-
- # An entry in the IXP file system.
- class Node
- attr_reader :path
-
- # Obtains the IXP node at the given path. Unless it already exists, the given path is created when aCreateIt is asserted.
- def initialize aPath, aCreateIt = false
- @path = aPath.to_s.squeeze('/')
- create! if aCreateIt && !exist?
- end
-
- # Open this node for IO operation.
- def open *aArgs, &aBlock # :yields: IO
- Client.open @path, *aArgs, &aBlock
- end
-
- # Creates this node.
- def create!
- Client.create @path
- end
-
- # Deletes this node.
- def remove!
- Client.remove @path
- end
-
- # Writes the given content to this node.
- def write! aContent
- Client.write @path, aContent
- end
-
- # Returns the contents of this node or the names of all entries if this is a directory.
- def read
- cont = Client.read(@path)
-
- if cont.respond_to? :to_ary
- cont.map {|stat| stat.name}
- else
- cont
- end
- end
-
- # Tests if this node is a file.
- def file?
- Client.file? @path
- end
-
- # Tests if this node is a directory.
- def directory?
- Client.directory? @path
- end
-
- # Tests if this node exists in the file system.
- def exist?
- Client.exist? @path
- end
-
- # Returns the basename of this file's path.
- def basename
- File.basename @path
- end
-
- # Returns the dirname of this file's path.
- def dirname
- File.dirname @path
- end
-
- # Accesses the given sub-path and dereferences it (reads its contents) if specified.
- def [] aSubPath, aDeref = false
- child = Node.new("#{@path}/#{aSubPath}")
-
- if aDeref
- child.read
- else
- child
- end
- end
-
- # Writes the given content to the given sub-path.
- def []= aSubPath, aContent
- self[aSubPath].write! aContent
- end
-
- # Provides access to sub-nodes through method calls.
- #
- # :call-seq:
- # node.child = value -> value
- # node.child (tree) -> Node
- # node.child (leaf) -> child.read
- #
- def method_missing aMeth, *aArgs
- case aMeth.to_s
- when /=$/
- self[$`] = *aArgs
-
- else
- if (n = self[aMeth]).file?
- n.read
- else
- n
- end
- end
- end
- end
-end
diff --git a/rc.rb b/rc.rb
@@ -1,191 +0,0 @@
-# Utility methods used by wmiirc.
-=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
-
-require 'wm'
-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!
- list.sort!
- list
-end
-
-# Shows a menu with the given items and returns the chosen item. If nothing was chosen, *nil* is returned.
-def show_menu *aChoices
- IO.popen('wmiimenu', 'r+') do |menu|
- menu.puts aChoices
- menu.close_write
-
- if (choice = menu.read).empty?
- nil
- else
- choice
- end
- end
-end
-
-# Focuses the client chosen from a menu.
-def focus_client_from_menu
- choices = Wmii.clients.map do |c|
- format "%d. [%s] %s", c.index, c.tags, c.name.downcase
- end
-
- if target = show_menu(choices)
- Wmii.focus_client target.scan(/\d+/).first
- end
-end
-
-# Changes the tag, chosen from a menu, of each selected client and returns the chosen tag.
-# The {+tag -tag idea}[http://zimbatm.oree.ch/articles/2006/06/15/wmii-3-and-ruby] is from Jonas Pfenniger.
-def change_tag_from_menu
- choices = Wmii.tags.map {|t| [t, "+#{t}", "-#{t}"]}.flatten
-
- if target = show_menu(choices)
- Wmii.selected_clients.each do |c|
- case target
- when /^\+/
- c.tag! $'
-
- when /^\-/
- c.untag! $'
-
- else
- c.tags = target
- end
- end
-
- target
- end
-end
-
-# Send selected clients to temporary view or switch back again.
-def toggle_temp_view
- curTag = Wmii.current_view.name
-
- if curTag =~ /~\d+$/
- Wmii.selected_clients.each do |c|
- c.with_tags do
- delete curTag
- push $` if empty?
- end
- end
-
- Wmii.focus_view $`
-
- else
- tmpTag = "#{curTag}~#{Time.now.to_i}"
-
- Wmii.selected_clients.each do |c|
- c.tag! tmpTag
- end
-
- Wmii.focus_view tmpTag
- Wmii.current_view.grid!
- end
-end
-
-# Puts focus on an adjacent view (:left or :right).
-def cycle_view aTarget
- tags = Wmii.tags
- curTag = Wmii.current_view.name
- curIndex = tags.index(curTag)
-
- newIndex =
- case aTarget
- when :right
- curIndex + 1
-
- when :left
- curIndex - 1
-
- else
- return
-
- end % tags.length
-
- Wmii.focus_view tags[newIndex]
-end
-
-# Toggles maximization of the currently focused client.
-def toggle_maximize
- src = Wmii.current_client
- srcId = src.index
-
- src.ctl = 'sendto toggle'
- dst = Wmii.current_view[0].sel
-
- if dst.exist? && dst.index == srcId
- dst.geom = '0 0 east south'
- end
-end
-
-# Focuses the view whose name matches the given pattern. If more than one view matches, then they are cycled (adapted from Fredrik Ternerot).
-def focus_view_matching aPattern
- choices = Wmii.tags.grep(aPattern)
-
- unless choices.empty?
- curIdx = choices.index(Wmii.current_view.name)
- maxIdx = choices.length
-
- idx = curIdx.next % maxIdx rescue 0
-
- Wmii.focus_view choices[idx]
- end
-end
-
-# Numbers the view buttons displayed on the bar, from left to right.
-def number_view_buttons
- bar = Wmii.fs.bar
-
- bar.read.each_with_index do |b, i|
- bar[b].data = "#{i + 1}:#{b}"
- end
-end
-
-
-## wmii-2 style client detaching
-
-DETACHED_TAG = 'status'
-
-# Detach the current selection.
-def detach_selection
- Wmii.selected_clients.each do |c|
- c.tags = DETACHED_TAG
- end
-end
-
-# Attach the most recently detached client
-def attach_last_client
- if a = Wmii.get_view(DETACHED_TAG).areas.last
- if c = a.clients.last
- c.tags = Wmii.current_view.name
- end
- end
-end
diff --git a/wm.rb b/wm.rb
@@ -1,579 +0,0 @@
-# Abstractions for the window manager.
-=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
-
-require 'fs'
-
-# Encapsulates access to the window manager.
-module Wmii
- ## state access
-
- # Returns the root of IXP file system hierarchy.
- def Wmii.fs
- Ixp::Node.new '/'
- end
-
- # Returns the currently focused client.
- def Wmii.current_client
- Client.new("/view/sel/sel")
- end
-
- # Returns the currently focused area.
- def Wmii.current_area
- Area.new("/view/sel")
- end
-
- # Returns the currently focused view.
- def Wmii.current_view
- View.new("/view")
- end
-
- # Returns the current set of tags.
- def Wmii.tags
- Ixp::Client.read('/tags').split
- end
-
- # Returns the current set of views.
- def Wmii.views
- tags.map {|v| get_view v}
- end
-
- # Returns the current set of clients.
- def Wmii.clients
- Area.new("/client").clients
- end
-
- # Returns the client which has the given ID.
- def Wmii.get_client aId
- Client.new("/client/#{aId}")
- end
-
- # Returns the view which has the given name.
- def Wmii.get_view aName
- View.new("/#{aName}")
- end
-
- # Searches for a client, which has the given ID, in the given places. If no places are specified, then all views are searched. If the client is not found, *nil* is returned.
- def Wmii.find_client aClientId, *aPlaces
- aClientId = aClientId.to_i
- needle = Wmii.get_client(aClientId)
-
- if needle.exist?
- haystack = []
-
- # populate the haystack (places to be searched)
- aPlaces.select {|p| p.exist?}.each do |place|
- if place.respond_to? :clients
- haystack << place
- end
-
- if place.respond_to? :areas
- haystack.concat place.areas
- end
- end
-
- if haystack.empty?
- needle.tags.map {|t| get_view t}.each do |v|
- haystack.concat v.areas
- end
- end
-
- haystack.each do |a|
- if a.indices.detect {|i| i == aClientId}
- return a[aClientId]
- end
- end
- end
-
- puts "could not find client #{aClientId} in area #{aArea.inspect} or view #{aView.inspect}" if $DEBUG
-
- nil
- end
-
-
- ## state manipulation
-
- # Focuses the view with the given name.
- def Wmii.focus_view aName
- get_view(aName).focus!
- end
-
- # Focuses the area with the given ID in the current view.
- def Wmii.focus_area aId
- Wmii.current_view[aId].focus!
- end
-
- # Focuses the client which has the given ID.
- def Wmii.focus_client aId
- if c = find_client(aId)
- v = (a = c.parent).parent
-
- v.focus!
- a.focus!
- c.focus!
- end
- end
-
-
- ## Multiple client selection
-
- SELECTION_TAG = 'SEL'
-
- # Returns a list of all selected clients in the currently focused view. If there are no selected clients, then the currently focused client is returned in the list.
- def Wmii.selected_clients
- list = current_view.areas.map do |a|
- a.clients.select {|c| c.selected?}
- end
- list.flatten!
-
- if list.empty?
- list << current_client
- end
-
- list
- end
-
- # Un-selects all selected clients so that there is nothing selected.
- def Wmii.select_none!
- get_view(SELECTION_TAG).unselect!
- end
-
-
- ## subclasses for abstraction
-
- # A region in the window manager's hierarchy.
- class Node < Ixp::Node
- def initialize aParentClass, aChildClass, aFocusCommand, *aArgs
- @parentClass = aParentClass
- @childClass = aChildClass
- @focusCmd = aFocusCommand
- 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
- basename.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
- read.grep(/^\d+$/).map {|s| s.to_i} rescue []
- end
-
- # Returns the number of items in this region.
- def length
- indices.length
- 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!
- parent.ctl = "#{@focusCmd} #{basename}"
- end
- end
-
- class Client < Node
- def initialize *aArgs
- super Area, Ixp::Node, :select, *aArgs
- end
-
- def index
- self[:index, true].to_i
- end
-
- TAG_DELIMITER = "+"
-
- # Returns the tags associated with this client.
- def tags
- self[:tags, true].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
-
- # Adds the given tags to this client.
- def tag! *aTags
- with_tags do
- push(*aTags.flatten.map {|t| t.to_s})
- end
- end
-
- # Removes the given tags from this client.
- def untag! *aTags
- with_tags do
- aTags.flatten.each do |tag|
- delete tag.to_s
- end
- end
- 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!
- untag! SELECTION_TAG
- end
-
- def invert_selection!
- if selected?
- unselect!
- else
- select!
- end
- end
- end
-
- class Area < Node
- def initialize *aArgs
- super View, Client, :select, *aArgs
- end
-
- alias clients children
-
- # Tests if this area is empty (has no clients).
- def empty?
- length < 1
- end
-
- # Inserts the given clients at the bottom of this area.
- def push! *aClients
- if target = clients.last
- target.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?
-
- setup_for_insertion! aClients.shift
-
- dst = self.index
- aClients.each do |c|
- c.ctl = "sendto #{dst}"
- end
- end
-
- # Inserts the given clients at the top of this area.
- def unshift! *aClients
- aClients.flatten!
- return if aClients.empty?
-
- if target = clients.first
- target.focus!
- end
-
- setup_for_insertion! aClients.shift
-
- if top = clients.first
- top.ctl = 'swap down'
- end
-
- dst = self.index
- aClients.each do |c|
- c.ctl = "sendto #{dst}"
- end
- end
-
- # Concatenates the given area to the bottom of this area.
- def concat! aArea
- push! aArea.clients
- end
-
- # Ensures that this area has at most the given number of clients. Areas to the right of this one serve as a buffer into which excess clients are evicted and from which deficit clients are imported.
- def length= aMaxClients
- return if aMaxClients < 0
- len = self.length
-
- if len > aMaxClients
- self.next.unshift! clients[aMaxClients..-1]
-
- elsif len < aMaxClients
- until (diff = aMaxClients - length) == 0
- immigrants = self.next.clients[0...diff]
- break if immigrants.empty?
-
- push! immigrants
- end
- end
- end
-
-
- private
-
- # Updates the path of this area for proper insertion and inserts the given client.
- def setup_for_insertion! aFirstClient
- raise ArgumentError, 'nonexistent client' unless aFirstClient.exist?
-
- dstIdx = self.index
- maxIdx = parent.indices.last
-
- if dstIdx > maxIdx
- # move *near* final destination
- clientId = aFirstClient.index
- aFirstClient.ctl = "sendto #{maxIdx}"
-
- # recalculate b/c sendto can be destructive
- maxIdx = parent.indices.last
- maxCol = parent[maxIdx]
-
- aFirstClient = Wmii.find_client(clientId, maxCol)
-
- # move *into* final destination
- if maxCol.indices.length > 1
- aFirstClient.ctl = "sendto next"
- dstIdx = maxIdx + 1
- else
- dstIdx = maxIdx
- end
-
- @path = "#{dirname}/#{dstIdx}"
-
- else
- aFirstClient.ctl = "sendto #{dstIdx}"
- end
- end
- end
-
- class View < Node
- def initialize *aArgs
- super Ixp::Node, Area, :view, *aArgs
- end
-
- alias areas children
-
- # Tests if this view is empty (has no clients).
- def empty?
- length < 3 &&
- self[0].length < 1 &&
- self[1].length < 1
- end
-
- # Iterates over columns in this view such that destructive operations are supported. If specified, the iteration starts with the column which has the given index.
- # Note that the floating area is not considered to be a column.
- def each_column aStartIdx = 1 # :yields: area
- return unless block_given?
-
- if (i = aStartIdx.to_i) < 1
- i = 1
- end
-
- until i >= (areaList = self.areas).length
- yield areaList[i]
- i += 1
- end
- end
-
- # Arranges the clients in this view, while maintaining their relative order, in the tiling fashion of LarsWM. 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!
- priCol, secCol, extCol = self[1], self[2], self[3]
-
- unless priCol.empty?
- # keep only the first client in primary column
- priClient, *rest = priCol.clients
- secCol.unshift! rest
-
- # squeeze extra columns into secondary column
- if (numAreas = self.indices.length) > 3
- (numAreas - 2).times do
- secCol.concat! extCol
- end
- end
-
- secCol.mode = :default
- #priCol.mode = :max
- priClient.focus!
- end
- end
-
- # Arranges the clients in this view, while maintaining their relative order, in a (at best) square grid.
- def grid! aMaxClientsPerColumn = nil
- # determine client distribution
- unless aMaxClientsPerColumn
- numClients = num_grounded_clients
- 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
- each_column do |a|
- a.length = aMaxClientsPerColumn
- a.mode = :default
- end
- end
- end
-
- # Arranges the clients in this view, while maintaining their relative order, in a (at best) equilateral triangle. However, the resulting arrangement appears like a diamond because wmii does not waste screen space.
- def diamond!
- if (numClients = num_grounded_clients) > 0
- subtriArea = numClients / 2
- crestArea = numClients % subtriArea
-
- # build fist sub-triangle upwards
- height = area = 0
- lastCol = nil
-
- each_column do |col|
- if area < subtriArea
- height += 1
-
- col.length = height
- area += height
-
- col.mode = :default
- lastCol = col
- else
- break
- end
- end
-
- # build crest of overall triangle
- if crestArea > 0
- lastCol.length = height + crestArea
- end
-
- # build second sub-triangle downwards
- each_column(lastCol.index + 1) do |col|
- if area > 0
- col.length = height
- area -= height
-
- height -= 1
- else
- break
- end
- end
- end
- end
-
-
- private
-
- # Returns the number of clients in the non-floating areas of this view.
- def num_grounded_clients
- if ground = areas[1..-1]
- ground.inject(0) do |count, area|
- count + area.length
- end
- else
- 0
- end
- end
- end
-end
-
-class Array
- alias original_each each
-
- # Supports destructive operations on each client in this array.
- def each
- if block_given?
- original_each do |c|
- # resolve stale paths caused by destructive operations
- if c.is_a?(Wmii::Client) && !c.exist?
- puts "\n trying to resolve nonexistent client: #{c.path}" if $DEBUG
-
- c = Wmii.find_client(c.basename, Wmii.current_view)
- next unless c
-
- puts "resolution OK: #{c.path}" if $DEBUG
- end
-
- yield c
- end
- end
- end
-end
diff --git a/wmiish b/wmiish
@@ -1,72 +0,0 @@
-#!/usr/bin/ruby -w
-# This is an interactive Ruby shell for wmii.
-# - Press the TAB key for command completion.
-
-=begin
- Copyright 2004 Joel VanderWerf
- 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
-
-
-## This code was adapted from ruby-talk:99201.
-
-require 'irb'
-require 'irb/completion'
-
-module IRB
- # Starts IRB within the context of the given object.
- def IRB.start_session aContextObj
- IRB.setup __FILE__ unless defined? $irb
-
- ws = WorkSpace.new(aContextObj)
-
- $irb = if @CONF[:SCRIPT] # normally, set by parse_opts
- Irb.new ws, @CONF[:SCRIPT]
- else
- Irb.new ws
- end
-
- @CONF[:IRB_RC].call($irb.context) if @CONF[:IRB_RC]
- @CONF[:MAIN_CONTEXT] = $irb.context
-
- trap 'INT' do
- $irb.signal_handle
- end
-
- custom_configuration if defined? IRB.custom_configuration
-
- catch :IRB_EXIT do
- $irb.eval_input
- end
- end
-end
-
-class Object
- include IRB::ExtendCommandBundle # so that Marshal.dump works
-end
-
-
-if __FILE__ == $0
- # show usage info
- require 'rdoc/usage'
- RDoc::usage_no_exit
-
- $:.unshift File.dirname(__FILE__)
- require 'rc'
-
- IRB.start_session Wmii
-end