1 module dstatus.terminal;
2 
3 import std.conv : text, to;
4 
5 short getTerminalWidth() {
6     version (Windows) {
7         import core.sys.windows.winbase : GetStdHandle, STD_OUTPUT_HANDLE;
8         import core.sys.windows.wincon : CONSOLE_SCREEN_BUFFER_INFO, GetConsoleScreenBufferInfo;
9         import core.sys.windows.windef;
10 
11         CONSOLE_SCREEN_BUFFER_INFO info;
12         if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &info) == TRUE) {
13             return info.dwSize.X;
14         }
15     }
16     else version (Posix) {
17         version (FreeBSD) {
18             import core.sys.posix.sys.ioctl : ioctl, winsize;
19             enum TIOCGWINSZ = 0x40087468;
20         }
21         else {
22             import core.sys.posix.sys.ioctl : ioctl, TIOCGWINSZ, winsize;
23         }
24 
25         import core.sys.posix.unistd : STDOUT_FILENO;
26 
27         winsize size;
28         if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &size) == 0) {
29             return size.ws_col;
30         }
31     }
32 
33     // Terminal width could not be gotten - return -1
34     return -1;
35 }
36 
37 version (Posix) {
38     import core.sys.posix.termios : ECHO, ICANON, tcflush, tcgetattr, TCIFLUSH, tcsetattr, TCSANOW, termios;
39     import unistd = core.sys.posix.unistd;
40     import std.stdio : File;
41 
42     class TerminalPosition {
43         private {
44             string setCursorPosition;
45             int _tty;
46         }
47 
48         this(File output) {
49             _tty = output.fileno();
50 
51             if (!unistd.isatty(_tty)) {
52                 return;
53             }
54 
55             // Retrieve current terminal settings
56             termios oldtermios;
57             tcgetattr(_tty, &oldtermios);
58 
59             // Disable echoing and waiting for ENTER
60             auto newtermios = oldtermios;
61             newtermios.c_lflag &= ~(ECHO | ICANON);
62             tcsetattr(_tty, TCSANOW, &newtermios);
63 
64             /* Ensure that original terminal settings are restored upon leaving the scope,
65                lest we mess up the user's terminal. */
66             scope(exit) tcsetattr(_tty, TCSANOW, &oldtermios);
67 
68             // Flush input to prevent user interfering with cursor position acquisition
69             tcflush(_tty, TCIFLUSH);
70 
71             // Send ANSI sequence for getting cursor position to STDOUT
72             auto getPosition = "\033[6n";
73             unistd.write(_tty, cast(void*)getPosition, getPosition.length);
74 
75             // Read the response from STDIN
76             char[16] response;
77             auto readlen = unistd.read(_tty, cast(void*)response, 16);
78 
79             /* Store ANSI sequence for settings cursor position from response
80                by taking the response, chopping off the R and appending an H instead. */
81             setCursorPosition = text(response[0..readlen-1], "H");
82         }
83 
84         void restore() {
85             // Send ANSI sequence for setting cursor position
86             unistd.write(_tty, cast(void*)setCursorPosition, setCursorPosition.length);
87         }
88     }
89 }
90 else version (Windows) {
91     import core.sys.windows.wincon : CONSOLE_CURSOR_INFO, CONSOLE_SCREEN_BUFFER_INFO, COORD, GetConsoleCursorInfo, GetConsoleScreenBufferInfo, SetConsoleCursorPosition;
92     import core.sys.windows.windef;
93     import std.stdio : File;
94 
95     class TerminalPosition {
96         private {
97             COORD _cursorPosition;
98             File _output;
99         }
100 
101         this(File output) {
102             _output = output;
103 
104             CONSOLE_SCREEN_BUFFER_INFO info;
105             if (GetConsoleScreenBufferInfo(_output.windowsHandle, &info) != TRUE) {
106                 throw new Exception("Error getting cursor position.");
107             }
108 
109             _cursorPosition = info.dwCursorPosition;
110         }
111 
112         void restore() {
113             SetConsoleCursorPosition(_output.windowsHandle, _cursorPosition);
114         }
115     }
116 }