#!/usr/bin/env ruby1.8 # Copyright (c) 2006 Mauricio Fernandez # http://eigenclass.org/hiki.rb?wmii+ruby # Licensed under the same terms as Ruby (see LICENSE). USE_IXP_EXTENSION = true WMIIRC_VERSION = "0.3.2" WMIIRC_RELEASE_DATE = "unreleased (preliminary/internal)" WMIIRC_HOME = File.join(ENV["HOME"], ".wmii-3") WMIIRC_CONFIG_FILE = File.join(WMIIRC_HOME, "wmiirc-config.rb") WMIIRC_PLUGIN_DIR = File.join(WMIIRC_HOME, "plugins") WMIIRC_HELP_MESSAGE = < (add 'wmii' to the subject to make sure it gets through the spam filters). Staying up to date ================== The latest version of ruby-wmii can be obtained from http://eigenclass.org/hiki.rb?wmii+ruby Run the included install.rb script with ruby install.rb to install wmiirc and the standard plugin. Were any further step necessary, it'd be noted in README.upgrade. Using plugins ============= ruby-wmii can be extended using third-party plugins comprising keyboard bindings and "bar applets". This involves two steps: 1) placing the plugin under #{WMIIRC_PLUGIN_DIR} 2) editing #{WMIIRC_CONFIG_FILE} to request that a given plugin be used Each plugin defines a number of key bindings and applets under a namespace. A namespace:name pair refers to a key binding or an applet; this is all you need to reference and use them. Here's a small example of how to use the 'volume' applet and a number of bindings defined under the "standard" namespace from("standard") { use_bar_applet("volume") use_binding("dict-lookup") use_binding("execute-program-with-tag") use_binding("execute-action") use_binding("execute-program") (0..9).each{|k| use_binding("numeric-jump-\#{k}") } } This is broadly equivalent to use_bar_applet("standard:volume") use_binding("standard:dict-lookup") use_binding("standard:execute-program-with-tag") use_binding("standard:execute-action") use_binding("standard:execute-program") (0..9).each{|k| use_binding("standard:numeric-jump-\#{k}") } (There's a small difference though: #from uses instance_eval to change self in the block, but this shouldn't matter normally.) Overriding default plugin settings ---------------------------------- The default position and the data (label displayed by wmiibar) of each bar applet are specified by the author, but you can override them with something like # override position only use_bar_applet("somebody@example.com:cool-bar-applet", 50) # position and label use_bar_applet("somebody@example.com:cool-bar-applet2", 100, "text to display") the second argument should be an integer between 0 and 999, which will be used to determine the relative position of the applets in use. You can as well override the default key combination for a key binding (allowing you to reuse the action while changing the actual key sequence that triggers it) as follows: use_binding("myfriend@example.com:do-magic", "MODKEY2-m", "MODKEY-Shift-m") This associates the action defined as 'do-magic' under the myfriend@example namespace to both MODKEY2 and MODKEY-Shift-m. Further configuration --------------------- Some plugins can be configured via the plugin_config hash. See the default #{WMIIRC_CONFIG_FILE} for an example. Read standard-plugin.rb to see the options accepted by the standard bindings/applets. Writing your own plugins ======================== If you think you've found a particularly useful action or applet, you can distribute it as a plugin which should look like this: Plugin.define("my-email@address.com") do # Use your email address to make sure namespaces are unique. # You can also use my-email@address.com/subspace if you need to # partition your namespace. author '"My Full Name" ' bar_applet("applet", 50) do |wmii, bar| bar.on_click(MOUSE_BUTTON_LEFT){ wmii.view "foo" } Thread.new{ loop{ bar.data = `somecmd` }; sleep 2 } end binding("do-magic", "MODKEY-Shift-m", "MODKEY2-m") do |wmii, keyhandler| # keyhandler.key is the key that was pressed. # you can unregisted the keyhandler (remove the binding) with # wmii.unregister(keyhandler) wmii.write "/view/sel/mode", "max" end end The end-user would have to place the above code in a .rb file under $HOME/.wmii-3/plugins and add this to his wmii-config.rb: from("my-email@address.com") { use_binding("do-magic") # optionally rebind it to another key use_bar_applet("applet") # optionally change position and initial label } or alternatively use_binding("my-email@address.com:do-magic") use_bar_applet("my-email@addres.com:applet") Further information =================== This should be enough to get you running, but there are still a few things to be learned from ruby-wmii's sources. You can see how some typical tasks are implemented in standard-plugin.rb, which contains the default bindings and applets under the "standard" namespace. Happy hacking, -- Mauricio Fernandez http://eigenclass.org EOF END{ "wmiirc #{Process.pid} finishing, $! is #{$!.inspect}" } def run unless File.exist?(WMIIRC_CONFIG_FILE) File.open(WMIIRC_CONFIG_FILE, "w"){|f| f.puts DATA.read } Thread.new do IO.popen("xmessage -file -", "w"){|f| f.puts WMIIRC_HELP_MESSAGE; f.close_write } end end LOGGER.info "Loading standard plugin" begin load File.join(WMIIRC_PLUGIN_DIR, "standard-plugin.rb"), true rescue LoadError LOGGER.error "standard-plugin.rb not found" end Dir["#{WMIIRC_PLUGIN_DIR}/*.rb"].each do |fname| next if File.basename(fname) == "standard-plugin.rb" LOGGER.info "Loading plugin #{fname}" load fname, true end # see if the standard plugin is available if Plugin.registered_plugins["standard"].empty? Thread.new do IO.popen("xmessage -file -", "w") do |f| f.puts < 0 rescue IXP::IXPError return "" end end end def foreach(file, &block) @client.foreach(file, &block) end private def establish_connection LOGGER.debug "Connecting to #{address}" @client = LowLevelClient.new(address.clone) LOGGER.debug "Connection established (fd #{@client.fileno})" if @client.fileno end end end require 'timeout' module WMII MOUSE_BUTTON_LEFT = 1 MOUSE_BUTTON_MIDDLE = 2 MOUSE_BUTTON_RIGHT = 3 MOUSE_SCROLL_UP = 4 MOUSE_SCROLL_DOWN = 5 module ConfigurationHelper def def_conf_var(*names) names.each do |name| define_method(name) do |*val| case val[0] when nil: instance_variable_get("@#{name}") else instance_variable_set("@#{name}", val[0]) end end end end def def_wmii_var(*names, &block) names.each do |name| define_method(name) do |*val| case val[0] when nil: @ixp_conn.read "/def/#{name}" else @ixp_conn.write "/def/#{name}", val[0] block.call val[0] if block end end end end end class Plugin class << self attr_reader :registered_plugins private :new end @registered_plugins = Hash.new{|h,namespace| h[namespace] = []} PluginClashError = Class.new(StandardError) KeyBindingClashError = Class.new(PluginClashError) BarAppletClashError = Class.new(PluginClashError) SettingsClashError = Class.new(PluginClashError) KeyBinding = Struct.new(:keys, :block) BarApplet = Struct.new(:position, :initial_data, :block) Settings = Struct.new(:block) def self.define(namespace, &block) r = new(namespace) r.instance_eval(&block) Plugin.registered_plugins[namespace] << r r end attr_reader :bindings attr_reader :bar_applets attr_reader :settings def initialize(namespace) @namespace = namespace @bindings = {} @bar_applets = {} @settings = {} end extend ConfigurationHelper def_conf_var :author def binding(name, *suggested_bindings, &block) if @bindings.has_key?(name) raise KeyBindingClashError, "Binding #{name} defined twice in namespace #{@namespace.inspect}" end @bindings[name] = KeyBinding.new(suggested_bindings, block) end def bar_applet(name, position, initial_data = "", &block) if @bar_applets.has_key?(name) raise BarAppletClashError, "Bar applet #{name} defined twice in namespace #{@namespace.inspect}" end @bar_applets[name] = BarApplet.new(position, initial_data, block) end def def_settings(name, &block) if @settings.has_key?(name) raise SettingsClashError, "Settings #{name} defined twice in namespace #{@namespace.inspect}" end @settings[name] = Settings.new(block) end end class Configuration DEFAULT_EVENTS = %w[BarClick ClientClick ClientFocus CreateClient Key Bye Starting] attr_reader :plugin_config, :prev_view class EventHandler attr_reader :type def initialize(type, &block) @type = type @block = block end def call(*a); @block.call(*a) end end class KeyHandler < EventHandler attr_reader :key def initialize(key, &block) @key = key super("Key", &block) end end class PluginConfigHash def initialize @opts = Hash.new{|h,k| h[k] = {} } end def [](x); @opts[x] end def []=(x, val); @opts[x] = val end end class << self; attr_reader :last_instance def define(*a, &b) r = new(*a, &b) @last_instance = r r end private :new end def initialize(&block) # Signal we're about to start to the previous wmiirc process, or wait # until the IXP server is up LOGGER.info "Waiting for the IXP server." begin @ixp_conn = IXP::Client.new ENV["WMII_ADDRESS"] rescue SystemCallError LOGGER.info "IXP server not up yet, waiting..." sleep 0.2 retry end LOGGER.info "Killing old wmiirc instances." loop{ @ixp_conn.write "/event", "Starting\n" and break } # standard wmiirc @ixp_conn.write "/event", "Bye\n" # new-style wmiirc begin Timeout.timeout(0.5) do loop do quittxt = @ixp_conn.read "/bar/QUIT/data" rescue "" if quittxt == "QUIT" LOGGER.debug "Previous wmiirc process signalled termination." remove "/bar/QUIT" break end sleep 0.1 end end rescue Timeout::Error LOGGER.debug "Previous wmiirc process didn't signal termination." end @procs = {"BarClick" => [], "ClientClick" => [], "ClientFocus" => [], "CreateClient" => [], "Key" => Hash.new{|h,k| h[k] = []}} update_custom_handlers_matcher @plugin_config = PluginConfigHash.new @children = $children # reset key bindings LOGGER.info "Resetting key bindings." @ixp_conn.write "/def/keys", "\n" @key_substitutions = { "MODKEY" => "Mod1", "UP" => "k", "DOWN" => "j", "LEFT" => "h", "RIGHT" => "l" } @view_history_decay = 0.8 @view_history_prev_bias = 0.4 @view_transition_table = Hash.new{|h,k| h[k] = Hash.new{|h2,k2| h2[k2] = 0} } @view_transitions = Hash.new{|h,k| h[k] = 0} @prev_view = curr_view @view_history = [curr_view] @view_history_index = 0 @managed_bar_applets = [] LOGGER.info "Loading configuration" LOGGER.info "Plugin specified settings..." load_settings = lambda do |namespace, settings| settings.each_pair do |name, setting| LOGGER.info "Loading settings #{name} from #{namespace}" setting.block.call(self) end end # Load from standard first if Plugin.registered_plugins.has_key?("standard") Plugin.registered_plugins["standard"].each{|plugin| load_settings.call("standard", plugin.settings) } end Plugin.registered_plugins.each_pair do |namespace, plugins| next if namespace == "standard" plugins.each{|plugin| load_settings.call(namespace, plugin.settings) } end instance_eval(&block) if block_given? end def write(file, contents) @ixp_conn.write(file, contents.to_s, IXP::OWRITE) end def read(file) @ixp_conn.read(file) end def remove(file) @ixp_conn.remove(file) end def create(file) @ixp_conn.create(file) end def update_custom_handlers_matcher @custom_handlers_re = /^#{(@procs.keys - DEFAULT_EVENTS).join("|")}(\s|$)/ end private :update_custom_handlers_matcher def from(namespace, &block) conf = self Class.new do define_method(:use_binding) do |name, *overriding_keys| conf.use_binding("#{namespace}:#{name}", *overriding_keys) end define_method(:use_bar_applet) do |name, *opts| conf.use_bar_applet("#{namespace}:#{name}", *opts) end end.new.instance_eval(&block) end def use_binding(binding_name, *overriding_keys) md = /([^:]+):(.+)/.match(binding_name) unless md LOGGER.error "Ignoring illegal binding name #{binding_name}." return end namespace, name = md.captures if (plugins = Plugin.registered_plugins[namespace]).empty? LOGGER.error "Unknown plugin #{namespace}" return end key_bindings = plugins.inject([]){|s,x| s + [x.bindings[name]]}.compact if key_bindings.empty? LOGGER.error "Key binding #{name} not found in #{namespace}." return end if key_bindings.size > 1 LOGGER.debug "Key binding #{name} defined more than once in #{namespace}." LOGGER.debug "Keeping last definition." end key_binding = key_bindings.last keys, block = key_binding.keys, key_binding.block actual_keys = overriding_keys.empty? ? keys : overriding_keys LOGGER.info "Importing key binding #{namespace}:#{name} as #{actual_keys.join(' ')}" on_key(*actual_keys, &block) end def use_bar_applet(bar_applet_name, position = nil, data = nil) md = /([^:]+):(.+)/.match(bar_applet_name) unless md LOGGER.error "Ignoring illegal applet name #{bar_applet_name}." return end namespace, name = md.captures if (plugins = Plugin.registered_plugins[namespace]).empty? LOGGER.error "Unknown plugin #{namespace}" return end bar_applets = plugins.inject([]){|s,x| s + [x.bar_applets[name]]}.compact if bar_applets.empty? LOGGER.error "Bar applet #{name} not found in #{namespace}." return end if bar_applets.size > 1 LOGGER.debug "Bar applet #{name} defined more than once in #{namespace}." LOGGER.debug "Keeping last definition." end bar_applet = bar_applets.last position = position || bar_applet.position initial_data = data || bar_applet.initial_data block = bar_applet.block LOGGER.info "Importing bar applet #{namespace}:#{name} at position #{position}" setup_bar("%03d_#{name}" % position, normcolors, initial_data, &block) end def register(type, param1 = nil, param2 = nil, &block) case param1 when nil handler = EventHandler.new(type, &block) else handler = EventHandler.new(type) do |*args| if param1 === args[0] && (!param2 || param2 === args[1]) block.call(*args) end end end (@procs[type] ||= []) << handler update_custom_handlers_matcher handler end def unregister(handler) case handler when KeyHandler @procs["Key"][handler.key].delete handler else @procs[handler.type].delete handler end update_custom_handlers_matcher end def register_key_bindings LOGGER.info "Setting /def/keys" @ixp_conn.write "/def/keys", @procs["Key"].keys.join("\n") end def main_loop @ixp_conn.write "/event", "Bye\n" register_key_bindings times = [] loop do begin times.unshift Time.new times = times[0,5] if times[4] && times[0] - times[4] < 0.1 cleanup LOGGER.error "wmiiwm seems to be gone, leaving" exit! end # wait for events LOGGER.debug "Opening /event" IXP::Client.new(ENV["WMII_ADDRESS"]).foreach("/event") do |line| begin LOGGER.debug "Got #{line.inspect}" if line case line when /^(BarClick|ClientClick)\s+(\S+)\s+(\S+)$/ @procs[$1].each{|x| x.call($2, $3.to_i)} when /^(ClientFocus|CreateClient)\s+(\S+)/ @procs[$1].each{|x| x.call($2.to_i)} when /^Key (\S+)$/ @procs["Key"][$1].each{|x| x.call(self, x)} if @procs["Key"].has_key?($1) when /^Bye|^Starting/ LOGGER.info "Cleanup..." cleanup LOGGER.info "QUIT" create "/bar/QUIT" rescue nil write "/bar/QUIT/data", "QUIT" exit! when @custom_handlers_re event, *args = *line.chomp.split(/\s/) LOGGER.debug "Custom event #{event}" @procs[event].each{|x| x.call(*args)} end rescue IOError, SystemCallError # terminate #foreach raise rescue StandardError => e LOGGER.debug e.inspect LOGGER.debug e.message e.backtrace.each{|x| LOGGER.debug x} end end rescue IXP::BusyError # this is a child process, it must die ASAP LOGGER.debug "child process #{Process.pid} exiting." exit! rescue Exception # ignore LOGGER.debug "Ignoring #{$!.inspect}" end end end # not worth meta-programming def on_barclick(name = nil, button = nil, &b) register("BarClick", name, button, &b) end def on_clientclick(client = nil, button = nil, &b) register("ClientClick", client, button, &b) end def on_clientfocus(client = nil, &b); register("ClientFocus", client, &b) end def on_createclient(client = nil, &b); register("CreateClient", client, &b) end def on_key(*aliasedkeys, &block) handlers = [] aliasedkeys.each do |aliasedkey| key = aliasedkey.clone handler = KeyHandler.new(key, &block) @key_substitutions.sort_by{|kalias, actual| kalias.size}.reverse.each do |kalias, val| key.gsub!(/\b#{Regexp.escape(kalias)}\b/, val) end LOGGER.info "Registering #{aliasedkey} => #{key}." @procs["Key"][key] << handler handlers << handler end return *handlers end def clean_fork(&b) @children << fork(&b) Process.detach @children.last LOGGER.debug "clean_fork(): #{@children.last}" end def cleanup LOGGER.info "Removing managed bar applets: #{@managed_bar_applets.join(' ')}" @managed_bar_applets.each do |name| remove "/bar/#{name}" rescue nil end LOGGER.debug "This is #{Process.pid}, killing #{@children.inspect}" @children.each do |pid| begin Process.kill("TERM", pid) rescue Exception end end end ####{{{ Configuration methods extend ConfigurationHelper def_conf_var :view_history_decay def_conf_var :view_history_prev_bias def_wmii_var :border, :colmode, :colwidth, :rules, :grabmod def_wmii_var(:selcolors){|colors| ENV["WMII_SELCOLORS"] = colors.to_s } def_wmii_var(:normcolors){|colors| ENV["WMII_NORMCOLORS"] = colors.to_s } def_wmii_var(:font){|font| ENV["WMII_FONT"] = font.to_s } def key_subs(associations) unless Hash === associations raise ArgumentError, "key_subs takes a hash with alias => actual_key associations" end associations.each_pair do |key, val| @key_substitutions[key.to_s] = val.to_s end end class BarButton attr_reader :name def initialize(wmiiconfig, name) @wmiiconfig = wmiiconfig @name = name end def on_click(key = nil, &block) raise "block wanted" unless block @wmiiconfig.on_barclick(name, key, &block) end def data; @wmiiconfig.read("/bar/#{@name}/data") end def data=(x); @wmiiconfig.write("/bar/#{@name}/data", x) end # Returns a triplet [fgcolor, bgcolor, bordercolor] # where each element is itself a RGB triplet (0 to 255). def colors @wmiiconfig.read("/bar/#{@name}/colors").split(/\s+/).map do |txt| txt.scan(/[a-fA-F0-9]{2}/).map{|hex| hex.to_i(16)} end end # Takes a triplet [fgcolor, bgcolor, bordercolor] # where each element is itself a RGB triplet (0 to 255). def colors=(col_arrays) col_arrays = col_arrays.map do |r,g,b| [ r < 0 ? 0 : (r > 255 ? 255 : r), g < 0 ? 0 : (g > 255 ? 255 : g), b < 0 ? 0 : (b > 255 ? 255 : b) ] end txt = col_arrays.map{|vals| "#" + vals.map{|y| "%02X" % y}.join("")}.join(" ") @wmiiconfig.write("/bar/#{@name}/colors", txt) end %w[fgcolor bgcolor bordercolor].each_with_index do |name, idx| define_method(name){ self.colors[idx] } define_method("#{name}=") do |col_arr| old = self.colors old[idx] = col_arr self.colors = old end end end # convenience methods def setup_bar(name, colors = normcolors, data = "", &block) LOGGER.info "Setting up bar applet #{name}" @ixp_conn.create "/bar/#{name}" @ixp_conn.write "/bar/#{name}/colors", colors @ixp_conn.write "/bar/#{name}/data", data.chomp @managed_bar_applets << name if block_given? yield self, BarButton.new(self, name) end end def normalize(tags) tags = tags.split(/\+/) unless Array === tags tags.map{|x| x.chomp}.sort.uniq.compact.grep(/./).join("+") end def views @ixp_conn.read("/tags").split(/\n/).grep(/./) end def views_intellisort views.sort.sort_by do |view| curr = curr_view if @view_transition_table[curr].has_key?(view) prob = @view_transition_table[curr][view] prob = [prob, @view_history_prev_bias].max if view == @prev_view [0, - prob, view] # handle ties elsif view == @prev_view [0, -@view_history_prev_bias, view] else [1, 0, view] end end end def curr_view @ixp_conn.read("/view/name").chomp end def view_history_forward allviews = views viewname = curr_view loop do @view_history_index = [@view_history.size - 1, @view_history_index + 1].min viewname = @view_history[@view_history_index] break if views.include? viewname || @view_history_index == @view_history.size - 1 end if viewname != curr_view LOGGER.info("History: forward to #{viewname.inspect}") @ixp_conn.write "/ctl", "view #{viewname}" end end def view_history_back allviews = views viewname = curr_view loop do @view_history_index = [0, @view_history_index - 1].max viewname = @view_history[@view_history_index] break if views.include? viewname || @view_history_index == 0 end if viewname != curr_view LOGGER.info("History: back to #{viewname.inspect}") @ixp_conn.write "/ctl", "view #{viewname}" end end def set_curr_view(viewname) viewname = viewname.to_s.chomp return unless views.include? viewname LOGGER.info("Switching to #{viewname.inspect}") @prev_view = curr_view @view_history[@view_history_index + 1..@view_history.size] = viewname @view_history_index += 1 @ixp_conn.write "/ctl", "view #{viewname}" n_trans = (@view_transitions[@prev_view] += 1) table = @view_transition_table[@prev_view] table.each_pair do |key, val| table[key] = val * @view_history_decay * n_trans / (n_trans + 1) end table[viewname] += (1.0 - @view_history_decay) + 1.0 / (n_trans + 1) total = table.values.inject{|s,x| s+x} table.each_key{|k| table[k] /= total } LOGGER.debug "Trans. table (order 1): #{@view_transition_table.inspect}" end alias_method :curr_view=, :set_curr_view alias_method :view, :set_curr_view def curr_view_index views.index(curr_view) end def curr_client_tags @ixp_conn.read("/view/sel/sel/tags").split(/\+/).reject{|x| x.empty?}.compact.uniq.sort end def curr_client_tags=(tags) @ixp_conn.write("/view/sel/sel/tags", normalize(tags)) end alias_method :set_curr_client_tags, :curr_client_tags= def retag_curr_client(new_tag) return if /^\s*$/ =~ new_tag old_tags = curr_client_tags new_tags = case new_tag when /^\+/ : old_tags + [new_tag[1..-1]] when /^-/ : old_tags - [new_tag[1..-1]] else [new_tag] end new_tags = normalize(new_tags) LOGGER.info "Retagging #{old_tags.inspect} => #{new_tags.inspect})" set_curr_client_tags new_tags end def retag_curr_client_ns(new_tag) return if /^\s*$/ =~ new_tag old_tags = curr_client_tags namespace = old_tags.reject{|x| /^\d+$|:/ =~ x}.last new_tags = case new_tag when /^\+/ : old_tags + ["#{namespace}:#{new_tag[1..-1]}"] when /^\-/ : old_tags - ["#{namespace}:#{new_tag[1..-1]}"] else ["#{namespace}:#{new_tag}"] end set_curr_client_tags new_tags end def wmiimenu(options = nil, &block) rd, wr = IO.pipe rd.fcntl Fcntl::F_SETFD, Fcntl::FD_CLOEXEC wr.fcntl Fcntl::F_SETFD, Fcntl::FD_CLOEXEC Thread.new do pid = fork do rd.close chosen = IO.popen("wmiimenu", "r+") do |f| f.puts options unless options.nil? f.close_write f.read end.chomp LOGGER.debug "wmiimenu(#{chosen.inspect}) finished" begin wr.print chosen wr.close rescue Exception # catch EPIPE etc. end yield chosen if block_given? exit! end Process.wait(pid) end wr.close def rd.value; ret = (gets||"").chomp; close; ret end rd end def action_list Dir["#{WMIIRC_HOME}/*"].select do |f| File.file?(f) && File.executable?(f) end.map{|f| File.basename(f)}.sort.uniq end def condition(&block) c = lambda(&block) def c.===(*x); call(*x) end c end end # Configuration end # WMII include WMII run __END__ # {{{ ======== ruby-wmii CONFIGURATION BEGINS HERE ============== # Set the log level # It defaults to Logger::INFO. # Set to Logger::DEBUG for extra verbosity. #LOGGER.level = Logger::DEBUG # programs to run when wmiirc starts # one per line, they're run sequentially right before the main loop begins START_PROGS = < dict /XMMS.*/ -> ~ /Gimp.*/ -> ~ /MPlayer.*/ -> ~ /XForm.*/ -> ~ /XSane.*/ -> ~ /fontforge.*/ -> ~ /.*/ -> ! /.*/ -> 1 EOF # Translate the following names in the on_key and use_binding definitions. key_subs :MODKEY => :Mod1, :MODKEY2 => :Mod4, :LEFT => :h, :RIGHT => :l, :UP => :k, :DOWN => :j # Constant used by the intellisort tag selection mechanism # set it to 0.0 <= value <= 1.0 # Lower values make recent choices more likely (modified first order # markovian process with exponential decay): # 0.0 means that only the last transition counts (all others forgotten) # 1.0 means that the probabilities aren't biased to make recent choices more # likely view_history_decay 0.8 # Favor the view we came from in intellisort. # 1.0: that view is the first choice # 0.0: that view comes after all views with non-zero transition probability, # but before all views we haven't yet jumped to from the current one view_history_prev_bias 0.4 # {{{ Plugin config # Uncomment and change to override default on_click actions for the status # bar #plugin_config["standard:status"]["left_click_action"] = lambda{ system "xeyes" } #plugin_config["standard:status"]["right_click_action"] = lambda{ system "xeyes" } #plugin_config["standard:status"]["middle_click_action"] = lambda{ system "xeyes" } plugin_config["standard:status"]["refresh_time"] = 1 # Uncomment and change to override default text #currload = nil #Thread.new{ loop { currload = `uptime`.chomp.sub(/.*: /,"").gsub(/,/,""); sleep 10 } } #plugin_config["standard:status"]["text_proc"] = lambda do # "#{Time.new.strftime("%d/%m/%Y %X %Z")} #{currload}" #end plugin_config["standard"]["x-terminal-emulator"] = "x-terminal-emulator" plugin_config["standard:actions"]["history_size"] = 3 # set to 0 to disable plugin_config["standard:programs"]["history_size"] = 5 # set to 0 to disable plugin_config["standard:volume"]["mixer"] = "Master" plugin_config["standard:mode"]["mode_toggle_keys"] = ["MODKEY2-space"] plugin_config["standard:battery-monitor"]["statefile"] = '/proc/acpi/battery/BAT0/state' plugin_config["standard:battery-monitor"]["infofile"] = '/proc/acpi/battery/BAT0/info' plugin_config["standard:battery-monitor"]["low"] = 5 plugin_config["standard:battery-monitor"]["low_action"] = 'echo "Low battery" | xmessage -center -buttons quit:0 -default quit -file -' plugin_config["standard:battery-monitor"]["critical"] = 1 plugin_config["standard:battery-monitor"]["critical_action"] = 'echo "Critical battery" | xmessage -center -buttons quit:0 -default quit -file -' # Allows you to override the default internal actions and define new ones: #plugin_config["standard:actions"]["internal"].update({ # "screenshot" => nil, # remove default screenshot action # "google" => lambda do |wmii, *selection| # require 'cgi' # if selection && !selection.empty? # selection = CGI.escape(selection.join(" ")) # else # selection = CGI.escape(%!#{`wmiipsel`.strip}!) # end # url = "http://www.google.com/search?q=#{selection}" # case browser = ENV["BROWSER"] # when nil: system "wmiisetsid /etc/alternatives/x-www-browser '#{url}' &" # else system "wmiisetsid #{browser} '#{url}' &" # end # end, # "foo" => lambda do |wmii, *args| # IO.popen("xmessage -file -", "w"){|f| f.puts "Args: #{args.inspect}"; f.close_write } # end #}) #{{{ Import bindings and bar applets from "standard" do use_bar_applet "volume", 999 use_bar_applet "mode", 900 use_bar_applet "status", 100 #use_bar_applet "cpuinfo", 150 #use_bar_applet "mpd", 110 #use_bar_applet "battery-monitor" use_binding "dict-lookup" use_binding "execute-program-with-tag" use_binding "execute-action" use_binding "execute-program" (0..9).each{|k| use_binding "numeric-jump-#{k}" } use_binding "tag-jump" use_binding "retag" use_binding "retag-jump" use_binding "namespace-retag" use_binding "namespace-retag-jump" (('a'..'z').to_a+('0'..'9').to_a).each{|k| use_binding "letter-jump-#{k}" } (0..9).each{|k| use_binding "numeric-retag-#{k}" } (('a'..'z').to_a+('0'..'9').to_a).each{|k| use_binding "letter-retag-#{k}" } use_binding "move-prev" use_binding "move-next" use_binding "namespace-move-prev" use_binding "namespace-move-next" use_binding "history-move-forward" use_binding "history-move-back" use_binding "bookmark" use_binding "bookmark-open" end # {{{ del.icio.us bookmark import #plugin_config["standard:bookmark"]["del.icio.us-user"] = 'myusername' ## WORD OF CAUTION! ## Before setting the sync mode to :bidirectional, make sure ## that your bookmarks.txt file contains all the bookmarks you want to keep, ## because all the del.icio.us bookmarks not listed there will be deleted! ## You can import your del.icio.us bookmarks by setting it to ## :unidirectional and reloading wmiirc ("ALT-a wmiirc" by default). ## Allow some time for the bookmarks to be downloaded (wait until you see ## "Done importing bookmarks from del.icio.us." in ## $HOME/.wmii-3/wmiirc.log). You can then change the mode to :bidirectional ## and reload wmiirc. From that point on, the bookmark lists will be ## synchronized, so local modifications will be propagated to del.icio.us, ## and if you remove a bookmark locally it will also be deleted on ## del.icio.us. #plugin_config["standard:bookmark"]["del.icio.us-mode"] = :bidirectional #plugin_config["standard:bookmark"]["del.icio.us-share"] = true ## Sets the encoding used to: # * store the bookmark descriptions in bookmarks.txt # * present choices through wmiimenu # Please make sure your bookmarks.txt uses the appropriate encoding before # setting the next line. If you had already imported bookmarks from # del.icio.us, they will be stored UTF-8, so you might want to # recode utf-8..NEW_ENCODING bookmarks.txt # # If left to nil, bookmarks imported from del.icio.us will be in UTF-8, and # those created locally will be in the encoding specified by your locale. #plugin_config["standard:bookmark"]["encoding"] = 'KOI8-R' # Allows you to override the default bookmark protocols and define new ones: #plugin_config["standard:bookmark"]["protocols"].update({ # 'http' => nil, # remove default http protocol # 'ssh' => { # :open_urls => lambda do |wmii,bms| # term = wmii.plugin_config["standard"]["x-terminal-emulator"] || "xterm" # bms.each do |bm| # uri = bm[:uri] # ssh_host = uri.host # ssh_host = "#{uri.user}@" + ssh_host unless uri.user.nil? # ssh_port = "-p #{uri.port}" unless uri.port.nil? # system "wmiisetsid #{term} -T '#{bm[:bm].url}' -e 'ssh #{ssh_host} #{ssh_port} || read' &" # end # end, # :get_title => lambda do |wmii,uri| # title = uri.host # title = "#{uri.user}@" + title unless uri.user.nil? # title << ":#{uri.port.to_s}" unless uri.port.nil? # title # end # }, # 'pdf' => { # :open_urls => lambda do |wmii,bms| # bms.each do |bm| # path = URI.unescape(bm[:uri].path) # LOGGER.info "Opening #{path} with xpdf." # system "wmiisetsid xpdf '#{path}' &" # end # end, # :get_title => lambda do |wmii,uri| # fname = File.basename(URI.unescape(uri.to_s)).gsub(/\.\S+$/,"") # [fname, fname.downcase, fname.capitalize] # end # } #}) # {{{ Click on view bars on_barclick(/./, MOUSE_BUTTON_LEFT){|name,| view name} on_barclick(/./, MOUSE_BUTTON_RIGHT){|name,| view name} # {{{ Tag all browser instances as 'web' in addition to the current tag browsers = %w[Firefox Konqueror] browser_re = /^#{browsers.join("|")}/ on_createclient(condition{|c| browser_re =~ read("/client/#{c}/class")}) do |cid| write("/client/#{cid}/tags", normalize(read("/client/#{cid}/tags") + "+web")) end #{{{ Simpler key bindings --- not defined in plugins on_key("MODKEY-LEFT"){ write "/view/ctl", "select prev" } on_key("MODKEY-RIGHT"){ write "/view/ctl", "select next" } on_key("MODKEY-DOWN"){ write "/view/sel/ctl", "select next" } on_key("MODKEY-UP"){ write "/view/sel/ctl", "select prev" } on_key("MODKEY-space"){ write "/view/ctl", "select toggle" } on_key("MODKEY-d"){ write "/view/sel/mode", "default" } on_key("MODKEY-s"){ write "/view/sel/mode", "stack" } on_key("MODKEY-m"){ write "/view/sel/mode", "max" } on_key("MODKEY-f"){ write "/view/0/sel/geom", "0 0 east south" } on_key("MODKEY-i"){ write "/view/sel/sel/geom", "+0 +0 +0 +48" } on_key("MODKEY-Shift-i"){ write "/view/sel/sel/geom", "+0 +0 +0 -48" } on_key("MODKEY-Return") do term = plugin_config["standard"]["x-terminal-emulator"] || "xterm" system "wmiisetsid #{term} &" end on_key("MODKEY-Shift-LEFT"){ write "/view/sel/sel/ctl", "sendto prev" } on_key("MODKEY-Shift-RIGHT"){ write "/view/sel/sel/ctl", "sendto next" } on_key("MODKEY-Shift-DOWN"){ write "/view/sel/sel/ctl", "swap down" } on_key("MODKEY-Shift-UP"){ write "/view/sel/sel/ctl", "swap up" } on_key("MODKEY-Shift-space"){ write "/view/sel/sel/ctl", "sendto toggle" } on_key("MODKEY-Shift-c"){ write "/view/sel/sel/ctl", "kill" } on_key("MODKEY-r"){ view prev_view } on_key("MODKEY-Control-LEFT") { write "/view/sel/sel/ctl", "swap prev" } on_key("MODKEY-Control-RIGHT"){ write "/view/sel/sel/ctl", "swap next" } # {{{ ======== CONFIGURATION ENDS HERE ============== end