From 099672e43c992f16921344fa6db0add23cc4788a Mon Sep 17 00:00:00 2001 From: dece Date: Sun, 27 Sep 2020 18:13:03 +0200 Subject: [PATCH] Handle array properties --- xfconf.py | 65 ++++++++++++++++++++++++++++++++++++++++++++++--------- xion.py | 28 ++++++------------------ 2 files changed, 62 insertions(+), 31 deletions(-) diff --git a/xfconf.py b/xfconf.py index 11b9231..84a8319 100644 --- a/xfconf.py +++ b/xfconf.py @@ -1,5 +1,6 @@ import re import shutil +import string import subprocess from dataclasses import dataclass @@ -7,6 +8,15 @@ from dataclasses import dataclass class Xfconf: """Interface around Xfconf, using xion-query behind the scene.""" + # GTypes to xfconf-query types along with a value string parser. + TYPE_MAP = { + "gboolean": "bool", + "gint": "int", + "guint": "uint", + "gdouble": "double", + "gchararray": "string", + } + def __init__(self, xq=None): self._xq = xq or self.find_xq() @@ -21,6 +31,7 @@ class Xfconf: if not print_failures: return None print(f"xion-query command failed with code {exc.returncode}.") + print("command:", command) if exc.stdout: print("stdout:", exc.stdout.decode().strip()) if exc.stderr: @@ -58,28 +69,44 @@ class Xfconf: return XfconfProperty.parse(output) def set_property(self, channel, prop, prop_type, value): - """Create or update this property.""" + """Create or update this property, return True on success.""" if not self.does_property_exist(channel, prop): - self.create_property(channel, prop, prop_type, value) + return self.create_property(channel, prop, prop_type, value) else: - self.update_property(channel, prop, value) + return self.update_property(channel, prop, value) def create_property(self, channel, prop, prop_type, value): """Create a new property with those params, return True on success.""" - if " " in value: - value = f'"{value}"' + prop_type = Xfconf.convert_type(prop_type) + if not prop_type: + return False + value = Xfconf.sanitize_str(value) output = self.xq(["-c", channel, "-p", prop, "-n", "-t", prop_type, "-s", value]) - if output is None: - return False + return output == "" def update_property(self, channel, prop, value): """Update an existing property, return True on success.""" - if " " in value: - value = f'"{value}"' + value = Xfconf.sanitize_str(value) output = self.xq(["-c", channel, "-p", prop, "-s", value]) return output == "" + def set_property_array(self, channel, prop, values): + """Set a property array, return True on success. + + Due to limitations in the way xfconf-query work, whether the array + exists or not it is entirely replaced by values. + """ + command = ["-c", channel, "-p", prop, "-n", "-a"] + for value in values: + subtype = Xfconf.convert_type(value["type"]) + if not subtype: + return False + subvalue = Xfconf.sanitize_str(value["value"]) + command += ["-t", subtype, "-s", subvalue] + output = self.xq(command) + return output == "" + def reset_root(self, channel, root): """Reset all channel properties under root, return True on success.""" output = self.xqs(f"-c {channel} -p {root} -r -R") @@ -87,11 +114,29 @@ class Xfconf: @staticmethod def find_xq(): + """Find xion-query in the path and return it, or None on failure.""" xq = shutil.which("xion-query") if not xq: - exit("Could not find xion-query in path.") + print("Could not find xion-query in path.") + return None return xq + @staticmethod + def convert_type(gtype): + """Get an xfconf-query type from a gtype.""" + xq_type = Xfconf.TYPE_MAP.get(gtype) + if xq_type is None: + print(f"Unknown gtype {gtype}.") + return xq_type + + @staticmethod + def sanitize_str(value): + """Wrap value with doublequotes if it contains whitespaces.""" + for char in string.whitespace: + if char in value: + return f'"{value}"' + return value + XION_PROP_RE = re.compile(r"t:(\S+) (.+)") diff --git a/xion.py b/xion.py index 22d2e16..179e6df 100644 --- a/xion.py +++ b/xion.py @@ -57,15 +57,6 @@ def main(): class Xion: """Manipulate Xfconf settings trees.""" - # GTypes to xfconf-query types along with a value string parser. - TYPE_MAP = { - "gboolean": "bool", - "gint": "int", - "guint": "uint", - "gdouble": "double", - "gchararray": "string", - } - def __init__(self, xq=None): self.xfconf = Xfconf(xq=xq) @@ -95,6 +86,10 @@ class Xion: tree[prop_name] = leaf return tree + @staticmethod + def _build_prop_leaf(prop): + return {"type": prop.gtype, "value": str(prop.value)} + def export_tree(self, channel, root, tree, output_path): """Export a property tree as a sorted JSON file.""" tree["channel"] = channel @@ -139,20 +134,11 @@ class Xion: def apply_property(self, channel, name, content): """Update one property in Xfconf, return True on success.""" - # if isinstance(content, list): - # for subprop in content: - # if not self.apply_property(channel, + if isinstance(content, list): + return self.xfconf.set_property_array(channel, name, content) prop_type = content["type"] - if not prop_type in Xion.TYPE_MAP: - print(f"Unknown property type {prop_type}!") - return False - xq_type = Xion.TYPE_MAP[prop_type] value = content["value"] - self.xfconf.set_property(channel, name, xq_type, value) - - @staticmethod - def _build_prop_leaf(prop): - return {"type": prop.gtype, "value": str(prop.value)} + return self.xfconf.set_property(channel, name, prop_type, value) if __name__ == "__main__":