diff options
author | Thorsten Wißmann <edu@thorsten-wissmann.de> | 2013-03-04 01:56:26 +0100 |
---|---|---|
committer | Thorsten Wißmann <edu@thorsten-wissmann.de> | 2013-03-04 01:56:26 +0100 |
commit | 41a47e9bce207829c69da498f044df764cd12389 (patch) | |
tree | 6239c4ed7d9ad005add364fef9c5b06186a601a5 | |
parent | e0525f4cb1adda4a105e18ed9deb71dc7293c650 (diff) | |
parent | b88718a7584e6fc1e79a822379d7f1d3a2846ded (diff) |
Merge branch 'hsobject'
This adds the object-tree and its commands.
-rw-r--r-- | HACKING | 39 | ||||
-rw-r--r-- | NEWS | 2 | ||||
-rw-r--r-- | doc/herbstluftwm.txt | 211 | ||||
-rw-r--r-- | src/clientlist.c | 66 | ||||
-rw-r--r-- | src/clientlist.h | 3 | ||||
-rw-r--r-- | src/command.c | 152 | ||||
-rw-r--r-- | src/command.h | 7 | ||||
-rw-r--r-- | src/layout.c | 27 | ||||
-rw-r--r-- | src/layout.h | 3 | ||||
-rw-r--r-- | src/main.c | 8 | ||||
-rw-r--r-- | src/monitor.c | 40 | ||||
-rw-r--r-- | src/monitor.h | 5 | ||||
-rw-r--r-- | src/object.c | 722 | ||||
-rw-r--r-- | src/object.h | 119 | ||||
-rw-r--r-- | src/tag.c | 125 | ||||
-rw-r--r-- | src/tag.h | 3 | ||||
-rw-r--r-- | src/utils.c | 14 | ||||
-rw-r--r-- | src/utils.h | 5 |
18 files changed, 1518 insertions, 33 deletions
@@ -116,4 +116,41 @@ herbstclient is rather simple: stdout <----| |<---' +------+ -// vim: nowrap ft=asciidoc +Objects +------- +There is one tree of objects in herbstluftwm to provide an easy access for the +user to many option. The usage is similar to kobjects known from the Linux +kernel. The most important functions are: + + - bool hsobject_init(HSObject* obj): Initialize the given HSObject. + - void hsobject_free(HSObject* obj): De-initialize the given HSObject. + - void hsobject_link(HSObject* parent, HSObject* child, char* name): Register + a new child node at a parent using the specified name. If a child with that + name already exists, it will be replaced. + - void hsobject_unlink(HSObject* parent, HSObject* child): Removes all child + entries at the parent object. + - HSObject* hsobject_root(): Return the root node of the object tree. + +Each object has a certain set of attributes: + + struct HSAttribute { + enum { + HSATTR_TYPE_BOOL, + HSATTR_TYPE_INT, + HSATTR_TYPE_STRING, + //HSATTR_TYPE_WINID, /* TODO: Win-ID als datentyp? */ + } type; /* the datatype */ + char* name; /* name as it is displayed to the user */ + union { + bool b; + int i; + char* str; + } value; + bool read_only; /* tells if this is read only for the user */ + /** on_change is called after the user changes the value. If this + * function returns false, the old value will be restored */ + bool (*on_change)(HSObject* obj, struct HSAttribute* attr); + }; + + +// vim: nowrap ft=asciidoc tw=80 @@ -20,6 +20,8 @@ Changes: can be printed to stdout using the 'printid' flag to the rule command. Unrule command accepts ids, and can remove rules individually. * new command: list_rules + * allow true/false as arguments to commands accepting boolean values. This + affects the commands: floating, fullscreen, pseudotile and rule. Release 0.5.1 on 2013-01-05 --------------------------- diff --git a/doc/herbstluftwm.txt b/doc/herbstluftwm.txt index 78cc5fa4..52ef755a 100644 --- a/doc/herbstluftwm.txt +++ b/doc/herbstluftwm.txt @@ -684,6 +684,58 @@ pseudotile [*on*|*off*|*toggle*]:: client is to ensure, that it fits into its tile. If no argument is given, pseudotile mode is toggled. +object_tree ['PATH']:: + Prints the tree of objects. If the object path 'PATH' is given, only the + subtree starting at 'PATH' is printed. See the <<OBJECTS,*OBJECTS section*>> + for more details. + +attr ['PATH' ['NEWVALUE']:: + Prints the children and attributes of the given object addressed by 'PATH'. + If 'PATH' is an attribute, then print the attribute value. If 'NEWVALUE' is + given, assign 'NEWVALUE' to the attribute given by 'PATH'. See the + <<OBJECTS,*OBJECTS section*>> for more details. + +get_attribute 'ATTRIBUTE':: + Print the value of the specified 'ATTRIBUTE' as described in the + <<OBJECTS,*OBJECTS section*>>. + +set_attribute 'ATTRIBUTE' 'NEWVALUE':: + Assign 'NEWVALUE' to the specified 'ATTRIBUTE' as described in the + <<OBJECTS,*OBJECTS section*>>. + +substitute 'IDENTIFIER' 'ATTRIBUTE' 'COMMAND' ['ARGS' ...]:: + Replaces all exact occurrences of 'IDENTIFIER' in 'COMMAND' and its 'ARGS' + by the value of the 'ATTRIBUTE'. Note that the 'COMMAND' also is replaced by + the attribute value if it equals 'IDENTIFIER'. The replaced command with its + arguments then is executed. Examples: + + * +substitute MYTITLE clients.focus.title echo MYTITLE+ + + Prints the title of the currently focused window. + +compare 'ATTRIBUTE' 'OPERATOR' 'VALUE':: + Compares the value of 'ATTRIBUTE' with 'VALUE' using the comparation method + 'OPERATOR'. If the comparation succeeds, it returns 0, else 1. The operators + are: + - *=*: 'ATTRIBUTEs' value equals 'VALUE' + - *!=*: 'ATTRIBUTEs' value does not equal 'VALUE' + - *le*: 'ATTRIBUTEs' value \<= 'VALUE' + - *lt*: 'ATTRIBUTEs' value < 'VALUE' + - *ge*: 'ATTRIBUTEs' value >= 'VALUE' + - *gt*: 'ATTRIBUTEs' value > 'VALUE' + + :: + The 'OPERATORs' *le*,*lt*,*ge*,*gt* can only be used if 'ATTRIBUTE' is of + the type integer or unsigned integer. Note that the first parameter must + always be an attribute and the second a constant value. If you want to + compare to attributes, use the substitute command: ++ +---- +substitute FC tags.focus.frame_count \ + compare tags.focus.client_count gt FC +---- ++ +It returns success if there are more clients on the focused tag than frames. + getenv 'NAME':: Gets the value of the environment variable 'NAME'. @@ -1025,6 +1077,165 @@ Examples: + Sets focus to new dialogs which set their +_NET_WM_WINDOW_TYPE+ correctly. +[[OBJECTS]] +OBJECTS +------- + +WARNING: The object tree is not stable yet, i.e. its interface may change until +the next stable release. So check this documentation again after upgrading the +next time. + +The object tree is a collection of objects with attributes similar to +/sys+ +known from the Linux Kernel. Many entities (like tags, monitors, clients, ...) +have objects to access their attributes directly. The tree is printed by the ++object_tree+ command and looks more or less as follows: +---- +$ herbstclient object_tree +╾─┐ + ├─┐ tags + │ ├─┐ by-name + │ │ ├─╼ 1 + │ │ ... + │ │ └─╼ 9 + │ └─╼ focus + ├─┐ clients + │ ├─╼ 0x1400022 + │ └─╼ focus + └─┐ monitors + ├─╼ by-name + └─╼ focus +---- +To print a subtree starting at a certain object, give the 'PATH' of the object +to +object_tree+. The object 'PATH' is the path using the separator +.+ (dot), +e.g. +tags.by-name+: + +---- +$ herbstclient object_tree tags.by-name. +╾─┐ tags.by-name. + ├─╼ 1 + ├─╼ 2 + ... + └─╼ 9 +---- + +To query all attributes and children of a object, pass its 'PATH' to +attr+: + +---- +$ herbstclient attr tags. +2 children: + by-name. + focus. + +1 attributes: + .---- type + | .-- writeable + V V + u - count = 9 + +$ herbstclient attr tags.focus. +0 children. +6 attributes: + .---- type + | .-- writeable + V V + s w name = "1" + b w floating = false + i - frame_count = 2 + i - client_count = 1 + i - curframe_windex = 0 + i - curframe_wcount = 1 +---- + +This already gives an intuition of the output: +attr+ first lists the names of +the child objects and then all attributes, telling for each attribute: + + - its type + + * +s+ for string + * +i+ for integer + * +b+ for boolean + * +u+ for unsigned integer + + - if it is writeable by the user: +w+ if yes, +-+ else. + - the name of the attribute + - its current value (only quoted for strings) + +To get the unquoted value of a certain attribute, address the attribute using +the same syntax as for object paths and pass it to +attr+ or +get_attribute+: + +---- +$ herbstclient attr clients.focus.title +herbstluftwm.txt = (~/dev/c/herbstluftwm/doc) - VIM +$ herbstclient get_attribute clients.focus.title +herbstluftwm.txt = (~/dev/c/herbstluftwm/doc) - VIM +---- + +To change a writeable attribute value pass the new value to +attr+ or to ++set_attribute+: + +---- +$ herbstclient attr tags.focus.floating +false +$ herbstclient attr tags.focus.floating true +$ herbstclient attr tags.focus.floating +true +$ herbstclient set_attribute tags.focus.floating false +$ herbstclient attr tags.focus.floating +false +---- + +Just look around to get a feeling what is there. The detailed tree content is +listed as follows: + + * +tags+: subtree for tags. ++ +[format="csv",cols="m,"] +|=========================== + u - count , number of tags +|=========================== + ** +by-name+ + *** 'TAG': a object for each tag with the name 'TAG' + ++ +[format="csv",cols="m,"] +|=========================== + s w name , name of the tag + b w floating , if it is in floating mode + i - frame_count , number of frames + i - client_count , number of clients on this tag + i - curframe_windex , index of the focused client in the select frame + i - curframe_wcount , number of clients in the selected frame +|=========================== + + ** +focus+: the object of the focused tag + + * +clients+ + ** 'WINID': a object for each client with its 'WINID' + ++ +[format="csv",cols="m,"] +|=========================== + s - winid , its window id + s - title , its window title + b w fullscreen , + b w pseudotile , + b w ewmhrequests , if ewmh requests are permitted for this client + b w ewmhnotify , if the client is told about its state via ewmh + b w urgent , its urgent state +|=========================== + + ** +focus+: the object of the focused client, if any + + * +monitors+ + ** +by-name+ + *** 'NAME': a object for each named monitor + ++ +[format="csv",cols="m,"] +|=========================== + s - name , its name + b - lock_tag , +|=========================== + + ** +focus+: the object of the focused monitor + AUTOSTART FILE -------------- diff --git a/src/clientlist.c b/src/clientlist.c index 6c7ee966..3f05cd3f 100644 --- a/src/clientlist.c +++ b/src/clientlist.c @@ -14,6 +14,7 @@ #include "ewmh.h" #include "rules.h" #include "ipc-protocol.h" +#include "object.h" // system #include <glib.h> #include <assert.h> @@ -41,15 +42,20 @@ unsigned long g_window_border_normal_color; unsigned long g_window_border_urgent_color; unsigned long g_window_border_inner_color; -GHashTable* g_clients; // container of all clients +static GHashTable* g_clients; // container of all clients +static HSObject* g_client_object; // atoms from dwm.c // default atoms enum { WMProtocols, WMDelete, WMState, WMTakeFocus, WMLast }; static Atom g_wmatom[WMLast]; +static void client_set_urgent_force(HSClient* client, bool state); + static HSClient* create_client() { HSClient* hc = g_new0(HSClient, 1); + hsobject_init(&hc->object); + hc->window_str = NULL; hc->float_size.width = 100; hc->float_size.height = 100; hc->title = g_string_new(""); @@ -87,6 +93,7 @@ void clientlist_init() { g_wmatom[WMState] = XInternAtom(g_display, "WM_STATE", False); g_wmatom[WMTakeFocus] = XInternAtom(g_display, "WM_TAKE_FOCUS", False); // init actual client list + g_client_object = hsobject_create_and_link(hsobject_root(), "clients"); g_clients = g_hash_table_new_full(g_int_hash, g_int_equal, NULL, (GDestroyNotify)client_destroy); } @@ -114,6 +121,7 @@ void clientlist_destroy() { g_hash_table_foreach(g_clients, client_move_to_floatpos, NULL); g_hash_table_destroy(g_clients); + hsobject_unlink_and_destroy(hsobject_root(), g_client_object); } @@ -125,6 +133,25 @@ HSClient* get_client_from_window(Window window) { return (HSClient*) g_hash_table_lookup(g_clients, &window); } +#define CLIENT_UPDATE_ATTR(FUNC,MEMBER) do { \ + HSClient* client = container_of(attr->value.b, HSClient, MEMBER); \ + FUNC(client, client->MEMBER); \ + return NULL; \ + } \ + while (0); + +static GString* client_attr_fullscreen(HSAttribute* attr) { + CLIENT_UPDATE_ATTR(client_set_fullscreen, fullscreen); +} + +static GString* client_attr_pseudotile(HSAttribute* attr) { + CLIENT_UPDATE_ATTR(client_set_pseudotile, pseudotile); +} + +static GString* client_attr_urgent(HSAttribute* attr) { + CLIENT_UPDATE_ATTR(client_set_urgent_force, urgent); +} + HSClient* manage_client(Window win) { if (is_herbstluft_window(g_display, win)) { // ignore our own window @@ -170,6 +197,9 @@ HSClient* manage_client(Window win) { // actually manage it g_hash_table_insert(g_clients, &(client->window), client); + client->window_str = g_string_sized_new(10); + g_string_printf(client->window_str, "0x%lx", win); + hsobject_link(g_client_object, &client->object, client->window_str->str); // insert to layout if (!client->tag) { client->tag = m->tag; @@ -190,6 +220,18 @@ HSClient* manage_client(Window win) { frame_focus_window(client->tag->frame, win); } + HSAttribute attributes[] = { + ATTRIBUTE_STRING( "winid", client->window_str, ATTR_READ_ONLY), + ATTRIBUTE_STRING( "title", client->title, ATTR_READ_ONLY), + ATTRIBUTE_BOOL( "fullscreen", client->fullscreen, client_attr_fullscreen), + ATTRIBUTE_BOOL( "pseudotile", client->pseudotile, client_attr_pseudotile), + ATTRIBUTE_BOOL( "ewmhrequests", client->ewmhrequests, ATTR_ACCEPT_ALL), + ATTRIBUTE_BOOL( "ewmhnotify", client->ewmhnotify, ATTR_ACCEPT_ALL), + ATTRIBUTE_BOOL( "urgent", client->urgent, client_attr_urgent), + ATTRIBUTE_LAST, + }; + hsobject_set_attributes(&client->object, attributes); + ewmh_window_update_tag(client->window, client->tag); tag_set_flags_dirty(); client_set_fullscreen(client, changes.fullscreen); @@ -242,16 +284,21 @@ void unmanage_client(Window win) { // destroys a special client void client_destroy(HSClient* client) { + hsobject_unlink(g_client_object, &client->object); if (client->tag && client->slice) { stack_remove_slice(client->tag->stack, client->slice); } if (client->slice) { slice_destroy(client->slice); } - if (client) { + if (client->title) { /* free window title */ g_string_free(client->title, true); } + if (client->window_str) { + g_string_free(client->window_str, true); + } + hsobject_free(&client->object); g_free(client); } @@ -273,6 +320,7 @@ void window_unfocus_last() { if (lastfocus) { window_unfocus(lastfocus); } + hsobject_unlink_by_name(g_client_object, "focus"); // give focus to root window XSetInputFocus(g_display, g_root, RevertToPointerRoot, CurrentTime); if (lastfocus) { @@ -300,6 +348,7 @@ void window_focus(Window window) { * only emit the hook if the focus *really* changes */ // unfocus last one window_unfocus(lastfocus); + hsobject_link(g_client_object, &client->object, "focus"); ewmh_update_active_window(window); tag_update_each_focus_layer(); char* title = client ? client->title->str : "?"; @@ -546,7 +595,10 @@ void client_set_urgent(HSClient* client, bool state) { // nothing to do return; } + client_set_urgent_force(client, state); +} +void client_set_urgent_force(HSClient* client, bool state) { char winid_str[STRING_BUF_SIZE]; snprintf(winid_str, STRING_BUF_SIZE, "0x%lx", client->window); hook_emit_list("urgent", state ? "on" : "off", winid_str, NULL); @@ -635,11 +687,6 @@ HSClient* get_current_client() { } void client_set_fullscreen(HSClient* client, bool state) { - if (client->fullscreen == state) { - // nothing to do - return; - } - client->fullscreen = state; if (client->ewmhnotify) { client->ewmhfullscreen = state; @@ -694,8 +741,11 @@ int client_set_property_command(int argc, char** argv) { } // if found, then change it + bool old_value = *(properties[i].value); bool state = string_to_bool(action, *(properties[i].value)); - properties[i].func(client, state); + if (state != old_value) { + properties[i].func(client, state); + } return 0; } diff --git a/src/clientlist.h b/src/clientlist.h index 18e40bbe..60ad8bc4 100644 --- a/src/clientlist.h +++ b/src/clientlist.h @@ -14,11 +14,13 @@ #include <stdbool.h> #include "layout.h" +#include "object.h" struct HSSlice; typedef struct HSClient { Window window; + GString* window_str; // the window id as a string XRectangle last_size; int last_border_width; HSTag* tag; @@ -32,6 +34,7 @@ typedef struct HSClient { bool ewmhrequests; // accept ewmh-requests for this client bool ewmhnotify; // send ewmh-notifications for this client int pid; + HSObject object; struct HSSlice* slice; } HSClient; diff --git a/src/command.c b/src/command.c index 34a35d57..929713b6 100644 --- a/src/command.c +++ b/src/command.c @@ -12,6 +12,7 @@ #include "clientlist.h" #include "monitor.h" #include "rules.h" +#include "object.h" #include <glib.h> #include <string.h> @@ -30,7 +31,7 @@ static char* completion_directions[] = { "left", "right", "down", "up",NULL}; static char* completion_focus_args[] = { "-i", "-e", NULL }; static char* completion_unrule_flags[] = { "-F", "--all", NULL }; static char* completion_keyunbind_args[]= { "-F", "--all", NULL }; -static char* completion_flag_args[] = { "on", "off", "toggle", NULL }; +static char* completion_flag_args[] = { "on", "off", "true", "false", "toggle", NULL }; static char* completion_status[] = { "status", NULL }; static char* completion_special_winids[]= { "urgent", "", NULL }; static char* completion_use_index_args[]= { "--skip-visible", NULL }; @@ -46,7 +47,10 @@ static bool no_completion(int argc, char** argv, int pos) { static bool first_parameter_is_tag(int argc, char** argv, int pos); static bool first_parameter_is_flag(int argc, char** argv, int pos); -static bool keybind_parameter_expected(int argc, char** argv, int pos); +static bool first_parameter_is_writable_attribute(int argc, char** argv, int pos); +static bool parameter_expected_offset(int argc, char** argv, int pos, int offset); +static bool parameter_expected_offset_2(int argc, char** argv, int pos); +static bool parameter_expected_offset_3(int argc, char** argv, int pos); /* find out, if a command still expects a parameter at a certain index. * only if this returns true, than a completion will be searched. @@ -69,7 +73,7 @@ struct { { "list_rules", 1, no_completion }, { "lock", 1, no_completion }, { "unlock", 1, no_completion }, - { "keybind", 2, keybind_parameter_expected }, + { "keybind", 2, parameter_expected_offset_2 }, { "keyunbind", 2, no_completion }, { "mousebind", 3, no_completion }, { "mouseunbind", 1, no_completion }, @@ -128,6 +132,12 @@ struct { { "unrule", 2, no_completion }, { "fullscreen", 2, no_completion }, { "pseudotile", 2, no_completion }, + { "attr", 2, first_parameter_is_writable_attribute }, + { "attr", 3, no_completion }, + { "object_tree", 2, no_completion }, + { "get_attribute", 2, no_completion }, + { "set_attribute", 3, no_completion }, + { "substitute", 3, parameter_expected_offset_3 }, { "getenv", 2, no_completion }, { "setenv", 3, no_completion }, { "unsetenv", 2, no_completion }, @@ -226,6 +236,23 @@ struct { { "setenv", EQ, 1, .function = complete_against_env }, { "getenv", EQ, 1, .function = complete_against_env }, { "unsetenv", EQ, 1, .function = complete_against_env }, + { "attr", EQ, 1, .function = complete_against_objects }, + { "attr", EQ, 1, .function = complete_against_attributes }, + { "attr", EQ, 2, .function = complete_against_attribute_values }, + { "compare", EQ, 1, .function = complete_against_objects }, + { "compare", EQ, 1, .function = complete_against_attributes }, + { "compare", EQ, 2, .function = complete_against_comparators }, + { "compare", EQ, 3, .function = complete_against_attribute_values }, + { "object_tree", EQ, 1, .function = complete_against_objects }, + { "get_attribute", EQ, 1, .function = complete_against_objects }, + { "get_attribute", EQ, 1, .function = complete_against_attributes }, + { "set_attribute", EQ, 1, .function = complete_against_objects }, + { "set_attribute", EQ, 1, .function = complete_against_attributes }, + { "set_attribute", EQ, 2, .function = complete_against_attribute_values }, + { "substitute", EQ, 2, .function = complete_against_objects }, + { "substitute", EQ, 2, .function = complete_against_attributes }, + { "substitute", GE, 3, .function = complete_against_commands_3 }, + { "substitute", GE, 3, .function = complete_against_arg_1 }, { 0 }, }; @@ -380,6 +407,7 @@ void complete_against_monitors(int argc, char** argv, int pos, GString* output) // complete against relative indices try_complete(needle, "-1", output); try_complete(needle, "+1", output); + try_complete(needle, "+0", output); GString* index_str = g_string_sized_new(10); for (int i = 0; i < monitor_count(); i++) { // complete against the absolute index @@ -394,6 +422,84 @@ void complete_against_monitors(int argc, char** argv, int pos, GString* output) g_string_free(index_str, true); } +void complete_against_objects(int argc, char** argv, int pos, GString* output) { + // Remove command name + (void)SHIFT(argc,argv); + pos--; + char* needle = (pos < argc) ? argv[pos] : ""; + char* suffix; + char* prefix = g_new(char, strlen(needle)+2); + HSObject* obj = hsobject_parse_path(needle, &suffix); + strncpy(prefix, needle, suffix-needle); + if (suffix != needle && prefix[suffix - needle - 1] != OBJECT_PATH_SEPARATOR) { + prefix[suffix - needle] = OBJECT_PATH_SEPARATOR; + prefix[suffix - needle + 1] = '\0'; + } else { + prefix[suffix - needle] = '\0'; + } + hsobject_complete_children(obj, suffix, prefix, output); + g_free(prefix); +} + +void complete_against_attributes(int argc, char** argv, int pos, GString* output) { + // Remove command name + (void)SHIFT(argc,argv); + pos--; + char* needle = (pos < argc) ? argv[pos] : ""; + char* unparsable; + HSObject* obj = hsobject_parse_path(needle, &unparsable); + if (obj && strchr(unparsable, OBJECT_PATH_SEPARATOR) == NULL) { + GString* prefix = g_string_new(needle); + g_string_truncate(prefix, unparsable - needle); + if (prefix->len >= 1) { + char last = prefix->str[prefix->len - 1]; + if (last != OBJECT_PATH_SEPARATOR) { + g_string_append_c(prefix, OBJECT_PATH_SEPARATOR); + } + } + hsobject_complete_attributes(obj, unparsable, prefix->str, output); + g_string_free(prefix, true); + } +} + +void complete_against_attribute_values(int argc, char** argv, int pos, GString* output) { + char* needle = (pos < argc) ? argv[pos] : ""; + char* path = (1 < argc) ? argv[1] : ""; + GString* path_error = g_string_new(""); + HSAttribute* attr = hsattribute_parse_path_verbose(path, path_error); + g_string_free(path_error, true); + if (attr) { + switch (attr->type) { + case HSATTR_TYPE_BOOL: + complete_against_list(needle, completion_flag_args, output); + default: + // no suitable completion + break; + } + } +} + +void complete_against_comparators(int argc, char** argv, int pos, GString* output) { + char* needle = (pos < argc) ? argv[pos] : ""; + char* path = (1 < argc) ? argv[1] : ""; + GString* path_error = g_string_new(""); + HSAttribute* attr = hsattribute_parse_path_verbose(path, path_error); + g_string_free(path_error, true); + char* equals[] = { "=", "!=", NULL }; + char* order[] = { "le", "lt", "ge", "gt", NULL }; + if (attr) { + switch (attr->type) { + case HSATTR_TYPE_INT: + case HSATTR_TYPE_UINT: + case HSATTR_TYPE_CUSTOM_INT: + complete_against_list(needle, order, output); + default: + complete_against_list(needle, equals, output); + break; + } + } +} + struct wcd { /* window id completion data */ char* needle; GString* output; @@ -542,6 +648,19 @@ void complete_against_env(int argc, char** argv, int position, g_string_free(curname, true); } +void complete_against_commands_3(int argc, char** argv, int position, + GString* output) { + complete_against_commands(argc - 3, argv + 3, position - 3, output); +} + +void complete_against_arg_1(int argc, char** argv, int position, + GString* output) +{ + if (argc > 2 && position > 2) { + char* needle = (position < argc) ? argv[position] : ""; + try_complete(needle, argv[1], output); + } +} int complete_against_commands(int argc, char** argv, int position, GString* output) { @@ -662,17 +781,36 @@ bool first_parameter_is_flag(int argc, char** argv, int pos) { } } -bool keybind_parameter_expected(int argc, char** argv, int pos) { - if (argc < 2 || pos < 2) { +bool first_parameter_is_writable_attribute(int argc, char** argv, int pos) { + GString* dummy = g_string_new(""); + HSAttribute* attr = NULL; + if (argc >= 2) { + attr = hsattribute_parse_path_verbose(argv[1], dummy); + } + g_string_free(dummy, true); + return attr && attr->on_change != ATTR_READ_ONLY; +} + +bool parameter_expected_offset(int argc, char** argv, int pos, int offset) { + if (argc < offset || pos < offset) { return true; } - if (pos == 2) { + if (pos == offset) { // at least a command name always is expected return true; } - return parameter_expected(argc - 2, argv + 2, pos - 2); + return parameter_expected(argc - offset, argv + offset, pos - offset); +} + +bool parameter_expected_offset_2(int argc, char** argv, int pos) { + return parameter_expected_offset(argc,argv, pos, 2); +} + +bool parameter_expected_offset_3(int argc, char** argv, int pos) { + return parameter_expected_offset(argc,argv, pos, 3); } + int command_chain(char* separator, bool (*condition)(int laststatus), int argc, char** argv, GString* output) { size_t uargc = argc; diff --git a/src/command.h b/src/command.h index 340cff1b..a828d628 100644 --- a/src/command.h +++ b/src/command.h @@ -51,6 +51,10 @@ void complete_settings(char* str, GString* output); void complete_against_list(char* needle, char** list, GString* output); void complete_against_tags(int argc, char** argv, int pos, GString* output); void complete_against_monitors(int argc, char** argv, int pos, GString* output); +void complete_against_objects(int argc, char** argv, int pos, GString* output); +void complete_against_attributes(int argc, char** argv, int pos, GString* output); +void complete_against_attribute_values(int argc, char** argv, int pos, GString* output); +void complete_against_comparators(int argc, char** argv, int pos, GString* output); void complete_against_winids(int argc, char** argv, int pos, GString* output); void complete_merge_tag(int argc, char** argv, int pos, GString* output); void complete_negate(int argc, char** argv, int pos, GString* output); @@ -58,6 +62,9 @@ void complete_against_settings(int argc, char** argv, int pos, GString* output); void complete_against_keybinds(int argc, char** argv, int pos, GString* output); int complete_against_commands(int argc, char** argv, int position, GString* output); +void complete_against_commands_3(int argc, char** argv, int position, + GString* output); +void complete_against_arg_1(int argc, char** argv, int position, GString* output); void complete_against_keybind_command(int argc, char** argv, int position, GString* output); void complete_against_env(int argc, char** argv, int position, GString* output); diff --git a/src/layout.c b/src/layout.c index a36d653a..ecec7a31 100644 --- a/src/layout.c +++ b/src/layout.c @@ -897,10 +897,7 @@ void frame_update_frame_window_visibility(HSFrame* frame) { frame_do_recursive(frame, frame_update_frame_window_visibility_helper, 2); } -HSFrame* frame_current_selection() { - HSMonitor* m = get_current_monitor(); - if (!m->tag) return NULL; - HSFrame* frame = m->tag->frame; +HSFrame* frame_current_selection_below(HSFrame* frame) { while (frame->type == TYPE_FRAMES) { frame = (frame->content.layout.selection == 0) ? frame->content.layout.a : @@ -909,6 +906,12 @@ HSFrame* frame_current_selection() { return frame; } +HSFrame* frame_current_selection() { + HSMonitor* m = get_current_monitor(); + if (!m->tag) return NULL; + return frame_current_selection_below(m->tag->frame); +} + int frame_current_bring(int argc, char** argv, GString* output) { HSClient* client = NULL; @@ -1620,6 +1623,22 @@ void frame_do_recursive(HSFrame* frame, void (*action)(HSFrame*), int order) { } } +void frame_do_recursive_data(HSFrame* frame, void (*action)(HSFrame*,void*), + int order, void* data) { + if (frame->type == TYPE_FRAMES) { + // clients and subframes + HSLayout* layout = &(frame->content.layout); + if (order <= 0) action(frame, data); + frame_do_recursive_data(layout->a, action, order, data); + if (order == 1) action(frame, data); + frame_do_recursive_data(layout->b, action, order, data); + if (order >= 2) action(frame, data); + } else { + // action only + action(frame, data); + } +} + static void frame_hide(HSFrame* frame) { frame_set_visible(frame, false); if (frame->type == TYPE_CLIENTS) { diff --git a/src/layout.h b/src/layout.h index b872daa1..7bb1c359 100644 --- a/src/layout.h +++ b/src/layout.h @@ -100,6 +100,7 @@ HSFrame* frame_create_empty(HSFrame* parent, HSTag* parenttag); void frame_insert_window(HSFrame* frame, Window window); HSFrame* lookup_frame(HSFrame* root, char* path); HSFrame* frame_current_selection(); +HSFrame* frame_current_selection_below(HSFrame* frame); // finds the subframe of frame that contains the window HSFrame* find_frame_with_window(HSFrame* frame, Window window); // removes window from a frame/subframes @@ -144,6 +145,8 @@ int frame_focus_command(int argc, char** argv, GString* output); // follow selection to leave and focus this frame int frame_focus_recursive(HSFrame* frame); void frame_do_recursive(HSFrame* frame, void (*action)(HSFrame*), int order); +void frame_do_recursive_data(HSFrame* frame, void (*action)(HSFrame*,void*), + int order, void* data); void frame_hide_recursive(HSFrame* frame); void frame_show_recursive(HSFrame* frame); int layout_rotate_command(); @@ -18,6 +18,7 @@ #include "rules.h" #include "ewmh.h" #include "stack.h" +#include "object.h" // standard #include <string.h> #include <stdio.h> @@ -164,6 +165,12 @@ CommandBinding g_commands[] = { CMD_BIND( "and", command_chain_command), CMD_BIND( "or", command_chain_command), CMD_BIND( "!", negate_command), + CMD_BIND( "attr", attr_command), + CMD_BIND( "compare", compare_command), + CMD_BIND( "object_tree", print_object_tree_command), + CMD_BIND( "get_attribute", hsattribute_get_command), + CMD_BIND( "set_attribute", hsattribute_set_command), + CMD_BIND( "substitute", substitute_command), CMD_BIND( "getenv", getenv_command), CMD_BIND( "setenv", setenv_command), CMD_BIND( "unsetenv", unsetenv_command), @@ -700,6 +707,7 @@ static struct { void (*destroy)(); } g_modules[] = { { ipc_init, ipc_destroy }, + { object_tree_init, object_tree_destroy }, { key_init, key_destroy }, { settings_init, settings_destroy }, { stacklist_init, stacklist_destroy }, diff --git a/src/monitor.c b/src/monitor.c index 042b6ba7..db7b7275 100644 --- a/src/monitor.c +++ b/src/monitor.c @@ -31,6 +31,8 @@ int* g_smart_frame_surroundings; int* g_mouse_recenter_gap; HSStack* g_monitor_stack; GArray* g_monitors; // Array of HSMonitor* +HSObject* g_monitor_object; +HSObject* g_monitor_by_name_object; typedef struct RectList { XRectangle rect; @@ -47,6 +49,8 @@ void monitor_init() { g_smart_frame_surroundings = &(settings_find("smart_frame_surroundings")->value.i); g_mouse_recenter_gap = &(settings_find("mouse_recenter_gap")->value.i); g_monitor_stack = stack_create(); + g_monitor_object = hsobject_create_and_link(hsobject_root(), "monitors"); + g_monitor_by_name_object = hsobject_create_and_link(g_monitor_object, "by-name"); } void monitor_destroy() { @@ -54,8 +58,15 @@ void monitor_destroy() { HSMonitor* m = monitor_with_index(i); stack_remove_slice(g_monitor_stack, m->slice); slice_destroy(m->slice); + hsobject_free(&m->object); + if (m->name) { + g_string_free(m->name, true); + } + g_string_free(m->display_name, true); g_free(m); } + hsobject_unlink_and_destroy(g_monitor_object, g_monitor_by_name_object); + hsobject_unlink_and_destroy(hsobject_root(), g_monitor_object); stack_destroy(g_monitor_stack); g_array_free(g_monitors, true); } @@ -375,16 +386,29 @@ HSMonitor* string_to_monitor(char* string) { HSMonitor* add_monitor(XRectangle rect, HSTag* tag, char* name) { assert(tag != NULL); HSMonitor* m = g_new0(HSMonitor, 1); + hsobject_init(&m->object); + if (name) { + hsobject_link(g_monitor_by_name_object, &m->object, name); + } m->rect = rect; m->tag = tag; m->tag_previous = tag; m->name = (name ? g_string_new(name) : NULL); + m->display_name = g_string_new(name ? name : ""); m->mouse.x = 0; m->mouse.y = 0; m->dirty = true; m->slice = slice_create_monitor(m); m->stacking_window = XCreateSimpleWindow(g_display, g_root, 42, 42, 42, 42, 1, 0, 0); + + HSAttribute attributes[] = { + ATTRIBUTE_STRING( "name", m->display_name,ATTR_READ_ONLY ), + ATTRIBUTE_BOOL( "lock_tag", m->lock_tag, ATTR_READ_ONLY ), + ATTRIBUTE_LAST, + }; + hsobject_set_attributes(&m->object, attributes); + stack_insert_slice(g_monitor_stack, m->slice); g_array_append_val(g_monitors, m); return g_array_index(g_monitors, HSMonitor*, g_monitors->len-1); @@ -487,10 +511,13 @@ int remove_monitor(int index) { stack_remove_slice(g_monitor_stack, monitor->slice); slice_destroy(monitor->slice); XDestroyWindow(g_display, monitor->stacking_window); + hsobject_unlink(g_monitor_by_name_object, &monitor->object); + hsobject_free(&monitor->object); // and remove monitor completely if (monitor->name) { g_string_free(monitor->name, true); } + g_string_free(monitor->display_name, true); g_free(monitor); g_array_remove_index(g_monitors, index); if (g_cur_monitor >= g_monitors->len) { @@ -549,6 +576,7 @@ int rename_monitor_command(int argc, char** argv, GString* output) { } else if (!strcmp("", argv[2])) { // empty name -> clear name if (mon->name != NULL) { + hsobject_unlink_by_name(g_monitor_by_name_object, mon->name->str); g_string_free(mon->name, true); mon->name = NULL; } @@ -559,14 +587,17 @@ int rename_monitor_command(int argc, char** argv, GString* output) { "%s: A monitor with the same name already exists\n", argv[0]); return HERBST_INVALID_ARGUMENT; } + g_string_assign(mon->display_name, argv[2]); if (mon->name == NULL) { // not named before GString* name = g_string_new(argv[2]); mon->name = name; } else { + hsobject_unlink_by_name(g_monitor_by_name_object, mon->name->str); // already named g_string_assign(mon->name, argv[2]); } + hsobject_link(g_monitor_by_name_object, &mon->object, mon->name->str); return 0; } @@ -659,6 +690,8 @@ void ensure_monitors_are_available() { HSMonitor* m = add_monitor(rect, g_array_index(g_tags, HSTag*, 0), NULL); g_cur_monitor = 0; g_cur_frame = m->tag->frame; + + monitor_update_focos_objects(); } HSMonitor* monitor_with_frame(HSFrame* frame) { @@ -921,11 +954,18 @@ void monitor_focus_by_index(int new_selection) { XSync(g_display, False); while(XCheckMaskEvent(g_display, EnterWindowMask, &ev)); } + // update objects + monitor_update_focos_objects(); // emit hooks ewmh_update_current_desktop(); emit_tag_changed(monitor->tag, new_selection); } +void monitor_update_focos_objects() { + hsobject_link(g_monitor_object, &get_current_monitor()->object, "focus"); + tag_update_focus_objects(); +} + int monitor_get_relative_x(HSMonitor* m, int x_root) { return x_root - m->rect.x - m->pad_left; } diff --git a/src/monitor.h b/src/monitor.h index 3cb02a62..fb15ce5d 100644 --- a/src/monitor.h +++ b/src/monitor.h @@ -13,6 +13,8 @@ #include <X11/extensions/Xinerama.h> #endif /* XINERAMA */ +#include "object.h" + struct HSTag; struct HSFrame; struct HSSlice; @@ -22,7 +24,9 @@ typedef struct HSMonitor { struct HSTag* tag; // currently viewed tag struct HSTag* tag_previous; // previously viewed tag struct HSSlice* slice; // slice in the monitor stack + HSObject object; GString* name; + GString* display_name; // name used for object IO int pad_up; int pad_right; int pad_down; @@ -98,6 +102,7 @@ void monitor_stack_to_window_buf(Window* buf, int len, bool only_clients, int* remain_len); struct HSStack* get_monitor_stack(); +void monitor_update_focos_objects(); typedef bool (*MonitorDetection)(XRectangle**, size_t*); bool detect_monitors_xinerama(XRectangle** ret_rects, size_t* ret_count); diff --git a/src/object.c b/src/object.c new file mode 100644 index 00000000..024a6bcf --- /dev/null +++ b/src/object.c @@ -0,0 +1,722 @@ +/** Copyright 2011-2013 Thorsten Wißmann. All rights reserved. + * + * This software is licensed under the "Simplified BSD License". + * See LICENSE for details */ + +#include "object.h" +#include "command.h" +#include "utils.h" +#include "assert.h" +#include "ipc-protocol.h" + +#include <string.h> +#include <stdlib.h> +#include <stdio.h> + +typedef struct { + char* name; + HSObject* child; +} HSObjectChild; + +static void hsobjectchild_destroy(HSObjectChild* oc); +static HSObjectChild* hsobjectchild_create(char* name, HSObject* obj); + +static HSObject g_root_object; + +void object_tree_init() { + hsobject_init(&g_root_object); +} + +void object_tree_destroy() { + hsobject_free(&g_root_object); +} + +HSObject* hsobject_root() { + return &g_root_object; +} + +bool hsobject_init(HSObject* obj) { + obj->attributes = NULL; + obj->attribute_count = 0; + obj->children = NULL; + return true; +} + +void hsobject_free(HSObject* obj) { + g_free(obj->attributes); + g_list_free_full(obj->children, (GDestroyNotify)hsobjectchild_destroy); +} + +HSObject* hsobject_create() { + HSObject* obj = g_new(HSObject, 1); + hsobject_init(obj); + return obj; +} + +void hsobject_destroy(HSObject* obj) { + if (!obj) return; + hsobject_free(obj); + g_free(obj); +} + +HSObject* hsobject_create_and_link(HSObject* parent, char* name) { + HSObject* obj = hsobject_create(); + hsobject_link(parent, obj, name); + return obj; +} + +void hsobject_unlink_and_destroy(HSObject* parent, HSObject* child) { + hsobject_unlink(parent, child); + hsobject_destroy(child); +} + +HSObjectChild* hsobjectchild_create(char* name, HSObject* obj) { + HSObjectChild* oc = g_new(HSObjectChild, 1); + oc->name = g_strdup(name); + oc->child = obj; + return oc; +} + +void hsobjectchild_destroy(HSObjectChild* oc) { + if (!oc) return; + g_free(oc->name); + g_free(oc); +} + +struct HSObjectComplChild { + char* needle; + char* prefix; + GString* curname; + GString* output; +}; + +static void completion_helper(HSObjectChild* child, struct HSObjectComplChild* data) { + g_string_assign(data->curname, child->name); + g_string_append_c(data->curname, OBJECT_PATH_SEPARATOR); + try_complete_prefix_partial(data->needle, data->curname->str, data->prefix, data->output); +} + +void hsobject_complete_children(HSObject* obj, char* needle, char* prefix, GString* output) { + struct HSObjectComplChild data = { + needle, + prefix, + g_string_new(""), + output + }; + g_list_foreach(obj->children, (GFunc) completion_helper, &data); + g_string_free(data.curname, true); +} + +void hsobject_complete_attributes(HSObject* obj, char* needle, char* prefix, + GString* output) { + for (int i = 0; i < obj->attribute_count; i++) { + HSAttribute* attr = obj->attributes + i; + try_complete_prefix(needle, attr->name, prefix, output); + } +} + +static int child_check_name(HSObjectChild* child, char* name) { + return strcmp(child->name, name); +} + +void hsobject_link(HSObject* parent, HSObject* child, char* name) { + GList* elem = g_list_find_custom(parent->children, name, + (GCompareFunc)child_check_name); + if (!elem) { + // create a new child node + HSObjectChild* oc = hsobjectchild_create(name, child); + parent->children = g_list_append(parent->children, oc); + } else { + // replace it + HSObjectChild* oc = (HSObjectChild*) elem->data; + oc->child = child; + } +} + +static int child_check_object(HSObjectChild* child, HSObject* obj) { + // return 0 if they are identical + return (child->child == obj) ? 0 : 1; +} + +static void hsobject_unlink_helper(HSObject* parent, GCompareFunc f, void* data) { + GList* elem = parent->children; + while (elem) { + elem = g_list_find_custom(elem, data, f); + if (elem) { + GList* next = elem->next; + hsobjectchild_destroy((HSObjectChild*)elem->data); + parent->children = g_list_delete_link(parent->children, elem); + elem = next; + } + } +} + +void hsobject_unlink(HSObject* parent, HSObject* child) { + hsobject_unlink_helper(parent, + (GCompareFunc)child_check_object, + child); +} + +void hsobject_unlink_by_name(HSObject* parent, char* name) { + hsobject_unlink_helper(parent, + (GCompareFunc)child_check_name, + name); +} + +void hsobject_link_rename(HSObject* parent, char* oldname, char* newname) { + if (!strcmp(oldname, newname)) { + return; + } + // remove object with target name + hsobject_unlink_by_name(parent, newname); + GList* elem = g_list_find_custom(parent->children, + oldname, + (GCompareFunc)child_check_name); + HSObjectChild* child = (HSObjectChild*)elem->data; + g_free(child->name); + child->name = g_strdup(newname); +} + +void hsobject_link_rename_object(HSObject* parent, HSObject* child, char* newname) { + // remove occurrences of that object + hsobject_unlink(parent, child); + // link it again (replacing any object with newname) + hsobject_link(parent, child, newname); +} + +HSObject* hsobject_find_child(HSObject* obj, char* name) { + GList* elem = g_list_find_custom(obj->children, name, + (GCompareFunc)child_check_name); + if (elem) { + return ((HSObjectChild*)(elem->data))->child; + } else { + return NULL; + } +} + +HSAttribute* hsobject_find_attribute(HSObject* obj, char* name) { + for (int i = 0; i < obj->attribute_count; i++) { + if (!strcmp(name, obj->attributes[i].name)) { + return obj->attributes + i; + } + } + return NULL; +} + +static void print_child_name(HSObjectChild* child, GString* output) { + g_string_append_printf(output, " %s%c\n", child->name, OBJECT_PATH_SEPARATOR); +} + +void hsattribute_append_to_string(HSAttribute* attribute, GString* output) { + switch (attribute->type) { + case HSATTR_TYPE_BOOL: + if (*(attribute->value.b)) { + g_string_append_printf(output, "true"); + } else { + g_string_append_printf(output, "false"); + } + break; + case HSATTR_TYPE_INT: + g_string_append_printf(output, "%d", *attribute->value.i); + break; + case HSATTR_TYPE_UINT: + g_string_append_printf(output, "%u", *attribute->value.u); + break; + case HSATTR_TYPE_STRING: + g_string_append_printf(output, "%s", (*attribute->value.str)->str); + break; + case HSATTR_TYPE_CUSTOM: + attribute->value.custom(attribute->object->data, output); + break; + case HSATTR_TYPE_CUSTOM_INT: + g_string_append_printf(output, "%d", + attribute->value.custom_int(attribute->object->data)); + break; + } +} + +GString* hsattribute_to_string(HSAttribute* attribute) { + GString* str = g_string_new(""); + hsattribute_append_to_string(attribute, str); + return str; +} + +int attr_command(int argc, char* argv[], GString* output) { + char* path = (argc < 2) ? "" : argv[1]; + char* unparsable; + GString* errormsg = g_string_new(""); + HSObject* obj = hsobject_parse_path_verbose(path, &unparsable, errormsg); + HSAttribute* attribute = NULL; + if (strcmp("", unparsable)) { + // if object could not be parsed, try attribute + attribute = hsattribute_parse_path_verbose(path, errormsg); + obj = NULL; + } + if (!obj && !attribute) { + // if nothing was found + g_string_append(output, errormsg->str); + g_string_free(errormsg, true); + return HERBST_INVALID_ARGUMENT; + } else { + g_string_free(errormsg, true); + } + char* new_value = (argc >= 3) ? argv[2] : NULL; + if (obj && new_value) { + g_string_append_printf(output, + "%s: Can not assign value \"%s\" to object \"%s\",", + argv[0], new_value, path); + } else if (obj && !new_value) { + // list children + int childcount = g_list_length(obj->children); + g_string_append_printf(output, "%d children%c\n", childcount, + childcount ? ':' : '.'); + g_list_foreach(obj->children, (GFunc) print_child_name, output); + if (childcount > 0) { + g_string_append_printf(output, "\n"); + } + // list attributes + g_string_append_printf(output, "%d attributes", obj->attribute_count); + if (obj->attribute_count > 0) { + g_string_append_printf(output, ":\n"); + g_string_append_printf(output, " .---- type\n"); + g_string_append_printf(output, " | .-- writeable\n"); + g_string_append_printf(output, " V V\n"); + } else { + g_string_append_printf(output, ".\n"); + } + for (int i = 0; i < obj->attribute_count; i++) { + HSAttribute* a = obj->attributes + i; + char write = (a->on_change == ATTR_READ_ONLY) ? '-' : 'w'; + char t = hsattribute_type_indicator(a->type); + g_string_append_printf(output, " %c %c %-20s = ", t, write, a->name); + if (a->type == HSATTR_TYPE_STRING) { + g_string_append_c(output, '\"'); + } + hsattribute_append_to_string(a, output); + if (a->type == HSATTR_TYPE_STRING) { + g_string_append_c(output, '\"'); + } + g_string_append_c(output, '\n'); + } + } else if (new_value) { // && (attribute) + return hsattribute_assign(attribute, new_value, output); + } else { // !new_value && (attribute) + hsattribute_append_to_string(attribute, output); + } + return 0; +} + +static void object_append_caption(HSTree tree, GString* output) { + HSObjectChild* oc = (HSObjectChild*) tree; + g_string_append(output, oc->name); +} + +static size_t object_child_count(HSTree tree) { + HSObjectChild* oc = (HSObjectChild*) tree; + return g_list_length(oc->child->children); +} + +static HSTreeInterface object_nth_child(HSTree tree, size_t idx) { + HSObjectChild* oc = (HSObjectChild*) tree; + assert(oc->child); + HSTreeInterface intf = { + .nth_child = object_nth_child, + .data = (HSTree) g_list_nth_data(oc->child->children, idx), + .destructor = NULL, + .child_count = object_child_count, + .append_caption = object_append_caption, + }; + return intf; +} + +HSObject* hsobject_by_path(char* path) { + HSObject* obj = hsobject_parse_path(path, &path); + if (!strcmp("", path)) { + return obj; + } else { + // an invalid path was given if it was not parsed entirely + return NULL; + } +} + +HSObject* hsobject_parse_path_verbose(char* path, char** unparsable, + GString* output) { + char* origpath = path; + char* pathdup = strdup(path); + char* curname = pathdup; + char* lastname = "root"; + char seps[] = { OBJECT_PATH_SEPARATOR, '\0' }; + // replace all separators by null bytes + g_strdelimit(curname, seps, '\0'); + HSObject* obj = hsobject_root(); + HSObject* child; + // skip separator characters + while (*path == OBJECT_PATH_SEPARATOR) { + path++; + curname++; + } + while (strcmp("", path)) { + child = hsobject_find_child(obj, curname); + if (!child) { + if (output) { + g_string_append_printf(output, "Invalid path \"%s\": ", origpath); + g_string_append_printf(output, "No child \"%s\" in object %s\n", + curname, lastname); + } + break; + } + lastname = curname; + obj = child; + // skip the name + path += strlen(curname); + curname += strlen(curname); + // skip separator characters + while (*path == OBJECT_PATH_SEPARATOR) { + path++; + curname++; + } + } + *unparsable = path; + free(pathdup); + return obj; +} + +HSObject* hsobject_parse_path(char* path, char** unparsable) { + return hsobject_parse_path_verbose(path, unparsable, NULL); +} + +HSAttribute* hsattribute_parse_path_verbose(char* path, GString* output) { + GString* object_error = g_string_new(""); + HSAttribute* attr; + char* unparsable; + HSObject* obj = hsobject_parse_path_verbose(path, &unparsable, object_error); + if (obj == NULL || strchr(unparsable, OBJECT_PATH_SEPARATOR) != NULL) { + // if there is still another path separator + // then unparsable is more than just the attribute name. + g_string_append(output, object_error->str); + attr = NULL; + } else { + // if there is no path remaining separator, then unparsable contains + // the attribute name + attr = hsobject_find_attribute(obj, unparsable); + if (!attr) { + GString* obj_path = g_string_new(path); + g_string_truncate(obj_path, unparsable - path); + g_string_append_printf(output, + "Unknown attribute \"%s\" in object \"%s\".\n", + unparsable, obj_path->str); + g_string_free(obj_path, true); + } + } + g_string_free(object_error, true); + return attr; +} + +int print_object_tree_command(int argc, char* argv[], GString* output) { + char* unparsable; + char* path = (argc < 2) ? "" : argv[1]; + HSObjectChild oc = { + .name = path, + .child = hsobject_parse_path_verbose(path, &unparsable, output), + }; + if (strcmp("", unparsable)) { + return HERBST_INVALID_ARGUMENT; + } + HSTreeInterface intf = { + .nth_child = object_nth_child, + .data = &oc, + .destructor = NULL, + .child_count = object_child_count, + .append_caption = object_append_caption, + }; + tree_print_to(&intf, output); + return 0; +} + +void hsobject_set_attributes(HSObject* obj, HSAttribute* attributes) { + // calculate new size + size_t count; + for (count = 0; attributes[count].name != NULL; count++) + ; + obj->attributes = g_renew(HSAttribute, obj->attributes, count); + obj->attribute_count = count; + memcpy(obj->attributes, attributes, count * sizeof(HSAttribute)); + for (int i = 0; i < count; i++) { + obj->attributes[i].object = obj; + } +} + +int hsattribute_get_command(int argc, char* argv[], GString* output) { + if (argc < 2) { + return HERBST_NEED_MORE_ARGS; + } + HSAttribute* attr = hsattribute_parse_path_verbose(argv[1], output); + if (!attr) { + return HERBST_INVALID_ARGUMENT; + } + hsattribute_append_to_string(attr, output); + return 0; +} + +int hsattribute_set_command(int argc, char* argv[], GString* output) { + if (argc < 3) { + return HERBST_NEED_MORE_ARGS; + } + HSAttribute* attr = hsattribute_parse_path_verbose(argv[1], output); + if (!attr) { + return HERBST_INVALID_ARGUMENT; + } + return hsattribute_assign(attr, argv[2], output); +} + +int hsattribute_assign(HSAttribute* attr, char* new_value_str, GString* output) { + if (attr->on_change == ATTR_READ_ONLY) { + g_string_append_printf(output, + "Can not write read-only attribute \"%s\"\n", + attr->name); + return HERBST_FORBIDDEN; + } + + bool error = false; + union { + bool b; + int i; + unsigned int u; + GString* str; + } new_value, old_value; + bool nothing_to_do = false; + +#define ATTR_DO_ASSIGN_COMPARE(NAME,MEM) \ + do { \ + if (error) { \ + g_string_append_printf(output, \ + "Can not parse "NAME" from \"%s\"", \ + new_value_str); \ + } \ + old_value.MEM = *attr->value.MEM; \ + if (old_value.MEM == new_value.MEM) { \ + nothing_to_do = true; \ + } else { \ + *attr->value.MEM = new_value.MEM; \ + } \ + } while (0); + + // change the value and backup the old value + switch (attr->type) { + case HSATTR_TYPE_BOOL: + new_value.b = string_to_bool_error(new_value_str, + *attr->value.b, + &error); + ATTR_DO_ASSIGN_COMPARE("boolean", b); + break; + + case HSATTR_TYPE_INT: + error = (1 != sscanf(new_value_str, "%d", &new_value.i)); + ATTR_DO_ASSIGN_COMPARE("integer", i); + break; + + case HSATTR_TYPE_UINT: + error = (1 != sscanf(new_value_str, "%u", &new_value.u)); + ATTR_DO_ASSIGN_COMPARE("unsigned integer", u); + break; + + + case HSATTR_TYPE_STRING: + if (!strcmp(new_value_str, (*attr->value.str)->str)) { + nothing_to_do = true; + } else { + old_value.str = g_string_new((*attr->value.str)->str); + g_string_assign(*attr->value.str, new_value_str); + } + break; + case HSATTR_TYPE_CUSTOM: break; + case HSATTR_TYPE_CUSTOM_INT: break; + } + if (nothing_to_do) { + return 0; + } + + // ask the attribute about the change + GString* errormsg = attr->on_change(attr); + int exit_status = 0; + if (errormsg && errormsg->len > 0) { + exit_status = HERBST_INVALID_ARGUMENT; + // print the message + if (errormsg->str[errormsg->len - 1] == '\n') { + g_string_truncate(errormsg, errormsg->len - 1); + } + g_string_append_printf(output, + "Can not write attribute \"%s\": %s\n", + attr->name, + errormsg->str); + g_string_free(errormsg, true); + // restore old value + switch (attr->type) { + case HSATTR_TYPE_BOOL: *attr->value.b = old_value.b; break; + case HSATTR_TYPE_INT: *attr->value.i = old_value.i; break; + case HSATTR_TYPE_UINT: *attr->value.u = old_value.u; break; + case HSATTR_TYPE_STRING: + g_string_assign(*attr->value.str, old_value.str->str); + break; + case HSATTR_TYPE_CUSTOM: break; + case HSATTR_TYPE_CUSTOM_INT: break; + } + } + // free old_value + switch (attr->type) { + case HSATTR_TYPE_BOOL: break; + case HSATTR_TYPE_INT: break; + case HSATTR_TYPE_UINT: break; + case HSATTR_TYPE_STRING: + g_string_free(old_value.str, true); + break; + case HSATTR_TYPE_CUSTOM: break; + case HSATTR_TYPE_CUSTOM_INT: break; + } + return exit_status; +} + + +int substitute_command(int argc, char* argv[], GString* output) { + // usage: substitute identifier attribute command [args ...] + // 0 1 2 3 + if (argc < 4) { + return HERBST_NEED_MORE_ARGS; + } + char* identifier = argv[1]; + HSAttribute* attribute = hsattribute_parse_path_verbose(argv[2], output); + if (!attribute) { + return HERBST_INVALID_ARGUMENT; + } + GString* attribute_string = hsattribute_to_string(attribute); + + (void) SHIFT(argc, argv); // remove command name + (void) SHIFT(argc, argv); // remove identifier + (void) SHIFT(argc, argv); // remove attribute + + // construct the new command + char** command = g_new(char*, argc + 1); + command[argc] = NULL; + for (int i = 0; i < argc; i++) { + if (!strcmp(identifier, argv[i])) { + // if argument equals the identifier, replace it by the attribute + // value + command[i] = attribute_string->str; + } else { + command[i] = argv[i]; + } + } + int status = call_command(argc, command, output); + g_free(command); + g_string_free(attribute_string, true); + return status; +} + +GString* ATTR_ACCEPT_ALL(HSAttribute* attr) { + (void) attr; + return NULL; +} + +int compare_command(int argc, char* argv[], GString* output) { + // usage: compare attribute operator constant + if (argc < 4) { + return HERBST_NEED_MORE_ARGS; + } + HSAttribute* attr = hsattribute_parse_path_verbose(argv[1], output); + if (!attr) { + return HERBST_INVALID_ARGUMENT; + } + char* op = argv[2]; + char* rvalue = argv[3]; + if (attr->type == HSATTR_TYPE_INT + || attr->type == HSATTR_TYPE_UINT + || attr->type == HSATTR_TYPE_CUSTOM_INT) + { + long long l; + long long r; + if (1 != sscanf(rvalue, "%lld", &r)) { + g_string_append_printf(output, + "Can not parse integer from \"%s\"\n", + rvalue); + return HERBST_INVALID_ARGUMENT; + } + switch (attr->type) { + case HSATTR_TYPE_INT: l = *attr->value.i; break; + case HSATTR_TYPE_UINT: l = *attr->value.u; break; + case HSATTR_TYPE_CUSTOM_INT: + l = attr->value.custom_int(attr->object->data); + break; + default: break; + } + struct { + char* name; + bool result; + } eval[] = { + { "=", l == r }, + { "!=", l != r }, + { "le", l <= r }, + { "lt", l < r }, + { "ge", l >= r }, + { "gt", l > r }, + }; + int result = -1; + for (int i = 0; i < LENGTH(eval); i++) { + if (!strcmp(eval[i].name, op)) { + result = !eval[i].result; // make false -> 1, true -> 0 + } + } + if (result == -1) { + g_string_append_printf(output, "Invalid operator \"%s\"", op); + result = HERBST_INVALID_ARGUMENT; + } + return result; + } else if (attr->type == HSATTR_TYPE_BOOL) { + bool l = *attr->value.b; + bool error = false; + bool r = string_to_bool_error(rvalue, l, &error); + if (error) { + g_string_append_printf(output, "Can not parse boolean from \"%s\"\n", rvalue); + return HERBST_INVALID_ARGUMENT; + } + if (!strcmp("=", op)) return !(l == r); + if (!strcmp("!=", op)) return !(l != r); + g_string_append_printf(output, "Invalid boolean operator \"%s\"", op); + return HERBST_INVALID_ARGUMENT; + } else { // STRING or CUSTOM + GString* l; + bool free_l = false; + if (attr->type == HSATTR_TYPE_STRING) { + l = *(attr->value.str); + } else { // TYPE == CUSTOM + l = g_string_new(""); + attr->value.custom(attr->object->data, l); + free_l = true; + } + bool equals = !strcmp(l->str, rvalue); + int status; + if (!strcmp("=", op)) status = !equals; + else if (!strcmp("!=", op)) status = equals; + else status = -1; + if (free_l) { + g_string_free(l, true); + } + if (status == -1) { + g_string_append_printf(output, "Invalid string operator \"%s\"", op); + return HERBST_INVALID_ARGUMENT; + } + return status; + } +} + +char hsattribute_type_indicator(int type) { + switch (type) { + case HSATTR_TYPE_BOOL: return 'b'; + case HSATTR_TYPE_UINT: return 'u'; + case HSATTR_TYPE_INT: return 'i'; + case HSATTR_TYPE_STRING: return 's'; + case HSATTR_TYPE_CUSTOM: return 's'; + case HSATTR_TYPE_CUSTOM_INT:return 'i'; + } + return '?'; +} + diff --git a/src/object.h b/src/object.h new file mode 100644 index 00000000..00c48138 --- /dev/null +++ b/src/object.h @@ -0,0 +1,119 @@ +/** Copyright 2011-2013 Thorsten Wißmann. All rights reserved. + * + * This software is licensed under the "Simplified BSD License". + * See LICENSE for details */ + +#ifndef __HS_OBJECT_H_ +#define __HS_OBJECT_H_ + +#include <stdbool.h> +#include <glib.h> + +#define OBJECT_PATH_SEPARATOR '.' + +typedef struct HSObject { + struct HSAttribute* attributes; + size_t attribute_count; + GList* children; // list of HSObjectChild + void* data; // user data pointer +} HSObject; + +typedef void (*HSAttributeCustom)(void* data, GString* output); +typedef int (*HSAttributeCustomInt)(void* data); + +typedef struct HSAttribute { + HSObject* object; /* the object this attribute is in */ + enum { + HSATTR_TYPE_BOOL, + HSATTR_TYPE_UINT, + HSATTR_TYPE_INT, + HSATTR_TYPE_STRING, + HSATTR_TYPE_CUSTOM, + HSATTR_TYPE_CUSTOM_INT, + } type; /* the datatype */ + char* name; /* name as it is displayed to the user */ + union { + bool* b; + int* i; + unsigned int* u; + GString** str; + HSAttributeCustom custom; + HSAttributeCustomInt custom_int; + } value; + /** if type is not custom: + * on_change is called after the user changes the value. If this + * function returns NULL, the value is accepted. If this function returns + * some error message, the old value is restored automatically and the + * message first is displayed to the user and then freed. + * + * if type is custom: + * on_change will never be called, because custom are read-only for now. + * */ + GString* (*on_change) (struct HSAttribute* attr); +} HSAttribute; + +#define ATTRIBUTE_BOOL(N, V, CHANGE) \ + { NULL, HSATTR_TYPE_BOOL, (N), { .b = &(V) }, (CHANGE) } +#define ATTRIBUTE_INT(N, V, CHANGE) \ + { NULL, HSATTR_TYPE_INT, (N), { .i = &(V) }, (CHANGE) } +#define ATTRIBUTE_UINT(N, V, CHANGE) \ + { NULL, HSATTR_TYPE_UINT, (N), { .u = &(V) }, (CHANGE) } +#define ATTRIBUTE_STRING(N, V, CHANGE) \ + { NULL, HSATTR_TYPE_STRING, (N), { .str = &(V) }, (CHANGE) } +#define ATTRIBUTE_CUSTOM(N, V, CHANGE) \ + { NULL, HSATTR_TYPE_CUSTOM, (N), { .custom = V }, (NULL) } +#define ATTRIBUTE_CUSTOM_INT(N, V, CHANGE) \ + { NULL, HSATTR_TYPE_CUSTOM_INT, (N), { .custom_int = V }, (NULL) } + +#define ATTRIBUTE_LAST { .name = NULL } + +void object_tree_init(); +void object_tree_destroy(); + +HSObject* hsobject_root(); + +bool hsobject_init(HSObject* obj); +void hsobject_free(HSObject* obj); +HSObject* hsobject_create(); +HSObject* hsobject_create_and_link(HSObject* parent, char* name); +void hsobject_destroy(HSObject* obj); +void hsobject_link(HSObject* parent, HSObject* child, char* name); +void hsobject_unlink(HSObject* parent, HSObject* child); +void hsobject_unlink_by_name(HSObject* parent, char* name); +void hsobject_link_rename(HSObject* parent, char* oldname, char* newname); +void hsobject_rename_child(HSObject* parent, HSObject* child, char* newname); +void hsobject_unlink_and_destroy(HSObject* parent, HSObject* child); + +HSObject* hsobject_by_path(char* path); +HSObject* hsobject_parse_path(char* path, char** unparsable); +HSObject* hsobject_parse_path_verbose(char* path, char** unparsable, GString* output); + +HSAttribute* hsattribute_parse_path_verbose(char* path, GString* output); + +void hsobject_set_attributes(HSObject* obj, HSAttribute* attributes); + +GString* ATTR_ACCEPT_ALL(HSAttribute* attr); +#define ATTR_READ_ONLY NULL + +HSObject* hsobject_find_child(HSObject* obj, char* name); +HSAttribute* hsobject_find_attribute(HSObject* obj, char* name); + +char hsattribute_type_indicator(int type); + +int attr_command(int argc, char* argv[], GString* output); +int print_object_tree_command(int argc, char* argv[], GString* output); +int hsattribute_get_command(int argc, char* argv[], GString* output); +int hsattribute_set_command(int argc, char* argv[], GString* output); +int hsattribute_assign(HSAttribute* attr, char* new_value_str, GString* output); +void hsattribute_append_to_string(HSAttribute* attribute, GString* output); +GString* hsattribute_to_string(HSAttribute* attribute); + +void hsobject_complete_children(HSObject* obj, char* needle, char* prefix, + GString* output); +void hsobject_complete_attributes(HSObject* obj, char* needle, char* prefix, + GString* output); +int substitute_command(int argc, char* argv[], GString* output); +int compare_command(int argc, char* argv[], GString* output); + +#endif + @@ -21,12 +21,23 @@ #include "settings.h" bool g_tag_flags_dirty = true; +HSObject* g_tag_object; +HSObject* g_tag_by_name; int* g_raise_on_focus_temporarily; +static int tag_rename(HSTag* tag, char* name, GString* output); + void tag_init() { g_tags = g_array_new(false, false, sizeof(HSTag*)); g_raise_on_focus_temporarily = &(settings_find("raise_on_focus_temporarily") ->value.i); + g_tag_object = hsobject_create_and_link(hsobject_root(), "tags"); + HSAttribute attributes[] = { + ATTRIBUTE_UINT("count", g_tags->len, ATTR_READ_ONLY), + ATTRIBUTE_LAST, + }; + hsobject_set_attributes(g_tag_object, attributes); + g_tag_by_name = hsobject_create_and_link(g_tag_object, "by-name"); } static void tag_free(HSTag* tag) { @@ -39,7 +50,9 @@ static void tag_free(HSTag* tag) { } } stack_destroy(tag->stack); + hsobject_unlink_and_destroy(g_tag_by_name, tag->object); g_string_free(tag->name, true); + g_string_free(tag->display_name, true); g_free(tag); } @@ -51,6 +64,8 @@ void tag_destroy() { tag_free(tag); } g_array_free(g_tags, true); + hsobject_unlink_and_destroy(g_tag_object, g_tag_by_name); + hsobject_unlink_and_destroy(hsobject_root(), g_tag_object); } @@ -122,6 +137,67 @@ HSTag* find_unused_tag() { return NULL; } +static GString* tag_attr_floating(HSAttribute* attr) { + HSTag* tag = container_of(attr->value.b, HSTag, floating); + HSMonitor* m = find_monitor_with_tag(tag); + if (m != NULL) { + monitor_apply_layout(m); + } + return NULL; +} + +static GString* tag_attr_name(HSAttribute* attr) { + HSTag* tag = container_of(attr->value.str, HSTag, display_name); + GString* error = g_string_new(""); + int status = tag_rename(tag, tag->display_name->str, error); + if (status == 0) { + g_string_free(error, true); + return NULL; + } else { + return error; + } +} + +static void sum_up_clientframes(HSFrame* frame, void* data) { + if (frame->type == TYPE_CLIENTS) { + (*(int*)data)++; + } +} + +static int tag_attr_frame_count(void* data) { + HSTag* tag = (HSTag*) data; + int i = 0; + frame_do_recursive_data(tag->frame, sum_up_clientframes, 0, &i); + return i; +} + +static void sum_up_clients(HSFrame* frame, void* data) { + if (frame->type == TYPE_CLIENTS) { + *(int*)data += frame->content.clients.count; + } +} + +static int tag_attr_client_count(void* data) { + HSTag* tag = (HSTag*) data; + int i = 0; + frame_do_recursive_data(tag->frame, sum_up_clients, 0, &i); + return i; +} + + +static int tag_attr_curframe_windex(void* data) { + HSTag* tag = (HSTag*) data; + HSFrame* frame = frame_current_selection_below(tag->frame); + return frame->content.clients.selection; +} + +static int tag_attr_curframe_wcount(void* data) { + HSTag* tag = (HSTag*) data; + HSFrame* frame = frame_current_selection_below(tag->frame); + return frame->content.clients.count; +} + + HSTag* add_tag(char* name) { HSTag* find_result = find_tag(name); if (find_result) { @@ -132,8 +208,24 @@ HSTag* add_tag(char* name) { tag->stack = stack_create(); tag->frame = frame_create_empty(NULL, tag); tag->name = g_string_new(name); + tag->display_name = g_string_new(name); tag->floating = false; g_array_append_val(g_tags, tag); + + // create object + tag->object = hsobject_create_and_link(g_tag_by_name, name); + tag->object->data = tag; + HSAttribute attributes[] = { + ATTRIBUTE_STRING("name", tag->display_name, tag_attr_name), + ATTRIBUTE_BOOL( "floating", tag->floating, tag_attr_floating), + ATTRIBUTE_CUSTOM_INT("frame_count", tag_attr_frame_count, ATTR_READ_ONLY), + ATTRIBUTE_CUSTOM_INT("client_count", tag_attr_client_count, ATTR_READ_ONLY), + ATTRIBUTE_CUSTOM_INT("curframe_windex",tag_attr_curframe_windex, ATTR_READ_ONLY), + ATTRIBUTE_CUSTOM_INT("curframe_wcount",tag_attr_curframe_wcount, ATTR_READ_ONLY), + ATTRIBUTE_LAST, + }; + hsobject_set_attributes(tag->object, attributes); + ewmh_update_desktops(); ewmh_update_desktop_names(); tag_set_flags_dirty(); @@ -154,6 +246,20 @@ int tag_add_command(int argc, char** argv, GString* output) { return 0; } +static int tag_rename(HSTag* tag, char* name, GString* output) { + if (find_tag(name)) { + g_string_append_printf(output, + "Error: Tag \"%s\" already exists\n", name); + return HERBST_TAG_IN_USE; + } + hsobject_link_rename(g_tag_by_name, tag->name->str, name); + g_string_assign(tag->name, name); + g_string_assign(tag->display_name, name); + ewmh_update_desktop_names(); + hook_emit_list("tag_renamed", tag->name->str, NULL); + return 0; +} + int tag_rename_command(int argc, char** argv, GString* output) { if (argc < 3) { return HERBST_NEED_MORE_ARGS; @@ -164,15 +270,7 @@ int tag_rename_command(int argc, char** argv, GString* output) { "%s: Tag \"%s\" not found\n", argv[0], argv[1]); return HERBST_INVALID_ARGUMENT; } - if (find_tag(argv[2])) { - g_string_append_printf(output, - "%s: Tag \"%s\" already exists\n", argv[0], argv[2]); - return HERBST_TAG_IN_USE; - } - g_string_assign(tag->name, argv[2]); - ewmh_update_desktop_names(); - hook_emit_list("tag_renamed", tag->name->str, NULL); - return 0; + return tag_rename(tag, argv[2], output); } int tag_remove_command(int argc, char** argv, GString* output) { @@ -259,10 +357,7 @@ int tag_set_floating_command(int argc, char** argv, GString* output) { } } - bool new_value = false; - if (!strcmp(action, "toggle")) new_value = ! tag->floating; - else if (!strcmp(action, "on")) new_value = true; - else if (!strcmp(action, "off")) new_value = false; + bool new_value = string_to_bool(action, tag->floating); if (!strcmp(action, "status")) { // just print status @@ -446,3 +541,7 @@ void tag_update_each_focus_layer() { tag_foreach(tag_update_focus_layer_helper, NULL); } +void tag_update_focus_objects() { + hsobject_link(g_tag_object, get_current_monitor()->tag->object, "focus"); +} + @@ -15,10 +15,12 @@ struct HSStack; typedef struct HSTag { GString* name; // name of this tag + GString* display_name; // name used for object-io struct HSFrame* frame; // the master frame bool floating; int flags; struct HSStack* stack; + struct HSObject* object; } HSTag; // globals @@ -47,6 +49,7 @@ int tag_set_floating_command(int argc, char** argv, GString* output); void tag_update_focus_layer(HSTag* tag); void tag_foreach(void (*action)(HSTag*,void*), void* data); void tag_update_each_focus_layer(); +void tag_update_focus_objects(); void tag_force_update_flags(); void tag_update_flags(); void tag_set_flags_dirty(); diff --git a/src/utils.c b/src/utils.c index 6a63ee76..78e3fe0c 100644 --- a/src/utils.c +++ b/src/utils.c @@ -198,14 +198,28 @@ char* strlasttoken(char* str, char* delim) { return str; } + bool string_to_bool(char* string, bool oldvalue) { + return string_to_bool_error(string, oldvalue, NULL); +} + +bool string_to_bool_error(char* string, bool oldvalue, bool* error) { bool val = oldvalue; + if (error) { + error = false; + } if (!strcmp(string, "on")) { val = true; } else if (!strcmp(string, "off")) { val = false; + } else if (!strcmp(string, "true")) { + val = true; + } else if (!strcmp(string, "false")) { + val = false; } else if (!strcmp(string, "toggle")) { val = ! oldvalue; + } else if (error) { + *error = true; } return val; } diff --git a/src/utils.h b/src/utils.h index bef01b6b..5888454e 100644 --- a/src/utils.h +++ b/src/utils.h @@ -16,6 +16,10 @@ #define SHIFT(ARGC, ARGV) (--(ARGC) && ++(ARGV)) #define MOD(X, N) ((((X) % (signed)(N)) + (signed)(N)) % (signed)(N)) +#define container_of(ptr, type, member) \ + ((type *)( (char *)(ptr)- offsetof(type,member) )) + + /// print a printf-like message to stderr and exit void die(const char *errstr, ...); @@ -49,6 +53,7 @@ bool is_window_mapped(Display* dpy, Window window); bool window_has_property(Display* dpy, Window window, char* prop_name); +bool string_to_bool_error(char* string, bool oldvalue, bool* error); bool string_to_bool(char* string, bool oldvalue); char* strlasttoken(char* str, char* delim); |