1 /*
2 * tag.c - tag management
3 *
4 * Copyright © 2007-2008 Julien Danjou <julien@danjou.info>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 *
20 */
21
22 /** awesome tag API
23 *
24 * Furthermore to the classes described here, one can also use signals as
25 * described in @{signals}.
26 *
27 * ![Client geometry](../images/tag_props.svg)
28 *
29 * **Creating tags**:
30 *
31 * The default config initializes tags like this:
32 *
33 * awful.tag(
34 * { "1", "2", "3", "4", "5", "6", "7", "8", "9" },
35 * s,
36 * awful.layout.layouts[1]
37 * )
38 *
39 * If you wish to have tags with different properties, then `awful.tag.add` is
40 * a better choice:
41 *
42 * awful.tag.add("First tag", {
43 * icon = "/path/to/icon1.png",
44 * layout = awful.layout.suit.tile,
45 * master_fill_policy = "master_width_factor",
46 * gap_single_client = true,
47 * gap = 15,
48 * screen = s,
49 * selected = true,
50 * })
51 *
52 * awful.tag.add("Second tag", {
53 * icon = "/path/to/icon2.png",
54 * layout = awful.layout.suit.max,
55 * screen = s,
56 * })
57 *
58 * Note: the example above sets "First tag" to be selected explicitly,
59 * because otherwise you will find yourself without any selected tag.
60 *
61 * **Accessing tags**:
62 *
63 * To access the "current tags", use
64 *
65 * local tags = awful.screen.focused().selected_tags
66 *
67 * See: `awful.screen.focused`
68 *
69 * See: `screen.selected_tags`
70 *
71 * To ignore the corner case where multiple tags are selected:
72 *
73 * local t = awful.screen.focused().selected_tag
74 *
75 * See: `screen.selected_tag`
76 *
77 * To get all tags for the focused screen:
78 *
79 * local tags = awful.screen.focused().tags
80 *
81 * See: `screen.tags`
82 *
83 * To get all tags:
84 *
85 * local tags = root.tags()
86 *
87 * To get the current tag of the focused client:
88 *
89 * local t = client.focus and client.focus.first_tag or nil
90 *
91 * See: `client.focus`
92 * See: `client.first_tag`
93 *
94 * To get a tag from its name:
95 *
96 * local t = awful.tag.find_by_name(awful.screen.focused(), "name")
97 *
98 * **Common shortcuts**:
99 *
100 * Here is a few useful shortcuts not part of the default `rc.lua`. Add these
101 * functions above `-- {{{ Key bindings`:
102 *
103 * Delete the current tag
104 *
105 * local function delete_tag()
106 * local t = awful.screen.focused().selected_tag
107 * if not t then return end
108 * t:delete()
109 * end
110 *
111 * Create a new tag at the end of the list
112 *
113 * local function add_tag()
114 * awful.tag.add("NewTag", {
115 * screen = awful.screen.focused(),
116 * layout = awful.layout.suit.floating }):view_only()
117 * end
118 *
119 * Rename the current tag
120 *
121 * local function rename_tag()
122 * awful.prompt.run {
123 * prompt = "New tag name: ",
124 * textbox = awful.screen.focused().mypromptbox.widget,
125 * exe_callback = function(new_name)
126 * if not new_name or #new_name == 0 then return end
127 *
128 * local t = awful.screen.focused().selected_tag
129 * if t then
130 * t.name = new_name
131 * end
132 * end
133 * }
134 * end
135 *
136 * Move the focused client to a new tag
137 *
138 * local function move_to_new_tag()
139 * local c = client.focus
140 * if not c then return end
141 *
142 * local t = awful.tag.add(c.class,{screen= c.screen })
143 * c:tags({t})
144 * t:view_only()
145 * end
146 *
147 * Copy the current tag at the end of the list
148 *
149 * local function copy_tag()
150 * local t = awful.screen.focused().selected_tag
151 * if not t then return end
152 *
153 * local clients = t:clients()
154 * local t2 = awful.tag.add(t.name, awful.tag.getdata(t))
155 * t2:clients(clients)
156 * t2:view_only()
157 * end
158 *
159 * And, in the `globalkeys` table:
160 *
161 * awful.key({ modkey, }, "a", add_tag,
162 * {description = "add a tag", group = "tag"}),
163 * awful.key({ modkey, "Shift" }, "a", delete_tag,
164 * {description = "delete the current tag", group = "tag"}),
165 * awful.key({ modkey, "Control" }, "a", move_to_new_tag,
166 * {description = "add a tag with the focused client", group = "tag"}),
167 * awful.key({ modkey, "Mod1" }, "a", copy_tag,
168 * {description = "create a copy of the current tag", group = "tag"}),
169 * awful.key({ modkey, "Shift" }, "r", rename_tag,
170 * {description = "rename the current tag", group = "tag"}),
171 *
172 * See the
173 * <a href="../documentation/05-awesomerc.md.html#global_keybindings">
174 * global keybindings
175 * </a> for more information about the keybindings.
176 *
177 * Some signal names are starting with a dot. These dots are artefacts from
178 * the documentation generation, you get the real signal name by
179 * removing the starting dot.
180 *
181 * @author Julien Danjou <julien@danjou.info>
182 * @copyright 2008-2009 Julien Danjou
183 * @classmod tag
184 */
185
186 #include "tag.h"
187 #include "screen.h"
188 #include "banning.h"
189 #include "client.h"
190 #include "ewmh.h"
191 #include "luaa.h"
192
193 lua_class_t tag_class;
194
195 /**
196 * @signal request::select
197 */
198
199 /** When a client gets tagged with this tag.
200 * @signal tagged
201 * @client c The tagged client.
202 */
203
204 /** When a client gets untagged with this tag.
205 * @signal untagged
206 * @client c The untagged client.
207 */
208
209 /**
210 * Tag name.
211 *
212 * **Signal:**
213 *
214 * * *property::name*
215 *
216 * @property name
217 * @param string
218 */
219
220 /**
221 * True if the tag is selected to be viewed.
222 *
223 * **Signal:**
224 *
225 * * *property::selected*
226 *
227 * @property selected
228 * @param boolean
229 */
230
231 /**
232 * True if the tag is active and can be used.
233 *
234 * **Signal:**
235 *
236 * * *property::activated*
237 *
238 * @property activated
239 * @param boolean
240 */
241
242 /** Get the number of instances.
243 *
244 * @return The number of tag objects alive.
245 * @function instances
246 */
247
248 /* Set a __index metamethod for all tag instances.
249 * @tparam function cb The meta-method
250 * @function set_index_miss_handler
251 */
252
253 /* Set a __newindex metamethod for all tag instances.
254 * @tparam function cb The meta-method
255 * @function set_newindex_miss_handler
256 */
257
258
259 void
tag_unref_simplified(tag_t ** tag)260 tag_unref_simplified(tag_t **tag)
261 {
262 lua_State *L = globalconf_get_lua_State();
263 luaA_object_unref(L, *tag);
264 }
265
266 static void
tag_wipe(tag_t * tag)267 tag_wipe(tag_t *tag)
268 {
269 client_array_wipe(&tag->clients);
270 p_delete(&tag->name);
271 }
272
OBJECT_EXPORT_PROPERTY(tag,tag_t,selected)273 OBJECT_EXPORT_PROPERTY(tag, tag_t, selected)
274 OBJECT_EXPORT_PROPERTY(tag, tag_t, name)
275
276 /** View or unview a tag.
277 * \param L The Lua VM state.
278 * \param udx The index of the tag on the stack.
279 * \param view Set selected or not.
280 */
281 static void
282 tag_view(lua_State *L, int udx, bool view)
283 {
284 tag_t *tag = luaA_checkudata(L, udx, &tag_class);
285 if(tag->selected != view)
286 {
287 tag->selected = view;
288 banning_need_update();
289 foreach(screen, globalconf.screens)
290 screen_update_workarea(*screen);
291
292 luaA_object_emit_signal(L, udx, "property::selected", 0);
293 }
294 }
295
296 static void
tag_client_emit_signal(tag_t * t,client_t * c,const char * signame)297 tag_client_emit_signal(tag_t *t, client_t *c, const char *signame)
298 {
299 lua_State *L = globalconf_get_lua_State();
300 luaA_object_push(L, c);
301 luaA_object_push(L, t);
302 /* emit signal on client, with new tag as argument */
303 luaA_object_emit_signal(L, -2, signame, 1);
304 /* re push tag */
305 luaA_object_push(L, t);
306 /* move tag before client */
307 lua_insert(L, -2);
308 luaA_object_emit_signal(L, -2, signame, 1);
309 /* Remove tag */
310 lua_pop(L, 1);
311 }
312
313 /** Tag a client with the tag on top of the stack.
314 * \param L The Lua VM state.
315 * \param c the client to tag
316 */
317 void
tag_client(lua_State * L,client_t * c)318 tag_client(lua_State *L, client_t *c)
319 {
320 tag_t *t = luaA_object_ref_class(L, -1, &tag_class);
321
322 /* don't tag twice */
323 if(is_client_tagged(c, t))
324 {
325 luaA_object_unref(L, t);
326 return;
327 }
328
329 client_array_append(&t->clients, c);
330 ewmh_client_update_desktop(c);
331 banning_need_update();
332 screen_update_workarea(c->screen);
333
334 tag_client_emit_signal(t, c, "tagged");
335 }
336
337 /** Untag a client with specified tag.
338 * \param c the client to tag
339 * \param t the tag to tag the client with
340 */
341 void
untag_client(client_t * c,tag_t * t)342 untag_client(client_t *c, tag_t *t)
343 {
344 for(int i = 0; i < t->clients.len; i++)
345 if(t->clients.tab[i] == c)
346 {
347 lua_State *L = globalconf_get_lua_State();
348 client_array_take(&t->clients, i);
349 banning_need_update();
350 ewmh_client_update_desktop(c);
351 screen_update_workarea(c->screen);
352 tag_client_emit_signal(t, c, "untagged");
353 luaA_object_unref(L, t);
354 return;
355 }
356 }
357
358 /** Check if a client is tagged with the specified tag.
359 * \param c the client
360 * \param t the tag
361 * \return true if the client is tagged with the tag, false otherwise.
362 */
363 bool
is_client_tagged(client_t * c,tag_t * t)364 is_client_tagged(client_t *c, tag_t *t)
365 {
366 for(int i = 0; i < t->clients.len; i++)
367 if(t->clients.tab[i] == c)
368 return true;
369
370 return false;
371 }
372
373 /** Get the index of the tag with focused client or first selected
374 * \return Its index
375 */
376 int
tags_get_current_or_first_selected_index(void)377 tags_get_current_or_first_selected_index(void)
378 {
379 /* Consider "current desktop" a tag, that has focused window,
380 * basically a tag user actively interacts with.
381 * If no focused windows are present, fallback to first selected.
382 */
383 if(globalconf.focus.client)
384 {
385 foreach(tag, globalconf.tags)
386 {
387 if((*tag)->selected && is_client_tagged(globalconf.focus.client, *tag))
388 return tag_array_indexof(&globalconf.tags, tag);
389 }
390 }
391 foreach(tag, globalconf.tags)
392 {
393 if((*tag)->selected)
394 return tag_array_indexof(&globalconf.tags, tag);
395 }
396 return 0;
397 }
398
399 /** Create a new tag.
400 * \param L The Lua VM state.
401 * \luastack
402 * \lparam A name.
403 * \lreturn A new tag object.
404 */
405 static int
luaA_tag_new(lua_State * L)406 luaA_tag_new(lua_State *L)
407 {
408 return luaA_class_new(L, &tag_class);
409 }
410
411 /** Get or set the clients attached to this tag.
412 *
413 * @param clients_table None or a table of clients to set as being tagged with
414 * this tag.
415 * @return A table with the clients attached to this tags.
416 * @function clients
417 */
418 static int
luaA_tag_clients(lua_State * L)419 luaA_tag_clients(lua_State *L)
420 {
421 tag_t *tag = luaA_checkudata(L, 1, &tag_class);
422 client_array_t *clients = &tag->clients;
423 int i;
424
425 if(lua_gettop(L) == 2)
426 {
427 luaA_checktable(L, 2);
428 foreach(c, tag->clients)
429 {
430 /* Only untag if we aren't going to add this tag again */
431 bool found = false;
432 lua_pushnil(L);
433 while(lua_next(L, 2))
434 {
435 client_t *tc = luaA_checkudata(L, -1, &client_class);
436 /* Pop the value from lua_next */
437 lua_pop(L, 1);
438 if (tc != *c)
439 continue;
440
441 /* Pop the key from lua_next */
442 lua_pop(L, 1);
443 found = true;
444 break;
445 }
446 if(!found)
447 untag_client(*c, tag);
448 }
449 lua_pushnil(L);
450 while(lua_next(L, 2))
451 {
452 client_t *c = luaA_checkudata(L, -1, &client_class);
453 /* push tag on top of the stack */
454 lua_pushvalue(L, 1);
455 tag_client(L, c);
456 lua_pop(L, 1);
457 }
458 }
459
460 lua_createtable(L, clients->len, 0);
461 for(i = 0; i < clients->len; i++)
462 {
463 luaA_object_push(L, clients->tab[i]);
464 lua_rawseti(L, -2, i + 1);
465 }
466
467 return 1;
468 }
469
LUA_OBJECT_EXPORT_PROPERTY(tag,tag_t,name,lua_pushstring)470 LUA_OBJECT_EXPORT_PROPERTY(tag, tag_t, name, lua_pushstring)
471 LUA_OBJECT_EXPORT_PROPERTY(tag, tag_t, selected, lua_pushboolean)
472 LUA_OBJECT_EXPORT_PROPERTY(tag, tag_t, activated, lua_pushboolean)
473
474 /** Set the tag name.
475 * \param L The Lua VM state.
476 * \param tag The tag to name.
477 * \return The number of elements pushed on stack.
478 */
479 static int
480 luaA_tag_set_name(lua_State *L, tag_t *tag)
481 {
482 const char *buf = luaL_checkstring(L, -1);
483 p_delete(&tag->name);
484 tag->name = a_strdup(buf);
485 luaA_object_emit_signal(L, -3, "property::name", 0);
486 ewmh_update_net_desktop_names();
487 return 0;
488 }
489
490 /** Set the tag selection status.
491 * \param L The Lua VM state.
492 * \param tag The tag to set the selection status for.
493 * \return The number of elements pushed on stack.
494 */
495 static int
luaA_tag_set_selected(lua_State * L,tag_t * tag)496 luaA_tag_set_selected(lua_State *L, tag_t *tag)
497 {
498 tag_view(L, -3, luaA_checkboolean(L, -1));
499 return 0;
500 }
501
502 /** Set the tag activated status.
503 * \param L The Lua VM state.
504 * \param tag The tag to set the activated status for.
505 * \return The number of elements pushed on stack.
506 */
507 static int
luaA_tag_set_activated(lua_State * L,tag_t * tag)508 luaA_tag_set_activated(lua_State *L, tag_t *tag)
509 {
510 bool activated = luaA_checkboolean(L, -1);
511 if(activated == tag->activated)
512 return 0;
513
514 tag->activated = activated;
515 if(activated)
516 {
517 lua_pushvalue(L, -3);
518 tag_array_append(&globalconf.tags, luaA_object_ref_class(L, -1, &tag_class));
519 }
520 else
521 {
522 for (int i = 0; i < globalconf.tags.len; i++)
523 if(globalconf.tags.tab[i] == tag)
524 {
525 tag_array_take(&globalconf.tags, i);
526 break;
527 }
528
529 if (tag->selected)
530 {
531 tag->selected = false;
532 luaA_object_emit_signal(L, -3, "property::selected", 0);
533 banning_need_update();
534 }
535 luaA_object_unref(L, tag);
536 }
537 ewmh_update_net_numbers_of_desktop();
538 ewmh_update_net_desktop_names();
539
540 luaA_object_emit_signal(L, -3, "property::activated", 0);
541
542 return 0;
543 }
544
545 void
tag_class_setup(lua_State * L)546 tag_class_setup(lua_State *L)
547 {
548 static const struct luaL_Reg tag_methods[] =
549 {
550 LUA_CLASS_METHODS(tag)
551 { "__call", luaA_tag_new },
552 { NULL, NULL }
553 };
554
555 static const struct luaL_Reg tag_meta[] =
556 {
557 LUA_OBJECT_META(tag)
558 LUA_CLASS_META
559 { "clients", luaA_tag_clients },
560 { NULL, NULL },
561 };
562
563 luaA_class_setup(L, &tag_class, "tag", NULL,
564 (lua_class_allocator_t) tag_new,
565 (lua_class_collector_t) tag_wipe,
566 NULL,
567 luaA_class_index_miss_property, luaA_class_newindex_miss_property,
568 tag_methods, tag_meta);
569 luaA_class_add_property(&tag_class, "name",
570 (lua_class_propfunc_t) luaA_tag_set_name,
571 (lua_class_propfunc_t) luaA_tag_get_name,
572 (lua_class_propfunc_t) luaA_tag_set_name);
573 luaA_class_add_property(&tag_class, "selected",
574 (lua_class_propfunc_t) luaA_tag_set_selected,
575 (lua_class_propfunc_t) luaA_tag_get_selected,
576 (lua_class_propfunc_t) luaA_tag_set_selected);
577 luaA_class_add_property(&tag_class, "activated",
578 (lua_class_propfunc_t) luaA_tag_set_activated,
579 (lua_class_propfunc_t) luaA_tag_get_activated,
580 (lua_class_propfunc_t) luaA_tag_set_activated);
581 }
582
583 /* @DOC_cobject_COMMON@ */
584
585 // vim: filetype=c:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
586