Repository navigation
Expand file tree
/
Copy pathtag.lua.in
More file actions
617 lines (541 loc) · 17.9 KB
/
Copy pathtag.lua.in
File metadata and controls
617 lines (541 loc) · 17.9 KB
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
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
---------------------------------------------------------------------------
-- @author Julien Danjou <julien@danjou.info>
-- @copyright 2008 Julien Danjou
-- @release @AWESOME_VERSION@
---------------------------------------------------------------------------
-- Grab environment we need
local util = require("awful.util")
local tostring = tostring
local pairs = pairs
local ipairs = ipairs
local table = table
local setmetatable = setmetatable
local capi =
{
tag = tag,
screen = screen,
mouse = mouse,
client = client,
root = root
}
--- Useful functions for tag manipulation.
-- awful.tag
local tag = { mt = {} }
-- Private data
local data = {}
data.history = {}
data.tags = setmetatable({}, { __mode = 'k' })
-- History functions
tag.history = {}
tag.history.limit = 20
--- Move a tag to an absolute position in the screen[]:tags() table.
-- @param new_index Integer absolute position in the table to insert.
-- @param target_tag The tag that should be moved. If null, the currently
-- selected tag is used.
function tag.move(new_index, target_tag)
local target_tag = target_tag or tag.selected()
local scr = tag.getscreen(target_tag)
local tmp_tags = tag.gettags(scr)
if (not new_index) or (new_index < 1) or (new_index > #tmp_tags) then
return
end
for i, t in ipairs(tmp_tags) do
if t == target_tag then
table.remove(tmp_tags, i)
break
end
end
table.insert(tmp_tags, new_index, target_tag)
for i, tmp_tag in ipairs(tmp_tags) do
tag.setproperty(tmp_tag, "index", i)
tag.setscreen(tmp_tag, scr)
end
end
--- Add a tag.
-- @param name The tag name, a string
-- @param props The tags properties, a table
-- @return The created tag
function tag.add(name, props)
local properties = props or {}
local newtag = capi.tag{ name = name, activated = true }
properties.screen = properties.screen or capi.mouse.screen
for k, v in pairs(properties) do
tag.setproperty(newtag, k, v)
end
return newtag
end
--- Create a set of tags and attach it to a screen.
-- @param names The tag name, in a table
-- @param screen The tag screen, or 1 if not set.
-- @param layout The layout or layout table to set for this tags by default.
-- @return A table with all created tags.
function tag.new(names, screen, layout)
local screen = screen or 1
local tags = {}
for id, name in ipairs(names) do
table.insert(tags, id, tag.add(name, {screen = screen,
layout = (layout and layout[id]) or
layout}))
-- Select the first tag.
if id == 1 then
tags[id].selected = true
end
end
return tags
end
--- Find a suitable fallback tag.
-- @param screen The screen number to look for a tag on. [mouse.screen]
-- @param invalids A table of tags we consider unacceptable. [selectedlist(scr)]
function tag.find_fallback(screen, invalids)
local scr = screen or capi.mouse.screen
local t = invalids or tag.selectedlist(scr)
for _, v in pairs(tag.gettags(scr)) do
if not util.table.hasitem(t, v) then return v end
end
end
--- Delete a tag.
-- @param target_tag Optional tag object to delete. [selected()]
-- @param fallback_tag Tag to assign stickied tags to. [~selected()]
-- @return Returns true if the tag is successfully deleted, nil otherwise.
-- If there are no clients exclusively on this tag then delete it. Any
-- stickied clients are assigned to the optional 'fallback_tag'.
-- If after deleting the tag there is no selected tag, try and restore from
-- history or select the first tag on the screen.
function tag.delete(target_tag, fallback_tag)
-- abort if no tag is passed or currently selected
local target_tag = target_tag or tag.selected()
if target_tag == nil then return end
local target_scr = tag.getscreen(target_tag)
local ntags = #tag.gettags(target_scr)
-- We can't use the target tag as a fallback.
local fallback_tag = fallback_tag
if fallback_tag == target_tag then return end
-- No fallback_tag provided, try and get one.
if fallback_tag == nil then
fallback_tag = tag.find_fallback(target_scr, {target_tag})
end
-- Abort if we would have un-tagged clients.
local clients = target_tag:clients()
if ( #clients > 0 and ntags <= 1 ) or fallback_tag == nil then return end
-- Move the clients we can off of this tag.
for _, c in pairs(clients) do
-- If a client has only this tag, or stickied clients with
-- nowhere to go, abort.
if (not c.sticky and #c:tags() == 1) or
(c.sticky and fallback_tag == nil) then
return
else
c:tags({fallback_tag})
end
end
-- delete the tag
data.tags[target_tag].screen = nil
target_tag.activated = false
-- If no tags are visible, try and view one.
if tag.selected(target_scr) == nil and ntags > 0 then
tag.history.restore(nil, 1)
if tag.selected(target_scr) == nil then
tag.gettags(target_scr)[1].selected = true
end
end
return true
end
--- Update the tag history.
-- @param obj Screen object.
function tag.history.update(obj)
local s = obj.index
local curtags = tag.selectedlist(s)
-- create history table
if not data.history[s] then
data.history[s] = {}
else
if data.history[s].current then
-- Check that the list is not identical
local identical = true
for idx, _tag in ipairs(data.history[s].current) do
if curtags[idx] ~= _tag then
identical = false
break
end
end
-- Do not update history the table are identical
if identical then return end
end
-- Limit history
if #data.history[s] >= tag.history.limit then
for i = tag.history.limit, #data.history[s] do
data.history[s][i] = nil
end
end
end
-- store previously selected tags in the history table
table.insert(data.history[s], 1, data.history[s].current)
data.history[s].previous = data.history[s][1]
-- store currently selected tags
data.history[s].current = setmetatable(curtags, { __mode = 'v' })
end
--- Revert tag history.
-- @param screen The screen number.
-- @param idx Index in history. Defaults to "previous" which is a special index
-- toggling between last two selected sets of tags. Number (eg 1) will go back
-- to the given index in history.
function tag.history.restore(screen, idx)
local s = screen or capi.mouse.screen
local i = idx or "previous"
local sel = tag.selectedlist(s)
-- do nothing if history empty
if not data.history[s] or not data.history[s][i] then return end
-- if all tags been deleted, try next entry
if #data.history[s][i] == 0 then
if i == "previous" then i = 0 end
tag.history.restore(s, i + 1)
return
end
-- deselect all tags
tag.viewnone(s)
-- select tags from the history entry
for _, t in ipairs(data.history[s][i]) do
t.selected = true
end
-- update currently selected tags table
data.history[s].current = data.history[s][i]
-- store previously selected tags
data.history[s].previous = setmetatable(sel, { __mode = 'v' })
-- remove the reverted history entry
if i ~= "previous" then table.remove(data.history[s], i) end
end
--- Get a list of all tags on a screen
-- @param s Screen number
-- @return A table with all available tags
function tag.gettags(s)
local tags = {}
for i, t in ipairs(root.tags()) do
if tag.getscreen(t) == s then
table.insert(tags, t)
end
end
local without_index = 0
for _, t in ipairs(tags) do
if not tag.getproperty(t, "index") then
without_index = without_index + 1
end
end
if without_index > 0 then
for _, t in ipairs(tags) do
if not tag.getproperty(t, "index") then
tag.setproperty(t, "index", (#tags - without_index + 1))
without_index = without_index - 1
end
end
end
table.sort(tags, function(a, b) return tag.getproperty(a, "index") < tag.getproperty(b, "index") end)
return tags
end
--- Set a tag's screen
-- @param t tag object
-- @param s Screen number
function tag.setscreen(t, s)
local s = s or capi.mouse.screen
-- Keeping the old index make very little sense when changing screen
tag.setproperty(t, "index", nil)
local old_screen = tag.getproperty(t,"screen")
-- Change the screen
tag.setproperty(t, "screen", s)
-- Make sure the client's screen matches its tags
for k,c in ipairs(t:clients()) do
c.screen = s --Move all clients
c:tags({t})
end
tag.history.restore(old_screen,1)
end
--- Get a tag's screen
-- @param t tag object
-- @return Screen number
function tag.getscreen(t)
return tag.getproperty(t, "screen")
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 = tag.gettags(screen)
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.
-- @param t The tag to modify, if null tag.selected() is used.
function tag.setmwfact(mwfact, t)
local t = t or tag.selected()
if mwfact >= 0 and mwfact <= 1 then
tag.setproperty(t, "mwfact", mwfact)
end
end
--- Increase master width factor.
-- @param add Value to add to master width factor.
-- @param t The tag to modify, if null tag.selected() is used.
function tag.incmwfact(add, t)
tag.setmwfact(tag.getmwfact(t) + add, t)
end
--- Get master width factor.
-- @param t Optional tag.
function tag.getmwfact(t)
local t = t or tag.selected()
return tag.getproperty(t, "mwfact") or 0.5
end
--- Set the number of master windows.
-- @param nmaster The number of master windows.
-- @param t Optional tag.
function tag.setnmaster(nmaster, t)
local t = t or tag.selected()
if nmaster >= 0 then
tag.setproperty(t, "nmaster", nmaster)
end
end
--- Get the number of master windows.
-- @param t Optional tag.
function tag.getnmaster(t)
local t = t or tag.selected()
return tag.getproperty(t, "nmaster") or 1
end
--- Increase the number of master windows.
-- @param add Value to add to number of master windows.
-- @param t The tag to modify, if null tag.selected() is used.
function tag.incnmaster(add, t)
tag.setnmaster(tag.getnmaster(t) + add, t)
end
--- Set the tag icon
-- @param icon the icon to set, either path or image object
-- @param _tag the tag
function tag.seticon(icon, _tag)
local _tag = _tag or tag.selected()
tag.setproperty(_tag, "icon", icon)
end
--- Get the tag icon
-- @param _tag the tag
function tag.geticon(_tag)
local _tag = _tag or tag.selected()
return tag.getproperty(_tag, "icon")
end
--- Set number of column windows.
-- @param ncol The number of column.
-- @param t The tag to modify, if null tag.selected() is used.
function tag.setncol(ncol, t)
local t = t or tag.selected()
if ncol >= 1 then
tag.setproperty(t, "ncol", ncol)
end
end
--- Get number of column windows.
-- @param t Optional tag.
function tag.getncol(t)
local t = t or tag.selected()
return tag.getproperty(t, "ncol") or 1
end
--- Increase number of column windows.
-- @param add Value to add to number of column windows.
-- @param t The tag to modify, if null tag.selected() is used.
function tag.incncol(add, t)
tag.setncol(tag.getncol(t) + add, t)
end
--- View no tag.
-- @param Optional screen number.
function tag.viewnone(screen)
local tags = tag.gettags(screen or capi.mouse.screen)
for i, t in pairs(tags) do
t.selected = false
end
end
--- View a tag by its taglist index.
-- @param i The relative index to see.
-- @param screen Optional screen number.
function tag.viewidx(i, screen)
local screen = screen or capi.mouse.screen
local tags = tag.gettags(screen)
local showntags = {}
for k, t in ipairs(tags) do
if not tag.getproperty(t, "hide") then
table.insert(showntags, t)
end
end
local sel = tag.selected(screen)
tag.viewnone(screen)
for k, t in ipairs(showntags) do
if t == sel then
showntags[util.cycle(#showntags, k + i)].selected = true
end
end
capi.screen[screen]:emit_signal("tag::history::update")
end
--- Get a tag's index in the gettags() table.
-- @param query_tag The tag object to find. [selected()]
-- @return The index of the tag, nil if the tag is not found.
function tag.getidx(query_tag)
local query_tag = query_tag or tag.selected()
if query_tag == nil then return end
for i, t in ipairs(tag.gettags(tag.getscreen(query_tag))) do
if t == query_tag then
return i
end
end
end
--- View next tag. This is the same as tag.viewidx(1).
-- @param screen The screen number.
function tag.viewnext(screen)
return tag.viewidx(1, screen)
end
--- View previous tag. This is the same a tag.viewidx(-1).
-- @param screen The screen number.
function tag.viewprev(screen)
return tag.viewidx(-1, screen)
end
--- View only a tag.
-- @param t The tag object.
function tag.viewonly(t)
local tags = tag.gettags(tag.getscreen(t))
-- First, untag everyone except the viewed tag.
for _, _tag in pairs(tags) do
if _tag ~= t then
_tag.selected = false
end
end
-- Then, set this one to selected.
-- We need to do that in 2 operations so we avoid flickering and several tag
-- selected at the same time.
t.selected = true
capi.screen[tag.getscreen(t)]:emit_signal("tag::history::update")
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)
local screen = screen or capi.mouse.screen
local screen_tags = tag.gettags(screen)
for _, _tag in ipairs(screen_tags) do
if not util.table.hasitem(tags, _tag) then
_tag.selected = false
end
end
for _, _tag in ipairs(tags) do
_tag.selected = true
end
capi.screen[screen]:emit_signal("tag::history::update")
end
--- Toggle selection of a tag
-- @param tag Tag to be toggled
function tag.viewtoggle(t)
t.selected = not t.selected
capi.screen[tag.getscreen(t)]:emit_signal("tag::history::update")
end
--- Get tag data table.
-- @param tag The Tag.
-- @return The data table.
function tag.getdata(_tag)
return data.tags[_tag]
end
--- Get a tag property.
-- @param _tag The tag.
-- @param prop The property name.
-- @return The property.
function tag.getproperty(_tag, prop)
if data.tags[_tag] then
return data.tags[_tag][prop]
end
end
--- Set a tag property.
-- This properties are internal to awful. Some are used to draw taglist, or to
-- handle layout, etc.
-- @param _tag The tag.
-- @param prop The property name.
-- @param value The value.
function tag.setproperty(_tag, prop, value)
if not data.tags[_tag] then
data.tags[_tag] = {}
end
data.tags[_tag][prop] = value
_tag:emit_signal("property::" .. prop)
end
--- Tag a client with the set of current tags.
-- @param c The client to tag.
function tag.withcurrent(c)
local tags = {}
for k, t in ipairs(c:tags()) do
if tag.getscreen(t) == c.screen then
table.insert(tags, t)
end
end
if #tags == 0 then
tags = tag.selectedlist(c.screen)
end
if #tags == 0 then
tags = tag.gettags(c.screen)
end
if #tags ~= 0 then
c:tags(tags)
end
end
local function attached_connect_signal_screen(screen, sig, func)
capi.tag.connect_signal(sig, function(_tag, ...)
if tag.getscreen(_tag) == screen then
func(_tag)
end
end)
end
--- Add a signal to all attached tag and all tag that will be attached in the
-- future. When a tag is detach from the screen, its signal is removed.
-- @param screen The screen concerned, or all if nil.
function tag.attached_connect_signal(screen, ...)
if screen then
attached_connect_signal_screen(screen, ...)
else
capi.tag.connect_signal(...)
end
end
-- Register standards signals
capi.client.connect_signal("manage", function(c, startup)
-- If we are not managing this application at startup,
-- move it to the screen where the mouse is.
-- We only do it for "normal" windows (i.e. no dock, etc).
if not startup and c.type ~= "desktop" and c.type ~= "dock" then
if c.transient_for then
c.screen = c.transient_for.screen
if not c.sticky then
c:tags(c.transient_for:tags())
end
else
c.screen = capi.mouse.screen
end
end
c:connect_signal("property::screen", tag.withcurrent)
end)
capi.client.connect_signal("manage", tag.withcurrent)
capi.tag.connect_signal("request::select", tag.viewonly)
capi.tag.add_signal("property::hide")
capi.tag.add_signal("property::icon")
capi.tag.add_signal("property::layout")
capi.tag.add_signal("property::mwfact")
capi.tag.add_signal("property::ncol")
capi.tag.add_signal("property::nmaster")
capi.tag.add_signal("property::windowfact")
capi.tag.add_signal("property::screen")
capi.tag.add_signal("property::index")
for s = 1, capi.screen.count() do
capi.screen[s]:add_signal("tag::history::update")
capi.screen[s]:connect_signal("tag::history::update", tag.history.update)
end
function tag.mt:__call(...)
return tag.new(...)
end
return setmetatable(tag, tag.mt)
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80