Mercurial > minori
annotate src/sys/x11/settings.cc @ 383:27c462bc7815
make the "Now Playing" page actually work
| author | Paper <paper@tflc.us> |
|---|---|
| date | Thu, 06 Nov 2025 07:10:58 -0500 |
| parents | 5eaafed6c10b |
| children |
| rev | line source |
|---|---|
| 351 | 1 #include "sys/x11/settings.h" |
|
364
99c961c91809
core: refactor out byte stream into its own file
Paper <paper@paper.us.eu.org>
parents:
354
diff
changeset
|
2 #include "core/byte_stream.h" |
| 351 | 3 |
| 379 | 4 #include <array> |
|
364
99c961c91809
core: refactor out byte stream into its own file
Paper <paper@paper.us.eu.org>
parents:
354
diff
changeset
|
5 #include <cassert> |
| 379 | 6 #include <climits> |
| 7 #include <cstdint> | |
| 351 | 8 #include <cstring> |
| 379 | 9 #include <map> |
| 10 #include <memory> | |
| 11 #include <optional> | |
| 351 | 12 #include <string_view> |
| 13 | |
| 14 #include <xcb/xcb.h> | |
| 15 | |
| 16 #include "fmt/core.h" | |
| 17 | |
| 18 namespace x11 { | |
| 19 | |
| 379 | 20 bool SettingsItem::VerifyType() |
| 21 { | |
| 351 | 22 switch (type) { |
| 23 case SettingsItem::TypeInt: | |
| 24 case SettingsItem::TypeStr: | |
| 379 | 25 case SettingsItem::TypeRgba: return true; |
| 26 default: return false; | |
| 351 | 27 } |
| 28 } | |
| 29 | |
| 30 /* -------------------------------------------------------------------------- */ | |
| 31 /* xsettings parser */ | |
| 32 | |
| 379 | 33 static constexpr std::size_t GetPadding(std::size_t length, std::size_t increment) |
| 34 { | |
| 351 | 35 /* ripped from xsettingsd */ |
| 36 return (increment - (length % increment)) % increment; | |
| 37 } | |
| 38 | |
| 39 class Parser { | |
| 40 public: | |
| 41 Parser(std::uint8_t *bytes, std::size_t size); | |
| 42 | |
|
364
99c961c91809
core: refactor out byte stream into its own file
Paper <paper@paper.us.eu.org>
parents:
354
diff
changeset
|
43 bool ParseHeader(void); |
| 351 | 44 std::optional<SettingsItem> ParseNextItem(void); |
| 45 | |
| 46 std::uint32_t GetTotalItems(void); | |
| 47 | |
| 48 private: | |
|
364
99c961c91809
core: refactor out byte stream into its own file
Paper <paper@paper.us.eu.org>
parents:
354
diff
changeset
|
49 enum ByteOrder { |
| 351 | 50 LSBFirst = 0, |
| 51 MSBFirst = 1, | |
| 52 }; | |
| 53 | |
|
364
99c961c91809
core: refactor out byte stream into its own file
Paper <paper@paper.us.eu.org>
parents:
354
diff
changeset
|
54 ByteStream stream; |
| 351 | 55 |
| 56 /* parsed in the constructor */ | |
| 57 std::uint32_t serial_ = 0; | |
| 58 std::uint32_t total_items_ = 0; | |
| 59 }; | |
| 60 | |
| 379 | 61 std::uint32_t Parser::GetTotalItems(void) |
| 62 { | |
| 351 | 63 return total_items_; |
| 64 } | |
| 65 | |
| 379 | 66 Parser::Parser(std::uint8_t *bytes, std::size_t size) : stream(bytes, size) |
| 67 { | |
| 351 | 68 } |
| 69 | |
| 379 | 70 bool Parser::ParseHeader(void) |
| 71 { | |
|
364
99c961c91809
core: refactor out byte stream into its own file
Paper <paper@paper.us.eu.org>
parents:
354
diff
changeset
|
72 std::uint8_t byte_order; |
|
99c961c91809
core: refactor out byte stream into its own file
Paper <paper@paper.us.eu.org>
parents:
354
diff
changeset
|
73 if (!stream.ReadBinary<std::uint8_t>(byte_order)) |
| 351 | 74 return false; |
| 75 | |
|
364
99c961c91809
core: refactor out byte stream into its own file
Paper <paper@paper.us.eu.org>
parents:
354
diff
changeset
|
76 switch (byte_order) { |
| 379 | 77 case MSBFirst: stream.SetEndianness(ByteStream::ByteOrder::Big); break; |
| 78 case LSBFirst: stream.SetEndianness(ByteStream::ByteOrder::Little); break; | |
| 79 default: return false; /* errr */ | |
|
364
99c961c91809
core: refactor out byte stream into its own file
Paper <paper@paper.us.eu.org>
parents:
354
diff
changeset
|
80 } |
| 351 | 81 |
|
364
99c961c91809
core: refactor out byte stream into its own file
Paper <paper@paper.us.eu.org>
parents:
354
diff
changeset
|
82 stream.Advance(3); |
| 351 | 83 |
|
364
99c961c91809
core: refactor out byte stream into its own file
Paper <paper@paper.us.eu.org>
parents:
354
diff
changeset
|
84 if (!stream.ReadInt<std::uint32_t>(serial_)) |
|
99c961c91809
core: refactor out byte stream into its own file
Paper <paper@paper.us.eu.org>
parents:
354
diff
changeset
|
85 return false; |
| 351 | 86 |
|
364
99c961c91809
core: refactor out byte stream into its own file
Paper <paper@paper.us.eu.org>
parents:
354
diff
changeset
|
87 if (!stream.ReadInt<std::uint32_t>(total_items_)) |
|
99c961c91809
core: refactor out byte stream into its own file
Paper <paper@paper.us.eu.org>
parents:
354
diff
changeset
|
88 return false; |
| 351 | 89 |
|
364
99c961c91809
core: refactor out byte stream into its own file
Paper <paper@paper.us.eu.org>
parents:
354
diff
changeset
|
90 return true; |
| 351 | 91 } |
| 92 | |
| 379 | 93 std::optional<SettingsItem> Parser::ParseNextItem(void) |
| 94 { | |
| 351 | 95 SettingsItem item; |
| 96 | |
| 97 /* read one byte */ | |
|
364
99c961c91809
core: refactor out byte stream into its own file
Paper <paper@paper.us.eu.org>
parents:
354
diff
changeset
|
98 if (!stream.ReadInt<std::uint8_t>(item.type)) |
| 351 | 99 return std::nullopt; |
| 100 | |
| 101 if (!item.VerifyType()) | |
| 102 return std::nullopt; | |
| 103 | |
| 104 /* skip padding */ | |
|
364
99c961c91809
core: refactor out byte stream into its own file
Paper <paper@paper.us.eu.org>
parents:
354
diff
changeset
|
105 if (!stream.Advance(1)) |
| 351 | 106 return std::nullopt; |
| 107 | |
| 108 /* parse the name */ | |
| 109 std::uint16_t name_size; | |
|
364
99c961c91809
core: refactor out byte stream into its own file
Paper <paper@paper.us.eu.org>
parents:
354
diff
changeset
|
110 if (!stream.ReadInt<std::uint16_t>(name_size)) |
| 351 | 111 return std::nullopt; |
| 112 | |
|
364
99c961c91809
core: refactor out byte stream into its own file
Paper <paper@paper.us.eu.org>
parents:
354
diff
changeset
|
113 if (!stream.ReadString(item.name, name_size)) |
| 351 | 114 return std::nullopt; |
| 115 | |
| 116 /* padding */ | |
|
364
99c961c91809
core: refactor out byte stream into its own file
Paper <paper@paper.us.eu.org>
parents:
354
diff
changeset
|
117 if (!stream.Advance(GetPadding(name_size, 4))) |
| 351 | 118 return std::nullopt; |
| 119 | |
|
364
99c961c91809
core: refactor out byte stream into its own file
Paper <paper@paper.us.eu.org>
parents:
354
diff
changeset
|
120 if (!stream.ReadInt<std::uint32_t>(item.serial)) |
| 351 | 121 return std::nullopt; |
| 122 | |
| 123 switch (item.type) { | |
| 124 case SettingsItem::TypeInt: { | |
|
364
99c961c91809
core: refactor out byte stream into its own file
Paper <paper@paper.us.eu.org>
parents:
354
diff
changeset
|
125 if (!stream.ReadInt<std::int32_t>(item.data.integer)) |
| 351 | 126 return std::nullopt; |
| 127 | |
| 128 break; | |
| 129 } | |
| 130 case SettingsItem::TypeStr: { | |
| 131 std::uint32_t size; | |
|
364
99c961c91809
core: refactor out byte stream into its own file
Paper <paper@paper.us.eu.org>
parents:
354
diff
changeset
|
132 if (!stream.ReadInt<std::uint32_t>(size)) |
| 351 | 133 return std::nullopt; |
| 134 | |
|
364
99c961c91809
core: refactor out byte stream into its own file
Paper <paper@paper.us.eu.org>
parents:
354
diff
changeset
|
135 if (!stream.ReadString(item.data.string, size)) |
| 351 | 136 return std::nullopt; |
| 137 | |
|
364
99c961c91809
core: refactor out byte stream into its own file
Paper <paper@paper.us.eu.org>
parents:
354
diff
changeset
|
138 /* don't fail if advancing fails on this padding, |
|
99c961c91809
core: refactor out byte stream into its own file
Paper <paper@paper.us.eu.org>
parents:
354
diff
changeset
|
139 * because this causes parsing to fail for strings |
|
99c961c91809
core: refactor out byte stream into its own file
Paper <paper@paper.us.eu.org>
parents:
354
diff
changeset
|
140 * at the end of the data */ |
|
99c961c91809
core: refactor out byte stream into its own file
Paper <paper@paper.us.eu.org>
parents:
354
diff
changeset
|
141 stream.Advance(GetPadding(size, 4)); |
| 351 | 142 |
| 143 break; | |
| 144 } | |
| 145 case SettingsItem::TypeRgba: { | |
|
364
99c961c91809
core: refactor out byte stream into its own file
Paper <paper@paper.us.eu.org>
parents:
354
diff
changeset
|
146 /* it's actually RBGA, but whatever. */ |
| 379 | 147 if (!stream.ReadInt<std::uint16_t>(item.data.rgba.red) || |
| 148 !stream.ReadInt<std::uint16_t>(item.data.rgba.blue) || | |
| 149 !stream.ReadInt<std::uint16_t>(item.data.rgba.green) || | |
| 150 !stream.ReadInt<std::uint16_t>(item.data.rgba.alpha)) | |
| 351 | 151 return std::nullopt; |
| 152 | |
| 153 break; | |
| 154 } | |
| 155 default: | |
| 156 /* can't do anything now, can we? */ | |
| 157 return std::nullopt; | |
| 158 } | |
| 159 | |
| 160 return item; | |
| 161 } | |
| 162 | |
| 163 /* ------------------------------------------------------------------------- */ | |
| 164 /* real X11 code */ | |
| 165 | |
| 166 template<typename T> | |
| 167 struct MallocDestructor { | |
| 168 void operator()(T *t) const { std::free(t); }; | |
| 169 }; | |
| 170 | |
| 171 struct XcbConnectionDestructor { | |
| 172 void operator()(xcb_connection_t *conn) const { ::xcb_disconnect(conn); }; | |
| 173 }; | |
| 174 | |
| 175 template<typename T> | |
| 176 using MallocPtr = std::unique_ptr<T, MallocDestructor<T>>; | |
| 177 | |
| 178 using XcbConnectionPtr = std::unique_ptr<xcb_connection_t, XcbConnectionDestructor>; | |
| 179 | |
| 180 /* RAII is nice */ | |
| 181 struct XcbGrabber { | |
| 379 | 182 XcbGrabber(::xcb_connection_t *conn) |
| 183 { | |
| 184 ::xcb_grab_server(conn); | |
| 185 conn_ = conn; | |
| 186 } | |
| 351 | 187 ~XcbGrabber() { ::xcb_ungrab_server(conn_); } |
| 188 | |
| 189 private: | |
| 190 ::xcb_connection_t *conn_; | |
| 191 }; | |
| 192 | |
| 379 | 193 static ::xcb_window_t GetSelectionOwner(::xcb_connection_t *conn, ::xcb_atom_t selection) |
| 194 { | |
| 351 | 195 ::xcb_window_t owner = XCB_NONE; |
| 379 | 196 MallocPtr<::xcb_get_selection_owner_reply_t> reply( |
| 197 ::xcb_get_selection_owner_reply(conn, ::xcb_get_selection_owner(conn, selection), nullptr)); | |
| 198 | |
| 351 | 199 if (reply) |
| 200 owner = reply->owner; | |
| 379 | 201 |
| 351 | 202 return owner; |
| 203 } | |
| 204 | |
| 379 | 205 static bool GetRawSettingsData(std::vector<uint8_t> &bytes) |
| 206 { | |
| 351 | 207 int screen; |
| 208 | |
| 209 XcbConnectionPtr conn(::xcb_connect(nullptr, &screen)); | |
| 210 if (::xcb_connection_has_error(conn.get())) | |
| 211 return false; | |
| 212 | |
| 213 /* get our needed atoms, available as atoms[Atom] */ | |
| 214 enum Atom { | |
| 215 XSETTINGS_SCREEN, /* _XSETTINGS_S[N] */ | |
| 216 XSETTINGS_SETTINGS, /* _XSETTINGS_SETTINGS */ | |
| 217 }; | |
| 218 | |
| 219 std::map<Atom, ::xcb_atom_t> atoms; | |
| 220 { | |
| 221 std::map<Atom, std::string> names = { | |
| 379 | 222 {XSETTINGS_SCREEN, fmt::format("_XSETTINGS_S{}", screen)}, |
| 223 {XSETTINGS_SETTINGS, "_XSETTINGS_SETTINGS"}, | |
| 351 | 224 }; |
| 225 | |
| 226 std::map<Atom, ::xcb_intern_atom_cookie_t> atom_cookies; | |
| 379 | 227 for (const auto &name : names) |
| 351 | 228 atom_cookies[name.first] = ::xcb_intern_atom(conn.get(), false, name.second.size(), name.second.data()); |
| 229 | |
| 379 | 230 for (const auto &cookie : atom_cookies) { |
| 351 | 231 MallocPtr<::xcb_intern_atom_reply_t> reply(::xcb_intern_atom_reply(conn.get(), cookie.second, nullptr)); |
| 232 if (!reply || reply->atom == XCB_NONE) | |
| 233 return false; | |
| 234 | |
| 235 atoms[cookie.first] = reply->atom; | |
| 236 } | |
| 237 } | |
| 238 | |
| 239 MallocPtr<xcb_get_property_reply_t> reply; | |
| 240 { | |
| 241 /* grab the X server as *required* by xsettings docs */ | |
| 242 const XcbGrabber grabber(conn.get()); | |
| 243 | |
| 244 ::xcb_window_t win = GetSelectionOwner(conn.get(), atoms[XSETTINGS_SCREEN]); | |
| 245 if (win == XCB_NONE) | |
| 246 return false; | |
| 247 | |
| 379 | 248 reply.reset(::xcb_get_property_reply( |
| 249 conn.get(), ::xcb_get_property(conn.get(), 0, win, atoms[XSETTINGS_SETTINGS], XCB_ATOM_ANY, 0L, UINT_MAX), | |
| 250 nullptr)); | |
| 351 | 251 }; |
| 252 if (!reply) | |
| 253 return false; | |
| 254 | |
| 255 uint8_t *data = reinterpret_cast<uint8_t *>(xcb_get_property_value(reply.get())); | |
| 256 int size = xcb_get_property_value_length(reply.get()); | |
| 257 if (size < 0) | |
| 258 return false; | |
| 259 | |
| 260 bytes.assign(data, data + size); | |
| 261 | |
| 262 return true; | |
| 263 } | |
| 264 | |
| 265 /* ------------------------------------------------------------------------- */ | |
| 266 /* now for the actual all-important public API stringing all this together */ | |
| 267 | |
| 379 | 268 bool GetSettings(std::vector<SettingsItem> &settings) |
| 269 { | |
| 351 | 270 std::vector<std::uint8_t> xsettings_raw; |
| 271 if (!GetRawSettingsData(xsettings_raw)) | |
| 272 return false; | |
| 273 | |
| 274 Parser parser(xsettings_raw.data(), xsettings_raw.size()); | |
|
364
99c961c91809
core: refactor out byte stream into its own file
Paper <paper@paper.us.eu.org>
parents:
354
diff
changeset
|
275 if (!parser.ParseHeader()) |
|
99c961c91809
core: refactor out byte stream into its own file
Paper <paper@paper.us.eu.org>
parents:
354
diff
changeset
|
276 return false; |
|
99c961c91809
core: refactor out byte stream into its own file
Paper <paper@paper.us.eu.org>
parents:
354
diff
changeset
|
277 |
|
99c961c91809
core: refactor out byte stream into its own file
Paper <paper@paper.us.eu.org>
parents:
354
diff
changeset
|
278 std::uint32_t total = parser.GetTotalItems(); |
|
99c961c91809
core: refactor out byte stream into its own file
Paper <paper@paper.us.eu.org>
parents:
354
diff
changeset
|
279 |
|
99c961c91809
core: refactor out byte stream into its own file
Paper <paper@paper.us.eu.org>
parents:
354
diff
changeset
|
280 while (total--) { |
|
99c961c91809
core: refactor out byte stream into its own file
Paper <paper@paper.us.eu.org>
parents:
354
diff
changeset
|
281 std::optional<SettingsItem> opt_item = parser.ParseNextItem(); |
|
99c961c91809
core: refactor out byte stream into its own file
Paper <paper@paper.us.eu.org>
parents:
354
diff
changeset
|
282 if (!opt_item) |
|
99c961c91809
core: refactor out byte stream into its own file
Paper <paper@paper.us.eu.org>
parents:
354
diff
changeset
|
283 break; |
|
99c961c91809
core: refactor out byte stream into its own file
Paper <paper@paper.us.eu.org>
parents:
354
diff
changeset
|
284 |
|
99c961c91809
core: refactor out byte stream into its own file
Paper <paper@paper.us.eu.org>
parents:
354
diff
changeset
|
285 settings.push_back(opt_item.value()); |
|
99c961c91809
core: refactor out byte stream into its own file
Paper <paper@paper.us.eu.org>
parents:
354
diff
changeset
|
286 } |
| 351 | 287 |
| 288 return true; | |
| 289 } | |
| 290 | |
| 379 | 291 bool FindSetting(const std::string &name, SettingsItem &setting) |
| 292 { | |
| 351 | 293 std::vector<std::uint8_t> xsettings_raw; |
| 294 if (!GetRawSettingsData(xsettings_raw)) | |
| 295 return false; | |
| 296 | |
| 297 Parser parser(xsettings_raw.data(), xsettings_raw.size()); | |
|
364
99c961c91809
core: refactor out byte stream into its own file
Paper <paper@paper.us.eu.org>
parents:
354
diff
changeset
|
298 if (!parser.ParseHeader()) |
|
99c961c91809
core: refactor out byte stream into its own file
Paper <paper@paper.us.eu.org>
parents:
354
diff
changeset
|
299 return false; |
| 351 | 300 |
| 301 std::uint32_t total = parser.GetTotalItems(); | |
| 302 | |
|
364
99c961c91809
core: refactor out byte stream into its own file
Paper <paper@paper.us.eu.org>
parents:
354
diff
changeset
|
303 while (total--) { |
| 351 | 304 std::optional<SettingsItem> opt_item = parser.ParseNextItem(); |
| 305 if (!opt_item) | |
| 306 return false; | |
| 307 | |
| 379 | 308 SettingsItem &item = opt_item.value(); |
| 351 | 309 if (item.name == name) { |
| 310 setting = item; | |
| 311 return true; | |
| 312 } | |
| 313 } | |
| 314 | |
| 315 return false; | |
| 316 } | |
| 317 | |
| 318 } // namespace x11 |
