Compare commits

..

15 Commits

Author SHA1 Message Date
Kovid Goyal
ec2cc29de7 Dont expose method used only for testing to wider code 2026-06-19 07:28:52 +05:30
Kovid Goyal
38dae45e07 Bump python for CVE 2026-06-18 19:28:24 +05:30
Kovid Goyal
9112d779d0 Update changelog 2026-06-18 19:17:50 +05:30
Kovid Goyal
424d6f1ca4 Merge branch 'copilot/add-support-for-alternate-extents' of https://github.com/kovidgoyal/kitty
Fixes #10165
2026-06-18 19:16:43 +05:30
copilot-swe-agent[bot]
0901fcbc21 Fix description of alternate/alternate_scrollback extents 2026-06-18 11:12:56 +00:00
copilot-swe-agent[bot]
6b2bb97cb5 Add alternate and alternate_scrollback extents to get-text remote control command 2026-06-18 08:25:39 +00:00
Kovid Goyal
285c576ddd Merge branch 'fix-wayland-start-drag-silently-ignored' of https://github.com/ttys3/kitty 2026-06-18 10:37:36 +05:30
Kovid Goyal
e4e1ef0641 ... 2026-06-17 06:23:22 +05:30
Kovid Goyal
bdfcfc4fc2 Another stab at #10152 2026-06-16 05:43:25 +05:30
Kovid Goyal
1dc3730782 macOS: handle the OS calling insertText: with a nil string
Fixes #10152
2026-06-15 13:36:18 +05:30
Kovid Goyal
65e6a127ff Merge branch 'dependabot/github_actions/actions-882fedbe01' of https://github.com/kovidgoyal/kitty 2026-06-15 09:20:28 +05:30
Kovid Goyal
3c648e42f6 Merge branch 'dependabot/go_modules/all-go-deps-d54a6f1562' of https://github.com/kovidgoyal/kitty 2026-06-15 09:20:08 +05:30
dependabot[bot]
9b218001a1 Bump the actions group with 2 updates
Bumps the actions group with 2 updates: [actions/checkout](https://github.com/actions/checkout) and [github/codeql-action](https://github.com/github/codeql-action).


Updates `actions/checkout` from 6.0.2 to 6.0.3
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v6.0.2...v6.0.3)

Updates `github/codeql-action` from 4.36.0 to 4.36.2
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v4.36.0...v4.36.2)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 6.0.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions
- dependency-name: github/codeql-action
  dependency-version: 4.36.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-15 03:45:12 +00:00
dependabot[bot]
b43ca8fe31 Bump golang.org/x/sys from 0.45.0 to 0.46.0 in the all-go-deps group
Bumps the all-go-deps group with 1 update: [golang.org/x/sys](https://github.com/golang/sys).


Updates `golang.org/x/sys` from 0.45.0 to 0.46.0
- [Commits](https://github.com/golang/sys/compare/v0.45.0...v0.46.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-version: 0.46.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all-go-deps
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-15 03:42:57 +00:00
ttyS3
98366076e1 fix(wayland): detect start_drag silently ignored by the compositor
The pointer_button_count check added in dc36e2165 uses the client side
view of the implicit grab, which is stale when the button release is
still in flight: glfwStartDrag passes the check, but by the time the
compositor processes wl_data_device.start_drag the implicit grab is
gone and the request is silently dropped. The data source then never
receives any event, so the tab drag state leaks forever, hijacking
mouse handling again, and the already created xdg_toplevel_drag
toplevel gets mapped as a stray undecorated window showing the drag
thumbnail that nothing ever destroys.

Detect this deterministically using protocol ordering: issue a
wl_display.sync right after start_drag. An accepted start_drag
synchronously produces events (wl_pointer.leave from the DND grab
taking over, wl_data_device.enter, wl_data_source events) that are
ordered before the sync callback. If the callback fires with none of
them seen, the request was dropped: cancel the drag, which notifies
the application (clearing the tab drag state) and destroys the drag
toplevel and data source.

Also defer mapping the drag toplevel until the session is confirmed,
so the stray window can never appear, not even for one frame.

Verified against headless GNOME Shell 50.2 (mutter) with injected
pointer timing scans: the unpatched build deadlocks with an orphaned
drag toplevel within ~13 fast flicks, the patched build catches every
race hit and cancels cleanly, with no deadlock, no stray window, and
normal slow drag/reorder/detach behaviour preserved.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-12 14:16:22 +08:00
14 changed files with 135 additions and 24 deletions

View File

@@ -45,7 +45,7 @@ jobs:
steps:
- name: Checkout source code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v6.0.3
with:
fetch-depth: 10
persist-credentials: false
@@ -73,7 +73,7 @@ jobs:
CFLAGS: -funsigned-char
steps:
- name: Checkout source code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v6.0.3
with:
fetch-depth: 0 # needed for :commit: docs role
persist-credentials: false
@@ -145,7 +145,7 @@ jobs:
KITTY_BUNDLE: 1
steps:
- name: Checkout source code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v6.0.3
with:
fetch-depth: 10
persist-credentials: false
@@ -166,7 +166,7 @@ jobs:
runs-on: macos-latest
steps:
- name: Checkout source code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v6.0.3
with:
fetch-depth: 0 # needed for :commit: docs role
persist-credentials: false
@@ -204,7 +204,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout source code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v6.0.3
with:
fetch-depth: 10
persist-credentials: false

View File

@@ -39,7 +39,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v6.0.2
uses: actions/checkout@v6.0.3
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
@@ -54,7 +54,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v4.36.0
uses: github/codeql-action/init@v4.36.2
with:
languages: ${{ matrix.language }}
trap-caching: false
@@ -64,7 +64,7 @@ jobs:
run: python3 .github/workflows/ci.py build
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4.36.0
uses: github/codeql-action/analyze@v4.36.2
- name: Run govulncheck
if: matrix.language == 'go'

View File

@@ -22,13 +22,13 @@ jobs:
KITTY_BUNDLE: 1
steps:
- name: Checkout source code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v6.0.3
with:
fetch-depth: 10
persist-credentials: false
- name: Checkout bypy
uses: actions/checkout@v6.0.2
uses: actions/checkout@v6.0.3
with:
fetch-depth: 1
persist-credentials: false

View File

@@ -145,10 +145,10 @@
},
{
"name": "python 3.14.5",
"name": "python 3.14.6",
"unix": {
"file_extension": "tar.xz",
"hash": "sha256:7e32597b99e5d9a39abed35de4693fa169df3e5850d4c334337ffd6a19a36db6",
"hash": "sha256:143b1dddefaec3bd2e21e3b839b34a2b7fb9842272883c576420d605e9f30c63",
"urls": ["https://www.python.org/ftp/python/{version}/Python-{version}.{file_extension}"]
}
},

View File

@@ -173,6 +173,11 @@ consumption to do the same tasks.
Detailed list of changes
-------------------------------------
0.50.0 [future]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- kitten @ get-text: Add support for :code:`alternate` and :code:`alternate_scrollback` extents to fetch text from the alternate screen buffer (:iss:`10165`)
0.47.4 [2026-06-15]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@@ -96,7 +96,8 @@ polymorphic_string_as_utf8(id string) {
characters = [string string];
else
characters = (NSString*) string;
return [characters UTF8String];
const char* ans = [characters UTF8String];
return ans ? ans : "(nil)";
}
static bool
@@ -2023,6 +2024,10 @@ void _glfwPlatformUpdateIMEState(_GLFWwindow *w, const GLFWIMEUpdateEvent *ev) {
- (void)insertText:(id)string replacementRange:(NSRange)replacementRange
{
if (!string) {
debug_input("\n\tinsertText: nil replacementRange: (%lu, %lu)\n", replacementRange.location, replacementRange.length);
return;
}
const char *utf8 = polymorphic_string_as_utf8(string);
debug_input("\n\tinsertText: %s replacementRange: (%lu, %lu)\n", utf8, replacementRange.location, replacementRange.length);
if ([self hasMarkedText] && !is_ascii_control_char(utf8[0])) {

3
glfw/wl_init.c vendored
View File

@@ -107,6 +107,9 @@ pointerHandleLeave(void* data UNUSED, struct wl_pointer* pointer UNUSED, uint32_
// over the pointer for drag-and-drop). The matching button releases will
// never be delivered to us.
_glfw.wl.pointer_button_count = 0;
// A DND grab taking over the pointer sends a leave, making this the
// earliest proof that a just requested start_drag was accepted.
_glfwWaylandConfirmDragSession();
_GLFWwindow* window = _glfw.wl.pointerFocus;
if (!window) return;
_glfw.wl.serial = serial;

15
glfw/wl_platform.h vendored
View File

@@ -434,6 +434,20 @@ typedef struct _GLFWlibraryWayland
struct xdg_surface *toplevel_xdg_surface;
struct xdg_toplevel *toplevel_xdg_toplevel;
struct wl_buffer *toplevel_buffer;
// wl_data_device.start_drag is silently ignored by compositors when
// its serial does not match an active pointer implicit grab, which
// can happen as drags are started asynchronously and the client side
// view of the grab can be stale. A wl_display.sync issued right after
// start_drag detects this: any compositor event proving the DND
// session is live (wl_pointer.leave, wl_data_device.enter, any
// wl_data_source event) is ordered before the sync callback, so if
// the callback fires first the start_drag was dropped.
struct wl_callback *start_confirmation;
bool session_confirmed;
// The drag toplevel was configured before the session was confirmed,
// mapping it was deferred so it cannot end up as a stray regular
// window if start_drag was silently ignored.
bool toplevel_map_deferred;
struct {
const char *mime_type;
int fd;
@@ -487,6 +501,7 @@ void animateCursorImage(id_type timer_id, void *data);
struct wl_cursor* _glfwLoadCursor(GLFWCursorShape, struct wl_cursor_theme*);
void destroy_data_offer(_GLFWWaylandDataOffer*);
const char* _glfwWaylandCompositorName(void);
void _glfwWaylandConfirmDragSession(void);
typedef struct wayland_cursor_shape {
int which; const char *name;

78
glfw/wl_window.c vendored
View File

@@ -2561,6 +2561,8 @@ update_drop_source_actions(_GLFWwindow *window, _GLFWWaylandDataOffer *offer) {
static void
drag_enter(void *data UNUSED, struct wl_data_device *wl_data_device UNUSED, uint32_t serial, struct wl_surface *surface, wl_fixed_t x, wl_fixed_t y, struct wl_data_offer *id) {
debug_input("Drop entered\n");
// an enter for a drag we started means the DND session is live
_glfwWaylandConfirmDragSession();
_GLFWWaylandDataOffer *offer = &_glfw.wl.drop_data_offer;
mark_data_offer(offer, id);
if (!offer->id) return;
@@ -3164,9 +3166,7 @@ GLFWAPI bool glfwWaylandBeep(GLFWwindow *handle) {
// Drag source {{{
static void
drag_toplevel_xdg_surface_configure(void *data UNUSED, struct xdg_surface *surface, uint32_t serial) {
debug_input("Drag toplevel surface configured\n");
xdg_surface_ack_configure(surface, serial);
map_drag_toplevel(void) {
struct wl_buffer *buf = _glfw.wl.drag.toplevel_buffer;
if (buf) {
wl_surface_attach(_glfw.wl.drag.drag_icon, buf, 0, 0);
@@ -3178,6 +3178,23 @@ drag_toplevel_xdg_surface_configure(void *data UNUSED, struct xdg_surface *surfa
if (buf) wl_buffer_destroy(buf);
}
static void
drag_toplevel_xdg_surface_configure(void *data UNUSED, struct xdg_surface *surface, uint32_t serial) {
debug_input("Drag toplevel surface configured\n");
xdg_surface_ack_configure(surface, serial);
if (_glfw.wl.drag.start_confirmation && !_glfw.wl.drag.session_confirmed) {
// The compositor has not yet proven that it accepted start_drag. If
// it silently ignored it (stale implicit grab serial), committing a
// buffer now would map the toplevel as a stray regular window that
// nothing ever destroys. Defer mapping until the session is
// confirmed; if it never is, the toplevel is destroyed unmapped.
debug_input("Deferring drag toplevel map until drag session is confirmed\n");
_glfw.wl.drag.toplevel_map_deferred = true;
return;
}
map_drag_toplevel();
}
static const struct xdg_surface_listener drag_toplevel_xdg_surface_listener = {
.configure = drag_toplevel_xdg_surface_configure,
};
@@ -3213,6 +3230,46 @@ cancel_drag2(GLFWDragEventType type, bool maybe_a_cancel) {
static void cancel_drag(GLFWDragEventType type) { cancel_drag2(type, false); }
void
_glfwWaylandConfirmDragSession(void) {
// Called on receipt of any compositor event that can only happen when a
// DND session is actually live: the start_drag was not silently dropped.
if (!_glfw.wl.drag.source || _glfw.wl.drag.session_confirmed) return;
_glfw.wl.drag.session_confirmed = true;
debug_input("Drag session confirmed as started by compositor\n");
if (_glfw.wl.drag.toplevel_map_deferred) {
_glfw.wl.drag.toplevel_map_deferred = false;
map_drag_toplevel();
}
}
static void
drag_start_confirmation_handle_done(void *data UNUSED, struct wl_callback *callback, uint32_t cb_data UNUSED) {
if (callback != _glfw.wl.drag.start_confirmation) {
// stale callback from a drag that was already cleaned up
wl_callback_destroy(callback);
return;
}
_glfw.wl.drag.start_confirmation = NULL;
wl_callback_destroy(callback);
if (!_glfw.wl.drag.session_confirmed) {
// The compositor processed start_drag before this sync callback, and
// an accepted start_drag synchronously produces events (pointer
// leave, data device enter, data source events) that are ordered
// before it. None arrived, so the compositor silently ignored
// start_drag (no active implicit grab matching the serial). Without
// this, the data source would never receive any event, leaking the
// drag state forever and orphaning the drag toplevel as a stray
// window.
_glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: start_drag was silently ignored by the compositor, cancelling drag");
cancel_drag(GLFW_DRAG_CANCELLED);
}
}
static const struct wl_callback_listener drag_start_confirmation_listener = {
.done = drag_start_confirmation_handle_done,
};
#define dr _glfw.wl.drag.data_requests[i]
static void
@@ -3387,6 +3444,7 @@ drag_source_send(void *data UNUSED, struct wl_data_source *source UNUSED, const
static void
drag_source_target(void *data UNUSED, struct wl_data_source *source UNUSED, const char *mime_type) {
debug_input("Drag source accepted MIME type: %s\n", mime_type);
_glfwWaylandConfirmDragSession();
_GLFWwindow *window = _glfwWindowForId(_glfw.drag.window_id);
if (window) {
GLFWDragEvent ev = {.type=GLFW_DRAG_ACCEPTED, .mime_type=mime_type};
@@ -3397,6 +3455,7 @@ drag_source_target(void *data UNUSED, struct wl_data_source *source UNUSED, cons
static void
drag_source_action(void *data UNUSED, struct wl_data_source *source UNUSED, uint32_t dnd_action) {
debug_input("Drag source action changed: %d\n", dnd_action);
_glfwWaylandConfirmDragSession();
_GLFWwindow *window = _glfwWindowForId(_glfw.drag.window_id);
if (window) {
GLFWDragOperationType op = GLFW_DRAG_OPERATION_GENERIC;
@@ -3414,6 +3473,7 @@ drag_source_action(void *data UNUSED, struct wl_data_source *source UNUSED, uint
static void
drag_source_dnd_drop_performed(void *data UNUSED, struct wl_data_source *source UNUSED) {
debug_input("Drag source drop performed\n");
_glfwWaylandConfirmDragSession();
_GLFWwindow *window = _glfwWindowForId(_glfw.drag.window_id);
if (window) {
GLFWDragEvent ev = {.type=GLFW_DRAG_DROPPED};
@@ -3465,6 +3525,7 @@ _glfwPlatformCancelDrag(_GLFWwindow* window UNUSED) {
void
_glfwPlatformFreeDragSourceData(void) {
if (_glfw.wl.drag.start_confirmation) wl_callback_destroy(_glfw.wl.drag.start_confirmation);
if (_glfw.wl.drag.drag_viewport) wp_viewport_destroy(_glfw.wl.drag.drag_viewport);
if (_glfw.wl.drag.toplevel_drag) xdg_toplevel_drag_v1_destroy(_glfw.wl.drag.toplevel_drag);
if (_glfw.wl.drag.toplevel_buffer) wl_buffer_destroy(_glfw.wl.drag.toplevel_buffer);
@@ -3594,6 +3655,17 @@ _glfwPlatformStartDrag(_GLFWwindow* window, const GLFWimage* thumbnail) {
if (icon_buffer) wl_buffer_destroy(icon_buffer);
// The pointer_button_count check above uses the client side view of the
// implicit grab, which can be stale: the button may already be released
// with the release event still in flight, in which case the compositor
// silently ignores start_drag and the data source never receives any
// event. Detect that with a sync: an accepted start_drag synchronously
// produces events ordered before the sync callback.
_glfw.wl.drag.session_confirmed = false;
_glfw.wl.drag.start_confirmation = wl_display_sync(_glfw.wl.display);
if (_glfw.wl.drag.start_confirmation)
wl_callback_add_listener(_glfw.wl.drag.start_confirmation, &drag_start_confirmation_listener, NULL);
return 0;
}
// }}}

2
go.mod
View File

@@ -27,7 +27,7 @@ require (
github.com/zeebo/xxh3 v1.1.0
golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b
golang.org/x/image v0.42.0
golang.org/x/sys v0.45.0
golang.org/x/sys v0.46.0
golang.org/x/text v0.38.0
howett.net/plist v1.0.1
)

4
go.sum
View File

@@ -80,8 +80,8 @@ golang.org/x/image v0.42.0 h1:1gSs6ehNWXLbkHBIPcWztk3D/6aIA/8hauiAYtlodVY=
golang.org/x/image v0.42.0/go.mod h1:rrpelvGFt+kLPAjPM4HeWPgrl0FtafueU//e5N0qk/Q=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@@ -732,7 +732,7 @@ end:
return data;
}
size_t
static size_t
disk_cache_clear_from_ram(PyObject *self_, bool(matches)(void*, void *key, unsigned keysz), void *data) {
DiskCache *self = (DiskCache*)self_;
size_t ans = 0;

View File

@@ -16,7 +16,6 @@ PyObject* read_from_disk_cache_python(PyObject *self_, const void *key, size_t k
bool disk_cache_wait_for_write(PyObject *self, monotonic_t timeout);
size_t disk_cache_total_size(PyObject *self);
size_t disk_cache_size_on_disk(PyObject *self);
size_t disk_cache_clear_from_ram(PyObject *self_, bool(matches)(void* data, void *key, unsigned keysz), void*);
size_t disk_cache_num_cached_in_ram(PyObject *self_);
static inline void* disk_cache_malloc_allocator(void *x, size_t sz) {

View File

@@ -14,9 +14,10 @@ class GetText(RemoteCommand):
protocol_spec = __doc__ = '''
match/str: The window to get text from
extent/choices.screen.first_cmd_output_on_screen.last_cmd_output.last_visited_cmd_output.all.selection: \
extent/choices.screen.first_cmd_output_on_screen.last_cmd_output.last_visited_cmd_output.all.selection.alternate.alternate_scrollback: \
One of :code:`screen`, :code:`first_cmd_output_on_screen`, :code:`last_cmd_output`, \
:code:`last_visited_cmd_output`, :code:`all`, or :code:`selection`
:code:`last_visited_cmd_output`, :code:`all`, :code:`selection`, :code:`alternate`, \
or :code:`alternate_scrollback`
ansi/bool: Boolean, if True send ANSI formatting codes
cursor/bool: Boolean, if True send cursor position/style as ANSI codes
wrap_markers/bool: Boolean, if True add wrap markers to output
@@ -28,7 +29,7 @@ class GetText(RemoteCommand):
options_spec = MATCH_WINDOW_OPTION + '''\n
--extent
default=screen
choices=screen, all, selection, first_cmd_output_on_screen, last_cmd_output, last_visited_cmd_output, last_non_empty_output
choices=screen, all, selection, first_cmd_output_on_screen, last_cmd_output, last_visited_cmd_output, last_non_empty_output, alternate, alternate_scrollback
What text to get. The default of :code:`screen` means all text currently on the screen.
:code:`all` means all the screen+scrollback and :code:`selection` means the
currently selected text. :code:`first_cmd_output_on_screen` means the output of the first
@@ -37,6 +38,9 @@ the output of the last command that was run in the window. :code:`last_visited_c
the first command output below the last scrolled position via scroll_to_prompt.
:code:`last_non_empty_output` is the output from the last command run in the window that had
some non empty output. The last four require :ref:`shell_integration` to be enabled.
:code:`alternate` means the text in the screen that is not currently visible (if the terminal
is in the main screen this is the secondary/alternate screen and vice versa).
:code:`alternate_scrollback` is the same but also includes the scrollback buffer (if any).
--ansi
@@ -112,6 +116,14 @@ Get text from the window this command is run in, rather than the active window.
as_ansi=bool(payload_get('ansi')),
add_wrap_markers=bool(payload_get('wrap_markers')),
)
elif payload_get('extent') in ('alternate', 'alternate_scrollback'):
ans = window.as_text(
as_ansi=bool(payload_get('ansi')),
add_history=payload_get('extent') == 'alternate_scrollback',
add_cursor=bool(payload_get('cursor')),
add_wrap_markers=bool(payload_get('wrap_markers')),
alternate_screen=True,
)
else:
ans = window.as_text(
as_ansi=bool(payload_get('ansi')),