/** 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 "globals.h" #include "ipc-protocol.h" #include #include #include typedef struct { char* name; HSObject* child; } HSObjectChild; static void hsobjectchild_destroy(HSObjectChild* oc); static HSObjectChild* hsobjectchild_create(const char* name, HSObject* obj); static void hsattribute_free(HSAttribute* attr); static HSObject g_root_object; static HSObject* g_tmp_object; void object_tree_init() { hsobject_init(&g_root_object); g_tmp_object = hsobject_create_and_link(&g_root_object, TMP_OBJECT_PATH); } void object_tree_destroy() { hsobject_unlink_and_destroy(&g_root_object, g_tmp_object); 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) { for (int i = 0; i < obj->attribute_count; i++) { hsattribute_free(obj->attributes + i); } g_free(obj->attributes); g_list_free_full(obj->children, (GDestroyNotify)hsobjectchild_destroy); } static void hsattribute_free(HSAttribute* attr) { if (attr->user_data) { g_free((char*)attr->name); if (attr->type == HSATTR_TYPE_STRING) { g_string_free(attr->user_data->str, true); } g_free(attr->user_data); } if (attr->unparsed_value) { g_string_free(attr->unparsed_value, true); } } 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, const 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); } static HSObjectChild* hsobjectchild_create(const char* name, HSObject* obj) { HSObjectChild* oc = g_new(HSObjectChild, 1); oc->name = g_strdup(name); oc->child = obj; return oc; } static void hsobjectchild_destroy(HSObjectChild* oc) { if (!oc) return; g_free((char*)oc->name); g_free(oc); } struct HSObjectComplChild { const char* needle; const 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, const char* needle, const 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, bool user_only, const char* needle, const char* prefix, GString* output) { for (int i = 0; i < obj->attribute_count; i++) { HSAttribute* attr = obj->attributes + i; if (user_only && !attr->user_attribute) { // do not complete default attributes if user_only is set continue; } 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, const 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, const 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, const 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, const 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, const char* name) { for (int i = 0; i < obj->attribute_count; i++) { if (!strcmp(name, obj->attributes[i].name)) { return obj->attributes + i; } } return NULL; } void hsobject_set_attributes_always_callback(HSObject* obj) { for (int i = 0; i < obj->attribute_count; i++) { obj->attributes[i].always_callback = true; } } 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_COLOR: g_string_append_printf(output, "%s", attribute->unparsed_value->str); break; case HSATTR_TYPE_CUSTOM: attribute->value.custom(attribute->data ? attribute->data : attribute->object->data, output); break; case HSATTR_TYPE_CUSTOM_INT: g_string_append_printf(output, "%d", attribute->value.custom_int(attribute->data ? attribute->data : 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) { const char* path = (argc < 2) ? "" : argv[1]; const 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, "%zu 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 = hsattribute_is_read_only(a) ? '-' : '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, /* .child_count = */ object_child_count, /* .append_caption = */ object_append_caption, /* .data = */ (HSTree) g_list_nth_data(oc->child->children, idx), /* .destructor = */ NULL, }; return intf; } HSObject* hsobject_by_path(char* path) { const char* unparsable; HSObject* obj = hsobject_parse_path(path, &unparsable); if (!strcmp("", unparsable)) { return obj; } else { // an invalid path was given if it was not parsed entirely return NULL; } } HSObject* hsobject_parse_path_verbose(const char* path, const char** unparsable, GString* output) { const char* origpath = path; char* pathdup = strdup(path); char* curname = pathdup; const 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(const char* path, const char** unparsable) { return hsobject_parse_path_verbose(path, unparsable, NULL); } HSAttribute* hsattribute_parse_path_verbose(const char* path, GString* output) { GString* object_error = g_string_new(""); HSAttribute* attr; const 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; } HSAttribute* hsattribute_parse_path(const char* path) { GString* out = g_string_new(""); HSAttribute* attr = hsattribute_parse_path_verbose(path, out); if (!attr) { HSError("Cannot parse %s: %s", path, out->str); } g_string_free(out, true); return attr; } int print_object_tree_command(int argc, char* argv[], GString* output) { const char* unparsable; const char* path = (argc < 2) ? "" : argv[1]; HSObjectChild oc = { (char*)path, hsobject_parse_path_verbose(path, &unparsable, output), }; if (strcmp("", unparsable)) { return HERBST_INVALID_ARGUMENT; } HSTreeInterface intf = { /* .nth_child = */ object_nth_child, /* .child_count = */ object_child_count, /* .append_caption = */ object_append_caption, /* .data = */ &oc, /* .destructor = */ NULL, }; 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, const 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); } bool hsattribute_is_read_only(HSAttribute* attr) { bool custom = attr->type == HSATTR_TYPE_CUSTOM || attr->type == HSATTR_TYPE_CUSTOM_INT; assert(!(custom && attr->on_change)); if (custom) return attr->change_custom == NULL; else return attr->on_change == NULL; } int hsattribute_assign(HSAttribute* attr, const char* new_value_str, GString* output) { if (hsattribute_is_read_only(attr)) { g_string_append_printf(output, "Can not write read-only attribute \"%s\"\n", attr->name); return HERBST_FORBIDDEN; } bool error = false; HSAttributeValue 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); \ } else { \ 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_COLOR: error = !getcolor_error(new_value_str, &new_value.color); if (error) { g_string_append_printf(output, "\"%s\" is not a valid color.", new_value_str); break; } if (!strcmp(new_value_str, (attr->unparsed_value)->str)) { nothing_to_do = true; } else { old_value.color = *attr->value.color; *attr->value.color = new_value.color; } break; case HSATTR_TYPE_CUSTOM: break; case HSATTR_TYPE_CUSTOM_INT: break; } if (error) { return HERBST_INVALID_ARGUMENT; } if (attr->always_callback) { nothing_to_do = false; // pretend that there was a change } if (nothing_to_do) { return 0; } GString* old_unparsed_value = attr->unparsed_value; if (attr->unparsed_value) attr->unparsed_value = g_string_new(new_value_str); // ask the attribute about the change GString* errormsg = NULL; if (attr->on_change) errormsg = attr->on_change(attr); else errormsg = attr->change_custom(attr, new_value_str); 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 if (old_unparsed_value) { g_string_free(attr->unparsed_value, true); attr->unparsed_value = old_unparsed_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_COLOR: *attr->value.color = old_value.color; break; 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_COLOR: g_string_assign(attr->unparsed_value, new_value_str); break; case HSATTR_TYPE_CUSTOM: break; case HSATTR_TYPE_CUSTOM_INT: break; } if (old_unparsed_value) { g_string_free(old_unparsed_value, true); } 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); char* repl = attribute_string->str; (void) SHIFT(argc, argv); // remove command name (void) SHIFT(argc, argv); // remove identifier (void) SHIFT(argc, argv); // remove attribute int status = call_command_substitute(identifier, repl, argc, argv, output); 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->data ? attr->data : attr->object->data); break; default: return HERBST_UNKNOWN_ERROR; break; } struct { const 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 if (attr->type == HSATTR_TYPE_COLOR) { HSColor l = *attr->value.color; HSColor r = getcolor(rvalue); if (!strcmp("=", op)) return !(l == r); if (!strcmp("!=", op)) return !(l != r); g_string_append_printf(output, "Invalid color 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->data ? attr->data : 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'; case HSATTR_TYPE_COLOR: return 'c'; } return '?'; } int userattribute_command(int argc, char* argv[], GString* output) { if (argc < 3) { return HERBST_NEED_MORE_ARGS; } char* type_str = argv[1]; char* path = argv[2]; const char* unparsable; GString* errormsg = g_string_new(""); HSObject* obj = hsobject_parse_path_verbose(path, &unparsable, errormsg); if (obj == NULL || strchr(unparsable, OBJECT_PATH_SEPARATOR) != NULL) { g_string_append(output, errormsg->str); g_string_free(errormsg, true); return HERBST_INVALID_ARGUMENT; } else { g_string_free(errormsg, true); } // check for an already existing attribute if (hsobject_find_attribute(obj, unparsable)) { g_string_append_printf(output, "Error: an attribute called \"%s\" already exists\n", unparsable); return HERBST_FORBIDDEN; } // do not check for children with that name, because they must not start // with the USER_ATTRIBUTE_PREFIX. // now create a new attribute named unparsable at obj const char* prefix = USER_ATTRIBUTE_PREFIX; if (strncmp(unparsable, prefix, strlen(prefix))) { g_string_append(output, "Error: the name of user attributes has to "); g_string_append_printf(output, "start with \"%s\" but yours is \"%s\"\n", prefix, unparsable); return HERBST_INVALID_ARGUMENT; } HSAttribute* attr = hsattribute_create(obj, unparsable, type_str, output); if (!attr) { return HERBST_INVALID_ARGUMENT; } attr->user_attribute = true; return 0; } HSAttribute* hsattribute_create(HSObject* obj, const char* name, char* type_str, GString* output) { struct { const char* name; int type; } types[] = { { "bool", HSATTR_TYPE_BOOL }, { "uint", HSATTR_TYPE_UINT }, { "int", HSATTR_TYPE_INT }, { "string", HSATTR_TYPE_STRING }, { "color", HSATTR_TYPE_COLOR }, }; int type = -1; for (int i = 0; i < LENGTH(types); i++) { if (!strcmp(type_str, types[i].name)) { type = types[i].type; break; } } if (type < 0) { g_string_append_printf(output, "Unknown attribute type \"%s\"\n", type_str); return NULL; } size_t count = obj->attribute_count + 1; obj->attributes = g_renew(HSAttribute, obj->attributes, count); obj->attribute_count = count; // initialize object HSAttribute* attr = obj->attributes + count - 1; memset(attr, 0, sizeof(*attr)); attr->object = obj; attr->type = (HSAttributeType)type; attr->name = g_strdup(name); attr->on_change = ATTR_ACCEPT_ALL; attr->user_attribute = false; attr->user_data = g_new(HSAttributeValue, 1); switch (type) { case HSATTR_TYPE_BOOL: attr->user_data->b = false; attr->value.b = &attr->user_data->b; break; case HSATTR_TYPE_INT: attr->user_data->i = 0; attr->value.i = &attr->user_data->i; break; case HSATTR_TYPE_UINT: attr->user_data->u = 0; attr->value.u = &attr->user_data->u; break; case HSATTR_TYPE_STRING: attr->user_data->str = g_string_new(""); attr->value.str = &attr->user_data->str; break; case HSATTR_TYPE_COLOR: attr->user_data->color = getcolor("#000000"); attr->unparsed_value = g_string_new("#000000"); attr->value.color = &attr->user_data->color; default: break; } return attr; } int userattribute_remove_command(int argc, char* argv[], GString* output) { if (argc < 2) { return HERBST_NEED_MORE_ARGS; } char* path = argv[1]; HSAttribute* attr = hsattribute_parse_path_verbose(path, output); if (!attr) { return HERBST_INVALID_ARGUMENT; } if (!attr->user_attribute) { g_string_append_printf(output, "Can only user-defined attributes, " "but \"%s\" is not user-defined.\n", path); return HERBST_FORBIDDEN; } return userattribute_remove(attr) ? 0 : HERBST_UNKNOWN_ERROR; } bool userattribute_remove(HSAttribute* attr) { HSObject* obj = attr->object; int idx = attr - obj->attributes; if (idx < 0 || idx >= obj->attribute_count) { fprintf(stderr, "Assertion 0 <= idx < count failed.\n"); return false; } hsattribute_free(attr); // remove it from buf size_t count = obj->attribute_count - 1; size_t bytes = (count - idx) * sizeof(HSAttribute); memmove(obj->attributes + idx, obj->attributes + idx + 1, bytes); obj->attributes = g_renew(HSAttribute, obj->attributes, count); obj->attribute_count = count; return 0; } #define FORMAT_CHAR '%' int sprintf_command(int argc, char* argv[], GString* output) { // usage: sprintf IDENTIFIER FORMAT [Params...] COMMAND [ARGS ...] if (argc < 4) { return HERBST_NEED_MORE_ARGS; } char* identifier = argv[1]; char* format = argv[2]; (void) SHIFT(argc, argv); (void) SHIFT(argc, argv); (void) SHIFT(argc, argv); GString* repl = g_string_new(""); int nextarg = 0; // next argument to consider for (int i = 0; format[i] != '\0'; i++) { if (format[i] == FORMAT_CHAR) { // FORMAT_CHAR is our format character // '%' is the printf format character switch (format[i+1]) { case FORMAT_CHAR: g_string_append(repl, "%%"); break; case 's': { if (nextarg >= (argc - 1)) { g_string_append_printf(output, "Error: Too few parameters. A %dth parameter missing. " "(treating \"%s\" as the command to execute)\n", nextarg, argv[argc - 1]); g_string_free(repl, true); return HERBST_INVALID_ARGUMENT; } HSAttribute* attr; attr = hsattribute_parse_path_verbose(argv[nextarg], output); if (!attr) { g_string_free(repl, true); return HERBST_INVALID_ARGUMENT; } GString* gs = hsattribute_to_string(attr); g_string_append(repl, gs->str); g_string_free(gs, true); nextarg++; break; } default: g_string_append_printf(output, "Error: unknown format specifier \'%c\' in format " "\"%s\" at position %d\n", format[i+1] ? format[i+1] : '?', format, i); g_string_free(repl, true); return HERBST_INVALID_ARGUMENT; break; } i++; } else { g_string_append_c(repl, format[i]); } } int cmdc = argc - nextarg; char** cmdv = argv + nextarg; int status; status = call_command_substitute(identifier, repl->str, cmdc, cmdv, output); g_string_free(repl, true); return status; } int tmpattribute_command(int argc, char* argv[], GString* output) { // usage: tmp type IDENTIFIER COMMAND [ARGS...] if (argc < 4) { return HERBST_NEED_MORE_ARGS; } static int tmpcount = 0; tmpcount++; char* name = g_strdup_printf("%stmp%d", USER_ATTRIBUTE_PREFIX, tmpcount); // attr may change, so only remember the object HSAttribute* attr = hsattribute_create(g_tmp_object, name, argv[1], output); if (!attr) { tmpcount--; g_free(name); return HERBST_INVALID_ARGUMENT; } HSObject* obj = attr->object; char* path = g_strdup_printf("%s%c%s", TMP_OBJECT_PATH, OBJECT_PATH_SEPARATOR, name); int status = call_command_substitute(argv[2], path, argc - 3, argv + 3, output); userattribute_remove(hsobject_find_attribute(obj, name)); g_free(name); g_free(path); tmpcount--; return status; }