diff --git a/xion.py b/xion.py index 8375dc2..3c6511c 100644 --- a/xion.py +++ b/xion.py @@ -1,61 +1,74 @@ -from ctypes import byref, cdll, c_char_p, c_void_p, POINTER - - -class Xfconf: - """Xfconf library interface.""" - - def __init__(self, libxfconf="libxfconf-0.so.2", - libglib="libglib-2.0.so.0"): - self.lib = cdll.LoadLibrary(libxfconf) - self.glib = cdll.LoadLibrary(libglib) - self.set_foreign_functions() - - def set_foreign_functions(self): - self._ff_init = self.lib.xfconf_init - self._ff_shutdown = self.lib.xfconf_shutdown - self._ff_list_channels = self.lib.xfconf_list_channels - self._ff_list_channels.restype = POINTER(c_void_p) - self._ff_channel_get = self.lib.xfconf_channel_get - self._ff_channel_get.argtypes = (c_char_p,) - self._ff_channel_get.restype = c_void_p - self._ff_channel_get_properties = self.lib.xfconf_channel_get_properties - self._ff_channel_get_properties.argtypes = (c_void_p, c_char_p) - self._ff_channel_get_properties.restype = c_void_p - - def init(self): - err = c_void_p() - if not self._ff_init(byref(err)): - raise XfconfError("xfconf_init: error") - - def shutdown(self): - self._ff_shutdown() - - def list_channels(self): - channels = self._ff_list_channels() - i = 0 - while channels[i] is not None: - yield c_char_p(channels[i]).value.decode() - i += 1 - self.glib.g_strfreev(channels) - - def get_channel(self, name): - return self._ff_channel_get(name.encode()) - - def list_properties(self, channel, base=None): - table = self._ff_channel_get_properties(channel, None) - print(table) - self.glib.g_hash_table_destroy(table) - - - -class XfconfError(Exception): - pass - - -xfconf = Xfconf() -xfconf.init() -for channel_name in xfconf.list_channels(): - print(channel_name) -channel = xfconf.get_channel("xfce4-keyboard-shortcuts") -xfconf.list_properties(channel) -xfconf.shutdown() +import argparse +import json + +from xfconf import Xfconf + + +DEFAULT_FILE_PATH = "xion.json" + + +def main(): + argparser = argparse.ArgumentParser() + argparser.add_argument("--xq-path", type=str, + help="Optional path to xion-query") + argparser.add_argument("-e", "--export", type=str, nargs=2, + metavar=("channel", "root"), + help="Channel and root to export") + argparser.add_argument("-f", "--file", type=str, + help="JSON file for import/export") + args = argparser.parse_args() + + xion = Xion(xq=args.xq_path) + if args.export: + channel, root = args.export + tree = xion.build_tree(channel, root) + if tree is None: + print("Failed to build config tree.") + return + if args.file: + output_path = args.file + else: + print(f"No output file, using {DEFAULT_FILE_PATH}.") + output_path = DEFAULT_FILE_PATH + xion.export_tree(tree, output_path) + + +class Xion: + + def __init__(self, xq=None): + self.xfconf = Xfconf(xq=xq) + + def build_tree(self, channel, root="/"): + """Return a dict of configs in this channel, filtering on root. + + Return None on error. + """ + props = self.xfconf.get_property_list(channel, root=root) + if props is None: + print(f"Failed to get property list for channel {channel}.") + return None + tree = {} + for prop_name in props: + prop = self.xfconf.get_property(channel, prop_name) + if prop is None: + print(f"Failed to get property {prop_name}.") + return None + if isinstance(prop, list): + leaf = [Xion._build_prop_leaf(p) for p in prop] + else: + leaf = Xion._build_prop_leaf(prop) + tree[prop_name] = leaf + return tree + + def export_tree(self, tree, output_path): + """Export a config tree as a sorted JSON file.""" + with open(output_path, "wt") as output_file: + json.dump(tree, output_file, indent=2, sort_keys=True) + + @staticmethod + def _build_prop_leaf(prop): + return {"type": prop.gtype, "value": str(prop.value)} + + +if __name__ == "__main__": + main()