summaryrefslogtreecommitdiffstats
path: root/terminaltables/terminal_io.py
blob: 8b8c10dc92ecbaa182405d5a69ec405495b72991 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
"""Get info about the current terminal window/screen buffer."""

import ctypes
import struct
import sys

DEFAULT_HEIGHT = 24
DEFAULT_WIDTH = 79
INVALID_HANDLE_VALUE = -1
IS_WINDOWS = sys.platform == 'win32'
STD_ERROR_HANDLE = -12
STD_OUTPUT_HANDLE = -11


def get_console_info(kernel32, handle):
    """Get information about this current console window (Windows only).

    https://github.com/Robpol86/colorclass/blob/ab42da59/colorclass/windows.py#L111

    :raise OSError: When handle is invalid or GetConsoleScreenBufferInfo API call fails.

    :param ctypes.windll.kernel32 kernel32: Loaded kernel32 instance.
    :param int handle: stderr or stdout handle.

    :return: Width (number of characters) and height (number of lines) of the terminal.
    :rtype: tuple
    """
    if handle == INVALID_HANDLE_VALUE:
        raise OSError('Invalid handle.')

    # Query Win32 API.
    lpcsbi = ctypes.create_string_buffer(22)  # Populated by GetConsoleScreenBufferInfo.
    if not kernel32.GetConsoleScreenBufferInfo(handle, lpcsbi):
        raise ctypes.WinError()  # Subclass of OSError.

    # Parse data.
    left, top, right, bottom = struct.unpack('hhhhHhhhhhh', lpcsbi.raw)[5:-2]
    width, height = right - left, bottom - top
    return width, height


def terminal_size(kernel32=None):
    """Get the width and height of the terminal.

    http://code.activestate.com/recipes/440694-determine-size-of-console-window-on-windows/
    http://stackoverflow.com/questions/17993814/why-the-irrelevant-code-made-a-difference

    :param kernel32: Optional mock kernel32 object. For testing.

    :return: Width (number of characters) and height (number of lines) of the terminal.
    :rtype: tuple
    """
    if IS_WINDOWS:
        kernel32 = kernel32 or ctypes.windll.kernel32
        try:
            return get_console_info(kernel32, kernel32.GetStdHandle(STD_ERROR_HANDLE))
        except OSError:
            try:
                return get_console_info(kernel32, kernel32.GetStdHandle(STD_OUTPUT_HANDLE))
            except OSError:
                return DEFAULT_WIDTH, DEFAULT_HEIGHT

    try:
        device = __import__('fcntl').ioctl(0, __import__('termios').TIOCGWINSZ, '\0\0\0\0\0\0\0\0')
    except IOError:
        return DEFAULT_WIDTH, DEFAULT_HEIGHT
    height, width = struct.unpack('hhhh', device)[:2]
    return width, height


def set_terminal_title(title, kernel32=None):
    """Set the terminal title.

    :param title: The title to set (string, unicode, bytes accepted).
    :param kernel32: Optional mock kernel32 object. For testing.

    :return: If title changed successfully (Windows only, always True on Linux/OSX).
    :rtype: bool
    """
    try:
        title_bytes = title.encode('utf-8')
    except AttributeError:
        title_bytes = title

    if IS_WINDOWS:
        kernel32 = kernel32 or ctypes.windll.kernel32
        try:
            is_ascii = all(ord(c) < 128 for c in title)  # str/unicode.
        except TypeError:
            is_ascii = all(c < 128 for c in title)  # bytes.
        if is_ascii:
            return kernel32.SetConsoleTitleA(title_bytes) != 0
        else:
            return kernel32.SetConsoleTitleW(title) != 0

    # Linux/OSX.
    sys.stdout.write(b'\033]0;' + title_bytes + b'\007')
    return True