local M = {} local awful = require("awful") local object = require("gears.object") local stack = require("stack") local timer = require("gears.timer") local utils = require("utils") local Workspace = {} local function desk_find_empty_page(desk) for i, page in ipairs(desk.pages) do if #page:clients() == 0 then return i end end return nil end function Workspace:_add_desktop(idx, name, nb_pages) local desk = object() desk.name = name desk.idx = idx desk.pages = {} desk.indices_free = stack.Stack:new() for i = nb_pages, 1, -1 do desk.pages[i] = awful.tag.add(string.format("%02x%02x", idx, i), { layout = self.layouts[1] }) desk.indices_free:push(i) end desk.screen_map = {} desk.find_empty_page = desk_find_empty_page self.desktops[idx] = desk return idx end function Workspace:rename_desktop(idx, name) local desk = self.desktops[idx] desk.name = name desk:emit_signal("desktop:name", name) end function Workspace:_apply_state() local orig_focus = mouse.screen for s in screen do if self.screen_state[s] then awful.tag.viewnone(s) local desk = self.screen_state[s].desktop_idx local page = self.desktops[desk].pages[self.screen_state[s].page_idx] page.screen = s end end for s in screen do local state = self.screen_state[s] if state then local desk_idx = state.desktop_idx local desk = self.desktops[desk_idx] local page_idx = state.page_idx local page = desk.pages[page_idx] page:view_only() desk:emit_signal("page:view", s, page_idx) end end timer.delayed_call(awful.screen.focus, orig_focus) end -- save pages when a screen is removed function Workspace:_tag_request_screen(page) local desk_idx = utils.tag_desk_idx(page) local page_idx = utils.tag_page_idx(page) if page.selected then self.desktops[desk_idx].indices_free:push(page_idx) page.selected = false end self.desktops[desk_idx].screen_map[page_idx] = nil for s_other in screen do if s_other ~= page.screen then page.screen = s_other break end end end function Workspace:view(screen, desktop_idx, page_idx) local desktop = self.desktops[desktop_idx] -- the current state of the target screen local state_cur = self.screen_state[screen] -- the screen on which the new page is currently displayed (if any) local screen_other = nil if page_idx == nil then -- the page is not specified if state_cur and state_cur.desktop_idx == desktop_idx then -- requested desktop is already displayed on this screen, nothing to do return end -- take the topmost free one page_idx = desktop.indices_free:pop() if page_idx == nil then utils.warn('Workspace', 'No free pages on desktop %d', desktop_idx) return end else -- check if the page is already displayed somewhere screen_other = desktop.screen_map[page_idx] if screen_other == screen then -- the page is already displayed on this screen, nothing to do return elseif screen_other == nil then desktop.indices_free:remove(page_idx) end end if screen_other then -- the page we want is already shown on some other screen, so we swap -- the contents of the two screens self.screen_state[screen] = self.screen_state[screen_other] self.screen_state[screen_other] = state_cur desktop.screen_map[page_idx] = screen self.signals:emit_signal("desktop:view", screen, desktop) if state_cur then self.desktops[state_cur.desktop_idx].screen_map[state_cur.page_idx] = screen_other self.signals:emit_signal("desktop:view", screen_other, self.desktops[state_cur.desktop_idx]) end else -- mark previous page as free if state_cur then local desk = self.desktops[state_cur.desktop_idx] desk.screen_map[state_cur.page_idx] = nil desk.indices_free:push(state_cur.page_idx) desk:emit_signal("page:hide", state_cur.page_idx) end desktop.screen_map[page_idx] = screen self.screen_state[screen] = { page_idx = page_idx, desktop_idx = desktop_idx } self.signals:emit_signal("desktop:view", screen, desktop) end self:_apply_state() end function Workspace:view_relative(offset, screen) screen = screen or mouse.screen local state = self.screen_state[screen] if state then local desk = state.desktop_idx local page = 1 + ((state.page_idx - 1 + offset) % #self.desktops[desk].pages) self:view(screen, desk, page) end end function Workspace:move_client(client, desk, page) local dst_page = self.desktops[desk].pages[page] client:move_to_screen(dst_page.screen) client:move_to_tag(dst_page) end function Workspace:client_move_relative(client, offset) -- FIXME this is wrong, mouse screen is not necessarily -- where the client is local state = self.screen_state[mouse.screen] if state then local desk = state.desktop_idx local page = 1 + ((state.page_idx - 1 + offset) % #self.desktops[desk].pages) self:move_client(client, desk, page) end end function Workspace:swap_screens(phys_idx) local screen_dst = mouse.screen local screen_src = utils.screen_physical(phys_idx) if screen_src and screen_src ~= screen_dst then local ss = self.screen_state[screen_src] self:view(screen_dst, ss.desktop_idx, ss.page_idx) timer.delayed_call(awful.screen.focus, screen_src) end end function Workspace:shift_cur_client(dir) if client.focus then local c = client.focus local s = c.screen self:client_move_relative(c, dir) timer.delayed_call(awful.screen.focus, s) end end function Workspace:new(layouts) local o = setmetatable({}, self) self.__index = self o.desktops = {} o.screen_state = {} o.layouts = layouts o.notify_tbl = {} o.signals = object() for i = 1, 12 do o:_add_desktop(i, "Desktop " .. i, 10) end -- handle screen removal tag.connect_signal("request::screen", function(t) o:_tag_request_screen(t) end) screen.connect_signal("removed", function (s) o.screen_state[s] = nil end) return o end M.Workspace = Workspace return M