pty determining by using the tty_nr

In some situations some programs like zsh are replacing their file
descriptors temporarily.
E.g. zsh replaces (at least on my system) stdout with a pipe on using widgets
(these changes are reverted after finishing using the widget)
So by now the tty_nr is used to figure out the pseudo tty of the
terminal emulator.

Also this change contains the removal of psutil.
The process information is gathered now by using the proc file system.
Reasons:
- the only os supported by this program is linux
- only the function to retrieve the ppid of a process was used
pull/133/head
seebye 3 years ago
parent 2f061d25ba
commit 83a0e9937d

@ -97,7 +97,7 @@ setuptools.setup(
# For an analysis of "install_requires" vs pip's requirements files see:
# https://packaging.python.org/en/latest/requirements.html
install_requires=['python-xlib', 'pillow', 'docopt',
'psutil', 'attrs>=18.2.0'], # Optional
'attrs>=18.2.0'], # Optional
python_requires='>=3.6',
# List additional URLs that are relevant to your project as a dict.

@ -0,0 +1,134 @@
import re
import os
import functools
MAX_PROCESS_NAME_LENGTH = 15
MINOR_DEVICE_NUMBER_MASK = 0b1111_1111_1111_0000_0000_0000_1111_1111
@functools.wraps(os.getpid)
def get_own_pid(*args, **kwargs):
# pylint: disable=missing-docstring
return os.getpid(*args, **kwargs)
def get_info(pid: int):
"""Determines information about the process with the given pid.
Determines
- the process id (pid)
- the command name (comm)
- the state (state)
- the process id of the parent process (ppid)
- the process group id (pgrp)
- the session id (session)
- the controlling terminal (tty_nr)
of the process with the given pid.
Args:
pid (int or str):
the associated pid of the process
for which to retrieve the information for
Returns:
dict of str: bytes:
containing the listed information.
The term in the brackets is used as key.
Raises:
FileNotFoundError: if there is no process with the given pid
"""
with open(f'/proc/{pid}/stat', 'rb') as proc_file:
data = proc_file.read()
return (
re.search(
rb'^(?P<pid>[-+]?\d+) '
rb'\((?P<comm>.{0,' +
str(MAX_PROCESS_NAME_LENGTH).encode() + rb'})\) '
rb'(?P<state>.) '
rb'(?P<ppid>[-+]?\d+) '
rb'(?P<pgrp>[-+]?\d+) '
rb'(?P<session>[-+]?\d+) '
rb'(?P<tty_nr>[-+]?\d+)', data)
.groupdict())
@functools.lru_cache()
def get_pty_slave_folders():
"""Determines the folders in which linux
creates the control device files of the pty slaves.
Returns:
list of str: containing the paths to these folders
"""
paths = []
with open('/proc/tty/drivers', 'rb') as drivers_file:
for line in drivers_file:
# The documentation about /proc/tty/drivers
# is a little bit short (man proc):
# /proc/tty
# Subdirectory containing the pseudo-files and
# subdirectories for tty drivers and line disciplines.
# So.. see the source code:
# https://github.com/torvalds/linux/blob/8653b778e454a7708847aeafe689bce07aeeb94e/fs/proc/proc_tty.c#L28-L67
driver = (
re.search(
rb'^(?P<name>(\S| )+?) +'
rb'(?P<path>/dev/\S+) ',
line)
.groupdict())
if driver['name'] == b'pty_slave':
paths += [driver['path'].decode()]
return paths
def get_parent_pid(pid: int):
"""Determines pid of the parent process of the process with the given pid.
Args:
pid (int or str):
the associated pid of the process
for which to retrieve the information for
Returns:
int: the pid of the parent process
Raises:
FileNotFoundError: if there is no process with the given pid
"""
process_info = get_info(pid)
return int(process_info['ppid'])
def get_pty_slave(pid: int):
"""Determines control device file
of the pty slave of the process with the given pid.
Args:
pid (int or str):
the associated pid of the process
for which to retrieve the information for
Returns:
str or None:
the path to the control device file
or None if no path was found
Raises:
FileNotFoundError: if there is no process with the given pid
"""
pty_slave_folders = get_pty_slave_folders()
process_info = get_info(pid)
tty_nr = int(process_info['tty_nr'])
minor_device_number = tty_nr & MINOR_DEVICE_NUMBER_MASK
for folder in pty_slave_folders:
device_path = f'{folder}/{minor_device_number}'
if tty_nr == os.stat(device_path).st_rdev:
return device_path
return None

@ -1,15 +1,13 @@
"""This module contains x11 utils"""
import os
import sys
import functools
import asyncio
import Xlib
import Xlib.display as Xdisplay
import psutil
import ueberzug.tmux_util as tmux_util
import ueberzug.terminal as terminal
import ueberzug.process as process
Xdisplay.Display.__enter__ = lambda self: self
@ -64,17 +62,16 @@ def get_display():
@functools.lru_cache()
def get_parent_pids(pid=None):
def get_parent_pids(pid):
"""Determines all parent pids of this process.
The list is sorted from youngest parent to oldest parent.
"""
pids = []
process = psutil.Process(pid=pid)
next_pid = pid
while (process is not None and
process.pid > 1):
pids.append(process.pid)
process = process.parent()
while next_pid > 1:
pids.append(next_pid)
next_pid = process.get_parent_pid(next_pid)
return pids
@ -147,10 +144,9 @@ def get_first_pty(pids: list):
the first process in the passed list which owns one.
"""
for pid in pids:
pty_candidate = '/proc/{pid}/fd/1'.format(pid=pid)
with open(pty_candidate) as pty:
if os.isatty(pty.fileno()):
return pty_candidate
pty_slave_file = process.get_pty_slave(pid)
if pty_slave_file:
return pty_slave_file
return None
@ -169,7 +165,7 @@ def get_parent_window_infos():
if tmux_util.is_used():
client_pids = tmux_util.get_client_pids()
else:
client_pids = {psutil.Process().pid}
client_pids = {process.get_own_pid()}
if client_pids:
pid_window_id_map = get_pid_window_id_map()

Loading…
Cancel
Save