wmiirc-rumai

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

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:
ruby-ixp/COPYING | 339+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ruby-ixp/examples/benchmark.rb | 70++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ruby-ixp/examples/wmiir.rb | 64++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ruby-ixp/examples/wmiirc-astro | 321+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ruby-ixp/lib/ixp.rb | 20++++++++++++++++++++
ruby-ixp/lib/ixp/client.rb | 310+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ruby-ixp/lib/ixp/fcall.rb | 240+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ruby-ixp/lib/wmii.rb | 194+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
status | 17+++++++++++++++++
wmii.rb | 135+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
wmiirc | 147++++++++++++++++++++++++++++++++++++++++++++++---------------------------------
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