summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThorsten Wißmann <edu@thorsten-wissmann.de>2013-03-04 01:56:26 +0100
committerThorsten Wißmann <edu@thorsten-wissmann.de>2013-03-04 01:56:26 +0100
commit41a47e9bce207829c69da498f044df764cd12389 (patch)
tree6239c4ed7d9ad005add364fef9c5b06186a601a5
parente0525f4cb1adda4a105e18ed9deb71dc7293c650 (diff)
parentb88718a7584e6fc1e79a822379d7f1d3a2846ded (diff)
Merge branch 'hsobject'
This adds the object-tree and its commands.
-rw-r--r--HACKING39
-rw-r--r--NEWS2
-rw-r--r--doc/herbstluftwm.txt211
-rw-r--r--src/clientlist.c66
-rw-r--r--src/clientlist.h3
-rw-r--r--src/command.c152
-rw-r--r--src/command.h7
-rw-r--r--src/layout.c27
-rw-r--r--src/layout.h3
-rw-r--r--src/main.c8
-rw-r--r--src/monitor.c40
-rw-r--r--src/monitor.h5
-rw-r--r--src/object.c722
-rw-r--r--src/object.h119
-rw-r--r--src/tag.c125
-rw-r--r--src/tag.h3
-rw-r--r--src/utils.c14
-rw-r--r--src/utils.h5
18 files changed, 1518 insertions, 33 deletions
diff --git a/HACKING b/HACKING
index a1f415ed..3c69dad9 100644
--- a/HACKING
+++ b/HACKING
@@ -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
diff --git a/NEWS b/NEWS
index ea31c0da..b8b3e139 100644
--- a/NEWS
+++ b/NEWS
@@ -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();
diff --git a/src/main.c b/src/main.c
index 33fc69b0..0e5a3604 100644
--- a/src/main.c
+++ b/src/main.c
@@ -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
+
diff --git a/src/tag.c b/src/tag.c
index aba6d7a1..7ace8f86 100644
--- a/src/tag.c
+++ b/src/tag.c
@@ -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");
+}
+
diff --git a/src/tag.h b/src/tag.h
index debcc22d..752b71f0 100644
--- a/src/tag.h
+++ b/src/tag.h
@@ -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);