commit 33cc57a5cc195c9be03bb2c2f175742aaf6f9080
parent ce625a6c2b450fb0031d78798cbdff39c28de30d
Author: Suraj N. Kurapati <sunaku@gmail.com>
Date: Sun, 6 Aug 2006 18:37:13 -0700
[project @ 257c6e031706f24bb4c744a565952303ce96a630]
[project @ 3]
import Ruby based wmiirc integrated with Ruby-IXP library
Diffstat:
11 files changed, 1763 insertions(+), 94 deletions(-)
diff --git a/ruby-ixp/COPYING b/ruby-ixp/COPYING
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ 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.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/ruby-ixp/examples/benchmark.rb b/ruby-ixp/examples/benchmark.rb
@@ -0,0 +1,70 @@
+=begin
+Ruby-IXP, Copyright 2006 Stephan Maka
+
+This file is part of Ruby-IXP.
+
+Ruby-IXP 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.
+
+Ruby-IXP 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 Ruby-IXP; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA)
+=end
+$:.unshift File::dirname(__FILE__) + '/../lib'
+require 'ixp'
+
+require 'benchmark'
+begin
+ require 'rubygems'
+rescue LoadError
+ $: << '/usr/local/lib/ruby/gems/1.8/gems/rstyx-0.2.0/lib/'
+end
+require 'rstyx'
+
+
+FILE = '/def/rules'
+N = 1000
+
+def do_ruby_ixp(c)
+ c.open(FILE) { |f| f.read_all }
+end
+
+def do_wmiir
+ IO::popen("wmiir read #{FILE}") { |io| io.readlines.to_s }
+end
+
+module RStyx
+ module Client
+ class UNIXConnection < Connection
+ def initialize(path, user='')
+ @path = path
+ super(user)
+ end
+ def startconn
+ UNIXSocket.new(@path)
+ end
+ end
+ end
+end
+
+def do_rstyx(c)
+ c.open(FILE) { |f| f.read }
+end
+
+ruby_ixp_client = IXP::Client.new
+rstyx_client = RStyx::Client::UNIXConnection.new(ENV['WMII_ADDRESS'].gsub(/^unix!/, ''))
+
+
+Benchmark::bmbm(10) do |b|
+ b.report('ruby_ixp:') { N.times { do_ruby_ixp(ruby_ixp_client) } }
+ b.report('wmiir:') { N.times { do_wmiir } }
+ b.report('rstyx:') { N.times { do_rstyx(rstyx_client) } }
+end
+
diff --git a/ruby-ixp/examples/wmiir.rb b/ruby-ixp/examples/wmiir.rb
@@ -0,0 +1,64 @@
+=begin
+Ruby-IXP, Copyright 2006 Stephan Maka
+
+This file is part of Ruby-IXP.
+
+Ruby-IXP 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.
+
+Ruby-IXP 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 Ruby-IXP; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA)
+=end
+
+$:.unshift File::dirname(__FILE__) + '/../lib'
+require 'ixp'
+
+
+if ARGV.size != 2
+ puts "Usage: #{$0} {read|write|create|remove} <path>"
+ exit
+end
+
+cmd, path = ARGV
+c = IXP::Client.new
+
+case cmd
+ when 'read'
+ c.open(path) { |f|
+ if f.kind_of? IXP::Directory
+ ents = []
+ while ent = f.next
+ ents << ent
+ end
+ ents.sort { |a,b| a.name <=> b.name }.each { |ent|
+ puts '%s %s %s %5u %s %s' %
+ [ent.mode_str, ent.uid, ent.gid,
+ ent.length, Time.at(ent.mtime).ctime, ent.name]
+ }
+ else
+ while buf = f.read
+ print buf
+ end
+ end
+ }
+ when 'write'
+ c.open(path) { |f|
+ while buf = $stdin.gets
+ f.write(buf)
+ end
+ }
+ when 'create'
+ c.create(path)
+ when 'remove'
+ c.remove(path)
+ else
+ puts "Error: unknown command #{cmd}"
+end
diff --git a/ruby-ixp/examples/wmiirc-astro b/ruby-ixp/examples/wmiirc-astro
@@ -0,0 +1,321 @@
+#!/usr/bin/env ruby
+=begin
+Ruby-IXP, Copyright 2006 Stephan Maka
+
+This file is part of Ruby-IXP.
+
+Ruby-IXP 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.
+
+Ruby-IXP 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 Ruby-IXP; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA)
+=end
+
+$:.unshift File::dirname(__FILE__) + '/../lib'
+require 'wmii'
+
+COLORS_BLUE = '#ffffff #285577 #4c7899'
+COLORS_RED = '#ffffff #993308 #b9582c'
+COLORS_GREEN = '#ffffff #287755 #4c9978'
+COLORS_BLACK = '#a0a0b0 #200000 #7f7f7f'
+COLORS_GREY = '#7fff7f #7f7f7f #a0a0b0'
+
+Thread.abort_on_exception = true
+wmii = WMII::new
+# Startdelay
+wmii.write('/event', "Start wmiirc\n")
+wmii.border = 2
+wmii.normcolors = '#ffffff #285577 #4c7899'
+wmii.selcolors = '#ffffff #993308 #b9582c'
+#wmii.env_font = wmii.font = '-*-arial-*-r-*-*-12-*-*-*-*-*-*-*'
+#wmii.env_font = wmii.font = '-*-cure-*-r-*-*-*-*-*-*-*-*-*-*'
+wmii.env_font = wmii.font = '-*-gelly-*-r-*-*-*-*-*-*-*-*-*-*'
+wmii.env_normcolors = '#a0a0b0 #200000 #7f7f7f'
+wmii.env_selcolors = '#7fff7f #7f7f7f #a0a0b0'
+wmii.write('/def/colmode', 'default')
+wmii.write('/def/colwidth', '0')
+# Mouse-Moving/Resizing
+wmii.write('/def/grabmod', 'Mod1')
+
+# Rules
+wmii.write '/def/rules', <<__RULES__
+/XMMS.*/ -> ~
+/Gimp.*/ -> ~
+/MPlayer.*/ -> ~
+/Gajim.*/ -> ~
+/.*/ -> !
+/.*/ -> 1
+__RULES__
+
+# Layout
+wmii.on_key('Control-Left') { wmii.write('/view/ctl', 'select prev') }
+wmii.on_key('Control-Right') { wmii.write('/view/ctl', 'select next') }
+wmii.on_key('Control-Up') { wmii.write('/view/sel/ctl', 'select prev') }
+wmii.on_key('Control-Down') { wmii.write('/view/sel/ctl', 'select next') }
+wmii.on_key('Control-Shift-Left') { wmii.write('/view/sel/sel/ctl', 'sendto prev') }
+wmii.on_key('Control-Shift-Right') { wmii.write('/view/sel/sel/ctl', 'sendto next') }
+wmii.on_key('Control-Shift-Up') { wmii.write('/view/sel/sel/ctl', 'swap up') }
+wmii.on_key('Control-Shift-Down') { wmii.write('/view/sel/sel/ctl', 'swap down') }
+
+wmii.on_key('Control-Shift-d') { wmii.write('/view/sel/mode', 'default') }
+wmii.on_key('Control-Shift-s') { wmii.write('/view/sel/mode', 'stack') }
+#wmii.on_key('Control-Shift-m') { wmii.write('/view/sel/mode', 'max') }
+wmii.on_key('Control-Shift-k') { wmii.write('/view/sel/sel/ctl', 'kill') }
+
+# Tags
+10.times { |i|
+ wmii.on_key("Mod1-F#{i + 1}") { wmii.go_view(i + 1) }
+ wmii.on_key("Control-F#{i + 1}") { wmii.write('/view/sel/sel/tags', "#{i + 1}") }
+ wmii.on_barclick("#{i + 1}") { wmii.go_view(i + 1) }
+}
+=begin
+wmii.on_key("XF86Back") {
+ current_view = wmii.read('/view/name')
+ views = wmii.read('/view').scan(/ (\d+)\n/m).flatten.select{|v|v!='0'}
+ prev_view = (views.index(current_view).to_i - 1) % views.size
+ p views
+ p prev_view
+ wmii.go_view views[prev_view]
+}
+wmii.on_key("XF86Forward") {
+ current_view = wmii.read('/view/name')
+ views = wmii.read('/view').scan(/ (\d+)\n/m).flatten
+ next_view = (views.index(current_view).to_i + 1) % views.size
+ wmii.go_view views[next_view]
+}
+=end
+
+# Floating windows
+wmii.on_key('Control-space') { wmii.write('/view/ctl', 'select toggle') }
+wmii.on_key('Control-Shift-space') { wmii.write('/view/sel/sel/ctl', 'sendto toggle') }
+
+# Progmenus
+wmii.on_key('Control-m') {
+ prog = wmii.menu(%w(firefox liferea gajim gimp beep aumix psi tkabber dillo 1024x768 suspend air.sin restart))
+ if prog
+ system case prog
+ when 'firefox' then 'firefox -P default &'
+ when 'beep' then 'beep-media-player &'
+ when '1024x768' then 'xrandr -s 1024x768'
+ when 'suspend' then 'xtrlock & sleep 2 && sudo /root/suspend.sh'
+ when 'air.sin' then 'urxvt -e sh -c "sudo /root/air_sin.sh; zsh" &'
+ when 'restart' then
+ Thread.list.each { |t| t.kill unless t == Thread.current }
+ "ruby #{__FILE__}"
+ else "#{prog} &"
+ end
+ end
+}
+wmii.on_key('Control-Shift-m') {
+ progs = []
+ ENV['PATH'].split(':').each { |path|
+ begin
+ progs += Dir.entries(path).select{|f|f[0..0]!='.'}
+ rescue Errno::ENOENT
+ end
+ }
+ prog = wmii.menu(progs.sort{|a,b|a.downcase<=>b.downcase})
+ system("#{prog} &") if prog
+}
+
+# Shortcuts
+wmii.on_key('Mod1-Return') { system("urxvt &") }
+wmii.on_key('Control-Shift-g') { system("gajim-remote show_next_unread &") }
+
+# Gajim menu
+wmii.on_key('Control-Shift-j') {
+ contacts = {}
+ IO::popen('gajim-remote list_contacts') {|io|
+ jid = nil
+ name = nil
+ while line = io.gets
+ line.strip!
+
+ if line =~ /^jid +: (.+)/
+ jid = $1
+ elsif line =~ /^name +: (.+)/
+ name = $1
+ elsif line =~ /^show +:/
+ contacts[jid] = name
+ end
+ end
+ }
+
+ contact = wmii.menu(contacts.collect{|j,n| (n ? n : j).downcase }.sort)
+ if contact
+ jid = nil
+ contacts.each { |j,n|
+ if (n ? n : j).downcase == contact
+ jid = j
+ end
+ }
+ system("gajim-remote open_chat \"#{jid}\"") if jid
+ end
+}
+
+# Bars
+
+bar_interfaces = wmii.new_bar('65interfaces')
+bar_interfaces.colors = COLORS_BLUE
+interfaces_up_old = {}
+interfaces_down_old = {}
+bar_interfaces.periodic(1) {
+ interfaces_up = {}
+ interfaces_down = {}
+ interfaces = []
+
+ `ifconfig -a`.split(/\n/).each do |line|
+ line.chomp!
+ if line =~ /^([a-z]+\d+): flags=\d+<(.+)> mtu \d+/
+ name = $1
+ up = $2.split(',').index('UP')
+ interfaces << name if up and name != 'lo0'
+ end
+ end
+
+ interfaces.each { |name|
+ `netstat -bnd -I #{name}`.scan(/<Link#\d+> +[0-9a-f:]+ +\d+ +\d+ +(\d+) +\d+ +\d+ +(\d+)/) { |ibytes,obytes|
+ interfaces_down[name] = ibytes.to_i
+ interfaces_up[name] = obytes.to_i
+ }
+ }
+
+ formatter = lambda { |l|
+ units = [''] + %w(K M G)
+ u = 0
+ while l > 10240 and u < units.size
+ l /= 1024
+ u += 1
+ end
+ "#{l}#{units[u]}B"
+ }
+
+ down_total = 0
+ up_total = 0
+ s = interfaces.collect { |name|
+ down = interfaces_down[name] - (interfaces_down_old[name] || 0)
+ down_total += down
+ up = interfaces_up[name] - (interfaces_up_old[name] || 0)
+ up_total += up
+ "#{name} (#{formatter.call down} + #{formatter.call up})"
+ }.join(', ')
+
+ if down_total > up_total
+ bar_interfaces.colors = COLORS_GREEN
+ elsif up_total > down_total
+ bar_interfaces.colors = COLORS_RED
+ else
+ bar_interfaces.colors = COLORS_BLUE
+ end
+
+ interfaces_down_old = interfaces_down
+ interfaces_up_old = interfaces_up
+
+ s
+}
+
+
+cpustates_old = []
+bar_stats = wmii.new_bar('70stats')
+bar_stats.periodic(2) {
+ cpustates_label = []
+ if `sysctl kern.cp_time` =~ /kern\.cp_time: (.+)\n$/
+ cpustates = $1.split(' ').collect {|v|v.to_i}
+ delta = []
+ cpustates.each_with_index{|v,i| delta[i] = v - (cpustates_old[i]||0)}
+ cpustates_old = cpustates
+ delta_tot = 0
+ delta.each{|v| delta_tot += v }
+ delta_tot = 1 if delta_tot == 1 # Avoid / 0
+ delta.each_with_index { |v,i|
+ cpustates_label << case i
+ when 0 then 'user'
+ when 1 then 'nice'
+ when 2 then 'sys'
+ when 3 then 'intr'
+ when 4 then 'idle'
+ end + ":#{(v * 100 / delta_tot).to_s.rjust(3)}%"
+ }
+
+ if delta[2] * 100 / delta_tot > 60
+ bar_stats.colors = COLORS_BLACK
+ elsif delta[0] * 100 / delta_tot > 60
+ bar_stats.colors = COLORS_RED
+ elsif delta[1] * 100 / delta_tot > 60
+ bar_stats.colors = COLORS_GREEN
+ else
+ bar_stats.colors = COLORS_BLUE
+ end
+ end
+
+ cpustates_label.join(' ')
+}
+
+bar_load = wmii.new_bar('80load')
+bar_load.colors = COLORS_BLUE
+bar_load.periodic(5) {
+ if `uptime` =~ /load averages: (.+)\n$/
+ $1
+ else
+ 'Bogus output from "uptime"'
+ end
+}
+
+if `sysctl dev.cpu.0.freq` != '' and `sysctl hw.acpi.thermal.tz0.temperature` != ''
+ bar_cpu = wmii.new_bar('90cpu')
+ bar_cpu.periodic(1) {
+ freq = '?'
+ freq = $1 if `sysctl dev.cpu.0.freq` =~ /: (\d+)/
+ temp = '?'
+ temp = $1.to_f if `sysctl hw.acpi.thermal.tz0.temperature` =~ /: (\d+\.\d+)C$/
+
+ if temp >= 75
+ bar_cpu.colors = COLORS_RED
+ elsif temp >= 50
+ bar_cpu.colors = COLORS_GREEN
+ else
+ bar_cpu.colors = COLORS_BLUE
+ end
+
+ "#{freq} MHz (#{temp}C)"
+ }
+end
+
+if `sysctl hw.acpi.battery` != ''
+ bar_bat = wmii.new_bar('95bat')
+ bar_bat.periodic(2) {
+ battery = {}
+ `sysctl hw.acpi.battery`.scan(/^hw\.acpi\.battery\.(.+?): (.+)$/) {|k,v|
+ battery[k] = v
+ }
+
+ bar_bat.colors = case battery['life'].to_i
+ when 90..100 then COLORS_BLUE
+ when 20..89 then COLORS_GREEN
+ else COLORS_RED
+ end
+
+ # state:
+ # 2: charging?
+ if battery['state'] == '0'
+ "#{battery['life']}%"
+ else
+ "#{battery['life']}% (#{battery['time']}m)"
+ end
+ }
+end
+
+bar_time = wmii.new_bar('99time')
+bar_time.colors = COLORS_BLUE
+bar_time.periodic { Time.now.strftime('%H:%M') }
+wmii.on_barclick('99time') { system('xtrlock') }
+
+wmii.event_loop
diff --git a/ruby-ixp/lib/ixp.rb b/ruby-ixp/lib/ixp.rb
@@ -0,0 +1,20 @@
+=begin
+Ruby-IXP, Copyright 2006 Stephan Maka
+
+This file is part of Ruby-IXP.
+
+Ruby-IXP 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.
+
+Ruby-IXP 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 Ruby-IXP; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA)
+=end
+require 'ixp/client'
diff --git a/ruby-ixp/lib/ixp/client.rb b/ruby-ixp/lib/ixp/client.rb
@@ -0,0 +1,310 @@
+=begin
+Ruby-IXP, Copyright 2006 Stephan Maka
+
+This file is part of Ruby-IXP.
+
+Ruby-IXP 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.
+
+Ruby-IXP 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 Ruby-IXP; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA)
+=end
+require 'socket'
+require 'thread'
+
+require 'ixp/fcall'
+
+module IXP
+ class IXPException < RuntimeError
+ end
+
+ class Connection
+ def initialize(path)
+ @sock = UNIXSocket.new(path)
+
+ @send_blocks = {}
+ @send_blocks_lock = Mutex.new
+ Thread.abort_on_exception = true
+ Thread.new {
+ parser
+ }
+ end
+
+ def gen_tag
+ @send_blocks_lock.synchronize {
+ begin
+ tag = rand(2 ** 16)
+ end while @send_blocks.has_key? tag
+
+ tag
+ }
+ end
+
+ def send(fcall)
+ fcall.fields[:tag] = gen_tag unless fcall.fields[:tag]
+ buf = fcall.to_b
+ id, tag = buf.unpack('cS')
+ #puts "Sending #{([buf.size + 6].pack('I') + buf).inspect}"
+
+ @send_blocks_lock.synchronize {
+ @send_blocks[tag] = Thread.current
+ }
+
+ begin
+ @sock.write([buf.size + 4].pack('I') + buf)
+
+ Thread.stop
+ rescue FcallResult => result
+ if result.fcall.kind_of? Rerror
+ raise IXPException.new(result.fcall.fields[:ename])
+ else
+ result.fcall
+ end
+ end
+ end
+
+ def parser
+ while buf = @sock.read(4)
+ len, = buf.unpack('I')
+ buf = @sock.read(len - 4)
+ id, tag = buf.unpack('cS')
+
+ @send_blocks_lock.synchronize {
+ if @send_blocks.has_key? tag
+ @send_blocks[tag].raise FcallResult.new(Fcall::from_b(buf))
+ @send_blocks.delete tag
+ else
+ puts "Unexpected packet with tag #{tag.inspect}"
+ end
+ }
+ end
+ end
+ end
+
+ class Client < Connection
+ def initialize(path=ENV['WMII_ADDRESS'])
+ raise 'Nowhere to connect' unless path
+
+ super(path.gsub(/^unix!/, ''))
+ @last_fid = -1
+
+ rv = send(Tversion.new(:tag=>0, :version=>'9P2000', :msize=>8192))
+ if rv.fields[:version] != '9P2000'
+ raise "Unknown 9P version: #{rv.version.inspect}"
+ end
+
+ @root_fid = gen_fid
+ send(Tattach.new(:tag=>0, :fid=>@root_fid, :afid=>0, :uname=>ENV['USER'], :aname=>''))
+ end
+
+ def gen_fid
+ @last_fid += 1
+ end
+
+ # result:: [Fixnum] newfid
+ def walk(filepath)
+ fid = gen_fid
+ send Twalk.new(:fid=>@root_fid, :newfid=>fid, :nwname=>filepath.sub(/^\//, '').split('/'))
+ fid
+ end
+
+ # modes
+ OREAD = 0x00
+ OWRITE = 0x01
+ ORDWR = 0x02
+ OEXEC = 0x03
+ OEXCL = 0x04
+ OTRUNC = 0x10
+ OREXEC = 0x20
+ ORCLOSE = 0x40
+ OAPPEND = 0x80
+ # qid_type
+ QTDIR = 0x80
+ def open(filepath, mode=OREAD)
+ fid = walk(filepath)
+ ro = send(Topen.new(:fid=>fid, :mode=>mode))
+ f = ((ro.fields[:qid_type] == QTDIR) ? Directory : File).new(self, fid, ro.fields[:iounit])
+
+ if block_given?
+ begin
+ yield f
+ ensure
+ f.close
+ end
+ else
+ f
+ end
+ end
+
+ DMWRITE = 0x80
+ def create(filepath, perm=DMWRITE, mode=OWRITE)
+ path = filepath.split('/')
+
+ fid = walk(path[0..-2].join('/'))
+ send Tcreate.new(
+ :fid=>fid,
+ :name=>path.last,
+ :perm=>perm,
+ :mode=>mode
+ )
+ send Tclunk.new(:fid=>fid)
+ end
+
+ def remove(filepath)
+ fid = walk(filepath)
+ send Tremove.new(:fid=>fid)
+ # No clunking needed
+ end
+ end
+
+ class File
+ def initialize(client, fid, iounit)
+ @client = client
+ @fid = fid
+ @iounit = iounit
+ @pos = 0
+ end
+
+ def read
+ rr = @client.send(Tread.new(
+ :fid=>@fid,
+ :offset=>@pos,
+ :count=>@iounit
+ ))
+ data = rr.fields[:data]
+ @pos += data.size
+ (data.size > 0) ? data : nil
+ end
+
+ def read_all
+ all = ''
+ while chunk = read
+ all << chunk
+ end
+ all
+ end
+
+ def write(buf_)
+ buf = buf_
+ while buf.size > @iounit
+ write buf[0..(@iounit-1)]
+ buf = buf[@iounit..-1]
+ end
+
+ rw = @client.send(Twrite.new(
+ :fid=>@fid,
+ :offset=>@pos,
+ :data=>buf
+ ))
+ @pos += rw.fields[:count]
+ rw.fields[:count]
+ end
+
+ def close
+ @client.send Tclunk.new(:fid=>@fid)
+ end
+ end
+
+ class Directory < File
+ def initialize(client, fid, iounit)
+ super
+ @buf = nil
+ end
+
+ def next
+ @buf = read_all unless @buf
+
+ if @buf.size < 2
+ nil
+ else
+ stat = Stat.new(@buf)
+ @buf = @buf[(stat.size+2)..-1]
+ stat
+ end
+ end
+ end
+
+ # size[2]
+ # total byte count of the following data
+ # type[2]
+ # for kernel use
+ # dev[4]
+ # for kernel use
+ # qid.type[1]
+ # the type of the file (directory, etc.), represented as a bit vector corresponding to the high 8 bits of the file's mode word.
+ # qid.vers[4]
+ # version number for given path
+ # qid.path[8]
+ # the file server's unique identification for the file
+ # mode[4]
+ # permissions and flags
+ # atime[4]
+ # last access time
+ # mtime[4]
+ # last modification time
+ # length[8]
+ # length of file in bytes
+ # name[ s ]
+ # file name; must be / if the file is the root directory of the server
+ # uid[ s ]
+ # owner name
+ # gid[ s ]
+ # group name
+ # muid[ s ]
+ # name of the user who last modified the file
+ class Stat
+ attr_reader :size, :type, :dev, :qid_type, :qid_version, :qid_path, :mode, :atime, :mtime, :length, :name, :uid, :gid, :muid
+ def initialize(buf_)
+ buf = buf_
+
+ @size, @type, @dev, @qid_type, @qid_version, @qid_path, @mode, @atime, @mtime, @length = buf.unpack('SSICIa8IIIQ')
+ buf = buf[(2+2+4+1+4+8+4+4+4+8)..-1]
+
+ name_size, = buf.unpack('S')
+ buf = buf[2..-1]
+ @name, = buf.unpack("a#{name_size}")
+ buf = buf[name_size..-1]
+
+ uid_size, = buf.unpack('S')
+ buf = buf[2..-1]
+ @uid, = buf.unpack("a#{uid_size}")
+ buf = buf[uid_size..-1]
+
+ gid_size, = buf.unpack('S')
+ buf = buf[2..-1]
+ @gid, = buf.unpack("a#{gid_size}")
+ buf = buf[gid_size..-1]
+
+ muid_size, = buf.unpack('S')
+ buf = buf[2..-1]
+ @muid, = buf.unpack("a#{muid_size}")
+ buf = buf[muid_size..-1]
+ end
+
+ def mode_str
+ modes = %w(--- --x -w- -wx r-- r-x rw- rwx)
+
+ ((@mode & 0x80000000 != 0) ? 'd' : '-') +
+ '-' +
+ modes[(mode >> 6) & 7] +
+ modes[(mode >> 3) & 7] +
+ modes[mode & 7]
+ end
+ end
+
+ class FcallResult < Exception
+ attr_reader :fcall
+ def initialize(fcall)
+ @fcall = fcall
+ end
+ end
+end
+
diff --git a/ruby-ixp/lib/ixp/fcall.rb b/ruby-ixp/lib/ixp/fcall.rb
@@ -0,0 +1,240 @@
+=begin
+Ruby-IXP, Copyright 2006 Stephan Maka
+
+This file is part of Ruby-IXP.
+
+Ruby-IXP 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.
+
+Ruby-IXP 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 Ruby-IXP; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA)
+=end
+class Hash
+ alias :key :index
+end
+
+module IXP
+ FCALL_IDS = {}
+
+ class Fcall
+ # Field access
+ def self.add_field(name, type)
+ self.fields << [name, type]
+ end
+ def self.fields
+ # Default fields
+ @fields = [[:id, 'C'], [:tag, 'S']] unless defined? @fields
+ @fields
+ end
+
+ # Binary deserialization
+ def self.from_b(buf)
+ klass = nil
+ id, = buf.unpack('C')
+
+ klass = FCALL_IDS.key(id)
+ raise "Unknown id #{id.inspect}" unless klass
+
+ f = klass.new
+ (klass.fields || []).each { |name,type|
+ offset = 0
+
+ if type =~ /^String/
+ if type =~ /4$/
+ length, = buf.unpack('I')
+ buf = buf[4..-1]
+ else
+ length, = buf.unpack('S')
+ buf = buf[2..-1]
+ end
+ f.fields[name], = buf.unpack("a#{length}")
+ offset += length
+ else
+ f.fields[name], = buf.unpack(type)
+ case type
+ when /^a(\d+)$/
+ offset += $1.to_i
+ when 'C'
+ offset += 1
+ when 'S'
+ offset += 2
+ when 'I'
+ offset += 4
+ when 'Q'
+ offset += 8
+ else
+ raise "Unsupport type #{type}"
+ end
+ end
+
+ buf = buf[offset..-1]
+ }
+
+ f
+ end
+
+ # Binary serialization
+ def to_b
+ self.class.fields.collect { |name,type|
+ case type
+ when /^String4?$/
+ [@fields[name].size, @fields[name]].pack("#{(type == 'String4') ? 'I' : 'S'}a#{@fields[name].size}")
+ when 'Strings'
+ strings = @fields[name]
+ ([strings.size] + strings.collect{|s|[s.size, s]}).flatten.pack("S" + strings.collect{|s|"Sa#{s.size}"}.to_s)
+ else
+ [@fields[name]].pack(type)
+ end
+ }.to_s
+ end
+
+ # Instance initialization
+ attr_reader :fields
+ def initialize(fields={})
+ id = FCALL_IDS[self.class]
+ @fields = {:id=>id}.merge(fields)
+ end
+ end
+
+ ##
+ # http://v9fs.sourceforge.net/rfc/
+ ##
+
+ # version negotiate protocol
+
+ class Tversion < Fcall
+ FCALL_IDS[Tversion] = 100
+ add_field :msize, 'I'
+ add_field :version, 'String'
+ end
+ class Rversion < Fcall
+ FCALL_IDS[Rversion] = 101
+ add_field :msize, 'I'
+ add_field :version, 'String'
+ end
+
+ # messages to establish a connection
+
+ class Tattach < Fcall
+ FCALL_IDS[Tattach] = 104
+ add_field :fid, 'I'
+ add_field :afid, 'I'
+ add_field :uname, 'String'
+ add_field :aname, 'String'
+ end
+ class Rattach < Fcall
+ FCALL_IDS[Rattach] = 105
+ add_field :qid_type, 'C'
+ add_field :qid_version, 'I'
+ add_field :qid_path, 'a8'
+ end
+
+ # error message
+
+ class Rerror < Fcall
+ FCALL_IDS[Rerror] = 107
+ add_field :ename, 'String'
+ end
+
+ # descend a directory hierarchy
+
+ class Twalk < Fcall
+ FCALL_IDS[Twalk] = 110
+ add_field :fid, 'I'
+ add_field :newfid, 'I'
+ add_field :nwname, 'Strings'
+ end
+ class Rwalk < Fcall
+ FCALL_IDS[Rwalk] = 111
+ add_field :nwqid, 'S'
+ end
+
+ # prepare a fid for I/O on an existing file
+ class Topen < Fcall
+ FCALL_IDS[Topen] = 112
+ add_field :fid, 'I'
+ add_field :mode, 'C'
+ end
+ class Ropen < Fcall
+ FCALL_IDS[Ropen] = 113
+ add_field :qid_type, 'C'
+ add_field :qid_version, 'I'
+ add_field :qid_path, 'a8'
+ add_field :iounit, 'I'
+ end
+
+ # create a directory
+ # size[4] Tcreate tag[2] fid[4] name[s] perm[4] mode[1]
+ # size[4] Rcreate tag[2] qid[13] iounit[4]
+
+ class Tcreate < Fcall
+ FCALL_IDS[Tcreate] = 114
+ add_field :fid, 'I'
+ add_field :name, 'String'
+ add_field :perm, 'I'
+ add_field :mode, 'C'
+ end
+ class Rcreate < Fcall
+ FCALL_IDS[Rcreate] = 115
+ add_field :qid_type, 'C'
+ add_field :qid_version, 'I'
+ add_field :qid_path, 'a8'
+ add_field :iounit, 'I'
+ end
+
+
+ # read, write - transfer data from and to a file
+ # size[4] Tread tag[2] fid[4] offset[8] count[4]
+ # size[4] Rread tag[2] count[4] data[count]
+ # size[4] Twrite tag[2] fid[4] offset[8] count[4] data[count]
+ # size[4] Rwrite tag[2] count[4]
+
+ class Tread < Fcall
+ FCALL_IDS[Tread] = 116
+ add_field :fid, 'I'
+ add_field :offset, 'Q'
+ add_field :count, 'I'
+ end
+ class Rread < Fcall
+ FCALL_IDS[Rread] = 117
+ add_field :data, 'String4'
+ end
+
+ class Twrite < Fcall
+ FCALL_IDS[Twrite] = 118
+ add_field :fid, 'I'
+ add_field :offset, 'Q'
+ add_field :data, 'String4'
+ end
+ class Rwrite < Fcall
+ FCALL_IDS[Rwrite] = 119
+ add_field :count, 'I'
+ end
+
+ # fid clunking
+
+ class Tclunk < Fcall
+ FCALL_IDS[Tclunk] = 120
+ add_field :fid, 'I'
+ end
+ class Rclunk < Fcall
+ FCALL_IDS[Rclunk] = 121
+ end
+
+ # remove a file
+ class Tremove < Fcall
+ FCALL_IDS[Tremove] = 122
+ add_field :fid, 'I'
+ end
+ class Rremove < Fcall
+ FCALL_IDS[Rremove] = 123
+ end
+end
diff --git a/ruby-ixp/lib/wmii.rb b/ruby-ixp/lib/wmii.rb
@@ -0,0 +1,194 @@
+=begin
+Ruby-IXP, Copyright 2006 Stephan Maka
+
+This file is part of Ruby-IXP.
+
+Ruby-IXP 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.
+
+Ruby-IXP 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 Ruby-IXP; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA)
+=end
+require 'ixp'
+
+class WMII
+ def initialize
+ begin
+ @cl = IXP::Client.new
+ rescue Errno::ECONNREFUSED
+ retry
+ end
+ @key_blocks = []
+ @barclick_blocks = []
+ end
+
+ def create(file)
+ @cl.create(file)
+ rescue IXP::IXPException => e
+ puts "#{e.backtrace.first}: #{e}"
+ end
+
+ def read(file)
+ @cl.open(file) do |f|
+ if f.respond_to? :next
+ str = ''
+ while i = f.next
+ str << i.name << "\n"
+ end
+ str
+ else
+ f.read_all
+ end
+ end
+ rescue IXP::IXPException => e
+ puts "#{e.backtrace.first}: #{e}"
+ end
+
+ def remove(file)
+ @cl.remove(file)
+ rescue IXP::IXPException => e
+ puts "#{e.backtrace.first}: #{e}"
+ end
+
+ def write(file, data)
+ @cl.open(file) { |f| f.write(data.to_s) }
+ rescue IXP::IXPException => e
+ puts "#{e.backtrace.first}: #{e}"
+ end
+
+ def menu(choices)
+ r = IO::popen("wmiimenu", "r+") {|io| io.puts choices.join("\n"); io.close_write; io.readlines.to_s }
+ r.size > 0 ? r : nil
+ end
+
+ def env_selcolors=(font_face_border)
+ ENV['WMII_SELCOLORS'] = font_face_border
+ end
+
+ def env_normcolors=(font_face_border)
+ ENV['WMII_NORMCOLORS'] = font_face_border
+ end
+
+ def env_font=(name)
+ ENV['WMII_FONT'] = name
+ end
+
+ def event_loop
+ @cl.open('/event') { |f|
+ while line = f.read
+ event, args = line.chomp.split(' ', 2)
+
+ case event
+ when 'Start'
+ return if args == 'wmiirc'
+ when 'Key'
+ @key_blocks.each { |sym,block|
+ if sym == args
+ block.call
+ end
+ }
+ when 'BarClick'
+ bar_name, btn = args.split(' ', 2)
+ @barclick_blocks.each { |bar,block|
+ if bar == bar_name
+ block.call(btn)
+ end
+ }
+ end
+ end
+ }
+ end
+
+ class Bar
+ def initialize(wmii, name)
+ @wmii = wmii
+ @name = name
+ @colors = nil
+ end
+
+ def path
+ "/bar/#{@name}"
+ end
+
+ def colors
+ unless @colors
+ @colors = @wmii.read("#{path}/colors")
+ end
+ @colors
+ end
+
+ def colors=(font_face_border)
+ if font_face_border != @colors
+ @wmii.write("#{path}/colors", font_face_border)
+ @colors = nil
+ end
+ end
+
+ def data=(data)
+ @wmii.write("#{path}/data", data)
+ end
+
+ def periodic(interval=1, &updater)
+ Thread.new do
+ old_data = nil
+ loop {
+ data = updater.call
+ if data != old_data
+ self.data = data
+ old_data = data
+ end
+ sleep interval
+ }
+ end
+ end
+ end
+
+ def new_bar(name)
+ bar = Bar.new(self, name)
+ create bar.path
+ bar
+ end
+
+ def go_view(view)
+ write('/ctl', "view #{view}")
+ end
+
+ def write_keys
+ write "/def/keys", @key_blocks.collect { |sym,block|
+ sym
+ }.join("\n") + "\n"
+ end
+
+ def on_key(sym, &block)
+ @key_blocks << [sym, block]
+ write_keys
+ end
+
+ def on_barclick(bar, &block)
+ @barclick_blocks << [bar, block]
+ end
+
+ def border=(width)
+ write '/def/border', width.to_s
+ end
+
+ def selcolors=(font_face_border)
+ write '/def/selcolors', font_face_border
+ end
+
+ def normcolors=(font_face_border)
+ write '/def/normcolors', font_face_border
+ end
+
+ def font=(name)
+ write '/def/font', name
+ end
+end
diff --git a/status b/status
@@ -0,0 +1,17 @@
+#!/usr/bin/ruby -w
+# periodically print date and load average to the bar
+
+$: << File.dirname(__FILE__)
+require 'wmii'
+
+DELAY = 1
+
+WM = Wmii.instance
+
+system('wmiir remove /bar/status') && sleep(DELAY * 2)
+system 'wmiir create /bar/status'
+WM.write '/bar/status/colors', ENV['WMII_NORMCOLORS']
+
+while WM.write('/bar/status/data', Time.now.to_s << " " << `uptime`.scan(/\d+\.\d+/).join(' '))
+ sleep DELAY
+end
diff --git a/wmii.rb b/wmii.rb
@@ -1,34 +1,83 @@
# Ruby interface to WMII.
+$:.unshift File.join(File.dirname(__FILE__), 'ruby-ixp/lib')
+require 'ixp'
+
require 'find'
+require 'singleton'
-module Wmii
- # Writes the given content to the given WM path, and returns +true+ if the operation was successful.
- def Wmii.write aPath, aContent
+class Wmii
+ include Singleton
+
+ def initialize
begin
- IO.popen("wmiir write #{aPath}", 'w') do |io|
- puts "wmiir: writing '#{aContent}' into '#{aPath}'" if $DEBUG
- io.write aContent
- end
- rescue Errno::EPIPE
- return false
+ @cl = IXP::Client.new
+ rescue Errno::ECONNREFUSED
+ retry
end
+ end
- $? == 0
+ def create(file)
+ @cl.create(file)
+ rescue IXP::IXPException => e
+ puts "#{e.backtrace.first}: #{e}"
end
- # Reads the filenames from a long listing of the given WM path.
- def Wmii.readList aPath
- `wmiir read #{aPath}`.scan(/\S+$/)
+ def remove(file)
+ @cl.remove(file)
+ rescue IXP::IXPException => e
+ puts "#{e.backtrace.first}: #{e}"
+ end
+
+ # Writes the given content to the given WM path, and returns +true+ if the operation was successful.
+ # def write aPath, aContent
+ # begin
+ # IO.popen("wmiir write #{aPath}", 'w') do |io|
+ # puts "wmiir: writing '#{aContent}' into '#{aPath}'" if $DEBUG
+ # io.write aContent
+ # end
+ # rescue Errno::EPIPE
+ # return false
+ # end
+
+ # $? == 0
+ # end
+
+ def write(file, data)
+ @cl.open(file) { |f| f.write(data.to_s) }
+ rescue IXP::IXPException => e
+ puts "#{e.backtrace.first}: #{e}"
+ end
+
+ # def read aPath
+ # `wmiir read #{aPath}`
+ # end
+
+ def read(file)
+ @cl.open(file) do |f|
+ if f.respond_to? :next # read directory listing
+ str = ''
+
+ while i = f.next
+ str << i.name << "\n"
+ end
+
+ str
+ else
+ f.read_all
+ end
+ end
+ rescue IXP::IXPException => e
+ puts "#{e.backtrace.first}: #{e}"
end
# Shows the view with the given name.
- def Wmii.showView aName
- Wmii.write '/ctl', "view #{aName}"
+ def showView aName
+ write '/ctl', "view #{aName}"
end
# Shows a WM menu with the given content and returns its output.
- def Wmii.showMenu aContent
+ def showMenu aContent
output = nil
IO.popen('wmiimenu', 'r+') do |menu|
@@ -42,14 +91,14 @@ module Wmii
end
# Shows the client which has the given ID.
- def Wmii.showClient aClientId
- `wmiir read /tags`.split.each do |view|
- Wmii.readList("/#{view}").grep(/^\d+$/).each do |column|
- Wmii.readList("/#{view}/#{column}").grep(/^\d+$/).each do |client|
- if `wmiir read /#{view}/#{column}/#{client}/index` == aClientId
- Wmii.showView view
- Wmii.write '/view/ctl', "select #{column}"
- Wmii.write "/view/sel/ctl", "select #{client}"
+ def showClient aClientId
+ read('/tags').split.each do |view|
+ read("/#{view}").split.grep(/^\d+$/).each do |column|
+ read("/#{view}/#{column}").split.grep(/^\d+$/).each do |client|
+ if read("/#{view}/#{column}/#{client}/index") == aClientId
+ showView view
+ write '/view/ctl', "select #{column}"
+ write "/view/sel/ctl", "select #{client}"
return
end
end
@@ -57,11 +106,31 @@ module Wmii
end
end
+ DETACHED_TAG = 'status'
+
+ # Detach the currently selected client
+ def detachClient
+ write '/view/sel/sel/tags', DETACHED_TAG
+ end
+
+ # Attach the most recently detached client
+ def attachClient
+ if areaList = read("/#{DETACHED_TAG}")
+ area = areaList.split.grep(/^\d+$/).last
+
+ if clientList = read("/#{DETACHED_TAG}/#{area}")
+ client = clientList.split.grep(/^\d+$/).last
+
+ write "/#{DETACHED_TAG}/#{area}/#{client}/tags", read('/view/name')
+ end
+ end
+ end
+
# Changes the current view to an adjacent one (:left or :right).
- def Wmii.cycleView aTarget
- tags = `wmiir read /tags`.split
+ def cycleView aTarget
+ tags = read('/tags').split
- curTag = `wmiir read /view/name`
+ curTag = read('/view/name')
curIndex = tags.index(curTag)
newIndex =
@@ -79,20 +148,20 @@ module Wmii
newTag = tags[newIndex]
- Wmii.showView newTag
+ showView newTag
end
# Renames the given view and sends its clients along for the ride.
- def Wmii.renameView aOld, aNew
- Wmii.readList('/client').each do |id|
- tags = `wmiir read /client/#{id}/tags`
+ def renameView aOld, aNew
+ read('/client').split.each do |id|
+ tags = read("/client/#{id}/tags")
- Wmii.write "/client/#{id}/tags", tags.gsub(aOld, aNew).squeeze('+')
+ write "/client/#{id}/tags", tags.gsub(aOld, aNew).squeeze('+')
end
end
# Returns a list of program names available in the given paths.
- def Wmii.findPrograms *aPaths
+ def findPrograms *aPaths
list = []
Find.find(*aPaths) do |f|
diff --git a/wmiirc b/wmiirc
@@ -5,9 +5,11 @@ $: << File.dirname(__FILE__)
require 'wmii'
+WM = Wmii.instance
+
# WM STARTUP
-# give wmiiwm a chance to start and terminate other wmiirc
-sleep 1 until Wmii.write('/event', "Start wmiirc\n")
+# terminate other running wmiirc
+sleep 1 until WM.write('/event', "Start wmiirc\n")
# UI CONFIGURATION
@@ -17,22 +19,22 @@ ENV['WMII_NORMCOLORS']='#222222 #eeeeee #666666'
# WM CONFIGURATION
-Wmii.write '/def/border', 2
-Wmii.write '/def/font', ENV['WMII_FONT']
-Wmii.write '/def/selcolors', ENV['WMII_SELCOLORS']
-Wmii.write '/def/normcolors', ENV['WMII_NORMCOLORS']
-Wmii.write '/def/colmode', 'default'
-Wmii.write '/def/colwidth', 0
+WM.write '/def/border', 2
+WM.write '/def/font', ENV['WMII_FONT']
+WM.write '/def/selcolors', ENV['WMII_SELCOLORS']
+WM.write '/def/normcolors', ENV['WMII_NORMCOLORS']
+WM.write '/def/colmode', 'default'
+WM.write '/def/colwidth', 0
# TAGGING RULES
-Wmii.write '/def/rules', <<EOF
+WM.write '/def/rules', <<EOF
/QEMU.*/ -> ~
/jEdit.*/ -> code
-/Buddy List.*/ -> mail
+/Buddy List.*/ -> chat
+/XChat.*/ -> chat
/.*thunderbird.*/ -> mail
/Gimp.*/ -> gimp
-/Dia.*/ -> dia
/MPlayer.*/ -> ~
/xconsole.*/ -> ~
/alsamixer.*/ -> ~
@@ -42,11 +44,11 @@ EOF
# MISC CONFIGURATION
-system 'xsetroot -solid "#333333"'
+# system 'xsetroot -solid "#333333"'
system 'status &'
-PROGRAM_MENU = Wmii.findPrograms(*ENV['PATH'].split(':')).join("\n")
-ACTION_MENU = Wmii.findPrograms('/home/sun/dry/apps/wmii/etc/wmii-3', '~/.wmii-3').join("\n")
+PROGRAM_MENU = WM.findPrograms(*ENV['PATH'].split(':')).join("\n")
+ACTION_MENU = WM.findPrograms('/home/sun/dry/apps/wmii/etc/wmii-3', '~/.wmii-3').join("\n")
# KEY CONFIGURATION
@@ -64,98 +66,118 @@ MENU="#{SELECT}"
PROGRAM="#{SELECT}"
SHORTCUTS = {
- "#{SELECT}#{LEFT}" => proc {Wmii.write '/view/ctl', 'select prev'},
- "#{SELECT}#{RIGHT}" => proc {Wmii.write '/view/ctl', 'select next'},
- "#{SELECT}#{DOWN}" => proc {Wmii.write '/view/sel/ctl', 'select next'},
- "#{SELECT}#{UP}" => proc {Wmii.write '/view/sel/ctl', 'select prev'},
- "#{SELECT}space" => proc {Wmii.write '/view/ctl', 'select toggle'},
+ "#{SELECT}#{LEFT}" => lambda do WM.write '/view/ctl', 'select prev' end,
+ "#{SELECT}#{RIGHT}" => lambda do WM.write '/view/ctl', 'select next' end,
+ "#{SELECT}#{DOWN}" => lambda do WM.write '/view/sel/ctl', 'select next' end,
+ "#{SELECT}#{UP}" => lambda do WM.write '/view/sel/ctl', 'select prev' end,
+ "#{SELECT}space" => lambda do WM.write '/view/ctl', 'select toggle' end,
- "#{SELECT}comma" => proc {Wmii.cycleView :left},
- "#{SELECT}period" => proc {Wmii.cycleView :right},
+ "#{SELECT}comma" => lambda do WM.cycleView :left end,
+ "#{SELECT}period" => lambda do WM.cycleView :right end,
- "#{LAYOUT}w" => proc {Wmii.write '/view/sel/mode', 'default'},
- "#{LAYOUT}v" => proc {Wmii.write '/view/sel/mode', 'stack'},
- "#{LAYOUT}m" => proc {Wmii.write '/view/sel/mode', 'max'},
- "#{LAYOUT}z" => proc {Wmii.write '/view/0/sel/geom', '0 0 east south'},
+ "#{LAYOUT}w" => lambda do WM.write '/view/sel/mode', 'default' end,
+ "#{LAYOUT}v" => lambda do WM.write '/view/sel/mode', 'stack' end,
+ "#{LAYOUT}m" => lambda do WM.write '/view/sel/mode', 'max' end,
+ "#{LAYOUT}z" => lambda do WM.write '/view/0/sel/geom', '0 0 east south' end,
- "#{MENU}i" => proc {system(Wmii.showMenu(ACTION_MENU) << '&')},
- "#{MENU}e" => proc {system(Wmii.showMenu(PROGRAM_MENU) << '&')},
- "#{MENU}v" => proc {Wmii.showView `wmiir read /tags | wmiimenu`},
+ "#{MENU}i" => lambda do system(WM.showMenu(ACTION_MENU) << '&') end,
+ "#{MENU}e" => lambda do system(WM.showMenu(PROGRAM_MENU) << '&') end,
+ "#{MENU}v" => lambda do WM.showView(WM.showMenu(WM.read('/tags'))) end,
# focus any client by choosing from a menu
- "#{MENU}a" => proc do
+ "#{MENU}a" => lambda do
# prepare a list of menu choices
- choices = Wmii.readList('/client').inject([]) do |acc, id|
- tags = `wmiir read /client/#{id}/tags`
- name = `wmiir read /client/#{id}/name`.downcase
+ choices = WM.read('/client').split.inject([]) do |acc, id|
+ tags = WM.read("/client/#{id}/tags")
+ name = WM.read("/client/#{id}/name").downcase
acc << format("%d. [%s] %s", id, tags, name)
end
# show the menu and focus the chosen client
- target = Wmii.showMenu(choices.join("\n"))
+ target = WM.showMenu(choices.join("\n"))
unless target.empty?
- Wmii.showClient target.scan(/\d+/).first
+ WM.showClient target.scan(/\d+/).first
end
end,
- "#{PROGRAM}x" => proc {system 'terminal &'},
- "#{PROGRAM}k" => proc {system 'firefox &'},
- "#{PROGRAM}j" => proc {system 'beagle-search &'},
- "#{PROGRAM}q" => proc {system 'nautilus --no-desktop &'},
+ "#{PROGRAM}x" => lambda do system 'terminal &' end,
+ "#{PROGRAM}k" => lambda do system 'epiphany &' end,
+ "#{PROGRAM}j" => lambda do system 'nautilus --no-desktop &' end,
+ # "#{PROGRAM}q" => lambda do system 'beagle-search &' end,
+
+
+ "#{SEND}#{LEFT}" => lambda do WM.write '/view/sel/sel/ctl', 'sendto prev' end,
+ "#{SEND}#{RIGHT}" => lambda do WM.write '/view/sel/sel/ctl', 'sendto next' end,
+ "#{SEND}space" => lambda do WM.write '/view/sel/sel/ctl', 'sendto toggle' end,
+ "#{SEND}Delete" => lambda do WM.write '/view/sel/sel/ctl', 'kill' end,
+ # change the tag of the current client
+ "#{SEND}t" => lambda do
+ choices = WM.read('/tags').split.map {|t| [t, "+#{t}", "-#{t}"]}.flatten
+ tags = WM.read('/view/sel/sel/tags').split('+')
- "#{SEND}#{LEFT}" => proc {Wmii.write '/view/sel/sel/ctl', 'sendto prev'},
- "#{SEND}#{RIGHT}" => proc {Wmii.write '/view/sel/sel/ctl', 'sendto next'},
- "#{SEND}space" => proc {Wmii.write '/view/sel/sel/ctl', 'sendto toggle'},
- "#{SEND}Delete" => proc {Wmii.write '/view/sel/sel/ctl', 'kill'},
- "#{SEND}t" => proc {Wmii.write '/view/sel/sel/tags', `wmiir read /tags | wmiimenu`},
+ case target = WM.showMenu(choices.join("\n"))
+ when /^\+/
+ tags << $'
+
+ when /^\-/
+ tags.delete $'
+
+ else
+ tags = [target]
+ end
+
+ WM.write '/view/sel/sel/tags', tags.join('+')
+ end,
+
+ "#{SEND}d" => lambda do WM.detachClient end,
+ "#{SEND}Shift-d" => lambda do WM.attachClient end,
# toggle maximizing the current client to full screen
- "#{SEND}m" => proc do
+ "#{SEND}m" => lambda do
SHORTCUTS["#{SEND}space"].call
SHORTCUTS["#{LAYOUT}z"].call
end,
# rename the current view
# NOTE: columns & layouts are not preserved
- "#{SEND}r" => proc do
- old = `wmiir read /view/name`
- new = Wmii.showMenu(`wmiir read /tags`)
+ "#{SEND}r" => lambda do
+ old = WM.read('/view/name')
+ new = WM.showMenu(WM.read('/tags'))
unless new.empty?
- Wmii.renameView old, new
- Wmii.showView new
+ WM.renameView old, new
+ WM.showView new
end
end,
- "#{SWAP}#{LEFT}" => proc {Wmii.write '/view/sel/sel/ctl', 'swap prev'},
- "#{SWAP}#{RIGHT}" => proc {Wmii.write '/view/sel/sel/ctl', 'swap next'},
- "#{SWAP}#{DOWN}" => proc {Wmii.write '/view/sel/sel/ctl', 'swap down'},
- "#{SWAP}#{UP}" => proc {Wmii.write '/view/sel/sel/ctl', 'swap up'},
+ "#{SWAP}#{LEFT}" => lambda do WM.write '/view/sel/sel/ctl', 'swap prev' end,
+ "#{SWAP}#{RIGHT}" => lambda do WM.write '/view/sel/sel/ctl', 'swap next' end,
+ "#{SWAP}#{DOWN}" => lambda do WM.write '/view/sel/sel/ctl', 'swap down' end,
+ "#{SWAP}#{UP}" => lambda do WM.write '/view/sel/sel/ctl', 'swap up' end,
}
# keyboard access to views (shown as labels on the WM bar)
10.times do |i|
- # associate '1' with the leftmost label, instead of '0'
- k = (i - 1) % 10
+ k = (i - 1) % 10 # associate '1' with the leftmost label, instead of '0'
-
- SHORTCUTS["#{SELECT}#{i}"] = proc {Wmii.showView(`wmiir read /tags`.split[k] || i)}
- SHORTCUTS["#{SEND}#{i}"] = proc {Wmii.write '/view/sel/sel/tags', (`wmiir read /tags`.split[k] || i)}
+ SHORTCUTS["#{SELECT}#{i}"] = lambda do WM.showView(WM.read('/tags').split[k] || i) end
+ SHORTCUTS["#{SEND}#{i}"] = lambda do WM.write '/view/sel/sel/tags', (WM.read('/tags').split[k] || i) end
end
-Wmii.write '/def/grabmod', MODKEY
-Wmii.write '/def/keys', SHORTCUTS.keys.join("\n")
+WM.write '/def/grabmod', MODKEY
+WM.write '/def/keys', SHORTCUTS.keys.join("\n")
# EVENT LOOP
+begin
IO.popen('wmiir read /event') do |io|
while event = io.readline.chomp
type, arg = event.split
@@ -167,10 +189,13 @@ IO.popen('wmiir read /event') do |io|
exit if arg == 'wmiirc'
when 'BarClick'
- Wmii.showView arg
+ WM.showView arg
when 'Key'
SHORTCUTS[arg].call
end
end
end
+rescue EOFError
+ exit # wmiiwm has quit
+end