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