summaryrefslogtreecommitdiff
path: root/workspace.lua
blob: 26e2e476ed6eaf98ba72ab35c849bee39acd0659 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
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