summaryrefslogtreecommitdiff
path: root/lib/awful/screen.lua
blob: e36e6224f86447fe6b8b89df66715d2d2a4582a9 (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
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
---------------------------------------------------------------------------
--- Screen module for awful
--
-- @author Julien Danjou <julien@danjou.info>
-- @copyright 2008 Julien Danjou
-- @module screen
---------------------------------------------------------------------------

-- Grab environment we need
local capi =
{
    mouse = mouse,
    screen = screen,
    client = client,
    awesome = awesome,
}
local util = require("awful.util")
local object = require("gears.object")
local grect =  require("gears.geometry").rectangle

local function get_screen(s)
    return s and capi.screen[s]
end

-- we use require("awful.client") inside functions to prevent circular dependencies.
local client

local screen = {object={}}

local data = {}
data.padding = {}

--- Take an input geometry and substract/add a delta.
-- @tparam table geo A geometry (width, height, x, y) table.
-- @tparam table delta A delta table (top, bottom, x, y).
-- @treturn table A geometry (width, height, x, y) table.
local function apply_geometry_ajustments(geo, delta)
    return {
        x      = geo.x      + (delta.left or 0),
        y      = geo.y      + (delta.top  or 0),
        width  = geo.width  - (delta.left or 0) - (delta.right  or 0),
        height = geo.height - (delta.top  or 0) - (delta.bottom or 0),
    }
end

--- Get the square distance between a `screen` and a point.
-- @deprecated awful.screen.getdistance_sq
-- @param s Screen
-- @param x X coordinate of point
-- @param y Y coordinate of point
-- @return The squared distance of the screen to the provided point.
-- @see screen.get_square_distance
function screen.getdistance_sq(s, x, y)
    util.deprecate("Use s:get_square_distance(x, y) instead of awful.screen.getdistance_sq")
    return screen.object.get_square_distance(s, x, y)
end

--- Get the square distance between a `screen` and a point.
-- @function screen.get_square_distance
-- @tparam number x X coordinate of point
-- @tparam number y Y coordinate of point
-- @treturn number The squared distance of the screen to the provided point.
function screen.object.get_square_distance(self, x, y)
    return grect.get_square_distance(get_screen(self).geometry, x, y)
end

--- Return the screen index corresponding to the given (pixel) coordinates.
--
-- The number returned can be used as an index into the global
-- `screen` table/object.
-- @function awful.screen.getbycoord
-- @tparam number x The x coordinate
-- @tparam number y The y coordinate
-- @treturn ?number The screen index
function screen.getbycoord(x, y)
    local s, sgeos = capi.screen.primary, {}
    for scr in capi.screen do
        sgeos[scr] = scr.geometry
    end
    s = grect.get_closest_by_coord(sgeos, x, y) or s
    return s and s.index
end

--- Move the focus to a screen.
--
-- This moves the mouse pointer to the last known position on the new screen,
-- or keeps its position relative to the current focused screen.
-- @function awful.screen.focus
-- @screen _screen Screen number (defaults / falls back to mouse.screen).
function screen.focus(_screen)
    client = client or require("awful.client")
    if type(_screen) == "number" and _screen > capi.screen.count() then _screen = screen.focused() end
    _screen = get_screen(_screen)

    -- screen and pos for current screen
    local s = get_screen(capi.mouse.screen)
    local pos

    if not _screen.mouse_per_screen then
        -- This is the first time we enter this screen,
        -- keep relative mouse position on the new screen.
        pos = capi.mouse.coords()
        local relx = (pos.x - s.geometry.x) / s.geometry.width
        local rely = (pos.y - s.geometry.y) / s.geometry.height

        pos.x = _screen.geometry.x + relx * _screen.geometry.width
        pos.y = _screen.geometry.y + rely * _screen.geometry.height
    else
        -- restore mouse position
        pos = _screen.mouse_per_screen
    end

    -- save pointer position of current screen
    s.mouse_per_screen = capi.mouse.coords()

   -- move cursor without triggering signals mouse::enter and mouse::leave
    capi.mouse.coords(pos, true)

    local c = client.focus.history.get(_screen, 0)
    if c then
        c:emit_signal("request::activate", "screen.focus", {raise=false})
    end
end

--- Move the focus to a screen in a specific direction.
--
-- This moves the mouse pointer to the last known position on the new screen,
-- or keeps its position relative to the current focused screen.
-- @function awful.screen.focus_bydirection
-- @param dir The direction, can be either "up", "down", "left" or "right".
-- @param _screen Screen.
function screen.focus_bydirection(dir, _screen)
    local sel = get_screen(_screen or screen.focused())
    if sel then
        local geomtbl = {}
        for s in capi.screen do
            geomtbl[s] = s.geometry
        end
        local target = grect.get_in_direction(dir, geomtbl, sel.geometry)
        if target then
            return screen.focus(target)
        end
    end
end

--- Move the focus to a screen relative to the current one,
--
-- This moves the mouse pointer to the last known position on the new screen,
-- or keeps its position relative to the current focused screen.
--
-- @function awful.screen.focus_relative
-- @tparam int offset Value to add to the current focused screen index. 1 to
--   focus the next one, -1 to focus the previous one.
function screen.focus_relative(offset)
    return screen.focus(util.cycle(capi.screen.count(),
                                   screen.focused().index + offset))
end

--- Get or set the screen padding.
--
-- @deprecated awful.screen.padding
-- @param _screen The screen object to change the padding on
-- @param[opt=nil] padding The padding, a table with 'top', 'left', 'right' and/or
-- 'bottom' or a number value to apply set the same padding on all sides. Can be
--  nil if you only want to retrieve padding
-- @treturn table A table with left, right, top and bottom number values.
-- @see padding
function screen.padding(_screen, padding)
    util.deprecate("Use _screen.padding = value instead of awful.screen.padding")
    if padding then
        screen.object.set_padding(_screen, padding)
    end
    return screen.object.get_padding(_screen)
end

--- The screen padding.
--
-- This adds a "buffer" section on each side of the screen.
--
-- **Signal:**
--
-- * *property::padding*
--
-- @property padding
-- @param table
-- @tfield integer table.left The padding on the left.
-- @tfield integer table.right The padding on the right.
-- @tfield integer table.top The padding on the top.
-- @tfield integer table.bottom The padding on the bottom.

function screen.object.get_padding(self)
    local p = data.padding[self] or {}
    -- Create a copy to avoid accidental mutation and nil values.
    return {
        left   = p.left   or 0,
        right  = p.right  or 0,
        top    = p.top    or 0,
        bottom = p.bottom or 0,
    }
end

function screen.object.set_padding(self, padding)
    if type(padding) == "number" then
        padding = {
            left   = padding,
            right  = padding,
            top    = padding,
            bottom = padding,
        }
    end

    self = get_screen(self)
    if padding then
        data.padding[self] = padding
        self:emit_signal("padding")
    end
end

--- Get the preferred screen in the context of a client.
--
-- This is exactly the same as `awful.screen.focused` except that it avoids
-- clients being moved when Awesome is restarted.
-- This is used in the default `rc.lua` to ensure clients get assigned to the
-- focused screen by default.
-- @tparam client c A client.
-- @treturn screen The preferred screen.
function screen.preferred(c)
    return capi.awesome.startup and c.screen or screen.focused()
end

--- The defaults arguments for `awful.screen.focused`.
-- @tfield[opt=nil] table awful.screen.default_focused_args

--- Get the focused screen.
--
-- It is possible to set `awful.screen.default_focused_args` to override the
-- default settings.
--
-- @function awful.screen.focused
-- @tparam[opt] table args
-- @tparam[opt=false] boolean args.client Use the client screen instead of the
--   mouse screen.
-- @tparam[opt=true] boolean args.mouse Use the mouse screen
-- @treturn ?screen The focused screen object, or `nil` in case no screen is
--   present currently.
function screen.focused(args)
    args = args or screen.default_focused_args or {}
    return get_screen(
        args.client and capi.client.focus and capi.client.focus.screen or capi.mouse.screen
    )
end

--- Get a placement bounding geometry.
--
-- This method computes the different variants of the "usable" screen geometry.
--
-- @function screen.get_bounding_geometry
-- @tparam[opt={}] table args The arguments
-- @tparam[opt=false] boolean args.honor_padding Whether to honor the screen's padding.
-- @tparam[opt=false] boolean args.honor_workarea Whether to honor the screen's workarea.
-- @tparam[opt] int|table args.margins Apply some margins on the output.
--   This can either be a number or a table with *left*, *right*, *top*
--   and *bottom* keys.
-- @tag[opt] args.tag Use this tag's screen.
-- @tparam[opt] drawable args.parent A parent drawable to use as base geometry.
-- @tab[opt] args.bounding_rect A bounding rectangle. This parameter is
--   incompatible with `honor_workarea`.
-- @treturn table A table with *x*, *y*, *width* and *height*.
-- @usage local geo = screen:get_bounding_geometry {
--     honor_padding  = true,
--     honor_workarea = true,
--     margins        = {
--          left = 20,
--     },
-- }
function screen.object.get_bounding_geometry(self, args)
    args = args or {}

    -- If the tag has a geometry, assume it is right
    if args.tag then
        self = args.tag.screen
    end

    self = get_screen(self or capi.mouse.screen)

    local geo = args.bounding_rect or (args.parent and args.parent:geometry()) or
        self[args.honor_workarea and "workarea" or "geometry"]

    if (not args.parent) and (not args.bounding_rect) and args.honor_padding then
        local padding = self.padding
        geo = apply_geometry_ajustments(geo, padding)
    end

    if args.margins then
        geo = apply_geometry_ajustments(geo,
            type(args.margins) == "table" and args.margins or {
                left = args.margins, right  = args.margins,
                top  = args.margins, bottom = args.margins,
            }
        )
    end
    return geo
end

--- Get the list of visible clients for the screen.
--
-- Minimized and unmanaged clients are not included in this list as they are
-- technically not on the screen.
--
-- The clients on tags that are currently not visible are not part of this list.
--
-- @property clients
-- @param table The clients list, ordered from top to bottom.
-- @see all_clients
-- @see hidden_clients
-- @see client.get

function screen.object.get_clients(s)
    local cls = capi.client.get(s, true)
    local vcls = {}
    for _, c in pairs(cls) do
        if c:isvisible() then
            table.insert(vcls, c)
        end
    end
    return vcls
end

--- Get the list of clients assigned to the screen but not currently visible.
--
-- This includes minimized clients and clients on hidden tags.
--
-- @property hidden_clients
-- @param table The clients list, ordered from top to bottom.
-- @see clients
-- @see all_clients
-- @see client.get

function screen.object.get_hidden_clients(s)
    local cls = capi.client.get(s, true)
    local vcls = {}
    for _, c in pairs(cls) do
        if not c:isvisible() then
            table.insert(vcls, c)
        end
    end
    return vcls
end

--- Get all clients assigned to the screen.
--
-- @property all_clients
-- @param table The clients list, ordered from top to bottom.
-- @see clients
-- @see hidden_clients
-- @see client.get

function screen.object.get_all_clients(s)
    return capi.client.get(s, true)
end

--- Get the list of tiled clients for the screen.
--
-- Same as `clients`, but excluding:
--
-- * fullscreen clients
-- * maximized clients
-- * floating clients
--
-- @property tiled_clients
-- @param table The clients list, ordered from top to bottom.

function screen.object.get_tiled_clients(s)
    local clients = s.clients
    local tclients = {}
    -- Remove floating clients
    for _, c in pairs(clients) do
        if not c.floating
            and not c.fullscreen
            and not c.maximized_vertical
            and not c.maximized_horizontal then
            table.insert(tclients, c)
        end
    end
    return tclients
end

--- Call a function for each existing and created-in-the-future screen.
--
-- @function awful.screen.connect_for_each_screen
-- @tparam function func The function to call.
-- @screen func.screen The screen.
function screen.connect_for_each_screen(func)
    for s in capi.screen do
        func(s)
    end
    capi.screen.connect_signal("added", func)
end

--- Undo the effect of connect_for_each_screen.
-- @function awful.screen.disconnect_for_each_screen
-- @tparam function func The function that should no longer be called.
function screen.disconnect_for_each_screen(func)
    capi.screen.disconnect_signal("added", func)
end

--- A list of all tags on the screen.
--
-- This property is read only, use `tag.screen`, `awful.tag.add`,
-- `awful.tag.new` or `t:delete()` to alter this list.
--
-- @property tags
-- @param table
-- @treturn table A table with all available tags.

function screen.object.get_tags(s, unordered)
    local tags = {}

    for _, t in ipairs(root.tags()) do
        if get_screen(t.screen) == s then
            table.insert(tags, t)
        end
    end

    -- Avoid infinite loop and save some time.
    if not unordered then
        table.sort(tags, function(a, b)
            return (a.index or math.huge) < (b.index or math.huge)
        end)
    end
    return tags
end

--- A list of all selected tags on the screen.
-- @property selected_tags
-- @param table
-- @treturn table A table with all selected tags.
-- @see tag.selected
-- @see client.to_selected_tags

function screen.object.get_selected_tags(s)
    local tags = screen.object.get_tags(s, true)

    local vtags = {}
    for _, t in pairs(tags) do
        if t.selected then
            vtags[#vtags + 1] = t
        end
    end
    return vtags
end

--- The first selected tag.
-- @property selected_tag
-- @param table
-- @treturn ?tag The first selected tag or nil.
-- @see tag.selected
-- @see selected_tags

function screen.object.get_selected_tag(s)
    return screen.object.get_selected_tags(s)[1]
end


--- When the tag history changed.
-- @signal tag::history::update

-- Extend the luaobject
object.properties(capi.screen, {
    getter_class = screen.object,
    setter_class = screen.object,
    auto_emit    = true,
})

return screen

-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80