1#!/usr/local/bin/python3.8 2# 3# select-session.py 4# Copyright (C) 2010 Canonical Ltd. 5# Copyright (C) 2012-2014 Dustin Kirkland <kirkland@byobu.org> 6# 7# Authors: Dustin Kirkland <kirkland@byobu.org> 8# Ryan C. Thompson <rct@thompsonclan.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 22 23import os 24import re 25import sys 26import subprocess 27try: 28 # For Python3, try and import input from builtins 29 from builtins import input 30except Exception: 31 # But fall back to using the default input 32 True 33 34 35PKG = "byobu" 36SHELL = os.getenv("SHELL", "/usr/local/bin/bash") 37HOME = os.getenv("HOME") 38BYOBU_CONFIG_DIR = os.getenv("BYOBU_CONFIG_DIR", HOME + "/.byobu") 39BYOBU_BACKEND = os.getenv("BYOBU_BACKEND", "tmux") 40choice = -1 41sessions = [] 42text = [] 43reuse_sessions = os.path.exists("%s/.reuse-session" % (BYOBU_CONFIG_DIR)) 44 45BYOBU_UPDATE_ENVVARS = ["DISPLAY", "DBUS_SESSION_BUS_ADDRESS", "SESSION_MANAGER", "GPG_AGENT_INFO", "XDG_SESSION_COOKIE", "XDG_SESSION_PATH", "GNOME_KEYRING_CONTROL", "GNOME_KEYRING_PID", "GPG_AGENT_INFO", "SSH_ASKPASS", "SSH_AUTH_SOCK", "SSH_AGENT_PID", "WINDOWID", "UPSTART_JOB", "UPSTART_EVENTS", "UPSTART_SESSION", "UPSTART_INSTANCE"] 46 47 48def get_sessions(): 49 sessions = [] 50 i = 0 51 output = False 52 if BYOBU_BACKEND == "screen": 53 try: 54 output = subprocess.Popen(["screen", "-ls"], stdout=subprocess.PIPE).communicate()[0] 55 except subprocess.CalledProcessError as cpe: 56 # screen -ls seems to always return 1 57 if cpe.returncode != 1: 58 raise 59 else: 60 output = cpe.output 61 if sys.stdout.encoding is None: 62 output = output.decode("UTF-8") 63 else: 64 output = output.decode(sys.stdout.encoding) 65 if output: 66 for s in output.splitlines(): 67 s = re.sub(r'\s+', ' ', s) 68 # Ignore hidden sessions (named sessions that start with a "." or a "_") 69 if s and s != " " and (s.find(" ") == 0 and len(s) > 1 and s.count("..") == 0 and s.count("._") == 0): 70 text.append("screen: %s" % s.strip()) 71 items = s.split(" ") 72 sessions.append("screen____%s" % items[1]) 73 i += 1 74 if BYOBU_BACKEND == "tmux": 75 output = subprocess.Popen(["tmux", "list-sessions"], stdout=subprocess.PIPE).communicate()[0] 76 if sys.stdout.encoding is None: 77 output = output.decode("UTF-8") 78 else: 79 output = output.decode(sys.stdout.encoding) 80 if output: 81 for s in output.splitlines(): 82 # Ignore hidden sessions (named sessions that start with a "_") 83 if s and not s.startswith("_") and s.find("-") == -1: 84 text.append("tmux: %s" % s.strip()) 85 sessions.append("tmux____%s" % s.split(":")[0]) 86 i += 1 87 return sessions 88 89 90def cull_zombies(session_name): 91 # When using tmux session groups, closing a client will leave 92 # unattached "zombie" sessions that will never be reattached. 93 # Search for and kill any unattached hidden sessions in the same group 94 if BYOBU_BACKEND == "tmux": 95 output = subprocess.Popen(["tmux", "list-sessions"], stdout=subprocess.PIPE).communicate()[0] 96 if sys.stdout.encoding is None: 97 output = output.decode("UTF-8") 98 else: 99 output = output.decode(sys.stdout.encoding) 100 if not output: 101 return 102 103 # Find the master session to extract the group name. We use 104 # the group number to be extra sure the right session is getting 105 # killed. We don't want to accidentally kill the wrong one 106 pattern = "^%s:.+\\((group [^\\)]+)\\).*$" % session_name 107 master = re.search(pattern, output, re.MULTILINE) 108 if not master: 109 return 110 111 # Kill all the matching hidden & unattached sessions 112 pattern = "^_%s-\\d+:.+\\(%s\\)$" % (session_name, master.group(1)) 113 for s in re.findall(pattern, output, re.MULTILINE): 114 subprocess.Popen(["tmux", "kill-session", "-t", s.split(":")[0]]) 115 116 117def update_environment(session): 118 backend, session_name = session.split("____", 2) 119 for var in BYOBU_UPDATE_ENVVARS: 120 value = os.getenv(var) 121 if value: 122 if backend == "tmux": 123 cmd = ["tmux", "setenv", "-t", session_name, var, value] 124 else: 125 cmd = ["screen", "-S", session_name, "-X", "setenv", var, value] 126 subprocess.call(cmd, stdout=open(os.devnull, "w")) 127 128 129def attach_session(session): 130 update_environment(session) 131 backend, session_name = session.split("____", 2) 132 cull_zombies(session_name) 133 # must use the binary, not the wrapper! 134 if backend == "tmux": 135 if reuse_sessions: 136 os.execvp("tmux", ["tmux", "-u", "new-session", "-t", session_name, ";", "set-option", "destroy-unattached"]) 137 else: 138 os.execvp("tmux", ["tmux", "-u", "attach", "-t", session_name]) 139 else: 140 os.execvp("screen", ["screen", "-AOxRR", session_name]) 141 142 143sessions = get_sessions() 144 145show_shell = os.path.exists("%s/.always-select" % (BYOBU_CONFIG_DIR)) 146if len(sessions) > 1 or show_shell: 147 sessions.append("NEW") 148 text.append("Create a new Byobu session (%s)" % BYOBU_BACKEND) 149 sessions.append("SHELL") 150 text.append("Run a shell without Byobu (%s)" % SHELL) 151 152if len(sessions) > 1: 153 sys.stdout.write("\nByobu sessions...\n\n") 154 tries = 0 155 while tries < 3: 156 i = 1 157 for s in text: 158 sys.stdout.write(" %d. %s\n" % (i, s)) 159 i += 1 160 try: 161 try: 162 user_input = input("\nChoose 1-%d [1]: " % (i - 1)) 163 except Exception: 164 user_input = "" 165 if not user_input or user_input == "": 166 choice = 1 167 break 168 try: 169 choice = int(user_input) 170 except Exception: 171 choice = int(eval(user_input)) 172 if choice >= 1 and choice < i: 173 break 174 else: 175 tries += 1 176 choice = -1 177 sys.stderr.write("\nERROR: Invalid input\n") 178 except KeyboardInterrupt: 179 sys.stdout.write("\n") 180 sys.exit(0) 181 except Exception: 182 if choice == "" or choice == -1: 183 choice = 1 184 break 185 tries += 1 186 choice = -1 187 sys.stderr.write("\nERROR: Invalid input\n") 188elif len(sessions) == 1: 189 # Auto-select the only session 190 choice = 1 191 192if choice >= 1: 193 if sessions[choice - 1] == "NEW": 194 # Create a new session 195 if BYOBU_BACKEND == "tmux": 196 os.execvp("byobu", ["byobu", "new-session", SHELL]) 197 else: 198 os.execvp("byobu", ["byobu", SHELL]) 199 elif sessions[choice - 1] == "SHELL": 200 os.execvp(SHELL, [SHELL]) 201 else: 202 # Attach to the chosen session; must use the binary, not the wrapper! 203 attach_session(sessions[choice - 1]) 204 205# No valid selection, default to the youngest session, create if necessary 206if BYOBU_BACKEND == "tmux": 207 os.execvp("tmux", ["tmux"]) 208else: 209 os.execvp("screen", ["screen", "-AOxRR"]) 210