Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# Unreleased
- Fix #9: Query console window size through platform terminal APIs so tmux
panes report their pane dimensions instead of the outer terminal size.

# 5.0.0
- *BREAKING*: Raise the minimum Dart SDK constraint to `>=3.10.0`.
- *BREAKING*: Update `win32` dependency support to `>=6.0.1 <7.0.0`.
Expand Down
4 changes: 2 additions & 2 deletions lib/src/console.dart
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ class Console {
/// Returns the width of the current console window in characters.
int get windowWidth {
if (hasTerminal) {
return stdout.terminalColumns;
return _termlib.windowWidth ?? stdout.terminalColumns;
} else {
// Treat a window that has no terminal as if it is 80x25. This should be
// more compatible with CI/CD environments.
Expand All @@ -138,7 +138,7 @@ class Console {
/// Returns the height of the current console window in characters.
int get windowHeight {
if (hasTerminal) {
return stdout.terminalLines;
return _termlib.windowHeight ?? stdout.terminalLines;
} else {
// Treat a window that has no terminal as if it is 80x25. This should be
// more compatible with CI/CD environments.
Expand Down
3 changes: 3 additions & 0 deletions lib/src/ffi/termlib.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ import 'unix/termlib_unix.dart';
import 'win/termlib_win.dart';

abstract class TermLib {
int? get windowHeight;
int? get windowWidth;

int setWindowHeight(int height);
int setWindowWidth(int width);

Expand Down
49 changes: 40 additions & 9 deletions lib/src/ffi/unix/termios.dart
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ const int TCSAFLUSH = 2; // drain output, flush input
const int VMIN = 16; // minimum number of characters to receive
const int VTIME = 17; // time in 1/10s before returning

const int TIOCGWINSZ_LINUX = 0x5413;
const int TIOCGWINSZ_MACOS = 0x40087468;

// typedef unsigned long tcflag_t;
typedef tcflag_t = UnsignedLong;

Expand Down Expand Up @@ -104,16 +107,44 @@ base class TermIOS extends Struct {
external int c_ospeed;
}

// struct winsize {
// unsigned short ws_row;
// unsigned short ws_col;
// unsigned short ws_xpixel;
// unsigned short ws_ypixel;
// };
base class WinSize extends Struct {
@UnsignedShort()
external int ws_row;
@UnsignedShort()
external int ws_col;
@UnsignedShort()
external int ws_xpixel;
@UnsignedShort()
external int ws_ypixel;
}

// int tcgetattr(int, struct termios *);
typedef TCGetAttrNative = Int32 Function(
Int32 fildes, Pointer<TermIOS> termios);
typedef TCGetAttrNative =
Int32 Function(Int32 fildes, Pointer<TermIOS> termios);
typedef TCGetAttrDart = int Function(int fildes, Pointer<TermIOS> termios);

// int tcsetattr(int, int, const struct termios *);
typedef TCSetAttrNative = Int32 Function(
Int32 fildes,
Int32 optional_actions,
Pointer<TermIOS> termios,
);
typedef TCSetAttrDart = int Function(
int fildes, int optional_actions, Pointer<TermIOS> termios);
typedef TCSetAttrNative =
Int32 Function(
Int32 fildes,
Int32 optional_actions,
Pointer<TermIOS> termios,
);
typedef TCSetAttrDart =
int Function(int fildes, int optional_actions, Pointer<TermIOS> termios);

// int ioctl(int, unsigned long, ...);
typedef IOCtlNative =
Int32 Function(
Int32 fildes,
UnsignedLong request,
Pointer<WinSize> winsize,
);
typedef IOCtlDart =
int Function(int fildes, int request, Pointer<WinSize> winsize);
33 changes: 31 additions & 2 deletions lib/src/ffi/unix/termlib_unix.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,32 @@ class TermLibUnix implements TermLib {

late final TCGetAttrDart tcgetattr;
late final TCSetAttrDart tcsetattr;
late final IOCtlDart ioctl;

int get _windowSizeRequest =>
Platform.isMacOS ? TIOCGWINSZ_MACOS : TIOCGWINSZ_LINUX;

int? _readWindowSize(int Function(WinSize size) value) {
final winsize = calloc<WinSize>();
try {
for (final fd in [STDOUT_FILENO, STDIN_FILENO, STDERR_FILENO]) {
if (ioctl(fd, _windowSizeRequest, winsize) == 0 &&
winsize.ref.ws_col > 0 &&
winsize.ref.ws_row > 0) {
return value(winsize.ref);
}
}
return null;
} finally {
calloc.free(winsize);
}
}

@override
int? get windowHeight => _readWindowSize((size) => size.ws_row);

@override
int? get windowWidth => _readWindowSize((size) => size.ws_col);

@override
int setWindowHeight(int height) {
Expand All @@ -47,8 +73,10 @@ class TermLibUnix implements TermLib {
..ref.c_cflag = (origTermIOS.c_cflag & ~CSIZE) | CS8
..ref.c_lflag = origTermIOS.c_lflag & ~(ECHO | ICANON | IEXTEN | ISIG)
..ref.c_cc = origTermIOS.c_cc
..ref.c_cc[VMIN] = 0 // VMIN -- return each byte, or 0 for timeout
..ref.c_cc[VTIME] = 1 // VTIME -- 100ms timeout (unit is 1/10s)
..ref.c_cc[VMIN] =
0 // VMIN -- return each byte, or 0 for timeout
..ref.c_cc[VTIME] =
1 // VTIME -- 100ms timeout (unit is 1/10s)
..ref.c_ispeed = origTermIOS.c_ispeed
..ref.c_oflag = origTermIOS.c_ospeed;

Expand All @@ -74,6 +102,7 @@ class TermLibUnix implements TermLib {
tcsetattr = _stdlib.lookupFunction<TCSetAttrNative, TCSetAttrDart>(
'tcsetattr',
);
ioctl = _stdlib.lookupFunction<IOCtlNative, IOCtlDart>('ioctl');

// store console mode settings so we can return them again as necessary
_origTermIOSPointer = calloc<TermIOS>();
Expand Down
20 changes: 20 additions & 0 deletions lib/src/ffi/win/termlib_win.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,26 @@ class TermLibWindows implements TermLib {
late final HANDLE inputHandle;
late final HANDLE outputHandle;

int? _readWindowSize(int Function(CONSOLE_SCREEN_BUFFER_INFO info) value) {
final pBufferInfo = calloc<CONSOLE_SCREEN_BUFFER_INFO>();
try {
if (!GetConsoleScreenBufferInfo(outputHandle, pBufferInfo).value) {
return null;
}
return value(pBufferInfo.ref);
} finally {
calloc.free(pBufferInfo);
}
}

@override
int? get windowHeight =>
_readWindowSize((info) => info.srWindow.Bottom - info.srWindow.Top + 1);

@override
int? get windowWidth =>
_readWindowSize((info) => info.srWindow.Right - info.srWindow.Left + 1);

@override
int setWindowHeight(int height) {
throw UnsupportedError(
Expand Down