config.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397
  1. #! /usr/bin/python3
  2. #
  3. # config.py
  4. # Copyright (C) 2008 Canonical Ltd.
  5. # Copyright (C) 2008-2014 Dustin Kirkland <kirkland@byobu.org>
  6. #
  7. # Authors: Nick Barcet <nick.barcet@ubuntu.com>
  8. # Dustin Kirkland <kirkland@byobu.org>
  9. #
  10. # This program is free software: you can redistribute it and/or modify
  11. # it under the terms of the GNU General Public License as published by
  12. # the Free Software Foundation, version 3 of the License.
  13. #
  14. # This program is distributed in the hope that it will be useful,
  15. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. # GNU General Public License for more details.
  18. #
  19. # You should have received a copy of the GNU General Public License
  20. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  21. # If you change any strings, please generate localization information with:
  22. # ./debian/rules get-po
  23. from __future__ import print_function
  24. import sys
  25. import os
  26. import os.path
  27. import time
  28. import string
  29. import subprocess
  30. import gettext
  31. import glob
  32. def error(msg):
  33. print("ERROR: %s" % msg)
  34. sys.exit(1)
  35. try:
  36. import snack
  37. from snack import *
  38. except Exception:
  39. error("Could not import the python snack module")
  40. PKG = "byobu"
  41. HOME = os.getenv("HOME")
  42. USER = os.getenv("USER")
  43. BYOBU_CONFIG_DIR = os.getenv("BYOBU_CONFIG_DIR", HOME + "/.byobu")
  44. BYOBU_RUN_DIR = os.getenv("BYOBU_RUN_DIR", HOME + "/.cache/byobu")
  45. BYOBU_BACKEND = os.getenv("BYOBU_BACKEND", "tmux")
  46. BYOBU_SOCKETDIR = os.getenv("SOCKETDIR", "/var/run/screen")
  47. BYOBU_PREFIX = os.getenv("BYOBU_PREFIX", "/usr")
  48. SHARE = BYOBU_PREFIX + '/share/' + PKG
  49. DOC = BYOBU_PREFIX + '/share/doc/' + PKG
  50. if not os.path.exists(SHARE):
  51. SHARE = BYOBU_CONFIG_DIR + "/" + SHARE
  52. if not os.path.exists(DOC):
  53. DOC = BYOBU_PREFIX + '/share/doc/packages/' + PKG
  54. if not os.path.exists(DOC):
  55. DOC = BYOBU_CONFIG_DIR + "/" + DOC
  56. DEF_ESC = "A"
  57. RELOAD = "If you are using the default set of keybindings, press\n<F5> or <ctrl-a-R> to activate these changes.\n\nOtherwise, exit this session and start a new one."
  58. RELOAD_FLAG = "%s/reload-required" % (BYOBU_RUN_DIR)
  59. ESC = ''
  60. snack.hotkeys[ESC] = ord(ESC)
  61. snack.hotkeys[ord(ESC)] = ESC
  62. gettext.bindtextdomain(PKG, SHARE + '/po')
  63. gettext.textdomain(PKG)
  64. _ = gettext.gettext
  65. def ioctl_GWINSZ(fd):
  66. # Discover terminal width
  67. try:
  68. import fcntl
  69. import termios
  70. import struct
  71. import os
  72. cr = struct.unpack('hh', fcntl.ioctl(fd, termios.TIOCGWINSZ, '1234'))
  73. except Exception:
  74. return None
  75. return cr
  76. def reload_required():
  77. try:
  78. if not os.path.exists(BYOBU_CONFIG_DIR):
  79. # 493 (decimal) is 0755 (octal)
  80. # Use decimal for portability across all python versions
  81. os.makedirs(BYOBU_CONFIG_DIR, 493)
  82. f = open(RELOAD_FLAG, 'w')
  83. f.close()
  84. if BYOBU_BACKEND == "screen":
  85. subprocess.call([BYOBU_BACKEND, "-X", "at", "0", "source", "%s/profile" % BYOBU_CONFIG_DIR])
  86. except Exception:
  87. True
  88. def terminal_size():
  89. # decide on some terminal size
  90. cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2)
  91. # try open fds
  92. if not cr:
  93. # ...then ctty
  94. try:
  95. fd = os.open(os.ctermid(), os.O_RDONLY)
  96. cr = ioctl_GWINSZ(fd)
  97. os.close(fd)
  98. except Exception:
  99. pass
  100. if not cr:
  101. # env vars or finally defaults
  102. try:
  103. cr = (env['LINES'], env['COLUMNS'])
  104. except Exception:
  105. cr = (25, 80)
  106. # reverse rows, cols
  107. return int(cr[1] - 5), int(cr[0] - 5)
  108. def menu(snackScreen, size, isInstalled):
  109. if isInstalled:
  110. installtext = _("Byobu currently launches at login (toggle off)")
  111. else:
  112. installtext = _("Byobu currently does not launch at login (toggle on)")
  113. li = Listbox(height=6, width=60, returnExit=1)
  114. li.append(_("Help -- Quick Start Guide"), 1)
  115. li.append(_("Toggle status notifications"), 2)
  116. li.append(_("Change escape sequence"), 3)
  117. li.append(installtext, 4)
  118. bb = ButtonBar(snackScreen, (("Exit", "exit", ESC),), compact=1)
  119. g = GridForm(snackScreen, _(" Byobu Configuration Menu"), 1, 2)
  120. g.add(li, 0, 0, padding=(4, 2, 4, 2))
  121. g.add(bb, 0, 1, padding=(1, 1, 0, 0))
  122. if bb.buttonPressed(g.runOnce()) == "exit":
  123. return 0
  124. else:
  125. return li.current()
  126. def messagebox(snackScreen, width, height, title, text, scroll=0, buttons=((_("Okay"), "okay"), (_("Cancel"), "cancel", ESC))):
  127. t = Textbox(width, height, text, scroll=scroll)
  128. bb = ButtonBar(snackScreen, buttons, compact=1)
  129. g = GridForm(snackScreen, title, 1, 2)
  130. g.add(t, 0, 0, padding=(0, 0, 0, 0))
  131. g.add(bb, 0, 1, padding=(1, 1, 0, 0))
  132. return bb.buttonPressed(g.runOnce())
  133. def help(snackScreen, size):
  134. f = open(DOC + '/help.' + BYOBU_BACKEND + '.txt')
  135. text = f.read()
  136. f.close()
  137. text = text.replace("<esckey>", getesckey(), 1)
  138. t = Textbox(67, 16, text, scroll=1, wrap=1)
  139. bb = ButtonBar(snackScreen, ((_("Menu"), "menu", ESC),), compact=1)
  140. g = GridForm(snackScreen, _("Byobu Help"), 2, 4)
  141. g.add(t, 1, 0)
  142. g.add(bb, 1, 1, padding=(1, 1, 0, 0))
  143. button = bb.buttonPressed(g.runOnce())
  144. return 100
  145. def readstatus():
  146. status = {}
  147. glo = {}
  148. loc = {}
  149. for f in [SHARE + '/status/status', BYOBU_CONFIG_DIR + '/status']:
  150. if os.path.exists(f):
  151. try:
  152. exec(open(f).read(), glo, loc)
  153. except Exception:
  154. error("Invalid configuration [%s]" % f)
  155. if BYOBU_BACKEND == "tmux":
  156. items = "%s %s" % (loc["tmux_left"], loc["tmux_right"])
  157. else:
  158. items = "%s %s %s %s" % (loc["screen_upper_left"], loc["screen_upper_right"], loc["screen_lower_left"], loc["screen_lower_right"])
  159. for i in items.split():
  160. if i.startswith("#"):
  161. i = i.replace("#", "")
  162. status[i] = "0"
  163. else:
  164. status[i] = "1"
  165. li = []
  166. keys = list(status.keys())
  167. for i in sorted(keys):
  168. window = [int(status[i]), i]
  169. li.append(window)
  170. return li
  171. def genstatusstring(s, status):
  172. new = ""
  173. glo = {}
  174. loc = {}
  175. exec(open(SHARE + '/status/status').read(), glo, loc)
  176. for i in loc[s].split():
  177. if i.startswith("#"):
  178. i = i.replace("#", "")
  179. if status[i] == 1:
  180. new += " " + i
  181. else:
  182. new += " #" + i
  183. return new
  184. def writestatus(items):
  185. status = {}
  186. path = BYOBU_CONFIG_DIR + '/status'
  187. for i in items:
  188. status[i[1]] = i[0]
  189. for key in ["tmux_left", "tmux_right", "screen_upper_left", "screen_upper_right", "screen_lower_left", "screen_lower_right"]:
  190. if key.startswith(BYOBU_BACKEND):
  191. try:
  192. f = open(path, "r")
  193. except Exception:
  194. f = open(SHARE + '/status/status', "r")
  195. lines = f.readlines()
  196. f.close()
  197. try:
  198. f = open(path, "w")
  199. except Exception:
  200. f = open(path, "a+")
  201. for l in lines:
  202. if l.startswith("%s=" % key):
  203. val = genstatusstring(key, status)
  204. f.write("%s=\"%s\"\n" % (key, val))
  205. else:
  206. f.write(l)
  207. f.close
  208. def togglestatus(snackScreen, size):
  209. itemlist = readstatus()
  210. rl = Label("")
  211. r = CheckboxTree(12, scroll=1)
  212. count = 0
  213. for item in itemlist:
  214. if item[0] != -1:
  215. r.append(item[1], count, selected=item[0])
  216. count = count + 1
  217. bb = ButtonBar(snackScreen, ((_("Apply"), "apply"), (_("Cancel"), "cancel", ESC)), compact=1)
  218. g = GridForm(snackScreen, _("Toggle status notifications"), 2, 4)
  219. g.add(rl, 0, 0, anchorLeft=1, anchorTop=1, padding=(4, 0, 0, 1))
  220. g.add(r, 1, 0)
  221. g.add(bb, 1, 1, padding=(4, 1, 0, 0))
  222. if bb.buttonPressed(g.runOnce()) != "cancel":
  223. count = 0
  224. for item in itemlist:
  225. if item[0] != -1:
  226. item[0] = r.getEntryValue(count)[1]
  227. count = count + 1
  228. writestatus(itemlist)
  229. reload_required()
  230. return 100
  231. def install(snackScreen, size, isInstalled):
  232. out = ""
  233. if isInstalled:
  234. if subprocess.call(["byobu-launcher-uninstall"]) == 0:
  235. out = _("Byobu will not be launched next time you login.")
  236. button = messagebox(snackScreen, 60, 2, _("Message"), out, buttons=((_("Menu"), )))
  237. return 101
  238. else:
  239. if subprocess.call(["byobu-launcher-install"]) == 0:
  240. out = _("Byobu will be launched automatically next time you login.")
  241. button = messagebox(snackScreen, 60, 2, "Message", out, buttons=((_("Menu"), )))
  242. return 100
  243. def appendtofile(p, s):
  244. f = open(p, 'a')
  245. try:
  246. f.write(s)
  247. except IOError:
  248. f.close()
  249. return
  250. f.close()
  251. return
  252. def getesckey():
  253. line = ""
  254. if BYOBU_BACKEND == "tmux":
  255. path = BYOBU_CONFIG_DIR + '/keybindings.tmux'
  256. if os.path.exists(path):
  257. for l in open(path):
  258. if l.startswith("set -g prefix "):
  259. line = l
  260. else:
  261. return DEF_ESC
  262. else:
  263. path = BYOBU_CONFIG_DIR + '/keybindings'
  264. if os.path.exists(path):
  265. for l in open(path):
  266. if l.startswith("escape "):
  267. line = l
  268. else:
  269. return DEF_ESC
  270. if line == "":
  271. return DEF_ESC
  272. esc = line[line.find('^') + 1]
  273. if esc == "`":
  274. esc = " "
  275. return esc
  276. def setesckey(key):
  277. if key.isalpha():
  278. # throw away outputs in order that the view isn't broken
  279. nullf = open(os.devnull, "w")
  280. subprocess.call(["byobu-ctrl-a", "screen", key], stdout=nullf)
  281. nullf.close()
  282. def chgesc(snackScreen, size):
  283. esc = Entry(2, text=getesckey(), returnExit=1)
  284. escl = Label(_("Escape key: ctrl-"))
  285. bb = ButtonBar(snackScreen, ((_("Apply"), "apply"), (_("Cancel"), "cancel", ESC)), compact=1)
  286. g = GridForm(snackScreen, _("Change escape sequence"), 2, 4)
  287. g.add(escl, 0, 0, anchorLeft=1, padding=(1, 0, 0, 1))
  288. g.add(esc, 1, 0, anchorLeft=1)
  289. g.add(bb, 1, 1)
  290. g.setTimer(100)
  291. loop = 1
  292. while loop:
  293. which = g.run()
  294. if which == "TIMER":
  295. val = esc.value()
  296. if len(val) > 1:
  297. esc.set(val[1])
  298. # Ensure that escape sequence is not \ or /
  299. if val == '/' or val == '\\':
  300. esc.set(DEF_ESC)
  301. # Ensure that the escape sequence is not set to a number
  302. try:
  303. dummy = int(esc.value())
  304. esc.set(DEF_ESC)
  305. except Exception:
  306. # do nothing
  307. dummy = "foo"
  308. else:
  309. loop = 0
  310. snackScreen.popWindow()
  311. button = bb.buttonPressed(which)
  312. if button != "cancel":
  313. setesckey(esc.value())
  314. reload_required()
  315. if button == "exit":
  316. return 0
  317. return 100
  318. def autolaunch():
  319. if os.path.exists(BYOBU_CONFIG_DIR + "/disable-autolaunch"):
  320. return 0
  321. try:
  322. for line in open("%s/.profile" % HOME):
  323. if "byobu-launch" in line:
  324. return 1
  325. except Exception:
  326. return 0
  327. if os.path.exists("/etc/profile.d/Z97-%s.sh" % PKG):
  328. return 1
  329. return 0
  330. def main():
  331. """This is the main loop of our utility"""
  332. size = terminal_size()
  333. snackScreen = SnackScreen()
  334. snackScreen.drawRootText(1, 0, _('Byobu Configuration Menu'))
  335. snackScreen.pushHelpLine(_('<Tab> between elements | <Enter> selects | <Esc> exits'))
  336. isInstalled = autolaunch()
  337. tag = 100
  338. while tag > 0:
  339. tag = menu(snackScreen, size, isInstalled)
  340. if tag == 1:
  341. tag = help(snackScreen, size)
  342. elif tag == 2:
  343. tag = togglestatus(snackScreen, size)
  344. elif tag == 3:
  345. tag = chgesc(snackScreen, size)
  346. elif tag == 4:
  347. tag = install(snackScreen, size, isInstalled)
  348. isInstalled = autolaunch()
  349. snackScreen.finish()
  350. sys.exit(0)
  351. if __name__ == "__main__":
  352. main()