├── .gitignore ├── CMakeLists.txt ├── COPYING ├── README.md ├── SConstruct ├── common.cc ├── common.h ├── common_test.cc ├── config_parser.cc ├── config_parser.h ├── config_parser_test.cc ├── data_reader.cc ├── data_reader.h ├── data_writer.cc ├── data_writer.h ├── dump_xsettings.1 ├── dump_xsettings.cc ├── setting.cc ├── setting.h ├── setting_test.cc ├── settings_manager.cc ├── settings_manager.h ├── xsettingsd.1 ├── xsettingsd.cc └── xsettingsd.service.in /.gitignore: -------------------------------------------------------------------------------- 1 | .*.swp 2 | .sconsign.dblite 3 | dump_xsettings 4 | xsettingsd 5 | *.[ao] 6 | *_test 7 | build-stamp 8 | /build/ 9 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | 3 | project(xsettingsd 4 | VERSION 1.0.2 5 | DESCRIPTION "Provides settings to X11 applications via the XSETTINGS specification" 6 | HOMEPAGE_URL "https://github.com/derat/xsettingsd" 7 | LANGUAGES CXX 8 | ) 9 | 10 | include(GNUInstallDirs) 11 | include(CTest) 12 | 13 | find_package(X11 REQUIRED) 14 | include_directories(${X11_INCLUDE_DIR}) 15 | find_package(GTest) 16 | 17 | add_library(libxsettingsd STATIC 18 | common.cc 19 | config_parser.cc 20 | data_reader.cc 21 | data_writer.cc 22 | setting.cc 23 | settings_manager.cc 24 | ) 25 | 26 | add_executable(xsettingsd xsettingsd.cc) 27 | target_link_libraries(xsettingsd PRIVATE libxsettingsd X11::X11) 28 | 29 | add_executable(dump_xsettings dump_xsettings.cc) 30 | target_link_libraries(dump_xsettings PRIVATE libxsettingsd X11::X11) 31 | 32 | install(TARGETS xsettingsd dump_xsettings DESTINATION ${CMAKE_INSTALL_BINDIR}) 33 | install(FILES xsettingsd.1 dump_xsettings.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1) 34 | 35 | configure_file(xsettingsd.service.in xsettingsd.service) 36 | install(FILES "${CMAKE_CURRENT_BINARY_DIR}/xsettingsd.service" DESTINATION lib/systemd/user) 37 | 38 | if(GTEST_FOUND AND BUILD_TESTING) 39 | include(GoogleTest) 40 | 41 | add_executable(common_test common_test.cc) 42 | target_link_libraries(common_test PRIVATE libxsettingsd GTest::GTest) 43 | gtest_discover_tests(common_test) 44 | 45 | add_executable(config_parser_test config_parser_test.cc) 46 | target_link_libraries(config_parser_test PRIVATE libxsettingsd GTest::GTest) 47 | target_compile_definitions(config_parser_test PRIVATE __TESTING) 48 | gtest_discover_tests(config_parser_test) 49 | 50 | add_executable(setting_test setting_test.cc) 51 | target_link_libraries(setting_test PRIVATE libxsettingsd GTest::GTest) 52 | target_compile_options(setting_test PRIVATE -Wno-narrowing) 53 | gtest_discover_tests(setting_test) 54 | endif() 55 | 56 | add_custom_target(uninstall COMMAND xargs rm -v < "${CMAKE_BINARY_DIR}/install_manifest.txt") 57 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009, Daniel Erat 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | * Neither the name of Daniel Erat nor the names of this software's other 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 22 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28 | POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Moved to 2 | -------------------------------------------------------------------------------- /SConstruct: -------------------------------------------------------------------------------- 1 | # -*- python -*- 2 | # Copyright 2009 Daniel Erat 3 | # All rights reserved. 4 | 5 | import os 6 | import subprocess 7 | 8 | Help(''' 9 | Type: 'scons xsettingsd' to build xsettingsd 10 | 'scons dump_xsettings' to build dump_xsettings 11 | 'scons test' to build and run all tests 12 | ''') 13 | 14 | 15 | def run_tests(target, source, env): 16 | '''Run all test binaries listed in 'source'.''' 17 | 18 | tests = sorted([str(t) for t in source]) 19 | max_length = max([len(t) for t in tests]) 20 | 21 | for test in tests: 22 | padded_name = test + ' ' * (max_length - len(test)) 23 | 24 | proc = subprocess.Popen('./%s' % test, 25 | stdout=subprocess.PIPE, 26 | stderr=subprocess.STDOUT, 27 | close_fds=True) 28 | [stdout, stderr] = proc.communicate() 29 | proc.wait() 30 | if proc.returncode == 0: 31 | print('%s OK' % padded_name) 32 | else: 33 | print("%s FAILED\n" % padded_name) 34 | print(stdout) 35 | 36 | run_tests_builder = Builder(action=run_tests) 37 | 38 | 39 | env = Environment( 40 | BUILDERS = { 41 | 'RunTests': run_tests_builder, 42 | }) 43 | 44 | env.Append(CPPFLAGS=os.environ.get('CPPFLAGS', ''), 45 | CFLAGS=os.environ.get('CFLAGS', ''), 46 | CXXFLAGS=os.environ.get('CXXFLAGS', ''), 47 | LINKFLAGS=os.environ.get('LDFLAGS', '')) 48 | 49 | env.Append(CCFLAGS='-Wall -Werror -Wno-narrowing') 50 | 51 | 52 | srcs = Split('''\ 53 | common.cc 54 | config_parser.cc 55 | data_reader.cc 56 | data_writer.cc 57 | setting.cc 58 | settings_manager.cc 59 | ''') 60 | libxsettingsd = env.Library('xsettingsd', srcs) 61 | env['LIBS'] = libxsettingsd 62 | env.ParseConfig('pkg-config --cflags --libs x11') 63 | 64 | xsettingsd = env.Program('xsettingsd', 'xsettingsd.cc') 65 | dump_xsettings = env.Program('dump_xsettings', 'dump_xsettings.cc') 66 | 67 | Default([xsettingsd, dump_xsettings]) 68 | 69 | 70 | gtest_env = env.Clone() 71 | gtest_env.Append(CCFLAGS='-I/usr/src/gtest') 72 | gtest_env.VariantDir('.', '/usr/src/gtest/src', duplicate=0) 73 | libgtest = gtest_env.Library('gtest', 'gtest-all.cc') 74 | 75 | test_env = env.Clone() 76 | test_env.Append(CCFLAGS='-D__TESTING') 77 | test_env['LIBS'] += [libgtest, 'pthread'] 78 | 79 | tests = [] 80 | for file in Glob('*_test.cc', strings=True): 81 | tests += test_env.Program(file) 82 | test_env.RunTests('test', tests) 83 | 84 | -------------------------------------------------------------------------------- /common.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2009 Daniel Erat 2 | // All rights reserved. 3 | 4 | #include "common.h" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | using std::string; 11 | using std::vector; 12 | 13 | namespace xsettingsd { 14 | 15 | string StringPrintf(const char* format, ...) { 16 | char buffer[1024]; 17 | va_list argp; 18 | va_start(argp, format); 19 | vsnprintf(buffer, sizeof(buffer), format, argp); 20 | va_end(argp); 21 | return string(buffer); 22 | } 23 | 24 | vector SplitString(const string& str, const string& delim) { 25 | if (str.empty()) 26 | return vector(); 27 | if (delim.empty()) 28 | return vector(1, str); 29 | 30 | vector parts; 31 | size_t start = 0; 32 | while (start <= str.size()) { 33 | if (start == str.size()) { 34 | parts.push_back(string()); 35 | break; 36 | } 37 | size_t next = str.find(delim, start); 38 | if (next == string::npos) { 39 | parts.push_back(str.substr(start, str.size() - start)); 40 | break; 41 | } 42 | parts.push_back(str.substr(start, next - start)); 43 | start = next + delim.size(); 44 | } 45 | return parts; 46 | } 47 | 48 | bool IsLittleEndian() { 49 | int i = 1; 50 | return reinterpret_cast(&i)[0]; 51 | } 52 | 53 | int GetPadding(int length, int increment) { 54 | return (increment - (length % increment)) % increment; 55 | // From xsettings-common.h in Owen Taylor's reference implementation -- 56 | // "n" is length and "m" is increment, I think. This produces results 57 | // that don't seem like padding, though: when "n" is 2 and "m" is 4, it 58 | // produces 4. 59 | //return ((n + m - 1) & (~(m - 1))); 60 | } 61 | 62 | vector GetDefaultConfigFilePaths() { 63 | vector paths; 64 | 65 | // Try ~/.xsettingsd first. 66 | const char* home_dir = getenv("HOME"); 67 | if (home_dir) 68 | paths.push_back(StringPrintf("%s/.xsettingsd", home_dir)); 69 | 70 | // Next look under $XDG_CONFIG_HOME, or in $HOME/.config if $XDG_CONFIG_HOME 71 | // is unset. 72 | vector xdg_dirs; 73 | const char* xdg_config_home = getenv("XDG_CONFIG_HOME"); 74 | if (xdg_config_home && xdg_config_home[0] != '\0') 75 | xdg_dirs.push_back(xdg_config_home); 76 | else if (home_dir) 77 | xdg_dirs.push_back(StringPrintf("%s/.config", home_dir)); 78 | 79 | // Finally split the colon-delimited $XDG_CONFIG_DIRS variable. 80 | const char* xdg_config_dirs = getenv("XDG_CONFIG_DIRS"); 81 | if (xdg_config_dirs) { 82 | vector split_dirs = SplitString(xdg_config_dirs, ":"); 83 | xdg_dirs.insert(xdg_dirs.end(), split_dirs.begin(), split_dirs.end()); 84 | } else { 85 | xdg_dirs.push_back("/etc"); 86 | } 87 | 88 | for (size_t i = 0; i < xdg_dirs.size(); ++i) { 89 | paths.push_back(StringPrintf("%s/xsettingsd/xsettingsd.conf", 90 | xdg_dirs[i].c_str())); 91 | } 92 | 93 | return paths; 94 | } 95 | 96 | const char* kProgName = "xsettingsd"; 97 | 98 | } // namespace xsettingsd 99 | -------------------------------------------------------------------------------- /common.h: -------------------------------------------------------------------------------- 1 | // Copyright 2009 Daniel Erat 2 | // All rights reserved. 3 | 4 | #ifndef __XSETTINGSD_COMMON_H__ 5 | #define __XSETTINGSD_COMMON_H__ 6 | 7 | #include 8 | #include 9 | 10 | namespace xsettingsd { 11 | 12 | #define DISALLOW_COPY_AND_ASSIGN(class_name) \ 13 | class_name(const class_name&); \ 14 | void operator=(const class_name&) 15 | 16 | std::string StringPrintf(const char* format, ...); 17 | 18 | // Splits |str| along |delim|. Repeated occurrences of |delim| in |str| will 19 | // result in empty strings in the output. An empty |delim| will result in |str| 20 | // being returned unsplit. An empty |str| will result in an empty vector. 21 | std::vector SplitString(const std::string& str, 22 | const std::string& delim); 23 | 24 | bool IsLittleEndian(); 25 | 26 | int GetPadding(int length, int increment); 27 | 28 | // Returns $HOME/.xsettingsd followed by all of the config file locations 29 | // specified by the XDG Base Directory Specification 30 | // (http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html). 31 | std::vector GetDefaultConfigFilePaths(); 32 | 33 | extern const char* kProgName; 34 | 35 | } // namespace xsettingsd 36 | 37 | #endif 38 | -------------------------------------------------------------------------------- /common_test.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2009 Daniel Erat 2 | // All rights reserved. 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | #include "common.h" 11 | 12 | using std::string; 13 | using std::vector; 14 | 15 | namespace xsettingsd { 16 | namespace { 17 | 18 | // Returns |parts| joined by '|'. 19 | string Join(const vector& parts) { 20 | string out; 21 | for (size_t i = 0; i < parts.size(); ++i) 22 | out += (i > 0 ? "|" : "") + parts[i]; 23 | return out; 24 | } 25 | 26 | } // namespace 27 | 28 | TEST(CommonTest, SplitString) { 29 | EXPECT_EQ("a|b|c", Join(SplitString("a,b,c", ","))); 30 | EXPECT_EQ("a|b|", Join(SplitString("a,b,", ","))); 31 | EXPECT_EQ("|a|b", Join(SplitString(",a,b", ","))); 32 | EXPECT_EQ("|a|b|", Join(SplitString(",a,b,", ","))); 33 | EXPECT_EQ("a||b", Join(SplitString("a,,b", ","))); 34 | EXPECT_EQ("|", Join(SplitString(",", ","))); 35 | EXPECT_EQ("foo", Join(SplitString("foo", ","))); 36 | EXPECT_EQ("foo|bar", Join(SplitString("fooabcbar", "abc"))); 37 | EXPECT_EQ("|foo", Join(SplitString("abcfoo", "abc"))); 38 | EXPECT_EQ("foo|", Join(SplitString("fooabc", "abc"))); 39 | EXPECT_EQ(0, SplitString("", ",").size()); 40 | EXPECT_EQ(0, SplitString("", "").size()); 41 | EXPECT_EQ("abc", Join(SplitString("abc", ""))); 42 | } 43 | 44 | TEST(CommonTest, GetPadding) { 45 | EXPECT_EQ(0, GetPadding(0, 4)); 46 | EXPECT_EQ(3, GetPadding(1, 4)); 47 | EXPECT_EQ(2, GetPadding(2, 4)); 48 | EXPECT_EQ(1, GetPadding(3, 4)); 49 | EXPECT_EQ(0, GetPadding(4, 4)); 50 | EXPECT_EQ(3, GetPadding(5, 4)); 51 | EXPECT_EQ(2, GetPadding(6, 4)); 52 | EXPECT_EQ(1, GetPadding(7, 4)); 53 | EXPECT_EQ(0, GetPadding(8, 4)); 54 | } 55 | 56 | TEST(CommonTest, GetDefaultConfigFilePath) { 57 | // With $HOME missing and none of the XDG vars, we should just use /etc. 58 | ASSERT_EQ(0, unsetenv("HOME")); 59 | ASSERT_EQ(0, unsetenv("XDG_CONFIG_HOME")); 60 | ASSERT_EQ(0, unsetenv("XDG_CONFIG_DIRS")); 61 | EXPECT_EQ("/etc/xsettingsd/xsettingsd.conf", 62 | Join(GetDefaultConfigFilePaths())); 63 | 64 | // Now set $HOME. It should be searched first, followed by the default XDG 65 | // paths. 66 | ASSERT_EQ(0, setenv("HOME", "/home/user", 1 /* overwrite */)); 67 | EXPECT_EQ("/home/user/.xsettingsd|" 68 | "/home/user/.config/xsettingsd/xsettingsd.conf|" 69 | "/etc/xsettingsd/xsettingsd.conf", 70 | Join(GetDefaultConfigFilePaths())); 71 | 72 | // Use a custom $XDG_CONFIG_HOME. 73 | ASSERT_EQ(0, setenv("XDG_CONFIG_HOME", "/home/user/.myconf", 1)); 74 | EXPECT_EQ("/home/user/.xsettingsd|" 75 | "/home/user/.myconf/xsettingsd/xsettingsd.conf|" 76 | "/etc/xsettingsd/xsettingsd.conf", 77 | Join(GetDefaultConfigFilePaths())); 78 | 79 | // Now put a few paths in $XDG_CONFIG_DIRS. 80 | ASSERT_EQ(0, setenv("XDG_CONFIG_DIRS", "/etc2:/etc3", 1)); 81 | EXPECT_EQ("/home/user/.xsettingsd|" 82 | "/home/user/.myconf/xsettingsd/xsettingsd.conf|" 83 | "/etc2/xsettingsd/xsettingsd.conf|" 84 | "/etc3/xsettingsd/xsettingsd.conf", 85 | Join(GetDefaultConfigFilePaths())); 86 | } 87 | 88 | } // namespace xsettingsd 89 | 90 | int main(int argc, char** argv) { 91 | testing::InitGoogleTest(&argc, argv); 92 | return RUN_ALL_TESTS(); 93 | } 94 | -------------------------------------------------------------------------------- /config_parser.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2009 Daniel Erat 2 | // All rights reserved. 3 | 4 | #include "config_parser.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "setting.h" 14 | 15 | using std::map; 16 | using std::string; 17 | using std::vector; 18 | 19 | namespace xsettingsd { 20 | 21 | ConfigParser::ConfigParser(CharStream* stream) 22 | : stream_(NULL), 23 | error_line_num_(0) { 24 | Reset(stream); 25 | } 26 | 27 | ConfigParser::~ConfigParser() { 28 | delete stream_; 29 | } 30 | 31 | string ConfigParser::FormatError() const { 32 | if (error_line_num_ == 0) 33 | return error_str_; 34 | return StringPrintf("%d: %s", error_line_num_, error_str_.c_str()); 35 | } 36 | 37 | void ConfigParser::Reset(CharStream* stream) { 38 | assert(stream); 39 | delete stream_; 40 | stream_ = stream; 41 | 42 | error_line_num_ = 0; 43 | error_str_.clear(); 44 | } 45 | 46 | bool ConfigParser::Parse(SettingsMap* settings, 47 | const SettingsMap* prev_settings, 48 | uint32_t serial) { 49 | assert(settings); 50 | settings->mutable_map()->clear(); 51 | 52 | string stream_error; 53 | if (!stream_->Init(&stream_error)) { 54 | SetErrorF("Couldn't init stream (%s)", stream_error.c_str()); 55 | return false; 56 | } 57 | 58 | enum State { 59 | // At the beginning of a line, before we've encountered a setting name. 60 | NO_SETTING_NAME = 0, 61 | 62 | // We've gotten the setting name but not its value. 63 | GOT_SETTING_NAME, 64 | 65 | // We've got the value. 66 | GOT_VALUE, 67 | }; 68 | State state = NO_SETTING_NAME; 69 | string setting_name; 70 | bool in_comment = false; 71 | 72 | while (!stream_->AtEOF()) { 73 | int ch = stream_->GetChar(); 74 | 75 | if (ch == '#') { 76 | in_comment = true; 77 | continue; 78 | } 79 | 80 | if (ch == '\n') { 81 | if (state == GOT_SETTING_NAME) { 82 | SetErrorF("No value for setting \"%s\"", setting_name.c_str()); 83 | return false; 84 | } 85 | state = NO_SETTING_NAME; 86 | setting_name.clear(); 87 | in_comment = false; 88 | } 89 | 90 | if (in_comment || isspace(ch)) 91 | continue; 92 | 93 | stream_->UngetChar(ch); 94 | 95 | switch (state) { 96 | case NO_SETTING_NAME: 97 | if (!ReadSettingName(&setting_name)) 98 | return false; 99 | if (settings->map().count(setting_name)) { 100 | SetErrorF("Got duplicate setting name \"%s\"", setting_name.c_str()); 101 | return false; 102 | } 103 | state = GOT_SETTING_NAME; 104 | break; 105 | case GOT_SETTING_NAME: 106 | { 107 | Setting* setting = NULL; 108 | if (!ReadValue(&setting)) 109 | return false; 110 | const Setting* prev_setting = 111 | prev_settings ? prev_settings->GetSetting(setting_name) : NULL; 112 | setting->UpdateSerial(prev_setting, serial); 113 | settings->mutable_map()->insert(make_pair(setting_name, setting)); 114 | } 115 | state = GOT_VALUE; 116 | break; 117 | case GOT_VALUE: 118 | SetErrorF("Got unexpected text after value"); 119 | return false; 120 | } 121 | } 122 | 123 | if (state != NO_SETTING_NAME && state != GOT_VALUE) { 124 | SetErrorF("Unexpected end of file"); 125 | return false; 126 | } 127 | return true; 128 | } 129 | 130 | bool ConfigParser::CharStream::Init(string* error_out) { 131 | assert(!initialized_); 132 | initialized_ = true; 133 | return InitImpl(error_out); 134 | } 135 | 136 | bool ConfigParser::CharStream::AtEOF() { 137 | assert(initialized_); 138 | return (!have_buffered_char_ && AtEOFImpl()); 139 | } 140 | 141 | int ConfigParser::CharStream::GetChar() { 142 | assert(initialized_); 143 | 144 | prev_at_line_end_ = at_line_end_; 145 | if (at_line_end_) { 146 | line_num_++; 147 | at_line_end_ = false; 148 | } 149 | 150 | int ch = 0; 151 | if (have_buffered_char_) { 152 | have_buffered_char_ = false; 153 | ch = buffered_char_; 154 | } else { 155 | ch = GetCharImpl(); 156 | } 157 | 158 | if (ch == '\n') 159 | at_line_end_ = true; 160 | 161 | return ch; 162 | } 163 | 164 | void ConfigParser::CharStream::UngetChar(int ch) { 165 | if (prev_at_line_end_) 166 | line_num_--; 167 | at_line_end_ = prev_at_line_end_; 168 | 169 | assert(initialized_); 170 | assert(!have_buffered_char_); 171 | buffered_char_ = ch; 172 | have_buffered_char_ = true; 173 | } 174 | 175 | ConfigParser::FileCharStream::FileCharStream(const string& filename) 176 | : filename_(filename), 177 | file_(NULL) { 178 | } 179 | 180 | ConfigParser::FileCharStream::~FileCharStream() { 181 | if (file_) { 182 | fclose(file_); 183 | file_ = NULL; 184 | } 185 | } 186 | 187 | bool ConfigParser::FileCharStream::InitImpl(string* error_out) { 188 | assert(!file_); 189 | file_ = fopen(filename_.c_str(), "r"); 190 | if (!file_) { 191 | if (error_out) 192 | *error_out = strerror(errno); 193 | return false; 194 | } 195 | return true; 196 | } 197 | 198 | bool ConfigParser::FileCharStream::AtEOFImpl() { 199 | assert(file_); 200 | int ch = GetChar(); 201 | UngetChar(ch); 202 | return ch == EOF; 203 | } 204 | 205 | int ConfigParser::FileCharStream::GetCharImpl() { 206 | assert(file_); 207 | return fgetc(file_); 208 | } 209 | 210 | ConfigParser::StringCharStream::StringCharStream(const string& data) 211 | : data_(data), 212 | pos_(0) { 213 | } 214 | 215 | bool ConfigParser::StringCharStream::AtEOFImpl() { 216 | return pos_ == data_.size(); 217 | } 218 | 219 | int ConfigParser::StringCharStream::GetCharImpl() { 220 | return data_.at(pos_++); 221 | } 222 | 223 | bool ConfigParser::ReadSettingName(string* name_out) { 224 | assert(name_out); 225 | name_out->clear(); 226 | 227 | bool prev_was_slash = false; 228 | while (true) { 229 | if (stream_->AtEOF()) 230 | break; 231 | 232 | int ch = stream_->GetChar(); 233 | if (isspace(ch) || ch == '#') { 234 | stream_->UngetChar(ch); 235 | break; 236 | } 237 | 238 | if (!(ch >= 'A' && ch <= 'Z') && 239 | !(ch >= 'a' && ch <= 'z') && 240 | !(ch >= '0' && ch <= '9') && 241 | !(ch == '_' || ch == '/')) { 242 | SetErrorF("Got invalid character '%c' in setting name", ch); 243 | return false; 244 | } 245 | 246 | if (ch == '/' && name_out->empty()) { 247 | SetErrorF("Got leading slash in setting name"); 248 | return false; 249 | } 250 | 251 | if (prev_was_slash) { 252 | if (ch == '/') { 253 | SetErrorF("Got two consecutive slashes in setting name"); 254 | return false; 255 | } 256 | if (ch >= '0' && ch <= '9') { 257 | SetErrorF("Got digit after slash in setting name"); 258 | return false; 259 | } 260 | } 261 | 262 | name_out->push_back(ch); 263 | 264 | prev_was_slash = (ch == '/'); 265 | } 266 | 267 | if (name_out->empty()) { 268 | SetErrorF("Got empty setting name"); 269 | return false; 270 | } 271 | 272 | if (name_out->at(name_out->size() - 1) == '/') { 273 | SetErrorF("Got trailing slash in setting name"); 274 | return false; 275 | } 276 | 277 | return true; 278 | } 279 | 280 | bool ConfigParser::ReadValue(Setting** setting_ptr) { 281 | assert(setting_ptr); 282 | *setting_ptr = NULL; 283 | 284 | if (stream_->AtEOF()) { 285 | SetErrorF("Got EOF when starting to read value"); 286 | return false; 287 | } 288 | 289 | int ch = stream_->GetChar(); 290 | stream_->UngetChar(ch); 291 | 292 | if ((ch >= '0' && ch <= '9') || ch == '-') { 293 | int32_t value = 0; 294 | if (!ReadInteger(&value)) 295 | return false; 296 | *setting_ptr = new IntegerSetting(value); 297 | } else if (ch == '"') { 298 | string value; 299 | if (!ReadString(&value)) 300 | return false; 301 | *setting_ptr = new StringSetting(value); 302 | } else if (ch == '(') { 303 | uint16_t red, green, blue, alpha; 304 | if (!ReadColor(&red, &green, &blue, &alpha)) 305 | return false; 306 | *setting_ptr = new ColorSetting(red, green, blue, alpha); 307 | } else { 308 | SetErrorF("Got invalid setting value"); 309 | return false; 310 | } 311 | return true; 312 | } 313 | 314 | bool ConfigParser::ReadInteger(int32_t* int_out) { 315 | assert(int_out); 316 | *int_out = 0; 317 | 318 | bool got_digit = false; 319 | bool negative = false; 320 | while (true) { 321 | if (stream_->AtEOF()) 322 | break; 323 | 324 | int ch = stream_->GetChar(); 325 | if (isspace(ch) || ch == '#') { 326 | stream_->UngetChar(ch); 327 | break; 328 | } 329 | 330 | if (ch == '-') { 331 | if (negative) { 332 | SetErrorF("Got extra '-' before integer"); 333 | return false; 334 | } 335 | 336 | if (!got_digit) { 337 | negative = true; 338 | continue; 339 | } else { 340 | SetErrorF("Got '-' mid-integer"); 341 | return false; 342 | } 343 | } 344 | 345 | if (!(ch >= '0' && ch <= '9')) { 346 | SetErrorF("Got non-numeric character '%c'", ch); 347 | return false; 348 | } 349 | 350 | got_digit = true; 351 | *int_out *= 10; 352 | *int_out += (ch - '0'); 353 | 354 | // TODO: Check for integer overflow (not a security hole; it'd just be 355 | // nice to warn the user that their setting is going to wrap). 356 | } 357 | 358 | if (!got_digit) { 359 | SetErrorF("Got empty integer"); 360 | return false; 361 | } 362 | 363 | if (negative) 364 | *int_out *= -1; 365 | 366 | return true; 367 | } 368 | 369 | bool ConfigParser::ReadString(string* str_out) { 370 | assert(str_out); 371 | str_out->clear(); 372 | 373 | bool in_backslash = false; 374 | if (stream_->AtEOF() || stream_->GetChar() != '\"') { 375 | SetErrorF("String is missing initial double-quote"); 376 | return false; 377 | } 378 | 379 | while (true) { 380 | if (stream_->AtEOF()) { 381 | SetErrorF("Open string at end of file"); 382 | return false; 383 | } 384 | 385 | int ch = stream_->GetChar(); 386 | if (ch == '\n') { 387 | SetErrorF("Got newline mid-string"); 388 | return false; 389 | } 390 | 391 | if (!in_backslash) { 392 | if (ch == '"') 393 | break; 394 | 395 | if (ch == '\\') { 396 | in_backslash = true; 397 | continue; 398 | } 399 | } 400 | 401 | if (in_backslash) { 402 | in_backslash = false; 403 | if (ch == 'n') ch = '\n'; 404 | else if (ch == 't') ch = '\t'; 405 | } 406 | str_out->push_back(ch); 407 | } 408 | 409 | return true; 410 | } 411 | 412 | bool ConfigParser::ReadColor(uint16_t* red_out, 413 | uint16_t* green_out, 414 | uint16_t* blue_out, 415 | uint16_t* alpha_out) { 416 | assert(red_out); 417 | assert(green_out); 418 | assert(blue_out); 419 | assert(alpha_out); 420 | 421 | if (stream_->AtEOF() || stream_->GetChar() != '(') { 422 | SetErrorF("Color is missing initial parethesis"); 423 | return false; 424 | } 425 | 426 | vector nums; 427 | 428 | enum State { 429 | BEFORE_NUM, 430 | IN_NUM, 431 | AFTER_NUM, 432 | }; 433 | int num = 0; 434 | 435 | State state = BEFORE_NUM; 436 | while (true) { 437 | if (stream_->AtEOF()) { 438 | SetErrorF("Got EOF mid-color"); 439 | return false; 440 | } 441 | 442 | int ch = stream_->GetChar(); 443 | if (ch == '\n') { 444 | SetErrorF("Got newline mid-color"); 445 | return false; 446 | } 447 | 448 | if (ch == ')') { 449 | if (state == BEFORE_NUM) { 450 | SetErrorF("Expected number but got ')'"); 451 | return false; 452 | } 453 | if (state == IN_NUM) 454 | nums.push_back(num); 455 | break; 456 | } 457 | 458 | if (isspace(ch)) { 459 | if (state == IN_NUM) { 460 | state = AFTER_NUM; 461 | nums.push_back(num); 462 | } 463 | continue; 464 | } 465 | 466 | if (ch == ',') { 467 | if (state == BEFORE_NUM) { 468 | SetErrorF("Got unexpected comma"); 469 | return false; 470 | } 471 | if (state == IN_NUM) 472 | nums.push_back(num); 473 | state = BEFORE_NUM; 474 | continue; 475 | } 476 | 477 | if (!(ch >= '0' && ch <= '9')) { 478 | SetErrorF("Got non-numeric character '%c'", ch); 479 | return false; 480 | } 481 | 482 | if (state == AFTER_NUM) { 483 | SetErrorF("Got unexpected digit '%c'", ch); 484 | return false; 485 | } 486 | if (state == BEFORE_NUM) { 487 | state = IN_NUM; 488 | num = 0; 489 | } 490 | num = num * 10 + (ch - '0'); 491 | 492 | // TODO: Check for integer overflow (not a security hole; it'd just be 493 | // nice to warn the user that their setting is going to wrap). 494 | } 495 | 496 | if (nums.size() < 3 || nums.size() > 4) { 497 | SetErrorF("Got %d number%s instead of 3 or 4", 498 | nums.size(), (nums.size() == 1 ? "" : "s")); 499 | return false; 500 | } 501 | 502 | *red_out = nums.at(0); 503 | *green_out = nums.at(1); 504 | *blue_out = nums.at(2); 505 | *alpha_out = (nums.size() == 4) ? nums.at(3) : 65535; 506 | return true; 507 | } 508 | 509 | void ConfigParser::SetErrorF(const char* format, ...) { 510 | char buffer[1024]; 511 | va_list argp; 512 | va_start(argp, format); 513 | vsnprintf(buffer, sizeof(buffer), format, argp); 514 | va_end(argp); 515 | error_line_num_ = stream_->line_num(); 516 | error_str_.assign(buffer); 517 | } 518 | 519 | } // namespace xsettingsd 520 | -------------------------------------------------------------------------------- /config_parser.h: -------------------------------------------------------------------------------- 1 | // Copyright 2009 Daniel Erat 2 | // All rights reserved. 3 | 4 | #ifndef __XSETTINGSD_CONFIG_PARSER_H__ 5 | #define __XSETTINGSD_CONFIG_PARSER_H__ 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #ifdef __TESTING 12 | #include 13 | #endif 14 | 15 | #include "common.h" 16 | 17 | namespace xsettingsd { 18 | 19 | class Setting; 20 | class SettingsMap; 21 | 22 | // Doing the parsing by hand like this for a line-based config format is 23 | // pretty much the worst idea ever -- it would've been much easier to use 24 | // libpcrecpp. :-( The tests all pass, though, for whatever that's worth. 25 | class ConfigParser { 26 | public: 27 | class CharStream; 28 | 29 | // The parser takes ownership of 'stream', which must be uninitialized 30 | // (that is, its Init() method shouldn't have been called yet). 31 | explicit ConfigParser(CharStream* stream); 32 | ~ConfigParser(); 33 | 34 | // Returns a formatted string describing a parse error. Can be called 35 | // after Parse() returns false. 36 | std::string FormatError() const; 37 | 38 | // Reset the parser to read from a new stream. 39 | void Reset(CharStream* stream); 40 | 41 | // Parse the data in the stream into 'settings', using 'prev_settings' 42 | // (pass the previous version if it exists or NULL otherwise) and 43 | // 'serial' (the new serial number) to determine which serial number each 44 | // setting should have. This method calls the stream's Init() method; 45 | // don't do it beforehand. 46 | bool Parse(SettingsMap* settings, 47 | const SettingsMap* prev_settings, 48 | uint32_t serial); 49 | 50 | // Abstract base class for reading a stream of characters. 51 | class CharStream { 52 | public: 53 | CharStream() 54 | : initialized_(false), 55 | have_buffered_char_(false), 56 | buffered_char_(0), 57 | at_line_end_(true), 58 | prev_at_line_end_(false), 59 | line_num_(0) { 60 | } 61 | virtual ~CharStream() {} 62 | 63 | int line_num() const { return line_num_; } 64 | 65 | // Must be called before using the stream. 66 | // The stream is unusable if false is returned. 67 | bool Init(std::string* error_out); 68 | 69 | // Are we currently at the end of the stream? 70 | bool AtEOF(); 71 | 72 | // Get the next character in the stream. 73 | int GetChar(); 74 | 75 | // Push a previously-read character back onto the stream. 76 | // At most one character can be buffered. 77 | void UngetChar(int ch); 78 | 79 | private: 80 | virtual bool InitImpl(std::string* error_out) { return true; } 81 | virtual bool AtEOFImpl() = 0; 82 | virtual int GetCharImpl() = 0; 83 | 84 | // Has Init() been called? 85 | bool initialized_; 86 | 87 | // Has a character been returned with UngetChar() but not yet re-read? 88 | bool have_buffered_char_; 89 | 90 | // The character returned by UngetChar(). 91 | int buffered_char_; 92 | 93 | // Are we currently at the end of the line? 94 | bool at_line_end_; 95 | 96 | bool prev_at_line_end_; 97 | 98 | // The line number of the last-fetched character. 99 | int line_num_; 100 | 101 | DISALLOW_COPY_AND_ASSIGN(CharStream); 102 | }; 103 | 104 | // An implementation of CharStream that reads from a file. 105 | class FileCharStream : public CharStream { 106 | public: 107 | FileCharStream(const std::string& filename); 108 | ~FileCharStream(); 109 | 110 | private: 111 | bool InitImpl(std::string* error_out); 112 | bool AtEOFImpl(); 113 | int GetCharImpl(); 114 | 115 | std::string filename_; 116 | FILE* file_; 117 | 118 | DISALLOW_COPY_AND_ASSIGN(FileCharStream); 119 | }; 120 | 121 | // An implementation of CharStream that reads from an in-memory string. 122 | class StringCharStream : public CharStream { 123 | public: 124 | StringCharStream(const std::string& data); 125 | 126 | private: 127 | bool AtEOFImpl(); 128 | int GetCharImpl(); 129 | 130 | std::string data_; 131 | size_t pos_; 132 | 133 | DISALLOW_COPY_AND_ASSIGN(StringCharStream); 134 | }; 135 | 136 | private: 137 | #ifdef __TESTING 138 | friend class ConfigParserTest; 139 | FRIEND_TEST(ConfigParserTest, ReadSettingName); 140 | #endif 141 | 142 | // Read a setting name starting at the current position in the stream. 143 | // Returns false if the setting name is invalid. 144 | bool ReadSettingName(std::string* name_out); 145 | 146 | // Read the value starting at the current position in the stream. 147 | // Its type is inferred from the first character. 'setting_ptr' is 148 | // updated to point at a newly-allocated Setting object (which the caller 149 | // is responsible for deleting). 150 | bool ReadValue(Setting** setting_ptr); 151 | 152 | // Read an integer starting at the current position in the stream. 153 | bool ReadInteger(int32_t* int_out); 154 | 155 | // Read a double-quoted string starting at the current position in the 156 | // stream. 157 | bool ReadString(std::string* str_out); 158 | 159 | // Read a color of the form "(red, green, blue, alpha)" or "(red, green, 160 | // blue)". 161 | bool ReadColor(uint16_t* red_out, uint16_t* green_out, 162 | uint16_t* blue_out, uint16_t* alpha_out); 163 | 164 | // Record an error to 'error_str_', also saving the stream's current line 165 | // number to 'error_line_num_'. 166 | void SetErrorF(const char* format, ...); 167 | 168 | // Stream from which the config is being parsed. 169 | CharStream* stream_; 170 | 171 | // If an error was encountered while parsing, the line number where 172 | // it happened and a string describing it. Line 0 is used for errors 173 | // occuring before making any progress into the file. 174 | int error_line_num_; 175 | std::string error_str_; 176 | 177 | DISALLOW_COPY_AND_ASSIGN(ConfigParser); 178 | }; 179 | 180 | } // namespace 181 | 182 | #endif 183 | -------------------------------------------------------------------------------- /config_parser_test.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2009 Daniel Erat 2 | // All rights reserved. 3 | 4 | #include 5 | #include 6 | #define __STDC_LIMIT_MACROS // needed to get MAX and MIN macros from stdint.h 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | #include "config_parser.h" 13 | #include "setting.h" 14 | 15 | using std::map; 16 | using std::string; 17 | 18 | namespace xsettingsd { 19 | 20 | // Test the basic operation of CharStream. 21 | TEST(CharStreamTest, Basic) { 22 | ConfigParser::StringCharStream stream("012"); 23 | 24 | // Read a character, put it back, and read it again. 25 | ASSERT_TRUE(stream.Init(NULL)); 26 | EXPECT_FALSE(stream.AtEOF()); 27 | EXPECT_EQ('0', stream.GetChar()); 28 | stream.UngetChar('0'); 29 | EXPECT_EQ('0', stream.GetChar()); 30 | 31 | // Do the same thing with the second character... 32 | EXPECT_FALSE(stream.AtEOF()); 33 | EXPECT_EQ('1', stream.GetChar()); 34 | stream.UngetChar('1'); 35 | EXPECT_EQ('1', stream.GetChar()); 36 | 37 | // ... and with the third. We should be at EOF after reading it. 38 | EXPECT_EQ('2', stream.GetChar()); 39 | EXPECT_TRUE(stream.AtEOF()); 40 | stream.UngetChar('2'); 41 | EXPECT_FALSE(stream.AtEOF()); 42 | EXPECT_EQ('2', stream.GetChar()); 43 | EXPECT_TRUE(stream.AtEOF()); 44 | } 45 | 46 | // Test that line numbers are reported correctly as we get and un-get 47 | // characters. 48 | TEST(CharStreamTest, LineNumbers) { 49 | ConfigParser::StringCharStream stream("a\nb\n\nc"); 50 | 51 | // We use line 0 to represent not having read any characters yet. 52 | ASSERT_TRUE(stream.Init(NULL)); 53 | EXPECT_EQ(0, stream.line_num()); 54 | 55 | // Getting the first 'a' should put us on line 1. We move back to 0 when 56 | // we un-get it and back to 1 when we re-get it. 57 | EXPECT_EQ('a', stream.GetChar()); 58 | EXPECT_EQ(1, stream.line_num()); 59 | stream.UngetChar('a'); 60 | EXPECT_EQ(0, stream.line_num()); 61 | EXPECT_EQ('a', stream.GetChar()); 62 | EXPECT_EQ(1, stream.line_num()); 63 | 64 | // The first newline should show up as being on line 1 as well. 65 | EXPECT_EQ('\n', stream.GetChar()); 66 | EXPECT_EQ(1, stream.line_num()); 67 | stream.UngetChar('\n'); 68 | EXPECT_EQ(1, stream.line_num()); 69 | EXPECT_EQ('\n', stream.GetChar()); 70 | EXPECT_EQ(1, stream.line_num()); 71 | 72 | // The 'b' is on line 2... 73 | EXPECT_EQ('b', stream.GetChar()); 74 | EXPECT_EQ(2, stream.line_num()); 75 | stream.UngetChar('b'); 76 | EXPECT_EQ(1, stream.line_num()); 77 | EXPECT_EQ('b', stream.GetChar()); 78 | EXPECT_EQ(2, stream.line_num()); 79 | 80 | // ... as is the first newline after it. 81 | EXPECT_EQ('\n', stream.GetChar()); 82 | EXPECT_EQ(2, stream.line_num()); 83 | stream.UngetChar('\n'); 84 | EXPECT_EQ(2, stream.line_num()); 85 | EXPECT_EQ('\n', stream.GetChar()); 86 | EXPECT_EQ(2, stream.line_num()); 87 | 88 | // The second newline should show up as being on line 3. 89 | EXPECT_EQ('\n', stream.GetChar()); 90 | EXPECT_EQ(3, stream.line_num()); 91 | stream.UngetChar('\n'); 92 | EXPECT_EQ(2, stream.line_num()); 93 | EXPECT_EQ('\n', stream.GetChar()); 94 | EXPECT_EQ(3, stream.line_num()); 95 | 96 | // And the 'c' is on line 4. 97 | EXPECT_EQ('c', stream.GetChar()); 98 | EXPECT_EQ(4, stream.line_num()); 99 | stream.UngetChar('c'); 100 | EXPECT_EQ(3, stream.line_num()); 101 | EXPECT_EQ('c', stream.GetChar()); 102 | EXPECT_EQ(4, stream.line_num()); 103 | 104 | EXPECT_TRUE(stream.AtEOF()); 105 | } 106 | 107 | class ConfigParserTest : public testing::Test { 108 | protected: 109 | // Helper methods to get the return value or string from 110 | // ConfigParser::ReadSettingName(). 111 | bool GetReadSettingNameResult(const string& input) { 112 | bool result; 113 | RunReadSettingName(input, &result, NULL); 114 | return result; 115 | } 116 | string GetReadSettingNameData(const string& input) { 117 | bool result; 118 | string data; 119 | RunReadSettingName(input, &result, &data); 120 | assert(result); 121 | return data; 122 | } 123 | 124 | bool GetReadIntegerResult(const string& input) { 125 | bool result; 126 | RunReadInteger(input, &result, NULL); 127 | return result; 128 | } 129 | int32_t GetReadIntegerData(const string& input) { 130 | bool result; 131 | int32_t data; 132 | RunReadInteger(input, &result, &data); 133 | assert(result); 134 | return data; 135 | } 136 | 137 | bool GetReadStringResult(const string& input) { 138 | bool result; 139 | RunReadString(input, &result, NULL); 140 | return result; 141 | } 142 | string GetReadStringData(const string& input) { 143 | bool result; 144 | string data; 145 | RunReadString(input, &result, &data); 146 | assert(result); 147 | return data; 148 | } 149 | 150 | bool GetReadColorResult(const string& input) { 151 | bool result; 152 | RunReadColor(input, &result, NULL); 153 | return result; 154 | } 155 | string GetReadColorData(const string& input) { 156 | bool result; 157 | string data; 158 | RunReadColor(input, &result, &data); 159 | assert(result); 160 | return data; 161 | } 162 | 163 | private: 164 | void RunReadSettingName(const string& input, 165 | bool* result_out, 166 | string* name_out) { 167 | ConfigParser::CharStream* stream = 168 | new ConfigParser::StringCharStream(input); 169 | assert(stream->Init(NULL)); 170 | ConfigParser parser(stream); 171 | string name; 172 | bool result = parser.ReadSettingName(&name); 173 | if (result_out) 174 | *result_out = result; 175 | if (name_out) 176 | *name_out = name; 177 | } 178 | 179 | void RunReadInteger(const string& input, 180 | bool* result_out, 181 | int32_t* num_out) { 182 | ConfigParser::CharStream* stream = 183 | new ConfigParser::StringCharStream(input); 184 | assert(stream->Init(NULL)); 185 | ConfigParser parser(stream); 186 | int32_t num = 0; 187 | bool result = parser.ReadInteger(&num); 188 | if (result_out) 189 | *result_out = result; 190 | if (num_out) 191 | *num_out = num; 192 | } 193 | 194 | void RunReadString(const string& input, 195 | bool* result_out, 196 | string* str_out) { 197 | ConfigParser::CharStream* stream = 198 | new ConfigParser::StringCharStream(input); 199 | assert(stream->Init(NULL)); 200 | ConfigParser parser(stream); 201 | string str; 202 | bool result = parser.ReadString(&str); 203 | if (result_out) 204 | *result_out = result; 205 | if (str_out) 206 | *str_out = str; 207 | } 208 | 209 | void RunReadColor(const string& input, 210 | bool* result_out, 211 | string* str_out) { 212 | ConfigParser::CharStream* stream = 213 | new ConfigParser::StringCharStream(input); 214 | assert(stream->Init(NULL)); 215 | ConfigParser parser(stream); 216 | uint16_t red = 0, green = 0, blue = 0, alpha = 0; 217 | bool result = parser.ReadColor(&red, &green, &blue, &alpha); 218 | if (result_out) 219 | *result_out = result; 220 | if (str_out) 221 | *str_out = StringPrintf("(%d,%d,%d,%d)", red, green, blue, alpha); 222 | } 223 | }; 224 | 225 | TEST_F(ConfigParserTest, ReadSettingName) { 226 | EXPECT_EQ("test", GetReadSettingNameData("test")); 227 | EXPECT_EQ("First/Second", GetReadSettingNameData("First/Second")); 228 | EXPECT_EQ("Has_Underscore", GetReadSettingNameData("Has_Underscore")); 229 | EXPECT_EQ("trailing_space", GetReadSettingNameData("trailing_space ")); 230 | EXPECT_EQ("blah", GetReadSettingNameData("blah#comment")); 231 | EXPECT_FALSE(GetReadSettingNameResult(" leading_space")); 232 | EXPECT_FALSE(GetReadSettingNameResult("/leading_slash")); 233 | EXPECT_FALSE(GetReadSettingNameResult("trailing_slash/")); 234 | EXPECT_FALSE(GetReadSettingNameResult("double//slash")); 235 | EXPECT_FALSE(GetReadSettingNameResult("digit_after_slash/0")); 236 | 237 | // For good measure, test the examples of legitimate and illegitimate 238 | // names from the spec. 239 | EXPECT_EQ("GTK/colors/background0", 240 | GetReadSettingNameData("GTK/colors/background0")); 241 | EXPECT_EQ("_background", GetReadSettingNameData("_background")); 242 | EXPECT_EQ("_111", GetReadSettingNameData("_111")); 243 | EXPECT_FALSE(GetReadSettingNameResult("/")); 244 | EXPECT_FALSE(GetReadSettingNameResult("_background/")); 245 | EXPECT_FALSE(GetReadSettingNameResult("GTK//colors")); 246 | EXPECT_FALSE(GetReadSettingNameResult("")); 247 | } 248 | 249 | TEST_F(ConfigParserTest, ReadInteger) { 250 | EXPECT_EQ(0, GetReadIntegerData("0")); 251 | EXPECT_EQ(10, GetReadIntegerData("10")); 252 | EXPECT_EQ(12, GetReadIntegerData("0012")); 253 | EXPECT_EQ(15, GetReadIntegerData("15#2 comment")); 254 | EXPECT_EQ(20, GetReadIntegerData("20 ")); 255 | EXPECT_EQ(30, GetReadIntegerData("30\n")); 256 | EXPECT_EQ(INT32_MAX, GetReadIntegerData("2147483647")); 257 | EXPECT_EQ(-5, GetReadIntegerData("-5")); 258 | EXPECT_EQ(INT32_MIN, GetReadIntegerData("-2147483648")); 259 | EXPECT_FALSE(GetReadIntegerResult("")); 260 | EXPECT_FALSE(GetReadIntegerResult("-")); 261 | EXPECT_FALSE(GetReadIntegerResult("--2")); 262 | EXPECT_FALSE(GetReadIntegerResult("2-3")); 263 | EXPECT_FALSE(GetReadIntegerResult(" ")); 264 | EXPECT_FALSE(GetReadIntegerResult(" 23")); 265 | } 266 | 267 | TEST_F(ConfigParserTest, ReadString) { 268 | EXPECT_EQ("test", GetReadStringData("\"test\"")); 269 | EXPECT_EQ(" some whitespace ", GetReadStringData("\" some whitespace \"")); 270 | EXPECT_EQ("a\tb\nc", GetReadStringData("\"a\\tb\\nc\"")); 271 | EXPECT_EQ("a\"b\\c", GetReadStringData("\"a\\\"b\\\\c\"")); 272 | EXPECT_EQ("ar", GetReadStringData("\"\\a\\r\"")); 273 | EXPECT_EQ(" ", GetReadStringData("\" \"")); 274 | EXPECT_EQ("", GetReadStringData("\"\"")); 275 | EXPECT_EQ("a", GetReadStringData("\"a\" ")); 276 | EXPECT_FALSE(GetReadStringResult("")); 277 | EXPECT_FALSE(GetReadStringResult("a")); 278 | EXPECT_FALSE(GetReadStringResult("\"")); 279 | EXPECT_FALSE(GetReadStringResult("\"\n\"")); 280 | } 281 | 282 | TEST_F(ConfigParserTest, ReadColor) { 283 | EXPECT_EQ("(1,2,3,4)", GetReadColorData("(1,2,3,4)")); 284 | EXPECT_EQ("(1,2,3,65535)", GetReadColorData("(1,2,3)")); 285 | EXPECT_EQ("(32768,32769,32770,32771)", 286 | GetReadColorData("( 32768 ,32769 , 32770, 32771 )")); 287 | EXPECT_FALSE(GetReadColorResult("")); 288 | EXPECT_FALSE(GetReadColorResult("(")); 289 | EXPECT_FALSE(GetReadColorResult(")")); 290 | EXPECT_FALSE(GetReadColorResult("()")); 291 | EXPECT_FALSE(GetReadColorResult("( )")); 292 | EXPECT_FALSE(GetReadColorResult("(2)")); 293 | EXPECT_FALSE(GetReadColorResult("(,2)")); 294 | EXPECT_FALSE(GetReadColorResult("(2,)")); 295 | EXPECT_FALSE(GetReadColorResult("(2,3)")); 296 | EXPECT_FALSE(GetReadColorResult("(2,3,4,)")); 297 | EXPECT_FALSE(GetReadColorResult("(2,3,,4)")); 298 | EXPECT_FALSE(GetReadColorResult("(2,3,4,5,)")); 299 | EXPECT_FALSE(GetReadColorResult("(2(,3,4,5)")); 300 | EXPECT_FALSE(GetReadColorResult("(2,3,4,5,6)")); 301 | EXPECT_FALSE(GetReadColorResult("(2a,3,4,5)")); 302 | EXPECT_FALSE(GetReadColorResult("(2 1,3,4,5)")); 303 | EXPECT_FALSE(GetReadColorResult("(2,3,4,5")); 304 | EXPECT_FALSE(GetReadColorResult("(2,3\n,4,5)")); 305 | } 306 | 307 | testing::AssertionResult IntegerSettingEquals( 308 | const char* expected_expr, 309 | const char* actual_expr, 310 | int32_t expected, 311 | const Setting* actual) { 312 | if (!actual) { 313 | testing::Message msg; 314 | msg << "Expected: " << expected << "\n" 315 | << " Actual: " << actual_expr << " is NULL"; 316 | return testing::AssertionFailure(msg); 317 | } 318 | 319 | const IntegerSetting *setting = dynamic_cast(actual); 320 | if (!setting) { 321 | testing::Message msg; 322 | msg << "Expected: " << expected << "\n" 323 | << " Actual: " << actual_expr << " (not an IntegerSetting)"; 324 | return testing::AssertionFailure(msg); 325 | } 326 | 327 | if (setting->value() != expected) { 328 | testing::Message msg; 329 | msg << "Expected: " << expected << "\n" 330 | << " Actual: " << actual_expr << " contains " << setting->value(); 331 | return testing::AssertionFailure(msg); 332 | } 333 | 334 | return testing::AssertionSuccess(); 335 | } 336 | 337 | testing::AssertionResult StringSettingEquals( 338 | const char* expected_expr, 339 | const char* actual_expr, 340 | const string& expected, 341 | const Setting* actual) { 342 | if (!actual) { 343 | testing::Message msg; 344 | msg << "Expected: " << expected << "\n" 345 | << " Actual: " << actual_expr << " is NULL"; 346 | return testing::AssertionFailure(msg); 347 | } 348 | 349 | const StringSetting *setting = dynamic_cast(actual); 350 | if (!setting) { 351 | testing::Message msg; 352 | msg << "Expected: " << expected << "\n" 353 | << " Actual: " << actual_expr << " (not a StringSetting)"; 354 | return testing::AssertionFailure(msg); 355 | } 356 | 357 | if (setting->value() != expected) { 358 | testing::Message msg; 359 | msg << "Expected: \"" << expected << "\"\n" 360 | << " Actual: " << actual_expr << " contains \"" 361 | << setting->value() << "\""; 362 | return testing::AssertionFailure(msg); 363 | } 364 | 365 | return testing::AssertionSuccess(); 366 | } 367 | 368 | testing::AssertionResult ColorSettingEquals( 369 | const char* expected_expr, 370 | const char* actual_expr, 371 | const string& expected_str, 372 | const Setting* actual) { 373 | if (!actual) { 374 | testing::Message msg; 375 | msg << "Expected: " << expected_str << "\n" 376 | << " Actual: " << actual_expr << " is NULL"; 377 | return testing::AssertionFailure(msg); 378 | } 379 | 380 | const ColorSetting *setting = dynamic_cast(actual); 381 | if (!setting) { 382 | testing::Message msg; 383 | msg << "Expected: " << expected_str << "\n" 384 | << " Actual: " << actual_expr << " (not a ColorSetting)"; 385 | return testing::AssertionFailure(msg); 386 | } 387 | 388 | string actual_str = StringPrintf("(%d,%d,%d,%d)", 389 | setting->red(), setting->green(), 390 | setting->blue(), setting->alpha()); 391 | if (actual_str != expected_str) { 392 | testing::Message msg; 393 | msg << "Expected: \"" << expected_str << "\"\n" 394 | << " Actual: " << actual_expr << " contains \"" 395 | << actual_str << "\""; 396 | return testing::AssertionFailure(msg); 397 | } 398 | 399 | return testing::AssertionSuccess(); 400 | } 401 | 402 | TEST_F(ConfigParserTest, Parse) { 403 | const char* good_input = 404 | "Setting1 5\n" 405 | "Setting2 \"this is a string\"\n" 406 | "# commented line\n" 407 | "\n" 408 | "Setting3 2 # trailing comment\n" 409 | "Setting4 \"\\\"quoted\\\"\"# comment\n" 410 | "Setting5 (45,21, 5 , 8)# color"; 411 | ConfigParser parser(new ConfigParser::StringCharStream(good_input)); 412 | SettingsMap settings; 413 | ASSERT_TRUE(parser.Parse(&settings, NULL, 0)); 414 | ASSERT_EQ(5, settings.map().size()); 415 | EXPECT_PRED_FORMAT2(IntegerSettingEquals, 5, settings.GetSetting("Setting1")); 416 | EXPECT_PRED_FORMAT2(StringSettingEquals, 417 | "this is a string", 418 | settings.GetSetting("Setting2")); 419 | EXPECT_PRED_FORMAT2(IntegerSettingEquals, 2, settings.GetSetting("Setting3")); 420 | EXPECT_PRED_FORMAT2(StringSettingEquals, 421 | "\"quoted\"", 422 | settings.GetSetting("Setting4")); 423 | EXPECT_PRED_FORMAT2(ColorSettingEquals, 424 | "(45,21,5,8)", 425 | settings.GetSetting("Setting5")); 426 | 427 | const char* extra_name = "SettingName 3 blah"; 428 | parser.Reset(new ConfigParser::StringCharStream(extra_name)); 429 | EXPECT_FALSE(parser.Parse(&settings, NULL, 0)); 430 | 431 | const char* missing_value = "SettingName"; 432 | parser.Reset(new ConfigParser::StringCharStream(missing_value)); 433 | EXPECT_FALSE(parser.Parse(&settings, NULL, 0)); 434 | 435 | const char* comment_instead_of_value = "SettingName # test 3\n"; 436 | parser.Reset(new ConfigParser::StringCharStream(comment_instead_of_value)); 437 | EXPECT_FALSE(parser.Parse(&settings, NULL, 0)); 438 | 439 | const char* duplicate_name = "SettingName 4\nSettingName 3"; 440 | parser.Reset(new ConfigParser::StringCharStream(duplicate_name)); 441 | EXPECT_FALSE(parser.Parse(&settings, NULL, 0)); 442 | } 443 | 444 | } // namespace xsettingsd 445 | 446 | int main(int argc, char** argv) { 447 | testing::InitGoogleTest(&argc, argv); 448 | return RUN_ALL_TESTS(); 449 | } 450 | -------------------------------------------------------------------------------- /data_reader.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2009 Daniel Erat 2 | // All rights reserved. 3 | 4 | #include "data_reader.h" 5 | 6 | #include 7 | 8 | using std::string; 9 | 10 | namespace xsettingsd { 11 | 12 | DataReader::DataReader(const char* buffer, size_t buf_len) 13 | : buffer_(buffer), 14 | buf_len_(buf_len), 15 | bytes_read_(0), 16 | reverse_bytes_(false) { 17 | } 18 | 19 | bool DataReader::ReadBytes(string* out, size_t size) { 20 | if (bytes_read_ + size > buf_len_ || 21 | bytes_read_ + size < bytes_read_) { 22 | return false; 23 | } 24 | if (out) 25 | out->assign(buffer_ + bytes_read_, size); 26 | bytes_read_ += size; 27 | return true; 28 | } 29 | 30 | bool DataReader::ReadInt8(int8_t* out) { 31 | if (bytes_read_ + sizeof(int8_t) > buf_len_ || 32 | bytes_read_ + sizeof(int8_t) < bytes_read_) { 33 | return false; 34 | } 35 | 36 | if (out) 37 | *out = *reinterpret_cast(buffer_ + bytes_read_); 38 | bytes_read_ += sizeof(int8_t); 39 | return true; 40 | } 41 | 42 | bool DataReader::ReadInt16(int16_t* out) { 43 | if (bytes_read_ + sizeof(int16_t) > buf_len_ || 44 | bytes_read_ + sizeof(int16_t) < bytes_read_) { 45 | return false; 46 | } 47 | 48 | if (out) { 49 | *out = *reinterpret_cast(buffer_ + bytes_read_); 50 | if (reverse_bytes_) { 51 | if (IsLittleEndian()) 52 | *out = ntohs(*out); 53 | else 54 | *out = htons(*out); 55 | } 56 | } 57 | bytes_read_ += sizeof(int16_t); 58 | return true; 59 | } 60 | 61 | bool DataReader::ReadInt32(int32_t* out) { 62 | if (bytes_read_ + sizeof(int32_t) > buf_len_ || 63 | bytes_read_ + sizeof(int32_t) < bytes_read_) { 64 | return false; 65 | } 66 | 67 | if (out) { 68 | *out = *reinterpret_cast(buffer_ + bytes_read_); 69 | if (reverse_bytes_) { 70 | if (IsLittleEndian()) 71 | *out = ntohl(*out); 72 | else 73 | *out = htonl(*out); 74 | } 75 | } 76 | bytes_read_ += sizeof(int32_t); 77 | return true; 78 | } 79 | 80 | } // namespace xsettingsd 81 | -------------------------------------------------------------------------------- /data_reader.h: -------------------------------------------------------------------------------- 1 | // Copyright 2009 Daniel Erat 2 | // All rights reserved. 3 | 4 | #ifndef __XSETTINGSD_DATA_READER_H__ 5 | #define __XSETTINGSD_DATA_READER_H__ 6 | 7 | #include // for size_t 8 | #include 9 | #include 10 | 11 | #include "common.h" 12 | 13 | namespace xsettingsd { 14 | 15 | // Like DataWriter, but not. 16 | class DataReader { 17 | public: 18 | DataReader(const char* buffer, size_t buf_len); 19 | 20 | void set_reverse_bytes(bool reverse) { reverse_bytes_ = reverse; } 21 | 22 | size_t bytes_read() const { return bytes_read_; } 23 | 24 | bool ReadBytes(std::string* out, size_t size); 25 | bool ReadInt8(int8_t* out); 26 | bool ReadInt16(int16_t* out); 27 | bool ReadInt32(int32_t* out); 28 | 29 | private: 30 | const char* buffer_; // not owned 31 | 32 | size_t buf_len_; 33 | 34 | size_t bytes_read_; 35 | 36 | bool reverse_bytes_; 37 | 38 | DISALLOW_COPY_AND_ASSIGN(DataReader); 39 | }; 40 | 41 | } // namespace xsettingsd 42 | 43 | #endif 44 | -------------------------------------------------------------------------------- /data_writer.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2009 Daniel Erat 2 | // All rights reserved. 3 | 4 | #include "data_writer.h" 5 | 6 | #include 7 | #include 8 | 9 | using std::min; 10 | 11 | namespace xsettingsd { 12 | 13 | DataWriter::DataWriter(char* buffer, size_t buf_len) 14 | : buffer_(buffer), 15 | buf_len_(buf_len), 16 | bytes_written_(0) { 17 | } 18 | 19 | bool DataWriter::WriteBytes(const char* data, size_t bytes_to_write) { 20 | if (bytes_to_write > buf_len_ - bytes_written_) 21 | return false; 22 | 23 | memcpy(buffer_ + bytes_written_, 24 | data, 25 | min(bytes_to_write, buf_len_ - bytes_written_)); 26 | bytes_written_ += bytes_to_write; 27 | return true; 28 | } 29 | 30 | bool DataWriter::WriteInt8(int8_t num) { 31 | if (sizeof(int8_t) > buf_len_ - bytes_written_) 32 | return false; 33 | 34 | *(reinterpret_cast(buffer_ + bytes_written_)) = num; 35 | bytes_written_ += sizeof(int8_t); 36 | return true; 37 | } 38 | 39 | bool DataWriter::WriteInt16(int16_t num) { 40 | if (sizeof(int16_t) > buf_len_ - bytes_written_) 41 | return false; 42 | 43 | *(reinterpret_cast(buffer_ + bytes_written_)) = num; 44 | bytes_written_ += sizeof(int16_t); 45 | return true; 46 | } 47 | 48 | bool DataWriter::WriteInt32(int32_t num) { 49 | if (sizeof(int32_t) > buf_len_ - bytes_written_) 50 | return false; 51 | 52 | *(reinterpret_cast(buffer_ + bytes_written_)) = num; 53 | bytes_written_ += sizeof(int32_t); 54 | return true; 55 | } 56 | 57 | bool DataWriter::WriteZeros(size_t bytes_to_write) { 58 | if (bytes_to_write > buf_len_ - bytes_written_) 59 | return false; 60 | 61 | bzero(buffer_ + bytes_written_, 62 | min(bytes_to_write, buf_len_ - bytes_written_)); 63 | bytes_written_ += bytes_to_write; 64 | return true; 65 | } 66 | 67 | } // namespace xsettingsd 68 | -------------------------------------------------------------------------------- /data_writer.h: -------------------------------------------------------------------------------- 1 | // Copyright 2009 Daniel Erat 2 | // All rights reserved. 3 | 4 | #ifndef __XSETTINGSD_DATA_WRITER_H__ 5 | #define __XSETTINGSD_DATA_WRITER_H__ 6 | 7 | #include // for size_t 8 | #include 9 | 10 | #include "common.h" 11 | 12 | namespace xsettingsd { 13 | 14 | // Provides an interface for writing different types of data to a buffer. 15 | class DataWriter { 16 | public: 17 | DataWriter(char* buffer, size_t buf_len); 18 | 19 | size_t bytes_written() const { return bytes_written_; } 20 | 21 | bool WriteBytes(const char* data, size_t bytes_to_write); 22 | bool WriteInt8(int8_t num); 23 | bool WriteInt16(int16_t num); 24 | bool WriteInt32(int32_t num); 25 | bool WriteZeros(size_t bytes_to_write); 26 | 27 | private: 28 | char* buffer_; // not owned 29 | 30 | size_t buf_len_; 31 | 32 | size_t bytes_written_; 33 | 34 | DISALLOW_COPY_AND_ASSIGN(DataWriter); 35 | }; 36 | 37 | } // namespace xsettingsd 38 | 39 | #endif 40 | -------------------------------------------------------------------------------- /dump_xsettings.1: -------------------------------------------------------------------------------- 1 | .TH DUMP_XSETTINGS 1 2 | .SH NAME 3 | dump_xsettings \- prints X11 application settings from an XSETTINGS provider 4 | .SH SYNOPSIS 5 | .B dump_xsettings 6 | .RI [ options ] 7 | .SH DESCRIPTION 8 | The XSETTINGS specification provides a method to notify X11 applications 9 | about changes to system settings. \fBdump_xsettings\fR prints an X 10 | screen's current settings using \fIxsettingsd\fR(1)'s configuration file 11 | format. 12 | .SH OPTIONS 13 | .TP 14 | \fB\-h\fR, \fB\-\-help\fR 15 | Display a help message and exit. 16 | .TP 17 | \fB\-s\fR, \fB\-\-screen\fR=\fISCREEN\fR 18 | Use the X screen numbered \fISCREEN\fR (default is 0). 19 | .SH BUGS 20 | \fIhttps://github.com/derat/xsettingsd/issues\fR 21 | .SH SEE ALSO 22 | \fIxsettingsd\fR(1) 23 | .SH AUTHOR 24 | dump_xsettings was written by Daniel Erat . 25 | -------------------------------------------------------------------------------- /dump_xsettings.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2009 Daniel Erat 2 | // All rights reserved. 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "common.h" 14 | #include "data_reader.h" 15 | #include "setting.h" 16 | 17 | using std::min; 18 | using std::string; 19 | 20 | namespace xsettingsd { 21 | 22 | bool GetData(int screen, char* buffer, size_t buffer_size, size_t* data_size) { 23 | assert(data_size); 24 | 25 | Display* display = XOpenDisplay(NULL); 26 | if (!display) { 27 | fprintf(stderr, "Couldn't open display\n"); 28 | return false; 29 | } 30 | 31 | string sel_name = StringPrintf("_XSETTINGS_S%d", screen); 32 | Atom sel_atom = XInternAtom(display, sel_name.c_str(), False); 33 | Window win = XGetSelectionOwner(display, sel_atom); 34 | if (win == None) { 35 | fprintf(stderr, "No current owner for %s selection\n", sel_name.c_str()); 36 | return false; 37 | } 38 | 39 | static const char* kPropName = "_XSETTINGS_SETTINGS"; 40 | Atom prop_atom = XInternAtom(display, kPropName, False); 41 | Atom type_ret = None; 42 | int format_ret = 0; 43 | unsigned long num_items_ret = 0; 44 | unsigned long rem_bytes_ret = 0; 45 | unsigned char* prop_ret = NULL; 46 | int retval = XGetWindowProperty(display, 47 | win, 48 | prop_atom, 49 | 0, // offset 50 | buffer_size / 4, // length (32-bit multiples) 51 | False, // delete 52 | AnyPropertyType, // type 53 | &type_ret, // actual type 54 | &format_ret, // actual format 55 | &num_items_ret, // actual num items 56 | &rem_bytes_ret, // remaining bytes 57 | &prop_ret); // property 58 | if (retval != Success) { 59 | fprintf(stderr, "XGetWindowProperty() returned %d\n", retval); 60 | return false; 61 | } 62 | if (num_items_ret == 0) { 63 | fprintf(stderr, "Property %s doesn't exist on 0x%x\n", 64 | kPropName, static_cast(win)); 65 | return false; 66 | } 67 | if (rem_bytes_ret > 0) { 68 | fprintf(stderr, "Property %s on 0x%x is more than %zu bytes (%lu remain)\n", 69 | kPropName, static_cast(win), buffer_size, 70 | rem_bytes_ret); 71 | XFree(prop_ret); 72 | return false; 73 | } 74 | if (format_ret != 8) { 75 | fprintf(stderr, "Got unexpected format %d\n", format_ret); 76 | XFree(prop_ret); 77 | return false; 78 | } 79 | 80 | *data_size = min(buffer_size, static_cast(num_items_ret)); 81 | memcpy(buffer, prop_ret, *data_size); 82 | return true; 83 | } 84 | 85 | bool DumpSetting(DataReader* reader) { 86 | int8_t type_byte = 0; 87 | if (!reader->ReadInt8(&type_byte)) { 88 | fprintf(stderr, "Unable to read setting type\n"); 89 | return false; 90 | } 91 | Setting::Type type = static_cast(type_byte); 92 | 93 | if (type != Setting::TYPE_INTEGER && 94 | type != Setting::TYPE_STRING && 95 | type != Setting::TYPE_COLOR) { 96 | fprintf(stderr, "Got setting with unhandled type %d\n", type); 97 | return false; 98 | } 99 | 100 | if (!reader->ReadBytes(NULL, 1)) { 101 | fprintf(stderr, "Unable to read 1-byte setting padding\n"); 102 | return false; 103 | } 104 | 105 | uint16_t name_size = 0; 106 | if (!reader->ReadInt16(reinterpret_cast(&name_size))) { 107 | fprintf(stderr, "Unable to read setting name size\n"); 108 | return false; 109 | } 110 | 111 | string name; 112 | if (!reader->ReadBytes(&name, name_size)) { 113 | fprintf(stderr, "Unable to read %u-byte setting name\n", name_size); 114 | return false; 115 | } 116 | 117 | size_t name_padding = GetPadding(name_size, 4); 118 | if (!reader->ReadBytes(NULL, name_padding)) { 119 | fprintf(stderr, "Unable to read %zu-byte setting name padding\n", 120 | name_padding); 121 | return false; 122 | } 123 | 124 | if (!reader->ReadBytes(NULL, 4)) { 125 | fprintf(stderr, "Unable to read setting serial number\n"); 126 | return false; 127 | } 128 | 129 | if (type == Setting::TYPE_INTEGER) { 130 | int32_t value = 0; 131 | if (!reader->ReadInt32(&value)) { 132 | fprintf(stderr, "Unable to read integer setting value\n"); 133 | return false; 134 | } 135 | printf("%s %d\n", name.c_str(), value); 136 | 137 | } else if (type == Setting::TYPE_STRING) { 138 | uint32_t value_size = 0; 139 | if (!reader->ReadInt32(reinterpret_cast(&value_size))) { 140 | fprintf(stderr, "Unable to read string setting value size\n"); 141 | return false; 142 | } 143 | 144 | string value; 145 | if (!reader->ReadBytes(&value, value_size)) { 146 | fprintf(stderr, "Unable to read %u-byte string setting value\n", 147 | value_size); 148 | return false; 149 | } 150 | 151 | size_t value_padding = GetPadding(value_size, 4); 152 | if (!reader->ReadBytes(NULL, value_padding)) { 153 | fprintf(stderr, "Unable to read %zu-byte string setting value padding\n", 154 | value_padding); 155 | return false; 156 | } 157 | 158 | string escaped_value; 159 | for (size_t i = 0; i < value.size(); ++i) { 160 | char ch = value.c_str()[i]; 161 | switch (ch) { 162 | case '\n': 163 | escaped_value.append("\\n"); 164 | break; 165 | case '"': 166 | escaped_value.append("\\\""); 167 | break; 168 | default: 169 | escaped_value.push_back(ch); 170 | } 171 | } 172 | printf("%s \"%s\"\n", name.c_str(), escaped_value.c_str()); 173 | 174 | } else if (type == Setting::TYPE_COLOR) { 175 | uint16_t red = 0, blue = 0, green = 0, alpha = 0; 176 | if (!reader->ReadInt16(reinterpret_cast(&red)) || 177 | !reader->ReadInt16(reinterpret_cast(&blue)) || 178 | !reader->ReadInt16(reinterpret_cast(&green)) || 179 | !reader->ReadInt16(reinterpret_cast(&alpha))) { 180 | fprintf(stderr, "Unable to read color values\n"); 181 | return false; 182 | } 183 | // Note that unlike the spec, our config uses RGB-order, not RBG. 184 | printf("%s (%u, %u, %u, %u)\n", name.c_str(), red, green, blue, alpha); 185 | 186 | } else { 187 | assert(false); 188 | } 189 | 190 | return true; 191 | } 192 | 193 | bool DumpSettings(DataReader* reader) { 194 | int byte_order = IsLittleEndian() ? LSBFirst : MSBFirst; 195 | 196 | // Read 1-byte byte order. 197 | int8_t prop_byte_order = 0; 198 | if (!reader->ReadInt8(&prop_byte_order)) { 199 | fprintf(stderr, "Couldn't read byte order\n"); 200 | return false; 201 | } 202 | if (prop_byte_order != byte_order) { 203 | reader->set_reverse_bytes(true); 204 | } 205 | 206 | // Read 3 bytes of padding and 4-byte serial. 207 | if (!reader->ReadBytes(NULL, 7)) { 208 | fprintf(stderr, "Unable to read header\n"); 209 | return false; 210 | } 211 | 212 | uint32_t num_settings = 0; 213 | if (!reader->ReadInt32(reinterpret_cast(&num_settings))) { 214 | fprintf(stderr, "Unable to read number of settings\n"); 215 | return false; 216 | } 217 | 218 | for (uint32_t i = 0; i < num_settings; ++i) { 219 | if (!DumpSetting(reader)) 220 | return false; 221 | } 222 | 223 | return true; 224 | } 225 | 226 | } // namespace xsettingsd 227 | 228 | int main(int argc, char** argv) { 229 | static const char* kUsage = 230 | "Usage: dump_xsettings [OPTION] ...\n" 231 | "\n" 232 | "Dump current XSETTINGS values in xsettingd's format.\n" 233 | "\n" 234 | "Options: -h, --help print this help message\n" 235 | " -s, --screen=SCREEN screen to use (default is 0)\n"; 236 | 237 | int screen = 0; 238 | 239 | struct option options[] = { 240 | { "help", 0, NULL, 'h', }, 241 | { "screen", 1, NULL, 's', }, 242 | { NULL, 0, NULL, 0 }, 243 | }; 244 | 245 | opterr = 0; 246 | while (true) { 247 | int ch = getopt_long(argc, argv, "hs:", options, NULL); 248 | if (ch == -1) { 249 | break; 250 | } else if (ch == 'h' || ch == '?') { 251 | fprintf(stderr, "%s", kUsage); 252 | return 1; 253 | } else if (ch == 's') { 254 | char* endptr = NULL; 255 | screen = strtol(optarg, &endptr, 10); 256 | if (optarg[0] == '\0' || endptr[0] != '\0' || screen < 0) { 257 | fprintf(stderr, "Invalid screen \"%s\"\n", optarg); 258 | return 1; 259 | } 260 | } 261 | } 262 | 263 | static const size_t kBufferSize = 2 << 15; 264 | char buffer[kBufferSize]; 265 | 266 | size_t data_size = 0; 267 | if (!xsettingsd::GetData(screen, buffer, kBufferSize, &data_size)) 268 | return 1; 269 | assert(data_size <= kBufferSize); 270 | 271 | xsettingsd::DataReader reader(buffer, data_size); 272 | if (!xsettingsd::DumpSettings(&reader)) 273 | return 1; 274 | 275 | return 0; 276 | } 277 | -------------------------------------------------------------------------------- /setting.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2009 Daniel Erat 2 | // All rights reserved. 3 | 4 | #include "setting.h" 5 | 6 | #include "data_writer.h" 7 | 8 | using std::string; 9 | 10 | namespace xsettingsd { 11 | 12 | bool Setting::operator==(const Setting& other) const { 13 | if (other.type_ != type_) 14 | return false; 15 | return EqualsImpl(other); 16 | } 17 | 18 | bool Setting::Write(const string& name, DataWriter* writer) const { 19 | if (!writer->WriteInt8(type_)) return false; 20 | if (!writer->WriteZeros(1)) return false; 21 | if (!writer->WriteInt16(name.size())) return false; 22 | if (!writer->WriteBytes(name.data(), name.size())) return false; 23 | if (!writer->WriteZeros(GetPadding(name.size(), 4))) return false; 24 | if (!writer->WriteInt32(serial_)) return false; 25 | 26 | return WriteBody(writer); 27 | } 28 | 29 | void Setting::UpdateSerial(const Setting* prev, uint32_t serial) { 30 | if (prev && operator==(*prev)) 31 | serial_ = prev->serial_; 32 | else 33 | serial_ = serial; 34 | } 35 | 36 | bool IntegerSetting::WriteBody(DataWriter* writer) const { 37 | return writer->WriteInt32(value_); 38 | } 39 | 40 | bool IntegerSetting::EqualsImpl(const Setting& other) const { 41 | const IntegerSetting* cast_other = 42 | dynamic_cast(&other); 43 | if (!cast_other) 44 | return false; 45 | return (cast_other->value_ == value_); 46 | } 47 | 48 | bool StringSetting::WriteBody(DataWriter* writer) const { 49 | if (!writer->WriteInt32(value_.size())) return false; 50 | if (!writer->WriteBytes(value_.data(), value_.size())) return false; 51 | if (!writer->WriteZeros(GetPadding(value_.size(), 4))) return false; 52 | return true; 53 | } 54 | 55 | bool StringSetting::EqualsImpl(const Setting& other) const { 56 | const StringSetting* cast_other = dynamic_cast(&other); 57 | if (!cast_other) 58 | return false; 59 | return (cast_other->value_ == value_); 60 | } 61 | 62 | bool ColorSetting::WriteBody(DataWriter* writer) const { 63 | // Note that XSETTINGS asks for RBG-order, not RGB. 64 | if (!writer->WriteInt16(red_)) return false; 65 | if (!writer->WriteInt16(blue_)) return false; 66 | if (!writer->WriteInt16(green_)) return false; 67 | if (!writer->WriteInt16(alpha_)) return false; 68 | return true; 69 | } 70 | 71 | bool ColorSetting::EqualsImpl(const Setting& other) const { 72 | const ColorSetting* cast_other = dynamic_cast(&other); 73 | if (!cast_other) 74 | return false; 75 | return (cast_other->red_ == red_ && 76 | cast_other->green_ == green_ && 77 | cast_other->blue_ == blue_ && 78 | cast_other->alpha_ == alpha_); 79 | } 80 | 81 | SettingsMap::~SettingsMap() { 82 | for (Map::iterator it = map_.begin(); it != map_.end(); ++it) { 83 | delete it->second; 84 | } 85 | map_.clear(); 86 | } 87 | 88 | const Setting* SettingsMap::GetSetting(const std::string& name) const { 89 | Map::const_iterator it = map_.find(name); 90 | if (it == map_.end()) 91 | return NULL; 92 | return it->second; 93 | } 94 | 95 | } // namespace xsettingsd 96 | -------------------------------------------------------------------------------- /setting.h: -------------------------------------------------------------------------------- 1 | // Copyright 2009 Daniel Erat 2 | // All rights reserved. 3 | 4 | #ifndef __XSETTINGSD_SETTING_H__ 5 | #define __XSETTINGSD_SETTING_H__ 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #ifdef __TESTING 12 | #include 13 | #endif 14 | 15 | #include "common.h" 16 | 17 | namespace xsettingsd { 18 | 19 | class DataWriter; 20 | 21 | // Base class for settings. 22 | class Setting { 23 | public: 24 | enum Type { 25 | TYPE_INTEGER = 0, 26 | TYPE_STRING = 1, 27 | TYPE_COLOR = 2, 28 | }; 29 | 30 | explicit Setting(Type type) 31 | : type_(type), 32 | serial_(0) { 33 | } 34 | virtual ~Setting() {} 35 | 36 | uint32_t serial() const { return serial_; } 37 | 38 | bool operator==(const Setting& other) const; 39 | 40 | // Write this setting (using the passed-in setting name) in the format 41 | // described in the XSETTINGS spec. 42 | bool Write(const std::string& name, DataWriter* writer) const; 43 | 44 | // Update this setting's serial number based on the previous version of 45 | // the setting. (If the setting changed, we use 'serial'; otherwise we 46 | // use the same serial as 'prev'.) 47 | void UpdateSerial(const Setting* prev, uint32_t serial); 48 | 49 | private: 50 | // Write type-specific data. 51 | virtual bool WriteBody(DataWriter* writer) const = 0; 52 | 53 | // Cast 'other' to this setting's type and compare it. 54 | virtual bool EqualsImpl(const Setting& other) const = 0; 55 | 56 | Type type_; 57 | 58 | // Incremented when the setting's value changes. 59 | uint32_t serial_; 60 | 61 | DISALLOW_COPY_AND_ASSIGN(Setting); 62 | }; 63 | 64 | class IntegerSetting : public Setting { 65 | public: 66 | explicit IntegerSetting(int32_t value) 67 | : Setting(TYPE_INTEGER), 68 | value_(value) { 69 | } 70 | 71 | int32_t value() const { return value_; } 72 | 73 | private: 74 | #ifdef __TESTING 75 | friend class IntegerSettingTest; 76 | FRIEND_TEST(IntegerSettingTest, WriteBody); 77 | #endif 78 | 79 | bool WriteBody(DataWriter* writer) const; 80 | bool EqualsImpl(const Setting& other) const; 81 | 82 | int32_t value_; 83 | 84 | DISALLOW_COPY_AND_ASSIGN(IntegerSetting); 85 | }; 86 | 87 | class StringSetting : public Setting { 88 | public: 89 | explicit StringSetting(const std::string& value) 90 | : Setting(TYPE_STRING), 91 | value_(value) { 92 | } 93 | 94 | const std::string& value() const { return value_; } 95 | 96 | private: 97 | bool WriteBody(DataWriter* writer) const; 98 | bool EqualsImpl(const Setting& other) const; 99 | 100 | std::string value_; 101 | 102 | DISALLOW_COPY_AND_ASSIGN(StringSetting); 103 | }; 104 | 105 | class ColorSetting : public Setting { 106 | public: 107 | ColorSetting(uint16_t red, 108 | uint16_t green, 109 | uint16_t blue, 110 | uint16_t alpha) 111 | : Setting(TYPE_COLOR), 112 | red_(red), 113 | green_(green), 114 | blue_(blue), 115 | alpha_(alpha) { 116 | } 117 | 118 | uint16_t red() const { return red_; } 119 | uint16_t green() const { return green_; } 120 | uint16_t blue() const { return blue_; } 121 | uint16_t alpha() const { return alpha_; } 122 | 123 | private: 124 | bool WriteBody(DataWriter* writer) const; 125 | bool EqualsImpl(const Setting& other) const; 126 | 127 | uint16_t red_; 128 | uint16_t green_; 129 | uint16_t blue_; 130 | uint16_t alpha_; 131 | 132 | DISALLOW_COPY_AND_ASSIGN(ColorSetting); 133 | }; 134 | 135 | // A simple wrapper around a string-to-Setting map. 136 | // Handles deleting the Setting objects in its d'tor. 137 | class SettingsMap { 138 | public: 139 | SettingsMap() {} 140 | ~SettingsMap(); 141 | 142 | typedef std::map Map; 143 | const Map& map() const { return map_; } 144 | Map* mutable_map() { return &map_; } 145 | 146 | void swap(SettingsMap* other) { map_.swap(other->map_); } 147 | 148 | // Get a pointer to a setting or NULL if it doesn't exist. 149 | const Setting* GetSetting(const std::string& name) const; 150 | 151 | private: 152 | Map map_; 153 | 154 | DISALLOW_COPY_AND_ASSIGN(SettingsMap); 155 | }; 156 | 157 | } // namespace xsettingsd 158 | 159 | #endif 160 | -------------------------------------------------------------------------------- /setting_test.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2009 Daniel Erat 2 | // All rights reserved. 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "data_writer.h" 10 | #include "setting.h" 11 | 12 | using std::string; 13 | 14 | namespace xsettingsd { 15 | 16 | testing::AssertionResult BytesAreEqual( 17 | const char* expected_expr, 18 | const char* actual_expr, 19 | const char* size_expr, 20 | const char* expected, 21 | const char* actual, 22 | size_t size) { 23 | for (size_t i = 0; i < size; ++i) { 24 | if (expected[i] != actual[i]) { 25 | testing::Message msg; 26 | string expected_str, actual_str, hl_str; 27 | bool first = true; 28 | for (size_t j = 0; j < size; ++j) { 29 | expected_str += 30 | StringPrintf(" %02x", static_cast(expected[j])); 31 | actual_str += 32 | StringPrintf(" %02x", static_cast(actual[j])); 33 | hl_str += (expected[j] == actual[j]) ? " " : " ^^"; 34 | if ((j % 16) == 15 || j == size - 1) { 35 | msg << (first ? "Expected:" : "\n ") << expected_str << "\n" 36 | << (first ? " Actual:" : " ") << actual_str << "\n" 37 | << " " << hl_str; 38 | expected_str = actual_str = hl_str = ""; 39 | first = false; 40 | } 41 | } 42 | return testing::AssertionFailure(msg); 43 | } 44 | } 45 | return testing::AssertionSuccess(); 46 | } 47 | 48 | TEST(IntegerSettingTest, Write) { 49 | static const int kBufSize = 1024; 50 | char buffer[kBufSize]; 51 | 52 | DataWriter writer(buffer, kBufSize); 53 | IntegerSetting setting(5); 54 | setting.UpdateSerial(NULL, 3); 55 | ASSERT_TRUE(setting.Write("name", &writer)); 56 | // TODO: Won't work on big-endian systems. 57 | const char expected[] = { 58 | 0x0, // type 59 | 0x0, // unused 60 | 0x4, 0x0, // name-len 61 | 0x6e, 0x61, 0x6d, 0x65, // "name" (multiple of 4, so no padding) 62 | 0x3, 0x0, 0x0, 0x0, // serial 63 | 0x5, 0x0, 0x0, 0x0, // value 64 | }; 65 | ASSERT_EQ(sizeof(expected), writer.bytes_written()); 66 | EXPECT_PRED_FORMAT3(BytesAreEqual, expected, buffer, sizeof(expected)); 67 | } 68 | 69 | TEST(StringSettingTest, Write) { 70 | static const int kBufSize = 1024; 71 | char buffer[kBufSize]; 72 | 73 | DataWriter writer(buffer, kBufSize); 74 | StringSetting setting("testing"); 75 | setting.UpdateSerial(NULL, 5); 76 | ASSERT_TRUE(setting.Write("setting", &writer)); 77 | // TODO: Won't work on big-endian systems. 78 | const char expected[] = { 79 | 0x1, // type 80 | 0x0, // unused 81 | 0x7, 0x0, // name-len 82 | 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, // "setting" (name) 83 | 0x0, // padding 84 | 0x5, 0x0, 0x0, 0x0, // serial 85 | 0x7, 0x0, 0x0, 0x0, // value-len 86 | 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, // "testing" (value) 87 | 0x0, // padding 88 | }; 89 | ASSERT_EQ(sizeof(expected), writer.bytes_written()); 90 | EXPECT_PRED_FORMAT3(BytesAreEqual, expected, buffer, sizeof(expected)); 91 | } 92 | 93 | TEST(ColorSettingTest, Write) { 94 | static const int kBufSize = 1024; 95 | char buffer[kBufSize]; 96 | 97 | DataWriter writer(buffer, kBufSize); 98 | ColorSetting setting(32768, 65535, 0, 255); 99 | setting.UpdateSerial(NULL, 2); 100 | ASSERT_TRUE(setting.Write("name", &writer)); 101 | // TODO: Won't work on big-endian systems. 102 | const char expected[] = { 103 | 0x2, // type 104 | 0x0, // unused 105 | 0x4, 0x0, // name-len 106 | 0x6e, 0x61, 0x6d, 0x65, // "name" (multiple of 4, so no padding) 107 | 0x2, 0x0, 0x0, 0x0, // serial 108 | 0x0, 0x80, // red 109 | 0x0, 0x0, // blue (yes, the order is strange) 110 | 0xff, 0xff, // green 111 | 0xff, 0x0, // alpha 112 | }; 113 | ASSERT_EQ(sizeof(expected), writer.bytes_written()); 114 | EXPECT_PRED_FORMAT3(BytesAreEqual, expected, buffer, sizeof(expected)); 115 | } 116 | 117 | TEST(SettingTest, Serials) { 118 | // Create a setting and give it a serial of 3. 119 | IntegerSetting setting(4); 120 | setting.UpdateSerial(NULL, 3); 121 | EXPECT_EQ(3, setting.serial()); 122 | 123 | // Now create a new setting with a different value. 124 | // It should get a new serial number. 125 | IntegerSetting setting2(5); 126 | setting2.UpdateSerial(&setting, 4); 127 | EXPECT_EQ(4, setting2.serial()); 128 | 129 | // Create a new setting with the same value. 130 | // The serial should stay the same as before. 131 | IntegerSetting setting3(5); 132 | setting3.UpdateSerial(&setting2, 5); 133 | EXPECT_EQ(4, setting3.serial()); 134 | } 135 | 136 | } // namespace xsettingsd 137 | 138 | int main(int argc, char** argv) { 139 | testing::InitGoogleTest(&argc, argv); 140 | return RUN_ALL_TESTS(); 141 | } 142 | -------------------------------------------------------------------------------- /settings_manager.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2009 Daniel Erat 2 | // All rights reserved. 3 | 4 | #include "settings_manager.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include "config_parser.h" 18 | #include "data_writer.h" 19 | #include "setting.h" 20 | 21 | using std::make_pair; 22 | using std::map; 23 | using std::max; 24 | using std::string; 25 | using std::vector; 26 | 27 | namespace xsettingsd { 28 | 29 | // Arbitrarily big number. 30 | static const int kMaxPropertySize = (2 << 15); 31 | 32 | SettingsManager::SettingsManager(const string& config_filename) 33 | : config_filename_(config_filename), 34 | serial_(0), 35 | display_(NULL), 36 | prop_atom_(None) { 37 | } 38 | 39 | SettingsManager::~SettingsManager() { 40 | if (display_) { 41 | if (!windows_.empty()) 42 | DestroyWindows(); 43 | XCloseDisplay(display_); 44 | display_ = NULL; 45 | } 46 | } 47 | 48 | bool SettingsManager::LoadConfig() { 49 | ConfigParser parser(new ConfigParser::FileCharStream(config_filename_)); 50 | SettingsMap new_settings; 51 | if (!parser.Parse(&new_settings, &settings_, serial_ + 1)) { 52 | fprintf(stderr, "%s: Unable to parse %s: %s\n", 53 | kProgName, config_filename_.c_str(), parser.FormatError().c_str()); 54 | return false; 55 | } 56 | serial_++; 57 | fprintf(stderr, "%s: Loaded %zu setting%s from %s\n", 58 | kProgName, new_settings.map().size(), 59 | (new_settings.map().size() == 1) ? "" : "s", 60 | config_filename_.c_str()); 61 | settings_.swap(&new_settings); 62 | return true; 63 | } 64 | 65 | bool SettingsManager::InitX11(int screen, bool replace_existing_manager) { 66 | assert(!display_); 67 | display_ = XOpenDisplay(NULL); 68 | if (!display_) { 69 | fprintf(stderr, "%s: Unable to open connection to X server\n", kProgName); 70 | return false; 71 | } 72 | 73 | prop_atom_ = XInternAtom(display_, "_XSETTINGS_SETTINGS", False); 74 | 75 | char data[kMaxPropertySize]; 76 | DataWriter writer(data, kMaxPropertySize); 77 | if (!WriteProperty(&writer)) 78 | return false; 79 | 80 | int min_screen = 0; 81 | int max_screen = ScreenCount(display_) - 1; 82 | if (screen >= 0) 83 | min_screen = max_screen = screen; 84 | 85 | for (screen = min_screen; screen <= max_screen; ++screen) { 86 | Window win = None; 87 | Time timestamp = 0; 88 | if (!CreateWindow(screen, &win, ×tamp)) { 89 | fprintf(stderr, "%s: Unable to create window on screen %d\n", 90 | kProgName, screen); 91 | return false; 92 | } 93 | fprintf(stderr, "%s: Created window 0x%x on screen %d with timestamp %lu\n", 94 | kProgName, static_cast(win), screen, timestamp); 95 | 96 | SetPropertyOnWindow(win, data, writer.bytes_written()); 97 | 98 | if (!ManageScreen(screen, win, timestamp, replace_existing_manager)) 99 | return false; 100 | 101 | windows_.push_back(win); 102 | } 103 | 104 | return true; 105 | } 106 | 107 | void SettingsManager::RunEventLoop() { 108 | int x11_fd = XConnectionNumber(display_); 109 | // TODO: Need to also use XAddConnectionWatch()? 110 | 111 | while (true) { 112 | // Rather than blocking in XNextEvent(), we just read all the available 113 | // events here. We block in select() instead, so that we'll get EINTR 114 | // if a SIGHUP came in to ask us to reload the config. 115 | while (XPending(display_)) { 116 | XEvent event; 117 | XNextEvent(display_, &event); 118 | 119 | switch (event.type) { 120 | case MappingNotify: 121 | // Doesn't really mean anything to us, but might as well handle it. 122 | XRefreshKeyboardMapping(&(event.xmapping)); 123 | break; 124 | case SelectionClear: { 125 | // If someone else took the selection, that's our sign to leave. 126 | fprintf(stderr, "%s: 0x%x took a selection from us; exiting\n", 127 | kProgName, 128 | static_cast(event.xselectionclear.window)); 129 | DestroyWindows(); 130 | return; 131 | } 132 | default: 133 | fprintf(stderr, "%s: Ignoring event of type %d\n", 134 | kProgName, event.type); 135 | } 136 | } 137 | 138 | // TODO: There's a small race condition here, in that SIGHUP can come 139 | // in while we're outside of the select() call, but it's probably not 140 | // worth trying to work around. 141 | 142 | fd_set fds; 143 | FD_ZERO(&fds); 144 | FD_SET(x11_fd, &fds); 145 | if (select(x11_fd + 1, &fds, NULL, NULL, NULL) == -1) { 146 | if (errno != EINTR) { 147 | fprintf(stderr, "%s: select() failed: %s\n", 148 | kProgName, strerror(errno)); 149 | return; 150 | } 151 | 152 | fprintf(stderr, "%s: Reloading configuration\n", kProgName); 153 | if (!LoadConfig()) 154 | continue; 155 | 156 | char data[kMaxPropertySize]; 157 | DataWriter writer(data, kMaxPropertySize); 158 | if (!WriteProperty(&writer)) 159 | continue; 160 | 161 | for (vector::const_iterator it = windows_.begin(); 162 | it != windows_.end(); ++it) { 163 | SetPropertyOnWindow(*it, data, writer.bytes_written()); 164 | } 165 | } 166 | } 167 | } 168 | 169 | void SettingsManager::DestroyWindows() { 170 | assert(display_); 171 | for (vector::iterator it = windows_.begin(); 172 | it != windows_.end(); ++it) { 173 | XDestroyWindow(display_, *it); 174 | } 175 | windows_.clear(); 176 | } 177 | 178 | bool SettingsManager::CreateWindow(int screen, 179 | Window* win_out, 180 | Time* timestamp_out) { 181 | assert(win_out); 182 | assert(timestamp_out); 183 | 184 | if (screen < 0 || screen >= ScreenCount(display_)) 185 | return false; 186 | 187 | XSetWindowAttributes attr; 188 | attr.override_redirect = True; 189 | Window win = XCreateWindow(display_, 190 | RootWindow(display_, screen), // parent 191 | -1, -1, // x, y 192 | 1, 1, // width, height 193 | 0, // border_width 194 | CopyFromParent, // depth 195 | InputOutput, // class 196 | CopyFromParent, // visual 197 | CWOverrideRedirect, // attr_mask 198 | &attr); 199 | if (win == None) 200 | return false; 201 | *win_out = win; 202 | 203 | // This sets a few properties for us, including WM_CLIENT_MACHINE. 204 | XSetWMProperties(display_, 205 | win, 206 | NULL, // window_name 207 | NULL, // icon_name 208 | NULL, // argv 209 | 0, // argc 210 | NULL, // normal_hints 211 | NULL, // wm_hints 212 | NULL); // class_hints 213 | 214 | XStoreName(display_, win, kProgName); 215 | XChangeProperty(display_, 216 | win, 217 | XInternAtom(display_, "_NET_WM_NAME", False), // property 218 | XInternAtom(display_, "UTF8_STRING", False), // type 219 | 8, // format (bits per element) 220 | PropModeReplace, 221 | reinterpret_cast(kProgName), 222 | strlen(kProgName)); 223 | 224 | // Grab a timestamp from our final property change; we'll need it later 225 | // when announcing that we've taken the manager selection. 226 | pid_t pid = getpid(); 227 | XSelectInput(display_, win, PropertyChangeMask); 228 | XChangeProperty(display_, 229 | win, 230 | XInternAtom(display_, "_NET_WM_PID", False), // property 231 | XA_CARDINAL, // type 232 | 32, // format (bits per element) 233 | PropModeReplace, 234 | reinterpret_cast(&pid), // value 235 | 1); // num elements 236 | XSelectInput(display_, win, NoEventMask); 237 | 238 | XEvent event; 239 | while (true) { 240 | XWindowEvent(display_, win, PropertyChangeMask, &event); 241 | if (event.type == PropertyNotify) { 242 | *timestamp_out = event.xproperty.time; 243 | break; 244 | } 245 | } 246 | 247 | return true; 248 | } 249 | 250 | bool SettingsManager::WriteProperty(DataWriter* writer) { 251 | assert(writer); 252 | 253 | int byte_order = IsLittleEndian() ? LSBFirst : MSBFirst; 254 | if (!writer->WriteInt8(byte_order)) return false; 255 | if (!writer->WriteZeros(3)) return false; 256 | if (!writer->WriteInt32(serial_)) return false; 257 | if (!writer->WriteInt32(settings_.map().size())) return false; 258 | 259 | for (SettingsMap::Map::const_iterator it = settings_.map().begin(); 260 | it != settings_.map().end(); ++it) { 261 | if (!it->second->Write(it->first, writer)) 262 | return false; 263 | } 264 | return true; 265 | } 266 | 267 | void SettingsManager::SetPropertyOnWindow( 268 | Window win, const char* data, size_t size) { 269 | XChangeProperty(display_, 270 | win, 271 | prop_atom_, // property 272 | prop_atom_, // type 273 | 8, // format (bits per element) 274 | PropModeReplace, 275 | reinterpret_cast(data), 276 | size); 277 | } 278 | 279 | bool SettingsManager::ManageScreen(int screen, 280 | Window win, 281 | Time timestamp, 282 | bool replace_existing_manager) { 283 | assert(display_); 284 | assert(win != None); 285 | assert(screen < ScreenCount(display_)); 286 | 287 | Window root = RootWindow(display_, screen); 288 | 289 | string sel_atom_name = StringPrintf("_XSETTINGS_S%d", screen); 290 | Atom sel_atom = XInternAtom(display_, sel_atom_name.c_str(), False); 291 | 292 | XGrabServer(display_); 293 | Window prev_win = XGetSelectionOwner(display_, sel_atom); 294 | fprintf(stderr, "%s: Selection %s is owned by 0x%x\n", 295 | kProgName, sel_atom_name.c_str(), 296 | static_cast(prev_win)); 297 | if (prev_win != None && !replace_existing_manager) { 298 | fprintf(stderr, "%s: Someone else already owns the %s selection " 299 | "and we weren't asked to replace them\n", 300 | kProgName, sel_atom_name.c_str()); 301 | XUngrabServer(display_); 302 | return false; 303 | } 304 | 305 | if (prev_win) 306 | XSelectInput(display_, prev_win, StructureNotifyMask); 307 | XSetSelectionOwner(display_, sel_atom, win, CurrentTime); 308 | fprintf(stderr, "%s: Took ownership of selection %s\n", 309 | kProgName, sel_atom_name.c_str()); 310 | XUngrabServer(display_); 311 | 312 | if (prev_win) { 313 | // Wait for the previous owner to go away. 314 | XEvent event; 315 | while (true) { 316 | XWindowEvent(display_, prev_win, StructureNotifyMask, &event); 317 | if (event.type == DestroyNotify) 318 | break; 319 | } 320 | } 321 | 322 | // Make sure that no one else took the selection while we were waiting. 323 | if (XGetSelectionOwner(display_, sel_atom) != win) { 324 | fprintf(stderr, "%s: Someone else took ownership of the %s selection\n", 325 | kProgName, sel_atom_name.c_str()); 326 | return false; 327 | } 328 | 329 | XEvent ev; 330 | ev.xclient.type = ClientMessage; 331 | ev.xclient.window = root; 332 | ev.xclient.message_type = XInternAtom(display_, "MANAGER", False); 333 | ev.xclient.format = 32; 334 | ev.xclient.data.l[0] = timestamp; 335 | ev.xclient.data.l[1] = sel_atom; 336 | ev.xclient.data.l[2] = win; 337 | ev.xclient.data.l[3] = 0; 338 | XSendEvent(display_, 339 | root, 340 | False, // propagate 341 | StructureNotifyMask, // event_mask 342 | &ev); 343 | 344 | return true; 345 | } 346 | 347 | } // namespace xsettingsd 348 | -------------------------------------------------------------------------------- /settings_manager.h: -------------------------------------------------------------------------------- 1 | // Copyright 2009 Daniel Erat 2 | // All rights reserved. 3 | 4 | #ifndef __XSETTINGSD_SETTINGS_MANAGER_H__ 5 | #define __XSETTINGSD_SETTINGS_MANAGER_H__ 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | #include "common.h" 14 | #include "setting.h" 15 | 16 | namespace xsettingsd { 17 | 18 | class DataWriter; 19 | 20 | // SettingsManager is the central class responsible for loading and parsing 21 | // configs (via ConfigParser), storing them (in the form of Setting 22 | // objects), and setting them as properties on X11 windows. 23 | class SettingsManager { 24 | public: 25 | SettingsManager(const std::string& config_filename); 26 | ~SettingsManager(); 27 | 28 | // Load settings from 'config_filename_', updating 'settings_' and 29 | // 'serial_' if successful. If the load was unsuccessful, false is 30 | // returned and an error is printed to stderr. 31 | bool LoadConfig(); 32 | 33 | // Connect to the X server, create windows, updates their properties, and 34 | // take the selections. A negative screen value will attempt to take the 35 | // manager selection on all screens. Returns false if someone else 36 | // already has a selection unless 'replace_existing_manager' is set. 37 | bool InitX11(int screen, bool replace_existing_manager); 38 | 39 | // Wait for events from the X server, destroying our windows and exiting 40 | // if we see someone else take a selection. 41 | void RunEventLoop(); 42 | 43 | private: 44 | // Destroy all windows in 'windows_'. 45 | void DestroyWindows(); 46 | 47 | // Create and initialize a window. 48 | bool CreateWindow(int screen, Window* win_out, Time* timestamp_out); 49 | 50 | // Write the currently-loaded property to the passed-in buffer. 51 | bool WriteProperty(DataWriter* writer); 52 | 53 | // Update the settings property on the passed-in window. 54 | void SetPropertyOnWindow(Window win, const char* data, size_t size); 55 | 56 | // Manage XSETTINGS for a particular screen. 57 | bool ManageScreen( 58 | int screen, Window win, Time timestamp, bool replace_existing_manager); 59 | 60 | // File from which we load settings. 61 | std::string config_filename_; 62 | 63 | // Currently-loaded settings. 64 | SettingsMap settings_; 65 | 66 | // Current serial number. 67 | uint32_t serial_; 68 | 69 | // Connection to the X server. 70 | Display* display_; 71 | 72 | // Atom representing "_XSETTINGS_SETTINGS". 73 | Atom prop_atom_; 74 | 75 | // Windows that we've created to hold settings properties (one per 76 | // screen). 77 | std::vector windows_; 78 | 79 | DISALLOW_COPY_AND_ASSIGN(SettingsManager); 80 | }; 81 | 82 | } // namespace xsettingsd 83 | 84 | #endif 85 | -------------------------------------------------------------------------------- /xsettingsd.1: -------------------------------------------------------------------------------- 1 | .TH XSETTINGSD 1 2 | .SH NAME 3 | xsettingsd \- provides settings to X11 applications 4 | .SH SYNOPSIS 5 | .B xsettingsd 6 | .RI [ options ] 7 | .SH DESCRIPTION 8 | xsettingsd is a daemon that implements the XSETTINGS specification. The 9 | typical invocation is from a user's \fB~/.xsession\fR file. 10 | .PP 11 | It is intended to be small, fast, and minimally dependent on other 12 | libraries. It can serve as an alternative to gnome-settings-daemon for 13 | users who are not using the GNOME desktop environment but who still run 14 | GTK+ applications and want to configure things such as themes, font 15 | antialiasing/hinting, and UI sound effects. 16 | .PP 17 | Additional documentation is available at the project's website: 18 | \fIhttps://github.com/derat/xsettingsd\fR 19 | .SH OPTIONS 20 | .TP 21 | \fB\-c\fR, \fB\-\-config\fR=\fIFILE\fR 22 | Load settings from \fIFILE\fR (default is \fB~/.xsettingsd\fR). 23 | .TP 24 | \fB\-h\fR, \fB\-\-help\fR 25 | Display a help message and exit. 26 | .TP 27 | \fB\-s\fR, \fB\-\-screen\fR=\fISCREEN\fR 28 | Use the X screen numbered \fISCREEN\fR (default of -1 means all screens). 29 | .SH BUGS 30 | \fIhttps://github.com/derat/xsettingsd/issues\fR 31 | .SH EXAMPLE 32 | Running \fIdump_xsettings\fR(1) while another XSETTINGS daemon, such as 33 | \fBgnome-settings-daemon\fR, is already running will print your current 34 | settings in \fBxsettingsd\fR's configuration file format. 35 | .PP 36 | Here is a short example \fB~/.xsettingsd\fR file: 37 | .PP 38 | .nf 39 | Net/ThemeName "Human" 40 | Xft/Antialias 1 41 | Xft/DPI 100352 42 | Xft/HintStyle "hintfull" 43 | Xft/Hinting 1 44 | Xft/RGBA "none" 45 | Xft/lcdfilter "none" 46 | .fi 47 | .SH SEE ALSO 48 | \fIdump_xsettings\fR\|(1) 49 | .SH AUTHOR 50 | xsettingsd was written by Daniel Erat . 51 | -------------------------------------------------------------------------------- /xsettingsd.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2009 Daniel Erat 2 | // All rights reserved. 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "common.h" 15 | #include "config_parser.h" 16 | #include "settings_manager.h" 17 | 18 | using std::string; 19 | using std::vector; 20 | 21 | namespace { 22 | 23 | void HandleSignal(int signum) { 24 | } 25 | 26 | // Returns the first path in |paths| that is readable, or an empty string if 27 | // none of the paths can be read. 28 | string GetFirstReadablePath(const vector& paths) { 29 | for (size_t i = 0; i < paths.size(); ++i) { 30 | if (access(paths[i].c_str(), R_OK) == 0) { 31 | return paths[i]; 32 | } 33 | } 34 | return string(); 35 | } 36 | 37 | } // namespace 38 | 39 | int main(int argc, char** argv) { 40 | static const char* kUsage = 41 | "Usage: xsettingsd [OPTION] ...\n" 42 | "\n" 43 | "Daemon implementing the XSETTINGS spec to control settings for X11\n" 44 | "applications.\n" 45 | "\n" 46 | "Options: -c, --config=FILE config file (default is ~/.xsettingsd)\n" 47 | " -h, --help print this help message\n" 48 | " -s, --screen=SCREEN screen to use (default is all)\n"; 49 | 50 | int screen = -1; 51 | string config_file; 52 | 53 | struct option options[] = { 54 | { "config", 1, NULL, 'c', }, 55 | { "help", 0, NULL, 'h', }, 56 | { "screen", 1, NULL, 's', }, 57 | { NULL, 0, NULL, 0 }, 58 | }; 59 | 60 | opterr = 0; 61 | while (true) { 62 | int ch = getopt_long(argc, argv, "c:hs:", options, NULL); 63 | if (ch == -1) { 64 | break; 65 | } else if (ch == 'c') { 66 | config_file = optarg; 67 | } else if (ch == 'h' || ch == '?') { 68 | fprintf(stderr, "%s", kUsage); 69 | return 1; 70 | } else if (ch == 's') { 71 | char* endptr = NULL; 72 | screen = strtol(optarg, &endptr, 10); 73 | if (optarg[0] == '\0' || endptr[0] != '\0' || screen < 0) { 74 | fprintf(stderr, "Invalid screen \"%s\"\n", optarg); 75 | return 1; 76 | } 77 | } 78 | } 79 | 80 | // Check default config file locations if one wasn't supplied via a flag. 81 | if (config_file.empty()) { 82 | const vector paths = xsettingsd::GetDefaultConfigFilePaths(); 83 | config_file = GetFirstReadablePath(paths); 84 | if (config_file.empty()) { 85 | fprintf(stderr, "%s: Couldn't find config file. Tried the following:\n", 86 | xsettingsd::kProgName); 87 | for (size_t i = 0; i < paths.size(); ++i) 88 | fprintf(stderr, " %s\n", paths[i].c_str()); 89 | return 1; 90 | } 91 | } 92 | 93 | xsettingsd::SettingsManager manager(config_file); 94 | if (!manager.LoadConfig()) 95 | return 1; 96 | if (!manager.InitX11(screen, true)) 97 | return 1; 98 | 99 | signal(SIGHUP, HandleSignal); 100 | 101 | manager.RunEventLoop(); 102 | return 0; 103 | } 104 | -------------------------------------------------------------------------------- /xsettingsd.service.in: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=XSETTINGS-protocol daemon 3 | PartOf=graphical-session.target 4 | 5 | [Service] 6 | ExecStart=@CMAKE_INSTALL_FULL_BINDIR@/xsettingsd 7 | Slice=session.slice 8 | --------------------------------------------------------------------------------