diff --git a/CMakeLists.txt b/CMakeLists.txt index 9285124c..789ff98c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -289,7 +289,12 @@ if(GENERATE_DOC) file(COPY ${SOURCE_DIR}/docs/ldoc.css DESTINATION ${BUILD_DIR}/docs) add_custom_target(ldoc ALL - DEPENDS ${BUILD_DIR}/doc/index.html ${BUILD_DIR}/docs/ldoc.css) + DEPENDS + ${BUILD_DIR}/doc/index.html + ${BUILD_DIR}/docs/ldoc.css + generate_awesomerc + ) + if (STRICT_TESTS) set(ldoc_args --fatalwarnings .) set(ldoc_desc_suffix " (fatal warnings)") diff --git a/awesomeConfig.cmake b/awesomeConfig.cmake index ea1d6057..1d4a4e5e 100644 --- a/awesomeConfig.cmake +++ b/awesomeConfig.cmake @@ -354,13 +354,39 @@ add_custom_target(lgi-check-run ALL # {{{ Generate some aggregated documentation from lua script -file(MAKE_DIRECTORY ${BUILD_DIR}/script_files/) +add_custom_target(setup_directories DEPENDS lgi-check-run) + +add_custom_command(TARGET setup_directories + COMMAND ${CMAKE_COMMAND} -E make_directory ${BUILD_DIR}/script_files/ + COMMAND ${CMAKE_COMMAND} -E make_directory ${BUILD_DIR}/docs/common/ + COMMAND ${CMAKE_COMMAND} -E make_directory ${BUILD_DIR}/doc/images/ + COMMAND ${CMAKE_COMMAND} -E copy ${SOURCE_DIR}/docs/_parser.lua ${BUILD_DIR}/docs/ +) add_custom_command( OUTPUT ${BUILD_DIR}/docs/06-appearance.md COMMAND lua ${SOURCE_DIR}/docs/06-appearance.md.lua ${BUILD_DIR}/docs/06-appearance.md - DEPENDS lgi-check-run + DEPENDS + lgi-check-run + ${SOURCE_DIR}/docs/06-appearance.md.lua + ${SOURCE_DIR}/docs/_parser.lua +) + +add_custom_command( + OUTPUT ${BUILD_DIR}/docs/common/rules_index.ldoc + COMMAND lua ${SOURCE_DIR}/docs/build_rules_index.lua + ${BUILD_DIR}/docs/common/rules_index.ldoc + + # Cheap trick until the ldoc `configure_file` is ported to be a build + # step rather than part of cmake. + COMMAND ${CMAKE_COMMAND} -E copy ${BUILD_DIR}/docs/common/rules_index.ldoc + ${SOURCE_DIR}/docs/common/rules_index.ldoc + + DEPENDS + lgi-check-run + ${SOURCE_DIR}/docs/build_rules_index.lua + ${SOURCE_DIR}/docs/_parser.lua ) add_custom_command( @@ -370,6 +396,7 @@ add_custom_command( ${BUILD_DIR}/docs/05-awesomerc.md ${SOURCE_DIR}/awesomerc.lua ${BUILD_DIR}/awesomerc.lua ${BUILD_DIR}/script_files/rc.lua + DEPENDS ${SOURCE_DIR}/awesomerc.lua ${SOURCE_DIR}/docs/05-awesomerc.md.lua ) add_custom_command( @@ -379,15 +406,20 @@ add_custom_command( # Create a target for the auto-generated awesomerc.lua and other files add_custom_target(generate_awesomerc DEPENDS + setup_directories ${BUILD_DIR}/awesomerc.lua ${BUILD_DIR}/script_files/theme.lua ${BUILD_DIR}/script_files/rc.lua ${SOURCE_DIR}/awesomerc.lua ${BUILD_DIR}/docs/06-appearance.md ${SOURCE_DIR}/docs/05-awesomerc.md.lua + ${SOURCE_DIR}/docs/build_rules_index.lua + ${BUILD_DIR}/docs/common/rules_index.ldoc ${SOURCE_DIR}/docs/sample_theme.lua ${SOURCE_DIR}/docs/sample_files.lua ${SOURCE_DIR}/awesomerc.lua + ${awesome_c_configure_files} + ${awesome_lua_configure_files} ) diff --git a/docs/06-appearance.md.lua b/docs/06-appearance.md.lua index 48fe4325..cfb583b9 100644 --- a/docs/06-appearance.md.lua +++ b/docs/06-appearance.md.lua @@ -1,174 +1,14 @@ #! /usr/bin/lua local args = {...} +local parser = require("docs._parser") local gio = require("lgi").Gio local gobject = require("lgi").GObject local glib = require("lgi").GLib -local name_attr = gio.FILE_ATTRIBUTE_STANDARD_NAME -local type_attr = gio.FILE_ATTRIBUTE_STANDARD_TYPE +local paths = parser.get_all_files("./lib/", "lua", parser.get_all_files("./", "c")) --- Like pairs(), but iterate over keys in a sorted manner. Does not support --- modifying the table while iterating. -local function sorted_pairs(t) - -- Collect all keys - local keys = {} - for k in pairs(t) do - table.insert(keys, k) - end - - table.sort(keys) - - -- return iterator function - local i = 0 - return function() - i = i + 1 - if keys[i] then - return keys[i], t[keys[i]] - end - end -end - --- Recursive file scanner -local function get_all_files(path, ext, ret) - ret = ret or {} - local enumerator = gio.File.new_for_path(path):enumerate_children( - table.concat({name_attr, type_attr}, ",") , 0, nil, nil - ) - - for file in function() return enumerator:next_file() end do - local file_name = file:get_attribute_as_string(name_attr) - local file_type = file:get_file_type() - if file_type == "REGULAR" and file_name:match(ext or "") then - table.insert(ret, enumerator:get_child(file):get_path()) - elseif file_type == "DIRECTORY" then - get_all_files(enumerator:get_child(file):get_path(), ext, ret) - end - end - - return ret -end - -local function path_to_module(path) - for _, module in ipairs { - "awful", "wibox", "gears", "naughty", "menubar", "beautiful" - } do - local match = path:match("/"..module.."/([^.]+).lua") - if match then - return module.."."..match:gsub("/",".") - end - end - - error("Cannot figure out module for " .. tostring(path)) -end - -local function path_to_html(path) - local mod = path_to_module(path):gsub(".init", "") - local f = assert(io.open(path)) - while true do - local line = f:read() - if not line then break end - if line:match("@classmod") then - f:close() - return "../classes/".. mod ..".html" - end - if line:match("@module") or line:match("@submodule") then - f:close() - return "../libraries/".. mod ..".html" - end - end - f:close() - - error("Cannot figure out if module or class: " .. tostring(path)) -end - -local function get_link(file, element) - return table.concat { - "", - element:match("[. ](.+)"), - "" - } -end - -local all_files = get_all_files("./lib/", "lua") -table.sort(all_files) - -local beautiful_vars = {} - --- Find all @beautiful doc entries -for _,file in ipairs(all_files) do - local f = io.open(file) - - local buffer = "" - - for line in f:lines() do - - local var = line:gmatch("--[ ]*@beautiful ([^ \n]*)")() - - -- There is no backward/forward pattern in lua - if #line <= 1 then - buffer = "" - elseif #buffer and not var then - buffer = buffer.."\n"..line - elseif line:sub(1,3) == "---" then - buffer = line - end - - - if var then - -- Get the @param, @see and @usage - local params = "" - for line in f:lines() do - if line:sub(1,2) ~= "--" then - break - else - params = params.."\n"..line - end - end - - local name = var:gmatch("[ ]*beautiful.(.+)")() - if not name then - print("WARNING:", var, - "seems to be misformatted. Use `beautiful.namespace_name`" - ) - else - table.insert(beautiful_vars, { - file = file, - name = name, - link = get_link(file, var), - desc = buffer:gmatch("[- ]+([^\n.]*)")() or "", - mod = path_to_module(file), - }) - end - - buffer = "" - end - end -end - -local function create_table(entries, columns) - local lines = {} - - for _, entry in ipairs(entries) do - local line = " " - - for _, column in ipairs(columns) do - line = line..""..entry[column].."" - end - - table.insert(lines, line.."\n") - end - - return [[

- - - - ]] .. table.concat(lines) .. "
NameDescription
\n" -end +local beautiful_vars = parser.parse_files(paths, "beautiful") local override_cats = { ["border" ] = true, @@ -200,7 +40,7 @@ local function create_sample(entries) " local theme = {}" } - for name, cat in sorted_pairs(categorize(entries)) do + for name, cat in parser.sorted_pairs(categorize(entries)) do table.insert(ret, "\n -- "..name) for _, v in ipairs(cat) do table.insert(ret, " -- theme."..v.name.." = nil") @@ -217,6 +57,7 @@ local function create_sample(entries) return table.concat(ret, '\n') end + -- Create the file local filename = args[1] @@ -229,8 +70,9 @@ f:write[[ Beautiful is where Awesome theme variables are stored. +

]] -f:write(create_table(beautiful_vars, {"link", "desc"})) +f:write(parser.create_table(beautiful_vars, {"link", "desc"})) f:write("\n\n## Sample theme file\n\n") @@ -241,6 +83,5 @@ f:close() --TODO add some linting to direct undeclared beautiful variables --TODO re-generate all official themes --TODO generate a complete sample theme ---TODO also parse C files. -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 diff --git a/docs/_parser.lua b/docs/_parser.lua new file mode 100644 index 00000000..323f8677 --- /dev/null +++ b/docs/_parser.lua @@ -0,0 +1,191 @@ +local gio = require("lgi").Gio +local gobject = require("lgi").GObject +local glib = require("lgi").GLib + +local name_attr = gio.FILE_ATTRIBUTE_STANDARD_NAME +local type_attr = gio.FILE_ATTRIBUTE_STANDARD_TYPE + +local module = {} + +-- Like pairs(), but iterate over keys in a sorted manner. Does not support +-- modifying the table while iterating. +local function sorted_pairs(t) + -- Collect all keys + local keys = {} + for k in pairs(t) do + table.insert(keys, k) + end + + table.sort(keys) + + -- return iterator function + local i = 0 + return function() + i = i + 1 + if keys[i] then + return keys[i], t[keys[i]] + end + end +end + +-- Recursive file scanner +local function get_all_files(path, ext, ret) + ret = ret or {} + local enumerator = gio.File.new_for_path(path):enumerate_children( + table.concat({name_attr, type_attr}, ",") , 0, nil, nil + ) + + for file in function() return enumerator:next_file() end do + local file_name = file:get_attribute_as_string(name_attr) + local file_type = file:get_file_type() + local match_ext = file_name:match("[.]"..ext.."$" or "") + local fpath = enumerator:get_child(file):get_path() + local is_test = fpath:match("/tests/") + if file_type == "REGULAR" and match_ext and not is_test then + table.insert(ret, fpath) + elseif file_type == "DIRECTORY" then + get_all_files(enumerator:get_child(file):get_path(), ext, ret) + end + end + + return ret +end + +local function path_to_module(path) + if path:match("[.]c$") then + return path:gmatch("/([^./]+)[.]c$")() + end + + for _, module in ipairs { + "awful", "wibox", "gears", "naughty", "menubar", "beautiful" + } do + local match = path:match("/"..module.."/([^.]+).lua") + if match then + return module.."."..match:gsub("/",".") + end + end + + error("Cannot figure out module for " .. tostring(path)) +end + +function module.path_to_html(path) + local mod = path_to_module(path):gsub(".init", "") + local f = assert(io.open(path)) + while true do + local line = f:read() + if not line then break end + if line:match("@classmod") then + f:close() + return "../classes/".. mod ..".html#" + end + if line:match("@module") or line:match("@submodule") then + f:close() + return "../libraries/".. mod ..".html#" + end + end + f:close() + + error("Cannot figure out if module or class: " .. tostring(path)) +end + +local function get_link(file, element, element_name) + return table.concat { + "", + element_name, + "" + } +end + +local function parse_files(paths, property_name, matcher, name_matcher) + local exp1 = "[-*]*[ ]*@".. property_name .." ([^ \n]*)" + local exp2 = matcher or "[-*]*[ ]*".. property_name ..".(.+)" + local exp3 = name_matcher or "[. ](.+)" + + local ret = {} + + table.sort(paths) + + -- Find all @beautiful doc entries + for _,file in ipairs(paths) do + local f = io.open(file) + + local buffer = "" + + for line in f:lines() do + + local var = line:gmatch(exp1)() + + -- There is no backward/forward pattern in lua + if #line <= 1 then + buffer = "" + elseif #buffer and not var then + buffer = buffer.."\n"..line + elseif line:sub(1,3) == "---" or line:sub(1,3) == "/**" then + buffer = line + end + + if var then + -- Get the @param, @see and @usage + local params = "" + for line in f:lines() do + if line:sub(1,2) ~= "--" and line:sub(1,2) ~= " *" then + break + else + params = params.."\n"..line + end + end + + local name = var:gmatch(exp2)() + if not name then + print("WARNING:", var, + "seems to be misformatted. Use `beautiful.namespace_name`" + ) + else + table.insert(ret, { + file = file, + name = name:gsub("_", "\\_"), + link = get_link(file, var, var:match(exp3):gsub("_", "\\_")), + desc = buffer:gmatch("[-*/ \n]+([^\n.]*)")() or "", + mod = path_to_module(file), + }) + end + + buffer = "" + end + end + end + + return ret +end + +local function create_table(entries, columns, prefix) + prefix = prefix or "" + local lines = {} + + for _, entry in ipairs(entries) do + local line = " " + + for _, column in ipairs(columns) do + line = line..""..entry[column].."" + end + + table.insert(lines, prefix..line.."\n") + end + + return [[-- +]]..prefix..[[ +]]..prefix..[[ +]]..prefix..[[ +]]..prefix..[[ +]] .. table.concat(lines) .. prefix .."
NameDescription
\n" +end + +module.create_table = create_table +module.parse_files = parse_files +module.sorted_pairs = sorted_pairs +module.get_all_files = get_all_files + +return module diff --git a/docs/build_rules_index.lua b/docs/build_rules_index.lua new file mode 100644 index 00000000..1e9341c4 --- /dev/null +++ b/docs/build_rules_index.lua @@ -0,0 +1,30 @@ +#! /usr/bin/lua +local args = {...} +local parser = require("docs._parser") + +local files = {"./objects/client.c", "./lib/awful/client.lua"} +local matcher, matcher2 = "(.*)", ".*" + +-- The client function comes from 5 different files, but all of those are +-- merged into one documentation page (aka, awful.client doesn't have content +-- anymore). This override the path so the parser doesn't have to be aware of it +function parser.path_to_html() + return "../classes/client.html#client." +end + +local clientruleproperty = parser.parse_files(files, "clientruleproperty", matcher, matcher2) + +for _, prop in ipairs(parser.parse_files(files, "property", matcher, matcher2)) do + table.insert(clientruleproperty, prop) +end + +-- Create the file +local filename = args[1] + +local f = io.open(filename, "w") + +f:write(parser.create_table(clientruleproperty, {"link", "desc"}, "-- ")) + +f:close() + +-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 diff --git a/docs/common/rules_index.ldoc b/docs/common/rules_index.ldoc new file mode 100644 index 00000000..ac75c08c --- /dev/null +++ b/docs/common/rules_index.ldoc @@ -0,0 +1,72 @@ +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +--
NameDescription
placementThe client default placement on the screen
honor\_paddingWhen applying the placement, honor the screen padding
honor\_workareaWhen applying the placement, honor the screen work area
tagThe client default tag
tagsThe client default tags
new\_tagCreate a new tag for this client
switch\_to\_tagsUnselect the current tags and select this client tags
focusDefine if the client should grab focus by default
titlebars\_enabledShould this client have a titlebar by default
callbackA function to call when this client is ready
markedIf a client is marked or not
is\_fixedReturn if a client has a fixed size or not
immobilizedIs the client immobilized horizontally?
immobilizedIs the client immobilized vertically?
floatingThe client floating state
xThe x coordinates
yThe y coordinates
widthThe width of the client
heightThe height of the client
dockableIf the client is dockable
requests\_no\_titlebarIf the client requests not to be decorated with a titlebar
shapeSet the client shape
windowThe X window id
nameThe client title
skip\_taskbarTrue if the client does not want to be in taskbar
typeThe window type
classThe client class
instanceThe client instance
pidThe client PID, if available
roleThe window role, if available
machineThe machine client is running on
icon\_nameThe client name when iconified
iconThe client icon as a surface
icon\_sizesThe available sizes of client icons
screenClient screen
hiddenDefine if the client must be hidden, i
minimizedDefine it the client must be iconify, i
size\_hints\_honorHonor size hints, e
border\_widthThe client border width
border\_colorThe client border color
urgentThe client urgent state
contentA cairo surface for the client window content
opacityThe client opacity
ontopThe client is on top of every other windows
aboveThe client is above normal windows
belowThe client is below normal windows
fullscreenThe client is fullscreen or not
maximizedThe client is maximized (horizontally and vertically) or not
maximized\_horizontalThe client is maximized horizontally or not
maximized\_verticalThe client is maximized vertically or not
transient\_forThe client the window is transient for
group\_windowWindow identification unique to a group of windows
leader\_windowIdentification unique to windows spawned by the same command
size\_hintsA table with size hints of the client
motif\_wm\_hintsThe motif WM hints of the client
stickySet the client sticky, i
modalIndicate if the client is modal
focusableTrue if the client can receive the input focus
shape\_boundingThe client's bounding shape as set by awesome as a (native) cairo surface
shape\_clipThe client's clip shape as set by awesome as a (native) cairo surface
shape\_inputThe client's input shape as set by awesome as a (native) cairo surface
client\_shape\_boundingThe client's bounding shape as set by the program as a (native) cairo surface
client\_shape\_clipThe client's clip shape as set by the program as a (native) cairo surface
startup\_idThe FreeDesktop StartId
validIf the client that this object refers to is still managed by awesome
first\_tagThe first tag of the client
diff --git a/docs/config.ld b/docs/config.ld index a640e4f7..0d783ace 100644 --- a/docs/config.ld +++ b/docs/config.ld @@ -72,6 +72,8 @@ new_type("callback", "Callback functions prototype", false, "Parameters") new_type("rulesources", "Rule sources", false, "param") -- Filter functions for the taglist/tasklist/layoutlist new_type("filterfunction", "List filters", false) +-- Extra client properties available only in awful.rules/spawn constructs +new_type("clientruleproperty", "Extra properties available in awful.rules and awful.spawn", false, "Type") -- More fitting section names kind_names={topic='Documentation', module='Libraries', script='Sample files'} @@ -135,10 +137,14 @@ file = { } } +local no_prefix = { + property = true, signal = true, clientruleproperty = true +} + custom_display_name_handler = function(item, default_handler) -- Remove the "namespace" from the signals and properties - if item.type == "property" or item.type == "signal" then + if no_prefix[item.type] then local name = item.name:match("%.([^.]+)$") return name ~= "" and name or item.name end diff --git a/docs/ldoc.css b/docs/ldoc.css index 6cea44f1..a761088f 100644 --- a/docs/ldoc.css +++ b/docs/ldoc.css @@ -22,7 +22,7 @@ hr { margin: 15px 0; } -code, tt { +tt { font-family: monospace; } span.parameter { @@ -47,6 +47,19 @@ p.name { font-family: monospace; } +kbd, p code, ol code { + background-color: #eaedf587; + padding-left: 3px; + padding-right: 3px; + border-radius: 3px; + border-style: solid; + border-width: 1px; + border-color: #b7bac1; + font-family: monospace; + margin-left: 5px; + margin-right: 5px; +} + #navigation { float: left; background-color: white; @@ -136,6 +149,7 @@ pre { padding: 15px; overflow: auto; font-family: monospace; + max-width: 720px; } #content ul pre.example { @@ -191,6 +205,58 @@ table th, table td { padding: 2px; } +.widget_list td { + padding-top: 10px; + padding-bottom: 10px; +} + +.widget_list tr td:first-child { + padding-left: 5px; +} + +.widget_list tr td:last-child { + padding-right: 10px; +} + +.widget_list { + border-collapse: unset; + overflow: hidden; + border-style: solid; + border-width: 0.5px; + border-top-left-radius: 7px; + border-top-right-radius: 7px; +} + +.widget_list th { + background-color: #2c3e67; + font-weight: bold; + color: white; + padding-top: 10px; + padding-bottom: 10px; +} + +.widget_list th:first-child { + border-top-left-radius: 7px; + border-bottom-width: 1px; + padding-left: 20px; + padding-right: 20px; +} + +.widget_list th:last-child { + border-top-right-radius: 7px; + border-width: 0px; + border-bottom-width: 1px; + padding-left: 20px; + padding-right: 20px; +} + +.widget_list td { + border-style: solid; + border-width: 0px; + border-right-width: 1px;; + border-bottom-width: 1px; +} + #about { padding: 15px; padding-left: 16em; diff --git a/docs/load_ldoc.cmake b/docs/load_ldoc.cmake index a783e7db..161d49e0 100644 --- a/docs/load_ldoc.cmake +++ b/docs/load_ldoc.cmake @@ -1,23 +1,23 @@ # To avoid copy pasting, some documentation is stored in reusable files set(SHAPE_FILE "${SOURCE_DIR}/docs/common/${SHAPE_NAME}.lua") -set(path "${SOURCE_DIR}/docs/common/") +foreach(path ${BUILD_DIR}/docs/common/;${SOURCE_DIR}/docs/common/) + # Get the documentation file list + file(GLOB doc_files RELATIVE "${path}" "${path}/*.ldoc") -# Get the documentation file list -file(GLOB doc_files RELATIVE "${path}" "${path}/*.ldoc") + foreach(doc_file_name ${doc_files}) + # Read the file + file(READ "${path}/${doc_file_name}" doc_file_content) -foreach(doc_file_name ${doc_files}) - # Read the file - file(READ "${path}/${doc_file_name}" doc_file_content) + # Remove the file extension + string(REGEX REPLACE "\\.ldoc" "" DOC_FILE_NAME ${doc_file_name}) - # Remove the file extension - string(REGEX REPLACE "\\.ldoc" "" DOC_FILE_NAME ${doc_file_name}) + # There is a trailing \n, remove it or it cannot be included in existing blocks + string(REGEX REPLACE "\n$" "" doc_file_content "${doc_file_content}") - # There is a trailing \n, remove it or it cannot be included in existing blocks - string(REGEX REPLACE "\n$" "" doc_file_content "${doc_file_content}") - - # Create a new variable usable from lua files - set(DOC_${DOC_FILE_NAME}_COMMON "${doc_file_content}") + # Create a new variable usable from lua files + set(DOC_${DOC_FILE_NAME}_COMMON "${doc_file_content}") + endforeach() endforeach() # vim: filetype=cmake:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80:foldmethod=marker diff --git a/lib/awful/client.lua b/lib/awful/client.lua index ffa20e04..f75270cf 100644 --- a/lib/awful/client.lua +++ b/lib/awful/client.lua @@ -59,6 +59,93 @@ client.property = {} client.shape = require("awful.client.shape") client.focus = require("awful.client.focus") +--- The client default placement on the screen. +-- +-- The default config uses: +-- +-- awful.placement.no_overlap+awful.placement.no_offscreen +-- +-- @clientruleproperty placement +-- @see awful.placement + +--- When applying the placement, honor the screen padding. +-- @clientruleproperty honor_padding +-- @param[opt=true] boolean +-- @see awful.placement + +--- When applying the placement, honor the screen work area. +-- +-- The workarea is the part of the screen that excludes the bars and docks. +-- +-- @clientruleproperty honor_workarea +-- @param[opt=true] boolean +-- @see awful.placement + +--- The client default tag. +-- @clientruleproperty tag +-- @param tag +-- @see tag +-- @see new_tag +-- @see tags +-- @see switch_to_tags + +--- The client default tags. +-- +-- Avoid using the tag and tags properties at the same time, it will cause +-- issues. +-- +-- @clientruleproperty tags +-- @param[opt={tag}] table +-- @see tag +-- @see new_tag +-- @see tags +-- @see switch_to_tags + +--- Create a new tag for this client. +-- +-- If the value is `true`, the new tag will be named after the client `class`. +-- If it is a string, it will be the tag name. +-- +-- If a table is used, all of its properties will be passed to the tag +-- constructor: +-- +-- new_tag = { +-- name = "My new tag!", -- The tag name. +-- layout = awful.layout.suit.max, -- Set the tag layout. +-- volatile = true, -- Remove the tag when the client is closed. +-- } +-- +-- @tparam[opt=false] table|string|boolean new_tag +-- @clientruleproperty new_tag +-- @see tag +-- @see tags +-- @see switch_to_tags + +--- Unselect the current tags and select this client tags. +-- Note that this property was called `switchtotag` in previous Awesome versions. +-- @clientruleproperty switch_to_tags +-- @param[opt=false] boolean +-- @see tag.selected + +--- Define if the client should grab focus by default. +-- +-- The `request::activate` context for this call is `rules`. +-- +-- @clientruleproperty focus +-- @param[opt=false] boolean + +--- Should this client have a titlebar by default. +-- @clientruleproperty titlebars_enabled +-- @param[opt=false] boolean +-- @see awful.titlebar + +--- A function to call when this client is ready. +-- +-- It can be useful to set extra properties or perform actions. +-- +-- @clientruleproperty callback +-- @see awful.spawn + --- Jump to the given client. -- Takes care of focussing the screen, the right tag, etc. -- diff --git a/lib/awful/rules.lua b/lib/awful/rules.lua index 29d7e4bf..8e60794c 100644 --- a/lib/awful/rules.lua +++ b/lib/awful/rules.lua @@ -8,17 +8,98 @@ -- to add random properties that will be later accessible as `c.property_name` -- (where `c` is a valid client object) -- --- In addition to the existing properties, the following are supported: +-- Syntax +-- === +-- 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: -- --- * placement --- * honor_padding --- * honor_workarea --- * tag --- * new_tag --- * switch_to_tags (also called switchtotag) --- * focus --- * titlebars_enabled --- * callback +-- { 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 } +-- } +-- +-- Applicable client properties +-- === +-- +-- The table below holds the list of default client properties along with +-- some extra properties that are specific to the rules. Note that any property +-- can be set in the rules and interpreted by user provided code. This table +-- only represent those offered by default. +-- +--@DOC_rules_index_COMMON@ -- -- @author Julien Danjou <julien@danjou.info> -- @copyright 2009 Julien Danjou @@ -44,90 +125,7 @@ local unpack = unpack or table.unpack -- luacheck: globals unpack (compatibility 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, switch_to_tags = 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 } - } -]]-- +--- This is the global rules table. rules.rules = {} --- Check if a client matches a rule. @@ -536,11 +534,6 @@ function rules.extra_properties.geometry(c, _, props) c:geometry(new_geo) --TODO use request::geometry end ---- Create a new tag based on a rule. --- @tparam client c The client --- @tparam boolean|function|string value The value. --- @tparam table props The properties. --- @treturn tag The new tag function rules.high_priority_properties.new_tag(c, value, props) local ty = type(value) local t = nil diff --git a/lib/awful/spawn.lua b/lib/awful/spawn.lua index 402dad5c..6eaff0c0 100644 --- a/lib/awful/spawn.lua +++ b/lib/awful/spawn.lua @@ -8,6 +8,9 @@ -- program after it has been launched. This requires currently that the -- applicaton supports them. -- +-- Frequently asked questions +-- === +-- -- **Rules of thumb when a shell is needed**: -- -- * A shell is required when the commands contain `&&`, `;`, `||`, `&` or @@ -45,44 +48,56 @@ -- *instance*, a *role*, and a *type*. See `client.class`, `client.instance`, -- `client.role` and `client.type` for more information about these properties. -- --- **The startup notification protocol**: +-- **Understanding blocking versus asynchronous execution**: -- --- The startup notification protocol is an optional specification implemented --- by X11 applications to bridge the chain of knowledge between the moment a --- program is launched to the moment its window (client) is shown. It can be --- found [on the FreeDesktop.org website](https://www.freedesktop.org/wiki/Specifications/startup-notification-spec/). +-- Awesome is single threaded, it means only one thing is executed at any time. +-- But Awesome isn't doomed to be slow. It may not have multiple threads, but +-- it has something called coroutine and also has callbacks. This means things +-- that take time to execute can still do so in the background (using C threads +-- or external process + sockets). When they are done, they can notify the +-- Awesome thread. This works perfectly and avoid blocking Awesome. -- --- Awesome has support for the various events that are part of the protocol, but --- the most useful is the identifier, usually identified by its `SNID` acronym in --- the documentation. It isn't usually necessary to even know it exists, as it --- is all done automatically. However, if more control is required, the --- identifier can be specified by an environment variable called --- `DESKTOP_STARTUP_ID`. For example, let us consider execution of the following --- command: +-- If you want to update the text of a `wibox.widget.textbox` with the output +-- of a shell command, you should use the `awful.spawn.easy_async_with_shell` +-- command. It is strongly recommanded not to use `io.popen` is explained in the +-- "Getting a command's output" section. Asynchronous execution is at first a +-- bit tricky to understand if you never used that before. The example below +-- should demonstrate how it works. -- --- DESKTOP_STARTUP_ID="something_TIME$(date '+%s')" my_command +-- If we do (but *really*, don't do that): -- --- This should (if the program correctly implements the protocol) result in --- `c.startup_id` to at least match `something`. --- This identifier can then be used in `awful.rules` to configure the client. +-- -- **NEVER, EVER, DO THIS**: your computer will freeze +-- os.execute("sleep 1; echo foo > /tmp/foo.txt") +-- mylabel.text = io.popen("cat /tmp/foo.txt"):read("*all") -- --- Awesome can automatically set the `DESKTOP_STARTUP_ID` variable. This is used --- by `awful.spawn` to specify additional rules for the startup. For example: +-- The label will display `foo`. But If we do: -- --- awful.spawn("urxvt -e maxima -name CALCULATOR", { --- floating = true, --- tag = mouse.screen.selected_tag, --- placement = awful.placement.bottom_right, --- }) +-- -- Don't do this, it wont work. +-- -- Assumes /tmp/foo.txt does not exist +-- awful.spawn.with_shell("sleep 1; echo foo > /tmp/foo.txt") +-- mylabel.text = io.popen("cat /tmp/foo.txt"):read("*all") -- --- This can also be used from the command line: +-- Then the label will be **empty**. `awful.spawn` and `awful.spawn.with_shell` +-- will **not** block and thus the `io.popen` will be executed before +-- `sleep 1` finishes. This is why we have async functions to execute shell +-- commands. There are many variants with difference characteristics and +-- complexity. `awful.spawn.easy_async` is the most common as it is good enough +-- for the general "I want to execute a command and do something with the +-- output when it finishes". -- --- awesome-client 'awful=require("awful"); --- awful.spawn("urxvt -e maxima -name CALCULATOR", { --- floating = true, --- tag = mouse.screen.selected_tag, --- placement = awful.placement.bottom_right, --- })' +-- -- This is the correct way +-- local command = "sleep 1; echo foo > /tmp/foo.txt" +-- +-- awful.spawn.easy_async_with_shell(command, function() +-- awful.spawn.easy_async_with_shell("cat /tmp/foo.txt", function(out) +-- mylabel.text = out +-- end) +-- end) +-- +-- In this variant, Awesome will not block. Again, like other spawn, you +-- cannot add code outside of the callback function to use the result of the +-- command. The code will be executed before the command is finished so the +-- result wont yet be available. -- -- **Getting a command's output**: -- @@ -148,6 +163,53 @@ -- [Desktop Entry](https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html) -- specification. -- +-- Spawning applications with specific properties +-- === +-- +-- **The startup notification protocol**: +-- +-- The startup notification protocol is an optional specification implemented +-- by X11 applications to bridge the chain of knowledge between the moment a +-- program is launched to the moment its window (client) is shown. It can be +-- found [on the FreeDesktop.org website](https://www.freedesktop.org/wiki/Specifications/startup-notification-spec/). +-- +-- Awesome has support for the various events that are part of the protocol, but +-- the most useful is the identifier, usually identified by its `SNID` acronym in +-- the documentation. It isn't usually necessary to even know it exists, as it +-- is all done automatically. However, if more control is required, the +-- identifier can be specified by an environment variable called +-- `DESKTOP_STARTUP_ID`. For example, let us consider execution of the following +-- command: +-- +-- DESKTOP_STARTUP_ID="something_TIME$(date '+%s')" my_command +-- +-- This should (if the program correctly implements the protocol) result in +-- `c.startup_id` to at least match `something`. +-- This identifier can then be used in `awful.rules` to configure the client. +-- +-- Awesome can automatically set the `DESKTOP_STARTUP_ID` variable. This is used +-- by `awful.spawn` to specify additional rules for the startup. For example: +-- +-- awful.spawn("urxvt -e maxima -name CALCULATOR", { +-- floating = true, +-- tag = mouse.screen.selected_tag, +-- placement = awful.placement.bottom_right, +-- }) +-- +-- This can also be used from the command line: +-- +-- awesome-client 'awful=require("awful"); +-- awful.spawn("urxvt -e maxima -name CALCULATOR", { +-- floating = true, +-- tag = mouse.screen.selected_tag, +-- placement = awful.placement.bottom_right, +-- })' +-- +-- This table contains the client properties that are valid when used the +-- `sn_rules` or `prop` function argument. They are the same as in `awful.rules`. +-- +--@DOC_rules_index_COMMON@ +-- -- @author Julien Danjou <julien@danjou.info> -- @author Emmanuel Lepage Vallee <elv1313@gmail.com> -- @copyright 2008 Julien Danjou diff --git a/objects/client.c b/objects/client.c index ce3e748d..2144c3c5 100644 --- a/objects/client.c +++ b/objects/client.c @@ -108,7 +108,28 @@ /** Client class. * - * @table object + * This table allow to add more dynamic properties to the clients. For example, + * doing: + * + * function awful.client.object.set_my_cool_property(c, value) + * -- Some logic code + * c._my_secret_my_cool_property = value + * c:emit_signal("property::my_cool_property) + * end + * + * function awful.client.object.get_my_cool_property() + * return c._my_secret_my_cool_property + * end + * + * Will add a new "my_cool_property" dyanmic property to all client. These + * methods will be called when an user does `c.my_cool_property = "something"` + * or set them in `awdul.rules`. + * + * Note that doing this isn't required to set random properties to the client, + * it is only useful when setting or getting these properties require code to + * executed. + * + * @table awful.object */ /** When a client gains focus.