diff -pruN 4.0.2-0.4/.github/workflows/cmake.yml 4.1.0-1/.github/workflows/cmake.yml
--- 4.0.2-0.4/.github/workflows/cmake.yml	2022-02-18 06:07:59.000000000 +0000
+++ 4.1.0-1/.github/workflows/cmake.yml	2025-07-02 07:37:01.000000000 +0000
@@ -6,35 +6,57 @@ on:
   pull_request:
     branches: [ develop ]
 
-env:
-  # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
-  BUILD_TYPE: RelWithDebInfo
-
 jobs:
   build:
-    # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac.
-    # You can convert this to a matrix build if you need cross-platform coverage.
-    # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix
-    runs-on: ubuntu-latest
+    name: ${{ matrix.config.name }}
+    runs-on: ${{ matrix.config.os }}
+    strategy:
+      fail-fast: false
+      matrix:
+        config:
+        - {
+            name: "Ubuntu",
+            os: ubuntu-latest,
+            build_type: RelWithDebInfo,
+            build_testing: "On",
+            gtk_tsm: "On"
+          }
+        - {
+            name: "macOS",
+            os: macos-latest,
+            build_type: RelWithDebInfo,
+            build_testing: "Off",
+            gtk_tsm: "Off"
+          }
 
     steps:
     - uses: actions/checkout@v2
 
-    - name: Install depenencies
+    - name: Print env
+      run: |
+        echo github.event.action: ${{ github.event.action }}
+        echo github.event_name: ${{ github.event_name }}
+
+    - name: Install dependencies on Ubuntu
+      if: startsWith(matrix.config.name, 'Ubuntu')
       run: sudo apt-get install -y check valgrind libgtk-3-dev libpango1.0-dev pkg-config
 
+    - name: Install dependencies on macOS
+      if: startsWith(matrix.config.name, 'macOS')
+      run: brew install check
+
     - name: Configure CMake
       # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.
       # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type
-      run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DBUILD_TESTING=On -DBUILD_GTKTSM=On
+      run: cmake -B ./build -DCMAKE_BUILD_TYPE=${{ matrix.config.build_type }} -DBUILD_TESTING=${{ matrix.config.build_testing}} -DBUILD_GTKTSM=${{ matrix.config.gtk_tsm }}
 
     - name: Build
       # Build your program with the given configuration
-      run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}
+      run: cmake --build ./build --config ${{ matrix.config.build_type }}
 
     - name: Test
-      working-directory: ${{github.workspace}}/build
+      working-directory: ./build
       # Execute tests defined by the CMake configuration.  
       # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail
-      run: ctest -C ${{env.BUILD_TYPE}}
+      run: ctest -C ${{ matrix.config.build_type }}
 
diff -pruN 4.0.2-0.4/CMakeLists.txt 4.1.0-1/CMakeLists.txt
--- 4.0.2-0.4/CMakeLists.txt	2022-02-18 06:07:59.000000000 +0000
+++ 4.1.0-1/CMakeLists.txt	2025-07-02 07:37:01.000000000 +0000
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.5)
 
 project(libtsm
     LANGUAGES C
-    VERSION 4.0.2
+    VERSION 4.1.0
 )
 
 # Some meta data
@@ -21,6 +21,7 @@ endif()
 
 # Include utilities
 include(cmake/Utilities.cmake)
+include(cmake/JoinPaths.cmake)
 # For feature_summary
 include(FeatureSummary)
 
@@ -142,6 +143,8 @@ endif(BUILD_TESTING)
 # Installation of other files
 #---------------------------------------------------------------------------------------
 # pkgconfig file for backward compatibility
+join_paths(libdir_for_pc_file "\${exec_prefix}" "${CMAKE_INSTALL_LIBDIR}")
+join_paths(includedir_for_pc_file "\${prefix}" "${CMAKE_INSTALL_INCLUDEDIR}")
 configure_file(etc/libtsm.pc.in ${CMAKE_CURRENT_BINARY_DIR}/libtsm.pc @ONLY)
 install(FILES ${CMAKE_CURRENT_BINARY_DIR}/libtsm.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
 
diff -pruN 4.0.2-0.4/NEWS 4.1.0-1/NEWS
--- 4.0.2-0.4/NEWS	2022-02-18 06:07:59.000000000 +0000
+++ 4.1.0-1/NEWS	2025-07-02 07:37:01.000000000 +0000
@@ -1,4 +1,18 @@
 = libtsm Release News =
 
-CHANGES WITH 1:
-	* TODO
+CHANGES WITH 4.1.0:
+	* New features!
+		- Make backspace key configurable (#23)
+		- Add mouse support (#29)
+		- Add getters for scrollback buffer line count and position (#32)
+		- Add new VGA palette, same as kernel VT (#38)
+		- Add ECMA-48 CSI sequences `\E[nE` and `\E[nF` (#39)
+
+	* Bug fixes
+		- Rewrite of tsm_screen_selection_copy (#36)
+		- Fix a memleak in tsm_screen_unref (#35)
+		- Check for nullptr in tsm_vte_get_flags (#31)
+		- Fix DECRQM SRM request (#30)
+		- Fix build on macOS (#24)
+		- Fix wrong background color of new cells after resize (#21)
+		- Fix path in pkg-config file
\ No newline at end of file
diff -pruN 4.0.2-0.4/cmake/CompileOptions.cmake 4.1.0-1/cmake/CompileOptions.cmake
--- 4.0.2-0.4/cmake/CompileOptions.cmake	2022-02-18 06:07:59.000000000 +0000
+++ 4.1.0-1/cmake/CompileOptions.cmake	2025-07-02 07:37:01.000000000 +0000
@@ -10,6 +10,12 @@ if(NOT WIN32)
     add_definitions(-D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE)
 endif(NOT WIN32)
 
+if(APPLE)
+    add_compile_definitions(_DARWIN_C_SOURCE)
+    link_directories("/usr/local/lib")
+    include_directories("external")
+endif()
+
 # Set compiler flags for warnings
 if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")
     # using Clang or AppleClang
@@ -75,7 +81,7 @@ function(add_libtsm_compile_options targ
     # Linker flags
     ## Make the linker discard all unused symbols.
     if(APPLE)
-        set(LDFLAGS "-Wl,-dead_strip -Wl,-dead_strip_dylibs -Wl,-bind_at_load")
+        set(LDFLAGS "-Wl,-dead_strip -Wl,-dead_strip_dylibs -Wl")
     elseif(UNIX)
         set(LDFLAGS "-Wl,--as-needed -Wl,--gc-sections -Wl,-z,relro -Wl,-z,now")
     else()
diff -pruN 4.0.2-0.4/cmake/JoinPaths.cmake 4.1.0-1/cmake/JoinPaths.cmake
--- 4.0.2-0.4/cmake/JoinPaths.cmake	1970-01-01 00:00:00.000000000 +0000
+++ 4.1.0-1/cmake/JoinPaths.cmake	2025-07-02 07:37:01.000000000 +0000
@@ -0,0 +1,23 @@
+# This module provides function for joining paths
+# known from most languages
+#
+# SPDX-License-Identifier: (MIT OR CC0-1.0)
+# Copyright 2020 Jan Tojnar
+# https://github.com/jtojnar/cmake-snips
+#
+# Modelled after Python’s os.path.join
+# https://docs.python.org/3.7/library/os.path.html#os.path.join
+# Windows not supported
+function(join_paths joined_path first_path_segment)
+    set(temp_path "${first_path_segment}")
+    foreach(current_segment IN LISTS ARGN)
+        if(NOT ("${current_segment}" STREQUAL ""))
+            if(IS_ABSOLUTE "${current_segment}")
+                set(temp_path "${current_segment}")
+            else()
+                set(temp_path "${temp_path}/${current_segment}")
+            endif()
+        endif()
+    endforeach()
+    set(${joined_path} "${temp_path}" PARENT_SCOPE)
+endfunction()
diff -pruN 4.0.2-0.4/debian/changelog 4.1.0-1/debian/changelog
--- 4.0.2-0.4/debian/changelog	2023-05-09 15:47:55.000000000 +0000
+++ 4.1.0-1/debian/changelog	2025-09-27 23:14:32.000000000 +0000
@@ -1,3 +1,14 @@
+libtsm (4.1.0-1) unstable; urgency=medium
+
+  * New upstream release.
+  * d/libtsm4.symbols: Update
+  * d/control:
+    - Update pkg-config to pkgconf in B-D.
+    - Update Standards-Version: 4.7.2 (no changes required).
+  * d/copyright: Update copyright year.
+
+ -- Nobuhiro Iwamatsu <iwamatsu@debian.org>  Sun, 28 Sep 2025 08:14:32 +0900
+
 libtsm (4.0.2-0.4) unstable; urgency=medium
 
   * Non-maintainer upload.
diff -pruN 4.0.2-0.4/debian/control 4.1.0-1/debian/control
--- 4.0.2-0.4/debian/control	2023-05-09 15:02:49.000000000 +0000
+++ 4.1.0-1/debian/control	2025-09-27 23:14:32.000000000 +0000
@@ -6,8 +6,8 @@ Build-Depends: check <!nocheck>,
                cmake,
                debhelper-compat (= 13),
                libxkbcommon-dev,
-               pkg-config,
-Standards-Version: 4.6.0
+               pkgconf,
+Standards-Version: 4.7.2
 Homepage: https://github.com/Aetf/libtsm
 Rules-Requires-Root: no
 
diff -pruN 4.0.2-0.4/debian/copyright 4.1.0-1/debian/copyright
--- 4.0.2-0.4/debian/copyright	2023-05-09 15:47:55.000000000 +0000
+++ 4.1.0-1/debian/copyright	2025-09-27 23:14:32.000000000 +0000
@@ -14,7 +14,7 @@ Copyright: 2016 Fredrik Fornwall <fredri
 License: Expat
 
 Files: debian/*
-Copyright: 2013 Nobuiro Iwamatsu <iwamatsu@debian.org>
+Copyright: 2013, 2025 Nobuiro Iwamatsu <iwamatsu@debian.org>
            2021-2022 Victor Westerhuis <victor@westerhu.is>
 License: Expat
 
diff -pruN 4.0.2-0.4/debian/libtsm4.symbols 4.1.0-1/debian/libtsm4.symbols
--- 4.0.2-0.4/debian/libtsm4.symbols	2023-05-09 15:02:49.000000000 +0000
+++ 4.1.0-1/debian/libtsm4.symbols	2025-09-27 23:14:32.000000000 +0000
@@ -1,6 +1,75 @@
 libtsm.so.4 libtsm4 #MINVER#
 * Build-Depends-Package: libtsm-dev
- (symver)LIBTSM_1 4.0.1
- (symver)LIBTSM_2 4.0.1
- (symver)LIBTSM_3 4.0.1
- (symver)LIBTSM_4 4.0.1
+ tsm_screen_clear_sb@Base 4.1.0
+ tsm_screen_delete_chars@Base 4.1.0
+ tsm_screen_delete_lines@Base 4.1.0
+ tsm_screen_draw@Base 4.1.0
+ tsm_screen_erase_chars@Base 4.1.0
+ tsm_screen_erase_current_line@Base 4.1.0
+ tsm_screen_erase_cursor@Base 4.1.0
+ tsm_screen_erase_cursor_to_end@Base 4.1.0
+ tsm_screen_erase_cursor_to_screen@Base 4.1.0
+ tsm_screen_erase_home_to_cursor@Base 4.1.0
+ tsm_screen_erase_screen@Base 4.1.0
+ tsm_screen_erase_screen_to_cursor@Base 4.1.0
+ tsm_screen_get_cursor_x@Base 4.1.0
+ tsm_screen_get_cursor_y@Base 4.1.0
+ tsm_screen_get_flags@Base 4.1.0
+ tsm_screen_get_height@Base 4.1.0
+ tsm_screen_get_width@Base 4.1.0
+ tsm_screen_insert_chars@Base 4.1.0
+ tsm_screen_insert_lines@Base 4.1.0
+ tsm_screen_move_down@Base 4.1.0
+ tsm_screen_move_left@Base 4.1.0
+ tsm_screen_move_line_end@Base 4.1.0
+ tsm_screen_move_line_home@Base 4.1.0
+ tsm_screen_move_right@Base 4.1.0
+ tsm_screen_move_to@Base 4.1.0
+ tsm_screen_move_up@Base 4.1.0
+ tsm_screen_new@Base 4.1.0
+ tsm_screen_newline@Base 4.1.0
+ tsm_screen_ref@Base 4.1.0
+ tsm_screen_reset@Base 4.1.0
+ tsm_screen_reset_all_tabstops@Base 4.1.0
+ tsm_screen_reset_flags@Base 4.1.0
+ tsm_screen_reset_tabstop@Base 4.1.0
+ tsm_screen_resize@Base 4.1.0
+ tsm_screen_sb_down@Base 4.1.0
+ tsm_screen_sb_page_down@Base 4.1.0
+ tsm_screen_sb_page_up@Base 4.1.0
+ tsm_screen_sb_reset@Base 4.1.0
+ tsm_screen_sb_up@Base 4.1.0
+ tsm_screen_scroll_down@Base 4.1.0
+ tsm_screen_scroll_up@Base 4.1.0
+ tsm_screen_selection_copy@Base 4.1.0
+ tsm_screen_selection_reset@Base 4.1.0
+ tsm_screen_selection_start@Base 4.1.0
+ tsm_screen_selection_target@Base 4.1.0
+ tsm_screen_set_def_attr@Base 4.1.0
+ tsm_screen_set_flags@Base 4.1.0
+ tsm_screen_set_margins@Base 4.1.0
+ tsm_screen_set_max_sb@Base 4.1.0
+ tsm_screen_set_tabstop@Base 4.1.0
+ tsm_screen_tab_left@Base 4.1.0
+ tsm_screen_tab_right@Base 4.1.0
+ tsm_screen_unref@Base 4.1.0
+ tsm_screen_write@Base 4.1.0
+ tsm_ucs4_get_width@Base 4.1.0
+ tsm_ucs4_to_utf8@Base 4.1.0
+ tsm_ucs4_to_utf8_alloc@Base 4.1.0
+ tsm_vte_get_def_attr@Base 4.1.0
+ tsm_vte_get_flags@Base 4.1.0
+ tsm_vte_get_mouse_event@Base 4.1.0
+ tsm_vte_get_mouse_mode@Base 4.1.0
+ tsm_vte_handle_keyboard@Base 4.1.0
+ tsm_vte_hard_reset@Base 4.1.0
+ tsm_vte_input@Base 4.1.0
+ tsm_vte_new@Base 4.1.0
+ tsm_vte_ref@Base 4.1.0
+ tsm_vte_reset@Base 4.1.0
+ tsm_vte_set_backspace_sends_delete@Base 4.1.0
+ tsm_vte_set_custom_palette@Base 4.1.0
+ tsm_vte_set_mouse_cb@Base 4.1.0
+ tsm_vte_set_osc_cb@Base 4.1.0
+ tsm_vte_set_palette@Base 4.1.0
+ tsm_vte_unref@Base 4.1.0
diff -pruN 4.0.2-0.4/etc/libtsm.pc.in 4.1.0-1/etc/libtsm.pc.in
--- 4.0.2-0.4/etc/libtsm.pc.in	2022-02-18 06:07:59.000000000 +0000
+++ 4.1.0-1/etc/libtsm.pc.in	2025-07-02 07:37:01.000000000 +0000
@@ -1,7 +1,7 @@
 prefix=@CMAKE_INSTALL_PREFIX@
 exec_prefix=${prefix}
-libdir=${exec_prefix}/@CMAKE_INSTALL_LIBDIR@
-includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@
+libdir=@libdir_for_pc_file@
+includedir=@includedir_for_pc_file@
 
 Name: @PROJECT_NAME@
 Description: @PROJECT_DESCRIPTION@
diff -pruN 4.0.2-0.4/src/shared/CMakeLists.txt 4.1.0-1/src/shared/CMakeLists.txt
--- 4.0.2-0.4/src/shared/CMakeLists.txt	2022-02-18 06:07:59.000000000 +0000
+++ 4.1.0-1/src/shared/CMakeLists.txt	2025-07-02 07:37:01.000000000 +0000
@@ -5,10 +5,12 @@
 #
 add_library(shl OBJECT
     shl-htable.c
-    shl-pty.c
-    shl-ring.c
 )
 
+if (BUILD_GTKTSM)
+    target_sources(shl PRIVATE shl-pty.c shl-ring.c)
+endif()
+
 target_include_directories(shl
     PUBLIC
         ${CMAKE_CURRENT_SOURCE_DIR}
diff -pruN 4.0.2-0.4/src/shared/shl-macro.h 4.1.0-1/src/shared/shl-macro.h
--- 4.0.2-0.4/src/shared/shl-macro.h	2022-02-18 06:07:59.000000000 +0000
+++ 4.1.0-1/src/shared/shl-macro.h	2025-07-02 07:37:01.000000000 +0000
@@ -20,6 +20,11 @@
 #include <string.h>
 #include <unistd.h>
 
+/* macOS compatibility */
+#if !defined static_assert
+#define static_assert _Static_assert
+#endif
+
 /* sanity checks required for some macros */
 #if __SIZEOF_POINTER__ != 4 && __SIZEOF_POINTER__ != 8
 #error "Pointer size is neither 4 nor 8 bytes"
diff -pruN 4.0.2-0.4/src/tsm/CMakeLists.txt 4.1.0-1/src/tsm/CMakeLists.txt
--- 4.0.2-0.4/src/tsm/CMakeLists.txt	2022-02-18 06:07:59.000000000 +0000
+++ 4.1.0-1/src/tsm/CMakeLists.txt	2025-07-02 07:37:01.000000000 +0000
@@ -62,6 +62,7 @@ add_library(tsm)
 target_link_object_libraries(tsm PRIVATE tsm_obj)
 apply_properties(tsm)
 # The production library additionally use version script
+if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
 set_property(TARGET tsm APPEND_STRING
     PROPERTY
         LINK_FLAGS " -Wl,--version-script=\"${CMAKE_CURRENT_SOURCE_DIR}/libtsm.sym\""
@@ -70,6 +71,7 @@ set_property(TARGET tsm APPEND
     PROPERTY
         LINK_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/libtsm.sym"
 )
+endif()
 
 # Add an alias so that library can be used inside the build tree
 add_library(libtsm::tsm ALIAS tsm)
diff -pruN 4.0.2-0.4/src/tsm/libtsm-int.h 4.1.0-1/src/tsm/libtsm-int.h
--- 4.0.2-0.4/src/tsm/libtsm-int.h	2022-02-18 06:07:59.000000000 +0000
+++ 4.1.0-1/src/tsm/libtsm-int.h	2025-07-02 07:37:01.000000000 +0000
@@ -114,6 +114,11 @@ struct tsm_screen {
 	/* default attributes for new cells */
 	struct tsm_screen_attr def_attr;
 
+	/* save default attributes of main screen here when we switch to alt screen
+	 * on resize of the alt screen we need to init the new cells of the main
+	 * screen with these attributes and not the ones of the alt screen */
+	struct tsm_screen_attr def_attr_main;
+
 	/* ageing */
 	tsm_age_t age_cnt;		/* current age counter */
 	unsigned int age_reset : 1;	/* age-overflow flag */
@@ -135,6 +140,7 @@ struct tsm_screen {
 	struct line *sb_last;		/* last line; was moved last*/
 	unsigned int sb_max;		/* max-limit of lines in sb */
 	struct line *sb_pos;		/* current position in sb or NULL */
+	unsigned int sb_pos_num;	/* current numeric position in sb */
 	uint64_t sb_last_id;		/* last id given to sb-line */
 
 	/* cursor: positions are always in-bound, but cursor_x might be
diff -pruN 4.0.2-0.4/src/tsm/libtsm.h 4.1.0-1/src/tsm/libtsm.h
--- 4.0.2-0.4/src/tsm/libtsm.h	2022-02-18 06:07:59.000000000 +0000
+++ 4.1.0-1/src/tsm/libtsm.h	2025-07-02 07:37:01.000000000 +0000
@@ -201,6 +201,8 @@ void tsm_screen_sb_down(struct tsm_scree
 void tsm_screen_sb_page_up(struct tsm_screen *con, unsigned int num);
 void tsm_screen_sb_page_down(struct tsm_screen *con, unsigned int num);
 void tsm_screen_sb_reset(struct tsm_screen *con);
+unsigned int tsm_screen_sb_get_line_count(struct tsm_screen *con);
+unsigned int tsm_screen_sb_get_line_pos(struct tsm_screen *con);
 
 void tsm_screen_set_def_attr(struct tsm_screen *con,
 			     const struct tsm_screen_attr *attr);
@@ -284,6 +286,26 @@ tsm_age_t tsm_screen_draw(struct tsm_scr
 
 struct tsm_vte;
 
+/* terminal flags */
+#define TSM_VTE_FLAG_CURSOR_KEY_MODE			0x00000001 /* DEC cursor key mode */
+#define TSM_VTE_FLAG_KEYPAD_APPLICATION_MODE		0x00000002 /* DEC keypad application mode; TODO: toggle on numlock? */
+#define TSM_VTE_FLAG_LINE_FEED_NEW_LINE_MODE		0x00000004 /* DEC line-feed/new-line mode */
+#define TSM_VTE_FLAG_8BIT_MODE				0x00000008 /* Disable UTF-8 mode and enable 8bit compatible mode */
+#define TSM_VTE_FLAG_7BIT_MODE				0x00000010 /* Disable 8bit mode and use 7bit compatible mode */
+#define TSM_VTE_FLAG_USE_C1				0x00000020 /* Explicitly use 8bit C1 codes; TODO: implement */
+#define TSM_VTE_FLAG_KEYBOARD_ACTION_MODE		0x00000040 /* Disable keyboard; TODO: implement? */
+#define TSM_VTE_FLAG_INSERT_REPLACE_MODE		0x00000080 /* Enable insert mode */
+#define TSM_VTE_FLAG_SEND_RECEIVE_MODE			0x00000100 /* Disable local echo */
+#define TSM_VTE_FLAG_TEXT_CURSOR_MODE			0x00000200 /* Show cursor */
+#define TSM_VTE_FLAG_INVERSE_SCREEN_MODE		0x00000400 /* Inverse colors */
+#define TSM_VTE_FLAG_ORIGIN_MODE			0x00000800 /* Relative origin for cursor */
+#define TSM_VTE_FLAG_AUTO_WRAP_MODE			0x00001000 /* Auto line wrap mode */
+#define TSM_VTE_FLAG_AUTO_REPEAT_MODE			0x00002000 /* Auto repeat key press; TODO: implement */
+#define TSM_VTE_FLAG_NATIONAL_CHARSET_MODE		0x00004000 /* Send keys from nation charsets; TODO: implement */
+#define TSM_VTE_FLAG_BACKGROUND_COLOR_ERASE_MODE	0x00008000 /* Set background color on erase (bce) */
+#define TSM_VTE_FLAG_PREPEND_ESCAPE			0x00010000 /* Prepend escape character to next output */
+#define TSM_VTE_FLAG_TITE_INHIBIT_MODE			0x00020000 /* Prevent switching to alternate screen buffer */
+
 /* keep in sync with shl_xkb_mods */
 enum tsm_vte_modifier {
 	TSM_SHIFT_MASK		= (1 << 0),
@@ -320,6 +342,62 @@ enum tsm_vte_color {
 	TSM_COLOR_NUM
 };
 
+/**
+ * Mouse Tracking
+ *
+ * Reference:
+ *
+ * https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
+ *
+ * The application running in the terminal can request a mouse tracking mode
+ * and it can configure the event type (send position on click only or on click
+ * and on mouse movement). The terminal will then send the position
+ * of the mouse cursor as requested by the application.
+ *
+ * Since libtsm doesn't know anything about the UI or the mouse this can only
+ * work if the terminal emulator built on top of libtsm cooperates.
+ *
+ * To implement mouse tracking a terminal emulator must first set a mouse
+ * callback with tsm_vte_set_mouse_cb. This callback will be called whenever the
+ * mouse mode changes in a way that is relevant to the terminal. It tells the
+ * terminal if it needs to pass mouse events on click, on move, or not at all.
+ *
+ * To pass the mouse events the terminal needs to call tsm_vte_handle_mouse with
+ * all parameters filled appropriately. The function will then take care of
+ * sending the mouse events to the application in the correct encoding and
+ * it will also discard events that are duplicate or unnecessary.
+ */
+
+/* control sequence codes sent be the application */
+#define TSM_VTE_MOUSE_MODE_X10      9 /* legacy mode (only cell mode, only on mouse click and x and y can be 223 max) */
+#define TSM_VTE_MOUSE_EVENT_BTN  1002 /* sends position on mouse click only */
+#define TSM_VTE_MOUSE_EVENT_ANY  1003 /* sends position on mouse click and mouse move */
+#define TSM_VTE_MOUSE_MODE_SGR   1006 /* modern mode that allows unlimited x and y coordinates */
+#define TSM_VTE_MOUSE_MODE_PIXEL 1016 /* sends pixel coordinates instead of cell coordinates */
+
+enum tsm_mouse_track_mode {
+	TSM_MOUSE_TRACK_DISABLE = 0, /* don't track mouse events */
+	TSM_MOUSE_TRACK_BTN = TSM_VTE_MOUSE_EVENT_BTN, /* call tsm_vte_handle_mouse only for mouse clicks */
+	TSM_MOUSE_TRACK_ANY = TSM_VTE_MOUSE_EVENT_ANY  /* call tsm_vte_handle_mouse for mouse clicks and mouse movement */
+};
+
+/* mouse buttons to be passed to tsm_vte_handle_mouse */
+#define TSM_MOUSE_BUTTON_LEFT       0
+#define TSM_MOUSE_BUTTON_MIDDLE     1
+#define TSM_MOUSE_BUTTON_RIGHT      2
+#define TSM_MOUSE_BUTTON_WHEEL_UP   4
+#define TSM_MOUSE_BUTTON_WHEEL_DOWN 5
+
+/* modifier keys to be passed to tsm_vte_handle_mouse (can be combined with OR) */
+#define TSM_MOUSE_MODIFIER_SHIFT  4
+#define TSM_MOUSE_MODIFIER_META   8
+#define TSM_MOUSE_MODIFIER_CTRL  16
+
+/* events to be passed to tsm_vte_handle_mouse */
+#define TSM_MOUSE_EVENT_PRESSED  1
+#define TSM_MOUSE_EVENT_RELEASED 2
+#define TSM_MOUSE_EVENT_MOVED    4
+
 typedef void (*tsm_vte_write_cb) (struct tsm_vte *vte,
 				  const char *u8,
 				  size_t len,
@@ -330,6 +408,11 @@ typedef void (*tsm_vte_osc_cb) (struct t
 				  size_t len,
 				  void *data);
 
+typedef void (*tsm_vte_mouse_cb) (struct tsm_vte *vte,
+				  enum tsm_mouse_track_mode track_mode,
+				  bool track_pixels,
+				  void *data);
+
 int tsm_vte_new(struct tsm_vte **out, struct tsm_screen *con,
 		tsm_vte_write_cb write_cb, void *data,
 		tsm_log_t log, void *log_data);
@@ -337,6 +420,7 @@ void tsm_vte_ref(struct tsm_vte *vte);
 void tsm_vte_unref(struct tsm_vte *vte);
 
 void tsm_vte_set_osc_cb(struct tsm_vte *vte, tsm_vte_osc_cb osc_cb, void *osc_data);
+void tsm_vte_set_mouse_cb(struct tsm_vte *vte, tsm_vte_mouse_cb mouse_cb, void *mouse_data);
 
 /**
  * @brief Set color palette to one of the predefined palette on the vte object.
@@ -405,13 +489,33 @@ int tsm_vte_set_palette(struct tsm_vte *
 int tsm_vte_set_custom_palette(struct tsm_vte *vte, uint8_t (*palette)[3]);
 
 void tsm_vte_get_def_attr(struct tsm_vte *vte, struct tsm_screen_attr *out);
+unsigned int tsm_vte_get_flags(struct tsm_vte *vte);
+
+unsigned int tsm_vte_get_mouse_mode(struct tsm_vte *vte);
+unsigned int tsm_vte_get_mouse_event(struct tsm_vte *vte);
 
 void tsm_vte_reset(struct tsm_vte *vte);
 void tsm_vte_hard_reset(struct tsm_vte *vte);
 void tsm_vte_input(struct tsm_vte *vte, const char *u8, size_t len);
+
+/**
+ * @brief Set backspace key to send either backspace or delete.
+ *
+ * Some terminals send ASCII backspace (010, 8, 0x08), some send ASCII delete
+ * (0177, 127, 0x7f).
+ *
+ * The default for vte is to send ASCII backspace.
+ *
+ * @param vte The vte object to set on
+ * @param enable Send ASCII delete if \c true, send ASCII backspace if \c false.
+ */
+void tsm_vte_set_backspace_sends_delete(struct tsm_vte *vte, bool enable);
 bool tsm_vte_handle_keyboard(struct tsm_vte *vte, uint32_t keysym,
 			     uint32_t ascii, unsigned int mods,
 			     uint32_t unicode);
+bool tsm_vte_handle_mouse(struct tsm_vte *vte, unsigned int cell_x,
+        unsigned int cell_y, unsigned int pixel_x, unsigned int pixel_y,
+        unsigned int button, unsigned int event, unsigned char flags);
 
 /** @} */
 
diff -pruN 4.0.2-0.4/src/tsm/libtsm.sym 4.1.0-1/src/tsm/libtsm.sym
--- 4.0.2-0.4/src/tsm/libtsm.sym	2022-02-18 06:07:59.000000000 +0000
+++ 4.1.0-1/src/tsm/libtsm.sym	2025-07-02 07:37:01.000000000 +0000
@@ -118,5 +118,17 @@ LIBTSM_4 {
 global:
 	tsm_screen_draw;
 
-    tsm_vte_set_custom_palette;
+	tsm_vte_set_custom_palette;
 } LIBTSM_3;
+
+LIBTSM_4_1 {
+global:
+	tsm_vte_set_backspace_sends_delete;
+	tsm_vte_get_flags;
+	tsm_vte_set_mouse_cb;
+	tsm_vte_get_mouse_mode;
+	tsm_vte_get_mouse_event;
+	tsm_vte_handle_mouse;
+	tsm_screen_sb_get_line_count;
+	tsm_screen_sb_get_line_pos;
+} LIBTSM_4;
diff -pruN 4.0.2-0.4/src/tsm/tsm-screen.c 4.1.0-1/src/tsm/tsm-screen.c
--- 4.0.2-0.4/src/tsm/tsm-screen.c	2022-02-18 06:07:59.000000000 +0000
+++ 4.1.0-1/src/tsm/tsm-screen.c	2025-07-02 07:37:01.000000000 +0000
@@ -110,12 +110,18 @@ static void move_cursor(struct tsm_scree
 	c->age = con->age_cnt;
 }
 
-void screen_cell_init(struct tsm_screen *con, struct cell *cell)
+void screen_cell_init_generic(struct tsm_screen *con, struct cell *cell, struct tsm_screen_attr *attr)
 {
 	cell->ch = 0;
 	cell->width = 1;
 	cell->age = con->age_cnt;
-	memcpy(&cell->attr, &con->def_attr, sizeof(cell->attr));
+
+	memcpy(&cell->attr, attr, sizeof(cell->attr));
+}
+
+void screen_cell_init(struct tsm_screen *con, struct cell *cell)
+{
+	screen_cell_init_generic(con, cell, &con->def_attr);
 }
 
 static int line_new(struct tsm_screen *con, struct line **out,
@@ -224,10 +230,13 @@ static void link_to_scrollback(struct ts
 		if (con->sb_pos) {
 			if (con->sb_pos == tmp ||
 			    !(con->flags & TSM_SCREEN_FIXED_POS)) {
-				if (con->sb_pos->next)
+				if (con->sb_pos->next) {
 					con->sb_pos = con->sb_pos->next;
-				else
+					++con->sb_pos_num;
+				} else {
 					con->sb_pos = line;
+					con->sb_pos_num = 0;
+				}
 			}
 		}
 
@@ -247,12 +256,17 @@ static void link_to_scrollback(struct ts
 	line->sb_id = ++con->sb_last_id;
 	line->next = NULL;
 	line->prev = con->sb_last;
-	if (con->sb_last)
+	if (con->sb_last) {
 		con->sb_last->next = line;
-	else
+	} else {
 		con->sb_first = line;
+	}
 	con->sb_last = line;
 	++con->sb_count;
+
+	if (con->sb_pos == NULL) {
+		con->sb_pos_num = con->sb_count;
+	}
 }
 
 static void screen_scroll_up(struct tsm_screen *con, unsigned int num)
@@ -532,10 +546,12 @@ void tsm_screen_unref(struct tsm_screen
 		line_free(con->main_lines[i]);
 		line_free(con->alt_lines[i]);
 	}
+
 	free(con->main_lines);
 	free(con->alt_lines);
 	free(con->tab_ruler);
 	tsm_symbol_table_unref(con->sym_table);
+	tsm_screen_clear_sb(con);
 	free(con);
 }
 
@@ -658,7 +674,6 @@ int tsm_screen_resize(struct tsm_screen
 			ret = line_resize(con, con->main_lines[i], x);
 			if (ret)
 				return ret;
-
 			ret = line_resize(con, con->alt_lines[i], x);
 			if (ret)
 				return ret;
@@ -678,7 +693,8 @@ int tsm_screen_resize(struct tsm_screen
 			i = start;
 
 		for ( ; i < con->main_lines[j]->size; ++i)
-			screen_cell_init(con, &con->main_lines[j]->cells[i]);
+			screen_cell_init_generic(con, &con->main_lines[j]->cells[i],
+				&con->def_attr_main);
 
 		/* alt-lines never go into SB, only clear visible cells */
 		i = 0;
@@ -824,6 +840,7 @@ void tsm_screen_clear_sb(struct tsm_scre
 	con->sb_last = NULL;
 	con->sb_count = 0;
 	con->sb_pos = NULL;
+	con->sb_pos_num = 0;
 
 	if (con->sel_active) {
 		if (con->sel_start.line) {
@@ -853,10 +870,12 @@ void tsm_screen_sb_up(struct tsm_screen
 				return;
 
 			con->sb_pos = con->sb_pos->prev;
+			--con->sb_pos_num;
 		} else if (!con->sb_last) {
 			return;
 		} else {
 			con->sb_pos = con->sb_last;
+			con->sb_pos_num = con->sb_count - 1;
 		}
 	}
 }
@@ -872,8 +891,10 @@ void tsm_screen_sb_down(struct tsm_scree
 	con->age = con->age_cnt;
 
 	while (num--) {
-		if (con->sb_pos)
+		if (con->sb_pos) {
 			con->sb_pos = con->sb_pos->next;
+			++con->sb_pos_num;
+		}
 		else
 			return;
 	}
@@ -910,6 +931,25 @@ void tsm_screen_sb_reset(struct tsm_scre
 	con->age = con->age_cnt;
 
 	con->sb_pos = NULL;
+	con->sb_pos_num = 0;
+}
+
+unsigned int tsm_screen_sb_get_line_count(struct tsm_screen *con)
+{
+	if (!con) {
+		return 0;
+	}
+
+	return con->sb_count;
+}
+
+unsigned int tsm_screen_sb_get_line_pos(struct tsm_screen *con)
+{
+	if (!con) {
+		return 0;
+	}
+
+	return con->sb_pos_num;
 }
 
 SHL_EXPORT
@@ -918,7 +958,6 @@ void tsm_screen_set_def_attr(struct tsm_
 {
 	if (!con || !attr)
 		return;
-
 	memcpy(&con->def_attr, attr, sizeof(*attr));
 }
 
@@ -963,6 +1002,9 @@ void tsm_screen_set_flags(struct tsm_scr
 	if (!(old & TSM_SCREEN_ALTERNATE) && (flags & TSM_SCREEN_ALTERNATE)) {
 		con->age = con->age_cnt;
 		con->lines = con->alt_lines;
+
+		/* save attributes of main screen when we switch to alt screen */
+		memcpy(&con->def_attr_main, &con->def_attr, sizeof(con->def_attr));
 	}
 
 	if (!(old & TSM_SCREEN_HIDE_CURSOR) &&
diff -pruN 4.0.2-0.4/src/tsm/tsm-selection.c 4.1.0-1/src/tsm/tsm-selection.c
--- 4.0.2-0.4/src/tsm/tsm-selection.c	2022-02-18 06:07:59.000000000 +0000
+++ 4.1.0-1/src/tsm/tsm-selection.c	2025-07-02 07:37:01.000000000 +0000
@@ -126,6 +126,21 @@ void tsm_screen_selection_target(struct
 	selection_set(con, &con->sel_end, posx, posy);
 }
 
+/* calculates the line length from the beginning to the last non zero character */
+static unsigned int calc_line_len(struct line *line)
+{
+	unsigned int line_len = 0;
+	int i;
+
+	for (i = 0; i < line->size; i++) {
+		if (line->cells[i].ch != 0) {
+			line_len = i + 1;
+		}
+	}
+
+	return line_len;
+}
+
 /* TODO: tsm_ucs4_to_utf8 expects UCS4 characters, but a cell contains a
  * tsm-symbol (which can contain multiple UCS4 chars). Fix this when introducing
  * support for combining characters. */
@@ -134,8 +149,19 @@ static unsigned int copy_line(struct lin
 {
 	unsigned int i, end;
 	char *pos = buf;
+	int line_len;
+
+	line_len = calc_line_len(line);
+	if (start > line_len) {
+		return 0;
+	}
 
 	end = start + len;
+
+	if (end > line_len) {
+		end = line_len;
+	}
+
 	for (i = start; i < line->size && i < end; ++i) {
 		if (i < line->size || !line->cells[i].ch)
 			pos += tsm_ucs4_to_utf8(line->cells[i].ch, pos);
@@ -143,215 +169,308 @@ static unsigned int copy_line(struct lin
 			pos += tsm_ucs4_to_utf8(' ', pos);
 	}
 
+	pos += tsm_ucs4_to_utf8('\n', pos);
+
 	return pos - buf;
 }
 
-/* TODO: This beast definitely needs some "beautification", however, it's meant
- * as a "proof-of-concept" so its enough for now. */
-SHL_EXPORT
-int tsm_screen_selection_copy(struct tsm_screen *con, char **out)
+static void swap_selections(struct selection_pos **a, struct selection_pos **b)
+{
+	struct selection_pos *c;
+
+	c  = *a;
+	*a = *b;
+	*b = c;
+}
+
+/*
+ * Normalize a selection
+ *
+ * Start must always point to the top left and end to the bottom right cell
+ */
+static void norm_selection(struct tsm_screen *con, struct selection_pos **start, struct selection_pos **end)
 {
-	unsigned int len, i;
-	struct selection_pos *start, *end;
 	struct line *iter;
-	char *str, *pos;
+	struct selection_pos *buffer;
 
-	if (!con || !out)
-		return -EINVAL;
+	if ((*end)->line == NULL && (*end)->y == SELECTION_TOP) {
+		swap_selections(start, end);
 
-	if (!con->sel_active)
-		return -ENOENT;
+		return;
+	}
 
-	/* check whether sel_start or sel_end comes first */
-	if (!con->sel_start.line && con->sel_start.y == SELECTION_TOP) {
-		if (!con->sel_end.line && con->sel_end.y == SELECTION_TOP) {
-			str = strdup("");
-			if (!str)
-				return -ENOMEM;
-			*out = str;
-			return 0;
-		}
-		start = &con->sel_start;
-		end = &con->sel_end;
-	} else if (!con->sel_end.line && con->sel_end.y == SELECTION_TOP) {
-		start = &con->sel_end;
-		end = &con->sel_start;
-	} else if (con->sel_start.line && con->sel_end.line) {
-		if (con->sel_start.line->sb_id < con->sel_end.line->sb_id) {
-			start = &con->sel_start;
-			end = &con->sel_end;
-		} else if (con->sel_start.line->sb_id > con->sel_end.line->sb_id) {
-			start = &con->sel_end;
-			end = &con->sel_start;
-		} else if (con->sel_start.x < con->sel_end.x) {
-			start = &con->sel_start;
-			end = &con->sel_end;
-		} else {
-			start = &con->sel_end;
-			end = &con->sel_start;
+	if ((*start)->line && (*end)->line) {
+		/* single line selection */
+		if ((*start)->line == (*end)->line) {
+			if ((*start)->x > (*end)->x) {
+				swap_selections(start, end);
+			}
+
+			return;
+		}
+
+		/*
+		 * multi line selection
+		 *
+		 * search from end->line to con->sb_last
+		 * if we find start->line on the way we
+		 * need to change start and end
+		*/
+		iter = (*end)->line;
+		while (iter && iter != con->sb_last) {
+			if (iter == (*start)->line) {
+				swap_selections(start, end);
+			}
+
+			iter = iter->next;
 		}
-	} else if (con->sel_start.line) {
-		start = &con->sel_start;
-		end = &con->sel_end;
-	} else if (con->sel_end.line) {
-		start = &con->sel_end;
-		end = &con->sel_start;
-	} else if (con->sel_start.y < con->sel_end.y) {
-		start = &con->sel_start;
-		end = &con->sel_end;
-	} else if (con->sel_start.y > con->sel_end.y) {
-		start = &con->sel_end;
-		end = &con->sel_start;
-	} else if (con->sel_start.x < con->sel_end.x) {
-		start = &con->sel_start;
-		end = &con->sel_end;
-	} else {
-		start = &con->sel_end;
-		end = &con->sel_start;
+
+		return;
 	}
 
-	/* calculate size of buffer */
-	len = 0;
-	iter = start->line;
-	if (!iter && start->y == SELECTION_TOP)
-		iter = con->sb_first;
+	/* end is in scroll back buffer and start on screen */
+	if (!(*start)->line && (*end)->line) {
+		swap_selections(start, end);
+		return;
+	}
+
+	/* reorder one-line selection if selection was created right to left */
+	if ((*start)->y == (*end)->y) {
+		if ((*start)->x > (*end)->x) {
+			swap_selections(start, end);
+		}
+
+		return;
+	}
+
+	/* reorder multi-line selection if selection was created bottom to top */
+	if ((*start)->y > (*end)->y) {
+		swap_selections(start, end);
+	}
+}
 
+/*
+ * Counts the lines a normalized selection selects on the scroll back buffer
+ *
+ * Does not count the lines selected on the screen
+ */
+static int selection_count_lines_sb(struct tsm_screen *con, struct selection_pos *start, struct selection_pos *end)
+{
+	struct line *iter;
+	int count = 0;
+
+	/* Single line selection */
+	if (start->line && (start->line == end->line)) {
+		return 1;
+	}
+
+	iter = start->line;
 	while (iter) {
-		if (iter == start->line && iter == end->line) {
-			if (iter->size > start->x) {
-				if (iter->size > end->x)
-					len += end->x - start->x + 1;
-				else
-					len += iter->size - start->x;
-			}
-			break;
-		} else if (iter == start->line) {
-			if (iter->size > start->x)
-				len += iter->size - start->x;
-		} else if (iter == end->line) {
-			if (iter->size > end->x)
-				len += end->x + 1;
-			else
-				len += iter->size;
+		count++;
+
+		if (iter == con->sb_last) {
 			break;
-		} else {
-			len += iter->size;
 		}
 
-		++len;
 		iter = iter->next;
 	}
 
-	if (!end->line) {
-		if (start->line || start->y == SELECTION_TOP)
-			i = 0;
-		else
-			i = start->y;
-		for ( ; i < con->size_y; ++i) {
-			if (!start->line && start->y == i && end->y == i) {
-				if (con->size_x > start->x) {
-					if (con->size_x > end->x)
-						len += end->x - start->x + 1;
-					else
-						len += con->size_x - start->x;
-				}
-				break;
-			} else if (!start->line && start->y == i) {
-				if (con->size_x > start->x)
-					len += con->size_x - start->x;
-			} else if (end->y == i) {
-				if (con->size_x > end->x)
-					len += end->x + 1;
-				else
-					len += con->size_x;
-				break;
-			} else {
-				len += con->size_x;
-			}
+	return count;
+}
+
+/*
+ * Counts the lines a normalized selection selects on the screen
+ *
+ * Does not count the lines selected in the scroll back buffer
+ */
+static int selection_count_lines(struct selection_pos *start, struct selection_pos *end)
+{
+	/* Selection only spans lines of the scroll back buffer */
+	if (start->line && end->line) {
+		return 0;
+	}
+
+	return end->y - start->y + 1;
+}
+
+/*
+ * Calculate the number of selected cells in a line
+ */
+static int calc_selection_line_len_sb(struct tsm_screen *con, struct selection_pos *start, struct selection_pos *end, struct line *line)
+{
+	/* one-line selection */
+	if (start->line == end->line) {
+		return end->x - start->x + 1;
+	}
+
+	/* first line of a multi-line selection */
+	if (line == start->line) {
+		return con->size_x - start->x;
+	}
 
-			++len;
+	/* last line of a multi-line selection */
+	if (line == end->line) {
+		return end->x + 1;
+	}
+
+	/* every other selection */
+	return con->size_x;
+}
+
+/*
+ * Calculate the number of selected cells in a line
+ */
+static int calc_selection_line_len(struct tsm_screen *con, struct selection_pos *start, struct selection_pos *end, int line_num)
+{
+	if (!start->line) {
+		/* one-line selection */
+		if (start->y == end->y) {
+			return end->x - start->x + 1;
+		}
+
+		/* first line of a multi-line selection */
+		if (line_num == start->y) {
+			return con->size_x - start->x;
 		}
 	}
 
-	/* allocate buffer */
-	len *= 4;
-	++len;
-	str = malloc(len);
-	if (!str)
-		return -ENOMEM;
-	pos = str;
+	/* last line of a multi-line selection */
+	if (line_num == end->y) {
+		return end->x + 1;
+	}
 
-	/* copy data into buffer */
-	iter = start->line;
-	if (!iter && start->y == SELECTION_TOP)
-		iter = con->sb_first;
+	/* every other selection */
+	return con->size_x;
+}
+
+/*
+ * Calculate the maximum needed space for the number of lines given
+ */
+static unsigned int calc_line_copy_buffer(struct tsm_screen *con, unsigned int num_lines)
+{
+	// 4 is the max size of a Unicode character
+	return con->size_x * num_lines * 4 + 1;
+}
 
+/*
+ * Copy all selected lines from the scroll back buffer
+ */
+static int copy_lines_sb(struct tsm_screen *con, struct selection_pos *start, struct selection_pos *end, char *buf, int pos)
+{
+	struct line *iter;
+	int line_x, line_len;
+
+	if (!start->line) {
+		return pos;
+	}
+
+	iter = start->line;
 	while (iter) {
-		if (iter == start->line && iter == end->line) {
-			if (iter->size > start->x) {
-				if (iter->size > end->x)
-					len = end->x - start->x + 1;
-				else
-					len = iter->size - start->x;
-				pos += copy_line(iter, pos, start->x, len);
-			}
-			break;
-		} else if (iter == start->line) {
-			if (iter->size > start->x)
-				pos += copy_line(iter, pos, start->x,
-						 iter->size - start->x);
-		} else if (iter == end->line) {
-			if (iter->size > end->x)
-				len = end->x + 1;
-			else
-				len = iter->size;
-			pos += copy_line(iter, pos, 0, len);
+		line_x = 0;
+		if (iter == start->line) {
+			line_x = start->x;
+		}
+
+		line_len = calc_selection_line_len_sb(con, start, end, iter);
+		pos += copy_line(iter, &(buf[pos]), line_x, line_len);
+
+		if (iter == con->sb_last || iter == end->line) {
 			break;
-		} else {
-			pos += copy_line(iter, pos, 0, iter->size);
 		}
 
-		*pos++ = '\n';
 		iter = iter->next;
 	}
 
-	if (!end->line) {
-		if (start->line || start->y == SELECTION_TOP)
-			i = 0;
-		else
-			i = start->y;
-		for ( ; i < con->size_y; ++i) {
-			iter = con->lines[i];
-			if (!start->line && start->y == i && end->y == i) {
-				if (con->size_x > start->x) {
-					if (con->size_x > end->x)
-						len = end->x - start->x + 1;
-					else
-						len = con->size_x - start->x;
-					pos += copy_line(iter, pos, start->x, len);
-				}
-				break;
-			} else if (!start->line && start->y == i) {
-				if (con->size_x > start->x)
-					pos += copy_line(iter, pos, start->x,
-							 con->size_x - start->x);
-			} else if (end->y == i) {
-				if (con->size_x > end->x)
-					len = end->x + 1;
-				else
-					len = con->size_x;
-				pos += copy_line(iter, pos, 0, len);
-				break;
-			} else {
-				pos += copy_line(iter, pos, 0, con->size_x);
-			}
+	return pos;
+}
+
+/*
+ * Copy all selected lines from the regular screen
+ */
+static int copy_lines(struct tsm_screen *con, struct selection_pos *start, struct selection_pos *end, char *buf, int pos)
+{
+	int line_len, line_x, i;
 
-			*pos++ = '\n';
+	/* selection is scroll back buffer only */
+	if (end->line) {
+		return pos;
+	}
+
+	for (i = start->y; i <= end->y; i++) {
+		line_len = calc_selection_line_len(con, start, end, i);
+
+		line_x = 0;
+		if (!start->line && i == start->y) {
+			line_x = start->x;
 		}
+
+		pos += copy_line(con->lines[i], &(buf[pos]), line_x, line_len);
+	}
+
+	return pos;
+}
+
+SHL_EXPORT
+int tsm_screen_selection_copy(struct tsm_screen *con, char **out)
+{
+	struct selection_pos *start, *end;
+	struct selection_pos start_copy, end_copy;
+	int buf_size = 0;
+	int pos = 0;
+	int total_lines;
+
+	if (!con || !out) {
+		return -EINVAL;
+	}
+
+	if (!con->sel_active) {
+		return -ENOENT;
+	}
+
+	/*
+	 * copy the selection start and end so we can modify it without affecting
+	 * the screen in any way
+	 */
+	memcpy(&start_copy, &con->sel_start, sizeof(con->sel_start));
+	memcpy(&end_copy, &con->sel_end, sizeof(con->sel_end));
+	start = &start_copy;
+	end   = &end_copy;
+
+	/* invalid selection */
+	if (start->y == SELECTION_TOP && start->line == NULL &&
+		end->y == SELECTION_TOP && end->line == NULL) {
+		*out = strdup("");
+		return 0;
+	}
+
+	norm_selection(con, &start, &end);
+
+	if (start->line == NULL && start->y == SELECTION_TOP) {
+		if (con->sb_first != NULL) {
+			start->line = con->sb_first;
+			start->x = 0;
+		} else {
+			start->y = 0;
+			start->x = 0;
+		}
+	}
+
+	total_lines =  selection_count_lines_sb(con, start, end);
+	total_lines += selection_count_lines(start, end);
+	buf_size = calc_line_copy_buffer(con, total_lines);
+
+	*out = calloc(buf_size, 1);
+	if (!*out) {
+		return -ENOMEM;
+	}
+
+	pos = copy_lines_sb(con, start, end, *out, pos);
+	pos = copy_lines(con, start, end, *out, pos);
+
+	/* remove last line break */
+	if (pos > 0) {
+		(*out)[--pos] = '\0';
 	}
 
-	/* return buffer */
-	*pos = 0;
-	*out = str;
-	return pos - str;
+	return pos;
 }
diff -pruN 4.0.2-0.4/src/tsm/tsm-vte.c 4.1.0-1/src/tsm/tsm-vte.c
--- 4.0.2-0.4/src/tsm/tsm-vte.c	2022-02-18 06:07:59.000000000 +0000
+++ 4.1.0-1/src/tsm/tsm-vte.c	2025-07-02 07:37:01.000000000 +0000
@@ -155,6 +155,7 @@ struct tsm_vte {
 	tsm_vte_write_cb write_cb;
 	void *data;
 	char *palette_name;
+	bool backspace_sends_delete;
 
 	struct tsm_utf8_mach *mach;
 	unsigned long parse_cnt;
@@ -169,6 +170,13 @@ struct tsm_vte {
 	unsigned int osc_len;
 	char osc_arg[OSC_MAX_LEN];
 
+	tsm_vte_mouse_cb mouse_cb;
+	void *mouse_data;
+	unsigned int mouse_mode;
+	unsigned int mouse_event;
+	unsigned int mouse_last_col;
+	unsigned int mouse_last_row;
+
 	uint8_t (*custom_palette_storage)[3];
 	uint8_t (*palette)[3];
 	struct tsm_screen_attr def_attr;
@@ -189,7 +197,7 @@ struct tsm_vte {
 	unsigned int alt_cursor_y;
 };
 
-static uint8_t color_palette[TSM_COLOR_NUM][3] = {
+static uint8_t color_palette_legacy[TSM_COLOR_NUM][3] = {
 	[TSM_COLOR_BLACK]         = {   0,   0,   0 }, /* black */
 	[TSM_COLOR_RED]           = { 205,   0,   0 }, /* red */
 	[TSM_COLOR_GREEN]         = {   0, 205,   0 }, /* green */
@@ -211,6 +219,29 @@ static uint8_t color_palette[TSM_COLOR_N
 	[TSM_COLOR_BACKGROUND]    = {   0,   0,   0 }, /* black */
 };
 
+// vga palette from https://en.wikipedia.org/wiki/ANSI_escape_code
+static uint8_t color_palette_vga[TSM_COLOR_NUM][3] = {
+	[TSM_COLOR_BLACK]         = {   0,   0,   0 }, /* black */
+	[TSM_COLOR_RED]           = { 170,   0,   0 }, /* red */
+	[TSM_COLOR_GREEN]         = {   0, 170,   0 }, /* green */
+	[TSM_COLOR_YELLOW]        = { 170,  85,   0 }, /* yellow */
+	[TSM_COLOR_BLUE]          = {   0,   0, 170 }, /* blue */
+	[TSM_COLOR_MAGENTA]       = { 170,   0, 170 }, /* magenta */
+	[TSM_COLOR_CYAN]          = {   0, 170, 170 }, /* cyan */
+	[TSM_COLOR_LIGHT_GREY]    = { 170, 170, 170 }, /* light grey */
+	[TSM_COLOR_DARK_GREY]     = {  85,  85,  85 }, /* dark grey */
+	[TSM_COLOR_LIGHT_RED]     = { 255,  85,  85 }, /* light red */
+	[TSM_COLOR_LIGHT_GREEN]   = {  85, 255,  85 }, /* light green */
+	[TSM_COLOR_LIGHT_YELLOW]  = { 255, 255,  85 }, /* light yellow */
+	[TSM_COLOR_LIGHT_BLUE]    = {  85,  85, 255 }, /* light blue */
+	[TSM_COLOR_LIGHT_MAGENTA] = { 255,  85, 255 }, /* light magenta */
+	[TSM_COLOR_LIGHT_CYAN]    = {  85, 255, 255 }, /* light cyan */
+	[TSM_COLOR_WHITE]         = { 255, 255, 255 }, /* white */
+
+	[TSM_COLOR_FOREGROUND]    = { 170, 170, 170 }, /* light grey */
+	[TSM_COLOR_BACKGROUND]    = {   0,   0,   0 }, /* black */
+};
+
 static uint8_t color_palette_solarized[TSM_COLOR_NUM][3] = {
 	[TSM_COLOR_BLACK]         = {   7,  54,  66 }, /* black */
 	[TSM_COLOR_RED]           = { 220,  50,  47 }, /* red */
@@ -346,7 +377,7 @@ static uint8_t color_palette_base16_ligh
 static uint8_t (*get_palette(struct tsm_vte *vte))[3]
 {
 	if (!vte->palette_name)
-		return color_palette;
+		return color_palette_legacy;
 
 	if (!strcmp(vte->palette_name, "custom") && vte->custom_palette_storage)
 		return vte->custom_palette_storage;
@@ -362,8 +393,12 @@ static uint8_t (*get_palette(struct tsm_
 		return color_palette_base16_dark;
 	if (!strcmp(vte->palette_name, "base16-light"))
 		return color_palette_base16_light;
+	if (!strcmp(vte->palette_name, "vga"))
+		return color_palette_vga;
+	if (!strcmp(vte->palette_name, "legacy"))
+		return color_palette_legacy;
 
-	return color_palette;
+	return color_palette_legacy;
 }
 
 /* Several effects may occur when non-RGB colors are used. For instance, if bold
@@ -442,8 +477,11 @@ int tsm_vte_new(struct tsm_vte **out, st
 	vte->con = con;
 	vte->write_cb = write_cb;
 	vte->data = data;
+	vte->backspace_sends_delete = false;
 	vte->osc_cb = NULL;
 	vte->osc_data = NULL;
+	vte->mouse_cb = NULL;
+	vte->mouse_data = NULL;
 	vte->custom_palette_storage = NULL;
 	vte->palette = get_palette(vte);
 	vte->def_attr.fccode = TSM_COLOR_FOREGROUND;
@@ -503,6 +541,16 @@ void tsm_vte_set_osc_cb(struct tsm_vte *
 	vte->osc_data = osc_data;
 }
 
+SHL_EXPORT
+void tsm_vte_set_mouse_cb(struct tsm_vte *vte, tsm_vte_mouse_cb mouse_cb, void *mouse_data)
+{
+	if (!vte)
+		return;
+
+	vte->mouse_cb = mouse_cb;
+	vte->mouse_data = mouse_data;
+}
+
 static int vte_update_palette(struct tsm_vte *vte)
 {
 	vte->palette = get_palette(vte);
@@ -541,7 +589,7 @@ int tsm_vte_set_palette(struct tsm_vte *
 SHL_EXPORT
 int tsm_vte_set_custom_palette(struct tsm_vte *vte, uint8_t (*palette)[3])
 {
-	const size_t palette_byte_size = sizeof(color_palette);
+	const size_t palette_byte_size = sizeof(color_palette_legacy);
 	uint8_t (*tmp)[3] = NULL;
 
 	if (!vte)
@@ -569,12 +617,42 @@ void tsm_vte_get_def_attr(struct tsm_vte
 	memcpy(out, &vte->def_attr, sizeof(*out));
 }
 
+SHL_EXPORT
+unsigned int tsm_vte_get_flags(struct tsm_vte *vte)
+{
+	if (!vte) {
+		return 0;
+	}
+
+	return vte->flags;
+}
+
+SHL_EXPORT
+unsigned int tsm_vte_get_mouse_mode(struct tsm_vte *vte)
+{
+	if (!vte) {
+		return 0;
+	}
+
+	return vte->mouse_mode;
+}
+
+SHL_EXPORT
+unsigned int tsm_vte_get_mouse_event(struct tsm_vte *vte)
+{
+	if (!vte) {
+		return 0;
+	}
+
+	return vte->mouse_event;
+}
+
 /*
  * Write raw byte-stream to pty.
  * When writing data to the client we must make sure that we send the correct
  * encoding. For backwards-compatibility reasons we should always send 7bit
- * characters exclusively. However, when FLAG_7BIT_MODE is not set, then we can
- * also send raw 8bit characters. For instance, in FLAG_8BIT_MODE we can use the
+ * characters exclusively. However, when TSM_VTE_FLAG_7BIT_MODE is not set, then we can
+ * also send raw 8bit characters. For instance, in TSM_VTE_FLAG_8BIT_MODE we can use the
  * GR characters as keyboard input and send them directly or even use the C1
  * escape characters. In unicode mode (default) we can send multi-byte utf-8
  * characters which are also 8bit. When sending these characters, set the \raw
@@ -619,17 +697,17 @@ static void vte_write_debug(struct tsm_v
 #endif
 
 	/* in local echo mode, directly parse the data again */
-	if (!vte->parse_cnt && !(vte->flags & FLAG_SEND_RECEIVE_MODE)) {
-		if (vte->flags & FLAG_PREPEND_ESCAPE)
+	if (!vte->parse_cnt && !(vte->flags & TSM_VTE_FLAG_SEND_RECEIVE_MODE)) {
+		if (vte->flags & TSM_VTE_FLAG_PREPEND_ESCAPE)
 			tsm_vte_input(vte, "\e", 1);
 		tsm_vte_input(vte, u8, len);
 	}
 
-	if (vte->flags & FLAG_PREPEND_ESCAPE)
+	if (vte->flags & TSM_VTE_FLAG_PREPEND_ESCAPE)
 		vte->write_cb(vte, "\e", 1, vte->data);
 	vte->write_cb(vte, u8, len, vte->data);
 
-	vte->flags &= ~FLAG_PREPEND_ESCAPE;
+	vte->flags &= ~TSM_VTE_FLAG_PREPEND_ESCAPE;
 }
 
 #define vte_write(_vte, _u8, _len) \
@@ -670,8 +748,8 @@ static void save_state(struct tsm_vte *v
 	vte->saved_state.cattr = vte->cattr;
 	vte->saved_state.gl = vte->gl;
 	vte->saved_state.gr = vte->gr;
-	vte->saved_state.wrap_mode = vte->flags & FLAG_AUTO_WRAP_MODE;
-	vte->saved_state.origin_mode = vte->flags & FLAG_ORIGIN_MODE;
+	vte->saved_state.wrap_mode = vte->flags & TSM_VTE_FLAG_AUTO_WRAP_MODE;
+	vte->saved_state.origin_mode = vte->flags & TSM_VTE_FLAG_ORIGIN_MODE;
 }
 
 static void restore_state(struct tsm_vte *vte)
@@ -680,24 +758,24 @@ static void restore_state(struct tsm_vte
 			       vte->saved_state.cursor_y);
 	vte->cattr = vte->saved_state.cattr;
 	to_rgb(vte, &vte->cattr);
-	if (vte->flags & FLAG_BACKGROUND_COLOR_ERASE_MODE)
+	if (vte->flags & TSM_VTE_FLAG_BACKGROUND_COLOR_ERASE_MODE)
 		tsm_screen_set_def_attr(vte->con, &vte->cattr);
 	vte->gl = vte->saved_state.gl;
 	vte->gr = vte->saved_state.gr;
 
 	if (vte->saved_state.wrap_mode) {
-		vte->flags |= FLAG_AUTO_WRAP_MODE;
+		vte->flags |= TSM_VTE_FLAG_AUTO_WRAP_MODE;
 		tsm_screen_set_flags(vte->con, TSM_SCREEN_AUTO_WRAP);
 	} else {
-		vte->flags &= ~FLAG_AUTO_WRAP_MODE;
+		vte->flags &= ~TSM_VTE_FLAG_AUTO_WRAP_MODE;
 		tsm_screen_reset_flags(vte->con, TSM_SCREEN_AUTO_WRAP);
 	}
 
 	if (vte->saved_state.origin_mode) {
-		vte->flags |= FLAG_ORIGIN_MODE;
+		vte->flags |= TSM_VTE_FLAG_ORIGIN_MODE;
 		tsm_screen_set_flags(vte->con, TSM_SCREEN_REL_ORIGIN);
 	} else {
-		vte->flags &= ~FLAG_ORIGIN_MODE;
+		vte->flags &= ~TSM_VTE_FLAG_ORIGIN_MODE;
 		tsm_screen_reset_flags(vte->con, TSM_SCREEN_REL_ORIGIN);
 	}
 }
@@ -715,11 +793,11 @@ void tsm_vte_reset(struct tsm_vte *vte)
 		return;
 
 	vte->flags = 0;
-	vte->flags |= FLAG_TEXT_CURSOR_MODE;
-	vte->flags |= FLAG_AUTO_REPEAT_MODE;
-	vte->flags |= FLAG_SEND_RECEIVE_MODE;
-	vte->flags |= FLAG_AUTO_WRAP_MODE;
-	vte->flags |= FLAG_BACKGROUND_COLOR_ERASE_MODE;
+	vte->flags |= TSM_VTE_FLAG_TEXT_CURSOR_MODE;
+	vte->flags |= TSM_VTE_FLAG_AUTO_REPEAT_MODE;
+	vte->flags |= TSM_VTE_FLAG_SEND_RECEIVE_MODE;
+	vte->flags |= TSM_VTE_FLAG_AUTO_WRAP_MODE;
+	vte->flags |= TSM_VTE_FLAG_BACKGROUND_COLOR_ERASE_MODE;
 	tsm_screen_reset(vte->con);
 	tsm_screen_set_flags(vte->con, TSM_SCREEN_AUTO_WRAP);
 
@@ -734,6 +812,11 @@ void tsm_vte_reset(struct tsm_vte *vte)
 	vte->g2 = &tsm_vte_unicode_lower;
 	vte->g3 = &tsm_vte_unicode_upper;
 
+	vte->mouse_mode = 0;
+	vte->mouse_event = 0;
+	vte->mouse_last_col = 0;
+	vte->mouse_last_row = 0;
+
 	memcpy(&vte->cattr, &vte->def_attr, sizeof(vte->cattr));
 	to_rgb(vte, &vte->cattr);
 	tsm_screen_set_def_attr(vte->con, &vte->def_attr);
@@ -790,7 +873,7 @@ static void do_execute(struct tsm_vte *v
 	case 0x0b: /* VT */
 	case 0x0c: /* FF */
 		/* Line feed or newline (CR/NL mode) */
-		if (vte->flags & FLAG_LINE_FEED_NEW_LINE_MODE)
+		if (vte->flags & TSM_VTE_FLAG_LINE_FEED_NEW_LINE_MODE)
 			tsm_screen_newline(vte->con);
 		else
 			tsm_screen_move_down(vte->con, 1, true);
@@ -1039,7 +1122,7 @@ static void do_esc(struct tsm_vte *vte,
 		if (vte->csi_flags & CSI_SPACE) {
 			/* S7C1T */
 			/* Disable 8bit C1 mode */
-			vte->flags &= ~FLAG_USE_C1;
+			vte->flags &= ~TSM_VTE_FLAG_USE_C1;
 			return;
 		}
 		break;
@@ -1047,7 +1130,7 @@ static void do_esc(struct tsm_vte *vte,
 		if (vte->csi_flags & CSI_SPACE) {
 			/* S8C1T */
 			/* Enable 8bit C1 mode */
-			vte->flags |= FLAG_USE_C1;
+			vte->flags |= TSM_VTE_FLAG_USE_C1;
 			return;
 		}
 		break;
@@ -1114,11 +1197,11 @@ static void do_esc(struct tsm_vte *vte,
 		break;
 	case '=': /* DECKPAM */
 		/* Set application keypad mode */
-		vte->flags |= FLAG_KEYPAD_APPLICATION_MODE;
+		vte->flags |= TSM_VTE_FLAG_KEYPAD_APPLICATION_MODE;
 		break;
 	case '>': /* DECKPNM */
 		/* Set numeric keypad mode */
-		vte->flags &= ~FLAG_KEYPAD_APPLICATION_MODE;
+		vte->flags &= ~TSM_VTE_FLAG_KEYPAD_APPLICATION_MODE;
 		break;
 	case 'c': /* RIS */
 		/* hard reset */
@@ -1360,7 +1443,7 @@ static void csi_attribute(struct tsm_vte
 	}
 
 	to_rgb(vte, &vte->cattr);
-	if (vte->flags & FLAG_BACKGROUND_COLOR_ERASE_MODE)
+	if (vte->flags & TSM_VTE_FLAG_BACKGROUND_COLOR_ERASE_MODE)
 		tsm_screen_set_def_attr(vte->con, &vte->cattr);
 }
 
@@ -1381,7 +1464,7 @@ static void csi_compat_mode(struct tsm_v
 		 * there is no need to explicitly select it.
 		 * However, we enable 7bit mode to avoid
 		 * character-table problems */
-		vte->flags |= FLAG_7BIT_MODE;
+		vte->flags |= TSM_VTE_FLAG_7BIT_MODE;
 		vte->g0 = &tsm_vte_unicode_lower;
 		vte->g1 = &tsm_vte_dec_supplemental_graphics;
 	} else if (vte->csi_argv[0] == 62 ||
@@ -1399,9 +1482,9 @@ static void csi_compat_mode(struct tsm_v
 		 * compatibility is requested explicitly. */
 		if (vte->csi_argv[1] == 1 ||
 		    vte->csi_argv[1] == 2)
-			vte->flags |= FLAG_USE_C1;
+			vte->flags |= TSM_VTE_FLAG_USE_C1;
 
-		vte->flags |= FLAG_8BIT_MODE;
+		vte->flags |= TSM_VTE_FLAG_8BIT_MODE;
 		vte->g0 = &tsm_vte_unicode_lower;
 		vte->g1 = &tsm_vte_dec_supplemental_graphics;
 	} else {
@@ -1430,11 +1513,11 @@ static void csi_mode(struct tsm_vte *vte
 				continue;
 			case 2: /* KAM */
 				set_reset_flag(vte, set,
-					       FLAG_KEYBOARD_ACTION_MODE);
+					       TSM_VTE_FLAG_KEYBOARD_ACTION_MODE);
 				continue;
 			case 4: /* IRM */
 				set_reset_flag(vte, set,
-					       FLAG_INSERT_REPLACE_MODE);
+					       TSM_VTE_FLAG_INSERT_REPLACE_MODE);
 				if (set)
 					tsm_screen_set_flags(vte->con,
 						TSM_SCREEN_INSERT_MODE);
@@ -1444,11 +1527,11 @@ static void csi_mode(struct tsm_vte *vte
 				continue;
 			case 12: /* SRM */
 				set_reset_flag(vte, set,
-					       FLAG_SEND_RECEIVE_MODE);
+					       TSM_VTE_FLAG_SEND_RECEIVE_MODE);
 				continue;
 			case 20: /* LNM */
 				set_reset_flag(vte, set,
-					       FLAG_LINE_FEED_NEW_LINE_MODE);
+					       TSM_VTE_FLAG_LINE_FEED_NEW_LINE_MODE);
 				continue;
 			default:
 				llog_debug(vte, "unknown non-DEC (Re)Set-Mode %d",
@@ -1461,7 +1544,7 @@ static void csi_mode(struct tsm_vte *vte
 		case -1:
 			continue;
 		case 1: /* DECCKM */
-			set_reset_flag(vte, set, FLAG_CURSOR_KEY_MODE);
+			set_reset_flag(vte, set, TSM_VTE_FLAG_CURSOR_KEY_MODE);
 			continue;
 		case 2: /* DECANM */
 			/* Select VT52 mode */
@@ -1492,7 +1575,7 @@ static void csi_mode(struct tsm_vte *vte
 			 * scrolling so ignore this here. */
 			continue;
 		case 5: /* DECSCNM */
-			set_reset_flag(vte, set, FLAG_INVERSE_SCREEN_MODE);
+			set_reset_flag(vte, set, TSM_VTE_FLAG_INVERSE_SCREEN_MODE);
 			if (set)
 				tsm_screen_set_flags(vte->con,
 						TSM_SCREEN_INVERSE);
@@ -1501,7 +1584,7 @@ static void csi_mode(struct tsm_vte *vte
 						TSM_SCREEN_INVERSE);
 			continue;
 		case 6: /* DECOM */
-			set_reset_flag(vte, set, FLAG_ORIGIN_MODE);
+			set_reset_flag(vte, set, TSM_VTE_FLAG_ORIGIN_MODE);
 			if (set)
 				tsm_screen_set_flags(vte->con,
 						TSM_SCREEN_REL_ORIGIN);
@@ -1510,7 +1593,7 @@ static void csi_mode(struct tsm_vte *vte
 						TSM_SCREEN_REL_ORIGIN);
 			continue;
 		case 7: /* DECAWN */
-			set_reset_flag(vte, set, FLAG_AUTO_WRAP_MODE);
+			set_reset_flag(vte, set, TSM_VTE_FLAG_AUTO_WRAP_MODE);
 			if (set)
 				tsm_screen_set_flags(vte->con,
 						TSM_SCREEN_AUTO_WRAP);
@@ -1519,7 +1602,15 @@ static void csi_mode(struct tsm_vte *vte
 						TSM_SCREEN_AUTO_WRAP);
 			continue;
 		case 8: /* DECARM */
-			set_reset_flag(vte, set, FLAG_AUTO_REPEAT_MODE);
+			set_reset_flag(vte, set, TSM_VTE_FLAG_AUTO_REPEAT_MODE);
+			continue;
+		case TSM_VTE_MOUSE_MODE_X10:
+			vte->mouse_mode = set ? vte->csi_argv[i] : 0;
+			vte->mouse_event = TSM_VTE_MOUSE_EVENT_BTN;
+
+			if (vte->mouse_cb) {
+			    vte->mouse_cb(vte, vte->mouse_event, false, vte->mouse_data);
+			}
 			continue;
 		case 12: /* blinking cursor */
 			/* TODO: implement */
@@ -1536,7 +1627,7 @@ static void csi_mode(struct tsm_vte *vte
 			 * this mode. */
 			continue;
 		case 25: /* DECTCEM */
-			set_reset_flag(vte, set, FLAG_TEXT_CURSOR_MODE);
+			set_reset_flag(vte, set, TSM_VTE_FLAG_TEXT_CURSOR_MODE);
 			if (set)
 				tsm_screen_reset_flags(vte->con,
 						TSM_SCREEN_HIDE_CURSOR);
@@ -1545,10 +1636,10 @@ static void csi_mode(struct tsm_vte *vte
 						TSM_SCREEN_HIDE_CURSOR);
 			continue;
 		case 42: /* DECNRCM */
-			set_reset_flag(vte, set, FLAG_NATIONAL_CHARSET_MODE);
+			set_reset_flag(vte, set, TSM_VTE_FLAG_NATIONAL_CHARSET_MODE);
 			continue;
 		case 47: /* Alternate screen buffer */
-			if (vte->flags & FLAG_TITE_INHIBIT_MODE)
+			if (vte->flags & TSM_VTE_FLAG_TITE_INHIBIT_MODE)
 				continue;
 
 			if (set)
@@ -1559,7 +1650,7 @@ static void csi_mode(struct tsm_vte *vte
 						       TSM_SCREEN_ALTERNATE);
 			continue;
 		case 1047: /* Alternate screen buffer with post-erase */
-			if (vte->flags & FLAG_TITE_INHIBIT_MODE)
+			if (vte->flags & TSM_VTE_FLAG_TITE_INHIBIT_MODE)
 				continue;
 
 			if (set) {
@@ -1572,7 +1663,7 @@ static void csi_mode(struct tsm_vte *vte
 			}
 			continue;
 		case 1048: /* Set/Reset alternate-screen buffer cursor */
-			if (vte->flags & FLAG_TITE_INHIBIT_MODE)
+			if (vte->flags & TSM_VTE_FLAG_TITE_INHIBIT_MODE)
 				continue;
 
 			if (set) {
@@ -1586,7 +1677,7 @@ static void csi_mode(struct tsm_vte *vte
 			}
 			continue;
 		case 1049: /* Alternate screen buffer with pre-erase+cursor */
-			if (vte->flags & FLAG_TITE_INHIBIT_MODE)
+			if (vte->flags & TSM_VTE_FLAG_TITE_INHIBIT_MODE)
 				continue;
 
 			if (set) {
@@ -1604,6 +1695,42 @@ static void csi_mode(struct tsm_vte *vte
 						   vte->alt_cursor_y);
 			}
 			continue;
+		case TSM_VTE_MOUSE_EVENT_BTN:
+		case TSM_VTE_MOUSE_EVENT_ANY:
+			if (vte->mouse_mode == TSM_VTE_MOUSE_MODE_X10) {
+			    vte->mouse_event = TSM_VTE_MOUSE_EVENT_BTN;
+			} else {
+			    vte->mouse_event = set ? vte->csi_argv[i] : 0;
+			}
+
+			if (vte->mouse_cb && vte->mouse_mode) {
+			    vte->mouse_cb(vte, vte->mouse_event, vte->mouse_mode == TSM_VTE_MOUSE_MODE_PIXEL, vte->mouse_data);
+			}
+			continue;
+		case TSM_VTE_MOUSE_MODE_SGR:
+			vte->mouse_mode = set ? vte->csi_argv[i] : 0;
+
+			if (!vte->mouse_cb) {
+			    continue;
+			}
+
+			if (!set || vte->mouse_event) {
+			    vte->mouse_cb(vte, vte->mouse_event, false, vte->mouse_data);
+			    continue;
+			}
+			continue;
+		case TSM_VTE_MOUSE_MODE_PIXEL:
+			vte->mouse_mode = set ? vte->csi_argv[i] : 0;
+
+			if (!vte->mouse_cb) {
+			    continue;
+			}
+
+			if (!set || vte->mouse_event) {
+			    vte->mouse_cb(vte, vte->mouse_event, set, vte->mouse_data);
+			    continue;
+			}
+			continue;
 		default:
 			llog_debug(vte, "unknown DEC %set-Mode %d",
 				   set?"S":"Res", vte->csi_argv[i]);
@@ -1691,6 +1818,14 @@ static void do_csi(struct tsm_vte *vte,
 		x = tsm_screen_get_cursor_x(vte->con);
 		tsm_screen_move_to(vte->con, x, num - 1);
 		break;
+	case 'E': /* CNL */
+		/* Move cursor down the indicated # of rows, to column 1 */
+		num = vte->csi_argv[0];
+		if (num <= 0)
+			num = 1;
+		tsm_screen_move_down(vte->con, num, false);
+		tsm_screen_move_line_home(vte->con);
+		break;
 	case 'e': /* VPR */
 		/* Vertical Line Position Relative */
 		num = vte->csi_argv[0];
@@ -1700,6 +1835,14 @@ static void do_csi(struct tsm_vte *vte,
 		y = tsm_screen_get_cursor_y(vte->con);
 		tsm_screen_move_to(vte->con, x, y + num);
 		break;
+	case 'F': /* CPL */
+		/* Move cursor up the indicated # of rows, to column 1 */
+		num = vte->csi_argv[0];
+		if (num <= 0)
+			num = 1;
+		tsm_screen_move_up(vte->con, num, false);
+		tsm_screen_move_line_home(vte->con);
+		break;
 	case 'H': /* CUP */
 	case 'f': /* HVP */
 		/* position cursor */
@@ -1774,7 +1917,10 @@ static void do_csi(struct tsm_vte *vte,
 			/* DECRQM: Request DEC Private Mode */
 			/* If CSI_WHAT is set, then enable,
 			 * otherwise disable */
-			csi_soft_reset(vte);
+
+			/* Ignore */
+
+			/* FIXME: Implement DECRQM */
 		} else {
 			/* DECSCL: Compatibility Level */
 			/* Sometimes CSI_DQUOTE is set here, too */
@@ -2370,12 +2516,12 @@ void tsm_vte_input(struct tsm_vte *vte,
 
 	++vte->parse_cnt;
 	for (i = 0; i < len; ++i) {
-		if (vte->flags & FLAG_7BIT_MODE) {
+		if (vte->flags & TSM_VTE_FLAG_7BIT_MODE) {
 			if (u8[i] & 0x80)
 				llog_debug(vte, "receiving 8bit character U+%d from pty while in 7bit mode",
 					   (int)u8[i]);
 			parse_data(vte, u8[i] & 0x7f);
-		} else if (vte->flags & FLAG_8BIT_MODE) {
+		} else if (vte->flags & TSM_VTE_FLAG_8BIT_MODE) {
 			parse_data(vte, u8[i]);
 		} else {
 			state = tsm_utf8_mach_feed(vte->mach, u8[i]);
@@ -2390,6 +2536,12 @@ void tsm_vte_input(struct tsm_vte *vte,
 }
 
 SHL_EXPORT
+void tsm_vte_set_backspace_sends_delete(struct tsm_vte *vte, bool enable)
+{
+	vte->backspace_sends_delete = enable;
+}
+
+SHL_EXPORT
 bool tsm_vte_handle_keyboard(struct tsm_vte *vte, uint32_t keysym,
 			     uint32_t ascii, unsigned int mods,
 			     uint32_t unicode)
@@ -2410,7 +2562,7 @@ bool tsm_vte_handle_keyboard(struct tsm_
 	 * disables this by default, why?) and whether we should implement the
 	 * fallback shifting that xterm does. */
 	if (mods & TSM_ALT_MASK)
-		vte->flags |= FLAG_PREPEND_ESCAPE;
+		vte->flags |= TSM_VTE_FLAG_PREPEND_ESCAPE;
 
 	/* A user might actually use multiple layouts for keyboard input. The
 	 * @keysym variable contains the actual keysym that the user used. But
@@ -2569,7 +2721,10 @@ bool tsm_vte_handle_keyboard(struct tsm_
 
 	switch (keysym) {
 		case XKB_KEY_BackSpace:
-			vte_write(vte, "\x08", 1);
+			if (vte->backspace_sends_delete)
+				vte_write(vte, "\x7f", 1);
+			else
+				vte_write(vte, "\x08", 1);
 			return true;
 		case XKB_KEY_Tab:
 		case XKB_KEY_KP_Tab:
@@ -2609,13 +2764,13 @@ bool tsm_vte_handle_keyboard(struct tsm_
 			vte_write(vte, "\x1b", 1);
 			return true;
 		case XKB_KEY_KP_Enter:
-			if (vte->flags & FLAG_KEYPAD_APPLICATION_MODE) {
+			if (vte->flags & TSM_VTE_FLAG_KEYPAD_APPLICATION_MODE) {
 				vte_write(vte, "\eOM", 3);
 				return true;
 			}
 			/* fallthrough */
 		case XKB_KEY_Return:
-			if (vte->flags & FLAG_LINE_FEED_NEW_LINE_MODE)
+			if (vte->flags & TSM_VTE_FLAG_LINE_FEED_NEW_LINE_MODE)
 				vte_write(vte, "\x0d\x0a", 2);
 			else
 				vte_write(vte, "\x0d", 1);
@@ -2644,7 +2799,7 @@ bool tsm_vte_handle_keyboard(struct tsm_
 		case XKB_KEY_KP_Up:
 			if (mods & TSM_CONTROL_MASK) {
 				vte_write(vte, "\e[1;5A", 6);
-			} else if (vte->flags & FLAG_CURSOR_KEY_MODE) {
+			} else if (vte->flags & TSM_VTE_FLAG_CURSOR_KEY_MODE) {
 				vte_write(vte, "\eOA", 3);
 			} else {
 				vte_write(vte, "\e[A", 3);
@@ -2654,7 +2809,7 @@ bool tsm_vte_handle_keyboard(struct tsm_
 		case XKB_KEY_KP_Down:
 			if (mods & TSM_CONTROL_MASK) {
 				vte_write(vte, "\e[1;5B", 6);
-			} else if (vte->flags & FLAG_CURSOR_KEY_MODE) {
+			} else if (vte->flags & TSM_VTE_FLAG_CURSOR_KEY_MODE) {
 				vte_write(vte, "\eOB", 3);
 			} else {
 				vte_write(vte, "\e[B", 3);
@@ -2664,7 +2819,7 @@ bool tsm_vte_handle_keyboard(struct tsm_
 		case XKB_KEY_KP_Right:
 			if (mods & TSM_CONTROL_MASK) {
 				vte_write(vte, "\e[1;5C", 6);
-			} else if (vte->flags & FLAG_CURSOR_KEY_MODE) {
+			} else if (vte->flags & TSM_VTE_FLAG_CURSOR_KEY_MODE) {
 				vte_write(vte, "\eOC", 3);
 			} else {
 				vte_write(vte, "\e[C", 3);
@@ -2674,7 +2829,7 @@ bool tsm_vte_handle_keyboard(struct tsm_
 		case XKB_KEY_KP_Left:
 			if (mods & TSM_CONTROL_MASK) {
 				vte_write(vte, "\e[1;5D", 6);
-			} else if (vte->flags & FLAG_CURSOR_KEY_MODE) {
+			} else if (vte->flags & TSM_VTE_FLAG_CURSOR_KEY_MODE) {
 				vte_write(vte, "\eOD", 3);
 			} else {
 				vte_write(vte, "\e[D", 3);
@@ -2682,113 +2837,113 @@ bool tsm_vte_handle_keyboard(struct tsm_
 			return true;
 		case XKB_KEY_KP_Insert:
 		case XKB_KEY_KP_0:
-			if (vte->flags & FLAG_KEYPAD_APPLICATION_MODE)
+			if (vte->flags & TSM_VTE_FLAG_KEYPAD_APPLICATION_MODE)
 				vte_write(vte, "\eOp", 3);
 			else
 				vte_write(vte, "0", 1);
 			return true;
 		case XKB_KEY_KP_1:
-			if (vte->flags & FLAG_KEYPAD_APPLICATION_MODE)
+			if (vte->flags & TSM_VTE_FLAG_KEYPAD_APPLICATION_MODE)
 				vte_write(vte, "\eOq", 3);
 			else
 				vte_write(vte, "1", 1);
 			return true;
 		case XKB_KEY_KP_2:
-			if (vte->flags & FLAG_KEYPAD_APPLICATION_MODE)
+			if (vte->flags & TSM_VTE_FLAG_KEYPAD_APPLICATION_MODE)
 				vte_write(vte, "\eOr", 3);
 			else
 				vte_write(vte, "2", 1);
 			return true;
 		case XKB_KEY_KP_3:
-			if (vte->flags & FLAG_KEYPAD_APPLICATION_MODE)
+			if (vte->flags & TSM_VTE_FLAG_KEYPAD_APPLICATION_MODE)
 				vte_write(vte, "\eOs", 3);
 			else
 				vte_write(vte, "3", 1);
 			return true;
 		case XKB_KEY_KP_4:
-			if (vte->flags & FLAG_KEYPAD_APPLICATION_MODE)
+			if (vte->flags & TSM_VTE_FLAG_KEYPAD_APPLICATION_MODE)
 				vte_write(vte, "\eOt", 3);
 			else
 				vte_write(vte, "4", 1);
 			return true;
 		case XKB_KEY_KP_5:
-			if (vte->flags & FLAG_KEYPAD_APPLICATION_MODE)
+			if (vte->flags & TSM_VTE_FLAG_KEYPAD_APPLICATION_MODE)
 				vte_write(vte, "\eOu", 3);
 			else
 				vte_write(vte, "5", 1);
 			return true;
 		case XKB_KEY_KP_6:
-			if (vte->flags & FLAG_KEYPAD_APPLICATION_MODE)
+			if (vte->flags & TSM_VTE_FLAG_KEYPAD_APPLICATION_MODE)
 				vte_write(vte, "\eOv", 3);
 			else
 				vte_write(vte, "6", 1);
 			return true;
 		case XKB_KEY_KP_7:
-			if (vte->flags & FLAG_KEYPAD_APPLICATION_MODE)
+			if (vte->flags & TSM_VTE_FLAG_KEYPAD_APPLICATION_MODE)
 				vte_write(vte, "\eOw", 3);
 			else
 				vte_write(vte, "7", 1);
 			return true;
 		case XKB_KEY_KP_8:
-			if (vte->flags & FLAG_KEYPAD_APPLICATION_MODE)
+			if (vte->flags & TSM_VTE_FLAG_KEYPAD_APPLICATION_MODE)
 				vte_write(vte, "\eOx", 3);
 			else
 				vte_write(vte, "8", 1);
 			return true;
 		case XKB_KEY_KP_9:
-			if (vte->flags & FLAG_KEYPAD_APPLICATION_MODE)
+			if (vte->flags & TSM_VTE_FLAG_KEYPAD_APPLICATION_MODE)
 				vte_write(vte, "\eOy", 3);
 			else
 				vte_write(vte, "9", 1);
 			return true;
 		case XKB_KEY_KP_Subtract:
-			if (vte->flags & FLAG_KEYPAD_APPLICATION_MODE)
+			if (vte->flags & TSM_VTE_FLAG_KEYPAD_APPLICATION_MODE)
 				vte_write(vte, "\eOm", 3);
 			else
 				vte_write(vte, "-", 1);
 			return true;
 		case XKB_KEY_KP_Separator:
-			if (vte->flags & FLAG_KEYPAD_APPLICATION_MODE)
+			if (vte->flags & TSM_VTE_FLAG_KEYPAD_APPLICATION_MODE)
 				vte_write(vte, "\eOl", 3);
 			else
 				vte_write(vte, ",", 1);
 			return true;
 		case XKB_KEY_KP_Delete:
 		case XKB_KEY_KP_Decimal:
-			if (vte->flags & FLAG_KEYPAD_APPLICATION_MODE)
+			if (vte->flags & TSM_VTE_FLAG_KEYPAD_APPLICATION_MODE)
 				vte_write(vte, "\eOn", 3);
 			else
 				vte_write(vte, ".", 1);
 			return true;
 		case XKB_KEY_KP_Equal:
 		case XKB_KEY_KP_Divide:
-			if (vte->flags & FLAG_KEYPAD_APPLICATION_MODE)
+			if (vte->flags & TSM_VTE_FLAG_KEYPAD_APPLICATION_MODE)
 				vte_write(vte, "\eOj", 3);
 			else
 				vte_write(vte, "/", 1);
 			return true;
 		case XKB_KEY_KP_Multiply:
-			if (vte->flags & FLAG_KEYPAD_APPLICATION_MODE)
+			if (vte->flags & TSM_VTE_FLAG_KEYPAD_APPLICATION_MODE)
 				vte_write(vte, "\eOo", 3);
 			else
 				vte_write(vte, "*", 1);
 			return true;
 		case XKB_KEY_KP_Add:
-			if (vte->flags & FLAG_KEYPAD_APPLICATION_MODE)
+			if (vte->flags & TSM_VTE_FLAG_KEYPAD_APPLICATION_MODE)
 				vte_write(vte, "\eOk", 3);
 			else
 				vte_write(vte, "+", 1);
 			return true;
 		case XKB_KEY_Home:
 		case XKB_KEY_KP_Home:
-			if (vte->flags & FLAG_CURSOR_KEY_MODE)
+			if (vte->flags & TSM_VTE_FLAG_CURSOR_KEY_MODE)
 				vte_write(vte, "\eOH", 3);
 			else
 				vte_write(vte, "\e[H", 3);
 			return true;
 		case XKB_KEY_End:
 		case XKB_KEY_KP_End:
-			if (vte->flags & FLAG_CURSOR_KEY_MODE)
+			if (vte->flags & TSM_VTE_FLAG_CURSOR_KEY_MODE)
 				vte_write(vte, "\eOF", 3);
 			else
 				vte_write(vte, "\e[F", 3);
@@ -2937,7 +3092,7 @@ bool tsm_vte_handle_keyboard(struct tsm_
 	}
 
 	if (unicode != TSM_VTE_INVALID) {
-		if (vte->flags & FLAG_7BIT_MODE) {
+		if (vte->flags & TSM_VTE_FLAG_7BIT_MODE) {
 			val = unicode;
 			if (unicode & 0x80) {
 				llog_debug(vte, "invalid keyboard input in 7bit mode U+%x; mapping to '?'",
@@ -2945,7 +3100,7 @@ bool tsm_vte_handle_keyboard(struct tsm_
 				val = '?';
 			}
 			vte_write(vte, &val, 1);
-		} else if (vte->flags & FLAG_8BIT_MODE) {
+		} else if (vte->flags & TSM_VTE_FLAG_8BIT_MODE) {
 			val = unicode;
 			if (unicode > 0xff) {
 				llog_debug(vte, "invalid keyboard input in 8bit mode U+%x; mapping to '?'",
@@ -2960,6 +3115,91 @@ bool tsm_vte_handle_keyboard(struct tsm_
 		return true;
 	}
 
-	vte->flags &= ~FLAG_PREPEND_ESCAPE;
+	vte->flags &= ~TSM_VTE_FLAG_PREPEND_ESCAPE;
+	return false;
+}
+
+bool tsm_vte_handle_mouse(struct tsm_vte *vte, unsigned int cell_x,
+		unsigned int cell_y, unsigned int pixel_x, unsigned int pixel_y,
+		unsigned int button, unsigned int event, unsigned char modifiers)
+{
+	char buffer[24];
+	unsigned char reply_flags = 0;
+	bool pressed = event & TSM_MOUSE_EVENT_PRESSED;
+
+	/* drop move event if we don't wait for move events */
+	if ((vte->mouse_mode == TSM_VTE_MOUSE_MODE_X10 || vte->mouse_event != TSM_VTE_MOUSE_EVENT_ANY) && (event & TSM_MOUSE_EVENT_MOVED)) {
+		return false;
+	}
+
+	if (vte->mouse_mode == TSM_VTE_MOUSE_MODE_SGR || vte->mouse_mode == TSM_VTE_MOUSE_MODE_PIXEL) {
+		/* internally we use zero indexing but the xterm spec requires the
+		 * top left cell to have the coordinates 1,1 */
+		cell_x++;
+		cell_y++;
+
+		if (button == TSM_MOUSE_BUTTON_WHEEL_UP) {
+			button = 64;
+		} else if (button == TSM_MOUSE_BUTTON_WHEEL_DOWN) {
+			button = 65;
+		}
+
+		reply_flags = button | modifiers;
+	}
+
+	if (vte->mouse_mode == TSM_VTE_MOUSE_MODE_X10) {
+		/* + 0x20 to start in the range of visible characters
+		 * and + 1 to start at 1,1 */
+		cell_x += 0x21;
+		cell_y += 0x21;
+
+		if (cell_x > 0xff) {
+			cell_x = 0xff;
+		}
+
+		if (cell_y > 0xff) {
+			cell_y = 0xff;
+		}
+
+		if (event & TSM_MOUSE_EVENT_RELEASED) {
+			/* translates to released but the information which key is
+			 * released gets lost by design of this encoding scheme */
+			button = 3;
+		}
+
+		reply_flags = (button | modifiers) + 0x20;
+		snprintf((char*) &buffer, sizeof(buffer), "\e[M%c%c%c", reply_flags, cell_x, cell_y);
+
+		vte_write(vte, buffer, strlen(buffer));
+		return true;
+	} else if (vte->mouse_mode == TSM_VTE_MOUSE_MODE_SGR && vte->mouse_event) {
+		if (event & TSM_MOUSE_EVENT_MOVED) {
+			if (cell_x == vte->mouse_last_col && cell_y == vte->mouse_last_row) {
+				return false;
+			}
+
+			reply_flags = 35;
+			pressed = true;
+
+			vte->mouse_last_col = cell_x;
+			vte->mouse_last_row = cell_y;
+		}
+
+		snprintf((char*) &buffer, sizeof(buffer), "\e[<%d;%d;%d%c", reply_flags, cell_x, cell_y, pressed ? 'M' : 'm');
+
+		vte_write(vte, buffer, strlen(buffer));
+		return true;
+	} else if (vte->mouse_mode == TSM_VTE_MOUSE_MODE_PIXEL && vte->mouse_event) {
+		if (event == TSM_MOUSE_EVENT_MOVED) {
+			reply_flags = 35;
+			pressed = true;
+		}
+
+		snprintf((char*) &buffer, sizeof(buffer), "\e[<%d;%d;%d%c", reply_flags, pixel_x, pixel_y, pressed ? 'M' : 'm');
+
+		vte_write(vte, buffer, strlen(buffer));
+		return true;
+	}
+
 	return false;
 }
diff -pruN 4.0.2-0.4/test/CMakeLists.txt 4.1.0-1/test/CMakeLists.txt
--- 4.0.2-0.4/test/CMakeLists.txt	2022-02-18 06:07:59.000000000 +0000
+++ 4.1.0-1/test/CMakeLists.txt	2025-07-02 07:37:01.000000000 +0000
@@ -41,10 +41,22 @@ libtsm_add_test(test_screen
         check::check
 )
 
+libtsm_add_test(test_selection
+    LINK_LIBRARIES
+        tsm_test
+        check::check
+)
+
 libtsm_add_test(test_vte
     LINK_LIBRARIES
         tsm_test
         check::check
+)
+
+libtsm_add_test(test_vte_mouse
+    LINK_LIBRARIES
+        tsm_test
+        check::check
 )
 
 # This is only a quick sanity check that verifies the
diff -pruN 4.0.2-0.4/test/test_common.h 4.1.0-1/test/test_common.h
--- 4.0.2-0.4/test/test_common.h	2022-02-18 06:07:59.000000000 +0000
+++ 4.1.0-1/test/test_common.h	2025-07-02 07:37:01.000000000 +0000
@@ -56,6 +56,8 @@
 								\
 		tc = tcase_create(#_name);			\
 
+#define CHECKED_FIXTURE(_setup, _teardown) tcase_add_checked_fixture(tc, _setup, _teardown);
+#define FIXTURE(_setup, _teardown) tcase_add_fixture(tc, _setup, _teardown);
 #define TEST(_name) tcase_add_test(tc, _name);
 
 #define TEST_END_CASE						\
@@ -103,4 +105,20 @@ static inline int test_run_suite(Suite *
 		return test_run_suite(_suite); \
 	}
 
+#ifndef ck_assert_mem_eq
+#include <string.h>
+#define ck_assert_mem_eq(_x, _y, _len) \
+	ck_assert(memcmp((_x), (_y), (_len)) == 0)
+#define ck_assert_mem_ne(_x, _y, _len) \
+	ck_assert(memcmp((_x), (_y), (_len)) != 0)
+#define ck_assert_mem_lt(_x, _y, _len) \
+	ck_assert(memcmp((_x), (_y), (_len)) < 0)
+#define ck_assert_mem_le(_x, _y, _len) \
+	ck_assert(memcmp((_x), (_y), (_len)) <= 0)
+#define ck_assert_mem_gt(_x, _y, _len) \
+	ck_assert(memcmp((_x), (_y), (_len)) > 0)
+#define ck_assert_mem_ge(_x, _y, _len) \
+	ck_assert(memcmp((_x), (_y), (_len)) >= 0)
+#endif
+
 #endif /* TEST_COMMON_H */
diff -pruN 4.0.2-0.4/test/test_screen.c 4.1.0-1/test/test_screen.c
--- 4.0.2-0.4/test/test_screen.c	2022-02-18 06:07:59.000000000 +0000
+++ 4.1.0-1/test/test_screen.c	2025-07-02 07:37:01.000000000 +0000
@@ -133,10 +133,141 @@ START_TEST(test_screen_null)
 }
 END_TEST
 
+START_TEST(test_screen_resize_alt_colors)
+{
+	struct tsm_screen *screen;
+	struct line *line;
+	int r, y, x;
+	struct tsm_screen_attr *attr;
+	struct tsm_screen_attr new_attr;
+
+	r = tsm_screen_new(&screen, NULL, NULL);
+	ck_assert_int_eq(r, 0);
+
+	/* start with an initial 2x2 screen */
+	r = tsm_screen_resize(screen, 2, 2);
+	ck_assert_int_eq(r, 0);
+
+	/* switch to alternate screen */
+	tsm_screen_set_flags(screen, TSM_SCREEN_ALTERNATE);
+
+	/* change background color to red */
+	bzero(&new_attr, sizeof(new_attr));
+	new_attr.br = 255;
+	new_attr.bg = 0;
+	new_attr.bb = 0;
+
+	tsm_screen_set_def_attr(screen, &new_attr);
+	tsm_screen_erase_screen(screen, false);
+
+	/* now all cells should be red */
+	for (y = 0; y < screen->size_y; y++) {
+		line = screen->lines[y];
+		for (x = 0; x < screen->size_x; x++) {
+			attr = &line->cells[x].attr;
+			ck_assert_int_eq(attr->br, 255);
+			ck_assert_int_eq(attr->bg, 0);
+			ck_assert_int_eq(attr->bb, 0);
+		}
+	}
+
+	/* enlarge to 4x4 while on alternate screen */
+	r = tsm_screen_resize(screen, 4, 4);
+	ck_assert_int_eq(r, 0);
+
+	/* leave alternate screen */
+	tsm_screen_reset_flags(screen, TSM_SCREEN_ALTERNATE);
+
+	/* now all cells should be black (including the new ones) */
+	for (y = 0; y < screen->size_y; y++) {
+		line = screen->lines[y];
+		for (x = 0; x < screen->size_x; x++) {
+			attr = &line->cells[x].attr;
+			ck_assert_int_eq(attr->br, 0);
+			ck_assert_int_eq(attr->bg, 0);
+			ck_assert_int_eq(attr->bb, 0);
+		}
+	}
+
+	tsm_screen_unref(screen);
+	screen = NULL;
+}
+END_TEST
+
+START_TEST(test_screen_sb_get_line_pos)
+{
+	struct tsm_screen *screen;
+	int r;
+
+	r = tsm_screen_new(&screen, NULL, NULL);
+	ck_assert_int_eq(r, 0);
+
+	r = tsm_screen_resize(screen, 5, 5);
+	ck_assert_int_eq(r, 0);
+
+	tsm_screen_set_max_sb(screen, 5);
+
+	ck_assert_int_eq(tsm_screen_sb_get_line_count(screen), 0);
+	ck_assert_int_eq(tsm_screen_sb_get_line_pos(screen), 0);
+
+	/* fill up screen */
+	tsm_screen_newline(screen);
+	tsm_screen_newline(screen);
+	tsm_screen_newline(screen);
+	tsm_screen_newline(screen);
+
+	/* create first entry in scrollback buffer */
+	tsm_screen_newline(screen);
+
+	ck_assert_int_eq(tsm_screen_sb_get_line_count(screen), 1);
+	ck_assert_int_eq(tsm_screen_sb_get_line_pos(screen), 1);
+
+	tsm_screen_newline(screen);
+
+	ck_assert_int_eq(tsm_screen_sb_get_line_count(screen), 2);
+	ck_assert_int_eq(tsm_screen_sb_get_line_pos(screen), 2);
+
+	tsm_screen_newline(screen);
+
+	ck_assert_int_eq(tsm_screen_sb_get_line_count(screen), 3);
+	ck_assert_int_eq(tsm_screen_sb_get_line_pos(screen), 3);
+
+	/* scroll up by one (third line of sb) */
+	tsm_screen_sb_up(screen, 1);
+	ck_assert_int_eq(tsm_screen_sb_get_line_count(screen), 3);
+	ck_assert_int_eq(tsm_screen_sb_get_line_pos(screen), 2);
+
+	/* scroll up by one (second line of sb) */
+	tsm_screen_sb_up(screen, 1);
+	ck_assert_int_eq(tsm_screen_sb_get_line_count(screen), 3);
+	ck_assert_int_eq(tsm_screen_sb_get_line_pos(screen), 1);
+
+	/* scroll up by one (first line of sb) */
+	tsm_screen_sb_up(screen, 1);
+	ck_assert_int_eq(tsm_screen_sb_get_line_count(screen), 3);
+	ck_assert_int_eq(tsm_screen_sb_get_line_pos(screen), 0);
+
+	/* scroll up beyond top end of buffer */
+	tsm_screen_sb_up(screen, 1);
+	ck_assert_int_eq(tsm_screen_sb_get_line_count(screen), 3);
+	ck_assert_int_eq(tsm_screen_sb_get_line_pos(screen), 0);
+
+	/* scroll out of scrollback buffer */
+	tsm_screen_sb_down(screen, 3);
+	ck_assert_int_eq(tsm_screen_sb_get_line_count(screen), 3);
+	ck_assert_int_eq(tsm_screen_sb_get_line_pos(screen), 3);
+
+	tsm_screen_unref(screen);
+	screen = NULL;
+}
+END_TEST
+
 
 TEST_DEFINE_CASE(misc)
 	TEST(test_screen_init)
 	TEST(test_screen_null)
+	TEST(test_screen_resize_alt_colors)
+	TEST(test_screen_sb_get_line_pos)
 TEST_END_CASE
 
 TEST_DEFINE(
diff -pruN 4.0.2-0.4/test/test_selection.c 4.1.0-1/test/test_selection.c
--- 4.0.2-0.4/test/test_selection.c	1970-01-01 00:00:00.000000000 +0000
+++ 4.1.0-1/test/test_selection.c	2025-07-02 07:37:01.000000000 +0000
@@ -0,0 +1,860 @@
+/*
+ * TSM - Selection Copy Tests
+ *
+ * Copyright (c) 2022 Andreas Heck <aheck@gmx.de>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include "test_common.h"
+#include "libtsm.h"
+#include "libtsm-int.h"
+
+static void write_string(struct tsm_screen *screen, const char *str)
+{
+	struct tsm_screen_attr attr;
+	int i;
+
+	bzero(&attr, sizeof(attr));
+	attr.fccode = 37; /* white */
+	attr.bccode = 40; /* black */
+
+	for (i = 0; str[i]; i++) {
+		tsm_screen_write(screen, str[i], &attr);
+	}
+}
+
+START_TEST(test_screen_copy_incomplete)
+{
+	struct tsm_screen *screen;
+	int r;
+	char *str = NULL;
+
+	r = tsm_screen_new(&screen, NULL, NULL);
+	ck_assert_int_eq(r, 0);
+
+	r = tsm_screen_resize(screen, 80, 40);
+	ck_assert_int_eq(r, 0);
+
+	tsm_screen_newline(screen);
+	write_string(screen, "   Hello World!");
+
+	/* select start but don't end selection and copy it */
+	tsm_screen_selection_start(screen, 3, 1);
+
+	r = tsm_screen_selection_copy(screen, &str);
+	ck_assert_ptr_ne(NULL, str);
+	ck_assert_int_eq(1, r); // strlen == 1?
+	ck_assert_str_eq("H", str);
+	free(str);
+	str = NULL;
+
+	tsm_screen_unref(screen);
+	screen = NULL;
+}
+
+START_TEST(test_screen_copy_one_cell)
+{
+	struct tsm_screen *screen;
+	int r;
+	char *str = NULL;
+
+	r = tsm_screen_new(&screen, NULL, NULL);
+	ck_assert_int_eq(r, 0);
+
+	r = tsm_screen_resize(screen, 80, 40);
+	ck_assert_int_eq(r, 0);
+
+	tsm_screen_newline(screen);
+	write_string(screen, "   Hello World!");
+
+	/* select start, end selection on same cell and copy it */
+	tsm_screen_selection_start(screen, 3, 1);
+	tsm_screen_selection_target(screen, 3, 1);
+
+	r = tsm_screen_selection_copy(screen, &str);
+	ck_assert_ptr_ne(NULL, str);
+	ck_assert_int_eq(1, r); // strlen == 1?
+	ck_assert_str_eq("H", str);
+	free(str);
+	str = NULL;
+
+	tsm_screen_unref(screen);
+	screen = NULL;
+}
+
+START_TEST(test_screen_copy_line)
+{
+	struct tsm_screen *screen;
+	int r;
+	char *str = NULL;
+
+	r = tsm_screen_new(&screen, NULL, NULL);
+	ck_assert_int_eq(r, 0);
+
+	r = tsm_screen_resize(screen, 80, 40);
+	ck_assert_int_eq(r, 0);
+
+	tsm_screen_newline(screen);
+	write_string(screen, "   Hello World!");
+	tsm_screen_newline(screen);
+	write_string(screen, "Filler Text");
+	tsm_screen_newline(screen);
+
+	/* select "Hello World!" from left to right and copy it */
+	tsm_screen_selection_start(screen, 3, 1);
+	tsm_screen_selection_target(screen, 14, 1);
+
+	r = tsm_screen_selection_copy(screen, &str);
+	ck_assert_ptr_ne(NULL, str);
+	const char *expected = "Hello World!";
+	ck_assert_int_eq(strlen(expected), r);
+	ck_assert_str_eq(expected, str);
+	free(str);
+	str = NULL;
+
+	/* select "Hello" from left to right and copy it */
+	tsm_screen_selection_reset(screen);
+	tsm_screen_selection_start(screen, 3, 1);
+	tsm_screen_selection_target(screen, 7, 1);
+
+	r = tsm_screen_selection_copy(screen, &str);
+	ck_assert_ptr_ne(NULL, str);
+	ck_assert_str_eq("Hello", str);
+	free(str);
+	str = NULL;
+
+	/* select "   Hello" from left to right and copy it */
+	tsm_screen_selection_reset(screen);
+	tsm_screen_selection_start(screen, 0, 1);
+	tsm_screen_selection_target(screen, 7, 1);
+
+	r = tsm_screen_selection_copy(screen, &str);
+	ck_assert_ptr_ne(NULL, str);
+	ck_assert_str_eq("   Hello", str);
+	free(str);
+	str = NULL;
+
+	/* select "Hello World!" from right to left and copy it */
+	tsm_screen_selection_reset(screen);
+	tsm_screen_selection_start(screen, 14, 1);
+	tsm_screen_selection_target(screen, 3, 1);
+
+	r = tsm_screen_selection_copy(screen, &str);
+	ck_assert_ptr_ne(NULL, str);
+	ck_assert_str_eq("Hello World!", str);
+	free(str);
+	str = NULL;
+
+	/* select "Hello" from right to left and copy it */
+	tsm_screen_selection_reset(screen);
+	tsm_screen_selection_start(screen, 7, 1);
+	tsm_screen_selection_target(screen, 3, 1);
+
+	r = tsm_screen_selection_copy(screen, &str);
+	ck_assert_ptr_ne(NULL, str);
+	ck_assert_str_eq("Hello", str);
+	free(str);
+	str = NULL;
+
+	tsm_screen_unref(screen);
+	screen = NULL;
+}
+END_TEST
+
+START_TEST(test_screen_copy_line_scrolled)
+{
+	struct tsm_screen *screen;
+	int r, i;
+	char *str = NULL;
+
+	r = tsm_screen_new(&screen, NULL, NULL);
+	ck_assert_int_eq(r, 0);
+
+	r = tsm_screen_resize(screen, 80, 40);
+	ck_assert_int_eq(r, 0);
+
+	for (int i = 0; i < 39; i++) {
+		tsm_screen_newline(screen);
+	}
+
+	write_string(screen, "   Hello World!");
+
+	/* select "Hello World!" from left to right and copy it */
+	tsm_screen_selection_start(screen, 3, 39);
+	tsm_screen_selection_target(screen, 14, 39);
+
+	ck_assert_int_eq(screen->sel_start.x, 3);
+	ck_assert_int_eq(screen->sel_start.y, 39);
+	ck_assert_int_eq(screen->sel_end.x, 14);
+	ck_assert_int_eq(screen->sel_end.y, 39);
+
+	/* force the selected text to scroll up */
+	tsm_screen_newline(screen);
+	tsm_screen_newline(screen);
+	tsm_screen_newline(screen);
+	tsm_screen_newline(screen);
+	tsm_screen_newline(screen);
+	tsm_screen_newline(screen);
+	tsm_screen_newline(screen);
+
+	ck_assert_int_eq(screen->sel_start.x, 3);
+	ck_assert_int_eq(screen->sel_start.y, 32);
+	ck_assert_int_eq(screen->sel_end.x, 14);
+	ck_assert_int_eq(screen->sel_end.y, 32);
+
+	r = tsm_screen_selection_copy(screen, &str);
+	ck_assert_ptr_ne(NULL, str);
+	ck_assert_str_eq("Hello World!", str);
+	free(str);
+	str = NULL;
+
+	tsm_screen_unref(screen);
+	screen = NULL;
+}
+END_TEST
+
+START_TEST(test_screen_copy_lines)
+{
+	struct tsm_screen *screen;
+	int r;
+	char *str = NULL;
+
+	r = tsm_screen_new(&screen, NULL, NULL);
+	ck_assert_int_eq(r, 0);
+
+	r = tsm_screen_resize(screen, 80, 40);
+	ck_assert_int_eq(r, 0);
+
+	tsm_screen_newline(screen);
+	write_string(screen, "   Hello World!");
+	tsm_screen_newline(screen);
+	write_string(screen, "This is a copy test");
+	tsm_screen_newline(screen);
+	write_string(screen, "for a selection with multiple lines.");
+	tsm_screen_newline(screen);
+	write_string(screen, "All of them are on screen (not in the sb).------");
+	tsm_screen_newline(screen);
+
+	/* Select all text excluding the first 3 spaces and the trailing '-' chars from top left to bottom right and copy it */
+	tsm_screen_selection_start(screen, 3, 1);
+	tsm_screen_selection_target(screen, 41, 4);
+
+	r = tsm_screen_selection_copy(screen, &str);
+	ck_assert_ptr_ne(NULL, str);
+	const char *expected = "Hello World!\nThis is a copy test\nfor a selection with multiple lines.\nAll of them are on screen (not in the sb).";
+	ck_assert_int_eq(strlen(expected), r);
+	ck_assert_str_eq(expected, str);
+	free(str);
+	str = NULL;
+
+	/* Select "This is a copy test\nfor a selection" from top left and to bottom right copy it */
+	tsm_screen_reset(screen);
+	tsm_screen_selection_start(screen, 0, 2);
+	tsm_screen_selection_target(screen, 14, 3);
+
+	r = tsm_screen_selection_copy(screen, &str);
+	ck_assert_ptr_ne(NULL, str);
+	ck_assert_str_eq("This is a copy test\nfor a selection", str);
+	free(str);
+	str = NULL;
+
+	/* Select all text excluding the first 3 spaces and the trailing '-' chars from bottom right to top left and copy it */
+	tsm_screen_reset(screen);
+	tsm_screen_selection_start(screen, 41, 4);
+	tsm_screen_selection_target(screen, 3, 1);
+
+	r = tsm_screen_selection_copy(screen, &str);
+	ck_assert_ptr_ne(NULL, str);
+	ck_assert_str_eq("Hello World!\nThis is a copy test\nfor a selection with multiple lines.\nAll of them are on screen (not in the sb).", str);
+	free(str);
+	str = NULL;
+
+	tsm_screen_unref(screen);
+	screen = NULL;
+}
+END_TEST
+
+START_TEST(test_screen_copy_lines_scrolled)
+{
+	struct tsm_screen *screen;
+	int r, i;
+	char *str = NULL;
+
+	r = tsm_screen_new(&screen, NULL, NULL);
+	ck_assert_int_eq(r, 0);
+
+	r = tsm_screen_resize(screen, 80, 40);
+	ck_assert_int_eq(r, 0);
+
+	for (int i = 0; i < 37; i++) {
+		tsm_screen_newline(screen);
+	}
+	tsm_screen_newline(screen);
+
+	write_string(screen, "   Hello World!");
+	tsm_screen_newline(screen);
+	write_string(screen, "Line 2");
+	tsm_screen_newline(screen);
+	write_string(screen, "Line 3");
+
+	/* select "Hello World!" from left to right and copy it */
+	tsm_screen_selection_start(screen, 3, 37);
+	tsm_screen_selection_target(screen, 5, 39);
+
+	ck_assert_int_eq(screen->sel_start.x, 3);
+	ck_assert_int_eq(screen->sel_start.y, 37);
+	ck_assert_int_eq(screen->sel_end.x, 5);
+	ck_assert_int_eq(screen->sel_end.y, 39);
+
+	/* force the selected text to scroll up */
+	tsm_screen_newline(screen);
+	tsm_screen_newline(screen);
+	tsm_screen_newline(screen);
+	tsm_screen_newline(screen);
+	tsm_screen_newline(screen);
+	tsm_screen_newline(screen);
+	tsm_screen_newline(screen);
+
+	ck_assert_int_eq(screen->sel_start.x, 3);
+	ck_assert_int_eq(screen->sel_start.y, 30);
+	ck_assert_int_eq(screen->sel_end.x, 5);
+	ck_assert_int_eq(screen->sel_end.y, 32);
+
+	r = tsm_screen_selection_copy(screen, &str);
+	ck_assert_ptr_ne(NULL, str);
+	ck_assert_str_eq("Hello World!\nLine 2\nLine 3", str);
+	free(str);
+	str = NULL;
+
+	tsm_screen_unref(screen);
+	screen = NULL;
+}
+END_TEST
+
+START_TEST(test_screen_copy_line_sb)
+{
+	struct tsm_screen *screen;
+	int r;
+	char *str = NULL;
+	int i;
+
+	r = tsm_screen_new(&screen, NULL, NULL);
+	ck_assert_int_eq(r, 0);
+
+	tsm_screen_set_max_sb(screen, 10);
+
+	r = tsm_screen_resize(screen, 80, 40);
+	ck_assert_int_eq(r, 0);
+
+	tsm_screen_newline(screen);
+	write_string(screen, "   Hello World!");
+	tsm_screen_newline(screen);
+	write_string(screen, "Filler Text");
+	tsm_screen_newline(screen);
+
+	for (i = 0; i < 40; i++) {
+		tsm_screen_newline(screen);
+	}
+
+	tsm_screen_sb_up(screen, 4);
+
+	/* select "Hello World!" from left to right and copy it */
+	tsm_screen_selection_start(screen, 3, 1);
+	tsm_screen_selection_target(screen, 14, 1);
+
+	r = tsm_screen_selection_copy(screen, &str);
+	ck_assert_ptr_ne(NULL, str);
+	ck_assert_str_eq("Hello World!", str);
+	free(str);
+	str = NULL;
+
+	/* select "Hello" from left to right and copy it */
+	tsm_screen_selection_reset(screen);
+	tsm_screen_selection_start(screen, 3, 1);
+	tsm_screen_selection_target(screen, 7, 1);
+
+	r = tsm_screen_selection_copy(screen, &str);
+	ck_assert_ptr_ne(NULL, str);
+	ck_assert_str_eq("Hello", str);
+	free(str);
+	str = NULL;
+
+	/* select "   Hello" from left to right and copy it */
+	tsm_screen_selection_reset(screen);
+	tsm_screen_selection_start(screen, 0, 1);
+	tsm_screen_selection_target(screen, 7, 1);
+
+	r = tsm_screen_selection_copy(screen, &str);
+	ck_assert_ptr_ne(NULL, str);
+	ck_assert_str_eq("   Hello", str);
+	free(str);
+	str = NULL;
+
+	/* select "Hello World!" from right to left and copy it */
+	tsm_screen_selection_reset(screen);
+	tsm_screen_selection_start(screen, 14, 1);
+	tsm_screen_selection_target(screen, 3, 1);
+
+	r = tsm_screen_selection_copy(screen, &str);
+	ck_assert_ptr_ne(NULL, str);
+	ck_assert_str_eq("Hello World!", str);
+	free(str);
+	str = NULL;
+
+	/* select "Hello" from right to left and copy it */
+	tsm_screen_selection_reset(screen);
+	tsm_screen_selection_start(screen, 7, 1);
+	tsm_screen_selection_target(screen, 3, 1);
+
+	r = tsm_screen_selection_copy(screen, &str);
+	ck_assert_ptr_ne(NULL, str);
+	ck_assert_str_eq("Hello", str);
+	free(str);
+	str = NULL;
+
+	tsm_screen_unref(screen);
+	screen = NULL;
+}
+END_TEST
+
+START_TEST(test_screen_copy_line_sb_scrolled)
+{
+	struct tsm_screen *screen;
+	int r, i;
+	char *str = NULL;
+
+	r = tsm_screen_new(&screen, NULL, NULL);
+	ck_assert_int_eq(r, 0);
+
+	r = tsm_screen_resize(screen, 80, 40);
+	ck_assert_int_eq(r, 0);
+
+	/*
+	 * Create a selection of text in one line on the main screen, scroll it
+	 * into the sb and copy it.
+	 */
+
+	tsm_screen_set_max_sb(screen, 10);
+
+	write_string(screen, "   Hello World!");
+
+	/* select "Hello World!" from left to right */
+	tsm_screen_selection_start(screen, 3, 0);
+	tsm_screen_selection_target(screen, 14, 0);
+
+	ck_assert_int_eq(screen->sel_start.x, 3);
+	ck_assert_int_eq(screen->sel_start.y, 0);
+	ck_assert_int_eq(screen->sel_end.x, 14);
+	ck_assert_int_eq(screen->sel_end.y, 0);
+
+	/* force the selected text to scroll up */
+	for (int i = 0; i < 40; i++) {
+		tsm_screen_newline(screen);
+	}
+
+	ck_assert_int_eq(screen->sel_start.x, 3);
+	ck_assert_int_eq(screen->sel_start.y, -1);
+	ck_assert_ptr_ne(screen->sel_start.line, NULL);
+	ck_assert_int_eq(screen->sel_end.x, 14);
+	ck_assert_int_eq(screen->sel_end.y, -1);
+	ck_assert_ptr_ne(screen->sel_end.line, NULL);
+
+	r = tsm_screen_selection_copy(screen, &str);
+	ck_assert_ptr_ne(NULL, str);
+	ck_assert_str_eq("Hello World!", str);
+	free(str);
+	str = NULL;
+
+	/*
+	 * Create a selection of the text in the sb, scroll it further into the
+	 * sb and copy it.
+	 */
+
+	tsm_screen_selection_reset(screen);
+	tsm_screen_sb_up(screen, 1);
+
+	tsm_screen_selection_start(screen, 3, 0);
+	tsm_screen_selection_target(screen, 14, 0);
+
+	ck_assert_int_eq(screen->sel_start.x, 3);
+	ck_assert_int_eq(screen->sel_start.y, 0);
+	ck_assert_ptr_ne(screen->sel_start.line, NULL);
+	ck_assert_int_eq(screen->sel_end.x, 14);
+	ck_assert_int_eq(screen->sel_end.y, 0);
+	ck_assert_ptr_ne(screen->sel_end.line, NULL);
+
+	tsm_screen_newline(screen);
+	tsm_screen_newline(screen);
+	tsm_screen_newline(screen);
+
+	ck_assert_int_eq(screen->sel_start.x, 3);
+	ck_assert_int_eq(screen->sel_start.y, 0);
+	ck_assert_ptr_ne(screen->sel_start.line, NULL);
+	ck_assert_int_eq(screen->sel_end.x, 14);
+	ck_assert_int_eq(screen->sel_end.y, 0);
+	ck_assert_ptr_ne(screen->sel_end.line, NULL);
+
+	r = tsm_screen_selection_copy(screen, &str);
+	ck_assert_ptr_ne(NULL, str);
+	ck_assert_str_eq("Hello World!", str);
+	free(str);
+	str = NULL;
+
+	tsm_screen_unref(screen);
+	screen = NULL;
+}
+END_TEST
+
+START_TEST(test_screen_copy_line_sb_scrolled_invalid)
+{
+	struct tsm_screen *screen;
+	int r, i;
+	char *str = NULL;
+
+	r = tsm_screen_new(&screen, NULL, NULL);
+	ck_assert_int_eq(r, 0);
+
+	r = tsm_screen_resize(screen, 80, 40);
+	ck_assert_int_eq(r, 0);
+
+	/*
+	 * Disable the sb. If a selection is moved out of the sb it becomes invalid.
+	 * Copying it should return an empty string.
+	 */
+	tsm_screen_set_max_sb(screen, 0);
+
+	write_string(screen, "   Hello World!");
+
+	/* select "Hello World!" from left to right */
+	tsm_screen_selection_start(screen, 3, 0);
+	tsm_screen_selection_target(screen, 14, 0);
+
+	ck_assert_int_eq(screen->sel_start.x, 3);
+	ck_assert_int_eq(screen->sel_start.y, 0);
+	ck_assert_int_eq(screen->sel_end.x, 14);
+	ck_assert_int_eq(screen->sel_end.y, 0);
+
+	/* force the selected text to scroll up */
+	for (int i = 0; i < 40; i++) {
+		tsm_screen_newline(screen);
+	}
+
+	/*
+	 * sel_start.y == -1, sel_start.line == NULL
+	 * sel_end.y == -1, sel_end.line == NULL
+	 *
+	 * => Invalid selection
+	 */
+	ck_assert_int_eq(screen->sel_start.x, 3);
+	ck_assert_int_eq(screen->sel_start.y, -1);
+	ck_assert_ptr_eq(screen->sel_start.line, NULL);
+	ck_assert_int_eq(screen->sel_end.x, 14);
+	ck_assert_int_eq(screen->sel_end.y, -1);
+	ck_assert_ptr_eq(screen->sel_end.line, NULL);
+
+	r = tsm_screen_selection_copy(screen, &str);
+	ck_assert_ptr_ne(NULL, str);
+	ck_assert_str_eq("", str);
+	free(str);
+	str = NULL;
+
+	tsm_screen_unref(screen);
+	screen = NULL;
+}
+END_TEST
+
+START_TEST(test_screen_copy_lines_sb)
+{
+	struct tsm_screen *screen;
+	int r;
+	char *str = NULL;
+	int i;
+
+	r = tsm_screen_new(&screen, NULL, NULL);
+	ck_assert_int_eq(r, 0);
+
+	tsm_screen_set_max_sb(screen, 10);
+
+	r = tsm_screen_resize(screen, 80, 40);
+	ck_assert_int_eq(r, 0);
+
+	tsm_screen_newline(screen);
+	write_string(screen, "   Hello World!");
+	tsm_screen_newline(screen);
+	write_string(screen, "This is a copy test");
+	tsm_screen_newline(screen);
+	write_string(screen, "for a selection with multiple lines.");
+	tsm_screen_newline(screen);
+	write_string(screen, "All of them are on screen (not in the sb).------");
+	tsm_screen_newline(screen);
+
+	write_string(screen, "Text not in SB");
+	tsm_screen_newline(screen);
+	write_string(screen, "More Text not in SB");
+	tsm_screen_newline(screen);
+
+
+	for (i = 0; i < 38; i++) {
+		tsm_screen_newline(screen);
+	}
+
+	tsm_screen_sb_up(screen, 6);
+
+	/* Select all text excluding the first 3 spaces and the trailing '-' chars from top left to bottom right and copy it */
+	tsm_screen_selection_start(screen, 3, 1);
+	tsm_screen_selection_target(screen, 41, 4);
+
+	r = tsm_screen_selection_copy(screen, &str);
+	ck_assert_ptr_ne(NULL, str);
+	ck_assert_str_eq("Hello World!\nThis is a copy test\nfor a selection with multiple lines.\nAll of them are on screen (not in the sb).", str);
+	free(str);
+	str = NULL;
+
+	/* Select "This is a copy test\nfor a selection" from top left and to bottom right copy it */
+	tsm_screen_reset(screen);
+	tsm_screen_selection_start(screen, 0, 2);
+	tsm_screen_selection_target(screen, 14, 3);
+
+	r = tsm_screen_selection_copy(screen, &str);
+	ck_assert_ptr_ne(NULL, str);
+	ck_assert_str_eq("This is a copy test\nfor a selection", str);
+	free(str);
+	str = NULL;
+
+	/* Select all text excluding the first 3 spaces and the trailing '-' chars from bottom right to top left and copy it */
+	tsm_screen_reset(screen);
+	tsm_screen_selection_start(screen, 41, 4);
+	tsm_screen_selection_target(screen, 3, 1);
+
+	r = tsm_screen_selection_copy(screen, &str);
+	ck_assert_ptr_ne(NULL, str);
+	ck_assert_str_eq("Hello World!\nThis is a copy test\nfor a selection with multiple lines.\nAll of them are on screen (not in the sb).", str);
+	free(str);
+	str = NULL;
+
+	/* Select from scroll back buffer and the screen from top left to bottom right and copy it */
+	tsm_screen_reset(screen);
+	tsm_screen_selection_start(screen, 0, 4);
+	tsm_screen_selection_target(screen, 18, 6);
+
+	r = tsm_screen_selection_copy(screen, &str);
+	ck_assert_ptr_ne(NULL, str);
+	ck_assert_str_eq("All of them are on screen (not in the sb).------\nText not in SB\nMore Text not in SB", str);
+	free(str);
+	str = NULL;
+
+	/* Select from scroll back buffer and the screen from bottom right to top left and copy it */
+	tsm_screen_reset(screen);
+	tsm_screen_selection_start(screen, 18, 6);
+	tsm_screen_selection_target(screen, 0, 4);
+
+	r = tsm_screen_selection_copy(screen, &str);
+	ck_assert_ptr_ne(NULL, str);
+	ck_assert_str_eq("All of them are on screen (not in the sb).------\nText not in SB\nMore Text not in SB", str);
+	free(str);
+	str = NULL;
+
+	/* Select from scroll back buffer and the screen from bottom right to top left and copy it */
+	tsm_screen_reset(screen);
+	tsm_screen_selection_start(screen, 8, 6);
+	tsm_screen_selection_target(screen, 7, 4);
+
+	r = tsm_screen_selection_copy(screen, &str);
+	ck_assert_ptr_ne(NULL, str);
+	ck_assert_str_eq("them are on screen (not in the sb).------\nText not in SB\nMore Text", str);
+	free(str);
+	str = NULL;
+
+	tsm_screen_unref(screen);
+	screen = NULL;
+}
+END_TEST
+
+START_TEST(test_screen_copy_lines_sb_scrolled)
+{
+	struct tsm_screen *screen;
+	int r, i;
+	char *str = NULL;
+
+	r = tsm_screen_new(&screen, NULL, NULL);
+	ck_assert_int_eq(r, 0);
+
+	r = tsm_screen_resize(screen, 80, 40);
+	ck_assert_int_eq(r, 0);
+
+	/*
+	 * Create a selection of text on the main screen, scroll it into the sb
+	 * and copy it.
+	 */
+
+	tsm_screen_set_max_sb(screen, 10);
+
+	write_string(screen, "   Hello World!");
+	tsm_screen_newline(screen);
+	write_string(screen, "Line 2");
+	tsm_screen_newline(screen);
+	write_string(screen, "Line 3");
+
+	/* select "Hello World!" from left to right */
+	tsm_screen_selection_start(screen, 3, 0);
+	tsm_screen_selection_target(screen, 5, 2);
+
+	ck_assert_int_eq(screen->sel_start.x, 3);
+	ck_assert_int_eq(screen->sel_start.y, 0);
+	ck_assert_int_eq(screen->sel_end.x, 5);
+	ck_assert_int_eq(screen->sel_end.y, 2);
+
+	/* force the selected text to scroll into the sb */
+	for (int i = 0; i < 40; i++) {
+		tsm_screen_newline(screen);
+	}
+
+	ck_assert_int_eq(screen->sel_start.x, 3);
+	ck_assert_int_eq(screen->sel_start.y, -1);
+	ck_assert_ptr_ne(screen->sel_start.line, NULL);
+	ck_assert_int_eq(screen->sel_end.x, 5);
+	ck_assert_int_eq(screen->sel_end.y, -1);
+	ck_assert_ptr_ne(screen->sel_end.line, NULL);
+	ck_assert_ptr_ne(screen->sel_start.line, screen->sel_end.line);
+
+	r = tsm_screen_selection_copy(screen, &str);
+	ck_assert_ptr_ne(NULL, str);
+	ck_assert_str_eq("Hello World!\nLine 2\nLine 3", str);
+	free(str);
+	str = NULL;
+
+	/*
+	 * Create a selection of the text in the sb, scroll it further into
+	 * the sb and copy it.
+	 */
+
+	tsm_screen_selection_reset(screen);
+	tsm_screen_sb_up(screen, 3);
+
+	tsm_screen_selection_start(screen, 3, 0);
+	tsm_screen_selection_target(screen, 5, 2);
+
+	ck_assert_int_eq(screen->sel_start.x, 3);
+	ck_assert_int_eq(screen->sel_start.y, 0);
+	ck_assert_int_eq(screen->sel_end.x, 5);
+	ck_assert_int_eq(screen->sel_end.y, 0);
+
+	tsm_screen_newline(screen);
+	tsm_screen_newline(screen);
+	tsm_screen_newline(screen);
+
+	ck_assert_int_eq(screen->sel_start.x, 3);
+	ck_assert_int_eq(screen->sel_start.y, 0);
+	ck_assert_ptr_ne(screen->sel_start.line, NULL);
+	ck_assert_int_eq(screen->sel_end.x, 5);
+	ck_assert_int_eq(screen->sel_end.y, 0);
+	ck_assert_ptr_ne(screen->sel_end.line, NULL);
+	ck_assert_ptr_ne(screen->sel_start.line, screen->sel_end.line);
+
+	r = tsm_screen_selection_copy(screen, &str);
+	ck_assert_ptr_ne(NULL, str);
+	ck_assert_str_eq("Hello World!\nLine 2\nLine 3", str);
+	free(str);
+	str = NULL;
+
+	tsm_screen_unref(screen);
+	screen = NULL;
+}
+END_TEST
+
+START_TEST(test_screen_copy_lines_sb_scrolled_cut_off)
+{
+	struct tsm_screen *screen;
+	int r, i;
+	char *str = NULL;
+
+	r = tsm_screen_new(&screen, NULL, NULL);
+	ck_assert_int_eq(r, 0);
+
+	r = tsm_screen_resize(screen, 80, 40);
+	ck_assert_int_eq(r, 0);
+
+	/*
+	 * Disable the sb. If a part of a selection is moved out of the sb it is
+	 * cut off. Copying it should return the remaining part of the selection.
+	 */
+	tsm_screen_set_max_sb(screen, 0);
+
+	write_string(screen, "   Hello World!");
+	tsm_screen_newline(screen);
+	write_string(screen, "Line 2");
+	tsm_screen_newline(screen);
+	write_string(screen, "Line 3");
+
+	/* select "Hello World!" from left to right */
+	tsm_screen_selection_start(screen, 3, 0);
+	tsm_screen_selection_target(screen, 5, 2);
+
+	ck_assert_int_eq(screen->sel_start.x, 3);
+	ck_assert_int_eq(screen->sel_start.y, 0);
+	ck_assert_int_eq(screen->sel_end.x, 5);
+	ck_assert_int_eq(screen->sel_end.y, 2);
+
+	/* force the selected text to scroll up */
+	for (int i = 0; i < 39; i++) {
+		tsm_screen_newline(screen);
+	}
+
+	ck_assert_int_eq(screen->sel_start.x, 3);
+	ck_assert_int_eq(screen->sel_start.y, -1);
+	ck_assert_ptr_eq(screen->sel_start.line, NULL);
+	ck_assert_int_eq(screen->sel_end.x, 5);
+	ck_assert_int_eq(screen->sel_end.y, 0);
+	ck_assert_ptr_eq(screen->sel_end.line, NULL);
+
+	r = tsm_screen_selection_copy(screen, &str);
+	ck_assert_ptr_ne(NULL, str);
+	ck_assert_str_eq("Line 3", str);
+	free(str);
+	str = NULL;
+
+	tsm_screen_unref(screen);
+	screen = NULL;
+}
+END_TEST
+
+TEST_DEFINE_CASE(misc)
+	TEST(test_screen_copy_incomplete)
+	TEST(test_screen_copy_one_cell)
+	TEST(test_screen_copy_line)
+	TEST(test_screen_copy_line_scrolled)
+	TEST(test_screen_copy_lines)
+	TEST(test_screen_copy_lines_scrolled)
+	TEST(test_screen_copy_line_sb)
+	TEST(test_screen_copy_line_sb_scrolled)
+	TEST(test_screen_copy_line_sb_scrolled_invalid)
+	TEST(test_screen_copy_lines_sb)
+	TEST(test_screen_copy_lines_sb_scrolled)
+	TEST(test_screen_copy_lines_sb_scrolled_cut_off)
+TEST_END_CASE
+
+TEST_DEFINE(
+	TEST_SUITE(selection,
+		TEST_CASE(misc),
+		TEST_END
+	)
+)
diff -pruN 4.0.2-0.4/test/test_vte.c 4.1.0-1/test/test_vte.c
--- 4.0.2-0.4/test/test_vte.c	2022-02-18 06:07:59.000000000 +0000
+++ 4.1.0-1/test/test_vte.c	2025-07-02 07:37:01.000000000 +0000
@@ -27,6 +27,9 @@
 #include "libtsm.h"
 #include "test_common.h"
 
+#include <xkbcommon/xkbcommon-keysyms.h>
+#include <stdio.h>
+
 static void log_cb(void *data, const char *file, int line, const char *func, const char *subs,
 				   unsigned int sev, const char *format, va_list args)
 {
@@ -89,6 +92,10 @@ START_TEST(test_vte_null)
 	ck_assert_int_eq(r, -EINVAL);
 
 	tsm_vte_get_def_attr(NULL, NULL);
+	tsm_vte_get_flags(NULL);
+
+	tsm_vte_get_mouse_mode(NULL);
+	tsm_vte_get_mouse_event(NULL);
 
 	tsm_vte_reset(NULL);
 	tsm_vte_hard_reset(NULL);
@@ -157,10 +164,184 @@ START_TEST(test_vte_custom_palette)
 }
 END_TEST
 
+static void checking_write_cb(struct tsm_vte *vte, const char *u8, size_t len, void *data)
+{
+	ck_assert_ptr_ne(vte, NULL);
+	ck_assert_ptr_ne(u8, NULL);
+	ck_assert_uint_gt(len, 0);
+
+	ck_assert_mem_eq(u8, data, len);
+}
+
+START_TEST(test_vte_backspace_key)
+{
+	struct tsm_screen *screen;
+	struct tsm_vte *vte;
+	char expected_output;
+	int r;
+
+	r = tsm_screen_new(&screen, log_cb, NULL);
+	ck_assert_int_eq(r, 0);
+
+	r = tsm_vte_new(&vte, screen, checking_write_cb, &expected_output, log_cb, NULL);
+	ck_assert_int_eq(r, 0);
+
+	expected_output = '\010';
+	r = tsm_vte_handle_keyboard(vte, XKB_KEY_BackSpace, 010, 0, 010);
+	ck_assert(r);
+	r = tsm_vte_handle_keyboard(vte, XKB_KEY_BackSpace, 0177, 0, 0177);
+	ck_assert(r);
+
+	tsm_vte_set_backspace_sends_delete(vte, true);
+
+	expected_output = '\177';
+	r = tsm_vte_handle_keyboard(vte, XKB_KEY_BackSpace, 010, 0, 010);
+	ck_assert(r);
+	r = tsm_vte_handle_keyboard(vte, XKB_KEY_BackSpace, 0177, 0, 0177);
+	ck_assert(r);
+
+	tsm_vte_set_backspace_sends_delete(vte, false);
+
+	expected_output = '\010';
+	r = tsm_vte_handle_keyboard(vte, XKB_KEY_BackSpace, 010, 0, 010);
+	ck_assert(r);
+	r = tsm_vte_handle_keyboard(vte, XKB_KEY_BackSpace, 0177, 0, 0177);
+	ck_assert(r);
+
+	tsm_vte_unref(vte);
+	vte = NULL;
+
+	tsm_screen_unref(screen);
+	screen = NULL;
+}
+END_TEST
+
+START_TEST(test_vte_get_flags)
+{
+	struct tsm_screen *screen;
+	struct tsm_vte *vte;
+	char expected_output;
+	int r, flags;
+
+	r = tsm_screen_new(&screen, log_cb, NULL);
+	ck_assert_int_eq(r, 0);
+
+	r = tsm_vte_new(&vte, screen, checking_write_cb, &expected_output, log_cb, NULL);
+	ck_assert_int_eq(r, 0);
+
+	flags = tsm_vte_get_flags(vte);
+	ck_assert(!(flags & TSM_VTE_FLAG_CURSOR_KEY_MODE));
+
+	// enable cursor key mode
+	tsm_vte_input(vte, "\033[?1h", 5);
+
+	flags = tsm_vte_get_flags(vte);
+	ck_assert(flags & TSM_VTE_FLAG_CURSOR_KEY_MODE);
+
+	tsm_vte_unref(vte);
+	vte = NULL;
+
+	tsm_screen_unref(screen);
+	screen = NULL;
+}
+END_TEST
+
+/* Regression test for https://github.com/Aetf/libtsm/issues/26 */
+START_TEST(test_vte_decrqm_no_reset)
+{
+	struct tsm_screen *screen;
+	struct tsm_vte *vte;
+	int r;
+	unsigned int flags;
+
+	r = tsm_screen_new(&screen, log_cb, NULL);
+	ck_assert_int_eq(r, 0);
+
+	r = tsm_vte_new(&vte, screen, write_cb, NULL, log_cb, NULL);
+	ck_assert_int_eq(r, 0);
+
+	/* switch terminal to alternate screen mode */
+	tsm_vte_input(vte, "\033[?1049h", 8);
+
+	flags = tsm_screen_get_flags(screen);
+	ck_assert(flags & TSM_SCREEN_ALTERNATE);
+
+	/* send DECRQM SRM (12) request */
+	tsm_vte_input(vte, "\033[?12$p", 7);
+
+	/* terminal should still be in alternate screen mode */
+	flags = tsm_screen_get_flags(screen);
+	ck_assert(flags & TSM_SCREEN_ALTERNATE);
+
+	tsm_vte_unref(vte);
+	vte = NULL;
+
+	tsm_screen_unref(screen);
+	screen = NULL;
+}
+END_TEST
+
+#define assert_tsm_screen_cursor_pos(screen, x1, y1)                 \
+	do {                                                             \
+		ck_assert_int_eq(x1, tsm_screen_get_cursor_x(screen));       \
+		ck_assert_int_eq(y1, tsm_screen_get_cursor_y(screen));       \
+	} while(0)
+
+/* test for https://github.com/Aetf/kmscon/issues/78 */
+START_TEST(test_vte_csi_cursor_up_down)
+{
+	struct tsm_screen *screen;
+	struct tsm_vte *vte;
+	int r;
+	int h, w;
+	char csi_cmd[64];
+
+	r = tsm_screen_new(&screen, log_cb, NULL);
+	ck_assert_int_eq(r, 0);
+
+	r = tsm_vte_new(&vte, screen, write_cb, NULL, log_cb, NULL);
+	ck_assert_int_eq(r, 0);
+
+	tsm_vte_input(vte, "\n123", 4);
+	assert_tsm_screen_cursor_pos(screen, 3, 1);
+
+	// cursor move up, first col
+	tsm_vte_input(vte, "\033[1F", 4);
+	assert_tsm_screen_cursor_pos(screen, 0, 0);
+
+	// cursor move down, first col
+	tsm_vte_input(vte, "\033[1E", 4);
+	assert_tsm_screen_cursor_pos(screen, 0, 1);
+
+	h = tsm_screen_get_height(screen);
+	w = tsm_screen_get_width(screen);
+
+	// move cursor up out of screen, should at first line
+	sprintf(csi_cmd, "\033[%dF", h + 10);
+	tsm_vte_input(vte, csi_cmd, strlen(csi_cmd));
+	assert_tsm_screen_cursor_pos(screen, 0, 0);
+
+	// move cursor down out of screen, should at last line
+	sprintf(csi_cmd, "\033[%dE", h + 10);
+	tsm_vte_input(vte, csi_cmd, strlen(csi_cmd));
+	assert_tsm_screen_cursor_pos(screen, 0, h - 1);
+
+	tsm_vte_unref(vte);
+	vte = NULL;
+
+	tsm_screen_unref(screen);
+	screen = NULL;
+}
+END_TEST
+
 TEST_DEFINE_CASE(misc)
 	TEST(test_vte_init)
-    TEST(test_vte_null)
-    TEST(test_vte_custom_palette)
+	TEST(test_vte_null)
+	TEST(test_vte_custom_palette)
+	TEST(test_vte_backspace_key)
+	TEST(test_vte_get_flags)
+	TEST(test_vte_decrqm_no_reset)
+	TEST(test_vte_csi_cursor_up_down)
 TEST_END_CASE
 
 // clang-format off
diff -pruN 4.0.2-0.4/test/test_vte_mouse.c 4.1.0-1/test/test_vte_mouse.c
--- 4.0.2-0.4/test/test_vte_mouse.c	1970-01-01 00:00:00.000000000 +0000
+++ 4.1.0-1/test/test_vte_mouse.c	2025-07-02 07:37:01.000000000 +0000
@@ -0,0 +1,344 @@
+/*
+ * TSM - VTE State Machine Tests
+ *
+ * Copyright (c) 2022 Andreas Heck <aheck@gmx.de>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include "test_common.h"
+#include "libtsm.h"
+#include "libtsm-int.h"
+
+char write_buffer[512];
+
+bool mouse_cb_called = false;
+unsigned int mouse_track_mode = 0;
+bool mouse_track_pixels = false;
+
+static void write_cb(struct tsm_vte *vte, const char *u8, size_t len, void *data)
+{
+	ck_assert_ptr_ne(vte, NULL);
+	ck_assert_ptr_ne(u8, NULL);
+
+	memcpy(&write_buffer, u8, len);
+	write_buffer[len] = '\0';
+}
+
+static void mouse_cb(struct tsm_vte *vte, enum tsm_mouse_track_mode track_mode, bool track_pixels, void *data)
+{
+	mouse_cb_called = true;
+	mouse_track_mode = track_mode;
+	mouse_track_pixels = track_pixels;
+}
+
+struct tsm_screen *screen;
+struct tsm_vte *vte;
+
+void setup()
+{
+	int r;
+
+	r = tsm_screen_new(&screen, NULL, NULL);
+	ck_assert_int_eq(r, 0);
+
+	r = tsm_vte_new(&vte, screen, write_cb, NULL, NULL, NULL);
+	ck_assert_int_eq(r, 0);
+
+	tsm_vte_set_mouse_cb(vte, mouse_cb, NULL);
+
+	mouse_cb_called = false;
+	mouse_track_mode = 0;
+	mouse_track_pixels = false;
+
+	bzero(&write_buffer, sizeof(write_buffer));
+}
+
+void teardown()
+{
+	tsm_vte_unref(vte);
+	vte = NULL;
+
+	tsm_screen_unref(screen);
+	screen = NULL;
+}
+
+START_TEST(test_mouse_cb_x10)
+{
+	char *msg;
+
+	/* Set X10 Mode */
+	msg = "\e[?9h";
+	tsm_vte_input(vte, msg, strlen(msg));
+
+	ck_assert(mouse_cb_called);
+	ck_assert_int_eq(mouse_track_mode, TSM_MOUSE_TRACK_BTN);
+	ck_assert(!mouse_track_pixels);
+}
+END_TEST
+
+START_TEST(test_mouse_x10)
+{
+	char *msg;
+	char *expected;
+	int r;
+
+	/* Set X10 Mode */
+	msg = "\e[?9h";
+	tsm_vte_input(vte, msg, strlen(msg));
+
+	/* left click on left upper cell (0, 0) should be translated to (1, 1) in output */
+	tsm_vte_handle_mouse(vte, 0, 0, 0, 0, 0, TSM_MOUSE_EVENT_PRESSED, 0);
+	expected = "\e[M !!";
+	r = memcmp(&write_buffer, expected, strlen(expected));
+	ck_assert_int_eq(r, 0);
+
+	/* right click on (0, 0) */
+	bzero(&write_buffer, sizeof(write_buffer));
+	tsm_vte_handle_mouse(vte, 0, 0, 0, 0, 2, TSM_MOUSE_EVENT_PRESSED, 0);
+	expected = "\e[M\"!!";
+	r = memcmp(&write_buffer, expected, strlen(expected));
+	ck_assert_int_eq(r, 0);
+
+	/* middle click on (0, 0) */
+	bzero(&write_buffer, sizeof(write_buffer));
+	tsm_vte_handle_mouse(vte, 0, 0, 0, 0, 1, TSM_MOUSE_EVENT_PRESSED, 0);
+	expected = "\e[M!!!";
+	r = memcmp(&write_buffer, expected, strlen(expected));
+	ck_assert_int_eq(r, 0);
+
+	/* left click out of range (299, 279) */
+	bzero(&write_buffer, sizeof(write_buffer));
+	tsm_vte_handle_mouse(vte, 299, 279, 0, 0, 0, TSM_MOUSE_EVENT_PRESSED, 0);
+	expected = "\e[M \xff\xff";
+	r = memcmp(&write_buffer, expected, strlen(expected));
+	ck_assert_int_eq(r, 0);
+}
+END_TEST
+
+START_TEST(test_mouse_cb_sgr)
+{
+	char *msg;
+
+	/* Set SGR Mode */
+	msg = "\e[?1006h";
+	tsm_vte_input(vte, msg, strlen(msg));
+
+	/* mouse_cb should not be called if event type is not set */
+	ck_assert(!mouse_cb_called);
+
+	/* reset for next test */
+	mouse_cb_called = false;
+	mouse_track_mode = 0;
+	mouse_track_pixels = false;
+
+	/* Set Button Events */
+	bzero(&write_buffer, sizeof(write_buffer));
+	msg = "\e[?1002h";
+	tsm_vte_input(vte, msg, strlen(msg));
+
+	ck_assert(mouse_cb_called);
+	ck_assert_int_eq(mouse_track_mode, TSM_MOUSE_TRACK_BTN);
+	ck_assert(!mouse_track_pixels);
+}
+END_TEST
+
+START_TEST(test_mouse_sgr)
+{
+	char *expected;
+	char *msg;
+	int r;
+
+	/* Set SGR Mode and only notify for mouse button clicks */
+	msg = "\e[?1006h\e[?1002h";
+	tsm_vte_input(vte, msg, strlen(msg));
+
+	/* left click on left upper cell (0, 0) should be translated to (1, 1) in output */
+	tsm_vte_handle_mouse(vte, 0, 0, 0, 0, 0, TSM_MOUSE_EVENT_PRESSED, 0);
+	expected = "\e[<0;1;1M";
+	r = memcmp(&write_buffer, expected, strlen(expected));
+	ck_assert_int_eq(r, 0);
+
+	/* button release event for (1, 1) */
+	bzero(&write_buffer, sizeof(write_buffer));
+	tsm_vte_handle_mouse(vte, 0, 0, 0, 0, 0, TSM_MOUSE_EVENT_RELEASED, 0);
+	expected = "\e[<0;1;1m";
+	r = memcmp(&write_buffer, expected, strlen(expected));
+	ck_assert_int_eq(r, 0);
+
+	/* button 1 (middle mouse button) */
+	bzero(&write_buffer, sizeof(write_buffer));
+	tsm_vte_handle_mouse(vte, 0, 0, 0, 0, 1, TSM_MOUSE_EVENT_PRESSED, 0);
+	expected = "\e[<1;1;1M";
+	r = memcmp(&write_buffer, expected, strlen(expected));
+	ck_assert_int_eq(r, 0);
+
+	/* button 2 (right mouse button) */
+	bzero(&write_buffer, sizeof(write_buffer));
+	tsm_vte_handle_mouse(vte, 0, 0, 0, 0, 2, TSM_MOUSE_EVENT_PRESSED, 0);
+	expected = "\e[<2;1;1M";
+	r = memcmp(&write_buffer, expected, strlen(expected));
+	ck_assert_int_eq(r, 0);
+
+	/* button 4 (mouse wheel up scroll) */
+	bzero(&write_buffer, sizeof(write_buffer));
+	tsm_vte_handle_mouse(vte, 0, 0, 0, 0, 4, TSM_MOUSE_EVENT_PRESSED, 0);
+	expected = "\e[<64;1;1M";
+	r = memcmp(&write_buffer, expected, strlen(expected));
+	ck_assert_int_eq(r, 0);
+
+	/* button 5 (mouse wheel down scroll) */
+	bzero(&write_buffer, sizeof(write_buffer));
+	tsm_vte_handle_mouse(vte, 0, 0, 0, 0, 5, TSM_MOUSE_EVENT_PRESSED, 0);
+	expected = "\e[<65;1;1M";
+	r = memcmp(&write_buffer, expected, strlen(expected));
+	ck_assert_int_eq(r, 0);
+
+	/* check for (50, 120) */
+	bzero(&write_buffer, sizeof(write_buffer));
+	tsm_vte_handle_mouse(vte, 49, 119, 0, 0, 0, TSM_MOUSE_EVENT_PRESSED, 0);
+	expected = "\e[<0;50;120M";
+	r = memcmp(&write_buffer, expected, strlen(expected));
+	ck_assert_int_eq(r, 0);
+}
+END_TEST
+
+START_TEST(test_mouse_sgr_cell_change)
+{
+	char *msg;
+	char *expected;
+	int r;
+
+	/* Set SGR Mode and only notify for mouse button clicks */
+	msg = "\e[?1006h\e[?1003h";
+	tsm_vte_input(vte, msg, strlen(msg));
+
+	/* move over (0, 0) */
+	tsm_vte_handle_mouse(vte, 0, 0, 0, 0, 0, TSM_MOUSE_EVENT_MOVED, 0);
+	expected = "\e[<35;1;1M";
+	r = memcmp(&write_buffer, expected, strlen(expected));
+	ck_assert_int_eq(r, 0);
+
+	/* repeated reportings of the same cell should be ignored */
+	bzero(&write_buffer, sizeof(write_buffer));
+	tsm_vte_handle_mouse(vte, 0, 0, 0, 0, 0, TSM_MOUSE_EVENT_MOVED, 0);
+	ck_assert_int_eq(write_buffer[0], 0);
+
+	/* different cells must be reported */
+	bzero(&write_buffer, sizeof(write_buffer));
+	tsm_vte_handle_mouse(vte, 1, 1, 0, 0, 0, TSM_MOUSE_EVENT_MOVED, 0);
+	expected = "\e[<35;2;2M";
+	r = memcmp(&write_buffer, expected, strlen(expected));
+	ck_assert_int_eq(r, 0);
+
+	/* a click must be reported in all cases */
+	bzero(&write_buffer, sizeof(write_buffer));
+	tsm_vte_handle_mouse(vte, 1, 1, 0, 0, 0, TSM_MOUSE_EVENT_PRESSED, 0);
+	expected = "\e[<0;2;2M";
+	r = memcmp(&write_buffer, expected, strlen(expected));
+	ck_assert_int_eq(r, 0);
+}
+END_TEST
+
+START_TEST(test_mouse_cb_pixels)
+{
+	char *msg;
+
+	/* Set Pixel Mode */
+	msg = "\e[?1016h";
+	tsm_vte_input(vte, msg, strlen(msg));
+
+	/* mouse_cb should not be called if event type is not set */
+	ck_assert(!mouse_cb_called);
+
+	/* reset for next test */
+	mouse_cb_called = false;
+	mouse_track_mode = 0;
+	mouse_track_pixels = false;
+
+	/* Set Button Events */
+	bzero(&write_buffer, sizeof(write_buffer));
+	msg = "\e[?1003h";
+	tsm_vte_input(vte, msg, strlen(msg));
+
+	ck_assert(mouse_cb_called);
+	ck_assert_int_eq(mouse_track_mode, TSM_MOUSE_TRACK_ANY);
+	ck_assert(mouse_track_pixels);
+}
+END_TEST
+
+START_TEST(test_mouse_pixels)
+{
+	char *msg;
+	char *expected;
+	int r;
+
+	/* Set SGR Mode and only notify for mouse button clicks */
+	msg = "\e[?1016h\e[?1003h";
+	tsm_vte_input(vte, msg, strlen(msg));
+
+	tsm_vte_handle_mouse(vte, 0, 0, 236, 120, 0, TSM_MOUSE_EVENT_MOVED, 0);
+	expected = "\e[<35;236;120M";
+	r = memcmp(&write_buffer, expected, strlen(expected));
+	ck_assert_int_eq(r, 0);
+
+	bzero(&write_buffer, sizeof(write_buffer));
+	tsm_vte_handle_mouse(vte, 0, 0, 236, 120, 0, TSM_MOUSE_EVENT_PRESSED, 0);
+	expected = "\e[<0;236;120M";
+	r = memcmp(&write_buffer, expected, strlen(expected));
+	ck_assert_int_eq(r, 0);
+
+	bzero(&write_buffer, sizeof(write_buffer));
+	tsm_vte_handle_mouse(vte, 0, 0, 236, 120, 0, TSM_MOUSE_EVENT_RELEASED, 0);
+	expected = "\e[<0;236;120m";
+	r = memcmp(&write_buffer, expected, strlen(expected));
+	ck_assert_int_eq(r, 0);
+}
+END_TEST
+
+TEST_DEFINE_CASE(tests_x10)
+	CHECKED_FIXTURE(setup, teardown)
+	TEST(test_mouse_cb_x10)
+	TEST(test_mouse_x10)
+TEST_END_CASE
+
+TEST_DEFINE_CASE(tests_sgr)
+	CHECKED_FIXTURE(setup, teardown)
+	TEST(test_mouse_cb_sgr)
+	TEST(test_mouse_sgr)
+	TEST(test_mouse_sgr_cell_change)
+TEST_END_CASE
+
+TEST_DEFINE_CASE(tests_sgr_pixels)
+	CHECKED_FIXTURE(setup, teardown)
+	TEST(test_mouse_cb_pixels)
+	TEST(test_mouse_pixels)
+TEST_END_CASE
+
+// clang-format off
+TEST_DEFINE(
+	TEST_SUITE(vte_mouse,
+		TEST_CASE(tests_x10),
+		TEST_CASE(tests_sgr),
+		TEST_CASE(tests_sgr_pixels),
+		TEST_END
+	)
+)
+// clang-format on
