diff --git a/autogen-qtile-keybinding.sh b/autogen-qtile-keybinding.sh new file mode 100644 index 0000000..534a337 --- /dev/null +++ b/autogen-qtile-keybinding.sh @@ -0,0 +1,421 @@ +#!/usr/bin/env python3 + +############################################## +# Auto Qtile keybindings - image generator # +# Qmade version of the configuration # +############################################## + +import getopt +import os +import sys + +import cairocffi as cairo +from cairocffi import ImageSurface + +this_dir = os.path.dirname(__file__) +base_dir = os.path.abspath(os.path.join(this_dir, "..")) +sys.path.insert(0, base_dir) + +BUTTON_NAME_Y = 65 +BUTTON_NAME_X = 10 + +COMMAND_Y = 20 +COMMAND_X = 10 + +LEGEND = ["modifiers", "layout", "group", "window", "other"] + +CUSTOM_KEYS = { + "Backspace": 2, + "Tab": 1.5, + "\\": 1.5, + "Return": 2.4533, + "shift": 2, + "space": 5, +} + +class Button: + def __init__(self, key, x, y, width, height): + self.key = key + self.x = x + self.y = y + self.width = width + self.height = height + + +class Pos: + WIDTH = 78 + HEIGHT = 70 + GAP = 5 + + def __init__(self, x, y): + self.x = x + self.row_x = x + self.y = y + self.custom_width = {} + for i, val in CUSTOM_KEYS.items(): + self.custom_width[i] = val * self.WIDTH + + def get_pos(self, name): + if name in self.custom_width: + width = self.custom_width[name] + else: + width = self.WIDTH + + info = Button(name, self.x, self.y, width, self.HEIGHT) + + self.x = self.x + self.GAP + width + + return info + + def skip_x(self, times=1): + self.x = self.x + self.GAP + times * self.WIDTH + + def next_row(self): + self.x = self.row_x + self.y = self.y + self.GAP + self.HEIGHT + + +class KeyboardPNGFactory: + def __init__(self, modifiers, keys): + self.keys = keys + self.modifiers = modifiers.split("-") + self.key_pos = self.calculate_pos(20, 140) + + def rgb_red(self, context): + context.set_source_rgb(0.8431372549, 0.3725490196, 0.3725490196) + + def rgb_green(self, context): + context.set_source_rgb(0.6862745098, 0.6862745098, 0) + + def rgb_yellow(self, context): + context.set_source_rgb(1, 0.6862745098, 0) + + def rgb_cyan(self, context): + context.set_source_rgb(0.5137254902, 0.6784313725, 0.6784313725) + + def rgb_violet(self, context): + context.set_source_rgb(0.831372549, 0.5215686275, 0.6784313725) + + def calculate_pos(self, x, y): + pos = Pos(x, y) + + key_pos = {} + for c in "`1234567890-=": + key_pos[c] = pos.get_pos(c) + + key_pos["Backspace"] = pos.get_pos("Backspace") + pos.next_row() + + key_pos["Tab"] = pos.get_pos("Tab") + for c in "qwertyuiop[]\\": + key_pos[c] = pos.get_pos(c) + pos.next_row() + + pos.skip_x(1.6) + for c in "asdfghjkl;'": + key_pos[c] = pos.get_pos(c) + key_pos["Return"] = pos.get_pos("Return") + pos.next_row() + + key_pos["shift"] = pos.get_pos("shift") + for c in "zxcvbnm": + key_pos[c] = pos.get_pos(c) + key_pos["period"] = pos.get_pos("period") + key_pos["comma"] = pos.get_pos("comma") + key_pos["/"] = pos.get_pos("/") + pos.next_row() + + key_pos["control"] = pos.get_pos("control") + pos.skip_x() + key_pos["mod4"] = pos.get_pos("mod4") + key_pos["mod1"] = pos.get_pos("mod1") + key_pos["space"] = pos.get_pos("space") + key_pos["Print"] = pos.get_pos("Print") + pos.skip_x(3) + key_pos["Up"] = pos.get_pos("Up") + + pos.next_row() + pos.skip_x(12.33) + key_pos["Left"] = pos.get_pos("Left") + key_pos["Down"] = pos.get_pos("Down") + key_pos["Right"] = pos.get_pos("Right") + + pos.next_row() + + for legend in LEGEND: + key_pos[legend] = pos.get_pos(legend) + + pos.skip_x(5) + key_pos["Button1"] = pos.get_pos("Button1") + key_pos["Button2"] = pos.get_pos("Button2") + key_pos["Button3"] = pos.get_pos("Button3") + + pos.next_row() + key_pos["FN_KEYS"] = pos.get_pos("FN_KEYS") + + return key_pos + + def render(self, filename): + surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 1280, 800) + context = cairo.Context(surface) + with context: + context.set_source_rgb(1, 1, 1) + context.paint() + + context.move_to(40, 50) + context.set_font_size(34) + context.show_text("Qmade - Keybindings for Qtile") + + context.move_to(40, 80) + context.set_font_size(22) + if len([i for i in self.modifiers if i]): + context.show_text("Modifiers: " + ", ".join(self.modifiers)) + else: + context.show_text("No modifiers used.") + + for i in self.key_pos.values(): + if i.key in ["FN_KEYS"]: + continue + + self.draw_button(context, i.key, i.x, i.y, i.width, i.height) + + # draw functional + fn = [i for i in keys.values() if i.key[:4] == "XF86"] + if len(fn): + fn_pos = self.key_pos["FN_KEYS"] + x = fn_pos.x + for i in fn: + self.draw_button(context, i.key, x, fn_pos.y, fn_pos.width, fn_pos.height) + x += Pos.GAP + Pos.WIDTH + + # draw mouse base + context.rectangle(830, 670, 244, 90) + context.set_source_rgb(0, 0, 0) + context.stroke() + context.set_font_size(28) + context.move_to(900, 730) + context.show_text("MOUSE") + + surface.write_to_png(filename) + + def draw_button(self, context, key, x, y, width, height): + radius = 5 # Radius for the rounded corners + fn = False + if key[:4] == "XF86": + fn = True + + if key in LEGEND: + if key == "modifiers": + self.rgb_red(context) + elif key == "group": + self.rgb_green(context) + elif key == "layout": + self.rgb_cyan(context) + elif key == "window": + self.rgb_yellow(context) + else: + self.rgb_violet(context) + self.rounded_rectangle(context, x, y, width, height, radius) + context.fill() + + if key in self.modifiers: + self.rounded_rectangle(context, x, y, width, height, radius) + self.rgb_red(context) + context.fill() + + if key in self.keys: + k = self.keys[key] + self.rounded_rectangle(context, x, y, width, height, radius) + self.set_key_color(context, k) + context.fill() + + self.show_multiline(context, x + COMMAND_X, y + COMMAND_Y, k) + + self.rounded_rectangle(context, x, y, width, height, radius) + context.set_source_rgb(0, 0, 0) + context.stroke() + + if fn: + key = key[4:] + context.set_font_size(10) + else: + context.set_font_size(14) + + context.move_to(x + BUTTON_NAME_X, y + BUTTON_NAME_Y) + context.show_text(self.translate(key)) + + def rounded_rectangle(self, context, x, y, width, height, radius): + context.new_path() + context.arc(x + radius, y + radius, radius, 2 * (3.14 / 2), 3 * (3.14 / 2)) + context.arc(x + width - radius, y + radius, radius, 3 * (3.14 / 2), 4 * (3.14 / 2)) + context.arc(x + width - radius, y + height - radius, radius, 0, 3.14 / 2) + context.arc(x + radius, y + height - radius, radius, 3.14 / 2, 2 * (3.14 / 2)) + context.close_path() + + def show_multiline(self, context, x, y, key): + """Cairo doesn't support multiline. Added with word wrapping.""" + c_width = 14 + if key.key in CUSTOM_KEYS: + c_width *= CUSTOM_KEYS[key.key] + + context.set_font_size(10) + context.set_source_rgb(0, 0, 0) + context.move_to(x, y) + words = key.command.split(" ") + words.reverse() + printable = last_word = words.pop() + while len(words): + last_word = words.pop() + if len(printable + " " + last_word) < c_width: + printable += " " + last_word + continue + + context.show_text(printable) + y += 10 + context.move_to(x, y) + printable = last_word + + if last_word is not None: + context.show_text(printable) + + def set_key_color(self, context, key): + if key.scope == "group": + self.rgb_green(context) + elif key.scope == "layout": + self.rgb_cyan(context) + elif key.scope == "window": + self.rgb_yellow(context) + else: + self.rgb_violet(context) + + def translate(self, text): + dictionary = { + "period": ",", + "comma": ".", + "Left": "←", + "Down": "↓", + "Right": "→", + "Up": "↑", + "AudioRaiseVolume": "Volume up", + "AudioLowerVolume": "Volume down", + "AudioMute": "Audio mute", + "AudioMicMute": "Mic mute", + "MonBrightnessUp": "Brightness up", + "MonBrightnessDown": "Brightness down", + } + + if text not in dictionary: + return text + + return dictionary[text] + + +class KInfo: + NAME_MAP = { + "togroup": "to group", + "toscreen": "to screen", + } + + def __init__(self, key): + self.key = key.key + self.command = self.get_command(key) + self.scope = self.get_scope(key) + + def get_command(self, key): + if hasattr(key, "desc") and key.desc: + return key.desc + + cmd = key.commands[0] + command = cmd.name + if command in self.NAME_MAP: + command = self.NAME_MAP[command] + + command = command.replace("_", " ") + + if len(cmd.args): + if isinstance(cmd.args[0], str): + command += " " + cmd.args[0] + + return command + + def get_scope(self, key): + selectors = key.commands[0].selectors + if len(selectors): + return selectors[0][0] + + +class MInfo(KInfo): + def __init__(self, mouse): + self.key = mouse.button + self.command = self.get_command(mouse) + self.scope = self.get_scope(mouse) + + +def get_kb_map(config_path=None): + from libqtile.confreader import Config + + c = Config(config_path) + if config_path: + c.load() + + kb_map = {} + for key in c.keys: + mod = "-".join(key.modifiers) + if mod not in kb_map: + kb_map[mod] = {} + + info = KInfo(key) + kb_map[mod][info.key] = info + + for mouse in c.mouse: + mod = "-".join(mouse.modifiers) + if mod not in kb_map: + kb_map[mod] = {} + + info = MInfo(mouse) + kb_map[mod][info.key] = info + + return kb_map + + +help_doc = """ +usage: gen-keybinding-img [-h] [-c CONFIGFILE] [-o OUTPUT_DIR] + +Qtile keybindings image generator + +optional arguments: + -h, --help show this help message and exit + -c CONFIGFILE, --config CONFIGFILE + use specified configuration file. If no presented + default will be used + -o OUTPUT_DIR, --output-dir OUTPUT_DIR + set directory to export all images to +""" +if __name__ == "__main__": + config_path = os.path.expanduser("~/.config/qtile/config.py") # Set default config path + output_dir = "" + try: + opts, args = getopt.getopt(sys.argv[1:], "hc:o:", ["help=", "config=", "output-dir="]) + except getopt.GetoptError: + print(help_doc) + sys.exit(2) + + for opt, arg in opts: + if opt in ("-h", "--help"): + print(help_doc) + sys.exit() + elif opt in ("-c", "--config"): + config_path = arg + elif opt in ("-o", "--output-dir"): + output_dir = arg + + kb_map = get_kb_map(config_path) + for modifier, keys in kb_map.items(): + if not modifier: + filename = "no_modifier.png" + else: + filename = "keybinding_{}.png".format(modifier) + + output_file = os.path.abspath(os.path.join(output_dir, filename)) + f = KeyboardPNGFactory(modifier, keys) + f.render(output_file)