awesome/lib/awful/rules.lua

338 lines
9.9 KiB
Lua

---------------------------------------------------------------------------
--- Apply rules to clients at startup.
--
-- @author Julien Danjou <julien@danjou.info>
-- @copyright 2009 Julien Danjou
-- @release @AWESOME_VERSION@
-- @module awful.rules
---------------------------------------------------------------------------
-- Grab environment we need
local client = client
local screen = screen
local table = table
local type = type
local ipairs = ipairs
local pairs = pairs
local atag = require("awful.tag")
local rules = {}
--[[--
This is the global rules table.
You should fill this table with your rule and properties to apply.
For example, if you want to set xterm maximized at startup, you can add:
{ rule = { class = "xterm" },
properties = { maximized_vertical = true, maximized_horizontal = true } }
If you want to set mplayer floating at startup, you can add:
{ rule = { name = "MPlayer" },
properties = { floating = true } }
If you want to put Firefox on a specific tag at startup, you can add:
{ rule = { instance = "firefox" },
properties = { tag = mytagobject } }
Alternatively, you can specify the tag by name:
{ rule = { instance = "firefox" },
properties = { tag = "3" } }
If you want to put Thunderbird on a specific screen at startup, use:
{ rule = { instance = "Thunderbird" },
properties = { screen = 1 } }
Assuming that your X11 server supports the RandR extension, you can also specify
the screen by name:
{ rule = { instance = "Thunderbird" },
properties = { screen = "VGA1" } }
If you want to put Emacs on a specific tag at startup, and immediately switch
to that tag you can add:
{ rule = { class = "Emacs" },
properties = { tag = mytagobject, switchtotag = true } }
If you want to apply a custom callback to execute when a rule matched,
for example to pause playing music from mpd when you start dosbox, you
can add:
{ rule = { class = "dosbox" },
callback = function(c)
awful.spawn('mpc pause')
end }
Note that all "rule" entries need to match. If any of the entry does not
match, the rule won't be applied.
If a client matches multiple rules, they are applied in the order they are
put in this global rules table. If the value of a rule is a string, then the
match function is used to determine if the client matches the rule.
If the value of a property is a function, that function gets called and
function's return value is used for the property.
To match multiple clients to a rule one need to use slightly different
syntax:
{ rule_any = { class = { "MPlayer", "Nitrogen" }, instance = { "xterm" } },
properties = { floating = true } }
To match multiple clients with an exception one can couple `rules.except` or
`rules.except_any` with the rules:
{ rule = { class = "Firefox" },
except = { instance = "Navigator" },
properties = {floating = true},
},
{ rule_any = { class = { "Pidgin", "Xchat" } },
except_any = { role = { "conversation" } },
properties = { tag = "1" }
}
{ rule = {},
except_any = { class = { "Firefox", "Vim" } },
properties = { floating = true }
}
]]--
rules.rules = {}
--- Check if a client matches a rule.
-- @client c The client.
-- @tab rule The rule to check.
-- @treturn bool True if it matches, false otherwise.
function rules.match(c, rule)
if not rule then return false end
for field, value in pairs(rule) do
if c[field] then
if type(c[field]) == "string" then
if not c[field]:match(value) and c[field] ~= value then
return false
end
elseif c[field] ~= value then
return false
end
else
return false
end
end
return true
end
--- Check if a client matches any part of a rule.
-- @client c The client.
-- @tab rule The rule to check.
-- @treturn bool True if at least one rule is matched, false otherwise.
function rules.match_any(c, rule)
if not rule then return false end
for field, values in pairs(rule) do
if c[field] then
for _, value in ipairs(values) do
if c[field] == value then
return true
elseif type(c[field]) == "string" and c[field]:match(value) then
return true
end
end
end
end
return false
end
--- Does a given rule entry match a client?
-- @client c The client.
-- @tab entry Rule entry (with keys `rule`, `rule_any`, `except` and/or
-- `except_any`).
-- @treturn bool
function rules.matches(c, entry)
return (rules.match(c, entry.rule) or rules.match_any(c, entry.rule_any)) and
(not rules.match(c, entry.except) and not rules.match_any(c, entry.except_any))
end
--- Get list of matching rules for a client.
-- @client c The client.
-- @tab _rules The rules to check. List with "rule", "rule_any", "except" and
-- "except_any" keys.
-- @treturn table The list of matched rules.
function rules.matching_rules(c, _rules)
local result = {}
for _, entry in ipairs(_rules) do
if (rules.matches(c, entry)) then
table.insert(result, entry)
end
end
return result
end
--- Check if a client matches a given set of rules.
-- @client c The client.
-- @tab _rules The rules to check. List of tables with `rule`, `rule_any`,
-- `except` and `except_any` keys.
-- @treturn bool True if at least one rule is matched, false otherwise.
function rules.matches_list(c, _rules)
for _, entry in ipairs(_rules) do
if (rules.matches(c, entry)) then
return true
end
end
return false
end
--- Apply awful.rules.rules to a client.
-- @client c The client.
function rules.apply(c)
local props = {}
local callbacks = {}
for _, entry in ipairs(rules.matching_rules(c, rules.rules)) do
if entry.properties then
for property, value in pairs(entry.properties) do
props[property] = value
end
end
if entry.callback then
table.insert(callbacks, entry.callback)
end
end
rules.execute(c, props, callbacks)
end
local function add_to_tag(c, t)
if not t then return end
local tags = c:tags()
table.insert(tags, t)
c:tags(tags)
end
--- Extra rules properties.
--
-- These properties are used in the rules only and are not sent to the client
-- afterward.
--
-- To add a new properties, just do:
--
-- function awful.rules.extra_properties.my_new_property(c, value)
-- -- do something
-- end
--
-- @tfield table awful.rules.extra_properties
rules.extra_properties = {}
--- Extra high priority properties.
--
-- Some properties, such as anything related to tags, geometry or focus, will
-- cause a race condition if set in the main property section. This is why
-- they have a section for them.
--
-- To add a new properties, just do:
--
-- function awful.rules.high_priority_properties.my_new_property(c, value)
-- -- do something
-- end
--
-- @tfield table awful.rules.high_priority_properties
rules.high_priority_properties = {}
rules.delayed_properties = {
focus = true,
switchtotag = true,
}
--- Apply properties and callbacks to a client.
-- @client c The client.
-- @tab props Properties to apply.
-- @tab[opt] callbacks Callbacks to apply.
function rules.execute(c, props, callbacks)
-- Some properties need to be handled first. For example, many properties
-- depend that the client is tagged, this isn't yet the case.
for prop, handler in pairs(rules.high_priority_properties) do
local value = props[prop]
if value ~= nil then
if type(value) == "function" then
value = value(c, props)
end
handler(c, props[prop])
end
end
for property, value in pairs(props) do
if property ~= "focus" and type(value) == "function" then
value = value(c)
end
-- Some properties are handled elsewhere
if not rules.high_priority_properties[property] and not rules.delayed_properties[property] then
if property == "tag" then
local t = value
if type(t) == "string" then
t = atag.find_by_name(props.screen, t)
elseif type(t) == "function" then
t = value(c, props)
end
if t then
c.screen = t.screen
c:tags{ t }
end
elseif property == "height" or property == "width" or
property == "x" or property == "y" then
local geo = c:geometry();
geo[property] = value
c:geometry(geo);
elseif rules.extra_properties[property] then
rules.extra_properties[property](c, value)
elseif type(c[property]) == "function" then
c[property](c, value)
else
c[property] = value
end
end
end
-- Only do this after the tag has been (possibly) set
if props.switchtotag and c.first_tag then
c.first_tag:view_only()
end
-- Apply all callbacks.
if callbacks then
for _, callback in pairs(callbacks) do
callback(c)
end
end
-- Do this at last so we do not erase things done by the focus signal.
if props.focus and (type(props.focus) ~= "function" or props.focus(c)) then
c:emit_signal('request::activate', "rules", {raise=true})
end
end
function rules.completed_with_payload_callback(c, props)
rules.execute(c, props, type(props.callback) == "function" and
{props.callback} or props.callback )
end
client.connect_signal("spawn::completed_with_payload", rules.completed_with_payload_callback)
client.connect_signal("manage", rules.apply)
return rules
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80