/usr/share/budgie-desktop/keycontrol/bin/gnome-custom-keybinding-setup.py is in budgie-desktop-environment 0.9.9.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 | #!/usr/bin/python3 -tt
# -*- coding: utf-8 -*-
__author__ = 'Bernard Gray'
__copyright__ = '(C) 2018 Bernard Gray <bernard.gray@gmail.com>'
__license__ = 'GPLv2 or any later version'
from subprocess import *
import argparse
import ast
import configparser
import logging as log
import os
import sys
import tempfile
SYS_KEYS = '/usr/share/budgie-desktop/keycontrol/custom-keys.d/'
# proposals only follow, they are not implemented
#ETC_KEYS = '/etc/budgie-desktop/keycontrol/custom-keys.d/'
#USR_KEYS = '${HOME}/.config/budgie-desktop/keycontrol/custom-keys.d/'
DCONF_KEY_PATH = '/org/gnome/settings-daemon/plugins/media-keys/'
class BudgieKeyError(Exception):
'''A generic error handler that does nothing.'''
pass
def parseDconf(conf, is_string=False):
_cparse = configparser.ConfigParser(interpolation=None)
if is_string == True:
_cparse.read_string(conf)
else:
_cparse.read(conf)
return _cparse
def keyNameExists(key, conf):
if key != 'DEFAULT':
try:
exists = conf[key]
return True
except KeyError:
return False
def getKeyFiles(key_dir):
'''return a list of filenames from a given directory,
conforming to our key filenaming standard (*.dconf)'''
_file_list = []
for dirpath,_,filenames in os.walk(key_dir):
for f in filenames:
if f.endswith('.dconf'):
_full_path = os.path.abspath(os.path.join(dirpath, f))
_file_list.append(os.path.abspath(os.path.join(dirpath, f)))
else:
log.debug('skipping %s, doesnt end in .dconf' % f)
return _file_list
def loadDconf(dconf_file):
'''write a key file into the dconf database'''
if args.dry_run:
log.debug('--dry-run active - not loading into dconf database, generated config follows')
log.debug('==============================================================================')
try:
cmd = Popen(['cat', dconf_file], close_fds=True)
except:
log.error('problem executing command: %s' % ' '.join(cmd))
raise BudgieKeyError
else:
try:
cmd = Popen(['cat', dconf_file], stdout=PIPE)
cmd_pipe = Popen(['dconf', 'load', DCONF_KEY_PATH], stdin = cmd.stdout, stdout = PIPE)
cmd.stdout.close()
cout = cmd_pipe.communicate()[0]
except:
log.error('problem executing command: %s' % ' '.join(cmd_pipe))
raise BudgieKeyError
if __name__ == '__main__':
aparse = argparse.ArgumentParser(description = "load custom keybindings via dconf keys")
aparse.add_argument(
'-r', '--dry-run', action = 'store_true',
help = 'do all operations *except* write to dconf'
)
aparse.add_argument(
'-f', '--force', action = 'store_true',
help = 'overwrite existing key values'
)
aparse.add_argument(
'-v', '--verbose', action = 'store_true',
help = 'increased output and informational/error messages'
)
group = aparse.add_mutually_exclusive_group()
group.add_argument(
'-d', '--dconf-file',
help = 'single dconf file to process'
)
group.add_argument(
'-D', '--dconf-dir', default=SYS_KEYS,
help = 'directory of dconf custom-keybinding files to process'
)
args = aparse.parse_args()
# setup logging
if args.verbose:
log.basicConfig(level='DEBUG', format='%(levelname)s: %(message)s')
else:
log.basicConfig(level='INFO', format='%(levelname)s: %(message)s')
# identify the files we want to parse
if args.dconf_file:
dconf_files = [ os.path.abspath(args.dconf_file) ]
else:
dconf_files = getKeyFiles(args.dconf_dir)
if len(dconf_files) == 0:
log.warning('no files found to process in (' + str(args.dconf_dir) + ', ' + str(args.dconf_file) + ')')
quit()
# get our current dconf custom keybinding config for comparison
curr_conf = parseDconf(check_output(['dconf', 'dump', '/org/gnome/settings-daemon/plugins/media-keys/']).decode('ascii'), True)
# prepare a single conf object to compile all our new keys into
out_conf = configparser.ConfigParser(interpolation=None)
key_ref = []
# parse all the custom keys in our conf files, and create our dconf entries from them
for dconf_file in dconf_files:
try:
dconf_key = parseDconf(dconf_file)
except configparser.MissingSectionHeaderError:
log.error('%s is missing a Section Header' % dconf_file)
except configparser.ParsingError:
log.error('%s has an incorrectly formatted (or missing) element in section %s' % (dconf_file,key_name))
# todo check if the key-combination is in use with some other keybinding
# we can handle having multiple keys defined per file
process_file = True
for key_name in dconf_key:
if key_name != 'DEFAULT':
if keyNameExists(key_name, curr_conf) and key_name != '/':
log.debug('dconf already has key %s' % (key_name))
if not args.force:
process_file = False
elif key_name != '/':
# ugly, but if we don't check for trailing / it crashes our DE
if key_name.endswith('/'):
key_ref.append(DCONF_KEY_PATH + key_name)
else:
key_ref.append(DCONF_KEY_PATH + key_name + '/')
if process_file:
out_conf.read(dconf_file)
else:
log.debug('NOT processing %s, keys already exist in dconf' % (dconf_file))
# assemble our custom-keybindings array option, pop it into a tempfile for dconf load
if len(key_ref) > 0:
# get custom-bindings ref from curr_conf
try:
curr_key_ref = ast.literal_eval(curr_conf.get('/', 'custom-keybindings'))
key_ref = curr_key_ref + key_ref
except:
pass
# build the root section with our new custom-keybindings references
try:
out_conf.add_section('/')
except configparser.DuplicateSectionError:
pass
out_conf.set('/', 'custom-keybindings', str(key_ref))
# we must apply the root '/' section which includes sys overrides before applying the keys
out_root_conf = configparser.ConfigParser(interpolation=None)
out_root_conf.read_dict(out_conf)
for s in out_root_conf.sections():
if s != '/':
out_root_conf.remove_section(s)
# we don't need '/' section in our out_conf object now
out_conf.remove_section('/')
for conf in [ out_root_conf, out_conf]:
with tempfile.NamedTemporaryFile(mode='w', delete=False) as configfile:
conf.write(configfile)
# money shot
loadDconf(configfile.name)
if not args.dry_run:
log.debug('Success! config loaded, you can confirm with:\n\tdconf dump %s' % DCONF_KEY_PATH)
else:
log.info('nothing to do!')
|