diff --git a/CMakeLists.txt b/CMakeLists.txt index 4634daa71..7e81aee8e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,10 +22,7 @@ include_directories( ${AWESOME_REQUIRED_INCLUDE_DIRS} ${AWESOME_OPTIONAL_INCLUDE_DIRS}) -set(AWE_LUA_FILES - ${BUILD_DIR}/lib/tabulous.lua - ${BUILD_DIR}/lib/beautiful.lua - ${BUILD_DIR}/lib/awful.lua) +file(GLOB_RECURSE AWE_LUA_FILES ${BUILD_DIR}/lib/*.lua) set(AWE_CONF_FILE_DEFAULT ${BUILD_DIR}/awesomerc.lua) set(AWE_CONF_FILE rc.lua) @@ -294,9 +291,10 @@ if(GENERATE_LUADOC) VERBATIM) # }}} - # dont include full path names in documentation - foreach(filename ${AWE_LUA_FILES} ${capi_lua}) - get_filename_component(filename ${filename} NAME) + # Generate documentation of lib//0lua + file(GLOB_RECURSE lua_lib_files ${BUILD_DIR}/lib/*.lua) + foreach(filename ${lua_lib_files}) + file(RELATIVE_PATH filename ${BUILD_DIR}/lib ${filename}) set(luadoc_srcs ${luadoc_srcs} ${filename}) endforeach() @@ -318,7 +316,7 @@ endif() # {{{ Installation install(TARGETS ${PROJECT_AWE_NAME} ${PROJECT_AWECLIENT_NAME} RUNTIME DESTINATION bin) install(FILES "utils/awsetbg" DESTINATION bin PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) -install(FILES ${AWE_LUA_FILES} DESTINATION ${AWESOME_LUA_LIB_PATH}) +install(DIRECTORY ${BUILD_DIR}/lib DESTINATION ${AWESOME_DATA_PATH}) install(FILES ${AWE_CONF_FILE_DEFAULT} DESTINATION ${AWESOME_SYSCONFDIR} RENAME ${AWE_CONF_FILE}) if(GENERATE_MANPAGES) diff --git a/awesomeConfig.cmake b/awesomeConfig.cmake index 6dd7a8916..6bafa1286 100644 --- a/awesomeConfig.cmake +++ b/awesomeConfig.cmake @@ -266,13 +266,12 @@ set(AWESOME_THEMES_PATH ${AWESOME_DATA_PATH}/themes) # }}} # {{{ Configure files -set(AWESOME_CONFIGURE_FILES +file(GLOB_RECURSE awesome_lua_configure_files RELATIVE ${SOURCE_DIR} ${SOURCE_DIR}/lib/*.lua.in) +set(AWESOME_CONFIGURE_FILES + ${awesome_lua_configure_files} config.h.in awesomerc.lua.in themes/default.in - lib/awful.lua.in - lib/beautiful.lua.in - lib/tabulous.lua.in awesome-version-internal.h.in awesome.doxygen.in) diff --git a/awesomerc.lua.in b/awesomerc.lua.in index d4643f9b3..af9d3b816 100644 --- a/awesomerc.lua.in +++ b/awesomerc.lua.in @@ -108,8 +108,8 @@ mytaglist.label = awful.widget.taglist.label.all mytasklist = widget({ type = "tasklist", name = "mytasklist" }) mytasklist:buttons({ button({ }, 1, function (object, c) client.focus = c; c:raise() end), - button({ }, 4, function () awful.client.focusbyidx(1) end), - button({ }, 5, function () awful.client.focusbyidx(-1) end) + button({ }, 4, function () awful.client.focus.byidx(1) end), + button({ }, 5, function () awful.client.focus.byidx(-1) end) }) mytasklist.label = awful.widget.tasklist.label.currenttags @@ -162,7 +162,7 @@ end -- {{{ Mouse bindings awesome.buttons({ - button({ }, 3, function () awful.spawn(terminal) end), + button({ }, 3, function () awful.util.spawn(terminal) end), button({ }, 4, awful.tag.viewnext), button({ }, 5, awful.tag.viewprev) }) @@ -215,7 +215,7 @@ keybinding({ modkey }, "Right", awful.tag.viewnext):add() keybinding({ modkey }, "Escape", awful.tag.history.restore):add() -- Standard program -keybinding({ modkey }, "Return", function () awful.spawn(terminal) end):add() +keybinding({ modkey }, "Return", function () awful.util.spawn(terminal) end):add() keybinding({ modkey, "Control" }, "r", awesome.restart):add() keybinding({ modkey, "Shift" }, "q", awesome.quit):add() @@ -224,8 +224,8 @@ keybinding({ modkey, "Shift" }, "q", awesome.quit):add() keybinding({ modkey }, "m", awful.client.maximize):add() keybinding({ modkey }, "f", function () client.focus.fullscreen = not client.focus.fullscreen end):add() keybinding({ modkey, "Shift" }, "c", function () client.focus:kill() end):add() -keybinding({ modkey }, "j", function () awful.client.focusbyidx(1); client.focus:raise() end):add() -keybinding({ modkey }, "k", function () awful.client.focusbyidx(-1); client.focus:raise() end):add() +keybinding({ modkey }, "j", function () awful.client.focus.byidx(1); client.focus:raise() end):add() +keybinding({ modkey }, "k", function () awful.client.focus.byidx(-1); client.focus:raise() end):add() keybinding({ modkey, "Shift" }, "j", function () awful.client.swap(1) end):add() keybinding({ modkey, "Shift" }, "k", function () awful.client.swap(-1) end):add() keybinding({ modkey, "Control" }, "j", function () awful.screen.focus(1) end):add() @@ -249,7 +249,7 @@ keybinding({ modkey, "Shift" }, "space", function () awful.layout.inc(layouts, - -- Prompt keybinding({ modkey }, "F1", function () - awful.prompt.run({ prompt = "Run: " }, mypromptbox, awful.spawn, awful.completion.bash, + awful.prompt.run({ prompt = "Run: " }, mypromptbox, awful.util.spawn, awful.completion.bash, os.getenv("HOME") .. "/.cache/awesome/history") end):add() keybinding({ modkey }, "F4", function () awful.prompt.run({ prompt = "Run Lua code: " }, mypromptbox, awful.eval, awful.prompt.bash, diff --git a/lib/awful.lua.in b/lib/awful.lua.in deleted file mode 100644 index 9b4e76795..000000000 --- a/lib/awful.lua.in +++ /dev/null @@ -1,1858 +0,0 @@ ---------------------------------------------------------------------------- --- @author Julien Danjou <julien@danjou.info> --- @copyright 2008 Julien Danjou --- @release @AWESOME_VERSION@ ---------------------------------------------------------------------------- - --- Grab environment we need -local type = type -local string = string -local assert = assert -local loadstring = loadstring -local ipairs = ipairs -local pairs = pairs -local os = os -local io = io -local math = math -local setmetatable = setmetatable -local table = table -local otable = otable -local capi = -{ - awesome = awesome, - screen = screen, - client = client, - mouse = mouse, - button = button, - wibox = wibox, - widget = widget, - hooks = hooks, - keygrabber = keygrabber -} - ---- awful: AWesome Functions very UsefuL -module("awful") - --- Local variable handling theme -local theme = {} - --- mapping of command/completion function -local bashcomp_funcs = {} -local bashcomp_src = "/etc/bash_completion" - --- Various public structures -beautiful = {} -hooks = {} -hooks.user = {} -prompt = {} -prompt.history = {} -prompt.history.data = {} -completion = {} -screen = {} -layout = {} -client = {} -client.data = {} -client.data.maximize = otable() -client.focus = {} -client.focus.history = {} -client.focus.history.data = {} -tag = {} -tag.history = {} -tag.history.data = {} -tag.history.data.past = {} -tag.history.data.current = {} -titlebar = {} -titlebar.data = otable() -widget = {} -widget.taglist = {} -widget.taglist.label = {} -widget.tasklist = {} -widget.tasklist.label = {} -client.urgent = {} -client.urgent.stack = {} -client.urgent.stack.data = {} -placement = {} - ---- Strip alpha part of color. --- @param color The color. --- @return The color without alpha channel. -local function color_strip_alpha(color) - if color:len() == 9 then - color = color:sub(1, 7) - end - return color -end - ---- Make i cycle. --- @param t A length. --- @param i An absolute index to fit into #t. --- @return The object at new index. -local function cycle(t, i) - while i > t do i = i - t end - while i < 1 do i = i + t end - return i -end - ---- Create a directory --- @param dir The directory. --- @return mkdir return code -function mkdir(dir) - return os.execute("mkdir -p " .. dir) -end - ---- Get the first client that got the urgent hint. --- @return The first urgent client. -function client.urgent.get() - if #client.urgent.stack.data > 0 then - return client.urgent.stack.data[1] - else - -- fallback behaviour: iterate through clients and get the first urgent - local clients = capi.client.get() - for k, cl in pairs(clients) do - if cl.urgent then - return cl - end - end - end -end - ---- Jump to the client that received the urgent hint first. -function client.urgent.jumpto() - local c = client.urgent.get() - if c then - local s = capi.client.focus and capi.client.focus.screen or capi.mouse.screen - -- focus the screen - if s ~= c.screen then - capi.mouse.screen = c.screen - end - -- focus the tag - tag.viewonly(c:tags()[1]) - -- focus the client - capi.client.focus = c - c:raise() - end -end - ---- Adds client to urgent stack. --- @param c The client object. --- @param prop The property which is updated. -function client.urgent.stack.add(c, prop) - if prop == "urgent" then - table.insert(client.urgent.stack.data, c) - end -end - ---- Remove client from urgent stack. --- @param c The client object. -function client.urgent.stack.delete(c) - for k, cl in ipairs(client.urgent.stack.data) do - if c == cl then - table.remove(client.urgent.stack.data, k) - break - end - end -end - ---- Remove a client from the focus history --- @param c The client that must be removed. -function client.focus.history.delete(c) - for k, v in ipairs(client.focus.history.data) do - if v == c then - table.remove(client.focus.history.data, k) - break - end - end -end - ---- Filter out window that we do not want handled by focus. --- This usually means that desktop, dock and splash windows are --- not registered and cannot get focus. --- @param c A client. --- @return The same client if it's ok, nil otherwise. -function client.focus.filter(c) - if c.type == "desktop" - or c.type == "dock" - or c.type == "splash" then - return nil - end - return c -end - ---- Update client focus history. --- @param c The client that has been focused. -function client.focus.history.add(c) - if client.focus.filter(c) then - -- Remove the client if its in stack - client.focus.history.delete(c) - -- Record the client has latest focused - table.insert(client.focus.history.data, 1, c) - end -end - ---- Get the latest focused client for a screen in history. --- @param screen The screen number to look for. --- @param idx The index: 0 will return first candidate, --- 1 will return second, etc. --- @return A client. -function client.focus.history.get(screen, idx) - -- When this counter is equal to idx, we return the client - local counter = 0 - local vc = client.visible(screen) - for k, c in ipairs(client.focus.history.data) do - if c.screen == screen then - for j, vcc in ipairs(vc) do - if vcc == c then - if counter == idx then - return c - end - -- We found one, increment the counter only. - counter = counter + 1 - break - end - end - end - end - -- Argh nobody found in history, give the first one visible if there is one - if counter == 0 then - return vc[1] - end -end - ---- Focus the previous client in history. -function client.focus.history.previous() - local sel = capi.client.focus - local s - if sel then - s = sel.screen - else - s = capi.mouse.screen - end - local c = client.focus.history.get(s, 1) - if c then capi.client.focus = c end -end - ---- Get visible clients from a screen. --- @param The screen number, or nil for all screen. --- @return A table with all visible clients. -function client.visible(screen) - local cls = capi.client.get(screen) - local vcls = {} - for k, c in pairs(cls) do - if c:isvisible() then - table.insert(vcls, c) - end - end - return vcls -end - ---- Get a client by its relative index to the focused window. --- @usage Set i to 1 to get next, -1 to get previous. --- @param i The index. --- @param c Optional client. --- @return A client, or nil if no client is available. -function client.next(i, c) - -- Get currently focused client - local sel = c or capi.client.focus - if sel then - -- Get all visible clients - local cls = client.visible(sel.screen) - -- Remove all no-normal clients - for idx, c in ipairs(cls) do - if not client.focus.filter(c) then - table.remove(cls, idx) - end - end - -- Loop upon each client - for idx, c in ipairs(cls) do - if c == sel then - -- Cycle - return cls[cycle(#cls, idx + i)] - end - end - end -end - ---- Return true whether client B is in the right direction --- compared to client A. --- @param dir The direction. --- @param cA The first client. --- @param cB The second client. --- @return True if B is in the direction of A. -local function is_in_direction(dir, cA, cB) - if dir == "up" then - return cA['y'] > cB['y'] - elseif dir == "down" then - return cA['y'] < cB['y'] - elseif dir == "left" then - return cA['x'] > cB['x'] - elseif dir == "right" then - return cA['x'] < cB['x'] - end - return false -end - ---- Calculate distance between two points. --- i.e: if we want to move to the right, we will take the right border --- of the currently focused client and the left side of the checked client. --- This avoid the focus of an upper client when you move to the right in a --- tilebottom layout with nmaster=2 and 5 clients open, for instance. --- @param dir The direction. --- @param cA The first client. --- @param cB The second client. --- @return The distance between the clients. -local function calculate_distance(dir, cA, cB) - local xA = cA['x'] - local xB = cB['x'] - local yA = cA['y'] - local yB = cB['y'] - - if dir == "up" then - yB = yB + cB['height'] - elseif dir == "down" then - yA = yA + cA['height'] - elseif dir == "left" then - xB = xB + cB['width'] - elseif dir == "right" then - xA = xA + cA['width'] - end - - return math.sqrt(math.pow(xB - xA, 2) + math.pow(yB - yA, 2)) -end - ---- Focus a client by the given direction. --- @param dir The direction, can be either "up", "down", "left" or "right". --- @param c Optional client. -function client.focusbydirection(dir, c) - local sel = c or capi.client.focus - if sel then - local coords = sel:coords() - local dist, dist_min - local target = nil - local cls = client.visible(sel.screen) - - -- We check each client. - for i, c in ipairs(cls) do - -- Check coords to see if client is located in the right direction. - if is_in_direction(dir, coords, c:coords()) then - - -- Calculate distance between focused client and checked client. - dist = calculate_distance(dir, coords, c:coords()) - - -- If distance is shorter then keep the client. - if not target or dist < dist_min then - target = c - dist_min = dist - end - end - end - - -- If we found a client to focus, then do it. - if target then - capi.client.focus = target - end - end -end - ---- Focus a client by its relative index. --- @param i The index. --- @param c Optional client. -function client.focusbyidx(i, c) - local target = client.next(i, c) - if target then - capi.client.focus = target - end -end - ---- Swap a client by its relative index. --- @param i The index. --- @param c Optional client, otherwise focused one is used. -function client.swap(i, c) - local sel = c or capi.client.focus - local target = client.next(i, sel) - if target then - target:swap(sel) - end -end - ---- Get the master window. --- @param screen Optional screen number, otherwise screen mouse is used. --- @return The master window. -function client.master(screen) - local s = screen or capi.mouse.screen - return client.visible(s)[1] -end - --- Set the client as slave: put it at the end of other windows. --- @param c The window to set as slave. --- @return -function client.setslave(c) - local cls = client.visible(c.screen) - for k, v in pairs(cls) do - c:swap(v) - end -end - ---- Move/resize a client relative to current coordinates. --- @param x The relative x coordinate. --- @param y The relative y coordinate. --- @param w The relative width. --- @param h The relative height. --- @param c The optional client, otherwise focused one is used. -function client.moveresize(x, y, w, h, c) - local sel = c or capi.client.focus - local coords = sel:coords() - coords['x'] = coords['x'] + x - coords['y'] = coords['y'] + y - coords['width'] = coords['width'] + w - coords['height'] = coords['height'] + h - sel:coords(coords) -end - ---- Maximize a client to use the full workarea. --- @param c A client, or the focused one if nil. -function client.maximize(c) - local sel = c or capi.client.focus - if sel then - local ws = capi.screen[sel.screen].workarea - ws.width = ws.width - 2 * sel.border_width - ws.height = ws.height - 2 * sel.border_width - if sel.floating and client.data.maximize[sel] then - sel.floating = client.data.maximize[sel].floating - if sel.floating then - sel:coords(client.data.maximize[sel].coords) - end - client.data.maximize[sel] = nil - else - client.data.maximize[sel] = { coords = sel:coords(), floating = sel.floating } - sel.floating = true - sel:coords(ws) - end - end -end - - ---- Erase eventual client data in maximize. --- @param c The client. -local function client_maximize_clean(c) - client.data.maximize[c] = nil -end - ---- Give the focus to a screen, and move pointer. --- @param Screen number. -function screen.focus(i) - local s = cycle(capi.screen.count(), capi.mouse.screen + i) - local c = client.focus.history.get(s, 0) - if c then capi.client.focus = c end - -- Move the mouse on the screen - capi.mouse.screen = s -end - ---- Compare 2 tables of tags. --- @param a The first table. --- @param b The second table of tags. --- @return True if the tables are identical, false otherwise. -local function tag_compare_select(a, b) - if not a or not b then - return false - end - -- Quick size comparison - if #a ~= #b then - return false - end - for ka, va in pairs(a) do - if b[ka] ~= va.selected then - return false - end - end - for kb, vb in pairs(b) do - if a[kb].selected ~= vb then - return false - end - end - return true -end - ---- Update the tag history. --- @param screen The screen number. -function tag.history.update(screen) - local curtags = capi.screen[screen]:tags() - if not tag_compare_select(curtags, tag.history.data.current[screen]) then - tag.history.data.past[screen] = tag.history.data.current[screen] - tag.history.data.current[screen] = {} - for k, v in ipairs(curtags) do - tag.history.data.current[screen][k] = v.selected - end - end -end - --- Revert tag history. --- @param screen The screen number. -function tag.history.restore(screen) - local s = screen or capi.mouse.screen - local tags = capi.screen[s]:tags() - for k, t in pairs(tags) do - t.selected = tag.history.data.past[s][k] - end -end - ---- Return a table with all visible tags --- @param s Screen number. --- @return A table with all selected tags. -function tag.selectedlist(s) - local screen = s or capi.mouse.screen - local tags = capi.screen[screen]:tags() - local vtags = {} - for i, t in pairs(tags) do - if t.selected then - vtags[#vtags + 1] = t - end - end - return vtags -end - ---- Return only the first visible tag. --- @param s Screen number. -function tag.selected(s) - return tag.selectedlist(s)[1] -end - ---- Set master width factor. --- @param mwfact Master width factor. -function tag.setmwfact(mwfact) - local t = tag.selected() - if t then - t.mwfact = mwfact - end -end - ---- Increase master width factor. --- @param add Value to add to master width factor. -function tag.incmwfact(add) - local t = tag.selected() - if t then - t.mwfact = t.mwfact + add - end -end - ---- Set the number of master windows. --- @param nmaster The number of master windows. -function tag.setnmaster(nmaster) - local t = tag.selected() - if t then - t.nmaster = nmaster - end -end - ---- Increase the number of master windows. --- @param add Value to add to number of master windows. -function tag.incnmaster(add) - local t = tag.selected() - if t then - t.nmaster = t.nmaster + add - end -end - ---- Set number of column windows. --- @param ncol The number of column. -function tag.setncol(ncol) - local t = tag.selected() - if t then - t.ncol = ncol - end -end - ---- Increase number of column windows. --- @param add Value to add to number of column windows. -function tag.incncol(add) - local t = tag.selected() - if t then - t.ncol = t.ncol + add - end -end - ---- View no tag. --- @param Optional screen number. -function tag.viewnone(screen) - local tags = capi.screen[screen or capi.mouse.screen]:tags() - for i, t in pairs(tags) do - t.selected = false - end -end - ---- View a tag by its index. --- @param i The relative index to see. --- @param screen Optional screen number. -function tag.viewidx(i, screen) - local tags = capi.screen[screen or capi.mouse.screen]:tags() - local sel = tag.selected() - tag.viewnone() - for k, t in ipairs(tags) do - if t == sel then - tags[cycle(#tags, k + i)].selected = true - end - end -end - ---- View next tag. This is the same as tag.viewidx(1). -function tag.viewnext() - return tag.viewidx(1) -end - ---- View previous tag. This is the same a tag.viewidx(-1). -function tag.viewprev() - return tag.viewidx(-1) -end - ---- View only a tag. --- @param t The tag object. -function tag.viewonly(t) - tag.viewnone(t.screen) - t.selected = true -end - ---- View only a set of tags. --- @param tags A table with tags to view only. --- @param screen Optional screen number of the tags. -function tag.viewmore(tags, screen) - tag.viewnone(screen) - for i, t in pairs(tags) do - t.selected = true - end -end - ---- Move a client to a tag. --- @param target The tag to move the client to. --- @param c Optional client to move, otherwise the focused one is used. -function client.movetotag(target, c) - local sel = c or capi.client.focus - if sel then - -- Check that tag and client screen are identical - if sel.screen ~= target.screen then return end - sel:tags({ target }) - end -end - ---- Toggle a tag on a client. --- @param target The tag to toggle. --- @param c Optional client to toggle, otherwise the focused one is used. -function client.toggletag(target, c) - local sel = c or capi.client.focus - -- Check that tag and client screen are identical - if sel and sel.screen == target.screen then - local tags = sel:tags() - if tags[target] then - -- If it's the only tag for the window, stop. - if #tags == 1 then return end - tags[tags[target]] = nil - else - tags[target] = target - end - sel:tags(tags) - end -end - ---- Toggle the floating status of a client. --- @param c Optional client, the focused on if not set. -function client.togglefloating(c) - local sel = c or capi.client.focus - if sel then - sel.floating = not sel.floating - end -end - ---- Move a client to a screen. Default is next screen, cycling. --- @param c The client to move. --- @param s The screen number, default to current + 1. -function client.movetoscreen(c, s) - local sel = c or capi.client.focus - if sel then - local sc = capi.screen.count() - if not s then - s = sel.screen + 1 - end - if s > sc then s = 1 elseif s < 1 then s = sc end - sel.screen = s - capi.mouse.coords(capi.screen[s].coords) - capi.client.focus = sel - end -end - ---- Get the current layout name. --- @param screen The screen number. -function layout.get(screen) - local t = tag.selected(screen) - if t then - return t.layout - end -end - ---- Create a new userhook (for external libs). --- @param name Hook name. -function hooks.user.create(name) - hooks[name] = {} - hooks[name].callbacks = {} - hooks[name].register = function (f) - table.insert(hooks[name].callbacks, f) - end - hooks[name].unregister = function (f) - for k, h in ipairs(hooks[name].callbacks) do - if h == f then - table.remove(hooks[name].callbacks, k) - break - end - end - end -end - ---- Call a created userhook (for external libs). --- @param name Hook name. -function hooks.user.call(name, ...) - for name, callback in pairs(hooks[name].callbacks) do - callback(...) - end -end - --- Just set an awful mark to a client to move it later. -local awfulmarked = {} -hooks.user.create('marked') -hooks.user.create('unmarked') - ---- Mark a client, and then call 'marked' hook. --- @param c The client to mark, the focused one if not specified. --- @return True if the client has been marked. False if the client was already marked. -function client.mark (c) - local cl = c or capi.client.focus - if cl then - for k, v in pairs(awfulmarked) do - if cl == v then - return false - end - end - - table.insert(awfulmarked, cl) - - -- Call callback - hooks.user.call('marked', cl) - return true - end -end - ---- Unmark a client and then call 'unmarked' hook. --- @param c The client to unmark, or the focused one if not specified. --- @return True if the client has been unmarked. False if the client was not marked. -function client.unmark(c) - local cl = c or capi.client.focus - - for k, v in pairs(awfulmarked) do - if cl == v then - table.remove(awfulmarked, k) - hooks.user.call('unmarked', cl) - return true - end - end - - return false -end - ---- Check if a client is marked. --- @param c The client to check, or the focused one otherwise. -function client.ismarked(c) - local cl = c or capi.client.focus - if cl then - for k, v in pairs(awfulmarked) do - if cl == v then - return true - end - end - end - return false -end - ---- Toggle a client as marked. --- @param c The client to toggle mark. -function client.togglemarked(c) - local cl = c or capi.client.focus - - if not client.mark(c) then - client.unmark(c) - end -end - ---- Return the marked clients and empty the marked table. --- @return A table with all marked clients. -function client.getmarked() - for k, v in pairs(awfulmarked) do - hooks.user.call('unmarked', v) - end - - t = awfulmarked - awfulmarked = {} - return t -end - ---- Change the layout of the current tag. --- @param layouts A table of layouts. --- @param i Relative index. -function layout.inc(layouts, i) - local t = tag.selected() - local number_of_layouts = 0 - local rev_layouts = {} - for i, v in ipairs(layouts) do - rev_layouts[v] = i - number_of_layouts = number_of_layouts + 1 - end - if t then - local cur_layout = layout.get() - local new_layout_index = (rev_layouts[cur_layout] + i) % number_of_layouts - if new_layout_index == 0 then - new_layout_index = number_of_layouts - end - t.layout = layouts[new_layout_index] - end -end - ---- Set the layout of the current tag by name. --- @param layout Layout name. -function layout.set(layout) - local t = tag.selected() - if t then - t.layout = layout - end -end - --- Autodeclare awful.hooks.* functions --- mapped to awesome hooks.* functions -for name, hook in pairs(capi.hooks) do - if name ~= 'timer' then - hooks[name] = {} - hooks[name].register = function (f) - if not hooks[name].callbacks then - hooks[name].callbacks = {} - hook(function (...) - for i, callback in ipairs(hooks[name].callbacks) do - callback(...) - end - end) - end - - table.insert(hooks[name].callbacks, f) - end - hooks[name].unregister = function (f) - if hooks[name].callbacks then - for k, h in ipairs(hooks[name].callbacks) do - if h == f then - table.remove(hooks[name].callbacks, k) - break - end - end - end - end - else - hooks[name] = {} - hooks[name].register = function (time, f, runnow) - if type(time) ~= 'number' or type(f) ~= 'function' or time <= 0 then - return - end - local new_timer - if hooks[name].timer then - -- Take the smallest between current and new - new_timer = math.min(time, hooks[name].timer) - else - new_timer = time - end - if not hooks[name].callbacks then - hooks[name].callbacks = {} - end - if hooks[name].timer ~= new_timer then - hooks[name].timer = new_timer - hook(hooks[name].timer, function (...) - for i, callback in ipairs(hooks[name].callbacks) do - callback['counter'] = callback['counter'] + hooks[name].timer - if callback['counter'] >= callback['timer'] then - callback['callback'](...) - callback['counter'] = 0 - end - end - end) - end - - if runnow then - table.insert(hooks[name].callbacks, { callback = f, timer = time, counter = time }) - else - table.insert(hooks[name].callbacks, { callback = f, timer = time, counter = 0 }) - end - end - hooks[name].unregister = function (f) - if hooks[name].callbacks then - for k, h in ipairs(hooks[name].callbacks) do - if h.callback == f then - table.remove(hooks[name].callbacks, k) - break - end - end - end - end - end -end - ---- Spawn a program. --- @param cmd The command. --- @param screen The screen where to spawn window. --- @return The awesome.spawn return value. -function spawn(cmd, screen) - if cmd and cmd ~= "" then - return capi.awesome.spawn(cmd .. "&", screen or capi.mouse.screen) - end -end - --- Read a program output and returns its output as a string. --- @param cmd The command to run. --- @return A string with the program output. -function pread(cmd) - if cmd and cmd ~= "" then - local f = io.popen(cmd, 'r') - local s = f:read("*all") - f:close() - return s - end -end - ---- Eval Lua code. --- @return The return value of Lua code. -function eval(s) - return assert(loadstring("return " .. s))() -end - ---- Load history file in history table --- @param id The prompt history identifier which is the path to the filename --- @param max Optional parameter, the maximum number of entries in file -local function prompt_history_check_load(id, max) - if id and id ~= "" - and not prompt.history[id] then - prompt.history[id] = { max = 50, table = {} } - - if max then - prompt.history[id].max = max - end - - local f = io.open(id, "r") - - -- Read history file - if f then - for line in f:lines() do - table.insert(prompt.history[id].table, line) - if #prompt.history[id].table >= prompt.history[id].max then - break - end - end - end - end -end - ---- Save history table in history file --- @param id The prompt history identifier -local function prompt_history_save(id) - if prompt.history[id] then - local f = io.open(id, "w") - if not f then - local i = 0 - for d in id:gmatch(".-/") do - i = i + #d - end - mkdir(id:sub(1, i - 1)) - f = assert(io.open(id, "w")) - end - for i = 1, math.min(#prompt.history[id].table, prompt.history[id].max) do - f:write(prompt.history[id].table[i] .. "\n") - end - f:close() - end -end - ---- Return the number of items in history table regarding the id --- @param id The prompt history identifier --- @return the number of items in history table, -1 if history is disabled -local function prompt_history_items(id) - if prompt.history[id] then - return #prompt.history[id].table - else - return -1 - end -end - ---- Add an entry to the history file --- @param id The prompt history identifier --- @param command The command to add -local function prompt_history_add(id, command) - if prompt.history[id] then - if command ~= "" - and command ~= prompt.history[id].table[#prompt.history[id].table] then - table.insert(prompt.history[id].table, command) - - -- Do not exceed our max_cmd - if #prompt.history[id].table > prompt.history[id].max then - table.remove(prompt.history[id].table, 1) - end - - prompt_history_save(id) - end - end -end - - ---- Enable programmable bash completion in awful.completion.bash at the price of --- a slight overhead --- @param src The bash completion source file, /etc/bash_completion by default. -function completion.bashcomp_load(src) - if src then bashcomp_src = src end - local c = io.popen("/usr/bin/env bash -c 'source " .. bashcomp_src .. "; complete -p'") - while true do - local line = c:read("*line") - if not line then break end - -- if a bash function is used for completion, register it - if line:match(".* -F .*") then - bashcomp_funcs[line:gsub(".* (%S+)$","%1")] = line:gsub(".*-F +(%S+) .*$", "%1") - end - end - c:close() -end - ---- Use bash completion system to complete command and filename. --- @param command The command line. --- @param cur_pos The cursor position. --- @param ncomp The element number to complete. --- @return The new command and the new cursor position. -function completion.bash(command, cur_pos, ncomp) - local wstart = 1 - local wend = 1 - local words = {} - local cword_index = 0 - local cword_start = 0 - local cword_end = 0 - local i = 1 - local comptype = "file" - - -- do nothing if we are on a letter, i.e. not at len + 1 or on a space - if cur_pos ~= #command + 1 and command:sub(cur_pos, cur_pos) ~= " " then - return command, cur_pos - elseif #command == 0 then - return command, cur_pos - end - - while wend <= #command do - wend = command:find(" ", wstart) - if not wend then wend = #command + 1 end - table.insert(words, command:sub(wstart, wend - 1)) - if cur_pos >= wstart and cur_pos <= wend + 1 then - cword_start = wstart - cword_end = wend - cword_index = i - end - wstart = wend + 1 - i = i + 1 - end - - if cword_index == 1 then - comptype = "command" - end - - local bash_cmd - if bashcomp_funcs[words[1]] then - -- fairly complex command with inline bash script to get the possible completions - bash_cmd = "/usr/bin/env bash -c 'source " .. bashcomp_src .. "; " .. - "__print_completions() { for ((i=0;i<${#COMPREPLY[*]};i++)); do echo ${COMPREPLY[i]}; done }; " .. - "COMP_WORDS=(" .. command .."); COMP_LINE=\"" .. command .. "\"; " .. - "COMP_COUNT=" .. cur_pos .. "; COMP_CWORD=" .. cword_index-1 .. "; " .. - bashcomp_funcs[words[1]] .. "; __print_completions | sort -u'" - else - bash_cmd = "/usr/bin/env bash -c 'compgen -A " .. comptype .. " " .. words[cword_index] .. "'" - end - local c = io.popen(bash_cmd) - local output = {} - i = 0 - while true do - local line = c:read("*line") - if not line then break end - table.insert(output, line) - end - - c:close() - - -- no completion, return - if #output == 0 then - return command, cur_pos - end - - -- cycle - while ncomp > #output do - ncomp = ncomp - #output - end - - local str = command:sub(1, cword_start - 1) .. output[ncomp] .. command:sub(cword_end) - cur_pos = cword_end + #output[ncomp] + 1 - - return str, cur_pos -end - ---- Draw the prompt text with a cursor. --- @param text The text. --- @param text_color The text color. --- @param cursor_color The cursor color. --- @param cursor_pos The cursor position. -local function prompt_text_with_cursor(text, text_color, cursor_color, cursor_pos) - local char - if not text then text = "" end - if #text < cursor_pos then - char = " " - else - char = escape(text:sub(cursor_pos, cursor_pos)) - end - local text_start = escape(text:sub(1, cursor_pos - 1)) - local text_end = escape(text:sub(cursor_pos + 1)) - return text_start .. "" .. char .. "" .. text_end -end - ---- Run a prompt in a box. --- @param args A table with optional arguments: fg_cursor, bg_cursor, prompt. --- @param textbox The textbox to use for the prompt. --- @param exe_callback The callback function to call with command as argument when finished. --- @param completion_callback The callback function to call to get completion. --- @param history_path Optional parameter: file path where the history should be saved, set nil to disable history --- @param history_max Optional parameter: set the maximum entries in history file, 50 by default --- @param done_callback Optional parameter: the callback function to always call without arguments, regardless of whether the prompt was cancelled. -function prompt.run(args, textbox, exe_callback, completion_callback, history_path, history_max, done_callback) - if not args then args = {} end - local command = "" - local command_before_comp - local cur_pos_before_comp - local prettyprompt = args.prompt or "" - local inv_col = args.fg_cursor or theme.fg_focus or "black" - local cur_col = args.bg_cursor or theme.bg_focus or "white" - - prompt_history_check_load(history_path, history_max) - local history_index = prompt_history_items(history_path) + 1 - -- The cursor position - local cur_pos = 1 - -- The completion element to use on completion request. - local ncomp = 1 - if not textbox or not exe_callback then - return - end - textbox.text = prettyprompt .. prompt_text_with_cursor(text, inv_col, cur_col, cur_pos) - capi.keygrabber.run( - function (mod, key) - -- Get out cases - if mod.Control then - if key == "c" or key == "g" then - textbox.text = "" - if done_callback then done_callback() end - return false - elseif key == "j" or key == "m" then - textbox.text = "" - exec_callback(command) - if done_callback then done_callback() end - return false - end - else - if key == "Return" then - textbox.text = "" - prompt_history_add(history_path, command) - exe_callback(command) - if done_callback then done_callback() end - return false - elseif key == "Escape" then - textbox.text = "" - if done_callback then done_callback() end - return false - end - end - - -- Control cases - if mod.Control then - if key == "a" then - cur_pos = 1 - elseif key == "b" then - if cur_pos > 1 then - cur_pos = cur_pos - 1 - end - elseif key == "d" then - if cur_pos <= #command then - command = command:sub(1, cur_pos - 1) .. command:sub(cur_pos + 1) - end - elseif key == "e" then - cur_pos = #command + 1 - elseif key == "f" then - if cur_pos <= #command then - cur_pos = cur_pos + 1 - end - elseif key == "h" then - if cur_pos > 1 then - command = command:sub(1, cur_pos - 2) .. command:sub(cur_pos) - cur_pos = cur_pos - 1 - end - elseif key == "k" then - command = command:sub(1, cur_pos - 1) - elseif key == "u" then - command = command:sub(cur_pos, #command) - cur_pos = 1 - elseif key == "w" then - local wstart = 1 - local wend = 1 - local cword_start = 1 - local cword_end = 1 - while wend < cur_pos do - wend = command:find(" ", wstart) - if not wend then wend = #command + 1 end - if cur_pos >= wstart and cur_pos <= wend + 1 then - cword_start = wstart - cword_end = cur_pos - 1 - break - end - wstart = wend + 1 - end - command = command:sub(1, cword_start - 1) .. command:sub(cword_end + 1) - cur_pos = cword_start - end - else - if completion_callback then - -- That's tab - if key:byte() == 9 or key == "ISO_Left_Tab" then - if key == "ISO_Left_Tab" then - if ncomp == 1 then return true end - if ncomp == 2 then - command = command_before_comp - textbox.text = prettyprompt .. prompt_text_with_cursor(command_before_comp, inv_col, cur_col, cur_pos) - return true - end - - ncomp = ncomp - 2 - elseif ncomp == 1 then - command_before_comp = command - cur_pos_before_comp = cur_pos - end - command, cur_pos = completion_callback(command_before_comp, cur_pos_before_comp, ncomp) - ncomp = ncomp + 1 - key = "" - else - ncomp = 1 - end - end - - -- Typin cases - if key == "Home" then - cur_pos = 1 - elseif key == "End" then - cur_pos = #command + 1 - elseif key == "BackSpace" then - if cur_pos > 1 then - command = command:sub(1, cur_pos - 2) .. command:sub(cur_pos) - cur_pos = cur_pos - 1 - end - -- That's DEL - elseif key:byte() == 127 then - command = command:sub(1, cur_pos - 1) .. command:sub(cur_pos + 1) - elseif key == "Left" then - cur_pos = cur_pos - 1 - elseif key == "Right" then - cur_pos = cur_pos + 1 - elseif key == "Up" then - if history_index > 1 then - history_index = history_index - 1 - - command = prompt.history[history_path].table[history_index] - cur_pos = #command + 2 - end - elseif key == "Down" then - if history_index < prompt_history_items(history_path) then - history_index = history_index + 1 - - command = prompt.history[history_path].table[history_index] - cur_pos = #command + 2 - elseif history_index == prompt_history_items(history_path) then - history_index = history_index + 1 - - command = "" - cur_pos = 1 - end - else - -- len() is UTF-8 aware but #key is not, - -- so check that we have one UTF-8 char but advance the cursor of # position - if key:len() == 1 then - command = command:sub(1, cur_pos - 1) .. key .. command:sub(cur_pos) - cur_pos = cur_pos + #key - end - end - if cur_pos < 1 then - cur_pos = 1 - elseif cur_pos > #command + 1 then - cur_pos = #command + 1 - end - end - - -- Update textbox - textbox.text = prettyprompt .. prompt_text_with_cursor(command, inv_col, cur_col, cur_pos) - - return true - end) -end - ---- Escape a string from XML char. --- Useful to set raw text in textbox. --- @param text Text to escape. --- @return Escape text. -function escape(text) - if text then - text = text:gsub("&", "&") - text = text:gsub("<", "<") - text = text:gsub(">", ">") - text = text:gsub("'", "'") - text = text:gsub("\"", """) - end - return text -end - ---- Unescape a string from entities. --- @param text Text to unescape. --- @return Unescaped text. -function unescape(text) - if text then - text = text:gsub("&", "&") - text = text:gsub("<", "<") - text = text:gsub(">", ">") - text = text:gsub("'", "'") - text = text:gsub(""", "\"") - end - return text -end - ---- Return labels for a taglist widget with all tag from screen. --- It returns the tag name and set a special --- foreground and background color for selected tags. --- @param t The tag. --- @param args The arguments table. --- bg_focus The background color for selected tag. --- fg_focus The foreground color for selected tag. --- bg_urgent The background color for urgent tags. --- fg_urgent The foreground color for urgent tags. --- taglist_squares Optional: set "true" or nil to display the taglist squares. --- taglist_squares_sel Optional: an user provided image for selected squares. --- taglist_squares_unsel Optional: an user provided image for unselected squares. --- @return A string to print. -function widget.taglist.label.all(t, args) - if not args then args = {} end - local fg_focus = args.fg_focus or theme.taglist_fg_focus or theme.fg_focus - local bg_focus = args.bg_focus or theme.taglist_bg_focus or theme.bg_focus - local fg_urgent = args.fg_urgent or theme.taglist_fg_urgent or theme.fg_urgent - local bg_urgent = args.bg_urgent or theme.taglist_bg_urgent or theme.bg_urgent - local taglist_squares = args.taglist_squares or theme.taglist_squares - local taglist_squares_sel = args.squares_sel or theme.squares_sel - local taglist_squares_unsel = args.squares_unsel or theme.squares_unsel - local text - local background = "" - local sel = capi.client.focus - local bg_color = nil - local fg_color = nil - if t.selected then - bg_color = bg_focus - fg_color = fg_focus - end - if sel and sel:tags()[t] then - if not taglist_squares or taglist_squares == "true" then - if taglist_squares_sel then - background = "resize=\"true\" image=\"" .. taglist_squares_sel .. "\"" - else - background = "resize=\"true\" image=\"@AWESOME_ICON_PATH@/taglist/squarefw.png\"" - end - end - elseif bg_urgent or fg_urgent then - for k, c in pairs(t:clients()) do - if not taglist_squares or taglist_squares == "true" then - if taglist_squares_unsel then - background = "resize=\"true\" image=\"" .. taglist_squares_unsel .. "\"" - else - background = "resize=\"true\" image=\"@AWESOME_ICON_PATH@/taglist/squarew.png\"" - end - end - if c.urgent then - bg_color = bg_urgent - fg_color = fg_urgent - break - end - end - end - if bg_color and fg_color then - text = " "..escape(t.name).." " - else - text = " "..escape(t.name).." " - end - return text -end - ---- Return labels for a taglist widget with all *non empty* tags from screen. --- It returns the tag name and set a special --- foreground and background color for selected tags. --- @param t The tag. --- @param args The arguments table. --- bg_focus The background color for selected tag. --- fg_focus The foreground color for selected tag. --- bg_urgent The background color for urgent tags. --- fg_urgent The foreground color for urgent tags. --- @return A string to print. -function widget.taglist.label.noempty(t, args) - if #t:clients() > 0 or t.selected then - if not args then args = {} end - local fg_focus = args.fg_focus or theme.taglist_fg_focus or theme.fg_focus - local bg_focus = args.bg_focus or theme.taglist_bg_focus or theme.bg_focus - local fg_urgent = args.fg_urgent or theme.taglist_fg_urgent or theme.fg_urgent - local bg_urgent = args.bg_urgent or theme.taglist_bg_urgent or theme.bg_urgent - local bg_color = nil - local fg_color = nil - local text - - if t.selected then - bg_color = bg_focus - fg_color = fg_focus - end - if bg_urgent and fg_urgent then - for k, c in pairs(t:clients()) do - if c.urgent then - bg_color = bg_urgent - fg_color = fg_urgent - break - end - end - end - if fg_color and bg_color then - text = " " .. escape(t.name) .. " " - else - text = " " .. escape(t.name) .. " " - end - return text - end -end - -local function widget_tasklist_label_common(c, args) - if not args then args = {} end - local fg_focus = args.fg_focus or theme.tasklist_fg_focus or theme.fg_focus - local bg_focus = args.bg_focus or theme.tasklist_bg_focus or theme.bg_focus - local fg_urgent = args.fg_urgent or theme.tasklist_fg_urgent or theme.fg_urgent - local bg_urgent = args.bg_urgent or theme.tasklist_bg_urgent or theme.bg_urgent - local text = "" - local name - if c.floating then - text = "" - end - if c.minimized then - name = escape(c.icon_name) or "" - else - name = escape(c.name) or "" - end - if capi.client.focus == c then - if bg_focus and fg_focus then - text = text .. " "..name.." " - else - text = text .. " "..name.." " - end - elseif c.urgent and bg_urgent and fg_urgent then - text = text .. " "..name.." " - else - text = text .. " "..name.." " - end - return text -end - ---- Return labels for a tasklist widget with clients from all tags and screen. --- It returns the client name and set a special --- foreground and background color for focused client. --- It also puts a special icon for floating windows. --- @param c The client. --- @param screen The screen we are drawing on. --- @param args The arguments table. --- bg_focus The background color for focused client. --- fg_focus The foreground color for focused client. --- bg_urgent The background color for urgent clients. --- fg_urgent The foreground color for urgent clients. --- @return A string to print. -function widget.tasklist.label.allscreen(c, screen, args) - return widget_tasklist_label_common(c, args) -end - ---- Return labels for a tasklist widget with clients from all tags. --- It returns the client name and set a special --- foreground and background color for focused client. --- It also puts a special icon for floating windows. --- @param c The client. --- @param screen The screen we are drawing on. --- @param args The arguments table. --- bg_focus The background color for focused client. --- fg_focus The foreground color for focused client. --- bg_urgent The background color for urgent clients. --- fg_urgent The foreground color for urgent clients. --- @return A string to print. -function widget.tasklist.label.alltags(c, screen, args) - -- Only print client on the same screen as this widget - if c.screen ~= screen then return end - return widget_tasklist_label_common(c, args) -end - ---- Return labels for a tasklist widget with clients from currently selected tags. --- It returns the client name and set a special --- foreground and background color for focused client. --- It also puts a special icon for floating windows. --- @param c The client. --- @param screen The screen we are drawing on. --- @param args The arguments table. --- bg_focus The background color for focused client. --- fg_focus The foreground color for focused client. --- bg_urgent The background color for urgent clients. --- fg_urgent The foreground color for urgent clients. --- @return A string to print. -function widget.tasklist.label.currenttags(c, screen, args) - -- Only print client on the same screen as this widget - if c.screen ~= screen then return end - for k, t in ipairs(capi.screen[screen]:tags()) do - if t.selected and c:tags()[t] then - return widget_tasklist_label_common(c, args) - end - end -end - ---- Create a standard titlebar. --- @param c The client. --- @param args Arguments. --- fg: the foreground color. --- bg: the background color. --- fg_focus: the foreground color for focused window. --- fg_focus: the background color for focused window. --- width: the titlebar width -function titlebar.add(c, args) - if not c or c.type ~= "normal" then return end - if not args then args = {} end - -- Store colors - titlebar.data[c] = {} - titlebar.data[c].fg = args.fg or theme.titlebar_fg_normal or theme.fg_normal - titlebar.data[c].bg = args.bg or theme.titlebar_bg_normal or theme.bg_normal - titlebar.data[c].fg_focus = args.fg_focus or theme.titlebar_fg_focus or theme.fg_focus - titlebar.data[c].bg_focus = args.bg_focus or theme.titlebar_bg_focus or theme.bg_focus - titlebar.data[c].width = args.width - - -- Built args - local targs = {} - if args.fg then targs.fg = args.fg end - if args.bg then targs.bg = args.bg end - local tb = capi.wibox(targs) - - local title = capi.widget({ type = "textbox", name = "title", align = "flex" }) - title.text = " " .. escape(c.name) .. " " - local bts = - { - capi.button({ }, 1, function (t) t.client:mouse_move() end), - capi.button({ args.modkey }, 3, function (t) t.client:mouse_resize() end) - } - title:buttons(bts) - - local appicon = capi.widget({ type = "imagebox", name = "appicon", align = "left" }) - appicon.image = c.icon - - if theme.titlebar_close_button == "true" then - local closef = widget.button({ name = "closef", align = "right", - image = theme.titlebar_close_button_focus - or theme.titlebar_close_button_img_focus - or "@AWESOME_ICON_PATH@/titlebar/closer.png" }) - local close = widget.button({ name = "close", align = "right", - image = theme.titlebar_close_button_normal - or theme.titlebar_close_button_img_normal - or "@AWESOME_ICON_PATH@/titlebar/close.png" }) - - -- Bind kill button - local b = capi.button({ }, 1, nil, function (t) t.client:kill() end) - local bts = closef:buttons() - bts[#bts + 1] = b - closef:buttons(bts) - - bts = close:buttons() - bts[#bts + 1] = b - close:buttons(bts) - - tb:widgets({ - appicon, - title, - closef, close - }) - else - tb:widgets({ - appicon, - title - }) - end - - c.titlebar = tb - - titlebar.update(c) - titlebar.update(c, "geometry") -end - ---- Update a titlebar. This should be called in some hooks. --- @param c The client to update. --- @param prop The property name which has changed. -function titlebar.update(c, prop) - if c.titlebar and titlebar.data[c] then - local widgets = c.titlebar:widgets() - local title, close, closef - for k, v in ipairs(widgets) do - if v.name == "title" then title = v - elseif v.name == "close" then close = v - elseif v.name == "closef" then closef = v - elseif v.name == "appicon" then appicon = v end - if title and close and closef and appicon then break end - end - if prop == "name" then - if title then - title.text = " " .. escape(c.name) .. " " - end - elseif prop == "icon" then - if appicon then - appicon.image = c.icon - end - elseif prop == "geometry" then - if titlebar.data[c].width then - if c.titlebar.position == "top" - or c.titlebar.position == "bottom" then - local w = math.min(titlebar.data[c].width, c:coords().width + 2 * c.border_width) - c.titlebar:geometry({ width = w }) - else - local w = math.min(titlebar.data[c].width, c:coords().height + 2 * c.border_width) - c.titlebar:geometry({ height = w }) - end - end - end - if capi.client.focus == c then - c.titlebar.fg = titlebar.data[c].fg_focus - c.titlebar.bg = titlebar.data[c].bg_focus - if closef then closef.visible = true end - if close then close.visible = false end - else - c.titlebar.fg = titlebar.data[c].fg - c.titlebar.bg = titlebar.data[c].bg - if closef then closef.visible = false end - if close then close.visible = true end - end - end -end - ---- Remove a titlebar from a client. --- @param c The client. -function titlebar.remove(c) - c.titlebar = nil - titlebar.data[c] = nil -end - ---- Set the beautiful theme if any. --- @param The beautiful theme. -function beautiful.register(btheme) - if btheme then - theme = btheme - else - theme = {} - end -end - ---- Create a button widget. When clicked, the image is deplaced to make it like --- a real button. --- @param args Standard widget table arguments, plus image for the image path. --- @return A textbox widget configured as a button. -function widget.button(args) - if not args then return end - args.type = "textbox" - local w = capi.widget(args) - local img_release = "" - local img_press = "" - w.text = img_release - w:buttons({ capi.button({}, 1, function () w.text = img_press end, function () w.text = img_release end) }) - function w.mouse_leave(s) w.text = img_release end - function w.mouse_enter(s) if capi.mouse.coords().buttons[1] then w.text = img_press end end - return w -end - ---- Create a button widget which will launch a command. --- @param args Standard widget table arguments, plus image for the image path --- and command for the command to run on click. --- @return A launcher widget. -function widget.launcher(args) - if not args.command then return end - local w = widget.button(args) - local b = w:buttons() - b[#b + 1] = capi.button({}, 1, nil, function () spawn(args.command) end) - w:buttons(b) - return w -end - ---- Check if an area intersect another area. --- @param a The area. --- @param b The other area. --- @return True if they intersect, false otherwise. -local function area_intersect_area(a, b) - return (b.x < a.x + a.width - and b.x + b.width > a.x - and b.y < a.y + a.height - and b.y + b.height > a.y) -end - ---- Get the intersect area between a and b. --- @param a The area. --- @param b The other area. --- @return The intersect area. -local function area_intersect_area_get(a, b) - local g = {} - g.x = math.max(a.x, b.x) - g.y = math.max(a.y, b.y) - g.width = math.min(a.x + a.width, b.x + b.width) - g.x - g.height = math.min(a.y + a.height, b.y + b.height) - g.y - return g -end - ---- Remove an area from a list, splitting the space between several area that --- can overlap. --- @param areas Table of areas. --- @param elem Area to remove. --- @return The new area list. -local function area_remove(areas, elem) - local newareas = areas - for i, r in ipairs(areas) do - -- Check if the 'elem' intersect - if area_intersect_area(r, elem) then - -- It does? remove it - table.remove(areas, i) - local inter = area_intersect_area_get(r, elem) - - if inter.x > r.x then - table.insert(newareas, { - x = r.x, - y = r.y, - width = inter.x - r.x, - height = r.height - }) - end - - if inter.y > r.y then - table.insert(newareas, { - x = r.x, - y = r.y, - width = r.width, - height = inter.y - r.y - }) - end - - if inter.x + inter.width < r.x + r.width then - table.insert(newareas, { - x = inter.x + inter.width, - y = r.y, - width = (r.x + r.width) - (inter.x + inter.width), - height = r.height - }) - end - - if inter.y + inter.height < r.y + r.height then - table.insert(newareas, { - x = r.x, - y = inter.y + inter.height, - width = r.width, - height = (r.y + r.height) - (inter.y + inter.height) - }) - end - end - end - - return newareas -end - ---- Place the client without it being outside the screen. --- @param c The client. -function placement.no_offscreen(c) - local geometry = c:fullcoords() - local screen_geometry = capi.screen[c.screen].workarea - - if geometry.x + geometry.width > screen_geometry.x + screen_geometry.width then - geometry.x = screen_geometry.x + screen_geometry.width - geometry.width - elseif geometry.x < screen_geometry.x then - geometry.x = screen_geometry.x - end - - if geometry.y + geometry.height > screen_geometry.y + screen_geometry.height then - geometry.y = screen_geometry.y + screen_geometry.height - geometry.height - elseif geometry.y < screen_geometry.y then - geometry.y = screen_geometry.y - end - - c:fullcoords(geometry) -end - ---- Place the client where there's place available with minimum overlap. --- @param c The client. -function placement.no_overlap(c) - local cls = client.visible(c.screen) - local layout = layout.get() - local areas = { capi.screen[c.screen].workarea } - local coords = c:coords() - local fullcoords = c:fullcoords() - for i, cl in pairs(cls) do - if cl ~= c and (cl.floating or layout == "floating") then - areas = area_remove(areas, cl:fullcoords()) - end - end - - -- Look for available space - local found = false - local new = { x = coords.x, y = coords.y, width = 0, height = 0 } - for i, r in ipairs(areas) do - if r.width >= fullcoords.width - and r.height >= fullcoords.height - and r.width * r.height > new.width * new.height then - found = true - new = r - end - end - - -- We did not foudn an area with enough space for our size: - -- just take the biggest available one and go in - if not found then - for i, r in ipairs(areas) do - if r.width * r.height > new.width * new.height then - new = r - end - end - end - - -- Restore height and width - new.width = coords.width - new.height = coords.height - - c:coords(new) -end - ---- Place the client under the mouse. --- @param c The client. -function placement.under_mouse(c) - local c_coords = c:coords() - local m_coords = capi.mouse.coords() - c:coords({ x = m_coords.x - c_coords.width / 2, - y = m_coords.y - c_coords.height / 2 }) -end - --- Register standards hooks -hooks.arrange.register(tag.history.update) - -hooks.focus.register(client.focus.history.add) -hooks.unmanage.register(client.focus.history.delete) -hooks.unmanage.register(client_maximize_clean) - -hooks.focus.register(titlebar.update) -hooks.unfocus.register(titlebar.update) -hooks.property.register(titlebar.update) -hooks.unmanage.register(titlebar.remove) - -hooks.property.register(client.urgent.stack.add) -hooks.focus.register(client.urgent.stack.delete) -hooks.unmanage.register(client.urgent.stack.delete) - --- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80 diff --git a/lib/awful/beautiful.lua.in b/lib/awful/beautiful.lua.in new file mode 100644 index 000000000..dcf597d03 --- /dev/null +++ b/lib/awful/beautiful.lua.in @@ -0,0 +1,31 @@ +--------------------------------------------------------------------------- +-- @author Julien Danjou <julien@danjou.info> +-- @copyright 2008 Julien Danjou +-- @release @AWESOME_VERSION@ +--------------------------------------------------------------------------- + +local package = package + +--- Beautiful module for awful +module("awful.beautiful") + +-- Exported variable handling theme +theme = {} + +--- Set the beautiful theme if any. +-- @param The beautiful theme. +function register(btheme) + if btheme then + theme = btheme + else + theme = {} + end +end + +--- Get the current registerde them. +-- @return The current theme table. +function get() + return package.loaded.awful.beautiful.theme +end + +-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80 diff --git a/lib/awful/client.lua.in b/lib/awful/client.lua.in new file mode 100644 index 000000000..a3b0d981e --- /dev/null +++ b/lib/awful/client.lua.in @@ -0,0 +1,529 @@ +--------------------------------------------------------------------------- +-- @author Julien Danjou <julien@danjou.info> +-- @copyright 2008 Julien Danjou +-- @release @AWESOME_VERSION@ +--------------------------------------------------------------------------- + +-- Grab environment we need +local hooks = require("awful.hooks") +local util = require("awful.util") +local pairs = pairs +local ipairs = ipairs +local table = table +local math = math +local otable = otable +local capi = +{ + client = client, + mouse = mouse, + screen = screen, +} + +--- Client module for awful +module("awful.client") + +-- Local variable handling theme +local theme = {} + +-- mapping of command/completion function +local bashcomp_funcs = {} +local bashcomp_src = "/etc/bash_completion" + +-- Private data +local data = {} +data.maximize = otable() +data.focus = {} +data.urgent = {} +data.marked = {} + +-- Urgent functions +urgent = {} +focus = {} +focus.history = {} + +-- User hooks +hooks.user.create('marked') +hooks.user.create('unmarked') + +--- Get the first client that got the urgent hint. +-- @return The first urgent client. +function urgent.get() + if #data.urgent > 0 then + return #data.urgent[1] + else + -- fallback behaviour: iterate through clients and get the first urgent + local clients = capi.client.get() + for k, cl in pairs(clients) do + if cl.urgent then + return cl + end + end + end +end + +--- Jump to the client that received the urgent hint first. +function urgent.jumpto() + local c = urgent.get() + if c then + local s = capi.client.focus and capi.client.focus.screen or capi.mouse.screen + -- focus the screen + if s ~= c.screen then + capi.mouse.screen = c.screen + end + -- focus the tag + tag.viewonly(c:tags()[1]) + -- focus the client + capi.client.focus = c + c:raise() + end +end + +--- Adds client to urgent stack. +-- @param c The client object. +-- @param prop The property which is updated. +function urgent.add(c, prop) + if prop == "urgent" and c.urgent then + table.insert(data.urgent, c) + end +end + +--- Remove client from urgent stack. +-- @param c The client object. +function urgent.delete(c) + for k, cl in ipairs(data.urgent) do + if c == cl then + table.remove(data.urgent, k) + break + end + end +end + +--- Remove a client from the focus history +-- @param c The client that must be removed. +function focus.history.delete(c) + for k, v in ipairs(data.focus) do + if v == c then + table.remove(data.focus, k) + break + end + end +end + +--- Filter out window that we do not want handled by focus. +-- This usually means that desktop, dock and splash windows are +-- not registered and cannot get focus. +-- @param c A client. +-- @return The same client if it's ok, nil otherwise. +function focus.filter(c) + if c.type == "desktop" + or c.type == "dock" + or c.type == "splash" then + return nil + end + return c +end + +--- Update client focus history. +-- @param c The client that has been focused. +function focus.history.add(c) + if focus.filter(c) then + -- Remove the client if its in stack + focus.history.delete(c) + -- Record the client has latest focused + table.insert(data.focus, 1, c) + end +end + +--- Get the latest focused client for a screen in history. +-- @param screen The screen number to look for. +-- @param idx The index: 0 will return first candidate, +-- 1 will return second, etc. +-- @return A client. +function focus.history.get(screen, idx) + -- When this counter is equal to idx, we return the client + local counter = 0 + local vc = visible(screen) + for k, c in ipairs(data.focus) do + if c.screen == screen then + for j, vcc in ipairs(vc) do + if vcc == c then + if counter == idx then + return c + end + -- We found one, increment the counter only. + counter = counter + 1 + break + end + end + end + end + -- Argh nobody found in history, give the first one visible if there is one + if counter == 0 then + return vc[1] + end +end + +--- Focus the previous client in history. +function focus.history.previous() + local sel = capi.client.focus + local s + if sel then + s = sel.screen + else + s = capi.mouse.screen + end + local c = focus.history.get(s, 1) + if c then capi.client.focus = c end +end + +--- Get visible clients from a screen. +-- @param The screen number, or nil for all screen. +-- @return A table with all visible clients. +function visible(screen) + local cls = capi.client.get(screen) + local vcls = {} + for k, c in pairs(cls) do + if c:isvisible() then + table.insert(vcls, c) + end + end + return vcls +end + +--- Get a client by its relative index to the focused window. +-- @usage Set i to 1 to get next, -1 to get previous. +-- @param i The index. +-- @param c Optional client. +-- @return A client, or nil if no client is available. +function next(i, c) + -- Get currently focused client + local sel = c or capi.client.focus + if sel then + -- Get all visible clients + local cls = visible(sel.screen) + -- Remove all no-normal clients + for idx, c in ipairs(cls) do + if not focus.filter(c) then + table.remove(cls, idx) + end + end + -- Loop upon each client + for idx, c in ipairs(cls) do + if c == sel then + -- Cycle + return cls[util.cycle(#cls, idx + i)] + end + end + end +end + +--- Return true whether client B is in the right direction +-- compared to client A. +-- @param dir The direction. +-- @param cA The first client. +-- @param cB The second client. +-- @return True if B is in the direction of A. +local function is_in_direction(dir, cA, cB) + if dir == "up" then + return cA['y'] > cB['y'] + elseif dir == "down" then + return cA['y'] < cB['y'] + elseif dir == "left" then + return cA['x'] > cB['x'] + elseif dir == "right" then + return cA['x'] < cB['x'] + end + return false +end + +--- Calculate distance between two points. +-- i.e: if we want to move to the right, we will take the right border +-- of the currently focused client and the left side of the checked client. +-- This avoid the focus of an upper client when you move to the right in a +-- tilebottom layout with nmaster=2 and 5 clients open, for instance. +-- @param dir The direction. +-- @param cA The first client. +-- @param cB The second client. +-- @return The distance between the clients. +local function calculate_distance(dir, cA, cB) + local xA = cA['x'] + local xB = cB['x'] + local yA = cA['y'] + local yB = cB['y'] + + if dir == "up" then + yB = yB + cB['height'] + elseif dir == "down" then + yA = yA + cA['height'] + elseif dir == "left" then + xB = xB + cB['width'] + elseif dir == "right" then + xA = xA + cA['width'] + end + + return math.sqrt(math.pow(xB - xA, 2) + math.pow(yB - yA, 2)) +end + +--- Focus a client by the given direction. +-- @param dir The direction, can be either "up", "down", "left" or "right". +-- @param c Optional client. +function focus.bydirection(dir, c) + local sel = c or capi.client.focus + if sel then + local coords = sel:coords() + local dist, dist_min + local target = nil + local cls = visible(sel.screen) + + -- We check each client. + for i, c in ipairs(cls) do + -- Check coords to see if client is located in the right direction. + if is_in_direction(dir, coords, c:coords()) then + + -- Calculate distance between focused client and checked client. + dist = calculate_distance(dir, coords, c:coords()) + + -- If distance is shorter then keep the client. + if not target or dist < dist_min then + target = c + dist_min = dist + end + end + end + + -- If we found a client to focus, then do it. + if target then + capi.client.focus = target + end + end +end + +function focusbyidx(i, c) + util.deprecate() + return focus.byidx(i, c) +end + +--- Focus a client by its relative index. +-- @param i The index. +-- @param c Optional client. +function focus.byidx(i, c) + local target = next(i, c) + if target then + capi.client.focus = target + end +end + +--- Swap a client by its relative index. +-- @param i The index. +-- @param c Optional client, otherwise focused one is used. +function swap(i, c) + local sel = c or capi.client.focus + local target = next(i, sel) + if target then + target:swap(sel) + end +end + +--- Get the master window. +-- @param screen Optional screen number, otherwise screen mouse is used. +-- @return The master window. +function master(screen) + local s = screen or capi.mouse.screen + return visible(s)[1] +end + +-- Set the client as slave: put it at the end of other windows. +-- @param c The window to set as slave. +-- @return +function setslave(c) + local cls = visible(c.screen) + for k, v in pairs(cls) do + c:swap(v) + end +end + +--- Move/resize a client relative to current coordinates. +-- @param x The relative x coordinate. +-- @param y The relative y coordinate. +-- @param w The relative width. +-- @param h The relative height. +-- @param c The optional client, otherwise focused one is used. +function moveresize(x, y, w, h, c) + local sel = c or capi.client.focus + local coords = sel:coords() + coords['x'] = coords['x'] + x + coords['y'] = coords['y'] + y + coords['width'] = coords['width'] + w + coords['height'] = coords['height'] + h + sel:coords(coords) +end + +--- Maximize a client to use the full workarea. +-- @param c A client, or the focused one if nil. +function maximize(c) + local sel = c or capi.client.focus + if sel then + local ws = capi.screen[sel.screen].workarea + ws.width = ws.width - 2 * sel.border_width + ws.height = ws.height - 2 * sel.border_width + if sel.floating and client.data.maximize[sel] then + sel.floating = client.data.maximize[sel].floating + if sel.floating then + sel:coords(client.data.maximize[sel].coords) + end + data.maximize[sel] = nil + else + data.maximize[sel] = { coords = sel:coords(), floating = sel.floating } + sel.floating = true + sel:coords(ws) + end + end +end + + +--- Erase eventual client data in maximize. +-- @param c The client. +local function maximize_clean(c) + data.maximize[c] = nil +end + +--- Move a client to a tag. +-- @param target The tag to move the client to. +-- @param c Optional client to move, otherwise the focused one is used. +function movetotag(target, c) + local sel = c or capi.client.focus + if sel then + -- Check that tag and client screen are identical + if sel.screen ~= target.screen then return end + sel:tags({ target }) + end +end + +--- Toggle a tag on a client. +-- @param target The tag to toggle. +-- @param c Optional client to toggle, otherwise the focused one is used. +function toggletag(target, c) + local sel = c or capi.client.focus + -- Check that tag and client screen are identical + if sel and sel.screen == target.screen then + local tags = sel:tags() + if tags[target] then + -- If it's the only tag for the window, stop. + if #tags == 1 then return end + tags[tags[target]] = nil + else + tags[target] = target + end + sel:tags(tags) + end +end + +--- Toggle the floating status of a client. +-- @param c Optional client, the focused on if not set. +function togglefloating(c) + local sel = c or capi.client.focus + if sel then + sel.floating = not sel.floating + end +end + +--- Move a client to a screen. Default is next screen, cycling. +-- @param c The client to move. +-- @param s The screen number, default to current + 1. +function movetoscreen(c, s) + local sel = c or capi.client.focus + if sel then + local sc = capi.screen.count() + if not s then + s = sel.screen + 1 + end + if s > sc then s = 1 elseif s < 1 then s = sc end + sel.screen = s + capi.mouse.coords(capi.screen[s].coords) + capi.client.focus = sel + end +end + +--- Mark a client, and then call 'marked' hook. +-- @param c The client to mark, the focused one if not specified. +-- @return True if the client has been marked. False if the client was already marked. +function mark(c) + local cl = c or capi.client.focus + if cl then + for k, v in pairs(data.marked) do + if cl == v then + return false + end + end + + table.insert(data.marked, cl) + + -- Call callback + hooks.user.call('marked', cl) + return true + end +end + +--- Unmark a client and then call 'unmarked' hook. +-- @param c The client to unmark, or the focused one if not specified. +-- @return True if the client has been unmarked. False if the client was not marked. +function unmark(c) + local cl = c or capi.client.focus + + for k, v in pairs(data.marked) do + if cl == v then + table.remove(data.marked, k) + hooks.user.call('unmarked', cl) + return true + end + end + + return false +end + +--- Check if a client is marked. +-- @param c The client to check, or the focused one otherwise. +function ismarked(c) + local cl = c or capi.client.focus + if cl then + for k, v in pairs(data.marked) do + if cl == v then + return true + end + end + end + return false +end + +--- Toggle a client as marked. +-- @param c The client to toggle mark. +function togglemarked(c) + local cl = c or capi.client.focus + + if not client.mark(c) then + client.unmark(c) + end +end + +--- Return the marked clients and empty the marked table. +-- @return A table with all marked clients. +function getmarked() + for k, v in pairs(data.marked) do + hooks.user.call('unmarked', v) + end + + t = data.marked + data.marked = {} + return t +end + +-- Register standards hooks +hooks.focus.register(focus.history.add) +hooks.unmanage.register(focus.history.delete) +hooks.unmanage.register(maximize_clean) + +hooks.property.register(urgent.add) +hooks.focus.register(urgent.delete) +hooks.unmanage.register(urgent.delete) + +-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80 diff --git a/lib/awful/completion.lua.in b/lib/awful/completion.lua.in new file mode 100644 index 000000000..2d167594d --- /dev/null +++ b/lib/awful/completion.lua.in @@ -0,0 +1,112 @@ +--------------------------------------------------------------------------- +-- @author Julien Danjou <julien@danjou.info> +-- @copyright 2008 Julien Danjou +-- @release @AWESOME_VERSION@ +--------------------------------------------------------------------------- + +-- Grab environment we need +local io = io +local table = table + +--- Completion module for awful +module("awful.completion") + +-- mapping of command/completion function +local bashcomp_funcs = {} +local bashcomp_src = "/etc/bash_completion" + +--- Enable programmable bash completion in awful.completion.bash at the price of +-- a slight overhead +-- @param src The bash completion source file, /etc/bash_completion by default. +function bashcomp_load(src) + if src then bashcomp_src = src end + local c = io.popen("/usr/bin/env bash -c 'source " .. bashcomp_src .. "; complete -p'") + while true do + local line = c:read("*line") + if not line then break end + -- if a bash function is used for completion, register it + if line:match(".* -F .*") then + bashcomp_funcs[line:gsub(".* (%S+)$","%1")] = line:gsub(".*-F +(%S+) .*$", "%1") + end + end + c:close() +end + +--- Use bash completion system to complete command and filename. +-- @param command The command line. +-- @param cur_pos The cursor position. +-- @param ncomp The element number to complete. +-- @return The new command and the new cursor position. +function bash(command, cur_pos, ncomp) + local wstart = 1 + local wend = 1 + local words = {} + local cword_index = 0 + local cword_start = 0 + local cword_end = 0 + local i = 1 + local comptype = "file" + + -- do nothing if we are on a letter, i.e. not at len + 1 or on a space + if cur_pos ~= #command + 1 and command:sub(cur_pos, cur_pos) ~= " " then + return command, cur_pos + elseif #command == 0 then + return command, cur_pos + end + + while wend <= #command do + wend = command:find(" ", wstart) + if not wend then wend = #command + 1 end + table.insert(words, command:sub(wstart, wend - 1)) + if cur_pos >= wstart and cur_pos <= wend + 1 then + cword_start = wstart + cword_end = wend + cword_index = i + end + wstart = wend + 1 + i = i + 1 + end + + if cword_index == 1 then + comptype = "command" + end + + local bash_cmd + if bashcomp_funcs[words[1]] then + -- fairly complex command with inline bash script to get the possible completions + bash_cmd = "/usr/bin/env bash -c 'source " .. bashcomp_src .. "; " .. + "__print_completions() { for ((i=0;i<${#COMPREPLY[*]};i++)); do echo ${COMPREPLY[i]}; done }; " .. + "COMP_WORDS=(" .. command .."); COMP_LINE=\"" .. command .. "\"; " .. + "COMP_COUNT=" .. cur_pos .. "; COMP_CWORD=" .. cword_index-1 .. "; " .. + bashcomp_funcs[words[1]] .. "; __print_completions | sort -u'" + else + bash_cmd = "/usr/bin/env bash -c 'compgen -A " .. comptype .. " " .. words[cword_index] .. "'" + end + local c = io.popen(bash_cmd) + local output = {} + i = 0 + while true do + local line = c:read("*line") + if not line then break end + table.insert(output, line) + end + + c:close() + + -- no completion, return + if #output == 0 then + return command, cur_pos + end + + -- cycle + while ncomp > #output do + ncomp = ncomp - #output + end + + local str = command:sub(1, cword_start - 1) .. output[ncomp] .. command:sub(cword_end) + cur_pos = cword_end + #output[ncomp] + 1 + + return str, cur_pos +end + +-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80 diff --git a/lib/awful/hooks.lua.in b/lib/awful/hooks.lua.in new file mode 100644 index 000000000..794f3c05a --- /dev/null +++ b/lib/awful/hooks.lua.in @@ -0,0 +1,125 @@ +--------------------------------------------------------------------------- +-- @author Julien Danjou <julien@danjou.info> +-- @copyright 2008 Julien Danjou +-- @release @AWESOME_VERSION@ +--------------------------------------------------------------------------- + +-- Grab environment we need +local pairs = pairs +local table = table +local ipairs = ipairs +local type = type +local math = math +local capi = +{ + hooks = hooks +} + +--- Hooks module for awful +module("awful.hooks") + +-- User hook functions +user = {} + +--- Create a new userhook (for external libs). +-- @param name Hook name. +function user.create(name) + _M[name] = {} + _M[name].callbacks = {} + _M[name].register = function (f) + table.insert(_M[name].callbacks, f) + end + _M[name].unregister = function (f) + for k, h in ipairs(_M[name].callbacks) do + if h == f then + table.remove(_M[name].callbacks, k) + break + end + end + end +end + +--- Call a created userhook (for external libs). +-- @param name Hook name. +function user.call(name, ...) + for name, callback in pairs(_M[name].callbacks) do + callback(...) + end +end + +-- Autodeclare awful.hooks.* functions +-- mapped to awesome hooks.* functions +for name, hook in pairs(capi.hooks) do + if name ~= 'timer' then + _M[name] = {} + _M[name].register = function (f) + if not _M[name].callbacks then + _M[name].callbacks = {} + hook(function (...) + for i, callback in ipairs(_M[name].callbacks) do + callback(...) + end + end) + end + + table.insert(_M[name].callbacks, f) + end + _M[name].unregister = function (f) + if _M[name].callbacks then + for k, h in ipairs(_M[name].callbacks) do + if h == f then + table.remove(_M[name].callbacks, k) + break + end + end + end + end + else + _M[name] = {} + _M[name].register = function (time, f, runnow) + if type(time) ~= 'number' or type(f) ~= 'function' or time <= 0 then + return + end + local new_timer + if _M[name].timer then + -- Take the smallest between current and new + new_timer = math.min(time, _M[name].timer) + else + new_timer = time + end + if not _M[name].callbacks then + _M[name].callbacks = {} + end + if _M[name].timer ~= new_timer then + _M[name].timer = new_timer + hook(_M[name].timer, function (...) + for i, callback in ipairs(_M[name].callbacks) do + callback['counter'] = callback['counter'] + _M[name].timer + if callback['counter'] >= callback['timer'] then + callback['callback'](...) + callback['counter'] = 0 + end + end + end) + end + + if runnow then + table.insert(_M[name].callbacks, { callback = f, timer = time, counter = time }) + else + table.insert(_M[name].callbacks, { callback = f, timer = time, counter = 0 }) + end + end + _M[name].unregister = function (f) + if _M[name].callbacks then + for k, h in ipairs(_M[name].callbacks) do + if h.callback == f then + table.remove(_M[name].callbacks, k) + break + end + end + end + end + end +end + +-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80 diff --git a/lib/awful/init.lua.in b/lib/awful/init.lua.in new file mode 100644 index 000000000..2c7981472 --- /dev/null +++ b/lib/awful/init.lua.in @@ -0,0 +1,23 @@ +--------------------------------------------------------------------------- +-- @author Julien Danjou <julien@danjou.info> +-- @copyright 2008 Julien Danjou +-- @release @AWESOME_VERSION@ +--------------------------------------------------------------------------- + +local beautiful = require("awful.beautiful") +local client = require("awful.client") +local completion = require("awful.completion") +local hooks = require("awful.hooks") +local layout = require("awful.layout") +local placement = require("awful.placement") +local prompt = require("awful.prompt") +local screen = require("awful.screen") +local tag = require("awful.tag") +local titlebar = require("awful.titlebar") +local util = require("awful.util") +local widget = require("awful.widget") + +--- awful: AWesome Functions very UsefuL +module("awful") + +-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80 diff --git a/lib/awful/layout.lua.in b/lib/awful/layout.lua.in new file mode 100644 index 000000000..a1f23bfa9 --- /dev/null +++ b/lib/awful/layout.lua.in @@ -0,0 +1,53 @@ +--------------------------------------------------------------------------- +-- @author Julien Danjou <julien@danjou.info> +-- @copyright 2008 Julien Danjou +-- @release @AWESOME_VERSION@ +--------------------------------------------------------------------------- + +-- Grab environment we need +local ipairs = ipairs +local tag = require("awful.tag") + +--- Layout module for awful +module("awful.layout") + +--- Get the current layout name. +-- @param screen The screen number. +function get(screen) + local t = tag.selected(screen) + if t then + return t.layout + end +end + +--- Change the layout of the current tag. +-- @param layouts A table of layouts. +-- @param i Relative index. +function inc(layouts, i) + local t = tag.selected() + local number_of_layouts = 0 + local rev_layouts = {} + for i, v in ipairs(layouts) do + rev_layouts[v] = i + number_of_layouts = number_of_layouts + 1 + end + if t then + local cur_layout = get() + local new_layout_index = (rev_layouts[cur_layout] + i) % number_of_layouts + if new_layout_index == 0 then + new_layout_index = number_of_layouts + end + t.layout = layouts[new_layout_index] + end +end + +--- Set the layout of the current tag by name. +-- @param layout Layout name. +function set(layout) + local t = tag.selected() + if t then + t.layout = layout + end +end + +-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80 diff --git a/lib/awful/placement.lua.in b/lib/awful/placement.lua.in new file mode 100644 index 000000000..95958a138 --- /dev/null +++ b/lib/awful/placement.lua.in @@ -0,0 +1,175 @@ +--------------------------------------------------------------------------- +-- @author Julien Danjou <julien@danjou.info> +-- @copyright 2008 Julien Danjou +-- @release @AWESOME_VERSION@ +--------------------------------------------------------------------------- + +-- Grab environment we need +local ipairs = ipairs +local pairs = pairs +local math = math +local table = table +local capi = +{ + screen = screen, + mouse = mouse, +} +local client = require("awful.client") +local layout = require("awful.layout") + +--- Placement module for awful +module("awful.placement") + +--- Check if an area intersect another area. +-- @param a The area. +-- @param b The other area. +-- @return True if they intersect, false otherwise. +local function area_intersect_area(a, b) + return (b.x < a.x + a.width + and b.x + b.width > a.x + and b.y < a.y + a.height + and b.y + b.height > a.y) +end + +--- Get the intersect area between a and b. +-- @param a The area. +-- @param b The other area. +-- @return The intersect area. +local function area_intersect_area_get(a, b) + local g = {} + g.x = math.max(a.x, b.x) + g.y = math.max(a.y, b.y) + g.width = math.min(a.x + a.width, b.x + b.width) - g.x + g.height = math.min(a.y + a.height, b.y + b.height) - g.y + return g +end + +--- Remove an area from a list, splitting the space between several area that +-- can overlap. +-- @param areas Table of areas. +-- @param elem Area to remove. +-- @return The new area list. +local function area_remove(areas, elem) + local newareas = areas + for i, r in ipairs(areas) do + -- Check if the 'elem' intersect + if area_intersect_area(r, elem) then + -- It does? remove it + table.remove(areas, i) + local inter = area_intersect_area_get(r, elem) + + if inter.x > r.x then + table.insert(newareas, { + x = r.x, + y = r.y, + width = inter.x - r.x, + height = r.height + }) + end + + if inter.y > r.y then + table.insert(newareas, { + x = r.x, + y = r.y, + width = r.width, + height = inter.y - r.y + }) + end + + if inter.x + inter.width < r.x + r.width then + table.insert(newareas, { + x = inter.x + inter.width, + y = r.y, + width = (r.x + r.width) - (inter.x + inter.width), + height = r.height + }) + end + + if inter.y + inter.height < r.y + r.height then + table.insert(newareas, { + x = r.x, + y = inter.y + inter.height, + width = r.width, + height = (r.y + r.height) - (inter.y + inter.height) + }) + end + end + end + + return newareas +end + +--- Place the client without it being outside the screen. +-- @param c The client. +function no_offscreen(c) + local geometry = c:fullcoords() + local screen_geometry = capi.screen[c.screen].workarea + + if geometry.x + geometry.width > screen_geometry.x + screen_geometry.width then + geometry.x = screen_geometry.x + screen_geometry.width - geometry.width + elseif geometry.x < screen_geometry.x then + geometry.x = screen_geometry.x + end + + if geometry.y + geometry.height > screen_geometry.y + screen_geometry.height then + geometry.y = screen_geometry.y + screen_geometry.height - geometry.height + elseif geometry.y < screen_geometry.y then + geometry.y = screen_geometry.y + end + + c:fullcoords(geometry) +end + +--- Place the client where there's place available with minimum overlap. +-- @param c The client. +function no_overlap(c) + local cls = client.visible(c.screen) + local layout = layout.get() + local areas = { capi.screen[c.screen].workarea } + local coords = c:coords() + local fullcoords = c:fullcoords() + for i, cl in pairs(cls) do + if cl ~= c and (cl.floating or layout == "floating") then + areas = area_remove(areas, cl:fullcoords()) + end + end + + -- Look for available space + local found = false + local new = { x = coords.x, y = coords.y, width = 0, height = 0 } + for i, r in ipairs(areas) do + if r.width >= fullcoords.width + and r.height >= fullcoords.height + and r.width * r.height > new.width * new.height then + found = true + new = r + end + end + + -- We did not foudn an area with enough space for our size: + -- just take the biggest available one and go in + if not found then + for i, r in ipairs(areas) do + if r.width * r.height > new.width * new.height then + new = r + end + end + end + + -- Restore height and width + new.width = coords.width + new.height = coords.height + + c:coords(new) +end + +--- Place the client under the mouse. +-- @param c The client. +function under_mouse(c) + local c_coords = c:coords() + local m_coords = capi.mouse.coords() + c:coords({ x = m_coords.x - c_coords.width / 2, + y = m_coords.y - c_coords.height / 2 }) +end + +-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80 diff --git a/lib/awful/prompt.lua.in b/lib/awful/prompt.lua.in new file mode 100644 index 000000000..4e10b0869 --- /dev/null +++ b/lib/awful/prompt.lua.in @@ -0,0 +1,305 @@ +--------------------------------------------------------------------------- +-- @author Julien Danjou <julien@danjou.info> +-- @copyright 2008 Julien Danjou +-- @release @AWESOME_VERSION@ +--------------------------------------------------------------------------- + +-- Grab environment we need +local assert = assert +local io = io +local table = table +local capi = +{ + keygrabber = keygrabber +} +local util = require("awful.util") +local beautiful = require("awful.beautiful") + +--- Prompt module for awful +module("awful.prompt") + +--- Private data +local data = {} +data.history = {} + +--- Load history file in history table +-- @param id The data.history identifier which is the path to the filename +-- @param max Optional parameter, the maximum number of entries in file +local function history_check_load(id, max) + if id and id ~= "" + and not data.history[id] then + data.history[id] = { max = 50, table = {} } + + if max then + data.history[id].max = max + end + + local f = io.open(id, "r") + + -- Read history file + if f then + for line in f:lines() do + table.insert(data.history[id].table, line) + if #data.history[id].table >= data.history[id].max then + break + end + end + end + end +end + +--- Save history table in history file +-- @param id The data.history identifier +local function history_save(id) + if data.history[id] then + local f = io.open(id, "w") + if not f then + local i = 0 + for d in id:gmatch(".-/") do + i = i + #d + end + util.mkdir(id:sub(1, i - 1)) + f = assert(io.open(id, "w")) + end + for i = 1, math.min(#data.history[id].table, data.history[id].max) do + f:write(data.history[id].table[i] .. "\n") + end + f:close() + end +end + +--- Return the number of items in history table regarding the id +-- @param id The data.history identifier +-- @return the number of items in history table, -1 if history is disabled +local function history_items(id) + if data.history[id] then + return #data.history[id].table + else + return -1 + end +end + +--- Add an entry to the history file +-- @param id The data.history identifier +-- @param command The command to add +local function history_add(id, command) + if data.history[id] then + if command ~= "" + and command ~= data.history[id].table[#data.history[id].table] then + table.insert(data.history[id].table, command) + + -- Do not exceed our max_cmd + if #data.history[id].table > data.history[id].max then + table.remove(data.history[id].table, 1) + end + + history_save(id) + end + end +end + + +--- Draw the prompt text with a cursor. +-- @param text The text. +-- @param text_color The text color. +-- @param cursor_color The cursor color. +-- @param cursor_pos The cursor position. +local function prompt_text_with_cursor(text, text_color, cursor_color, cursor_pos) + local char + if not text then text = "" end + if #text < cursor_pos then + char = " " + else + char = util.escape(text:sub(cursor_pos, cursor_pos)) + end + local text_start = util.escape(text:sub(1, cursor_pos - 1)) + local text_end = util.escape(text:sub(cursor_pos + 1)) + return text_start .. "" .. char .. "" .. text_end +end + +--- Run a prompt in a box. +-- @param args A table with optional arguments: fg_cursor, bg_cursor, prompt. +-- @param textbox The textbox to use for the prompt. +-- @param exe_callback The callback function to call with command as argument when finished. +-- @param completion_callback The callback function to call to get completion. +-- @param history_path Optional parameter: file path where the history should be saved, set nil to disable history +-- @param history_max Optional parameter: set the maximum entries in history file, 50 by default +-- @param done_callback Optional parameter: the callback function to always call without arguments, regardless of whether the prompt was cancelled. +function run(args, textbox, exe_callback, completion_callback, history_path, history_max, done_callback) + local theme = beautiful.get() + if not args then args = {} end + local command = "" + local command_before_comp + local cur_pos_before_comp + local prettyprompt = args.prompt or "" + local inv_col = args.fg_cursor or theme.fg_focus or "black" + local cur_col = args.bg_cursor or theme.bg_focus or "white" + + history_check_load(history_path, history_max) + local history_index = history_items(history_path) + 1 + -- The cursor position + local cur_pos = 1 + -- The completion element to use on completion request. + local ncomp = 1 + if not textbox or not exe_callback then + return + end + textbox.text = prettyprompt .. prompt_text_with_cursor(text, inv_col, cur_col, cur_pos) + capi.keygrabber.run( + function (mod, key) + -- Get out cases + if mod.Control then + if key == "c" or key == "g" then + textbox.text = "" + if done_callback then done_callback() end + return false + elseif key == "j" or key == "m" then + textbox.text = "" + exec_callback(command) + if done_callback then done_callback() end + return false + end + else + if key == "Return" then + textbox.text = "" + data.history_add(history_path, command) + exe_callback(command) + if done_callback then done_callback() end + return false + elseif key == "Escape" then + textbox.text = "" + if done_callback then done_callback() end + return false + end + end + + -- Control cases + if mod.Control then + if key == "a" then + cur_pos = 1 + elseif key == "b" then + if cur_pos > 1 then + cur_pos = cur_pos - 1 + end + elseif key == "d" then + if cur_pos <= #command then + command = command:sub(1, cur_pos - 1) .. command:sub(cur_pos + 1) + end + elseif key == "e" then + cur_pos = #command + 1 + elseif key == "f" then + if cur_pos <= #command then + cur_pos = cur_pos + 1 + end + elseif key == "h" then + if cur_pos > 1 then + command = command:sub(1, cur_pos - 2) .. command:sub(cur_pos) + cur_pos = cur_pos - 1 + end + elseif key == "k" then + command = command:sub(1, cur_pos - 1) + elseif key == "u" then + command = command:sub(cur_pos, #command) + cur_pos = 1 + elseif key == "w" then + local wstart = 1 + local wend = 1 + local cword_start = 1 + local cword_end = 1 + while wend < cur_pos do + wend = command:find(" ", wstart) + if not wend then wend = #command + 1 end + if cur_pos >= wstart and cur_pos <= wend + 1 then + cword_start = wstart + cword_end = cur_pos - 1 + break + end + wstart = wend + 1 + end + command = command:sub(1, cword_start - 1) .. command:sub(cword_end + 1) + cur_pos = cword_start + end + else + if completion_callback then + -- That's tab + if key:byte() == 9 or key == "ISO_Left_Tab" then + if key == "ISO_Left_Tab" then + if ncomp == 1 then return true end + if ncomp == 2 then + command = command_before_comp + textbox.text = prettyprompt .. prompt_text_with_cursor(command_before_comp, inv_col, cur_col, cur_pos) + return true + end + + ncomp = ncomp - 2 + elseif ncomp == 1 then + command_before_comp = command + cur_pos_before_comp = cur_pos + end + command, cur_pos = completion_callback(command_before_comp, cur_pos_before_comp, ncomp) + ncomp = ncomp + 1 + key = "" + else + ncomp = 1 + end + end + + -- Typin cases + if key == "Home" then + cur_pos = 1 + elseif key == "End" then + cur_pos = #command + 1 + elseif key == "BackSpace" then + if cur_pos > 1 then + command = command:sub(1, cur_pos - 2) .. command:sub(cur_pos) + cur_pos = cur_pos - 1 + end + -- That's DEL + elseif key:byte() == 127 then + command = command:sub(1, cur_pos - 1) .. command:sub(cur_pos + 1) + elseif key == "Left" then + cur_pos = cur_pos - 1 + elseif key == "Right" then + cur_pos = cur_pos + 1 + elseif key == "Up" then + if history_index > 1 then + history_index = history_index - 1 + + command = data.history[history_path].table[history_index] + cur_pos = #command + 2 + end + elseif key == "Down" then + if history_index < history_items(history_path) then + history_index = history_index + 1 + + command = data.history[history_path].table[history_index] + cur_pos = #command + 2 + elseif history_index == history_items(history_path) then + history_index = history_index + 1 + + command = "" + cur_pos = 1 + end + else + -- len() is UTF-8 aware but #key is not, + -- so check that we have one UTF-8 char but advance the cursor of # position + if key:len() == 1 then + command = command:sub(1, cur_pos - 1) .. key .. command:sub(cur_pos) + cur_pos = cur_pos + #key + end + end + if cur_pos < 1 then + cur_pos = 1 + elseif cur_pos > #command + 1 then + cur_pos = #command + 1 + end + end + + -- Update textbox + textbox.text = prettyprompt .. prompt_text_with_cursor(command, inv_col, cur_col, cur_pos) + + return true + end) +end + +-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80 diff --git a/lib/awful/screen.lua.in b/lib/awful/screen.lua.in new file mode 100644 index 000000000..24729598c --- /dev/null +++ b/lib/awful/screen.lua.in @@ -0,0 +1,30 @@ +--------------------------------------------------------------------------- +-- @author Julien Danjou <julien@danjou.info> +-- @copyright 2008 Julien Danjou +-- @release @AWESOME_VERSION@ +--------------------------------------------------------------------------- + +-- Grab environment we need +local capi = +{ + mouse = mouse, + screen = screen, + client = client +} +local util = require("awful.util") +local client = require("awful.client") + +--- Screen module for awful +module("awful.screen") + +--- Give the focus to a screen, and move pointer. +-- @param Screen number. +function focus(i) + local s = util.cycle(capi.screen.count(), capi.mouse.screen + i) + local c = client.focus.history.get(s, 0) + if c then capi.client.focus = c end + -- Move the mouse on the screen + capi.mouse.screen = s +end + +-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80 diff --git a/lib/awful/tag.lua.in b/lib/awful/tag.lua.in new file mode 100644 index 000000000..18e118ebc --- /dev/null +++ b/lib/awful/tag.lua.in @@ -0,0 +1,206 @@ +--------------------------------------------------------------------------- +-- @author Julien Danjou <julien@danjou.info> +-- @copyright 2008 Julien Danjou +-- @release @AWESOME_VERSION@ +--------------------------------------------------------------------------- + +-- Grab environment we need +local hooks = require("awful.hooks") +local util = require("awful.util") +local pairs = pairs +local ipairs = ipairs +local capi = +{ + screen = screen, + mouse = mouse +} + +--- Tag module for awful +module("awful.tag") + +-- Private data +local data = {} +data.history = {} +data.history.past = {} +data.history.current = {} + +-- History functions +history = {} + +--- Compare 2 tables of tags. +-- @param a The first table. +-- @param b The second table of tags. +-- @return True if the tables are identical, false otherwise. +local function compare_select(a, b) + if not a or not b then + return false + end + -- Quick size comparison + if #a ~= #b then + return false + end + for ka, va in pairs(a) do + if b[ka] ~= va.selected then + return false + end + end + for kb, vb in pairs(b) do + if a[kb].selected ~= vb then + return false + end + end + return true +end + +--- Update the tag history. +-- @param screen The screen number. +function history.update(screen) + local curtags = capi.screen[screen]:tags() + if not compare_select(curtags, data.history.current[screen]) then + data.history.past[screen] = data.history.current[screen] + data.history.current[screen] = {} + for k, v in ipairs(curtags) do + data.history.current[screen][k] = v.selected + end + end +end + +-- Revert tag history. +-- @param screen The screen number. +function history.restore(screen) + local s = screen or capi.mouse.screen + local tags = capi.screen[s]:tags() + for k, t in pairs(tags) do + t.selected = data.history.past[s][k] + end +end + +--- Return a table with all visible tags +-- @param s Screen number. +-- @return A table with all selected tags. +function selectedlist(s) + local screen = s or capi.mouse.screen + local tags = capi.screen[screen]:tags() + local vtags = {} + for i, t in pairs(tags) do + if t.selected then + vtags[#vtags + 1] = t + end + end + return vtags +end + +--- Return only the first visible tag. +-- @param s Screen number. +function selected(s) + return selectedlist(s)[1] +end + +--- Set master width factor. +-- @param mwfact Master width factor. +function setmwfact(mwfact) + local t = selected() + if t then + t.mwfact = mwfact + end +end + +--- Increase master width factor. +-- @param add Value to add to master width factor. +function incmwfact(add) + local t = selected() + if t then + t.mwfact = t.mwfact + add + end +end + +--- Set the number of master windows. +-- @param nmaster The number of master windows. +function setnmaster(nmaster) + local t = selected() + if t then + t.nmaster = nmaster + end +end + +--- Increase the number of master windows. +-- @param add Value to add to number of master windows. +function incnmaster(add) + local t = selected() + if t then + t.nmaster = t.nmaster + add + end +end + +--- Set number of column windows. +-- @param ncol The number of column. +function setncol(ncol) + local t = selected() + if t then + t.ncol = ncol + end +end + +--- Increase number of column windows. +-- @param add Value to add to number of column windows. +function incncol(add) + local t = selected() + if t then + t.ncol = t.ncol + add + end +end + +--- View no tag. +-- @param Optional screen number. +function viewnone(screen) + local tags = capi.screen[screen or capi.mouse.screen]:tags() + for i, t in pairs(tags) do + t.selected = false + end +end + +--- View a tag by its index. +-- @param i The relative index to see. +-- @param screen Optional screen number. +function viewidx(i, screen) + local tags = capi.screen[screen or capi.mouse.screen]:tags() + local sel = selected() + viewnone() + for k, t in ipairs(tags) do + if t == sel then + tags[util.cycle(#tags, k + i)].selected = true + end + end +end + +--- View next tag. This is the same as tag.viewidx(1). +function viewnext() + return viewidx(1) +end + +--- View previous tag. This is the same a tag.viewidx(-1). +function viewprev() + return viewidx(-1) +end + +--- View only a tag. +-- @param t The tag object. +function viewonly(t) + viewnone(t.screen) + t.selected = true +end + +--- View only a set of tags. +-- @param tags A table with tags to view only. +-- @param screen Optional screen number of the tags. +function viewmore(tags, screen) + viewnone(screen) + for i, t in pairs(tags) do + t.selected = true + end +end + +-- Register standards hooks +hooks.arrange.register(history.update) + +-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80 diff --git a/lib/awful/titlebar.lua.in b/lib/awful/titlebar.lua.in new file mode 100644 index 000000000..090f3913e --- /dev/null +++ b/lib/awful/titlebar.lua.in @@ -0,0 +1,163 @@ +--------------------------------------------------------------------------- +-- @author Julien Danjou <julien@danjou.info> +-- @copyright 2008 Julien Danjou +-- @release @AWESOME_VERSION@ +--------------------------------------------------------------------------- + +-- Grab environment we need +local math = math +local ipairs = ipairs +local otable = otable +local capi = +{ + wibox = wibox, + widget = widget, + button = button, + client = client, +} +local hooks = require("awful.hooks") +local util = require("awful.util") + +--- Titlebar module for awful +module("awful.titlebar") + +-- Privata data +local data = otable() + +--- Create a standard titlebar. +-- @param c The client. +-- @param args Arguments. +-- fg: the foreground color. +-- bg: the background color. +-- fg_focus: the foreground color for focused window. +-- fg_focus: the background color for focused window. +-- width: the titlebar width +function add(c, args) + if not c or c.type ~= "normal" then return end + if not args then args = {} end + -- Store colors + data[c] = {} + data[c].fg = args.fg or theme.titlebar_fg_normal or theme.fg_normal + data[c].bg = args.bg or theme.titlebar_bg_normal or theme.bg_normal + data[c].fg_focus = args.fg_focus or theme.titlebar_fg_focus or theme.fg_focus + data[c].bg_focus = args.bg_focus or theme.titlebar_bg_focus or theme.bg_focus + data[c].width = args.width + + -- Built args + local targs = {} + if args.fg then targs.fg = args.fg end + if args.bg then targs.bg = args.bg end + local tb = capi.wibox(targs) + + local title = capi.widget({ type = "textbox", name = "title", align = "flex" }) + title.text = " " .. util.escape(c.name) .. " " + local bts = + { + capi.button({ }, 1, function (t) t.client:mouse_move() end), + capi.button({ args.modkey }, 3, function (t) t.client:mouse_resize() end) + } + title:buttons(bts) + + local appicon = capi.widget({ type = "imagebox", name = "appicon", align = "left" }) + appicon.image = c.icon + + if theme.titlebar_close_button == "true" then + local closef = widget.button({ name = "closef", align = "right", + image = theme.titlebar_close_button_focus + or theme.titlebar_close_button_img_focus + or "@AWESOME_ICON_PATH@/titlebar/closer.png" }) + local close = widget.button({ name = "close", align = "right", + image = theme.titlebar_close_button_normal + or theme.titlebar_close_button_img_normal + or "@AWESOME_ICON_PATH@/titlebar/close.png" }) + + -- Bind kill button + local b = capi.button({ }, 1, nil, function (t) t.client:kill() end) + local bts = closef:buttons() + bts[#bts + 1] = b + closef:buttons(bts) + + bts = close:buttons() + bts[#bts + 1] = b + close:buttons(bts) + + tb:widgets({ + appicon, + title, + closef, close + }) + else + tb:widgets({ + appicon, + title + }) + end + + c.titlebar = tb + + update(c) + update(c, "geometry") +end + +--- Update a titlebar. This should be called in some hooks. +-- @param c The client to update. +-- @param prop The property name which has changed. +function update(c, prop) + if c.titlebar and data[c] then + local widgets = c.titlebar:widgets() + local title, close, closef + for k, v in ipairs(widgets) do + if v.name == "title" then title = v + elseif v.name == "close" then close = v + elseif v.name == "closef" then closef = v + elseif v.name == "appicon" then appicon = v end + if title and close and closef and appicon then break end + end + if prop == "name" then + if title then + title.text = " " .. util.escape(c.name) .. " " + end + elseif prop == "icon" then + if appicon then + appicon.image = c.icon + end + elseif prop == "geometry" then + if data[c].width then + if c.titlebar.position == "top" + or c.titlebar.position == "bottom" then + local w = math.min(data[c].width, c:coords().width + 2 * c.border_width) + c.titlebar:geometry({ width = w }) + else + local w = math.min(data[c].width, c:coords().height + 2 * c.border_width) + c.titlebar:geometry({ height = w }) + end + end + end + if capi.client.focus == c then + c.titlebar.fg = data[c].fg_focus + c.titlebar.bg = data[c].bg_focus + if closef then closef.visible = true end + if close then close.visible = false end + else + c.titlebar.fg = data[c].fg + c.titlebar.bg = data[c].bg + if closef then closef.visible = false end + if close then close.visible = true end + end + end +end + +--- Remove a titlebar from a client. +-- @param c The client. +function remove(c) + c.titlebar = nil + data[c] = nil +end + +-- Register standards hooks +hooks.focus.register(update) +hooks.unfocus.register(update) +hooks.property.register(update) +hooks.unmanage.register(remove) + +-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80 diff --git a/lib/awful/util.lua.in b/lib/awful/util.lua.in new file mode 100644 index 000000000..be2369bf0 --- /dev/null +++ b/lib/awful/util.lua.in @@ -0,0 +1,112 @@ +--------------------------------------------------------------------------- +-- @author Julien Danjou <julien@danjou.info> +-- @copyright 2008 Julien Danjou +-- @release @AWESOME_VERSION@ +--------------------------------------------------------------------------- + +-- Grab environment we need +local os = os +local io = io +local assert = assert +local loadstring = loadstring +local debug = debug +local print = print +local capi = +{ + awesome = awesome, + mouse = mouse +} + +--- Utility module for awful +module("awful.util") + +function deprecate() + print("W: awful: function is deprecated") + print(debug.traceback()) +end + +--- Strip alpha part of color. +-- @param color The color. +-- @return The color without alpha channel. +function color_strip_alpha(color) + if color:len() == 9 then + color = color:sub(1, 7) + end + return color +end + +--- Make i cycle. +-- @param t A length. +-- @param i An absolute index to fit into #t. +-- @return The object at new index. +function cycle(t, i) + while i > t do i = i - t end + while i < 1 do i = i + t end + return i +end + +--- Create a directory +-- @param dir The directory. +-- @return mkdir return code +function mkdir(dir) + return os.execute("mkdir -p " .. dir) +end + +--- Spawn a program. +-- @param cmd The command. +-- @param screen The screen where to spawn window. +-- @return The awesome.spawn return value. +function spawn(cmd, screen) + if cmd and cmd ~= "" then + return capi.awesome.spawn(cmd .. "&", screen or capi.mouse.screen) + end +end + +-- Read a program output and returns its output as a string. +-- @param cmd The command to run. +-- @return A string with the program output. +function pread(cmd) + if cmd and cmd ~= "" then + local f = io.popen(cmd, 'r') + local s = f:read("*all") + f:close() + return s + end +end + +--- Eval Lua code. +-- @return The return value of Lua code. +function eval(s) + return assert(loadstring("return " .. s))() +end + +--- Escape a string from XML char. +-- Useful to set raw text in textbox. +-- @param text Text to escape. +-- @return Escape text. +function escape(text) + if text then + text = text:gsub("&", "&") + text = text:gsub("<", "<") + text = text:gsub(">", ">") + text = text:gsub("'", "'") + text = text:gsub("\"", """) + end + return text +end + +--- Unescape a string from entities. +-- @param text Text to unescape. +-- @return Unescaped text. +function unescape(text) + if text then + text = text:gsub("&", "&") + text = text:gsub("<", "<") + text = text:gsub(">", ">") + text = text:gsub("'", "'") + text = text:gsub(""", "\"") + end + return text +end + +-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80 diff --git a/lib/awful/widget.lua.in b/lib/awful/widget.lua.in new file mode 100644 index 000000000..03fdbddf9 --- /dev/null +++ b/lib/awful/widget.lua.in @@ -0,0 +1,255 @@ +--------------------------------------------------------------------------- +-- @author Julien Danjou <julien@danjou.info> +-- @copyright 2008 Julien Danjou +-- @release @AWESOME_VERSION@ +--------------------------------------------------------------------------- + +-- Grab environment we need +local ipairs = ipairs +local pairs = pairs +local capi = +{ + screen = screen, + client = client, + button = button, + widget = widget, + mouse = mouse +} +local util = require("awful.util") +local beautiful = require("awful.beautiful") + +--- Widget module for awful +module("awful.widget") + +-- Various public structures +taglist = {} +taglist.label = {} +tasklist = {} +tasklist.label = {} + +--- Return labels for a taglist widget with all tag from screen. +-- It returns the tag name and set a special +-- foreground and background color for selected tags. +-- @param t The tag. +-- @param args The arguments table. +-- bg_focus The background color for selected tag. +-- fg_focus The foreground color for selected tag. +-- bg_urgent The background color for urgent tags. +-- fg_urgent The foreground color for urgent tags. +-- taglist_squares Optional: set "true" or nil to display the taglist squares. +-- taglist_squares_sel Optional: an user provided image for selected squares. +-- taglist_squares_unsel Optional: an user provided image for unselected squares. +-- @return A string to print. +function taglist.label.all(t, args) + if not args then args = {} end + local theme = beautiful.get() + local fg_focus = args.fg_focus or theme.taglist_fg_focus or theme.fg_focus + local bg_focus = args.bg_focus or theme.taglist_bg_focus or theme.bg_focus + local fg_urgent = args.fg_urgent or theme.taglist_fg_urgent or theme.fg_urgent + local bg_urgent = args.bg_urgent or theme.taglist_bg_urgent or theme.bg_urgent + local taglist_squares = args.taglist_squares or theme.taglist_squares + local taglist_squares_sel = args.squares_sel or theme.squares_sel + local taglist_squares_unsel = args.squares_unsel or theme.squares_unsel + local text + local background = "" + local sel = capi.client.focus + local bg_color = nil + local fg_color = nil + if t.selected then + bg_color = bg_focus + fg_color = fg_focus + end + if sel and sel:tags()[t] then + if not taglist_squares or taglist_squares == "true" then + if taglist_squares_sel then + background = "resize=\"true\" image=\"" .. taglist_squares_sel .. "\"" + else + background = "resize=\"true\" image=\"@AWESOME_ICON_PATH@/taglist/squarefw.png\"" + end + end + elseif bg_urgent or fg_urgent then + for k, c in pairs(t:clients()) do + if not taglist_squares or taglist_squares == "true" then + if taglist_squares_unsel then + background = "resize=\"true\" image=\"" .. taglist_squares_unsel .. "\"" + else + background = "resize=\"true\" image=\"@AWESOME_ICON_PATH@/taglist/squarew.png\"" + end + end + if c.urgent then + bg_color = bg_urgent + fg_color = fg_urgent + break + end + end + end + if bg_color and fg_color then + text = " "..util.escape(t.name).." " + else + text = " "..util.escape(t.name).." " + end + return text +end + +--- Return labels for a taglist widget with all *non empty* tags from screen. +-- It returns the tag name and set a special +-- foreground and background color for selected tags. +-- @param t The tag. +-- @param args The arguments table. +-- bg_focus The background color for selected tag. +-- fg_focus The foreground color for selected tag. +-- bg_urgent The background color for urgent tags. +-- fg_urgent The foreground color for urgent tags. +-- @return A string to print. +function taglist.label.noempty(t, args) + if #t:clients() > 0 or t.selected then + if not args then args = {} end + local theme = beautiful.get() + local fg_focus = args.fg_focus or theme.taglist_fg_focus or theme.fg_focus + local bg_focus = args.bg_focus or theme.taglist_bg_focus or theme.bg_focus + local fg_urgent = args.fg_urgent or theme.taglist_fg_urgent or theme.fg_urgent + local bg_urgent = args.bg_urgent or theme.taglist_bg_urgent or theme.bg_urgent + local bg_color = nil + local fg_color = nil + local text + + if t.selected then + bg_color = bg_focus + fg_color = fg_focus + end + if bg_urgent and fg_urgent then + for k, c in pairs(t:clients()) do + if c.urgent then + bg_color = bg_urgent + fg_color = fg_urgent + break + end + end + end + if fg_color and bg_color then + text = " " .. util.escape(t.name) .. " " + else + text = " " .. util.escape(t.name) .. " " + end + return text + end +end + +local function widget_tasklist_label_common(c, args) + if not args then args = {} end + local theme = beautiful.get() + local fg_focus = args.fg_focus or theme.tasklist_fg_focus or theme.fg_focus + local bg_focus = args.bg_focus or theme.tasklist_bg_focus or theme.bg_focus + local fg_urgent = args.fg_urgent or theme.tasklist_fg_urgent or theme.fg_urgent + local bg_urgent = args.bg_urgent or theme.tasklist_bg_urgent or theme.bg_urgent + local text = "" + local name + if c.floating then + text = "" + end + if c.minimized then + name = util.escape(c.icon_name) or "" + else + name = util.escape(c.name) or "" + end + if capi.client.focus == c then + if bg_focus and fg_focus then + text = text .. " "..name.." " + else + text = text .. " "..name.." " + end + elseif c.urgent and bg_urgent and fg_urgent then + text = text .. " "..name.." " + else + text = text .. " "..name.." " + end + return text +end + +--- Return labels for a tasklist widget with clients from all tags and screen. +-- It returns the client name and set a special +-- foreground and background color for focused client. +-- It also puts a special icon for floating windows. +-- @param c The client. +-- @param screen The screen we are drawing on. +-- @param args The arguments table. +-- bg_focus The background color for focused client. +-- fg_focus The foreground color for focused client. +-- bg_urgent The background color for urgent clients. +-- fg_urgent The foreground color for urgent clients. +-- @return A string to print. +function tasklist.label.allscreen(c, screen, args) + return widget_tasklist_label_common(c, args) +end + +--- Return labels for a tasklist widget with clients from all tags. +-- It returns the client name and set a special +-- foreground and background color for focused client. +-- It also puts a special icon for floating windows. +-- @param c The client. +-- @param screen The screen we are drawing on. +-- @param args The arguments table. +-- bg_focus The background color for focused client. +-- fg_focus The foreground color for focused client. +-- bg_urgent The background color for urgent clients. +-- fg_urgent The foreground color for urgent clients. +-- @return A string to print. +function tasklist.label.alltags(c, screen, args) + -- Only print client on the same screen as this widget + if c.screen ~= screen then return end + return widget_tasklist_label_common(c, args) +end + +--- Return labels for a tasklist widget with clients from currently selected tags. +-- It returns the client name and set a special +-- foreground and background color for focused client. +-- It also puts a special icon for floating windows. +-- @param c The client. +-- @param screen The screen we are drawing on. +-- @param args The arguments table. +-- bg_focus The background color for focused client. +-- fg_focus The foreground color for focused client. +-- bg_urgent The background color for urgent clients. +-- fg_urgent The foreground color for urgent clients. +-- @return A string to print. +function tasklist.label.currenttags(c, screen, args) + -- Only print client on the same screen as this widget + if c.screen ~= screen then return end + for k, t in ipairs(capi.screen[screen]:tags()) do + if t.selected and c:tags()[t] then + return widget_tasklist_label_common(c, args) + end + end +end + +--- Create a button widget. When clicked, the image is deplaced to make it like +-- a real button. +-- @param args Standard widget table arguments, plus image for the image path. +-- @return A textbox widget configured as a button. +function button(args) + if not args then return end + args.type = "textbox" + local w = capi.widget(args) + local img_release = "" + local img_press = "" + w.text = img_release + w:buttons({ capi.button({}, 1, function () w.text = img_press end, function () w.text = img_release end) }) + function w.mouse_leave(s) w.text = img_release end + function w.mouse_enter(s) if capi.mouse.coords().buttons[1] then w.text = img_press end end + return w +end + +--- Create a button widget which will launch a command. +-- @param args Standard widget table arguments, plus image for the image path +-- and command for the command to run on click. +-- @return A launcher widget. +function launcher(args) + if not args.command then return end + local w = button(args) + local b = w:buttons() + b[#b + 1] = capi.button({}, 1, nil, function () util.spawn(args.command) end) + w:buttons(b) + return w +end + +-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80 diff --git a/lib/beautiful.lua.in b/lib/beautiful.lua.in index 150f06ebe..7afb59c9f 100644 --- a/lib/beautiful.lua.in +++ b/lib/beautiful.lua.in @@ -9,7 +9,7 @@ local io = io local print = print local setmetatable = setmetatable -local awful = require("awful") +local util = require("awful.util") local package = package local capi = { @@ -69,7 +69,7 @@ function init(path) if key then if key == "wallpaper_cmd" then for s = 1, capi.screen.count() do - awful.spawn(value, s) + util.spawn(value, s) end elseif key == "font" then capi.awesome.font(value)