├── .gitignore ├── .gitmodules ├── .travis.valgrind.supp ├── .travis.yml ├── COPYING ├── Doxyfile ├── Makefile.am ├── README ├── autogen.sh ├── configure.ac ├── include ├── database.h ├── entry.h ├── externals.h ├── match.h ├── resource.h ├── util.h └── xcb_xrm.h ├── src ├── database.c ├── entry.c ├── match.c ├── resource.c └── util.c ├── tests ├── resources │ ├── 1 │ │ ├── sub │ │ │ └── xresources3 │ │ ├── xresources1 │ │ └── xresources2 │ ├── 2 │ │ ├── .Xresources │ │ └── xenvironment │ └── 3 │ │ └── loop.xresources ├── tests_database.c ├── tests_database_runner.sh ├── tests_match.c ├── tests_parser.c ├── tests_utils.c └── tests_utils.h ├── xcb-xrm.pc.in └── xcb_xrm_intro.in /.gitignore: -------------------------------------------------------------------------------- 1 | # 2 | # X.Org module default exclusion patterns 3 | # The next section if for module specific patterns 4 | # 5 | # Do not edit the following section 6 | # GNU Build System (Autotools) 7 | aclocal.m4 8 | autom4te.cache/ 9 | autoscan.log 10 | ChangeLog 11 | compile 12 | config.guess 13 | config.h 14 | config.h.in 15 | config.log 16 | config-ml.in 17 | config.py 18 | config.status 19 | config.status.lineno 20 | config.sub 21 | configure 22 | configure.scan 23 | depcomp 24 | .deps/ 25 | INSTALL 26 | install-sh 27 | .libs/ 28 | libtool 29 | libtool.m4 30 | ltmain.sh 31 | lt~obsolete.m4 32 | ltoptions.m4 33 | ltsugar.m4 34 | ltversion.m4 35 | Makefile 36 | Makefile.in 37 | mdate-sh 38 | missing 39 | mkinstalldirs 40 | *.pc 41 | py-compile 42 | stamp-h? 43 | symlink-tree 44 | texinfo.tex 45 | ylwrap 46 | 47 | # Do not edit the following section 48 | # Edit Compile Debug Document Distribute 49 | *~ 50 | *.[0-9] 51 | *.[0-9]x 52 | *.bak 53 | *.bin 54 | core 55 | *.dll 56 | *.exe 57 | *-ISO*.bdf 58 | *-JIS*.bdf 59 | *-KOI8*.bdf 60 | *.kld 61 | *.ko 62 | *.ko.cmd 63 | *.lai 64 | *.l[oa] 65 | *.[oa] 66 | *.obj 67 | *.patch 68 | *.so 69 | *.pcf.gz 70 | *.pdb 71 | *.tar.bz2 72 | *.tar.gz 73 | # 74 | # Add & Override patterns for xinit 75 | # 76 | # Edit the following section as needed 77 | # For example, !report.pc overrides *.pc. See 'man gitignore' 78 | # 79 | xcb_xrm_intro 80 | vgcore.* 81 | test-driver 82 | test-suite.log 83 | *.dirstamp 84 | tests/tests_parser 85 | tests/tests_match 86 | tests/tests_database 87 | tests/*.log 88 | tests/*.trs 89 | documentation/* 90 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "m4"] 2 | path = m4 3 | url = git://anongit.freedesktop.org/xcb/util-common-m4.git 4 | -------------------------------------------------------------------------------- /.travis.valgrind.supp: -------------------------------------------------------------------------------- 1 | # http://valgrind.org/docs/manual/manual-core.html#manual-core.suppress 2 | 3 | # XrmGetStringDatabase 4 | { 5 | Ignore XrmGetStringDatabase leak 6 | Memcheck:Leak 7 | ... 8 | fun:XrmGetStringDatabase 9 | ... 10 | } 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | compiler: 3 | - gcc 4 | addons: 5 | apt: 6 | packages: 7 | - libxcb-util0-dev 8 | - libxcb1-dev 9 | - libx11-xcb-dev 10 | - xutils-dev 11 | - xvfb 12 | - valgrind 13 | script: 14 | - "git submodule update --init && ./autogen.sh --prefix=/usr" 15 | - "make all" 16 | - "make check" 17 | - "sleep 1" 18 | - "valgrind --leak-check=full -q --error-exitcode=1 --suppressions=./.travis.valgrind.supp tests/.libs/lt-tests_parser" 19 | - "valgrind --leak-check=full -q --error-exitcode=1 --suppressions=./.travis.valgrind.supp tests/.libs/lt-tests_match" 20 | - "xvfb-run valgrind --leak-check=full -q --error-exitcode=1 --suppressions=./.travis.valgrind.supp tests/.libs/lt-tests_database" 21 | - "sleep 1" 22 | - "make distcheck" 23 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright © 2016 Ingo Bürk 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a 4 | copy of this software and associated documentation files (the "Software"), 5 | to deal in the Software without restriction, including without limitation 6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | and/or sell copies of the Software, and to permit persons to whom the 8 | Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 17 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | Except as contained in this notice, the names of the authors or their 21 | institutions shall not be used in advertising or otherwise to promote the 22 | sale, use or other dealings in this Software without prior written 23 | authorization from the authors. 24 | -------------------------------------------------------------------------------- /Makefile.am: -------------------------------------------------------------------------------- 1 | ACLOCAL_AMFLAGS = -I m4 2 | 3 | MAINTAINERCLEANFILES = ChangeLog INSTALL Makefile.in 4 | 5 | .PHONY: ChangeLog INSTALL 6 | 7 | INSTALL: 8 | $(INSTALL_CMD) 9 | 10 | ChangeLog: 11 | $(CHANGELOG_CMD) 12 | 13 | dist-hook: ChangeLog INSTALL 14 | 15 | EXTRA_DIST = autogen.sh xcb-xrm.pc.in include/xcb_xrm.h include/database.h 16 | EXTRA_DIST += include/entry.h include/externals.h include/match.h 17 | EXTRA_DIST += include/resource.h include/util.h 18 | EXTRA_DIST += tests/tests_utils.h tests/tests_database_runner.sh 19 | EXTRA_DIST += tests/resources/1/xresources1 tests/resources/1/xresources2 20 | EXTRA_DIST += tests/resources/1/sub/xresources3 21 | EXTRA_DIST += tests/resources/2/xenvironment tests/resources/2/.Xresources 22 | EXTRA_DIST += tests/resources/3/loop.xresources 23 | 24 | lib_LTLIBRARIES = libxcb-xrm.la 25 | 26 | xcbinclude_HEADERS = include/xcb_xrm.h 27 | 28 | AM_CFLAGS = $(CWARNFLAGS) 29 | 30 | libxcb_xrm_la_SOURCES = src/database.c src/resource.c src/entry.c src/match.c src/util.c 31 | libxcb_xrm_la_CPPFLAGS = -I$(srcdir)/include/ $(XCB_CFLAGS) $(XCB_AUX_CFLAGS) 32 | libxcb_xrm_la_LIBADD = $(XCB_LIBS) $(XCB_AUX_LIBS) -lm 33 | libxcb_xrm_la_LDFLAGS = -version-info 0:0:0 -no-undefined -export-symbols-regex '^xcb_xrm_' 34 | 35 | pkgconfig_DATA = xcb-xrm.pc 36 | 37 | DIRECT_TESTS = tests/tests_parser tests/tests_match 38 | TESTS = $(DIRECT_TESTS) tests/tests_database_runner.sh 39 | check_PROGRAMS = $(DIRECT_TESTS) tests/tests_database 40 | 41 | tests_tests_parser_SOURCES = tests/tests_utils.c tests/tests_parser.c 42 | tests_tests_parser_CPPFLAGS = -I$(srcdir)/include/ 43 | tests_tests_parser_LDADD = libxcb-xrm.la 44 | 45 | tests_tests_match_SOURCES = tests/tests_utils.c tests/tests_match.c 46 | tests_tests_match_CPPFLAGS = -I$(srcdir)/include/ $(XLIB_CFLAGS) 47 | tests_tests_match_LDADD = libxcb-xrm.la $(XLIB_LIBS) 48 | 49 | tests_tests_database_SOURCES = tests/tests_utils.c tests/tests_database.c 50 | tests_tests_database_CPPFLAGS = -I$(srcdir)/include/ $(XCB_CFLAGS) $(XCB_AUX_CFLAGS) 51 | tests_tests_database_LDADD = libxcb-xrm.la $(XCB_LIBS) $(XCB_AUX_LIBS) 52 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | About XCB util modules 2 | ====================== 3 | 4 | The XCB util modules provides a number of libraries which sit on top 5 | of libxcb, the core X protocol library, and some of the extension 6 | libraries. These experimental libraries provide convenience functions 7 | and interfaces which make the raw X protocol more usable. Some of the 8 | libraries also provide client-side code which is not strictly part of 9 | the X protocol but which have traditionally been provided by Xlib. 10 | 11 | If you find any of these libraries useful, please let us know what 12 | you're using and why you aren't in a mental hospital yet. We'd welcome 13 | patches/suggestions for enhancement and new libraries; Please report any 14 | issues you find to the freedesktop.org bug tracker, at: 15 | 16 | 17 | 18 | Discussion about XCB occurs on the XCB mailing list: 19 | 20 | 21 | 22 | 23 | About XCB util-xrm module 24 | ========================= 25 | 26 | XCB util-xrm module provides the following libraries: 27 | 28 | - xrm: utility functions for the X resource manager 29 | 30 | You can obtain the latest development versions of XCB util-xrm using 31 | GIT. Use 32 | 33 | git clone --recursive https://github.com/Airblader/xcb-util-xrm.git 34 | -------------------------------------------------------------------------------- /autogen.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | srcdir=`dirname $0` 4 | test -z "$srcdir" && srcdir=. 5 | 6 | ORIGDIR=`pwd` 7 | cd $srcdir 8 | 9 | # If this is a git checkout, verify that the submodules are initialized, 10 | # otherwise autotools will just fail with an unhelpful error message. 11 | if [ -d ".git" ] && [ -r ".gitmodules" ] 12 | then 13 | # If git is not in PATH, this will not return 0, thus not keeping us 14 | # from building. Since the message is worthless when git is not 15 | # installed, this is what we want. 16 | if git submodule status 2>/dev/null | grep -q '^-' 17 | then 18 | echo "You have uninitialized git submodules." >&2 19 | echo "Please run: git submodule update --init" >&2 20 | exit 1 21 | fi 22 | fi 23 | 24 | autoreconf -v --install || exit 1 25 | cd $ORIGDIR || exit $? 26 | 27 | if test -z "$NOCONFIGURE"; then 28 | $srcdir/configure --enable-maintainer-mode "$@" 29 | fi 30 | -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | dnl XCB_UTIL_M4_WITH_INCLUDE_PATH requires Autoconf >= 2.62 2 | AC_PREREQ(2.62) 3 | AC_INIT([xcb-util-xrm],1.3,[xcb@lists.freedesktop.org]) 4 | AC_CONFIG_SRCDIR([Makefile.am]) 5 | AC_CONFIG_MACRO_DIR([m4]) 6 | 7 | # Set common system defines for POSIX extensions, such as _GNU_SOURCE 8 | # Must be called before any macros that run the compiler (like AC_PROG_LIBTOOL) 9 | # to avoid autoconf errors. 10 | AC_USE_SYSTEM_EXTENSIONS 11 | 12 | AM_INIT_AUTOMAKE([foreign dist-bzip2 subdir-objects]) 13 | AM_MAINTAINER_MODE 14 | 15 | XCB_UTIL_M4_WITH_INCLUDE_PATH 16 | XCB_UTIL_COMMON([1.4], [1.6]) 17 | 18 | PKG_CHECK_MODULES(XCB_AUX, xcb-aux) 19 | PKG_CHECK_MODULES(XLIB, x11) 20 | 21 | AC_OUTPUT([Makefile 22 | xcb-xrm.pc 23 | xcb_xrm_intro 24 | ]) 25 | -------------------------------------------------------------------------------- /include/database.h: -------------------------------------------------------------------------------- 1 | /* 2 | * vim:ts=4:sw=4:expandtab 3 | * 4 | * Copyright © 2016 Ingo Bürk 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a 7 | * copy of this software and associated documentation files (the "Software"), 8 | * to deal in the Software without restriction, including without limitation 9 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | * and/or sell copies of the Software, and to permit persons to whom the 11 | * Software is furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 20 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | * 23 | * Except as contained in this notice, the names of the authors or their 24 | * institutions shall not be used in advertising or otherwise to promote the 25 | * sale, use or other dealings in this Software without prior written 26 | * authorization from the authors. 27 | * 28 | */ 29 | #ifndef __DATABASE_H__ 30 | #define __DATABASE_H__ 31 | 32 | #include "externals.h" 33 | 34 | #include "xcb_xrm.h" 35 | #include "entry.h" 36 | 37 | TAILQ_HEAD(xcb_xrm_database_t, xcb_xrm_entry_t); 38 | 39 | #endif /* __DATABASE_H__ */ 40 | -------------------------------------------------------------------------------- /include/entry.h: -------------------------------------------------------------------------------- 1 | /* 2 | * vim:ts=4:sw=4:expandtab 3 | * 4 | * Copyright © 2016 Ingo Bürk 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a 7 | * copy of this software and associated documentation files (the "Software"), 8 | * to deal in the Software without restriction, including without limitation 9 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | * and/or sell copies of the Software, and to permit persons to whom the 11 | * Software is furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 20 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | * 23 | * Except as contained in this notice, the names of the authors or their 24 | * institutions shall not be used in advertising or otherwise to promote the 25 | * sale, use or other dealings in this Software without prior written 26 | * authorization from the authors. 27 | * 28 | */ 29 | #ifndef __ENTRY_H__ 30 | #define __ENTRY_H__ 31 | 32 | /** Defines where the parser is currently at. */ 33 | typedef enum { 34 | /* Reading initial workspace before anything else. */ 35 | CS_INITIAL = 0, 36 | /* Reading the resource path. */ 37 | CS_COMPONENTS = 1, 38 | /* Reading whitespace between ':' and the value. */ 39 | CS_PRE_VALUE_WHITESPACE = 2, 40 | /* Reading the resource's value. */ 41 | CS_VALUE = 3 42 | } xcb_xrm_entry_parser_chunk_status_t; 43 | 44 | /** Specifies the type of a component. */ 45 | typedef enum { 46 | /* A "normal" component, i.e., a name/class is given. */ 47 | CT_NORMAL = 0, 48 | /* A wildcard component ("?"). */ 49 | CT_WILDCARD = 1, 50 | } xcb_xrm_component_type_t; 51 | 52 | /** The binding type of a component. */ 53 | typedef enum { 54 | BT_TIGHT = 0, 55 | BT_LOOSE = 1 56 | } xcb_xrm_binding_type_t; 57 | 58 | /** One component of a resource, either in the name or class. */ 59 | typedef struct xcb_xrm_component_t { 60 | /* The type of this component. */ 61 | xcb_xrm_component_type_t type; 62 | /* The binding type of this component. */ 63 | xcb_xrm_binding_type_t binding_type; 64 | /* This component's name. Only useful if the type is CT_NORMAL. */ 65 | char *name; 66 | 67 | TAILQ_ENTRY(xcb_xrm_component_t) components; 68 | } xcb_xrm_component_t; 69 | 70 | /** Used in xcb_xrm_entry_parse. */ 71 | typedef struct xcb_xrm_entry_parser_state_t { 72 | xcb_xrm_entry_parser_chunk_status_t chunk; 73 | char *buffer; 74 | char *buffer_pos; 75 | xcb_xrm_binding_type_t current_binding_type; 76 | } xcb_xrm_entry_parser_state_t; 77 | 78 | /** 79 | * Parsed structure for a single entry in the xrm database, e.g. representing 80 | * the parsted state of 81 | * Application*class?subclass.resource. 82 | */ 83 | typedef struct xcb_xrm_entry_t { 84 | /* The value of this entry. */ 85 | char *value; 86 | 87 | /* The individual components making up this entry. */ 88 | TAILQ_HEAD(components_head, xcb_xrm_component_t) components; 89 | 90 | TAILQ_ENTRY(xcb_xrm_entry_t) entries; 91 | } xcb_xrm_entry_t; 92 | 93 | /** 94 | * Parses a specific resource string. 95 | * 96 | * @param str The resource string. 97 | * @param entry A return struct that will contain the parsed resource. The 98 | * memory will be allocated dynamically, so it must be freed. 99 | * @param resource_only If true, no wildcards are allowed and only a resource 100 | * name is parsed. 101 | * 102 | * @return 0 on success, a negative error code otherwise. 103 | * 104 | */ 105 | int xcb_xrm_entry_parse(const char *str, xcb_xrm_entry_t **entry, bool resource_only); 106 | 107 | /** 108 | * Returns the number of components of the given entry. 109 | * 110 | */ 111 | int __xcb_xrm_entry_num_components(xcb_xrm_entry_t *entry); 112 | 113 | /** 114 | * Compares the two entries. 115 | * Returns 0 if they are the same and a negative error code otherwise. 116 | * 117 | */ 118 | int __xcb_xrm_entry_compare(xcb_xrm_entry_t *first, xcb_xrm_entry_t *second); 119 | 120 | /** 121 | * Returns a string representation of this entry. 122 | * 123 | */ 124 | char *__xcb_xrm_entry_to_string(xcb_xrm_entry_t *entry); 125 | 126 | /** 127 | * Copy the entry. 128 | * 129 | */ 130 | xcb_xrm_entry_t *__xcb_xrm_entry_copy(xcb_xrm_entry_t *entry); 131 | 132 | /** 133 | * Escapes magic values. 134 | * 135 | */ 136 | char *__xcb_xrm_entry_escape_value(const char *value); 137 | 138 | /** 139 | * Frees the given entry. 140 | * 141 | * @param entry The entry to be freed. 142 | * 143 | */ 144 | void xcb_xrm_entry_free(xcb_xrm_entry_t *entry); 145 | 146 | #endif /* __ENTRY_H__ */ 147 | -------------------------------------------------------------------------------- /include/externals.h: -------------------------------------------------------------------------------- 1 | /* 2 | * vim:ts=4:sw=4:expandtab 3 | * 4 | * Copyright © 2016 Ingo Bürk 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a 7 | * copy of this software and associated documentation files (the "Software"), 8 | * to deal in the Software without restriction, including without limitation 9 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | * and/or sell copies of the Software, and to permit persons to whom the 11 | * Software is furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 20 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | * 23 | * Except as contained in this notice, the names of the authors or their 24 | * institutions shall not be used in advertising or otherwise to promote the 25 | * sale, use or other dealings in this Software without prior written 26 | * authorization from the authors. 27 | * 28 | */ 29 | #ifndef __EXTERNALS_H__ 30 | #define __EXTERNALS_H__ 31 | 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | 49 | #include 50 | #include 51 | 52 | #endif /* __EXTERNALS_H__ */ 53 | -------------------------------------------------------------------------------- /include/match.h: -------------------------------------------------------------------------------- 1 | /* 2 | * vim:ts=4:sw=4:expandtab 3 | * 4 | * Copyright © 2016 Ingo Bürk 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a 7 | * copy of this software and associated documentation files (the "Software"), 8 | * to deal in the Software without restriction, including without limitation 9 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | * and/or sell copies of the Software, and to permit persons to whom the 11 | * Software is furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 20 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | * 23 | * Except as contained in this notice, the names of the authors or their 24 | * institutions shall not be used in advertising or otherwise to promote the 25 | * sale, use or other dealings in this Software without prior written 26 | * authorization from the authors. 27 | * 28 | */ 29 | #ifndef __MATCH_H__ 30 | #define __MATCH_H__ 31 | 32 | #include "database.h" 33 | #include "resource.h" 34 | #include "entry.h" 35 | 36 | /** Information about a matched component. */ 37 | typedef enum xcb_xrm_match_flags_t { 38 | MF_NONE = 1 << 0, 39 | 40 | /* The component was matched on the name. */ 41 | MF_NAME = 1 << 1, 42 | /* The component was matched on the class. */ 43 | MF_CLASS = 1 << 2, 44 | /* The component was matched via a '?' wildcard. */ 45 | MF_WILDCARD = 1 << 3, 46 | /* The component was matched as part of a loose binding. */ 47 | MF_SKIPPED = 1 << 4, 48 | 49 | /* This component was preceded by a loose binding. */ 50 | MF_PRECEDING_LOOSE = 1 << 5, 51 | } xcb_xrm_match_flags_t; 52 | 53 | /** 54 | * Helper enum to decide whether a component in a loose binding shall be 55 | * skipped even if it matches. 56 | */ 57 | typedef enum xcb_xrm_match_ignore_t { 58 | MI_UNDECIDED, 59 | MI_IGNORE, 60 | MI_DO_NOT_IGNORE, 61 | } xcb_xrm_match_ignore_t; 62 | 63 | typedef struct xcb_xrm_match_t { 64 | /* Reference to the database entry this match refers to. */ 65 | xcb_xrm_entry_t *entry; 66 | /* An array where the n-th element describes how the n-th element of the 67 | * query strings was matched. */ 68 | xcb_xrm_match_flags_t *flags; 69 | } xcb_xrm_match_t; 70 | 71 | /** 72 | * Finds the matching entry in the database given a full name / class query string. 73 | * 74 | */ 75 | int __xcb_xrm_match(xcb_xrm_database_t *database, xcb_xrm_entry_t *query_name, xcb_xrm_entry_t *query_class, 76 | xcb_xrm_resource_t *resource); 77 | 78 | #endif /* __MATCH_H__ */ 79 | -------------------------------------------------------------------------------- /include/resource.h: -------------------------------------------------------------------------------- 1 | /* 2 | * vim:ts=4:sw=4:expandtab 3 | * 4 | * Copyright © 2016 Ingo Bürk 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a 7 | * copy of this software and associated documentation files (the "Software"), 8 | * to deal in the Software without restriction, including without limitation 9 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | * and/or sell copies of the Software, and to permit persons to whom the 11 | * Software is furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 20 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | * 23 | * Except as contained in this notice, the names of the authors or their 24 | * institutions shall not be used in advertising or otherwise to promote the 25 | * sale, use or other dealings in this Software without prior written 26 | * authorization from the authors. 27 | * 28 | */ 29 | #ifndef __RESOURCE_H__ 30 | #define __RESOURCE_H__ 31 | 32 | #include "externals.h" 33 | 34 | #include "xcb_xrm.h" 35 | #include "util.h" 36 | #include "entry.h" 37 | 38 | typedef struct xcb_xrm_resource_t { 39 | char *value; 40 | } xcb_xrm_resource_t; 41 | 42 | #endif /* __RESOURCE_H__ */ 43 | -------------------------------------------------------------------------------- /include/util.h: -------------------------------------------------------------------------------- 1 | /* 2 | * vim:ts=4:sw=4:expandtab 3 | * 4 | * Copyright © 2016 Ingo Bürk 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a 7 | * copy of this software and associated documentation files (the "Software"), 8 | * to deal in the Software without restriction, including without limitation 9 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | * and/or sell copies of the Software, and to permit persons to whom the 11 | * Software is furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 20 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | * 23 | * Except as contained in this notice, the names of the authors or their 24 | * institutions shall not be used in advertising or otherwise to promote the 25 | * sale, use or other dealings in this Software without prior written 26 | * authorization from the authors. 27 | * 28 | */ 29 | #ifndef __UTIL_H__ 30 | #define __UTIL_H__ 31 | 32 | #include "externals.h" 33 | 34 | #define FREE(p) \ 35 | do { \ 36 | free(p); \ 37 | p = NULL; \ 38 | } while (0) 39 | 40 | #undef MAX 41 | #define MAX(x, y) ((x) > (y) ? (x) : (y)) 42 | #undef MIN 43 | #define MIN(x, y) ((x) < (y) ? (x) : (y)) 44 | 45 | #define SUCCESS 0 46 | #define FAILURE 1 47 | 48 | int str2long(long *out, const char *input, const int base); 49 | 50 | char *get_home_dir_file(const char *filename); 51 | 52 | char *resolve_path(const char *path, const char *base); 53 | 54 | char *file_get_contents(const char *filename); 55 | 56 | char *xcb_util_get_property(xcb_connection_t *conn, xcb_window_t window, xcb_atom_t atom, 57 | xcb_atom_t type, size_t size); 58 | 59 | #endif /* __UTIL_H__ */ 60 | -------------------------------------------------------------------------------- /include/xcb_xrm.h: -------------------------------------------------------------------------------- 1 | /* 2 | * vim:ts=4:sw=4:expandtab 3 | * 4 | * Copyright © 2016 Ingo Bürk 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a 7 | * copy of this software and associated documentation files (the "Software"), 8 | * to deal in the Software without restriction, including without limitation 9 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | * and/or sell copies of the Software, and to permit persons to whom the 11 | * Software is furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 20 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | * 23 | * Except as contained in this notice, the names of the authors or their 24 | * institutions shall not be used in advertising or otherwise to promote the 25 | * sale, use or other dealings in this Software without prior written 26 | * authorization from the authors. 27 | * 28 | */ 29 | #ifndef __XCB_XRM_H__ 30 | #define __XCB_XRM_H__ 31 | 32 | #include 33 | #include 34 | 35 | #ifdef __cplusplus 36 | extern "C" { 37 | #endif 38 | 39 | /** 40 | * @defgroup xcb_xrm_database_t XCB XRM Functions 41 | * 42 | * These functions are the xcb equivalent of the Xrm* function family in Xlib. 43 | * They allow the parsing and matching of X resources as well as some utility 44 | * functions. 45 | * 46 | * Here is an example of how this library can be used to retrieve a 47 | * user-configured resource: 48 | * @code 49 | * xcb_connection_t *conn = xcb_connect(NULL, &screennr); 50 | * if (conn == NULL || xcb_connection_has_error(conn)) 51 | * err(EXIT_FAILURE, "Could not connect to the X server."); 52 | * 53 | * xcb_xrm_database_t *database = xcb_xrm_database_from_default(conn); 54 | * if (database == NULL) 55 | * err(EXIT_FAILURE, "Could not open database"); 56 | * 57 | * char *value; 58 | * if (xcb_xrm_resource_get_string(database, "Xft.dpi", NULL, &value) >= 0) { 59 | * fprintf(stdout, "Xft.dpi: %s\n", value); 60 | * free(value); 61 | * } 62 | * 63 | * xcb_xrm_database_free(database); 64 | * xcb_disconnect(conn); 65 | * @endcode 66 | * 67 | * @{ 68 | */ 69 | 70 | /** 71 | * @struct xcb_xrm_database_t 72 | * Reference to a database. 73 | * 74 | * The database can be loaded in different ways, e.g., from the 75 | * RESOURCE_MANAGER property by using @ref 76 | * xcb_xrm_database_from_resource_manager (). All queries for a resource go 77 | * against a specific database. A database must always be free'd by using @ref 78 | * xcb_xrm_database_free (). 79 | * 80 | * Note that a database is not thread-safe, i.e., multiple threads should not 81 | * operate on the same database instance. This is especially true for write 82 | * operations on the database. However, you can use this library in a 83 | * multi-threaded application as long as the database is thread-local. 84 | */ 85 | typedef struct xcb_xrm_database_t xcb_xrm_database_t; 86 | 87 | /** 88 | * Creates a database similarly to XGetDefault(). For typical applications, 89 | * this is the recommended way to construct the resource database. 90 | * 91 | * The database is created as follows: 92 | * - If the RESOURCE_MANAGER property exists on the root window of 93 | * screen 0, the database is constructed from it using @ref 94 | * xcb_xrm_database_from_resource_manager(). 95 | * - Otherwise, if $HOME/.Xresources exists, the database is constructed from 96 | * it using @ref xcb_xrm_database_from_file(). 97 | * - Otherwise, if $HOME/.Xdefaults exists, the database is constructed from 98 | * it using @ref xcb_xrm_database_from_file(). 99 | * - If the environment variable XENVIRONMENT is set, the file specified by 100 | * it is loaded using @ref xcb_xrm_database_from_file and then combined with 101 | * the database using @ref xcb_xrm_database_combine() with override set to 102 | * true. 103 | * If XENVIRONMENT is not specified, the same is done with 104 | * $HOME/.Xdefaults-$HOSTNAME, wherein $HOSTNAME is determined by 105 | * gethostname(2). 106 | * 107 | * This represents the way XGetDefault() creates the database for the most 108 | * part, but is not exactly the same. In particular, XGetDefault() does not 109 | * consider $HOME/.Xresources. 110 | * 111 | * @param conn XCB connection. 112 | * @returns The constructed database. Can return NULL, e.g., if the screen 113 | * cannot be determined. 114 | */ 115 | xcb_xrm_database_t *xcb_xrm_database_from_default(xcb_connection_t *conn); 116 | 117 | /** 118 | * Loads the RESOURCE_MANAGER property and creates a database with its 119 | * contents. If the database could not be created, this function will return 120 | * NULL. 121 | * 122 | * @param conn A working XCB connection. 123 | * @param screen The xcb_screen_t* screen to use. 124 | * @returns The database described by the RESOURCE_MANAGER property. 125 | * 126 | * @ingroup xcb_xrm_database_t 127 | */ 128 | xcb_xrm_database_t *xcb_xrm_database_from_resource_manager(xcb_connection_t *conn, xcb_screen_t *screen); 129 | 130 | /** 131 | * Creates a database from the given string. 132 | * If the database could not be created, this function will return NULL. 133 | * 134 | * @param str The resource string. 135 | * @returns The database described by the resource string. 136 | * 137 | * @ingroup xcb_xrm_database_t 138 | */ 139 | xcb_xrm_database_t *xcb_xrm_database_from_string(const char *str); 140 | 141 | /** 142 | * Creates a database from a given file. 143 | * If the file cannot be found or opened, NULL is returned. 144 | * 145 | * @param filename Valid filename. 146 | * @returns The database described by the file's contents. 147 | */ 148 | xcb_xrm_database_t *xcb_xrm_database_from_file(const char *filename); 149 | 150 | /** 151 | * Returns a string representation of a database. 152 | * The string is owned by the caller and must be free'd. 153 | * 154 | * @param database The database to return in string format. 155 | * @returns A string representation of the specified database. 156 | */ 157 | char *xcb_xrm_database_to_string(xcb_xrm_database_t *database); 158 | 159 | /** 160 | * Combines two databases. 161 | * The entries from the source database are stored in the target database. If 162 | * the same specifier already exists in the target database, the value will be 163 | * overridden if override is set; otherwise, the value is discarded. 164 | * If NULL is passed for target_db, a new and empty database will be created 165 | * and returned in the pointer. 166 | * 167 | * @param source_db Source database. 168 | * @param target_db Target database. 169 | * @param override If true, entries from the source database override entries 170 | * in the target database using the same resource specifier. 171 | */ 172 | void xcb_xrm_database_combine(xcb_xrm_database_t *source_db, xcb_xrm_database_t **target_db, bool override); 173 | 174 | /** 175 | * Inserts a new resource into the database. 176 | * If the resource already exists, the current value will be replaced. 177 | * If NULL is passed for database, a new and empty database will be created and 178 | * returned in the pointer. 179 | * 180 | * Note that this is not the equivalent of @ref 181 | * xcb_xrm_database_put_resource_line when concatenating the resource name and 182 | * value with a colon. For example, if the value starts with a leading space, 183 | * this must (and will) be replaced with the special '\ ' sequence. 184 | * 185 | * @param database The database to modify. 186 | * @param resource The fully qualified or partial resource specifier. 187 | * @param value The value of the resource. 188 | */ 189 | void xcb_xrm_database_put_resource(xcb_xrm_database_t **database, const char *resource, const char *value); 190 | 191 | /** 192 | * Inserts a new resource into the database. 193 | * If the resource already exists, the current value will be replaced. 194 | * If NULL is passed for database, a new and empty database will be created and 195 | * returned in the pointer. 196 | * 197 | * @param database The database to modify. 198 | * @param line The complete resource specification to insert. 199 | */ 200 | void xcb_xrm_database_put_resource_line(xcb_xrm_database_t **database, const char *line); 201 | 202 | /** 203 | * Destroys the given database. 204 | * 205 | * @param database The database to destroy. 206 | * 207 | * @ingroup xcb_xrm_database_t 208 | */ 209 | void xcb_xrm_database_free(xcb_xrm_database_t *database); 210 | 211 | /** 212 | * Find the string value of a resource. 213 | * 214 | * Note that the string is owned by the caller and must be free'd. 215 | * 216 | * @param database The database to query. 217 | * @param res_name The fully qualified resource name string. 218 | * @param res_class The fully qualified resource class string. This argument 219 | * may be left empty / NULL, but if given, it must contain the same number of 220 | * components as res_name. 221 | * @param out Out parameter to which the value will be written. 222 | * @returns 0 if the resource was found, a negative error code otherwise. 223 | */ 224 | int xcb_xrm_resource_get_string(xcb_xrm_database_t *database, 225 | const char *res_name, const char *res_class, char **out); 226 | 227 | /** 228 | * Find the long value of a resource. 229 | * 230 | * @param database The database to query. 231 | * @param res_name The fully qualified resource name string. 232 | * @param res_class The fully qualified resource class string. This argument 233 | * may be left empty / NULL, but if given, it must contain the same number of 234 | * components as res_name. 235 | * @param out Out parameter to which the converted value will be written. 236 | * @returns 0 if the resource was found and converted, -1 if the resource was 237 | * found but could not be converted and -2 if the resource was not found. 238 | */ 239 | int xcb_xrm_resource_get_long(xcb_xrm_database_t *database, 240 | const char *res_name, const char *res_class, long *out); 241 | 242 | /** 243 | * Find the bool value of a resource. 244 | * 245 | * The conversion to a bool is done by applying the following steps in order: 246 | * - If the value can be converted to a long, return the truthiness of the 247 | * converted number. 248 | * - If the value is one of "true", "on" or "yes" (case-insensitive), return 249 | * true. 250 | * - If the value is one of "false", "off" or "no" (case-insensitive), return 251 | * false. 252 | * 253 | * @param database The database to query. 254 | * @param res_name The fully qualified resource name string. 255 | * @param res_class The fully qualified resource class string. This argument 256 | * may be left empty / NULL, but if given, it must contain the same number of 257 | * components as res_name. 258 | * @param out Out parameter to which the converted value will be written. 259 | * @returns 0 if the resource was found and converted, -1 if the resource was 260 | * found but could not be converted and -2 if the resource was not found. 261 | */ 262 | int xcb_xrm_resource_get_bool(xcb_xrm_database_t *database, 263 | const char *res_name, const char *res_class, bool *out); 264 | 265 | /** 266 | * @} 267 | */ 268 | 269 | #ifdef __cplusplus 270 | } 271 | #endif 272 | 273 | #endif /* __XCB_XRM_H__ */ 274 | -------------------------------------------------------------------------------- /src/database.c: -------------------------------------------------------------------------------- 1 | /* 2 | * vim:ts=4:sw=4:expandtab 3 | * 4 | * Copyright © 2016 Ingo Bürk 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a 7 | * copy of this software and associated documentation files (the "Software"), 8 | * to deal in the Software without restriction, including without limitation 9 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | * and/or sell copies of the Software, and to permit persons to whom the 11 | * Software is furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 20 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | * 23 | * Except as contained in this notice, the names of the authors or their 24 | * institutions shall not be used in advertising or otherwise to promote the 25 | * sale, use or other dealings in this Software without prior written 26 | * authorization from the authors. 27 | * 28 | */ 29 | #include "externals.h" 30 | 31 | #include "database.h" 32 | #include "match.h" 33 | #include "util.h" 34 | 35 | #ifndef MAX_INCLUDE_DEPTH 36 | /* We want to limit the maximum depth of (recursive) #include directives. This 37 | * is to avoid accidental cyclic inclusions which would lead to an endless loop 38 | * otherwise. */ 39 | #define MAX_INCLUDE_DEPTH 100 40 | #endif 41 | 42 | /* Forward declarations */ 43 | static xcb_xrm_database_t *__xcb_xrm_database_from_string(const char *_str, const char *base, int depth); 44 | static xcb_xrm_database_t *__xcb_xrm_database_from_file(const char *_filename, const char *base, int depth); 45 | static void __xcb_xrm_database_put(xcb_xrm_database_t *database, xcb_xrm_entry_t *entry, bool override); 46 | 47 | /* 48 | * Creates a database similarly to XGetDefault(). For typical applications, 49 | * this is the recommended way to construct the resource database. 50 | * 51 | * The database is created as follows: 52 | * - If the RESOURCE_MANAGER property exists on the root window of 53 | * screen 0, the database is constructed from it using @ref 54 | * xcb_xrm_database_from_resource_manager(). 55 | * - Otherwise, if $HOME/.Xresources exists, the database is constructed from 56 | * it using @ref xcb_xrm_database_from_file(). 57 | * - Otherwise, if $HOME/.Xdefaults exists, the database is constructed from 58 | * it using @ref xcb_xrm_database_from_file(). 59 | * - If the environment variable XENVIRONMENT is set, the file specified by 60 | * it is loaded using @ref xcb_xrm_database_from_file and then combined with 61 | * the database using @ref xcb_xrm_database_combine() with override set to 62 | * true. 63 | * If XENVIRONMENT is not specified, the same is done with 64 | * $HOME/.Xdefaults-$HOSTNAME, wherein $HOSTNAME is determined by 65 | * gethostname(2). 66 | * 67 | * This represents the way XGetDefault() creates the database for the most 68 | * part, but is not exactly the same. In particular, XGetDefault() does not 69 | * consider $HOME/.Xresources. 70 | * 71 | * @param conn XCB connection. 72 | * @returns The constructed database. Can return NULL, e.g., if the screen 73 | * cannot be determined. 74 | */ 75 | xcb_xrm_database_t *xcb_xrm_database_from_default(xcb_connection_t *conn) { 76 | xcb_screen_t *screen; 77 | xcb_xrm_database_t *database; 78 | char *xenvironment; 79 | 80 | screen = xcb_aux_get_screen(conn, 0); 81 | if (screen == NULL) 82 | return NULL; 83 | 84 | /* 1. Try to load the database from RESOURCE_MANAGER. */ 85 | database = xcb_xrm_database_from_resource_manager(conn, screen); 86 | 87 | /* 2. Otherwise, try to load the database from $HOME/.Xresources. */ 88 | if (database == NULL) { 89 | char *xresources = get_home_dir_file(".Xresources"); 90 | database = xcb_xrm_database_from_file(xresources); 91 | FREE(xresources); 92 | } 93 | 94 | /* 3. Otherwise, try to load the database from $HOME/.Xdefaults. */ 95 | if (database == NULL) { 96 | char *xdefaults = get_home_dir_file(".Xdefaults"); 97 | database = xcb_xrm_database_from_file(xdefaults); 98 | FREE(xdefaults); 99 | } 100 | 101 | /* 4. If XENVIRONMENT is specified, merge the database defined by that file. 102 | * Otherwise, use $HOME/.Xdefaults-$HOSTNAME. */ 103 | if ((xenvironment = getenv("XENVIRONMENT")) != NULL) { 104 | xcb_xrm_database_t *source = xcb_xrm_database_from_file(xenvironment); 105 | xcb_xrm_database_combine(source, &database, true); 106 | xcb_xrm_database_free(source); 107 | } else { 108 | char hostname[1024]; 109 | hostname[1023] = '\0'; 110 | if (gethostname(hostname, 1023) == 0) { 111 | char *name; 112 | if (asprintf(&name, ".Xdefaults-%s", hostname) >= 0) { 113 | xcb_xrm_database_t *source; 114 | 115 | char *xdefaults = get_home_dir_file(name); 116 | FREE(name); 117 | 118 | source = xcb_xrm_database_from_file(xdefaults); 119 | FREE(xdefaults); 120 | 121 | xcb_xrm_database_combine(source, &database, true); 122 | xcb_xrm_database_free(source); 123 | } 124 | } 125 | } 126 | 127 | return database; 128 | } 129 | 130 | /* 131 | * Loads the RESOURCE_MANAGER property and creates a database with its 132 | * contents. If the database could not be created, this function will return 133 | * NULL. 134 | * 135 | * @param conn A working XCB connection. 136 | * @param screen The xcb_screen_t* screen to use. 137 | * @returns The database described by the RESOURCE_MANAGER property. 138 | * 139 | * @ingroup xcb_xrm_database_t 140 | */ 141 | xcb_xrm_database_t *xcb_xrm_database_from_resource_manager(xcb_connection_t *conn, xcb_screen_t *screen) { 142 | xcb_xrm_database_t *database; 143 | 144 | char *resources = xcb_util_get_property(conn, screen->root, XCB_ATOM_RESOURCE_MANAGER, 145 | XCB_ATOM_STRING, 16 * 1024); 146 | if (resources == NULL) { 147 | return NULL; 148 | } 149 | 150 | /* Parse the resource string. */ 151 | database = xcb_xrm_database_from_string(resources); 152 | FREE(resources); 153 | return database; 154 | } 155 | 156 | /* 157 | * Creates a database from the given string. 158 | * If the database could not be created, this function will return NULL. 159 | * 160 | * @param str The resource string. 161 | * @returns The database described by the resource string. 162 | * 163 | * @ingroup xcb_xrm_database_t 164 | */ 165 | xcb_xrm_database_t *xcb_xrm_database_from_string(const char *str) { 166 | return __xcb_xrm_database_from_string(str, NULL, 0); 167 | } 168 | 169 | static xcb_xrm_database_t *__xcb_xrm_database_from_string(const char *_str, const char *base, int depth) { 170 | xcb_xrm_database_t *database; 171 | char *str; 172 | int num_continuations = 0; 173 | char *str_continued; 174 | char *outwalk; 175 | char *saveptr = NULL; 176 | 177 | if (_str == NULL) 178 | return xcb_xrm_database_from_string(""); 179 | 180 | str = strdup(_str); 181 | if (str == NULL) 182 | return NULL; 183 | 184 | /* Count the number of line continuations. */ 185 | for (char *walk = str; *walk != '\0'; walk++) { 186 | if (*walk == '\\' && *(walk + 1) == '\n') { 187 | num_continuations++; 188 | } 189 | } 190 | 191 | /* Take care of line continuations. */ 192 | str_continued = calloc(1, strlen(str) + 1 - 2 * num_continuations); 193 | if (str_continued == NULL) { 194 | FREE(str); 195 | return NULL; 196 | } 197 | 198 | outwalk = str_continued; 199 | for (char *walk = str; *walk != '\0'; walk++) { 200 | if (*walk == '\\' && *(walk + 1) == '\n') { 201 | walk++; 202 | continue; 203 | } 204 | 205 | *(outwalk++) = *walk; 206 | } 207 | *outwalk = '\0'; 208 | 209 | database = calloc(1, sizeof(struct xcb_xrm_database_t)); 210 | if (database == NULL) { 211 | FREE(str); 212 | FREE(str_continued); 213 | return NULL; 214 | } 215 | 216 | TAILQ_INIT(database); 217 | 218 | for (char *line = strtok_r(str_continued, "\n", &saveptr); line != NULL; line = strtok_r(NULL, "\n", &saveptr)) { 219 | /* Handle include directives. */ 220 | if (line[0] == '#') { 221 | int i = 1; 222 | 223 | /* Skip whitespace and quotes. */ 224 | while (line[i] == ' ' || line[i] == '\t') 225 | i++; 226 | 227 | if (depth < MAX_INCLUDE_DEPTH && 228 | line[i++] == 'i' && 229 | line[i++] == 'n' && 230 | line[i++] == 'c' && 231 | line[i++] == 'l' && 232 | line[i++] == 'u' && 233 | line[i++] == 'd' && 234 | line[i++] == 'e') { 235 | xcb_xrm_database_t *included; 236 | char *filename; 237 | char *copy; 238 | char *new_base; 239 | int j = strlen(line) - 1; 240 | 241 | /* Skip whitespace and quotes. */ 242 | while (line[i] == ' ' || line[i] == '\t' || line[i] == '"') 243 | i++; 244 | while (line[j] == ' ' || line[j] == '\t' || line[j] == '"') 245 | j--; 246 | 247 | if (j < i) { 248 | /* Only whitespace left in this line. */ 249 | continue; 250 | } 251 | 252 | line[j+1] = '\0'; 253 | filename = resolve_path(&line[i], base); 254 | if (filename == NULL) 255 | continue; 256 | 257 | /* We need to strdup() the filename since dirname() will modify it. */ 258 | copy = strdup(filename); 259 | if (copy == NULL) { 260 | FREE(filename); 261 | continue; 262 | } 263 | 264 | new_base = dirname(copy); 265 | if (new_base == NULL) { 266 | FREE(filename); 267 | FREE(copy); 268 | continue; 269 | } 270 | 271 | included = __xcb_xrm_database_from_file(filename, new_base, depth + 1); 272 | FREE(filename); 273 | FREE(copy); 274 | 275 | if (included != NULL) { 276 | xcb_xrm_database_combine(included, &database, true); 277 | xcb_xrm_database_free(included); 278 | } 279 | 280 | continue; 281 | } 282 | } 283 | 284 | xcb_xrm_database_put_resource_line(&database, line); 285 | } 286 | 287 | FREE(str); 288 | FREE(str_continued); 289 | return database; 290 | } 291 | 292 | /* 293 | * Creates a database from a given file. 294 | * If the file cannot be found or opened, NULL is returned. 295 | * 296 | * @param filename Valid filename. 297 | * @returns The database described by the file's contents. 298 | */ 299 | xcb_xrm_database_t *xcb_xrm_database_from_file(const char *filename) { 300 | return __xcb_xrm_database_from_file(filename, NULL, 0); 301 | } 302 | 303 | static xcb_xrm_database_t *__xcb_xrm_database_from_file(const char *_filename, const char *base, int depth) { 304 | char *filename = NULL; 305 | char *copy = NULL; 306 | char *new_base = NULL; 307 | char *content = NULL; 308 | xcb_xrm_database_t *database = NULL; 309 | 310 | if (_filename == NULL) 311 | return NULL; 312 | 313 | filename = resolve_path(_filename, base); 314 | if (filename == NULL) 315 | return NULL; 316 | 317 | /* We need to strdup() the filename since dirname() will modify it. */ 318 | copy = strdup(filename); 319 | if (copy == NULL) 320 | goto done_from_file; 321 | 322 | new_base = dirname(copy); 323 | if (new_base == NULL) 324 | goto done_from_file; 325 | 326 | content = file_get_contents(filename); 327 | if (content == NULL) 328 | goto done_from_file; 329 | 330 | database = __xcb_xrm_database_from_string(content, new_base, depth); 331 | 332 | done_from_file: 333 | FREE(filename); 334 | FREE(copy); 335 | FREE(content); 336 | 337 | return database; 338 | } 339 | 340 | /* 341 | * Returns a string representation of a database. 342 | * The string is owned by the caller and must be free'd. 343 | * 344 | * @param database The database to return in string format. 345 | * @returns A string representation of the specified database. 346 | */ 347 | char *xcb_xrm_database_to_string(xcb_xrm_database_t *database) { 348 | char *result = NULL; 349 | xcb_xrm_entry_t *entry; 350 | 351 | if (database == NULL) 352 | return NULL; 353 | 354 | TAILQ_FOREACH(entry, database, entries) { 355 | char *entry_str = __xcb_xrm_entry_to_string(entry); 356 | char *tmp; 357 | if (asprintf(&tmp, "%s%s\n", result == NULL ? "" : result, entry_str) < 0) { 358 | FREE(entry_str); 359 | FREE(result); 360 | return NULL; 361 | } 362 | FREE(entry_str); 363 | FREE(result); 364 | result = tmp; 365 | } 366 | 367 | return result; 368 | } 369 | 370 | /* 371 | * Combines two databases. 372 | * The entries from the source database are stored in the target database. If 373 | * the same specifier already exists in the target database, the value will be 374 | * overridden if override is set; otherwise, the value is discarded. 375 | * If NULL is passed for target_db, a new and empty database will be created 376 | * and returned in the pointer. 377 | * 378 | * @param source_db Source database. 379 | * @param target_db Target database. 380 | * @param override If true, entries from the source database override entries 381 | * in the target database using the same resource specifier. 382 | */ 383 | void xcb_xrm_database_combine(xcb_xrm_database_t *source_db, xcb_xrm_database_t **target_db, bool override) { 384 | xcb_xrm_entry_t *entry; 385 | 386 | if (*target_db == NULL) 387 | *target_db = xcb_xrm_database_from_string(""); 388 | if (source_db == NULL) 389 | return; 390 | 391 | if (source_db == *target_db) 392 | return; 393 | 394 | TAILQ_FOREACH(entry, source_db, entries) { 395 | xcb_xrm_entry_t *copy = __xcb_xrm_entry_copy(entry); 396 | __xcb_xrm_database_put(*target_db, copy, override); 397 | } 398 | } 399 | 400 | /* 401 | * Inserts a new resource into the database. 402 | * If the resource already exists, the current value will be replaced. 403 | * If NULL is passed for database, a new and empty database will be created and 404 | * returned in the pointer. 405 | * 406 | * Note that this is not the equivalent of @ref 407 | * xcb_xrm_database_put_resource_line when concatenating the resource name and 408 | * value with a colon. For example, if the value starts with a leading space, 409 | * this must (and will) be replaced with the special '\ ' sequence. 410 | * 411 | * @param database The database to modify. 412 | * @param resource The fully qualified or partial resource specifier. 413 | * @param value The value of the resource. 414 | */ 415 | void xcb_xrm_database_put_resource(xcb_xrm_database_t **database, const char *resource, const char *value) { 416 | char *escaped; 417 | char *line; 418 | 419 | assert(resource != NULL); 420 | assert(value != NULL); 421 | 422 | if (*database == NULL) 423 | *database = xcb_xrm_database_from_string(""); 424 | 425 | escaped = __xcb_xrm_entry_escape_value(value); 426 | if (escaped == NULL) 427 | return; 428 | if (asprintf(&line, "%s: %s", resource, escaped) < 0) { 429 | FREE(escaped); 430 | return; 431 | } 432 | FREE(escaped); 433 | xcb_xrm_database_put_resource_line(database, line); 434 | FREE(line); 435 | } 436 | 437 | /* 438 | * Inserts a new resource into the database. 439 | * If the resource already exists, the current value will be replaced. 440 | * If NULL is passed for database, a new and empty database will be created and 441 | * returned in the pointer. 442 | * 443 | * @param database The database to modify. 444 | * @param line The complete resource specification to insert. 445 | */ 446 | void xcb_xrm_database_put_resource_line(xcb_xrm_database_t **database, const char *line) { 447 | xcb_xrm_entry_t *entry; 448 | 449 | assert(line != NULL); 450 | 451 | if (*database == NULL) 452 | *database = xcb_xrm_database_from_string(""); 453 | 454 | /* Ignore comments and directives. The specification guarantees that no 455 | * whitespace is allowed before these characters. */ 456 | if (line[0] == '!' || line[0] == '#') 457 | return; 458 | 459 | if (xcb_xrm_entry_parse(line, &entry, false) == 0) { 460 | __xcb_xrm_database_put(*database, entry, true); 461 | } 462 | } 463 | 464 | /** 465 | * Destroys the given database. 466 | * 467 | * @param database The database to destroy. 468 | * 469 | * @ingroup xcb_xrm_database_t 470 | */ 471 | void xcb_xrm_database_free(xcb_xrm_database_t *database) { 472 | if (database == NULL) 473 | return; 474 | 475 | while (!TAILQ_EMPTY(database)) { 476 | xcb_xrm_entry_t *entry = TAILQ_FIRST(database); 477 | TAILQ_REMOVE(database, entry, entries); 478 | xcb_xrm_entry_free(entry); 479 | } 480 | 481 | FREE(database); 482 | } 483 | 484 | static void __xcb_xrm_database_put(xcb_xrm_database_t *database, xcb_xrm_entry_t *entry, bool override) { 485 | xcb_xrm_entry_t *current; 486 | 487 | if (database == NULL || entry == NULL) 488 | return; 489 | 490 | /* Let's see whether this is a duplicate entry. */ 491 | current = TAILQ_FIRST(database); 492 | while (current != NULL) { 493 | xcb_xrm_entry_t *previous = TAILQ_PREV(current, xcb_xrm_database_t, entries); 494 | 495 | if (__xcb_xrm_entry_compare(entry, current) == 0) { 496 | if (!override) { 497 | xcb_xrm_entry_free(entry); 498 | return; 499 | } 500 | 501 | TAILQ_REMOVE(database, current, entries); 502 | xcb_xrm_entry_free(current); 503 | 504 | current = previous; 505 | if (current == NULL) 506 | current = TAILQ_FIRST(database); 507 | } 508 | 509 | if (current == NULL) 510 | break; 511 | current = TAILQ_NEXT(current, entries); 512 | } 513 | 514 | TAILQ_INSERT_TAIL(database, entry, entries); 515 | } 516 | -------------------------------------------------------------------------------- /src/entry.c: -------------------------------------------------------------------------------- 1 | /* 2 | * vim:ts=4:sw=4:expandtab 3 | * 4 | * Copyright © 2016 Ingo Bürk 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a 7 | * copy of this software and associated documentation files (the "Software"), 8 | * to deal in the Software without restriction, including without limitation 9 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | * and/or sell copies of the Software, and to permit persons to whom the 11 | * Software is furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 20 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | * 23 | * Except as contained in this notice, the names of the authors or their 24 | * institutions shall not be used in advertising or otherwise to promote the 25 | * sale, use or other dealings in this Software without prior written 26 | * authorization from the authors. 27 | * 28 | */ 29 | #include "externals.h" 30 | 31 | #include "entry.h" 32 | #include "util.h" 33 | 34 | #define BUFFER_SIZE 1024 35 | 36 | /** 37 | * Appends a single character to the current buffer. 38 | * If the buffer is not yet initialized or has been invalidated, it will be set up. 39 | * 40 | */ 41 | static void xcb_xrm_append_char(xcb_xrm_entry_t *entry, xcb_xrm_entry_parser_state_t *state, 42 | const char str) { 43 | ptrdiff_t offset; 44 | 45 | if (state->buffer_pos == NULL) { 46 | FREE(state->buffer); 47 | state->buffer = calloc(1, BUFFER_SIZE); 48 | state->buffer_pos = state->buffer; 49 | if (state->buffer == NULL) { 50 | /* Let's ignore this character and try again next time. */ 51 | return; 52 | } 53 | } 54 | 55 | /* Increase the buffer if necessary. */ 56 | offset = state->buffer_pos - state->buffer; 57 | if (offset % BUFFER_SIZE == BUFFER_SIZE - 1) { 58 | state->buffer = realloc(state->buffer, offset + BUFFER_SIZE + 1); 59 | state->buffer_pos = state->buffer + offset; 60 | } 61 | 62 | *(state->buffer_pos++) = str; 63 | } 64 | 65 | /** 66 | * Insert a new component of the given type. 67 | * This function does not check whether there is an open buffer. 68 | * 69 | */ 70 | static void xcb_xrm_insert_component(xcb_xrm_entry_t *entry, xcb_xrm_component_type_t type, 71 | xcb_xrm_binding_type_t binding_type, const char *str) { 72 | xcb_xrm_component_t *new = calloc(1, sizeof(struct xcb_xrm_component_t)); 73 | if (new == NULL) 74 | return; 75 | 76 | if (str != NULL) { 77 | new->name = strdup(str); 78 | if (new->name == NULL) { 79 | FREE(new); 80 | return; 81 | } 82 | } 83 | 84 | new->type = type; 85 | new->binding_type = binding_type; 86 | TAILQ_INSERT_TAIL(&(entry->components), new, components); 87 | } 88 | 89 | /** 90 | * Finalize the current buffer by writing it into a component if necessary. 91 | * This function also resets the buffer to a clean slate. 92 | * 93 | */ 94 | static void xcb_xrm_finalize_component(xcb_xrm_entry_t *entry, xcb_xrm_entry_parser_state_t *state) { 95 | if (state->buffer_pos != NULL && state->buffer_pos != state->buffer) { 96 | *(state->buffer_pos) = '\0'; 97 | xcb_xrm_insert_component(entry, CT_NORMAL, state->current_binding_type, state->buffer); 98 | } 99 | 100 | FREE(state->buffer); 101 | /* No need to handle NULL for this calloc call. */ 102 | state->buffer = calloc(1, BUFFER_SIZE); 103 | state->buffer_pos = state->buffer; 104 | state->current_binding_type = BT_TIGHT; 105 | } 106 | 107 | /* 108 | * Parses a specific resource string. 109 | * 110 | * @param str The resource string. 111 | * @param entry A return struct that will contain the parsed resource. The 112 | * memory will be allocated dynamically, so it must be freed. 113 | * @param resource_only If true, only components of type CT_NORMAL are allowed. 114 | * 115 | * @return 0 on success, a negative error code otherwise. 116 | * 117 | */ 118 | int xcb_xrm_entry_parse(const char *_str, xcb_xrm_entry_t **_entry, bool resource_only) { 119 | char *str; 120 | xcb_xrm_entry_t *entry = NULL; 121 | xcb_xrm_component_t *last; 122 | char *value; 123 | char *value_walk; 124 | xcb_xrm_binding_type_t binding_type; 125 | 126 | xcb_xrm_entry_parser_state_t state = { 127 | .chunk = CS_INITIAL, 128 | .current_binding_type = BT_TIGHT, 129 | }; 130 | 131 | /* Copy the input string since it's const. */ 132 | str = strdup(_str); 133 | if (str == NULL) 134 | return -FAILURE; 135 | 136 | /* This is heavily overestimated, but we'll just keep it simple here. 137 | * While this does not account for replacement of magic values, those only 138 | * make the resulting string shorter than the input, so we're okay. */ 139 | value = calloc(1, strlen(str)); 140 | if (value == NULL) { 141 | FREE(str); 142 | return -FAILURE; 143 | } 144 | value_walk = value; 145 | 146 | /* Allocate memory for the return parameter. */ 147 | *_entry = calloc(1, sizeof(struct xcb_xrm_entry_t)); 148 | if (_entry == NULL) { 149 | FREE(str); 150 | FREE(value); 151 | return -FAILURE; 152 | } 153 | 154 | entry = *_entry; 155 | TAILQ_INIT(&(entry->components)); 156 | 157 | for (char *walk = str; *walk != '\0'; walk++) { 158 | switch (*walk) { 159 | case '.': 160 | case '*': 161 | state.chunk = MAX(state.chunk, CS_COMPONENTS); 162 | if (state.chunk >= CS_PRE_VALUE_WHITESPACE) { 163 | goto process_normally; 164 | } 165 | 166 | if (*walk == '*' && resource_only) { 167 | goto done_error; 168 | } 169 | 170 | /* Subsequent bindings must be collapsed into a loose binding if at 171 | * least one was a loose binding and a tight binding otherwise. */ 172 | binding_type = (*walk == '*') ? BT_LOOSE : BT_TIGHT; 173 | while (*(walk + 1) == '.' || *(walk + 1) == '*') { 174 | walk++; 175 | 176 | if (*walk == '*') { 177 | binding_type = BT_LOOSE; 178 | } 179 | } 180 | 181 | xcb_xrm_finalize_component(entry, &state); 182 | state.current_binding_type = binding_type; 183 | break; 184 | case '?': 185 | state.chunk = MAX(state.chunk, CS_COMPONENTS); 186 | if (state.chunk >= CS_PRE_VALUE_WHITESPACE) { 187 | goto process_normally; 188 | } 189 | 190 | if (resource_only) { 191 | goto done_error; 192 | } 193 | 194 | xcb_xrm_insert_component(entry, CT_WILDCARD, state.current_binding_type, NULL); 195 | break; 196 | case ' ': 197 | case '\t': 198 | /* Spaces are only allowed in the value, but spaces between the 199 | * ':' and the value are omitted. */ 200 | if (state.chunk <= CS_PRE_VALUE_WHITESPACE) { 201 | break; 202 | } 203 | 204 | goto process_normally; 205 | case ':': 206 | if (resource_only) { 207 | goto done_error; 208 | } 209 | 210 | if (state.chunk == CS_INITIAL) { 211 | goto done_error; 212 | } else if (state.chunk == CS_COMPONENTS) { 213 | xcb_xrm_finalize_component(entry, &state); 214 | state.chunk = CS_PRE_VALUE_WHITESPACE; 215 | break; 216 | } else if (state.chunk >= CS_PRE_VALUE_WHITESPACE) { 217 | state.chunk = CS_VALUE; 218 | goto process_normally; 219 | } 220 | break; 221 | default: 222 | process_normally: 223 | state.chunk = MAX(state.chunk, CS_COMPONENTS); 224 | 225 | if (state.chunk == CS_PRE_VALUE_WHITESPACE) { 226 | state.chunk = CS_VALUE; 227 | } 228 | 229 | if (state.chunk == CS_COMPONENTS) { 230 | if ((*walk != '_' && *walk != '-') && 231 | (*walk < '0' || *walk > '9') && 232 | (*walk < 'a' || *walk > 'z') && 233 | (*walk < 'A' || *walk > 'Z')) { 234 | goto done_error; 235 | } 236 | } 237 | 238 | if (state.chunk < CS_VALUE) { 239 | xcb_xrm_append_char(entry, &state, *walk); 240 | } else { 241 | if (*walk == '\\') { 242 | if (*(walk + 1) == ' ') { 243 | *(value_walk++) = ' '; 244 | walk++; 245 | } else if (*(walk + 1) == '\t') { 246 | *(value_walk++) = '\t'; 247 | walk++; 248 | } else if (*(walk + 1) == '\\') { 249 | *(value_walk++) = '\\'; 250 | walk++; 251 | } else if (*(walk + 1) == 'n') { 252 | *(value_walk++) = '\n'; 253 | walk++; 254 | } else if (isdigit(*(walk + 1)) && isdigit(*(walk + 2)) && isdigit(*(walk + 3)) && 255 | *(walk + 1) < '8' && *(walk + 2) < '8' && *(walk + 3) < '8') { 256 | *(value_walk++) = (*(walk + 1) - '0') * 64 + (*(walk + 2) - '0') * 8 + (*(walk + 3) - '0'); 257 | walk += 3; 258 | } else { 259 | *(value_walk++) = *walk; 260 | } 261 | } else { 262 | *(value_walk++) = *walk; 263 | } 264 | } 265 | 266 | break; 267 | } 268 | } 269 | 270 | if (state.chunk == CS_PRE_VALUE_WHITESPACE || state.chunk == CS_VALUE) { 271 | *value_walk = '\0'; 272 | entry->value = strdup(value); 273 | if (entry->value == NULL) 274 | goto done_error; 275 | } else if (!resource_only) { 276 | /* Return error if there was no value for this entry. */ 277 | goto done_error; 278 | } else { 279 | /* Since in the case of resource_only we never went into CS_VALUE, we 280 | * need to finalize the last component. */ 281 | xcb_xrm_finalize_component(entry, &state); 282 | } 283 | 284 | /* Assert that this entry actually had a resource component. */ 285 | if ((last = TAILQ_LAST(&(entry->components), components_head)) == NULL) { 286 | goto done_error; 287 | } 288 | 289 | /* Assert that the last component is not a wildcard. */ 290 | if (last->type != CT_NORMAL) { 291 | goto done_error; 292 | } 293 | 294 | FREE(str); 295 | FREE(value); 296 | FREE(state.buffer); 297 | return 0; 298 | 299 | done_error: 300 | FREE(str); 301 | FREE(value); 302 | FREE(state.buffer); 303 | 304 | xcb_xrm_entry_free(entry); 305 | *_entry = NULL; 306 | return -1; 307 | } 308 | 309 | /* 310 | * Returns the number of components of the given entry. 311 | * 312 | */ 313 | int __xcb_xrm_entry_num_components(xcb_xrm_entry_t *entry) { 314 | int result = 0; 315 | 316 | xcb_xrm_component_t *current; 317 | TAILQ_FOREACH(current, &(entry->components), components) { 318 | result++; 319 | } 320 | 321 | return result; 322 | } 323 | 324 | /* 325 | * Compares the two entries. 326 | * Returns 0 if they are the same and a negative error code otherwise. 327 | * 328 | */ 329 | int __xcb_xrm_entry_compare(xcb_xrm_entry_t *first, xcb_xrm_entry_t *second) { 330 | xcb_xrm_component_t *comp_first = TAILQ_FIRST(&(first->components)); 331 | xcb_xrm_component_t *comp_second = TAILQ_FIRST(&(second->components)); 332 | 333 | while (comp_first != NULL && comp_second != NULL) { 334 | if (comp_first->type != comp_second->type) 335 | return -FAILURE; 336 | 337 | if (comp_first->binding_type != comp_second->binding_type) 338 | return -FAILURE; 339 | 340 | if (comp_first->type == CT_NORMAL && strcmp(comp_first->name, comp_second->name) != 0) 341 | return -FAILURE; 342 | 343 | comp_first = TAILQ_NEXT(comp_first, components); 344 | comp_second = TAILQ_NEXT(comp_second, components); 345 | } 346 | 347 | /* At this point, at least one of the two is NULL. If they aren't both 348 | * NULL, they have a different number of components and cannot be equal. */ 349 | if (comp_first != comp_second) { 350 | return -FAILURE; 351 | } 352 | 353 | return SUCCESS; 354 | } 355 | 356 | /* 357 | * Returns a string representation of this entry. 358 | * 359 | */ 360 | char *__xcb_xrm_entry_to_string(xcb_xrm_entry_t *entry) { 361 | char *result = NULL; 362 | char *value_buf; 363 | char *escaped_value; 364 | xcb_xrm_component_t *component; 365 | bool is_first = true; 366 | 367 | assert(entry != NULL); 368 | TAILQ_FOREACH(component, &(entry->components), components) { 369 | char *tmp; 370 | if (asprintf(&tmp, "%s%s%s", result == NULL ? "" : result, 371 | (is_first && component->binding_type == BT_TIGHT) 372 | ? "" 373 | : (component->binding_type == BT_TIGHT ? "." : "*"), 374 | component->type == CT_NORMAL ? component->name : "?") < 0) { 375 | FREE(result); 376 | return NULL; 377 | } 378 | FREE(result); 379 | result = tmp; 380 | 381 | is_first = false; 382 | } 383 | 384 | escaped_value = __xcb_xrm_entry_escape_value(entry->value); 385 | if (asprintf(&value_buf, "%s: %s", result, escaped_value) < 0) { 386 | FREE(escaped_value); 387 | FREE(result); 388 | return NULL; 389 | } 390 | FREE(escaped_value); 391 | FREE(result); 392 | result = value_buf; 393 | 394 | return result; 395 | } 396 | 397 | /* 398 | * Copy the entry. 399 | * 400 | */ 401 | xcb_xrm_entry_t *__xcb_xrm_entry_copy(xcb_xrm_entry_t *entry) { 402 | xcb_xrm_entry_t *copy; 403 | xcb_xrm_component_t *component; 404 | 405 | assert(entry != NULL); 406 | 407 | copy = calloc(1, sizeof(struct xcb_xrm_entry_t)); 408 | if (copy == NULL) 409 | return NULL; 410 | 411 | copy->value = strdup(entry->value); 412 | if (copy->value == NULL) { 413 | FREE(copy); 414 | return NULL; 415 | } 416 | 417 | TAILQ_INIT(&(copy->components)); 418 | TAILQ_FOREACH(component, &(entry->components), components) { 419 | xcb_xrm_component_t *new = calloc(1, sizeof(struct xcb_xrm_component_t)); 420 | if (new == NULL) { 421 | xcb_xrm_entry_free(copy); 422 | return NULL; 423 | } 424 | 425 | new->name = strdup(component->name); 426 | if (new->name == NULL) { 427 | xcb_xrm_entry_free(copy); 428 | FREE(new); 429 | return NULL; 430 | } 431 | 432 | new->type = component->type; 433 | new->binding_type = component->binding_type; 434 | TAILQ_INSERT_TAIL(&(copy->components), new, components); 435 | } 436 | 437 | return copy; 438 | } 439 | 440 | /* 441 | * Escapes magic values. 442 | * 443 | */ 444 | char *__xcb_xrm_entry_escape_value(const char *value) { 445 | char *escaped; 446 | char *outwalk; 447 | int new_size = strlen(value) + 1; 448 | 449 | if (value[0] == ' ' || value[0] == '\t') 450 | new_size++; 451 | for (const char *walk = value; *walk != '\0'; walk++) { 452 | if (*walk == '\n' || *walk == '\\') 453 | new_size++; 454 | } 455 | 456 | escaped = calloc(1, new_size); 457 | if (escaped == NULL) 458 | return NULL; 459 | 460 | outwalk = escaped; 461 | if (value[0] == ' ' || value[0] == '\t') { 462 | *(outwalk++) = '\\'; 463 | } 464 | for (const char *walk = value; *walk != '\0'; walk++) { 465 | if (*walk == '\n') { 466 | *(outwalk++) = '\\'; 467 | *(outwalk++) = 'n'; 468 | } else if (*walk == '\\') { 469 | *(outwalk++) = '\\'; 470 | *(outwalk++) = '\\'; 471 | } else { 472 | *(outwalk++) = *walk; 473 | } 474 | } 475 | *outwalk = '\0'; 476 | 477 | return escaped; 478 | } 479 | 480 | /* 481 | * Frees the given entry. 482 | * 483 | * @param entry The entry to be freed. 484 | * 485 | */ 486 | void xcb_xrm_entry_free(xcb_xrm_entry_t *entry) { 487 | if (entry == NULL) 488 | return; 489 | 490 | FREE(entry->value); 491 | while (!TAILQ_EMPTY(&(entry->components))) { 492 | xcb_xrm_component_t *component = TAILQ_FIRST(&(entry->components)); 493 | FREE(component->name); 494 | TAILQ_REMOVE(&(entry->components), component, components); 495 | FREE(component); 496 | } 497 | 498 | FREE(entry); 499 | return; 500 | } 501 | -------------------------------------------------------------------------------- /src/match.c: -------------------------------------------------------------------------------- 1 | /* 2 | * vim:ts=4:sw=4:expandtab 3 | * 4 | * Copyright © 2016 Ingo Bürk 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a 7 | * copy of this software and associated documentation files (the "Software"), 8 | * to deal in the Software without restriction, including without limitation 9 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | * and/or sell copies of the Software, and to permit persons to whom the 11 | * Software is furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 20 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | * 23 | * Except as contained in this notice, the names of the authors or their 24 | * institutions shall not be used in advertising or otherwise to promote the 25 | * sale, use or other dealings in this Software without prior written 26 | * authorization from the authors. 27 | * 28 | */ 29 | #include "externals.h" 30 | 31 | #include "match.h" 32 | #include "util.h" 33 | 34 | /* Forward declarations */ 35 | static int __match_matches(int num_components, xcb_xrm_component_t *cur_comp_db, 36 | xcb_xrm_component_t *cur_comp_name, xcb_xrm_component_t *cur_comp_class, 37 | bool has_class, int position, xcb_xrm_match_ignore_t ignore, xcb_xrm_match_t **match); 38 | static xcb_xrm_match_flags_t __match_matches_component(xcb_xrm_component_t *comp_db, 39 | xcb_xrm_component_t *comp_name, xcb_xrm_component_t *comp_class); 40 | static int __match_compare(int length, xcb_xrm_match_t *best, xcb_xrm_match_t *candidate); 41 | static xcb_xrm_match_t *__match_new(int length); 42 | static void __match_copy(xcb_xrm_match_t *src, xcb_xrm_match_t *dest, int length); 43 | static void __match_free(xcb_xrm_match_t *match); 44 | 45 | /* 46 | * Finds the matching entry in the database given a full name / class query string. 47 | * 48 | */ 49 | int __xcb_xrm_match(xcb_xrm_database_t *database, xcb_xrm_entry_t *query_name, xcb_xrm_entry_t *query_class, 50 | xcb_xrm_resource_t *resource) { 51 | xcb_xrm_match_t *best_match = NULL; 52 | xcb_xrm_entry_t *cur_entry = TAILQ_FIRST(database); 53 | 54 | int num = __xcb_xrm_entry_num_components(query_name); 55 | 56 | while (cur_entry != NULL) { 57 | xcb_xrm_match_t *cur_match = NULL; 58 | 59 | /* First we check whether the current database entry even matches. */ 60 | bool has_class = query_class != NULL; 61 | xcb_xrm_component_t *first_comp_name = TAILQ_FIRST(&(query_name->components)); 62 | xcb_xrm_component_t *first_comp_class = has_class ? TAILQ_FIRST(&(query_class->components)) : NULL; 63 | xcb_xrm_component_t *first_comp_db = TAILQ_FIRST(&(cur_entry->components)); 64 | if (__match_matches(num, first_comp_db, first_comp_name, first_comp_class, has_class, 65 | 0, MI_UNDECIDED, &cur_match) == 0) { 66 | cur_match->entry = cur_entry; 67 | 68 | /* The first matching entry is the first one we pick as the best matching entry. */ 69 | if (best_match == NULL) { 70 | best_match = cur_match; 71 | } else { 72 | /* Otherwise, check whether this match is better than the current best. */ 73 | if (__match_compare(num, best_match, cur_match) == 0) { 74 | __match_free(best_match); 75 | best_match = cur_match; 76 | } else { 77 | __match_free(cur_match); 78 | } 79 | } 80 | } else { 81 | __match_free(cur_match); 82 | } 83 | 84 | /* Advance to the next database entry. */ 85 | cur_entry = TAILQ_NEXT(cur_entry, entries); 86 | } 87 | 88 | if (best_match != NULL) { 89 | resource->value = strdup(best_match->entry->value); 90 | if (resource->value == NULL) { 91 | __match_free(best_match); 92 | return -FAILURE; 93 | } 94 | 95 | __match_free(best_match); 96 | return SUCCESS; 97 | } 98 | 99 | return -FAILURE; 100 | } 101 | 102 | static int __match_matches(int num_components, xcb_xrm_component_t *cur_comp_db, 103 | xcb_xrm_component_t *cur_comp_name, xcb_xrm_component_t *cur_comp_class, 104 | bool has_class, int position, xcb_xrm_match_ignore_t ignore, xcb_xrm_match_t **match) { 105 | xcb_xrm_match_flags_t comp_match; 106 | 107 | if (*match == NULL) { 108 | *match = __match_new(num_components); 109 | if (*match == NULL) 110 | return -FAILURE; 111 | } 112 | 113 | /* Check if we reached the end of the recursion. */ 114 | if (cur_comp_name == NULL || (has_class && cur_comp_class == NULL) || cur_comp_db == NULL) { 115 | if (cur_comp_db == NULL && cur_comp_name == NULL && (!has_class || cur_comp_class == NULL)) { 116 | return SUCCESS; 117 | } 118 | 119 | return -FAILURE; 120 | } 121 | 122 | comp_match = __match_matches_component(cur_comp_db, cur_comp_name, cur_comp_class); 123 | 124 | /* If we have a matching component in a loose binding, we need to continue 125 | * matching both normally and ignoring this match. */ 126 | if (ignore == MI_UNDECIDED && cur_comp_db->binding_type == BT_LOOSE && 127 | (comp_match & MF_NAME || comp_match & MF_CLASS || comp_match & MF_WILDCARD)) { 128 | 129 | /* Store references / copies to the current parameters for the second call. */ 130 | xcb_xrm_component_t *copy_comp_db = cur_comp_db; 131 | xcb_xrm_component_t *copy_comp_name = cur_comp_name; 132 | xcb_xrm_component_t *copy_comp_class = cur_comp_class; 133 | xcb_xrm_match_t *copy_match = __match_new(num_components); 134 | __match_copy(*match, copy_match, num_components); 135 | 136 | /* First, we try to match normally. */ 137 | if (__match_matches(num_components, cur_comp_db, cur_comp_name, cur_comp_class, has_class, 138 | position, MI_DO_NOT_IGNORE, match) == 0) { 139 | __match_free(copy_match); 140 | return SUCCESS; 141 | } 142 | 143 | /* We had no success the first time around, so let's try to reset and 144 | * go again, but this time ignoring this match. */ 145 | __match_free(*match); 146 | *match = copy_match; 147 | if (__match_matches(num_components, copy_comp_db, copy_comp_name, copy_comp_class, has_class, 148 | position, MI_IGNORE, match) == 0) { 149 | return SUCCESS; 150 | } 151 | 152 | /* Give up. */ 153 | return -FAILURE; 154 | } 155 | 156 | /* Store the match flags on the match so we can use them later for precedence evaluation. */ 157 | (*match)->flags[position] = comp_match; 158 | if (comp_match == MF_NONE) 159 | return -FAILURE; 160 | 161 | #define ADVANCE(entry) do { \ 162 | if (entry != NULL) \ 163 | entry = TAILQ_NEXT((entry), components); \ 164 | } while (0) 165 | 166 | /* We have matched this component, so advance to the next one. */ 167 | ADVANCE(cur_comp_name); 168 | ADVANCE(cur_comp_class); 169 | /* We advance the pointer to the database entry component if both of the following are true: 170 | * 1. This match isn't ignored due to a loose binding path. 171 | * 2. It was an actual match and not skipped due to a loose binding. */ 172 | if (ignore != MI_IGNORE && 173 | (comp_match & MF_NAME || comp_match & MF_CLASS || comp_match & MF_WILDCARD)) { 174 | ADVANCE(cur_comp_db); 175 | } 176 | 177 | #undef ADVANCE 178 | 179 | /* Recursively descend to the next component. */ 180 | return __match_matches(num_components, cur_comp_db, cur_comp_name, cur_comp_class, has_class, 181 | position + 1, MI_UNDECIDED, match); 182 | } 183 | 184 | static xcb_xrm_match_flags_t __match_matches_component(xcb_xrm_component_t *comp_db, 185 | xcb_xrm_component_t *comp_name, xcb_xrm_component_t *comp_class) { 186 | xcb_xrm_match_flags_t result = MF_NONE; 187 | if (comp_db->binding_type == BT_LOOSE) 188 | result = MF_PRECEDING_LOOSE; 189 | 190 | if (comp_db->type == CT_NORMAL) { 191 | if (strcmp(comp_db->name, comp_name->name) == 0) { 192 | result |= MF_NAME; 193 | } else if (comp_class != NULL && strcmp(comp_db->name, comp_class->name) == 0) { 194 | result |= MF_CLASS; 195 | } else { 196 | if (comp_db->binding_type == BT_TIGHT) 197 | return MF_NONE; 198 | 199 | /* We remove this flag again because we need to apply 200 | * it to the last component in the matching chain for 201 | * the loose binding. */ 202 | result &= ~MF_PRECEDING_LOOSE; 203 | result |= MF_SKIPPED; 204 | } 205 | } else { 206 | result |= MF_WILDCARD; 207 | } 208 | 209 | return result; 210 | } 211 | 212 | static int __match_compare(int length, xcb_xrm_match_t *best, xcb_xrm_match_t *candidate) { 213 | for (int i = 0; i < length; i++) { 214 | xcb_xrm_match_flags_t mt_best = best->flags[i]; 215 | xcb_xrm_match_flags_t mt_candidate = candidate->flags[i]; 216 | 217 | /* Precedence rule #1: Matching components, including '?', outweigh '*'. */ 218 | if (mt_best & MF_SKIPPED && 219 | (mt_candidate & MF_NAME || mt_candidate & MF_CLASS || mt_candidate & MF_WILDCARD)) 220 | return SUCCESS; 221 | 222 | /* Precedence rule #2: Matching name outweighs both matching class and '?'. 223 | * Matching class outweighs '?'. */ 224 | if ((mt_best & MF_CLASS || mt_best & MF_WILDCARD) && mt_candidate & MF_NAME) 225 | return SUCCESS; 226 | 227 | if (mt_best & MF_WILDCARD && mt_candidate & MF_CLASS) 228 | return SUCCESS; 229 | 230 | /* Precedence rule #3: A preceding exact match outweighs a preceding '*'. */ 231 | if (mt_best & MF_PRECEDING_LOOSE && !(mt_candidate & MF_PRECEDING_LOOSE)) 232 | return SUCCESS; 233 | } 234 | 235 | return -FAILURE; 236 | } 237 | 238 | static xcb_xrm_match_t *__match_new(int length) { 239 | xcb_xrm_match_t *match = calloc(1, sizeof(struct xcb_xrm_match_t)); 240 | if (match == NULL) 241 | return NULL; 242 | 243 | match->entry = NULL; 244 | match->flags = calloc(1, length * sizeof(xcb_xrm_match_flags_t)); 245 | if (match->flags == NULL) { 246 | FREE(match); 247 | return NULL; 248 | } 249 | 250 | return match; 251 | } 252 | 253 | static void __match_copy(xcb_xrm_match_t *src, xcb_xrm_match_t *dest, int length) { 254 | memcpy(dest->flags, src->flags, length * sizeof(xcb_xrm_match_flags_t)); 255 | } 256 | 257 | static void __match_free(xcb_xrm_match_t *match) { 258 | FREE(match->flags); 259 | FREE(match); 260 | } 261 | -------------------------------------------------------------------------------- /src/resource.c: -------------------------------------------------------------------------------- 1 | /* 2 | * vim:ts=4:sw=4:expandtab 3 | * 4 | * Copyright © 2016 Ingo Bürk 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a 7 | * copy of this software and associated documentation files (the "Software"), 8 | * to deal in the Software without restriction, including without limitation 9 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | * and/or sell copies of the Software, and to permit persons to whom the 11 | * Software is furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 20 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | * 23 | * Except as contained in this notice, the names of the authors or their 24 | * institutions shall not be used in advertising or otherwise to promote the 25 | * sale, use or other dealings in this Software without prior written 26 | * authorization from the authors. 27 | * 28 | */ 29 | #include "externals.h" 30 | 31 | #include "resource.h" 32 | #include "database.h" 33 | #include "match.h" 34 | #include "util.h" 35 | 36 | /* Forward declarations */ 37 | static int __resource_get(xcb_xrm_database_t *database, const char *res_name, const char *res_class, 38 | xcb_xrm_resource_t **_resource); 39 | static void __resource_free(xcb_xrm_resource_t *resource); 40 | 41 | /* 42 | * Find the string value of a resource. 43 | * 44 | * Note that the string is owned by the caller and must be free'd. 45 | * 46 | * @param database The database to query. 47 | * @param res_name The fully qualified resource name string. 48 | * @param res_class The fully qualified resource class string. This argument 49 | * may be left empty / NULL, but if given, it must contain the same number of 50 | * components as res_name. 51 | * @param out Out parameter to which the value will be written. 52 | * @returns 0 if the resource was found, a negative error code otherwise. 53 | */ 54 | int xcb_xrm_resource_get_string(xcb_xrm_database_t *database, 55 | const char *res_name, const char *res_class, char **out) { 56 | xcb_xrm_resource_t *resource; 57 | if (__resource_get(database, res_name, res_class, &resource) < 0) { 58 | __resource_free(resource); 59 | *out = NULL; 60 | return -1; 61 | } 62 | 63 | assert(resource->value != NULL); 64 | *out = strdup(resource->value); 65 | __resource_free(resource); 66 | 67 | return 0; 68 | } 69 | 70 | /* 71 | * Find the long value of a resource. 72 | * 73 | * @param database The database to query. 74 | * @param res_name The fully qualified resource name string. 75 | * @param res_class The fully qualified resource class string. This argument 76 | * may be left empty / NULL, but if given, it must contain the same number of 77 | * components as res_name. 78 | * @param out Out parameter to which the converted value will be written. 79 | * @returns 0 if the resource was found and converted, -1 if the resource was 80 | * found but could not be converted and -2 if the resource was not found. 81 | */ 82 | int xcb_xrm_resource_get_long(xcb_xrm_database_t *database, 83 | const char *res_name, const char *res_class, long *out) { 84 | char *value; 85 | if (xcb_xrm_resource_get_string(database, res_name, res_class, &value) < 0 || value == NULL) { 86 | *out = LONG_MIN; 87 | return -2; 88 | } 89 | 90 | if (str2long(out, value, 10) < 0) { 91 | *out = LONG_MIN; 92 | FREE(value); 93 | return -1; 94 | } 95 | 96 | FREE(value); 97 | return 0; 98 | } 99 | 100 | /* 101 | * Find the bool value of a resource. 102 | * 103 | * The conversion to a bool is done by applying the following steps in order: 104 | * - If the value can be converted to a long, return the truthiness of the 105 | * converted number. 106 | * - If the value is one of "true", "on" or "yes" (case-insensitive), return 107 | * true. 108 | * - If the value is one of "false", "off" or "no" (case-insensitive), return 109 | * false. 110 | * 111 | * @param database The database to query. 112 | * @param res_name The fully qualified resource name string. 113 | * @param res_class The fully qualified resource class string. This argument 114 | * may be left empty / NULL, but if given, it must contain the same number of 115 | * components as res_name. 116 | * @param out Out parameter to which the converted value will be written. 117 | * @returns 0 if the resource was found and converted, -1 if the resource was 118 | * found but could not be converted and -2 if the resource was not found. 119 | */ 120 | int xcb_xrm_resource_get_bool(xcb_xrm_database_t *database, 121 | const char *res_name, const char *res_class, bool *out) { 122 | char *value; 123 | long converted; 124 | 125 | if (xcb_xrm_resource_get_string(database, res_name, res_class, &value) < 0 || value == NULL) { 126 | *out = false; 127 | return -2; 128 | } 129 | 130 | /* Let's first see if the value can be parsed into an integer directly. */ 131 | if (str2long(&converted, value, 10) == 0) { 132 | FREE(value); 133 | *out = converted; 134 | return 0; 135 | } 136 | 137 | /* Next up, we take care of signal words. */ 138 | if (strcasecmp(value, "true") == 0 || 139 | strcasecmp(value, "on") == 0 || 140 | strcasecmp(value, "yes") == 0) { 141 | FREE(value); 142 | *out = true; 143 | return 0; 144 | } 145 | 146 | if (strcasecmp(value, "false") == 0 || 147 | strcasecmp(value, "off") == 0 || 148 | strcasecmp(value, "no") == 0) { 149 | FREE(value); 150 | *out = false; 151 | return 0; 152 | } 153 | 154 | FREE(value); 155 | *out = false; 156 | return -1; 157 | } 158 | 159 | static int __resource_get(xcb_xrm_database_t *database, const char *res_name, const char *res_class, 160 | xcb_xrm_resource_t **_resource) { 161 | xcb_xrm_resource_t *resource; 162 | xcb_xrm_entry_t *query_name = NULL; 163 | xcb_xrm_entry_t *query_class = NULL; 164 | int result = SUCCESS; 165 | 166 | if (database == NULL || TAILQ_EMPTY(database)) { 167 | *_resource = NULL; 168 | return -FAILURE; 169 | } 170 | 171 | *_resource = calloc(1, sizeof(struct xcb_xrm_resource_t)); 172 | if (_resource == NULL) { 173 | result = -FAILURE; 174 | goto done; 175 | } 176 | resource = *_resource; 177 | 178 | if (res_name == NULL || xcb_xrm_entry_parse(res_name, &query_name, true) < 0) { 179 | result = -FAILURE; 180 | goto done; 181 | } 182 | 183 | /* For the resource class input, we allow NULL and empty string as 184 | * placeholders for not specifying this string. Technically this is 185 | * violating the spec, but it seems to be widely used. */ 186 | if (res_class != NULL && strlen(res_class) > 0 && 187 | xcb_xrm_entry_parse(res_class, &query_class, true) < 0) { 188 | result = -1; 189 | goto done; 190 | } 191 | 192 | /* We rely on name and class query strings to have the same number of 193 | * components, so let's check that this is the case. The specification 194 | * backs us up here. */ 195 | if (query_class != NULL && 196 | __xcb_xrm_entry_num_components(query_name) != __xcb_xrm_entry_num_components(query_class)) { 197 | result = -1; 198 | goto done; 199 | } 200 | 201 | result = __xcb_xrm_match(database, query_name, query_class, resource); 202 | done: 203 | xcb_xrm_entry_free(query_name); 204 | xcb_xrm_entry_free(query_class); 205 | return result; 206 | } 207 | 208 | static void __resource_free(xcb_xrm_resource_t *resource) { 209 | if (resource == NULL) 210 | return; 211 | 212 | FREE(resource->value); 213 | FREE(resource); 214 | } 215 | -------------------------------------------------------------------------------- /src/util.c: -------------------------------------------------------------------------------- 1 | /* 2 | * vim:ts=4:sw=4:expandtab 3 | * 4 | * Copyright © 2016 Ingo Bürk 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a 7 | * copy of this software and associated documentation files (the "Software"), 8 | * to deal in the Software without restriction, including without limitation 9 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | * and/or sell copies of the Software, and to permit persons to whom the 11 | * Software is furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 20 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | * 23 | * Except as contained in this notice, the names of the authors or their 24 | * institutions shall not be used in advertising or otherwise to promote the 25 | * sale, use or other dealings in this Software without prior written 26 | * authorization from the authors. 27 | * 28 | */ 29 | #include "externals.h" 30 | 31 | #include "util.h" 32 | 33 | int str2long(long *out, const char *input, const int base) { 34 | char *end; 35 | long result; 36 | 37 | if (input[0] == '\0' || isspace(input[0])) 38 | return -FAILURE; 39 | 40 | errno = 0; 41 | result = strtol(input, &end, base); 42 | if (errno == ERANGE && result == LONG_MAX) 43 | return -FAILURE; 44 | if (errno == ERANGE && result == LONG_MIN) 45 | return -FAILURE; 46 | if (*end != '\0') 47 | return -FAILURE; 48 | 49 | *out = result; 50 | return SUCCESS; 51 | } 52 | 53 | char *get_home_dir_file(const char *filename) { 54 | char *result; 55 | 56 | char *home = getenv("HOME"); 57 | if (home == NULL) 58 | return NULL; 59 | 60 | if (asprintf(&result, "%s/%s", home, filename) < 0) 61 | return NULL; 62 | 63 | return result; 64 | } 65 | 66 | char *resolve_path(const char *path, const char *_base) { 67 | char *base; 68 | char *result; 69 | 70 | if (path[0] == '/') 71 | return strdup(path); 72 | 73 | base = (_base == NULL) ? getcwd(NULL, 0) : strdup(_base); 74 | if (base == NULL) 75 | return NULL; 76 | 77 | if (asprintf(&result, "%s/%s", base, path) < 0) { 78 | FREE(base); 79 | return NULL; 80 | } 81 | FREE(base); 82 | 83 | return result; 84 | } 85 | 86 | char *file_get_contents(const char *filename) { 87 | FILE *file; 88 | struct stat stbuf; 89 | size_t file_size; 90 | char *content; 91 | 92 | if ((file = fopen(filename, "rb")) == NULL) 93 | return NULL; 94 | 95 | /* We want to read the file in one go, so figure out the file size. */ 96 | if (fstat(fileno(file), &stbuf) < 0) { 97 | fclose(file); 98 | return NULL; 99 | } 100 | file_size = stbuf.st_size; 101 | 102 | /* Read the file content. */ 103 | content = calloc(file_size + 1, 1); 104 | if (content == NULL) { 105 | fclose(file); 106 | return NULL; 107 | } 108 | 109 | if (fread(content, 1, file_size, file) != file_size) { 110 | FREE(content); 111 | fclose(file); 112 | return NULL; 113 | } 114 | 115 | fclose(file); 116 | content[file_size] = '\0'; 117 | return content; 118 | } 119 | 120 | char *xcb_util_get_property(xcb_connection_t *conn, xcb_window_t window, xcb_atom_t atom, 121 | xcb_atom_t type, size_t size) { 122 | xcb_get_property_cookie_t cookie; 123 | xcb_get_property_reply_t *reply; 124 | xcb_generic_error_t *err; 125 | int reply_length; 126 | char *content; 127 | 128 | cookie = xcb_get_property(conn, 0, window, atom, type, 0, size); 129 | reply = xcb_get_property_reply(conn, cookie, &err); 130 | if (err != NULL) { 131 | FREE(err); 132 | return NULL; 133 | } 134 | 135 | if (reply == NULL || (reply_length = xcb_get_property_value_length(reply)) == 0) { 136 | FREE(reply); 137 | return NULL; 138 | } 139 | 140 | if (reply->bytes_after > 0) { 141 | size_t adjusted_size = size + ceil(reply->bytes_after / 4.0); 142 | FREE(reply); 143 | return xcb_util_get_property(conn, window, atom, type, adjusted_size); 144 | } 145 | 146 | if (asprintf(&content, "%.*s", reply_length, (char *)xcb_get_property_value(reply)) < 0) { 147 | FREE(reply); 148 | return NULL; 149 | } 150 | 151 | FREE(reply); 152 | return content; 153 | } 154 | -------------------------------------------------------------------------------- /tests/resources/1/sub/xresources3: -------------------------------------------------------------------------------- 1 | Third: 3 2 | -------------------------------------------------------------------------------- /tests/resources/1/xresources1: -------------------------------------------------------------------------------- 1 | First: 1 2 | 3 | #include "xresources2" 4 | -------------------------------------------------------------------------------- /tests/resources/1/xresources2: -------------------------------------------------------------------------------- 1 | #include "sub/xresources3" 2 | Second: 2 3 | -------------------------------------------------------------------------------- /tests/resources/2/.Xresources: -------------------------------------------------------------------------------- 1 | First: 1 2 | -------------------------------------------------------------------------------- /tests/resources/2/xenvironment: -------------------------------------------------------------------------------- 1 | Second: 2 2 | -------------------------------------------------------------------------------- /tests/resources/3/loop.xresources: -------------------------------------------------------------------------------- 1 | First: 1 2 | ! Provoke an endless chain of self-inclusion 3 | #include "loop.xresources" 4 | Second: 2 5 | -------------------------------------------------------------------------------- /tests/tests_database.c: -------------------------------------------------------------------------------- 1 | /* Copyright © 2016 Ingo Bürk 2 | * 3 | * Permission is hereby granted, free of charge, to any person obtaining a 4 | * copy of this software and associated documentation files (the "Software"), 5 | * to deal in the Software without restriction, including without limitation 6 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | * and/or sell copies of the Software, and to permit persons to whom the 8 | * Software is furnished to do so, subject to the following conditions: 9 | * 10 | * The above copyright notice and this permission notice shall be included in 11 | * all copies or substantial portions of the Software. 12 | * 13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | * AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 17 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | * 20 | * Except as contained in this notice, the names of the authors or their 21 | * institutions shall not be used in advertising or otherwise to promote the 22 | * sale, use or other dealings in this Software without prior written 23 | * authorization from the authors. 24 | */ 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include 32 | #include 33 | 34 | #include "tests_utils.h" 35 | 36 | /* Forward declarations */ 37 | static int test_put_resource(void); 38 | static int test_combine_databases(void); 39 | static int test_from_file(void); 40 | static void setup(void); 41 | static void cleanup(void); 42 | 43 | xcb_connection_t *conn; 44 | xcb_screen_t *screen; 45 | 46 | int main(void) { 47 | bool err = false; 48 | 49 | setup(); 50 | err |= test_put_resource(); 51 | err |= test_combine_databases(); 52 | err |= test_from_file(); 53 | cleanup(); 54 | 55 | return err; 56 | } 57 | 58 | static int test_put_resource(void) { 59 | bool err = false; 60 | 61 | xcb_xrm_database_t *database = NULL; 62 | xcb_xrm_database_put_resource(&database, "First", "1"); 63 | xcb_xrm_database_put_resource(&database, "First*second", "2"); 64 | xcb_xrm_database_put_resource(&database, "Third", " a\\ b\nc d\te "); 65 | xcb_xrm_database_put_resource(&database, "Fourth", "\t\ta\\ b\nc d\te "); 66 | err |= check_database(database, 67 | "First: 1\n" 68 | "First*second: 2\n" 69 | "Third: \\ a\\\\ b\\nc d\te \n" 70 | "Fourth: \\\t\ta\\\\ b\\nc d\te \n"); 71 | 72 | xcb_xrm_database_put_resource(&database, "First", "3"); 73 | xcb_xrm_database_put_resource(&database, "First*second", "4"); 74 | xcb_xrm_database_put_resource(&database, "Third", "x"); 75 | xcb_xrm_database_put_resource(&database, "Fourth", "x"); 76 | err |= check_database(database, 77 | "First: 3\n" 78 | "First*second: 4\n" 79 | "Third: x\n" 80 | "Fourth: x\n"); 81 | 82 | xcb_xrm_database_put_resource_line(&database, "Second:xyz"); 83 | xcb_xrm_database_put_resource_line(&database, "Third: xyz"); 84 | xcb_xrm_database_put_resource_line(&database, "*Fifth.sixth*seventh.?.eigth*?*last: xyz"); 85 | err |= check_database(database, 86 | "First: 3\n" 87 | "First*second: 4\n" 88 | "Fourth: x\n" 89 | "Second: xyz\n" 90 | "Third: xyz\n" 91 | "*Fifth.sixth*seventh.?.eigth*?*last: xyz\n"); 92 | 93 | xcb_xrm_database_free(database); 94 | return err; 95 | } 96 | 97 | static int test_combine_databases(void) { 98 | bool err = false; 99 | 100 | xcb_xrm_database_t *source_db; 101 | xcb_xrm_database_t *target_db; 102 | 103 | source_db = xcb_xrm_database_from_string( 104 | "a1.b1*c1: 1\n" 105 | "a2.b2: 2\n" 106 | "a3: 3\n"); 107 | target_db = xcb_xrm_database_from_string( 108 | "a3: 0\n" 109 | "a1.b1*c1: 0\n" 110 | "a4.?.b4: 0\n"); 111 | xcb_xrm_database_combine(source_db, &target_db, false); 112 | err |= check_database(target_db, 113 | "a3: 0\n" 114 | "a1.b1*c1: 0\n" 115 | "a4.?.b4: 0\n" 116 | "a2.b2: 2\n"); 117 | xcb_xrm_database_free(source_db); 118 | xcb_xrm_database_free(target_db); 119 | 120 | source_db = xcb_xrm_database_from_string( 121 | "a1.b1*c1: 1\n" 122 | "a2.b2: 2\n" 123 | "a3: 3\n"); 124 | target_db = xcb_xrm_database_from_string( 125 | "a3: 0\n" 126 | "a1.b1*c1: 0\n" 127 | "a4.?.b4: 0\n"); 128 | xcb_xrm_database_combine(source_db, &target_db, true); 129 | err |= check_database(target_db, 130 | "a4.?.b4: 0\n" 131 | "a1.b1*c1: 1\n" 132 | "a2.b2: 2\n" 133 | "a3: 3\n"); 134 | xcb_xrm_database_free(source_db); 135 | xcb_xrm_database_free(target_db); 136 | 137 | return err; 138 | } 139 | 140 | static void set_env_var_to_path(const char *var, const char *srcdir, const char *path) { 141 | char *buffer; 142 | asprintf(&buffer, "%s/%s", srcdir, path); 143 | setenv(var, buffer, true); 144 | free(buffer); 145 | } 146 | 147 | static int test_from_file(void) { 148 | bool err = false; 149 | xcb_xrm_database_t *database; 150 | char *path; 151 | const char *srcdir; 152 | 153 | /* Set by automake, needed for out-of-tree builds */ 154 | srcdir = getenv("srcdir"); 155 | if (srcdir == NULL) 156 | srcdir = "."; 157 | 158 | /* Test xcb_xrm_database_from_file with relative #include directives */ 159 | asprintf(&path, "%s/tests/resources/1/xresources1", srcdir); 160 | database = xcb_xrm_database_from_file(path); 161 | free(path); 162 | err |= check_database(database, 163 | "First: 1\n" 164 | "Third: 3\n" 165 | "Second: 2\n"); 166 | xcb_xrm_database_free(database); 167 | 168 | /* Test that the inclusion depth is limited */ 169 | asprintf(&path, "%s/tests/resources/3/loop.xresources", srcdir); 170 | database = xcb_xrm_database_from_file(path); 171 | free(path); 172 | err |= check_database(database, 173 | "First: 1\n" 174 | "Second: 2\n"); 175 | xcb_xrm_database_free(database); 176 | 177 | /* Test xcb_xrm_database_from_default for resolution of $HOME. */ 178 | set_env_var_to_path("HOME", srcdir, "tests/resources/2"); 179 | set_env_var_to_path("XENVIRONMENT", srcdir, "tests/resources/2/xenvironment"); 180 | database = xcb_xrm_database_from_default(conn); 181 | err |= check_database(database, 182 | "First: 1\n" 183 | "Second: 2\n"); 184 | xcb_xrm_database_free(database); 185 | 186 | /* Test xcb_xrm_database_from_resource_manager. */ 187 | xcb_change_property_checked(conn, XCB_PROP_MODE_REPLACE, screen->root, XCB_ATOM_RESOURCE_MANAGER, 188 | XCB_ATOM_STRING, 8, strlen("First: 1\n*Second: 2") + 1, "First: 1\n*Second: 2\0"); 189 | xcb_flush(conn); 190 | database = xcb_xrm_database_from_resource_manager(conn, screen); 191 | err |= check_database(database, 192 | "First: 1\n" 193 | "*Second: 2\n"); 194 | xcb_xrm_database_free(database); 195 | 196 | return err; 197 | } 198 | 199 | static void setup(void) { 200 | int screennr; 201 | conn = xcb_connect(NULL, &screennr); 202 | if (xcb_connection_has_error(conn)) { 203 | fprintf(stderr, "Failed to connect to X11 server.\n"); 204 | exit(EXIT_FAILURE); 205 | } 206 | 207 | screen = xcb_aux_get_screen(conn, 0); 208 | } 209 | 210 | static void cleanup(void) { 211 | xcb_generic_event_t *ev; 212 | 213 | xcb_aux_sync(conn); 214 | while ((ev = xcb_poll_for_event(conn))) { 215 | if (ev->response_type == 0) { 216 | puts("X11 error occurred"); 217 | exit(EXIT_FAILURE); 218 | } 219 | free(ev); 220 | } 221 | if (xcb_connection_has_error(conn)) { 222 | fprintf(stderr, "The X11 connection broke at runtime.\n"); 223 | exit(EXIT_FAILURE); 224 | } 225 | xcb_disconnect(conn); 226 | } 227 | -------------------------------------------------------------------------------- /tests/tests_database_runner.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | command -v xvfb-run > /dev/null || exit 77 # Skip test if no xvfb-run in $PATH 3 | exec xvfb-run tests/tests_database 4 | -------------------------------------------------------------------------------- /tests/tests_match.c: -------------------------------------------------------------------------------- 1 | /* Copyright © 2016 Ingo Bürk 2 | * 3 | * Permission is hereby granted, free of charge, to any person obtaining a 4 | * copy of this software and associated documentation files (the "Software"), 5 | * to deal in the Software without restriction, including without limitation 6 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | * and/or sell copies of the Software, and to permit persons to whom the 8 | * Software is furnished to do so, subject to the following conditions: 9 | * 10 | * The above copyright notice and this permission notice shall be included in 11 | * all copies or substantial portions of the Software. 12 | * 13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | * AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 17 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | * 20 | * Except as contained in this notice, the names of the authors or their 21 | * institutions shall not be used in advertising or otherwise to promote the 22 | * sale, use or other dealings in this Software without prior written 23 | * authorization from the authors. 24 | */ 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #include 33 | 34 | #include "tests_utils.h" 35 | 36 | /* Forward declarations */ 37 | static int test_get_resource(void); 38 | static int test_convert(void); 39 | static void setup(void); 40 | static void cleanup(void); 41 | 42 | static char *check_get_resource_xlib(const char *str_database, const char *res_name, const char *res_class); 43 | static int check_get_resource(const char *database, const char *res_name, const char *res_class, const char *value, 44 | bool expected_xlib_mismatch); 45 | static int check_convert_to_long(const char *value, const long expected, int expected_return_code); 46 | static int check_convert_to_bool(const char *value, const bool expected, int expected_return_code); 47 | 48 | int main(void) { 49 | bool err = false; 50 | 51 | setup(); 52 | err |= test_get_resource(); 53 | cleanup(); 54 | 55 | err |= test_convert(); 56 | 57 | return err; 58 | } 59 | 60 | static int test_get_resource(void) { 61 | bool err = false; 62 | 63 | /* Non-matches / Errors */ 64 | err |= check_get_resource("", "", "", NULL, false); 65 | err |= check_get_resource("", NULL, "", NULL, false); 66 | err |= check_get_resource("", "", NULL, NULL, false); 67 | err |= check_get_resource("", NULL, NULL, NULL, false); 68 | /* Xlib returns the match here, despite the query violating the specs. */ 69 | err |= check_get_resource("First.second: 1", "First.second", "First.second.third", NULL, true); 70 | err |= check_get_resource("", "First.second", "", NULL, false); 71 | err |= check_get_resource("First.second: 1", "First.third", "", NULL, false); 72 | err |= check_get_resource("First.second: 1", "First", "", NULL, false); 73 | err |= check_get_resource("First: 1", "First.second", "", NULL, false); 74 | err |= check_get_resource("First.?.fourth: 1", "First.second.third.fourth", "", NULL, false); 75 | err |= check_get_resource("First*?.third: 1", "First.third", "", NULL, false); 76 | err |= check_get_resource("First: 1", "first", "", NULL, false); 77 | err |= check_get_resource("First: 1", "", "first", NULL, false); 78 | /* Duplicate entries */ 79 | err |= check_get_resource( 80 | "First: 1\n" 81 | "First: 2\n" 82 | "First: 3\n", 83 | "First", "", "3", false); 84 | err |= check_get_resource( 85 | "First: 1\n" 86 | "Second: 2\n" 87 | "Second: 3\n" 88 | "Third: 4\n", 89 | "Second", "", "3", false); 90 | 91 | /* Basic matching */ 92 | err |= check_get_resource("First: 1", "First", "", "1", false); 93 | err |= check_get_resource("First.second: 1", "First.second", "", "1", false); 94 | err |= check_get_resource("?.second: 1", "First.second", "", "1", false); 95 | err |= check_get_resource("First.?.third: 1", "First.second.third", "", "1", false); 96 | err |= check_get_resource("First.?.?.fourth: 1", "First.second.third.fourth", "", "1", false); 97 | err |= check_get_resource("*second: 1", "First.second", "", "1", false); 98 | err |= check_get_resource(".second: 1", "First.second", "", NULL, false); 99 | err |= check_get_resource("*third: 1", "First.second.third", "", "1", false); 100 | err |= check_get_resource("First*second: 1", "First.second", "", "1", false); 101 | err |= check_get_resource("First*third: 1", "First.second.third", "", "1", false); 102 | err |= check_get_resource("First*fourth: 1", "First.second.third.fourth", "", "1", false); 103 | err |= check_get_resource("First*?.third: 1", "First.second.third", "", "1", false); 104 | err |= check_get_resource("First: 1", "Second", "First", "1", false); 105 | err |= check_get_resource("First.second: 1", "First.third", "first.second", "1", false); 106 | err |= check_get_resource("First.second.third: 1", "First.third.third", "first.second.fourth", "1", false); 107 | err |= check_get_resource("First*third*fifth: 1", "First.second.third.fourth.third.fifth", "", "1", false); 108 | err |= check_get_resource("First: x\\\ny", "First", "", "xy", false); 109 | err |= check_get_resource("! First: x", "First", "", NULL, false); 110 | err |= check_get_resource("# First: x", "First", "", NULL, false); 111 | err |= check_get_resource("First:", "First", "", "", false); 112 | err |= check_get_resource("First: ", "First", "", "", false); 113 | err |= check_get_resource("First: \t ", "First", "", "", false); 114 | /* Consecutive bindings */ 115 | err |= check_get_resource("*.bar: 1", "foo.foo.bar", "", "1", false); 116 | err |= check_get_resource("...bar: 1", "foo.bar", "", NULL, false); 117 | err |= check_get_resource("...bar: 1", "foo.foo.foo.bar", "", NULL, false); 118 | err |= check_get_resource("***bar: 1", "foo.bar", "", "1", false); 119 | err |= check_get_resource(".*.bar: 1", "foo.bar", "", "1", false); 120 | err |= check_get_resource(".*.bar: 1", "foo.foo.bar", "", "1", false); 121 | err |= check_get_resource("..*bar: 1", "foo.foo.foo.foo.bar", "", "1", false); 122 | err |= check_get_resource("a.*.z: 1", "a.b.c.d.e.f.z", "", "1", false); 123 | err |= check_get_resource("a...z: 1", "a.z", "", "1", false); 124 | err |= check_get_resource("a...z: 1", "a.b.z", "", NULL, false); 125 | /* Matching among multiple entries */ 126 | err |= check_get_resource( 127 | "First: 1\n" 128 | "Second: 2\n", 129 | "First", "", "1", false); 130 | err |= check_get_resource( 131 | "First: 1\n" 132 | "Second: 2\n", 133 | "Second", "", "2", false); 134 | /* Greediness */ 135 | err |= check_get_resource("a*c.e: 1", "a.b.c.d.c.e", "", "1", false); 136 | err |= check_get_resource("a*c.e: 1", "a.b.c.c.e", "", "1", false); 137 | err |= check_get_resource("a*?.e: 1", "a.b.c.e", "", "1", false); 138 | err |= check_get_resource("a*c*e: 1", "a.b.c.d.c.d.e.d.e", "", "1", false); 139 | 140 | /* Precedence rules */ 141 | /* Rule 1 */ 142 | err |= check_get_resource( 143 | "First.second.third: 1\n" 144 | "First*third: 2\n", 145 | "First.second.third", "", "1", false); 146 | err |= check_get_resource( 147 | "First*third: 2\n" 148 | "First.second.third: 1\n", 149 | "First.second.third", "", "1", false); 150 | err |= check_get_resource( 151 | "First.second.third: 1\n" 152 | "First*third: 2\n", 153 | "x.x.x", "First.second.third", "1", false); 154 | err |= check_get_resource( 155 | "First*third: 2\n" 156 | "First.second.third: 1\n", 157 | "x.x.x", "First.second.third", "1", false); 158 | 159 | /* Rule 2 */ 160 | err |= check_get_resource( 161 | "First.second: 1\n" 162 | "First.third: 2\n", 163 | "First.second", "First.third", "1", false); 164 | err |= check_get_resource( 165 | "First.third: 2\n" 166 | "First.second: 1\n", 167 | "First.second", "First.third", "1", false); 168 | err |= check_get_resource( 169 | "First.second.third: 1\n" 170 | "First.?.third: 2\n", 171 | "First.second.third", "", "1", false); 172 | err |= check_get_resource( 173 | "First.?.third: 2\n" 174 | "First.second.third: 1\n", 175 | "First.second.third", "", "1", false); 176 | err |= check_get_resource( 177 | "First.second.third: 1\n" 178 | "First.?.third: 2\n", 179 | "x.x.x", "First.second.third", "1", false); 180 | err |= check_get_resource( 181 | "First.?.third: 2\n" 182 | "First.second.third: 1\n", 183 | "x.x.x", "First.second.third", "1", false); 184 | /* Rule 3 */ 185 | err |= check_get_resource( 186 | "First.second: 1\n" 187 | "First*second: 2\n", 188 | "First.second", "", "1", false); 189 | err |= check_get_resource( 190 | "First*second: 2\n" 191 | "First.second: 1\n", 192 | "First.second", "", "1", false); 193 | 194 | /* Some real world examples. May contain duplicates to the above tests. */ 195 | 196 | /* From the specification: 197 | * https://tronche.com/gui/x/xlib/resource-manager/matching-rules.html */ 198 | err |= check_get_resource( 199 | "xmh*Paned*activeForeground: red\n" 200 | "*incorporate.Foreground: blue\n" 201 | "xmh.toc*Command*activeForeground: green\n" 202 | "xmh.toc*?.Foreground: white\n" 203 | "xmh.toc*Command.activeForeground: black", 204 | "xmh.toc.messagefunctions.incorporate.activeForeground", 205 | "Xmh.Paned.Box.Command.Foreground", 206 | "black", false); 207 | err |= check_get_resource("urxvt*background: [95]#000", "urxvt.background", "", "[95]#000", false); 208 | err |= check_get_resource("urxvt*scrollBar_right:true", "urxvt.scrollBar_right", "", "true", false); 209 | err |= check_get_resource("urxvt*cutchars: '\"'()*<>[]{|}", "urxvt.cutchars", "", "'\"'()*<>[]{|}", false); 210 | err |= check_get_resource("urxvt.keysym.Control-Shift-Up: perl:font:increment", "urxvt.keysym.Control-Shift-Up", 211 | "", "perl:font:increment", false); 212 | err |= check_get_resource("rofi.normal: #000000, #000000, #000000, #000000", "rofi.normal", "", 213 | "#000000, #000000, #000000, #000000", false); 214 | 215 | return err; 216 | } 217 | 218 | static int test_convert(void) { 219 | bool err = false; 220 | 221 | err |= check_convert_to_bool(NULL, false, -2); 222 | err |= check_convert_to_bool("", false, -1); 223 | err |= check_convert_to_bool("0", false, 0); 224 | err |= check_convert_to_bool("1", true, 0); 225 | err |= check_convert_to_bool("10", true, 0); 226 | err |= check_convert_to_bool("true", true, 0); 227 | err |= check_convert_to_bool("TRUE", true, 0); 228 | err |= check_convert_to_bool("false", false, 0); 229 | err |= check_convert_to_bool("FALSE", false, 0); 230 | err |= check_convert_to_bool("on", true, 0); 231 | err |= check_convert_to_bool("ON", true, 0); 232 | err |= check_convert_to_bool("off", false, 0); 233 | err |= check_convert_to_bool("OFF", false, 0); 234 | err |= check_convert_to_bool("yes", true, 0); 235 | err |= check_convert_to_bool("YES", true, 0); 236 | err |= check_convert_to_bool("no", false, 0); 237 | err |= check_convert_to_bool("NO", false, 0); 238 | err |= check_convert_to_bool("abc", false, -1); 239 | 240 | err |= check_convert_to_long(NULL, LONG_MIN, -2); 241 | err |= check_convert_to_long("", LONG_MIN, -1); 242 | err |= check_convert_to_long("0", 0, 0); 243 | err |= check_convert_to_long("1", 1, 0); 244 | err |= check_convert_to_long("-1", -1, 0); 245 | err |= check_convert_to_long("100", 100, 0); 246 | 247 | return err; 248 | } 249 | 250 | static char *check_get_resource_xlib(const char *str_database, const char *res_name, const char *res_class) { 251 | int res_code; 252 | char *res_type; 253 | XrmValue res_value; 254 | char *result; 255 | 256 | XrmDatabase database = XrmGetStringDatabase(str_database); 257 | res_code = XrmGetResource(database, res_name, res_class, &res_type, &res_value); 258 | 259 | if (res_code) { 260 | result = strdup((char *)res_value.addr); 261 | } else { 262 | result = NULL; 263 | } 264 | 265 | XrmDestroyDatabase(database); 266 | return result; 267 | } 268 | 269 | static int check_get_resource(const char *str_database, const char *res_name, const char *res_class, const char *value, 270 | bool expected_xlib_mismatch) { 271 | xcb_xrm_database_t *database; 272 | 273 | bool err = false; 274 | char *xcb_value; 275 | char *xlib_value; 276 | 277 | fprintf(stderr, "== Assert that getting resource <%s> / <%s> returns <%s>\n", 278 | res_name, res_class, value); 279 | 280 | database = xcb_xrm_database_from_string(str_database); 281 | if (xcb_xrm_resource_get_string(database, res_name, res_class, &xcb_value) < 0) { 282 | if (value != NULL) { 283 | fprintf(stderr, "xcb_xrm_resource_get_string() returned NULL\n"); 284 | err = true; 285 | } 286 | 287 | if (!expected_xlib_mismatch) { 288 | xlib_value = check_get_resource_xlib(str_database, res_name, res_class); 289 | err |= check_strings(NULL, xlib_value, "Returned NULL, but Xlib returned <%s>\n", xlib_value); 290 | if (xlib_value != NULL) 291 | free(xlib_value); 292 | } 293 | 294 | goto done_get_resource; 295 | } 296 | 297 | err |= check_strings(value, xcb_value, "Expected <%s>, but got <%s>\n", value, xcb_value); 298 | free(xcb_value); 299 | 300 | if (!expected_xlib_mismatch) { 301 | /* And for good measure, also compare it against Xlib. */ 302 | xlib_value = check_get_resource_xlib(str_database, res_name, res_class); 303 | err |= check_strings(value, xlib_value, "Xlib returns <%s>, but expected <%s>\n", 304 | xlib_value, value); 305 | if (xlib_value != NULL) 306 | free(xlib_value); 307 | } 308 | 309 | done_get_resource: 310 | xcb_xrm_database_free(database); 311 | return err; 312 | } 313 | 314 | static int check_convert_to_long(const char *value, const long expected, int expected_return_code) { 315 | char *db_str = NULL; 316 | long actual; 317 | int actual_return_code; 318 | xcb_xrm_database_t *database; 319 | 320 | fprintf(stderr, "== Assert that <%s> is converted to long value <%ld>\n", value, expected); 321 | 322 | if (value != NULL) 323 | asprintf(&db_str, "x: %s\n", value); 324 | 325 | database = xcb_xrm_database_from_string(db_str); 326 | free(db_str); 327 | actual_return_code = xcb_xrm_resource_get_long(database, "x", NULL, &actual); 328 | xcb_xrm_database_free(database); 329 | 330 | return check_ints(expected_return_code, actual_return_code, "Expected <%d>, but found <%d>\n", 331 | expected_return_code, actual_return_code) || 332 | check_longs(expected, actual, "Expected <%ld>, but found <%ld>\n", expected, actual); 333 | } 334 | 335 | static int check_convert_to_bool(const char *value, const bool expected, const int expected_return_code) { 336 | char *db_str = NULL; 337 | bool actual; 338 | int actual_return_code; 339 | xcb_xrm_database_t *database; 340 | 341 | fprintf(stderr, "== Assert that <%s> is converted to boolean value <%d>\n", value, expected); 342 | 343 | if (value != NULL) 344 | asprintf(&db_str, "x: %s\n", value); 345 | 346 | database = xcb_xrm_database_from_string(db_str); 347 | free(db_str); 348 | actual_return_code = xcb_xrm_resource_get_bool(database, "x", NULL, &actual); 349 | xcb_xrm_database_free(database); 350 | 351 | return check_ints(expected_return_code, actual_return_code, "Expected <%d>, but found <%d>\n", 352 | expected_return_code, actual_return_code) || 353 | check_ints(expected, actual, "Expected <%d>, but found <%d>\n", expected, actual); 354 | } 355 | 356 | static void setup(void) { 357 | XrmInitialize(); 358 | } 359 | 360 | static void cleanup(void) { 361 | } 362 | -------------------------------------------------------------------------------- /tests/tests_parser.c: -------------------------------------------------------------------------------- 1 | /* Copyright © 2016 Ingo Bürk 2 | * 3 | * Permission is hereby granted, free of charge, to any person obtaining a 4 | * copy of this software and associated documentation files (the "Software"), 5 | * to deal in the Software without restriction, including without limitation 6 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | * and/or sell copies of the Software, and to permit persons to whom the 8 | * Software is furnished to do so, subject to the following conditions: 9 | * 10 | * The above copyright notice and this permission notice shall be included in 11 | * all copies or substantial portions of the Software. 12 | * 13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | * AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 17 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | * 20 | * Except as contained in this notice, the names of the authors or their 21 | * institutions shall not be used in advertising or otherwise to promote the 22 | * sale, use or other dealings in this Software without prior written 23 | * authorization from the authors. 24 | */ 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include "tests_utils.h" 32 | 33 | /* We need this because to need to access non-public API. */ 34 | #include "database.h" 35 | #include "resource.h" 36 | 37 | /* Forward declarations */ 38 | static int check_parse_entry(const char *str, const char *value, const char *bindings, const int count, ...); 39 | static int check_parse_entry_error(const char *str, const int result); 40 | 41 | static bool check_parse_entry_resource_only; 42 | 43 | int main(void) { 44 | bool err = false; 45 | 46 | check_parse_entry_resource_only = false; 47 | 48 | /* Basics */ 49 | err |= check_parse_entry("First: 1", "1", ".", 1, "First"); 50 | err |= check_parse_entry("First.second: 1", "1", "..", 2, "First", "second"); 51 | err |= check_parse_entry("First..second: 1", "1", "..", 2, "First", "second"); 52 | /* Wildcards */ 53 | err |= check_parse_entry("?.second: 1", "1", "..", 2, "?", "second"); 54 | err |= check_parse_entry("First.?.third: 1", "1", "...", 3, "First", "?", "third"); 55 | /* Loose bindings */ 56 | err |= check_parse_entry("*second: 1", "1", "*", 1, "second"); 57 | err |= check_parse_entry("First*third: 1", "1", ".*", 2, "First", "third"); 58 | err |= check_parse_entry("First**third: 1", "1", ".*", 2, "First", "third"); 59 | /* Combinations */ 60 | err |= check_parse_entry("First*?.fourth: 1", "1", ".*.", 3, "First", "?", "fourth"); 61 | /* Values */ 62 | err |= check_parse_entry("First: 1337", "1337", ".", 1, "First"); 63 | err |= check_parse_entry("First: -1337", "-1337", ".", 1, "First"); 64 | err |= check_parse_entry("First: 13.37", "13.37", ".", 1, "First"); 65 | err |= check_parse_entry("First: value", "value", ".", 1, "First"); 66 | err |= check_parse_entry("First: #abcdef", "#abcdef", ".", 1, "First"); 67 | err |= check_parse_entry("First: { key: 'value' }", "{ key: 'value' }", ".", 1, "First"); 68 | err |= check_parse_entry("First: x?y", "x?y", ".", 1, "First"); 69 | err |= check_parse_entry("First: x*y", "x*y", ".", 1, "First"); 70 | /* Whitespace */ 71 | err |= check_parse_entry("First: x", "x", ".", 1, "First"); 72 | err |= check_parse_entry("First: x ", "x ", ".", 1, "First"); 73 | err |= check_parse_entry("First: x ", "x ", ".", 1, "First"); 74 | err |= check_parse_entry("First:x", "x", ".", 1, "First"); 75 | err |= check_parse_entry("First: \t x", "x", ".", 1, "First"); 76 | err |= check_parse_entry("First: \t x \t", "x \t", ".", 1, "First"); 77 | /* Special characters */ 78 | err |= check_parse_entry("First: \\ x", " x", ".", 1, "First"); 79 | err |= check_parse_entry("First: x\\ x", "x x", ".", 1, "First"); 80 | err |= check_parse_entry("First: \\\tx", "\tx", ".", 1, "First"); 81 | err |= check_parse_entry("First: \\011x", "\tx", ".", 1, "First"); 82 | err |= check_parse_entry("First: x\\\\x", "x\\x", ".", 1, "First"); 83 | err |= check_parse_entry("First: x\\nx", "x\nx", ".", 1, "First"); 84 | err |= check_parse_entry("First: \\080", "\\080", ".", 1, "First"); 85 | err |= check_parse_entry("First: \\00a", "\\00a", ".", 1, "First"); 86 | 87 | /* Invalid entries */ 88 | err |= check_parse_entry_error(": 1", -1); 89 | err |= check_parse_entry_error("?: 1", -1); 90 | err |= check_parse_entry_error("First", -1); 91 | err |= check_parse_entry_error("First second", -1); 92 | err |= check_parse_entry_error("First.?: 1", -1); 93 | err |= check_parse_entry_error("Först: 1", -1); 94 | err |= check_parse_entry_error("F~rst: 1", -1); 95 | 96 | /* Buffer size tests. Let's hope you don't have line wrapping enabled. */ 97 | err |= check_parse_entry( 98 | "First: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", 99 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", 100 | ".", 1, "First"); 101 | err |= check_parse_entry( 102 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy: 1", 103 | "1", "..", 2, 104 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", 105 | "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"); 106 | 107 | /* Test for parsing a resource used for queries. */ 108 | check_parse_entry_resource_only = true; 109 | err |= check_parse_entry("First.second", NULL, "..", 2, "First", "second"); 110 | err |= check_parse_entry_error("First.second: on", -1); 111 | err |= check_parse_entry_error("First*second", -1); 112 | err |= check_parse_entry_error("First.?.second", -1); 113 | err |= check_parse_entry_error("*second", -1); 114 | err |= check_parse_entry_error("?.second", -1); 115 | 116 | return err; 117 | } 118 | 119 | static int check_parse_entry(const char *str, const char *value, const char *bindings, const int count, ...) { 120 | bool err = false; 121 | xcb_xrm_entry_t *entry; 122 | xcb_xrm_component_t *component; 123 | int actual_length = 0; 124 | int i = 0; 125 | va_list ap; 126 | 127 | fprintf(stderr, "== Assert that parsing \"%s\" is successful\n", str); 128 | 129 | if (xcb_xrm_entry_parse(str, &entry, check_parse_entry_resource_only) < 0) { 130 | fprintf(stderr, "xcb_xrm_entry_parse() < 0\n"); 131 | return true; 132 | } 133 | 134 | if (!check_parse_entry_resource_only) { 135 | /* Assert the entry's value. */ 136 | err |= check_strings(value, entry->value, "Wrong entry value: <%s> / <%s>\n", value, entry->value); 137 | } else { 138 | err |= check_strings(NULL, entry->value, "Expected no value, but found <%s>\n", entry->value); 139 | } 140 | 141 | /* Assert the number of components. */ 142 | TAILQ_FOREACH(component, &(entry->components), components) { 143 | actual_length++; 144 | } 145 | err |= check_ints(count, actual_length, "Wrong number of components: <%d> / <%d>\n", count, actual_length); 146 | 147 | /* Assert the individual components. */ 148 | va_start(ap, count); 149 | TAILQ_FOREACH(component, &(entry->components), components) { 150 | const char *curr = va_arg(ap, const char *); 151 | char tmp[2] = "\0"; 152 | 153 | switch (component->type) { 154 | case CT_WILDCARD: 155 | err |= check_strings("?", curr, "Expected '?', but got <%s>\n", curr); 156 | break; 157 | case CT_NORMAL: 158 | err |= check_strings(component->name, curr, "Expected <%s>, but got <%s>\n", component->name, curr); 159 | break; 160 | default: 161 | err = true; 162 | break; 163 | } 164 | 165 | tmp[0] = bindings[i++]; 166 | switch (component->binding_type) { 167 | case BT_TIGHT: 168 | err |= check_strings(tmp, ".", "Expected <%s>, but got <.>\n", tmp); 169 | break; 170 | case BT_LOOSE: 171 | err |= check_strings(tmp, "*", "Expected <%s>, but got <*>\n", tmp); 172 | break; 173 | default: 174 | err = true; 175 | break; 176 | } 177 | } 178 | va_end(ap); 179 | 180 | xcb_xrm_entry_free(entry); 181 | return err; 182 | } 183 | 184 | static int check_parse_entry_error(const char *str, const int result) { 185 | xcb_xrm_entry_t *entry; 186 | int actual; 187 | 188 | fprintf(stderr, "== Assert that parsing \"%s\" returns <%d>\n", str, result); 189 | 190 | actual = xcb_xrm_entry_parse(str, &entry, check_parse_entry_resource_only); 191 | xcb_xrm_entry_free(entry); 192 | return check_ints(result, actual, "Wrong result code: <%d> / <%d>\n", result, actual); 193 | } 194 | -------------------------------------------------------------------------------- /tests/tests_utils.c: -------------------------------------------------------------------------------- 1 | /* Copyright © 2016 Ingo Bürk 2 | * 3 | * Permission is hereby granted, free of charge, to any person obtaining a 4 | * copy of this software and associated documentation files (the "Software"), 5 | * to deal in the Software without restriction, including without limitation 6 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | * and/or sell copies of the Software, and to permit persons to whom the 8 | * Software is furnished to do so, subject to the following conditions: 9 | * 10 | * The above copyright notice and this permission notice shall be included in 11 | * all copies or substantial portions of the Software. 12 | * 13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | * AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 17 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | * 20 | * Except as contained in this notice, the names of the authors or their 21 | * institutions shall not be used in advertising or otherwise to promote the 22 | * sale, use or other dealings in this Software without prior written 23 | * authorization from the authors. 24 | */ 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include "tests_utils.h" 32 | 33 | bool check_strings(const char *expected, const char *actual, const char *format, ...) { 34 | va_list ap; 35 | 36 | if (expected == NULL && actual == NULL) 37 | return false; 38 | 39 | if (expected != NULL && actual != NULL && strcmp(expected, actual) == 0) 40 | return false; 41 | 42 | va_start(ap, format); 43 | vfprintf(stderr, format, ap); 44 | va_end(ap); 45 | return true; 46 | } 47 | 48 | bool check_ints(const int expected, const int actual, const char *format, ...) { 49 | va_list ap; 50 | 51 | if (expected == actual) 52 | return false; 53 | 54 | va_start(ap, format); 55 | vfprintf(stderr, format, ap); 56 | va_end(ap); 57 | return true; 58 | } 59 | 60 | bool check_longs(const long expected, const long actual, const char *format, ...) { 61 | va_list ap; 62 | 63 | if (expected == actual) 64 | return false; 65 | 66 | va_start(ap, format); 67 | vfprintf(stderr, format, ap); 68 | va_end(ap); 69 | return true; 70 | } 71 | 72 | int check_database(xcb_xrm_database_t *database, const char *expected) { 73 | bool err = false; 74 | char *actual = xcb_xrm_database_to_string(database); 75 | 76 | fprintf(stderr, "== Assert that database is correct.\n"); 77 | err |= check_strings(expected, actual, "Expected database <%s>, but found <%s>\n", expected, actual); 78 | 79 | if (actual != NULL) 80 | free(actual); 81 | return err; 82 | } 83 | -------------------------------------------------------------------------------- /tests/tests_utils.h: -------------------------------------------------------------------------------- 1 | /* 2 | * vim:ts=4:sw=4:expandtab 3 | * 4 | * Copyright © 2016 Ingo Bürk 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a 7 | * copy of this software and associated documentation files (the "Software"), 8 | * to deal in the Software without restriction, including without limitation 9 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | * and/or sell copies of the Software, and to permit persons to whom the 11 | * Software is furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 20 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | * 23 | * Except as contained in this notice, the names of the authors or their 24 | * institutions shall not be used in advertising or otherwise to promote the 25 | * sale, use or other dealings in this Software without prior written 26 | * authorization from the authors. 27 | * 28 | */ 29 | #ifndef __TESTS_UTILS_H__ 30 | #define __TESTS_UTILS_H__ 31 | 32 | #include "xcb_xrm.h" 33 | 34 | #if defined(__GNUC__) && ((__GNUC__ * 100 + __GNUC_MINOR__) >= 203) 35 | #define ATTRIBUTE_PRINTF(x,y) __attribute__((__format__(__printf__,x,y))) 36 | #else 37 | #define ATTRIBUTE_PRINTF(x,y) 38 | #endif 39 | 40 | #define SKIP 77 41 | 42 | bool check_strings(const char *expected, const char *actual, const char *format, ...) ATTRIBUTE_PRINTF(3, 4); 43 | 44 | bool check_ints(const int expected, const int actual, const char *format, ...) ATTRIBUTE_PRINTF(3, 4); 45 | 46 | bool check_longs(const long expected, const long actual, const char *format, ...) ATTRIBUTE_PRINTF(3, 4); 47 | 48 | int check_database(xcb_xrm_database_t *database, const char *expected); 49 | 50 | #endif /* __TESTS_UTILS_H__ */ 51 | -------------------------------------------------------------------------------- /xcb-xrm.pc.in: -------------------------------------------------------------------------------- 1 | prefix=@prefix@ 2 | exec_prefix=@exec_prefix@ 3 | libdir=@libdir@ 4 | includedir=@includedir@ 5 | 6 | Name: XCB XRM library 7 | Description: XCB X resource manager utility functions 8 | Version: @PACKAGE_VERSION@ 9 | Requires: xcb 10 | Requires.private: xcb-aux 11 | Libs: -L${libdir} -lxcb-xrm @LIBS@ 12 | Cflags: -I${includedir} 13 | -------------------------------------------------------------------------------- /xcb_xrm_intro.in: -------------------------------------------------------------------------------- 1 | /** 2 | @file 3 | @brief X resource manager utility functions for XCB 4 | 5 | These functions allow access to the X resource manager. 6 | */ 7 | 8 | /** 9 | 10 | @author Ingo Bürk 11 | @date 2016 12 | 13 | */ 14 | --------------------------------------------------------------------------------