hotkeys 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. #!/usr/bin/env python3
  2. import os
  3. import sys
  4. from re import compile
  5. from subprocess import run, Popen, PIPE
  6. __author__ = "Mariusz 'Maledorak' Korzekwa"
  7. __credits__ = ["Mariusz 'Maledorak' Korzekwa"]
  8. __license__ = "CC BY 4.0"
  9. __version__ = "1.0.0"
  10. __email__ = "maledorak@gmail.com"
  11. I3 = "i3"
  12. OPENBOX = "openbox"
  13. SUPPORTED_APPS = (I3, OPENBOX)
  14. CONFIG = {
  15. I3: {"name": I3, "path": (".config", "i3", "config"), "parser": "I3ConfigParser"},
  16. OPENBOX: {
  17. "name": OPENBOX,
  18. "path": (".config", "openbox", "rc.xml"),
  19. "parser": "OpenBoxConfigParser",
  20. },
  21. }
  22. DMENU_COMMAND = [
  23. "rofi",
  24. "-dmenu",
  25. "-i",
  26. "-p",
  27. "HotKeys",
  28. "-lines",
  29. "20",
  30. "-width",
  31. "35",
  32. ]
  33. START_LINE_SEARCHING_PATTERN = "%%hotkey:"
  34. END_LINE_SEARCHING_PATTERN = "%%"
  35. ADDITIONAL_DOTS = 10
  36. class BaseConfigParser(object):
  37. def parse_hotkey(self, line):
  38. """
  39. Line parsing
  40. :param line: string
  41. :return: string
  42. """
  43. raise NotImplementedError
  44. class I3ConfigParser(BaseConfigParser):
  45. def parse_hotkey(self, line):
  46. """
  47. Parsing hotkey from line like following:
  48. `bindsym $mod+Return exec $term`
  49. :param line: string
  50. :return: string
  51. """
  52. return line.split(None)[1]
  53. class OpenBoxConfigParser(BaseConfigParser):
  54. def parse_hotkey(self, line):
  55. """
  56. Parsing hotkey from line like following:
  57. `<keybind key="my-key-combination">`
  58. :param line: string
  59. :return: string
  60. """
  61. return line.split('"')[1]
  62. class DmenuHotKeys(object):
  63. """
  64. Getting hotkeys info from your app config file.
  65. If you want use this script you should:
  66. 1. Add the following comment line before your hotkey line which you want to use in your app config
  67. eg:
  68. `i3 line: # %%hotkey: Some description of the following hotkey %%`
  69. `openbox line: <--%%hotkey: Some description of the following hotkey %%-->`
  70. 2. Run this script with your app arg (i3 or openbox)
  71. `./dmenu_hotkeys.py i3`
  72. `./dmenu_hotkeys.py openbox`
  73. """
  74. def __init__(self, app):
  75. self.app = app
  76. self.parser = self.get_parser(app)()
  77. self.content = self.get_config_file_content()
  78. self.entries = self.get_entries(self.content)
  79. self.output = self.format_entries(self.entries)
  80. def get_parser(self, app):
  81. app_parser_name = CONFIG[app]["parser"]
  82. parser = [
  83. subcls
  84. for subcls in BaseConfigParser.__subclasses__()
  85. if subcls.__name__ == app_parser_name
  86. ][0]
  87. return parser
  88. def get_config_file_content(self):
  89. """
  90. Getting content of app config file.
  91. :param path: string with path to your app config file
  92. :return: string
  93. """
  94. path = os.path.join(
  95. os.path.join(os.environ.get("HOME"), *CONFIG[self.app]["path"])
  96. )
  97. with open(path, "r") as file_:
  98. content = file_.read()
  99. return content
  100. def get_entries(self, content):
  101. """
  102. Geting entries of hotkey "info" and hotkey itself.
  103. :param content: string with app config content
  104. :return: list of tuples, eg. [(hotkey, info), (hotkey, info)]
  105. """
  106. regex_search = compile(
  107. r"^.*{start}([^%]+){end}.*$".format(
  108. start=START_LINE_SEARCHING_PATTERN, end=END_LINE_SEARCHING_PATTERN
  109. )
  110. )
  111. content_lines = content.splitlines()
  112. entries = list()
  113. for index, line in enumerate(content_lines):
  114. match = regex_search.match(line)
  115. if match:
  116. info = match.group(1).strip()
  117. hotkey_line = content_lines[index + 1]
  118. hotkey = self.parser.parse_hotkey(hotkey_line)
  119. entries.append((hotkey, info))
  120. return entries
  121. def format_entries(self, entries):
  122. """
  123. Adding nice looking dots between "hotkey" and "info" and return entries in string.
  124. :param entries: list of tuples, eg. [(hotkey, info), (hotkey, info)]
  125. :return: string
  126. """
  127. if not entries:
  128. return ""
  129. longest_hotkey = max(set(len(entry[0]) for entry in entries))
  130. dots_length = longest_hotkey + ADDITIONAL_DOTS
  131. output = list()
  132. for hotkey, info in entries:
  133. output.append(
  134. "{hotkey} {dots} {info}".format(
  135. hotkey=hotkey, dots="." * (dots_length - len(hotkey)), info=info
  136. )
  137. )
  138. return "\n".join(output)
  139. if __name__ == "__main__":
  140. app = sys.argv[1]
  141. if app not in SUPPORTED_APPS:
  142. print(
  143. 'This app "{}" is not supported, try these {}'.format(app, SUPPORTED_APPS)
  144. )
  145. else:
  146. hot_keys = DmenuHotKeys(app)
  147. # subprocess piping was created based on: https://stackoverflow.com/a/4846923
  148. echo = Popen(["echo", hot_keys.output], stdout=PIPE)
  149. run(DMENU_COMMAND, stdin=echo.stdout)
  150. echo.stdout.close()