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 &lt;julien@danjou.info&gt;
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