pax_global_header00006660000000000000000000000064137761166550014533gustar00rootroot0000000000000052 comment=b9908f92b2fedd4e2f345c1e36879750504b0f06 xsettingsd-1.0.2/000077500000000000000000000000001377611665500137275ustar00rootroot00000000000000xsettingsd-1.0.2/.gitignore000066400000000000000000000001241377611665500157140ustar00rootroot00000000000000.*.swp .sconsign.dblite dump_xsettings xsettingsd *.[ao] *_test build-stamp /build/ xsettingsd-1.0.2/CMakeLists.txt000066400000000000000000000033731377611665500164750ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.15) project(xsettingsd VERSION 1.0.2 DESCRIPTION "Provides settings to X11 applications via the XSETTINGS specification" HOMEPAGE_URL "https://github.com/derat/xsettingsd" LANGUAGES CXX ) include(GNUInstallDirs) include(CTest) find_package(X11 REQUIRED) find_package(GTest) add_library(libxsettingsd STATIC common.cc config_parser.cc data_reader.cc data_writer.cc setting.cc settings_manager.cc ) add_executable(xsettingsd xsettingsd.cc) target_link_libraries(xsettingsd PRIVATE libxsettingsd X11::X11) add_executable(dump_xsettings dump_xsettings.cc) target_link_libraries(dump_xsettings PRIVATE libxsettingsd X11::X11) install(TARGETS xsettingsd dump_xsettings DESTINATION ${CMAKE_INSTALL_BINDIR}) install(FILES xsettingsd.1 dump_xsettings.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1) configure_file(xsettingsd.service.in xsettingsd.service) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/xsettingsd.service" DESTINATION lib/systemd/user) if(GTEST_FOUND AND BUILD_TESTING) include(GoogleTest) add_executable(common_test common_test.cc) target_link_libraries(common_test PRIVATE libxsettingsd GTest::GTest) gtest_discover_tests(common_test) add_executable(config_parser_test config_parser_test.cc) target_link_libraries(config_parser_test PRIVATE libxsettingsd GTest::GTest) target_compile_definitions(config_parser_test PRIVATE __TESTING) gtest_discover_tests(config_parser_test) add_executable(setting_test setting_test.cc) target_link_libraries(setting_test PRIVATE libxsettingsd GTest::GTest) target_compile_options(setting_test PRIVATE -Wno-narrowing) gtest_discover_tests(setting_test) endif() add_custom_target(uninstall COMMAND xargs rm -v < "${CMAKE_BINARY_DIR}/install_manifest.txt") xsettingsd-1.0.2/COPYING000066400000000000000000000027521377611665500147700ustar00rootroot00000000000000Copyright (c) 2009, Daniel Erat All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Daniel Erat nor the names of this software's other contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. xsettingsd-1.0.2/README.md000066400000000000000000000015671377611665500152170ustar00rootroot00000000000000# xsettingsd xsettingsd is a daemon that implements the *XSETTINGS* specification. It is intended to be small, fast, and minimally dependent on other libraries. It can serve as an alternative to gnome-settings-daemon for users who are not using the GNOME desktop environment but who still run GTK+ applications and want to configure things such as themes, font antialiasing/hinting, and UI sound effects. ## Build instructions requirements: * C++ compiler * CMake * X11 headers (`libx11-dev` in Debian) * GoogleTest (optional, `libgtest-dev` in Debian) execute build: ``` mkdir build cd build cmake -DCMAKE_INSTALL_PREFIX=/path .. make make install ``` run tests: ``` make test ``` delete all installed files: ``` make uninstall ``` ## Documentation Documentation is available at https://github.com/derat/xsettingsd/wiki. ## Contact Daniel Erat xsettingsd-1.0.2/SConstruct000066400000000000000000000041261377611665500157640ustar00rootroot00000000000000# -*- python -*- # Copyright 2009 Daniel Erat # All rights reserved. import os import subprocess Help(''' Type: 'scons xsettingsd' to build xsettingsd 'scons dump_xsettings' to build dump_xsettings 'scons test' to build and run all tests ''') def run_tests(target, source, env): '''Run all test binaries listed in 'source'.''' tests = sorted([str(t) for t in source]) max_length = max([len(t) for t in tests]) for test in tests: padded_name = test + ' ' * (max_length - len(test)) proc = subprocess.Popen('./%s' % test, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, close_fds=True) [stdout, stderr] = proc.communicate() proc.wait() if proc.returncode == 0: print('%s OK' % padded_name) else: print("%s FAILED\n" % padded_name) print(stdout) run_tests_builder = Builder(action=run_tests) env = Environment( BUILDERS = { 'RunTests': run_tests_builder, }) env.Append(CPPFLAGS=os.environ.get('CPPFLAGS', ''), CFLAGS=os.environ.get('CFLAGS', ''), CXXFLAGS=os.environ.get('CXXFLAGS', ''), LINKFLAGS=os.environ.get('LDFLAGS', '')) env.Append(CCFLAGS='-Wall -Werror -Wno-narrowing') srcs = Split('''\ common.cc config_parser.cc data_reader.cc data_writer.cc setting.cc settings_manager.cc ''') libxsettingsd = env.Library('xsettingsd', srcs) env['LIBS'] = libxsettingsd env.ParseConfig('pkg-config --cflags --libs x11') xsettingsd = env.Program('xsettingsd', 'xsettingsd.cc') dump_xsettings = env.Program('dump_xsettings', 'dump_xsettings.cc') Default([xsettingsd, dump_xsettings]) gtest_env = env.Clone() gtest_env.Append(CCFLAGS='-I/usr/src/gtest') gtest_env.VariantDir('.', '/usr/src/gtest/src', duplicate=0) libgtest = gtest_env.Library('gtest', 'gtest-all.cc') test_env = env.Clone() test_env.Append(CCFLAGS='-D__TESTING') test_env['LIBS'] += [libgtest, 'pthread'] tests = [] for file in Glob('*_test.cc', strings=True): tests += test_env.Program(file) test_env.RunTests('test', tests) xsettingsd-1.0.2/common.cc000066400000000000000000000051401377611665500155260ustar00rootroot00000000000000// Copyright 2009 Daniel Erat // All rights reserved. #include "common.h" #include #include #include using std::string; using std::vector; namespace xsettingsd { string StringPrintf(const char* format, ...) { char buffer[1024]; va_list argp; va_start(argp, format); vsnprintf(buffer, sizeof(buffer), format, argp); va_end(argp); return string(buffer); } vector SplitString(const string& str, const string& delim) { if (str.empty()) return vector(); if (delim.empty()) return vector(1, str); vector parts; size_t start = 0; while (start <= str.size()) { if (start == str.size()) { parts.push_back(string()); break; } size_t next = str.find(delim, start); if (next == string::npos) { parts.push_back(str.substr(start, str.size() - start)); break; } parts.push_back(str.substr(start, next - start)); start = next + delim.size(); } return parts; } bool IsLittleEndian() { int i = 1; return reinterpret_cast(&i)[0]; } int GetPadding(int length, int increment) { return (increment - (length % increment)) % increment; // From xsettings-common.h in Owen Taylor's reference implementation -- // "n" is length and "m" is increment, I think. This produces results // that don't seem like padding, though: when "n" is 2 and "m" is 4, it // produces 4. //return ((n + m - 1) & (~(m - 1))); } vector GetDefaultConfigFilePaths() { vector paths; // Try ~/.xsettingsd first. const char* home_dir = getenv("HOME"); if (home_dir) paths.push_back(StringPrintf("%s/.xsettingsd", home_dir)); // Next look under $XDG_CONFIG_HOME, or in $HOME/.config if $XDG_CONFIG_HOME // is unset. vector xdg_dirs; const char* xdg_config_home = getenv("XDG_CONFIG_HOME"); if (xdg_config_home && xdg_config_home[0] != '\0') xdg_dirs.push_back(xdg_config_home); else if (home_dir) xdg_dirs.push_back(StringPrintf("%s/.config", home_dir)); // Finally split the colon-delimited $XDG_CONFIG_DIRS variable. const char* xdg_config_dirs = getenv("XDG_CONFIG_DIRS"); if (xdg_config_dirs) { vector split_dirs = SplitString(xdg_config_dirs, ":"); xdg_dirs.insert(xdg_dirs.end(), split_dirs.begin(), split_dirs.end()); } else { xdg_dirs.push_back("/etc"); } for (size_t i = 0; i < xdg_dirs.size(); ++i) { paths.push_back(StringPrintf("%s/xsettingsd/xsettingsd.conf", xdg_dirs[i].c_str())); } return paths; } const char* kProgName = "xsettingsd"; } // namespace xsettingsd xsettingsd-1.0.2/common.h000066400000000000000000000021351377611665500153710ustar00rootroot00000000000000// Copyright 2009 Daniel Erat // All rights reserved. #ifndef __XSETTINGSD_COMMON_H__ #define __XSETTINGSD_COMMON_H__ #include #include namespace xsettingsd { #define DISALLOW_COPY_AND_ASSIGN(class_name) \ class_name(const class_name&); \ void operator=(const class_name&) std::string StringPrintf(const char* format, ...); // Splits |str| along |delim|. Repeated occurrences of |delim| in |str| will // result in empty strings in the output. An empty |delim| will result in |str| // being returned unsplit. An empty |str| will result in an empty vector. std::vector SplitString(const std::string& str, const std::string& delim); bool IsLittleEndian(); int GetPadding(int length, int increment); // Returns $HOME/.xsettingsd followed by all of the config file locations // specified by the XDG Base Directory Specification // (http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html). std::vector GetDefaultConfigFilePaths(); extern const char* kProgName; } // namespace xsettingsd #endif xsettingsd-1.0.2/common_test.cc000066400000000000000000000056541377611665500165770ustar00rootroot00000000000000// Copyright 2009 Daniel Erat // All rights reserved. #include #include #include #include #include "common.h" using std::string; using std::vector; namespace xsettingsd { namespace { // Returns |parts| joined by '|'. string Join(const vector& parts) { string out; for (size_t i = 0; i < parts.size(); ++i) out += (i > 0 ? "|" : "") + parts[i]; return out; } } // namespace TEST(CommonTest, SplitString) { EXPECT_EQ("a|b|c", Join(SplitString("a,b,c", ","))); EXPECT_EQ("a|b|", Join(SplitString("a,b,", ","))); EXPECT_EQ("|a|b", Join(SplitString(",a,b", ","))); EXPECT_EQ("|a|b|", Join(SplitString(",a,b,", ","))); EXPECT_EQ("a||b", Join(SplitString("a,,b", ","))); EXPECT_EQ("|", Join(SplitString(",", ","))); EXPECT_EQ("foo", Join(SplitString("foo", ","))); EXPECT_EQ("foo|bar", Join(SplitString("fooabcbar", "abc"))); EXPECT_EQ("|foo", Join(SplitString("abcfoo", "abc"))); EXPECT_EQ("foo|", Join(SplitString("fooabc", "abc"))); EXPECT_EQ(0, SplitString("", ",").size()); EXPECT_EQ(0, SplitString("", "").size()); EXPECT_EQ("abc", Join(SplitString("abc", ""))); } TEST(CommonTest, GetPadding) { EXPECT_EQ(0, GetPadding(0, 4)); EXPECT_EQ(3, GetPadding(1, 4)); EXPECT_EQ(2, GetPadding(2, 4)); EXPECT_EQ(1, GetPadding(3, 4)); EXPECT_EQ(0, GetPadding(4, 4)); EXPECT_EQ(3, GetPadding(5, 4)); EXPECT_EQ(2, GetPadding(6, 4)); EXPECT_EQ(1, GetPadding(7, 4)); EXPECT_EQ(0, GetPadding(8, 4)); } TEST(CommonTest, GetDefaultConfigFilePath) { // With $HOME missing and none of the XDG vars, we should just use /etc. ASSERT_EQ(0, unsetenv("HOME")); ASSERT_EQ(0, unsetenv("XDG_CONFIG_HOME")); ASSERT_EQ(0, unsetenv("XDG_CONFIG_DIRS")); EXPECT_EQ("/etc/xsettingsd/xsettingsd.conf", Join(GetDefaultConfigFilePaths())); // Now set $HOME. It should be searched first, followed by the default XDG // paths. ASSERT_EQ(0, setenv("HOME", "/home/user", 1 /* overwrite */)); EXPECT_EQ("/home/user/.xsettingsd|" "/home/user/.config/xsettingsd/xsettingsd.conf|" "/etc/xsettingsd/xsettingsd.conf", Join(GetDefaultConfigFilePaths())); // Use a custom $XDG_CONFIG_HOME. ASSERT_EQ(0, setenv("XDG_CONFIG_HOME", "/home/user/.myconf", 1)); EXPECT_EQ("/home/user/.xsettingsd|" "/home/user/.myconf/xsettingsd/xsettingsd.conf|" "/etc/xsettingsd/xsettingsd.conf", Join(GetDefaultConfigFilePaths())); // Now put a few paths in $XDG_CONFIG_DIRS. ASSERT_EQ(0, setenv("XDG_CONFIG_DIRS", "/etc2:/etc3", 1)); EXPECT_EQ("/home/user/.xsettingsd|" "/home/user/.myconf/xsettingsd/xsettingsd.conf|" "/etc2/xsettingsd/xsettingsd.conf|" "/etc3/xsettingsd/xsettingsd.conf", Join(GetDefaultConfigFilePaths())); } } // namespace xsettingsd int main(int argc, char** argv) { testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } xsettingsd-1.0.2/config_parser.cc000066400000000000000000000257761377611665500171000ustar00rootroot00000000000000// Copyright 2009 Daniel Erat // All rights reserved. #include "config_parser.h" #include #include #include #include #include #include #include "setting.h" using std::map; using std::string; using std::vector; namespace xsettingsd { ConfigParser::ConfigParser(CharStream* stream) : stream_(NULL), error_line_num_(0) { Reset(stream); } ConfigParser::~ConfigParser() { delete stream_; } string ConfigParser::FormatError() const { if (error_line_num_ == 0) return error_str_; return StringPrintf("%d: %s", error_line_num_, error_str_.c_str()); } void ConfigParser::Reset(CharStream* stream) { assert(stream); delete stream_; stream_ = stream; error_line_num_ = 0; error_str_.clear(); } bool ConfigParser::Parse(SettingsMap* settings, const SettingsMap* prev_settings, uint32_t serial) { assert(settings); settings->mutable_map()->clear(); string stream_error; if (!stream_->Init(&stream_error)) { SetErrorF("Couldn't init stream (%s)", stream_error.c_str()); return false; } enum State { // At the beginning of a line, before we've encountered a setting name. NO_SETTING_NAME = 0, // We've gotten the setting name but not its value. GOT_SETTING_NAME, // We've got the value. GOT_VALUE, }; State state = NO_SETTING_NAME; string setting_name; bool in_comment = false; while (!stream_->AtEOF()) { int ch = stream_->GetChar(); if (ch == '#') { in_comment = true; continue; } if (ch == '\n') { if (state == GOT_SETTING_NAME) { SetErrorF("No value for setting \"%s\"", setting_name.c_str()); return false; } state = NO_SETTING_NAME; setting_name.clear(); in_comment = false; } if (in_comment || isspace(ch)) continue; stream_->UngetChar(ch); switch (state) { case NO_SETTING_NAME: if (!ReadSettingName(&setting_name)) return false; if (settings->map().count(setting_name)) { SetErrorF("Got duplicate setting name \"%s\"", setting_name.c_str()); return false; } state = GOT_SETTING_NAME; break; case GOT_SETTING_NAME: { Setting* setting = NULL; if (!ReadValue(&setting)) return false; const Setting* prev_setting = prev_settings ? prev_settings->GetSetting(setting_name) : NULL; setting->UpdateSerial(prev_setting, serial); settings->mutable_map()->insert(make_pair(setting_name, setting)); } state = GOT_VALUE; break; case GOT_VALUE: SetErrorF("Got unexpected text after value"); return false; } } if (state != NO_SETTING_NAME && state != GOT_VALUE) { SetErrorF("Unexpected end of file"); return false; } return true; } bool ConfigParser::CharStream::Init(string* error_out) { assert(!initialized_); initialized_ = true; return InitImpl(error_out); } bool ConfigParser::CharStream::AtEOF() { assert(initialized_); return (!have_buffered_char_ && AtEOFImpl()); } int ConfigParser::CharStream::GetChar() { assert(initialized_); prev_at_line_end_ = at_line_end_; if (at_line_end_) { line_num_++; at_line_end_ = false; } int ch = 0; if (have_buffered_char_) { have_buffered_char_ = false; ch = buffered_char_; } else { ch = GetCharImpl(); } if (ch == '\n') at_line_end_ = true; return ch; } void ConfigParser::CharStream::UngetChar(int ch) { if (prev_at_line_end_) line_num_--; at_line_end_ = prev_at_line_end_; assert(initialized_); assert(!have_buffered_char_); buffered_char_ = ch; have_buffered_char_ = true; } ConfigParser::FileCharStream::FileCharStream(const string& filename) : filename_(filename), file_(NULL) { } ConfigParser::FileCharStream::~FileCharStream() { if (file_) { fclose(file_); file_ = NULL; } } bool ConfigParser::FileCharStream::InitImpl(string* error_out) { assert(!file_); file_ = fopen(filename_.c_str(), "r"); if (!file_) { if (error_out) *error_out = strerror(errno); return false; } return true; } bool ConfigParser::FileCharStream::AtEOFImpl() { assert(file_); int ch = GetChar(); UngetChar(ch); return ch == EOF; } int ConfigParser::FileCharStream::GetCharImpl() { assert(file_); return fgetc(file_); } ConfigParser::StringCharStream::StringCharStream(const string& data) : data_(data), pos_(0) { } bool ConfigParser::StringCharStream::AtEOFImpl() { return pos_ == data_.size(); } int ConfigParser::StringCharStream::GetCharImpl() { return data_.at(pos_++); } bool ConfigParser::ReadSettingName(string* name_out) { assert(name_out); name_out->clear(); bool prev_was_slash = false; while (true) { if (stream_->AtEOF()) break; int ch = stream_->GetChar(); if (isspace(ch) || ch == '#') { stream_->UngetChar(ch); break; } if (!(ch >= 'A' && ch <= 'Z') && !(ch >= 'a' && ch <= 'z') && !(ch >= '0' && ch <= '9') && !(ch == '_' || ch == '/')) { SetErrorF("Got invalid character '%c' in setting name", ch); return false; } if (ch == '/' && name_out->empty()) { SetErrorF("Got leading slash in setting name"); return false; } if (prev_was_slash) { if (ch == '/') { SetErrorF("Got two consecutive slashes in setting name"); return false; } if (ch >= '0' && ch <= '9') { SetErrorF("Got digit after slash in setting name"); return false; } } name_out->push_back(ch); prev_was_slash = (ch == '/'); } if (name_out->empty()) { SetErrorF("Got empty setting name"); return false; } if (name_out->at(name_out->size() - 1) == '/') { SetErrorF("Got trailing slash in setting name"); return false; } return true; } bool ConfigParser::ReadValue(Setting** setting_ptr) { assert(setting_ptr); *setting_ptr = NULL; if (stream_->AtEOF()) { SetErrorF("Got EOF when starting to read value"); return false; } int ch = stream_->GetChar(); stream_->UngetChar(ch); if ((ch >= '0' && ch <= '9') || ch == '-') { int32_t value = 0; if (!ReadInteger(&value)) return false; *setting_ptr = new IntegerSetting(value); } else if (ch == '"') { string value; if (!ReadString(&value)) return false; *setting_ptr = new StringSetting(value); } else if (ch == '(') { uint16_t red, green, blue, alpha; if (!ReadColor(&red, &green, &blue, &alpha)) return false; *setting_ptr = new ColorSetting(red, green, blue, alpha); } else { SetErrorF("Got invalid setting value"); return false; } return true; } bool ConfigParser::ReadInteger(int32_t* int_out) { assert(int_out); *int_out = 0; bool got_digit = false; bool negative = false; while (true) { if (stream_->AtEOF()) break; int ch = stream_->GetChar(); if (isspace(ch) || ch == '#') { stream_->UngetChar(ch); break; } if (ch == '-') { if (negative) { SetErrorF("Got extra '-' before integer"); return false; } if (!got_digit) { negative = true; continue; } else { SetErrorF("Got '-' mid-integer"); return false; } } if (!(ch >= '0' && ch <= '9')) { SetErrorF("Got non-numeric character '%c'", ch); return false; } got_digit = true; *int_out *= 10; *int_out += (ch - '0'); // TODO: Check for integer overflow (not a security hole; it'd just be // nice to warn the user that their setting is going to wrap). } if (!got_digit) { SetErrorF("Got empty integer"); return false; } if (negative) *int_out *= -1; return true; } bool ConfigParser::ReadString(string* str_out) { assert(str_out); str_out->clear(); bool in_backslash = false; if (stream_->AtEOF() || stream_->GetChar() != '\"') { SetErrorF("String is missing initial double-quote"); return false; } while (true) { if (stream_->AtEOF()) { SetErrorF("Open string at end of file"); return false; } int ch = stream_->GetChar(); if (ch == '\n') { SetErrorF("Got newline mid-string"); return false; } if (!in_backslash) { if (ch == '"') break; if (ch == '\\') { in_backslash = true; continue; } } if (in_backslash) { in_backslash = false; if (ch == 'n') ch = '\n'; else if (ch == 't') ch = '\t'; } str_out->push_back(ch); } return true; } bool ConfigParser::ReadColor(uint16_t* red_out, uint16_t* green_out, uint16_t* blue_out, uint16_t* alpha_out) { assert(red_out); assert(green_out); assert(blue_out); assert(alpha_out); if (stream_->AtEOF() || stream_->GetChar() != '(') { SetErrorF("Color is missing initial parethesis"); return false; } vector nums; enum State { BEFORE_NUM, IN_NUM, AFTER_NUM, }; int num = 0; State state = BEFORE_NUM; while (true) { if (stream_->AtEOF()) { SetErrorF("Got EOF mid-color"); return false; } int ch = stream_->GetChar(); if (ch == '\n') { SetErrorF("Got newline mid-color"); return false; } if (ch == ')') { if (state == BEFORE_NUM) { SetErrorF("Expected number but got ')'"); return false; } if (state == IN_NUM) nums.push_back(num); break; } if (isspace(ch)) { if (state == IN_NUM) { state = AFTER_NUM; nums.push_back(num); } continue; } if (ch == ',') { if (state == BEFORE_NUM) { SetErrorF("Got unexpected comma"); return false; } if (state == IN_NUM) nums.push_back(num); state = BEFORE_NUM; continue; } if (!(ch >= '0' && ch <= '9')) { SetErrorF("Got non-numeric character '%c'", ch); return false; } if (state == AFTER_NUM) { SetErrorF("Got unexpected digit '%c'", ch); return false; } if (state == BEFORE_NUM) { state = IN_NUM; num = 0; } num = num * 10 + (ch - '0'); // TODO: Check for integer overflow (not a security hole; it'd just be // nice to warn the user that their setting is going to wrap). } if (nums.size() < 3 || nums.size() > 4) { SetErrorF("Got %d number%s instead of 3 or 4", nums.size(), (nums.size() == 1 ? "" : "s")); return false; } *red_out = nums.at(0); *green_out = nums.at(1); *blue_out = nums.at(2); *alpha_out = (nums.size() == 4) ? nums.at(3) : 65535; return true; } void ConfigParser::SetErrorF(const char* format, ...) { char buffer[1024]; va_list argp; va_start(argp, format); vsnprintf(buffer, sizeof(buffer), format, argp); va_end(argp); error_line_num_ = stream_->line_num(); error_str_.assign(buffer); } } // namespace xsettingsd xsettingsd-1.0.2/config_parser.h000066400000000000000000000120531377611665500167220ustar00rootroot00000000000000// Copyright 2009 Daniel Erat // All rights reserved. #ifndef __XSETTINGSD_CONFIG_PARSER_H__ #define __XSETTINGSD_CONFIG_PARSER_H__ #include #include #include #ifdef __TESTING #include #endif #include "common.h" namespace xsettingsd { class Setting; class SettingsMap; // Doing the parsing by hand like this for a line-based config format is // pretty much the worst idea ever -- it would've been much easier to use // libpcrecpp. :-( The tests all pass, though, for whatever that's worth. class ConfigParser { public: class CharStream; // The parser takes ownership of 'stream', which must be uninitialized // (that is, its Init() method shouldn't have been called yet). explicit ConfigParser(CharStream* stream); ~ConfigParser(); // Returns a formatted string describing a parse error. Can be called // after Parse() returns false. std::string FormatError() const; // Reset the parser to read from a new stream. void Reset(CharStream* stream); // Parse the data in the stream into 'settings', using 'prev_settings' // (pass the previous version if it exists or NULL otherwise) and // 'serial' (the new serial number) to determine which serial number each // setting should have. This method calls the stream's Init() method; // don't do it beforehand. bool Parse(SettingsMap* settings, const SettingsMap* prev_settings, uint32_t serial); // Abstract base class for reading a stream of characters. class CharStream { public: CharStream() : initialized_(false), have_buffered_char_(false), buffered_char_(0), at_line_end_(true), prev_at_line_end_(false), line_num_(0) { } virtual ~CharStream() {} int line_num() const { return line_num_; } // Must be called before using the stream. // The stream is unusable if false is returned. bool Init(std::string* error_out); // Are we currently at the end of the stream? bool AtEOF(); // Get the next character in the stream. int GetChar(); // Push a previously-read character back onto the stream. // At most one character can be buffered. void UngetChar(int ch); private: virtual bool InitImpl(std::string* error_out) { return true; } virtual bool AtEOFImpl() = 0; virtual int GetCharImpl() = 0; // Has Init() been called? bool initialized_; // Has a character been returned with UngetChar() but not yet re-read? bool have_buffered_char_; // The character returned by UngetChar(). int buffered_char_; // Are we currently at the end of the line? bool at_line_end_; bool prev_at_line_end_; // The line number of the last-fetched character. int line_num_; DISALLOW_COPY_AND_ASSIGN(CharStream); }; // An implementation of CharStream that reads from a file. class FileCharStream : public CharStream { public: FileCharStream(const std::string& filename); ~FileCharStream(); private: bool InitImpl(std::string* error_out); bool AtEOFImpl(); int GetCharImpl(); std::string filename_; FILE* file_; DISALLOW_COPY_AND_ASSIGN(FileCharStream); }; // An implementation of CharStream that reads from an in-memory string. class StringCharStream : public CharStream { public: StringCharStream(const std::string& data); private: bool AtEOFImpl(); int GetCharImpl(); std::string data_; size_t pos_; DISALLOW_COPY_AND_ASSIGN(StringCharStream); }; private: #ifdef __TESTING friend class ConfigParserTest; FRIEND_TEST(ConfigParserTest, ReadSettingName); #endif // Read a setting name starting at the current position in the stream. // Returns false if the setting name is invalid. bool ReadSettingName(std::string* name_out); // Read the value starting at the current position in the stream. // Its type is inferred from the first character. 'setting_ptr' is // updated to point at a newly-allocated Setting object (which the caller // is responsible for deleting). bool ReadValue(Setting** setting_ptr); // Read an integer starting at the current position in the stream. bool ReadInteger(int32_t* int_out); // Read a double-quoted string starting at the current position in the // stream. bool ReadString(std::string* str_out); // Read a color of the form "(red, green, blue, alpha)" or "(red, green, // blue)". bool ReadColor(uint16_t* red_out, uint16_t* green_out, uint16_t* blue_out, uint16_t* alpha_out); // Record an error to 'error_str_', also saving the stream's current line // number to 'error_line_num_'. void SetErrorF(const char* format, ...); // Stream from which the config is being parsed. CharStream* stream_; // If an error was encountered while parsing, the line number where // it happened and a string describing it. Line 0 is used for errors // occuring before making any progress into the file. int error_line_num_; std::string error_str_; DISALLOW_COPY_AND_ASSIGN(ConfigParser); }; } // namespace #endif xsettingsd-1.0.2/config_parser_test.cc000066400000000000000000000356271377611665500201330ustar00rootroot00000000000000// Copyright 2009 Daniel Erat // All rights reserved. #include #include #define __STDC_LIMIT_MACROS // needed to get MAX and MIN macros from stdint.h #include #include #include #include "config_parser.h" #include "setting.h" using std::map; using std::string; namespace xsettingsd { // Test the basic operation of CharStream. TEST(CharStreamTest, Basic) { ConfigParser::StringCharStream stream("012"); // We should die if we try to do anything before calling Init(). ASSERT_DEATH(stream.AtEOF(), "initialized"); ASSERT_DEATH(stream.GetChar(), "initialized"); ASSERT_DEATH(stream.UngetChar('0'), "initialized"); // Now read a character, put it back, and read it again. ASSERT_TRUE(stream.Init(NULL)); EXPECT_FALSE(stream.AtEOF()); EXPECT_EQ('0', stream.GetChar()); stream.UngetChar('0'); EXPECT_EQ('0', stream.GetChar()); // Do the same thing with the second character... EXPECT_FALSE(stream.AtEOF()); EXPECT_EQ('1', stream.GetChar()); stream.UngetChar('1'); EXPECT_EQ('1', stream.GetChar()); // ... and with the third. We should be at EOF after reading it. EXPECT_EQ('2', stream.GetChar()); EXPECT_TRUE(stream.AtEOF()); stream.UngetChar('2'); EXPECT_FALSE(stream.AtEOF()); EXPECT_EQ('2', stream.GetChar()); EXPECT_TRUE(stream.AtEOF()); } // Test that line numbers are reported correctly as we get and un-get // characters. TEST(CharStreamTest, LineNumbers) { ConfigParser::StringCharStream stream("a\nb\n\nc"); // We use line 0 to represent not having read any characters yet. ASSERT_TRUE(stream.Init(NULL)); EXPECT_EQ(0, stream.line_num()); // Getting the first 'a' should put us on line 1. We move back to 0 when // we un-get it and back to 1 when we re-get it. EXPECT_EQ('a', stream.GetChar()); EXPECT_EQ(1, stream.line_num()); stream.UngetChar('a'); EXPECT_EQ(0, stream.line_num()); EXPECT_EQ('a', stream.GetChar()); EXPECT_EQ(1, stream.line_num()); // The first newline should show up as being on line 1 as well. EXPECT_EQ('\n', stream.GetChar()); EXPECT_EQ(1, stream.line_num()); stream.UngetChar('\n'); EXPECT_EQ(1, stream.line_num()); EXPECT_EQ('\n', stream.GetChar()); EXPECT_EQ(1, stream.line_num()); // The 'b' is on line 2... EXPECT_EQ('b', stream.GetChar()); EXPECT_EQ(2, stream.line_num()); stream.UngetChar('b'); EXPECT_EQ(1, stream.line_num()); EXPECT_EQ('b', stream.GetChar()); EXPECT_EQ(2, stream.line_num()); // ... as is the first newline after it. EXPECT_EQ('\n', stream.GetChar()); EXPECT_EQ(2, stream.line_num()); stream.UngetChar('\n'); EXPECT_EQ(2, stream.line_num()); EXPECT_EQ('\n', stream.GetChar()); EXPECT_EQ(2, stream.line_num()); // The second newline should show up as being on line 3. EXPECT_EQ('\n', stream.GetChar()); EXPECT_EQ(3, stream.line_num()); stream.UngetChar('\n'); EXPECT_EQ(2, stream.line_num()); EXPECT_EQ('\n', stream.GetChar()); EXPECT_EQ(3, stream.line_num()); // And the 'c' is on line 4. EXPECT_EQ('c', stream.GetChar()); EXPECT_EQ(4, stream.line_num()); stream.UngetChar('c'); EXPECT_EQ(3, stream.line_num()); EXPECT_EQ('c', stream.GetChar()); EXPECT_EQ(4, stream.line_num()); EXPECT_TRUE(stream.AtEOF()); } class ConfigParserTest : public testing::Test { protected: // Helper methods to get the return value or string from // ConfigParser::ReadSettingName(). bool GetReadSettingNameResult(const string& input) { bool result; RunReadSettingName(input, &result, NULL); return result; } string GetReadSettingNameData(const string& input) { bool result; string data; RunReadSettingName(input, &result, &data); assert(result); return data; } bool GetReadIntegerResult(const string& input) { bool result; RunReadInteger(input, &result, NULL); return result; } int32_t GetReadIntegerData(const string& input) { bool result; int32_t data; RunReadInteger(input, &result, &data); assert(result); return data; } bool GetReadStringResult(const string& input) { bool result; RunReadString(input, &result, NULL); return result; } string GetReadStringData(const string& input) { bool result; string data; RunReadString(input, &result, &data); assert(result); return data; } bool GetReadColorResult(const string& input) { bool result; RunReadColor(input, &result, NULL); return result; } string GetReadColorData(const string& input) { bool result; string data; RunReadColor(input, &result, &data); assert(result); return data; } private: void RunReadSettingName(const string& input, bool* result_out, string* name_out) { ConfigParser::CharStream* stream = new ConfigParser::StringCharStream(input); assert(stream->Init(NULL)); ConfigParser parser(stream); string name; bool result = parser.ReadSettingName(&name); if (result_out) *result_out = result; if (name_out) *name_out = name; } void RunReadInteger(const string& input, bool* result_out, int32_t* num_out) { ConfigParser::CharStream* stream = new ConfigParser::StringCharStream(input); assert(stream->Init(NULL)); ConfigParser parser(stream); int32_t num = 0; bool result = parser.ReadInteger(&num); if (result_out) *result_out = result; if (num_out) *num_out = num; } void RunReadString(const string& input, bool* result_out, string* str_out) { ConfigParser::CharStream* stream = new ConfigParser::StringCharStream(input); assert(stream->Init(NULL)); ConfigParser parser(stream); string str; bool result = parser.ReadString(&str); if (result_out) *result_out = result; if (str_out) *str_out = str; } void RunReadColor(const string& input, bool* result_out, string* str_out) { ConfigParser::CharStream* stream = new ConfigParser::StringCharStream(input); assert(stream->Init(NULL)); ConfigParser parser(stream); uint16_t red = 0, green = 0, blue = 0, alpha = 0; bool result = parser.ReadColor(&red, &green, &blue, &alpha); if (result_out) *result_out = result; if (str_out) *str_out = StringPrintf("(%d,%d,%d,%d)", red, green, blue, alpha); } }; TEST_F(ConfigParserTest, ReadSettingName) { EXPECT_EQ("test", GetReadSettingNameData("test")); EXPECT_EQ("First/Second", GetReadSettingNameData("First/Second")); EXPECT_EQ("Has_Underscore", GetReadSettingNameData("Has_Underscore")); EXPECT_EQ("trailing_space", GetReadSettingNameData("trailing_space ")); EXPECT_EQ("blah", GetReadSettingNameData("blah#comment")); EXPECT_FALSE(GetReadSettingNameResult(" leading_space")); EXPECT_FALSE(GetReadSettingNameResult("/leading_slash")); EXPECT_FALSE(GetReadSettingNameResult("trailing_slash/")); EXPECT_FALSE(GetReadSettingNameResult("double//slash")); EXPECT_FALSE(GetReadSettingNameResult("digit_after_slash/0")); // For good measure, test the examples of legitimate and illegitimate // names from the spec. EXPECT_EQ("GTK/colors/background0", GetReadSettingNameData("GTK/colors/background0")); EXPECT_EQ("_background", GetReadSettingNameData("_background")); EXPECT_EQ("_111", GetReadSettingNameData("_111")); EXPECT_FALSE(GetReadSettingNameResult("/")); EXPECT_FALSE(GetReadSettingNameResult("_background/")); EXPECT_FALSE(GetReadSettingNameResult("GTK//colors")); EXPECT_FALSE(GetReadSettingNameResult("")); } TEST_F(ConfigParserTest, ReadInteger) { EXPECT_EQ(0, GetReadIntegerData("0")); EXPECT_EQ(10, GetReadIntegerData("10")); EXPECT_EQ(12, GetReadIntegerData("0012")); EXPECT_EQ(15, GetReadIntegerData("15#2 comment")); EXPECT_EQ(20, GetReadIntegerData("20 ")); EXPECT_EQ(30, GetReadIntegerData("30\n")); EXPECT_EQ(INT32_MAX, GetReadIntegerData("2147483647")); EXPECT_EQ(-5, GetReadIntegerData("-5")); EXPECT_EQ(INT32_MIN, GetReadIntegerData("-2147483648")); EXPECT_FALSE(GetReadIntegerResult("")); EXPECT_FALSE(GetReadIntegerResult("-")); EXPECT_FALSE(GetReadIntegerResult("--2")); EXPECT_FALSE(GetReadIntegerResult("2-3")); EXPECT_FALSE(GetReadIntegerResult(" ")); EXPECT_FALSE(GetReadIntegerResult(" 23")); } TEST_F(ConfigParserTest, ReadString) { EXPECT_EQ("test", GetReadStringData("\"test\"")); EXPECT_EQ(" some whitespace ", GetReadStringData("\" some whitespace \"")); EXPECT_EQ("a\tb\nc", GetReadStringData("\"a\\tb\\nc\"")); EXPECT_EQ("a\"b\\c", GetReadStringData("\"a\\\"b\\\\c\"")); EXPECT_EQ("ar", GetReadStringData("\"\\a\\r\"")); EXPECT_EQ(" ", GetReadStringData("\" \"")); EXPECT_EQ("", GetReadStringData("\"\"")); EXPECT_EQ("a", GetReadStringData("\"a\" ")); EXPECT_FALSE(GetReadStringResult("")); EXPECT_FALSE(GetReadStringResult("a")); EXPECT_FALSE(GetReadStringResult("\"")); EXPECT_FALSE(GetReadStringResult("\"\n\"")); } TEST_F(ConfigParserTest, ReadColor) { EXPECT_EQ("(1,2,3,4)", GetReadColorData("(1,2,3,4)")); EXPECT_EQ("(1,2,3,65535)", GetReadColorData("(1,2,3)")); EXPECT_EQ("(32768,32769,32770,32771)", GetReadColorData("( 32768 ,32769 , 32770, 32771 )")); EXPECT_FALSE(GetReadColorResult("")); EXPECT_FALSE(GetReadColorResult("(")); EXPECT_FALSE(GetReadColorResult(")")); EXPECT_FALSE(GetReadColorResult("()")); EXPECT_FALSE(GetReadColorResult("( )")); EXPECT_FALSE(GetReadColorResult("(2)")); EXPECT_FALSE(GetReadColorResult("(,2)")); EXPECT_FALSE(GetReadColorResult("(2,)")); EXPECT_FALSE(GetReadColorResult("(2,3)")); EXPECT_FALSE(GetReadColorResult("(2,3,4,)")); EXPECT_FALSE(GetReadColorResult("(2,3,,4)")); EXPECT_FALSE(GetReadColorResult("(2,3,4,5,)")); EXPECT_FALSE(GetReadColorResult("(2(,3,4,5)")); EXPECT_FALSE(GetReadColorResult("(2,3,4,5,6)")); EXPECT_FALSE(GetReadColorResult("(2a,3,4,5)")); EXPECT_FALSE(GetReadColorResult("(2 1,3,4,5)")); EXPECT_FALSE(GetReadColorResult("(2,3,4,5")); EXPECT_FALSE(GetReadColorResult("(2,3\n,4,5)")); } testing::AssertionResult IntegerSettingEquals( const char* expected_expr, const char* actual_expr, int32_t expected, const Setting* actual) { if (!actual) { testing::Message msg; msg << "Expected: " << expected << "\n" << " Actual: " << actual_expr << " is NULL"; return testing::AssertionFailure(msg); } const IntegerSetting *setting = dynamic_cast(actual); if (!setting) { testing::Message msg; msg << "Expected: " << expected << "\n" << " Actual: " << actual_expr << " (not an IntegerSetting)"; return testing::AssertionFailure(msg); } if (setting->value() != expected) { testing::Message msg; msg << "Expected: " << expected << "\n" << " Actual: " << actual_expr << " contains " << setting->value(); return testing::AssertionFailure(msg); } return testing::AssertionSuccess(); } testing::AssertionResult StringSettingEquals( const char* expected_expr, const char* actual_expr, const string& expected, const Setting* actual) { if (!actual) { testing::Message msg; msg << "Expected: " << expected << "\n" << " Actual: " << actual_expr << " is NULL"; return testing::AssertionFailure(msg); } const StringSetting *setting = dynamic_cast(actual); if (!setting) { testing::Message msg; msg << "Expected: " << expected << "\n" << " Actual: " << actual_expr << " (not a StringSetting)"; return testing::AssertionFailure(msg); } if (setting->value() != expected) { testing::Message msg; msg << "Expected: \"" << expected << "\"\n" << " Actual: " << actual_expr << " contains \"" << setting->value() << "\""; return testing::AssertionFailure(msg); } return testing::AssertionSuccess(); } testing::AssertionResult ColorSettingEquals( const char* expected_expr, const char* actual_expr, const string& expected_str, const Setting* actual) { if (!actual) { testing::Message msg; msg << "Expected: " << expected_str << "\n" << " Actual: " << actual_expr << " is NULL"; return testing::AssertionFailure(msg); } const ColorSetting *setting = dynamic_cast(actual); if (!setting) { testing::Message msg; msg << "Expected: " << expected_str << "\n" << " Actual: " << actual_expr << " (not a ColorSetting)"; return testing::AssertionFailure(msg); } string actual_str = StringPrintf("(%d,%d,%d,%d)", setting->red(), setting->green(), setting->blue(), setting->alpha()); if (actual_str != expected_str) { testing::Message msg; msg << "Expected: \"" << expected_str << "\"\n" << " Actual: " << actual_expr << " contains \"" << actual_str << "\""; return testing::AssertionFailure(msg); } return testing::AssertionSuccess(); } TEST_F(ConfigParserTest, Parse) { const char* good_input = "Setting1 5\n" "Setting2 \"this is a string\"\n" "# commented line\n" "\n" "Setting3 2 # trailing comment\n" "Setting4 \"\\\"quoted\\\"\"# comment\n" "Setting5 (45,21, 5 , 8)# color"; ConfigParser parser(new ConfigParser::StringCharStream(good_input)); SettingsMap settings; ASSERT_TRUE(parser.Parse(&settings, NULL, 0)); ASSERT_EQ(5, settings.map().size()); EXPECT_PRED_FORMAT2(IntegerSettingEquals, 5, settings.GetSetting("Setting1")); EXPECT_PRED_FORMAT2(StringSettingEquals, "this is a string", settings.GetSetting("Setting2")); EXPECT_PRED_FORMAT2(IntegerSettingEquals, 2, settings.GetSetting("Setting3")); EXPECT_PRED_FORMAT2(StringSettingEquals, "\"quoted\"", settings.GetSetting("Setting4")); EXPECT_PRED_FORMAT2(ColorSettingEquals, "(45,21,5,8)", settings.GetSetting("Setting5")); const char* extra_name = "SettingName 3 blah"; parser.Reset(new ConfigParser::StringCharStream(extra_name)); EXPECT_FALSE(parser.Parse(&settings, NULL, 0)); const char* missing_value = "SettingName"; parser.Reset(new ConfigParser::StringCharStream(missing_value)); EXPECT_FALSE(parser.Parse(&settings, NULL, 0)); const char* comment_instead_of_value = "SettingName # test 3\n"; parser.Reset(new ConfigParser::StringCharStream(comment_instead_of_value)); EXPECT_FALSE(parser.Parse(&settings, NULL, 0)); const char* duplicate_name = "SettingName 4\nSettingName 3"; parser.Reset(new ConfigParser::StringCharStream(duplicate_name)); EXPECT_FALSE(parser.Parse(&settings, NULL, 0)); } } // namespace xsettingsd int main(int argc, char** argv) { testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } xsettingsd-1.0.2/data_reader.cc000066400000000000000000000033541377611665500164760ustar00rootroot00000000000000// Copyright 2009 Daniel Erat // All rights reserved. #include "data_reader.h" #include using std::string; namespace xsettingsd { DataReader::DataReader(const char* buffer, size_t buf_len) : buffer_(buffer), buf_len_(buf_len), bytes_read_(0), reverse_bytes_(false) { } bool DataReader::ReadBytes(string* out, size_t size) { if (bytes_read_ + size > buf_len_ || bytes_read_ + size < bytes_read_) { return false; } if (out) out->assign(buffer_ + bytes_read_, size); bytes_read_ += size; return true; } bool DataReader::ReadInt8(int8_t* out) { if (bytes_read_ + sizeof(int8_t) > buf_len_ || bytes_read_ + sizeof(int8_t) < bytes_read_) { return false; } if (out) *out = *reinterpret_cast(buffer_ + bytes_read_); bytes_read_ += sizeof(int8_t); return true; } bool DataReader::ReadInt16(int16_t* out) { if (bytes_read_ + sizeof(int16_t) > buf_len_ || bytes_read_ + sizeof(int16_t) < bytes_read_) { return false; } if (out) { *out = *reinterpret_cast(buffer_ + bytes_read_); if (reverse_bytes_) { if (IsLittleEndian()) *out = ntohs(*out); else *out = htons(*out); } } bytes_read_ += sizeof(int16_t); return true; } bool DataReader::ReadInt32(int32_t* out) { if (bytes_read_ + sizeof(int32_t) > buf_len_ || bytes_read_ + sizeof(int32_t) < bytes_read_) { return false; } if (out) { *out = *reinterpret_cast(buffer_ + bytes_read_); if (reverse_bytes_) { if (IsLittleEndian()) *out = ntohl(*out); else *out = htonl(*out); } } bytes_read_ += sizeof(int32_t); return true; } } // namespace xsettingsd xsettingsd-1.0.2/data_reader.h000066400000000000000000000015001377611665500163270ustar00rootroot00000000000000// Copyright 2009 Daniel Erat // All rights reserved. #ifndef __XSETTINGSD_DATA_READER_H__ #define __XSETTINGSD_DATA_READER_H__ #include // for size_t #include #include #include "common.h" namespace xsettingsd { // Like DataWriter, but not. class DataReader { public: DataReader(const char* buffer, size_t buf_len); void set_reverse_bytes(bool reverse) { reverse_bytes_ = reverse; } size_t bytes_read() const { return bytes_read_; } bool ReadBytes(std::string* out, size_t size); bool ReadInt8(int8_t* out); bool ReadInt16(int16_t* out); bool ReadInt32(int32_t* out); private: const char* buffer_; // not owned size_t buf_len_; size_t bytes_read_; bool reverse_bytes_; DISALLOW_COPY_AND_ASSIGN(DataReader); }; } // namespace xsettingsd #endif xsettingsd-1.0.2/data_writer.cc000066400000000000000000000030761377611665500165510ustar00rootroot00000000000000// Copyright 2009 Daniel Erat // All rights reserved. #include "data_writer.h" #include #include using std::min; namespace xsettingsd { DataWriter::DataWriter(char* buffer, size_t buf_len) : buffer_(buffer), buf_len_(buf_len), bytes_written_(0) { } bool DataWriter::WriteBytes(const char* data, size_t bytes_to_write) { if (bytes_to_write > buf_len_ - bytes_written_) return false; memcpy(buffer_ + bytes_written_, data, min(bytes_to_write, buf_len_ - bytes_written_)); bytes_written_ += bytes_to_write; return true; } bool DataWriter::WriteInt8(int8_t num) { if (sizeof(int8_t) > buf_len_ - bytes_written_) return false; *(reinterpret_cast(buffer_ + bytes_written_)) = num; bytes_written_ += sizeof(int8_t); return true; } bool DataWriter::WriteInt16(int16_t num) { if (sizeof(int16_t) > buf_len_ - bytes_written_) return false; *(reinterpret_cast(buffer_ + bytes_written_)) = num; bytes_written_ += sizeof(int16_t); return true; } bool DataWriter::WriteInt32(int32_t num) { if (sizeof(int32_t) > buf_len_ - bytes_written_) return false; *(reinterpret_cast(buffer_ + bytes_written_)) = num; bytes_written_ += sizeof(int32_t); return true; } bool DataWriter::WriteZeros(size_t bytes_to_write) { if (bytes_to_write > buf_len_ - bytes_written_) return false; bzero(buffer_ + bytes_written_, min(bytes_to_write, buf_len_ - bytes_written_)); bytes_written_ += bytes_to_write; return true; } } // namespace xsettingsd xsettingsd-1.0.2/data_writer.h000066400000000000000000000014571377611665500164140ustar00rootroot00000000000000// Copyright 2009 Daniel Erat // All rights reserved. #ifndef __XSETTINGSD_DATA_WRITER_H__ #define __XSETTINGSD_DATA_WRITER_H__ #include // for size_t #include #include "common.h" namespace xsettingsd { // Provides an interface for writing different types of data to a buffer. class DataWriter { public: DataWriter(char* buffer, size_t buf_len); size_t bytes_written() const { return bytes_written_; } bool WriteBytes(const char* data, size_t bytes_to_write); bool WriteInt8(int8_t num); bool WriteInt16(int16_t num); bool WriteInt32(int32_t num); bool WriteZeros(size_t bytes_to_write); private: char* buffer_; // not owned size_t buf_len_; size_t bytes_written_; DISALLOW_COPY_AND_ASSIGN(DataWriter); }; } // namespace xsettingsd #endif xsettingsd-1.0.2/dump_xsettings.1000066400000000000000000000013271377611665500170710ustar00rootroot00000000000000.TH DUMP_XSETTINGS 1 .SH NAME dump_xsettings \- prints X11 application settings from an XSETTINGS provider .SH SYNOPSIS .B dump_xsettings .RI [ options ] .SH DESCRIPTION The XSETTINGS specification provides a method to notify X11 applications about changes to system settings. \fBdump_xsettings\fR prints an X screen's current settings using \fIxsettingsd\fR(1)'s configuration file format. .SH OPTIONS .TP \fB\-h\fR, \fB\-\-help\fR Display a help message and exit. .TP \fB\-s\fR, \fB\-\-screen\fR=\fISCREEN\fR Use the X screen numbered \fISCREEN\fR (default is 0). .SH BUGS \fIhttps://github.com/derat/xsettingsd/issues\fR .SH SEE ALSO \fIxsettingsd\fR(1) .SH AUTHOR dump_xsettings was written by Daniel Erat . xsettingsd-1.0.2/dump_xsettings.cc000066400000000000000000000175301377611665500173210ustar00rootroot00000000000000// Copyright 2009 Daniel Erat // All rights reserved. #include #include #include #include #include #include #include #include #include "common.h" #include "data_reader.h" #include "setting.h" using std::min; using std::string; namespace xsettingsd { bool GetData(int screen, char* buffer, size_t buffer_size, size_t* data_size) { assert(data_size); Display* display = XOpenDisplay(NULL); if (!display) { fprintf(stderr, "Couldn't open display\n"); return false; } string sel_name = StringPrintf("_XSETTINGS_S%d", screen); Atom sel_atom = XInternAtom(display, sel_name.c_str(), False); Window win = XGetSelectionOwner(display, sel_atom); if (win == None) { fprintf(stderr, "No current owner for %s selection\n", sel_name.c_str()); return false; } static const char* kPropName = "_XSETTINGS_SETTINGS"; Atom prop_atom = XInternAtom(display, kPropName, False); Atom type_ret = None; int format_ret = 0; unsigned long num_items_ret = 0; unsigned long rem_bytes_ret = 0; unsigned char* prop_ret = NULL; int retval = XGetWindowProperty(display, win, prop_atom, 0, // offset buffer_size / 4, // length (32-bit multiples) False, // delete AnyPropertyType, // type &type_ret, // actual type &format_ret, // actual format &num_items_ret, // actual num items &rem_bytes_ret, // remaining bytes &prop_ret); // property if (retval != Success) { fprintf(stderr, "XGetWindowProperty() returned %d\n", retval); return false; } if (num_items_ret == 0) { fprintf(stderr, "Property %s doesn't exist on 0x%x\n", kPropName, static_cast(win)); return false; } if (rem_bytes_ret > 0) { fprintf(stderr, "Property %s on 0x%x is more than %zu bytes (%lu remain)\n", kPropName, static_cast(win), buffer_size, rem_bytes_ret); XFree(prop_ret); return false; } if (format_ret != 8) { fprintf(stderr, "Got unexpected format %d\n", format_ret); XFree(prop_ret); return false; } *data_size = min(buffer_size, static_cast(num_items_ret)); memcpy(buffer, prop_ret, *data_size); return true; } bool DumpSetting(DataReader* reader) { int8_t type_byte = 0; if (!reader->ReadInt8(&type_byte)) { fprintf(stderr, "Unable to read setting type\n"); return false; } Setting::Type type = static_cast(type_byte); if (type != Setting::TYPE_INTEGER && type != Setting::TYPE_STRING && type != Setting::TYPE_COLOR) { fprintf(stderr, "Got setting with unhandled type %d\n", type); return false; } if (!reader->ReadBytes(NULL, 1)) { fprintf(stderr, "Unable to read 1-byte setting padding\n"); return false; } uint16_t name_size = 0; if (!reader->ReadInt16(reinterpret_cast(&name_size))) { fprintf(stderr, "Unable to read setting name size\n"); return false; } string name; if (!reader->ReadBytes(&name, name_size)) { fprintf(stderr, "Unable to read %u-byte setting name\n", name_size); return false; } size_t name_padding = GetPadding(name_size, 4); if (!reader->ReadBytes(NULL, name_padding)) { fprintf(stderr, "Unable to read %zu-byte setting name padding\n", name_padding); return false; } if (!reader->ReadBytes(NULL, 4)) { fprintf(stderr, "Unable to read setting serial number\n"); return false; } if (type == Setting::TYPE_INTEGER) { int32_t value = 0; if (!reader->ReadInt32(&value)) { fprintf(stderr, "Unable to read integer setting value\n"); return false; } printf("%s %d\n", name.c_str(), value); } else if (type == Setting::TYPE_STRING) { uint32_t value_size = 0; if (!reader->ReadInt32(reinterpret_cast(&value_size))) { fprintf(stderr, "Unable to read string setting value size\n"); return false; } string value; if (!reader->ReadBytes(&value, value_size)) { fprintf(stderr, "Unable to read %u-byte string setting value\n", value_size); return false; } size_t value_padding = GetPadding(value_size, 4); if (!reader->ReadBytes(NULL, value_padding)) { fprintf(stderr, "Unable to read %zu-byte string setting value padding\n", value_padding); return false; } string escaped_value; for (size_t i = 0; i < value.size(); ++i) { char ch = value.c_str()[i]; switch (ch) { case '\n': escaped_value.append("\\n"); break; case '"': escaped_value.append("\\\""); break; default: escaped_value.push_back(ch); } } printf("%s \"%s\"\n", name.c_str(), escaped_value.c_str()); } else if (type == Setting::TYPE_COLOR) { uint16_t red = 0, blue = 0, green = 0, alpha = 0; if (!reader->ReadInt16(reinterpret_cast(&red)) || !reader->ReadInt16(reinterpret_cast(&blue)) || !reader->ReadInt16(reinterpret_cast(&green)) || !reader->ReadInt16(reinterpret_cast(&alpha))) { fprintf(stderr, "Unable to read color values\n"); return false; } // Note that unlike the spec, our config uses RGB-order, not RBG. printf("%s (%u, %u, %u, %u)\n", name.c_str(), red, green, blue, alpha); } else { assert(false); } return true; } bool DumpSettings(DataReader* reader) { int byte_order = IsLittleEndian() ? LSBFirst : MSBFirst; // Read 1-byte byte order. int8_t prop_byte_order = 0; if (!reader->ReadInt8(&prop_byte_order)) { fprintf(stderr, "Couldn't read byte order\n"); return false; } if (prop_byte_order != byte_order) { reader->set_reverse_bytes(true); } // Read 3 bytes of padding and 4-byte serial. if (!reader->ReadBytes(NULL, 7)) { fprintf(stderr, "Unable to read header\n"); return false; } uint32_t num_settings = 0; if (!reader->ReadInt32(reinterpret_cast(&num_settings))) { fprintf(stderr, "Unable to read number of settings\n"); return false; } for (uint32_t i = 0; i < num_settings; ++i) { if (!DumpSetting(reader)) return false; } return true; } } // namespace xsettingsd int main(int argc, char** argv) { static const char* kUsage = "Usage: dump_xsettings [OPTION] ...\n" "\n" "Dump current XSETTINGS values in xsettingd's format.\n" "\n" "Options: -h, --help print this help message\n" " -s, --screen=SCREEN screen to use (default is 0)\n"; int screen = 0; struct option options[] = { { "help", 0, NULL, 'h', }, { "screen", 1, NULL, 's', }, { NULL, 0, NULL, 0 }, }; opterr = 0; while (true) { int ch = getopt_long(argc, argv, "hs:", options, NULL); if (ch == -1) { break; } else if (ch == 'h' || ch == '?') { fprintf(stderr, "%s", kUsage); return 1; } else if (ch == 's') { char* endptr = NULL; screen = strtol(optarg, &endptr, 10); if (optarg[0] == '\0' || endptr[0] != '\0' || screen < 0) { fprintf(stderr, "Invalid screen \"%s\"\n", optarg); return 1; } } } static const size_t kBufferSize = 2 << 15; char buffer[kBufferSize]; size_t data_size = 0; if (!xsettingsd::GetData(screen, buffer, kBufferSize, &data_size)) return 1; assert(data_size <= kBufferSize); xsettingsd::DataReader reader(buffer, data_size); if (!xsettingsd::DumpSettings(&reader)) return 1; return 0; } xsettingsd-1.0.2/setting.cc000066400000000000000000000054101377611665500157130ustar00rootroot00000000000000// Copyright 2009 Daniel Erat // All rights reserved. #include "setting.h" #include "data_writer.h" using std::string; namespace xsettingsd { bool Setting::operator==(const Setting& other) const { if (other.type_ != type_) return false; return EqualsImpl(other); } bool Setting::Write(const string& name, DataWriter* writer) const { if (!writer->WriteInt8(type_)) return false; if (!writer->WriteZeros(1)) return false; if (!writer->WriteInt16(name.size())) return false; if (!writer->WriteBytes(name.data(), name.size())) return false; if (!writer->WriteZeros(GetPadding(name.size(), 4))) return false; if (!writer->WriteInt32(serial_)) return false; return WriteBody(writer); } void Setting::UpdateSerial(const Setting* prev, uint32_t serial) { if (prev && operator==(*prev)) serial_ = prev->serial_; else serial_ = serial; } bool IntegerSetting::WriteBody(DataWriter* writer) const { return writer->WriteInt32(value_); } bool IntegerSetting::EqualsImpl(const Setting& other) const { const IntegerSetting* cast_other = dynamic_cast(&other); if (!cast_other) return false; return (cast_other->value_ == value_); } bool StringSetting::WriteBody(DataWriter* writer) const { if (!writer->WriteInt32(value_.size())) return false; if (!writer->WriteBytes(value_.data(), value_.size())) return false; if (!writer->WriteZeros(GetPadding(value_.size(), 4))) return false; return true; } bool StringSetting::EqualsImpl(const Setting& other) const { const StringSetting* cast_other = dynamic_cast(&other); if (!cast_other) return false; return (cast_other->value_ == value_); } bool ColorSetting::WriteBody(DataWriter* writer) const { // Note that XSETTINGS asks for RBG-order, not RGB. if (!writer->WriteInt16(red_)) return false; if (!writer->WriteInt16(blue_)) return false; if (!writer->WriteInt16(green_)) return false; if (!writer->WriteInt16(alpha_)) return false; return true; } bool ColorSetting::EqualsImpl(const Setting& other) const { const ColorSetting* cast_other = dynamic_cast(&other); if (!cast_other) return false; return (cast_other->red_ == red_ && cast_other->green_ == green_ && cast_other->blue_ == blue_ && cast_other->alpha_ == alpha_); } SettingsMap::~SettingsMap() { for (Map::iterator it = map_.begin(); it != map_.end(); ++it) { delete it->second; } map_.clear(); } const Setting* SettingsMap::GetSetting(const std::string& name) const { Map::const_iterator it = map_.find(name); if (it == map_.end()) return NULL; return it->second; } } // namespace xsettingsd xsettingsd-1.0.2/setting.h000066400000000000000000000067161377611665500155670ustar00rootroot00000000000000// Copyright 2009 Daniel Erat // All rights reserved. #ifndef __XSETTINGSD_SETTING_H__ #define __XSETTINGSD_SETTING_H__ #include #include #include #ifdef __TESTING #include #endif #include "common.h" namespace xsettingsd { class DataWriter; // Base class for settings. class Setting { public: enum Type { TYPE_INTEGER = 0, TYPE_STRING = 1, TYPE_COLOR = 2, }; explicit Setting(Type type) : type_(type), serial_(0) { } virtual ~Setting() {} uint32_t serial() const { return serial_; } bool operator==(const Setting& other) const; // Write this setting (using the passed-in setting name) in the format // described in the XSETTINGS spec. bool Write(const std::string& name, DataWriter* writer) const; // Update this setting's serial number based on the previous version of // the setting. (If the setting changed, we use 'serial'; otherwise we // use the same serial as 'prev'.) void UpdateSerial(const Setting* prev, uint32_t serial); private: // Write type-specific data. virtual bool WriteBody(DataWriter* writer) const = 0; // Cast 'other' to this setting's type and compare it. virtual bool EqualsImpl(const Setting& other) const = 0; Type type_; // Incremented when the setting's value changes. uint32_t serial_; DISALLOW_COPY_AND_ASSIGN(Setting); }; class IntegerSetting : public Setting { public: explicit IntegerSetting(int32_t value) : Setting(TYPE_INTEGER), value_(value) { } int32_t value() const { return value_; } private: #ifdef __TESTING friend class IntegerSettingTest; FRIEND_TEST(IntegerSettingTest, WriteBody); #endif bool WriteBody(DataWriter* writer) const; bool EqualsImpl(const Setting& other) const; int32_t value_; DISALLOW_COPY_AND_ASSIGN(IntegerSetting); }; class StringSetting : public Setting { public: explicit StringSetting(const std::string& value) : Setting(TYPE_STRING), value_(value) { } const std::string& value() const { return value_; } private: bool WriteBody(DataWriter* writer) const; bool EqualsImpl(const Setting& other) const; std::string value_; DISALLOW_COPY_AND_ASSIGN(StringSetting); }; class ColorSetting : public Setting { public: ColorSetting(uint16_t red, uint16_t green, uint16_t blue, uint16_t alpha) : Setting(TYPE_COLOR), red_(red), green_(green), blue_(blue), alpha_(alpha) { } uint16_t red() const { return red_; } uint16_t green() const { return green_; } uint16_t blue() const { return blue_; } uint16_t alpha() const { return alpha_; } private: bool WriteBody(DataWriter* writer) const; bool EqualsImpl(const Setting& other) const; uint16_t red_; uint16_t green_; uint16_t blue_; uint16_t alpha_; DISALLOW_COPY_AND_ASSIGN(ColorSetting); }; // A simple wrapper around a string-to-Setting map. // Handles deleting the Setting objects in its d'tor. class SettingsMap { public: SettingsMap() {} ~SettingsMap(); typedef std::map Map; const Map& map() const { return map_; } Map* mutable_map() { return &map_; } void swap(SettingsMap* other) { map_.swap(other->map_); } // Get a pointer to a setting or NULL if it doesn't exist. const Setting* GetSetting(const std::string& name) const; private: Map map_; DISALLOW_COPY_AND_ASSIGN(SettingsMap); }; } // namespace xsettingsd #endif xsettingsd-1.0.2/setting_test.cc000066400000000000000000000106221377611665500167530ustar00rootroot00000000000000// Copyright 2009 Daniel Erat // All rights reserved. #include #include #include #include "data_writer.h" #include "setting.h" using std::string; namespace xsettingsd { testing::AssertionResult BytesAreEqual( const char* expected_expr, const char* actual_expr, const char* size_expr, const char* expected, const char* actual, size_t size) { for (size_t i = 0; i < size; ++i) { if (expected[i] != actual[i]) { testing::Message msg; string expected_str, actual_str, hl_str; bool first = true; for (size_t j = 0; j < size; ++j) { expected_str += StringPrintf(" %02x", static_cast(expected[j])); actual_str += StringPrintf(" %02x", static_cast(actual[j])); hl_str += (expected[j] == actual[j]) ? " " : " ^^"; if ((j % 16) == 15 || j == size - 1) { msg << (first ? "Expected:" : "\n ") << expected_str << "\n" << (first ? " Actual:" : " ") << actual_str << "\n" << " " << hl_str; expected_str = actual_str = hl_str = ""; first = false; } } return testing::AssertionFailure(msg); } } return testing::AssertionSuccess(); } TEST(IntegerSettingTest, Write) { static const int kBufSize = 1024; char buffer[kBufSize]; DataWriter writer(buffer, kBufSize); IntegerSetting setting(5); setting.UpdateSerial(NULL, 3); ASSERT_TRUE(setting.Write("name", &writer)); // TODO: Won't work on big-endian systems. const char expected[] = { 0x0, // type 0x0, // unused 0x4, 0x0, // name-len 0x6e, 0x61, 0x6d, 0x65, // "name" (multiple of 4, so no padding) 0x3, 0x0, 0x0, 0x0, // serial 0x5, 0x0, 0x0, 0x0, // value }; ASSERT_EQ(sizeof(expected), writer.bytes_written()); EXPECT_PRED_FORMAT3(BytesAreEqual, expected, buffer, sizeof(expected)); } TEST(StringSettingTest, Write) { static const int kBufSize = 1024; char buffer[kBufSize]; DataWriter writer(buffer, kBufSize); StringSetting setting("testing"); setting.UpdateSerial(NULL, 5); ASSERT_TRUE(setting.Write("setting", &writer)); // TODO: Won't work on big-endian systems. const char expected[] = { 0x1, // type 0x0, // unused 0x7, 0x0, // name-len 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, // "setting" (name) 0x0, // padding 0x5, 0x0, 0x0, 0x0, // serial 0x7, 0x0, 0x0, 0x0, // value-len 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, // "testing" (value) 0x0, // padding }; ASSERT_EQ(sizeof(expected), writer.bytes_written()); EXPECT_PRED_FORMAT3(BytesAreEqual, expected, buffer, sizeof(expected)); } TEST(ColorSettingTest, Write) { static const int kBufSize = 1024; char buffer[kBufSize]; DataWriter writer(buffer, kBufSize); ColorSetting setting(32768, 65535, 0, 255); setting.UpdateSerial(NULL, 2); ASSERT_TRUE(setting.Write("name", &writer)); // TODO: Won't work on big-endian systems. const char expected[] = { 0x2, // type 0x0, // unused 0x4, 0x0, // name-len 0x6e, 0x61, 0x6d, 0x65, // "name" (multiple of 4, so no padding) 0x2, 0x0, 0x0, 0x0, // serial 0x0, 0x80, // red 0x0, 0x0, // blue (yes, the order is strange) 0xff, 0xff, // green 0xff, 0x0, // alpha }; ASSERT_EQ(sizeof(expected), writer.bytes_written()); EXPECT_PRED_FORMAT3(BytesAreEqual, expected, buffer, sizeof(expected)); } TEST(SettingTest, Serials) { // Create a setting and give it a serial of 3. IntegerSetting setting(4); setting.UpdateSerial(NULL, 3); EXPECT_EQ(3, setting.serial()); // Now create a new setting with a different value. // It should get a new serial number. IntegerSetting setting2(5); setting2.UpdateSerial(&setting, 4); EXPECT_EQ(4, setting2.serial()); // Create a new setting with the same value. // The serial should stay the same as before. IntegerSetting setting3(5); setting3.UpdateSerial(&setting2, 5); EXPECT_EQ(4, setting3.serial()); } } // namespace xsettingsd int main(int argc, char** argv) { testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } xsettingsd-1.0.2/settings_manager.cc000066400000000000000000000254131377611665500175750ustar00rootroot00000000000000// Copyright 2009 Daniel Erat // All rights reserved. #include "settings_manager.h" #include #include #include #include #include #include #include #include #include #include #include "config_parser.h" #include "data_writer.h" #include "setting.h" using std::make_pair; using std::map; using std::max; using std::string; using std::vector; namespace xsettingsd { // Arbitrarily big number. static const int kMaxPropertySize = (2 << 15); SettingsManager::SettingsManager(const string& config_filename) : config_filename_(config_filename), serial_(0), display_(NULL), prop_atom_(None) { } SettingsManager::~SettingsManager() { if (display_) { if (!windows_.empty()) DestroyWindows(); XCloseDisplay(display_); display_ = NULL; } } bool SettingsManager::LoadConfig() { ConfigParser parser(new ConfigParser::FileCharStream(config_filename_)); SettingsMap new_settings; if (!parser.Parse(&new_settings, &settings_, serial_ + 1)) { fprintf(stderr, "%s: Unable to parse %s: %s\n", kProgName, config_filename_.c_str(), parser.FormatError().c_str()); return false; } serial_++; fprintf(stderr, "%s: Loaded %zu setting%s from %s\n", kProgName, new_settings.map().size(), (new_settings.map().size() == 1) ? "" : "s", config_filename_.c_str()); settings_.swap(&new_settings); return true; } bool SettingsManager::InitX11(int screen, bool replace_existing_manager) { assert(!display_); display_ = XOpenDisplay(NULL); if (!display_) { fprintf(stderr, "%s: Unable to open connection to X server\n", kProgName); return false; } prop_atom_ = XInternAtom(display_, "_XSETTINGS_SETTINGS", False); char data[kMaxPropertySize]; DataWriter writer(data, kMaxPropertySize); if (!WriteProperty(&writer)) return false; int min_screen = 0; int max_screen = ScreenCount(display_) - 1; if (screen >= 0) min_screen = max_screen = screen; for (screen = min_screen; screen <= max_screen; ++screen) { Window win = None; Time timestamp = 0; if (!CreateWindow(screen, &win, ×tamp)) { fprintf(stderr, "%s: Unable to create window on screen %d\n", kProgName, screen); return false; } fprintf(stderr, "%s: Created window 0x%x on screen %d with timestamp %lu\n", kProgName, static_cast(win), screen, timestamp); SetPropertyOnWindow(win, data, writer.bytes_written()); if (!ManageScreen(screen, win, timestamp, replace_existing_manager)) return false; windows_.push_back(win); } return true; } void SettingsManager::RunEventLoop() { int x11_fd = XConnectionNumber(display_); // TODO: Need to also use XAddConnectionWatch()? while (true) { // Rather than blocking in XNextEvent(), we just read all the available // events here. We block in select() instead, so that we'll get EINTR // if a SIGHUP came in to ask us to reload the config. while (XPending(display_)) { XEvent event; XNextEvent(display_, &event); switch (event.type) { case MappingNotify: // Doesn't really mean anything to us, but might as well handle it. XRefreshKeyboardMapping(&(event.xmapping)); break; case SelectionClear: { // If someone else took the selection, that's our sign to leave. fprintf(stderr, "%s: 0x%x took a selection from us; exiting\n", kProgName, static_cast(event.xselectionclear.window)); DestroyWindows(); return; } default: fprintf(stderr, "%s: Ignoring event of type %d\n", kProgName, event.type); } } // TODO: There's a small race condition here, in that SIGHUP can come // in while we're outside of the select() call, but it's probably not // worth trying to work around. fd_set fds; FD_ZERO(&fds); FD_SET(x11_fd, &fds); if (select(x11_fd + 1, &fds, NULL, NULL, NULL) == -1) { if (errno != EINTR) { fprintf(stderr, "%s: select() failed: %s\n", kProgName, strerror(errno)); return; } fprintf(stderr, "%s: Reloading configuration\n", kProgName); if (!LoadConfig()) continue; char data[kMaxPropertySize]; DataWriter writer(data, kMaxPropertySize); if (!WriteProperty(&writer)) continue; for (vector::const_iterator it = windows_.begin(); it != windows_.end(); ++it) { SetPropertyOnWindow(*it, data, writer.bytes_written()); } } } } void SettingsManager::DestroyWindows() { assert(display_); for (vector::iterator it = windows_.begin(); it != windows_.end(); ++it) { XDestroyWindow(display_, *it); } windows_.clear(); } bool SettingsManager::CreateWindow(int screen, Window* win_out, Time* timestamp_out) { assert(win_out); assert(timestamp_out); if (screen < 0 || screen >= ScreenCount(display_)) return false; XSetWindowAttributes attr; attr.override_redirect = True; Window win = XCreateWindow(display_, RootWindow(display_, screen), // parent -1, -1, // x, y 1, 1, // width, height 0, // border_width CopyFromParent, // depth InputOutput, // class CopyFromParent, // visual CWOverrideRedirect, // attr_mask &attr); if (win == None) return false; *win_out = win; // This sets a few properties for us, including WM_CLIENT_MACHINE. XSetWMProperties(display_, win, NULL, // window_name NULL, // icon_name NULL, // argv 0, // argc NULL, // normal_hints NULL, // wm_hints NULL); // class_hints XStoreName(display_, win, kProgName); XChangeProperty(display_, win, XInternAtom(display_, "_NET_WM_NAME", False), // property XInternAtom(display_, "UTF8_STRING", False), // type 8, // format (bits per element) PropModeReplace, reinterpret_cast(kProgName), strlen(kProgName)); // Grab a timestamp from our final property change; we'll need it later // when announcing that we've taken the manager selection. pid_t pid = getpid(); XSelectInput(display_, win, PropertyChangeMask); XChangeProperty(display_, win, XInternAtom(display_, "_NET_WM_PID", False), // property XA_CARDINAL, // type 32, // format (bits per element) PropModeReplace, reinterpret_cast(&pid), // value 1); // num elements XSelectInput(display_, win, NoEventMask); XEvent event; while (true) { XWindowEvent(display_, win, PropertyChangeMask, &event); if (event.type == PropertyNotify) { *timestamp_out = event.xproperty.time; break; } } return true; } bool SettingsManager::WriteProperty(DataWriter* writer) { assert(writer); int byte_order = IsLittleEndian() ? LSBFirst : MSBFirst; if (!writer->WriteInt8(byte_order)) return false; if (!writer->WriteZeros(3)) return false; if (!writer->WriteInt32(serial_)) return false; if (!writer->WriteInt32(settings_.map().size())) return false; for (SettingsMap::Map::const_iterator it = settings_.map().begin(); it != settings_.map().end(); ++it) { if (!it->second->Write(it->first, writer)) return false; } return true; } void SettingsManager::SetPropertyOnWindow( Window win, const char* data, size_t size) { XChangeProperty(display_, win, prop_atom_, // property prop_atom_, // type 8, // format (bits per element) PropModeReplace, reinterpret_cast(data), size); } bool SettingsManager::ManageScreen(int screen, Window win, Time timestamp, bool replace_existing_manager) { assert(display_); assert(win != None); assert(screen < ScreenCount(display_)); Window root = RootWindow(display_, screen); string sel_atom_name = StringPrintf("_XSETTINGS_S%d", screen); Atom sel_atom = XInternAtom(display_, sel_atom_name.c_str(), False); XGrabServer(display_); Window prev_win = XGetSelectionOwner(display_, sel_atom); fprintf(stderr, "%s: Selection %s is owned by 0x%x\n", kProgName, sel_atom_name.c_str(), static_cast(prev_win)); if (prev_win != None && !replace_existing_manager) { fprintf(stderr, "%s: Someone else already owns the %s selection " "and we weren't asked to replace them\n", kProgName, sel_atom_name.c_str()); XUngrabServer(display_); return false; } if (prev_win) XSelectInput(display_, prev_win, StructureNotifyMask); XSetSelectionOwner(display_, sel_atom, win, CurrentTime); fprintf(stderr, "%s: Took ownership of selection %s\n", kProgName, sel_atom_name.c_str()); XUngrabServer(display_); if (prev_win) { // Wait for the previous owner to go away. XEvent event; while (true) { XWindowEvent(display_, prev_win, StructureNotifyMask, &event); if (event.type == DestroyNotify) break; } } // Make sure that no one else took the selection while we were waiting. if (XGetSelectionOwner(display_, sel_atom) != win) { fprintf(stderr, "%s: Someone else took ownership of the %s selection\n", kProgName, sel_atom_name.c_str()); return false; } XEvent ev; ev.xclient.type = ClientMessage; ev.xclient.window = root; ev.xclient.message_type = XInternAtom(display_, "MANAGER", False); ev.xclient.format = 32; ev.xclient.data.l[0] = timestamp; ev.xclient.data.l[1] = sel_atom; ev.xclient.data.l[2] = win; ev.xclient.data.l[3] = 0; XSendEvent(display_, root, False, // propagate StructureNotifyMask, // event_mask &ev); return true; } } // namespace xsettingsd xsettingsd-1.0.2/settings_manager.h000066400000000000000000000045571377611665500174450ustar00rootroot00000000000000// Copyright 2009 Daniel Erat // All rights reserved. #ifndef __XSETTINGSD_SETTINGS_MANAGER_H__ #define __XSETTINGSD_SETTINGS_MANAGER_H__ #include #include #include #include #include "common.h" #include "setting.h" namespace xsettingsd { class DataWriter; // SettingsManager is the central class responsible for loading and parsing // configs (via ConfigParser), storing them (in the form of Setting // objects), and setting them as properties on X11 windows. class SettingsManager { public: SettingsManager(const std::string& config_filename); ~SettingsManager(); // Load settings from 'config_filename_', updating 'settings_' and // 'serial_' if successful. If the load was unsuccessful, false is // returned and an error is printed to stderr. bool LoadConfig(); // Connect to the X server, create windows, updates their properties, and // take the selections. A negative screen value will attempt to take the // manager selection on all screens. Returns false if someone else // already has a selection unless 'replace_existing_manager' is set. bool InitX11(int screen, bool replace_existing_manager); // Wait for events from the X server, destroying our windows and exiting // if we see someone else take a selection. void RunEventLoop(); private: // Destroy all windows in 'windows_'. void DestroyWindows(); // Create and initialize a window. bool CreateWindow(int screen, Window* win_out, Time* timestamp_out); // Write the currently-loaded property to the passed-in buffer. bool WriteProperty(DataWriter* writer); // Update the settings property on the passed-in window. void SetPropertyOnWindow(Window win, const char* data, size_t size); // Manage XSETTINGS for a particular screen. bool ManageScreen( int screen, Window win, Time timestamp, bool replace_existing_manager); // File from which we load settings. std::string config_filename_; // Currently-loaded settings. SettingsMap settings_; // Current serial number. uint32_t serial_; // Connection to the X server. Display* display_; // Atom representing "_XSETTINGS_SETTINGS". Atom prop_atom_; // Windows that we've created to hold settings properties (one per // screen). std::vector windows_; DISALLOW_COPY_AND_ASSIGN(SettingsManager); }; } // namespace xsettingsd #endif xsettingsd-1.0.2/xsettingsd.1000066400000000000000000000030521377611665500162050ustar00rootroot00000000000000.TH XSETTINGSD 1 .SH NAME xsettingsd \- provides settings to X11 applications .SH SYNOPSIS .B xsettingsd .RI [ options ] .SH DESCRIPTION xsettingsd is a daemon that implements the XSETTINGS specification. The typical invocation is from a user's \fB~/.xsession\fR file. .PP It is intended to be small, fast, and minimally dependent on other libraries. It can serve as an alternative to gnome-settings-daemon for users who are not using the GNOME desktop environment but who still run GTK+ applications and want to configure things such as themes, font antialiasing/hinting, and UI sound effects. .PP Additional documentation is available at the project's website: \fIhttps://github.com/derat/xsettingsd\fR .SH OPTIONS .TP \fB\-c\fR, \fB\-\-config\fR=\fIFILE\fR Load settings from \fIFILE\fR (default is \fB~/.xsettingsd\fR). .TP \fB\-h\fR, \fB\-\-help\fR Display a help message and exit. .TP \fB\-s\fR, \fB\-\-screen\fR=\fISCREEN\fR Use the X screen numbered \fISCREEN\fR (default of -1 means all screens). .SH BUGS \fIhttps://github.com/derat/xsettingsd/issues\fR .SH EXAMPLE Running \fIdump_xsettings\fR(1) while another XSETTINGS daemon, such as \fBgnome-settings-daemon\fR, is already running will print your current settings in \fBxsettingsd\fR's configuration file format. .PP Here is a short example \fB~/.xsettingsd\fR file: .PP .nf Net/ThemeName "Human" Xft/Antialias 1 Xft/DPI 100352 Xft/HintStyle "hintfull" Xft/Hinting 1 Xft/RGBA "none" Xft/lcdfilter "none" .fi .SH SEE ALSO \fIdump_xsettings\fR\|(1) .SH AUTHOR xsettingsd was written by Daniel Erat . xsettingsd-1.0.2/xsettingsd.cc000066400000000000000000000051051377611665500164330ustar00rootroot00000000000000// Copyright 2009 Daniel Erat // All rights reserved. #include #include #include #include #include #include #include #include #include "common.h" #include "config_parser.h" #include "settings_manager.h" using std::string; using std::vector; namespace { void HandleSignal(int signum) { } // Returns the first path in |paths| that is readable, or an empty string if // none of the paths can be read. string GetFirstReadablePath(const vector& paths) { for (size_t i = 0; i < paths.size(); ++i) { if (access(paths[i].c_str(), R_OK) == 0) { return paths[i]; } } return string(); } } // namespace int main(int argc, char** argv) { static const char* kUsage = "Usage: xsettingsd [OPTION] ...\n" "\n" "Daemon implementing the XSETTINGS spec to control settings for X11\n" "applications.\n" "\n" "Options: -c, --config=FILE config file (default is ~/.xsettingsd)\n" " -h, --help print this help message\n" " -s, --screen=SCREEN screen to use (default is all)\n"; int screen = -1; string config_file; struct option options[] = { { "config", 1, NULL, 'c', }, { "help", 0, NULL, 'h', }, { "screen", 1, NULL, 's', }, { NULL, 0, NULL, 0 }, }; opterr = 0; while (true) { int ch = getopt_long(argc, argv, "c:hs:", options, NULL); if (ch == -1) { break; } else if (ch == 'c') { config_file = optarg; } else if (ch == 'h' || ch == '?') { fprintf(stderr, "%s", kUsage); return 1; } else if (ch == 's') { char* endptr = NULL; screen = strtol(optarg, &endptr, 10); if (optarg[0] == '\0' || endptr[0] != '\0' || screen < 0) { fprintf(stderr, "Invalid screen \"%s\"\n", optarg); return 1; } } } // Check default config file locations if one wasn't supplied via a flag. if (config_file.empty()) { const vector paths = xsettingsd::GetDefaultConfigFilePaths(); config_file = GetFirstReadablePath(paths); if (config_file.empty()) { fprintf(stderr, "%s: Couldn't find config file. Tried the following:\n", xsettingsd::kProgName); for (size_t i = 0; i < paths.size(); ++i) fprintf(stderr, " %s\n", paths[i].c_str()); return 1; } } xsettingsd::SettingsManager manager(config_file); if (!manager.LoadConfig()) return 1; if (!manager.InitX11(screen, true)) return 1; signal(SIGHUP, HandleSignal); manager.RunEventLoop(); return 0; } xsettingsd-1.0.2/xsettingsd.service.in000066400000000000000000000002351377611665500201120ustar00rootroot00000000000000[Unit] Description=XSETTINGS-protocol daemon PartOf=graphical-session.target [Service] ExecStart=@CMAKE_INSTALL_FULL_BINDIR@/xsettingsd Slice=session.slice