changeset 26270:f93337ae0612 v8.2.3666

patch 8.2.3666: libvterm is outdated Commit: https://github.com/vim/vim/commit/7da341560ec8db7e81cd80092b046b60a482fbbe Author: Bram Moolenaar <Bram@vim.org> Date: Wed Nov 24 19:30:55 2021 +0000 patch 8.2.3666: libvterm is outdated Problem: Libvterm is outdated. Solution: Include patches from revision 769 to revision 789.
author Bram Moolenaar <Bram@vim.org>
date Wed, 24 Nov 2021 20:45:04 +0100
parents 511655f714e9
children 488094d12d74
files Filelist src/libvterm/Makefile src/libvterm/doc/seqs.txt src/libvterm/include/vterm.h src/libvterm/src/mouse.c src/libvterm/src/parser.c src/libvterm/src/state.c src/libvterm/src/vterm.c src/libvterm/src/vterm_internal.h src/libvterm/t/02parser.test src/libvterm/t/17state_mouse.test src/libvterm/t/29state_fallback.test src/libvterm/t/40state_selection.test src/libvterm/t/harness.c src/libvterm/t/run-test.pl src/libvterm/vterm.pc.in src/terminal.c src/version.c
diffstat 18 files changed, 955 insertions(+), 240 deletions(-) [+]
line wrap: on
line diff
--- a/Filelist
+++ b/Filelist
@@ -377,6 +377,7 @@ SRC_ALL =	\
 		src/libvterm/t/30state_pen.test \
 		src/libvterm/t/31state_rep.test \
 		src/libvterm/t/32state_flow.test \
+		src/libvterm/t/40state_selection.test \
 		src/libvterm/t/60screen_ascii.test \
 		src/libvterm/t/61screen_unicode.test \
 		src/libvterm/t/62screen_damage.test \
--- a/src/libvterm/Makefile
+++ b/src/libvterm/Makefile
@@ -37,7 +37,7 @@ INCFILES=$(TBLFILES:.tbl=.inc)
 HFILES_INT=$(sort $(wildcard src/*.h)) $(HFILES)
 
 VERSION_MAJOR=0
-VERSION_MINOR=1
+VERSION_MINOR=2
 
 VERSION_CURRENT=0
 VERSION_REVISION=0
@@ -97,7 +97,7 @@ install-inc:
 	install -d $(DESTDIR)$(INCDIR)
 	install -m644 $(HFILES) $(DESTDIR)$(INCDIR)
 	install -d $(DESTDIR)$(LIBDIR)/pkgconfig
-	sed -e "s,@PREFIX@,$(PREFIX)," -e "s,@LIBDIR@,$(LIBDIR)," -e "s,@VERSION@,$(VERSION)," <vterm.pc.in >$(DESTDIR)$(LIBDIR)/pkgconfig/vterm.pc
+	sed -e "s,@INCDIR@,$(INCDIR)," -e "s,@LIBDIR@,$(LIBDIR)," -e "s,@VERSION@,$(VERSION)," <vterm.pc.in >$(DESTDIR)$(LIBDIR)/pkgconfig/vterm.pc
 
 install-lib: $(LIBRARY)
 	install -d $(DESTDIR)$(LIBDIR)
--- a/src/libvterm/doc/seqs.txt
+++ b/src/libvterm/doc/seqs.txt
@@ -4,228 +4,275 @@ between states.
 1 = VT100
 2 = VT220
 3 = VT320
+x = xterm
 
-   C0 controls
+    C0 controls
+
+123    0x00             = NUL
+123x   0x07             = BEL
+123x   0x08             = BS
+123x   0x09             = HT
+123x   0x0A             = LF
+123x   0x0B             = VT
+123x   0x0C             = FF
+123x   0x0D             = CR
+123x   0x0E             = LS1
+123x   0x0F             = LS0
+      (0x18             = CAN)
+      (0x1A             = SUB)
+      (0x1B             = ESC)
 
-123   0x00             = NUL
-123   0x07             = BEL
-123   0x08             = BS
-123   0x09             = HT
-123   0x0A             = LF
-123   0x0B             = VT
-123   0x0C             = FF
-123   0x0D             = CR
-123   0x0E             = LS1
-123   0x0F             = LS0
-     (0x18             = CAN)
-     (0x1A             = SUB)
-     (0x1B             = ESC)
+123    0x7f             = DEL (ignored)
+
+    C1 controls
 
-123   0x7f             = DEL (ignored)
-
-   C1 controls
+123x   0x84             = IND
+123x   0x85             = NEL
+123x   0x88             = HTS
+123x   0x8D             = RI
+ 23x   0x8E             = SS2
+ 23x   0x8F             = SS3
+      (0x90             = DCS)
+      (0x98             = SOS)
+      (0x9B             = CSI)
+      (0x9C             = ST)
+      (0x9D             = OSC)
+      (0x9E             = PM)
+      (0x9F             = APC)
 
-123   0x84             = IND
-123   0x85             = NEL
-123   0x88             = HTS
-123   0x8D             = RI
- 23   0x8e             = SS2
- 23   0x8f             = SS3
-     (0x90             = DCS)
-     (0x9B             = CSI)
-     (0x9C             = ST)
-     (0x9D             = OSC)
+    Escape sequences
+     - excluding sequences that are C1 aliases
 
-   Escape sequences
-    - excluding sequences that are C1 aliases
+123x   ESC (            = SCS, select character set G0
+123x   ESC )            = SCS, select character set G1
+ 23x   ESC *            = SCS, select character set G2
+ 23x   ESC +            = SCS, select character set G3
+123x   ESC 7            = DECSC - save cursor
+123x   ESC 8            = DECRC - restore cursor
+123x   ESC # 3          = DECDHL, double-height line (top half)
+123x   ESC # 4          = DECDHL, double-height line (bottom half)
+123x   ESC # 5          = DECSWL, single-width single-height line
+123x   ESC # 6          = DECDWL, double-width single-height line
+123x   ESC # 8          = DECALN
+123    ESC <            = Ignored (used by VT100 to exit VT52 mode)
+123x   ESC =            = DECKPAM, keypad application mode
+123x   ESC >            = DECKPNM, keypad numeric mode
+ 23x   ESC Sp F         = S7C1T
+ 23x   ESC Sp G         = S8C1T
+      (ESC P            = DCS)
+      (ESC X            = SOS)
+      (ESC [            = CSI)
+      (ESC \            = ST)
+      (ESC ]            = OSC)
+      (ESC ^            = PM)
+      (ESC _            = APC)
+123x   ESC c            = RIS, reset initial state
+  3x   ESC n            = LS2
+  3x   ESC o            = LS3
+  3x   ESC |            = LS3R
+  3x   ESC }            = LS2R
+  3x   ESC ~            = LS1R
 
-123   ESC ()           = SCS, select character set (G0, G1)
- 23   ESC *+           = SCS, select character set (G2, G3)
-123   ESC 7            = DECSC - save cursor
-123   ESC 8            = DECRC - restore cursor
-123   ESC # 3          = DECDHL, double-height line (top half)
-123   ESC # 4          = DECDHL, double-height line (bottom half)
-123   ESC # 5          = DECSWL, single-width single-height line
-123   ESC # 6          = DECDWL, double-width single-height line
-123   ESC # 8          = DECALN
-123   ESC <            = Ignored (used by VT100 to exit VT52 mode)
-123   ESC =            = DECKPAM, keypad application mode
-123   ESC >            = DECKPNM, keypad numeric mode
- 23   ESC Sp F         = S7C1T
- 23   ESC Sp G         = S8C1T
-     (ESC P            = DCS)
-     (ESC [            = CSI)
-     (ESC \            = ST)
-     (ESC ]            = OSC)
-123   ESC c            = RIS, reset initial state
-  3   ESC n            = LS2
-  3   ESC o            = LS3
-  3   ESC ~            = LS1R
-  3   ESC }            = LS2R
-  3   ESC |            = LS3R
+    DCSes
 
-   DCSes
-
-  3   DCS $ q      ST  = DECRQSS
-  3           m        =   Request SGR
-              Sp q     =   Request DECSCUSR
-  3           " q      =   Request DECSCA
-  3           r        =   Request DECSTBM
-              s        =   Request DECSLRM
+  3x   DCS $ q      ST  = DECRQSS
+  3x           m        =   Request SGR
+   x           Sp q     =   Request DECSCUSR
+  3x           " q      =   Request DECSCA
+  3x           r        =   Request DECSTBM
+   x           s        =   Request DECSLRM
 
-   CSIs
- 23   CSI @            = ICH
-123   CSI A            = CUU
-123   CSI B            = CUD
-123   CSI C            = CUF
-123   CSI D            = CUB
-      CSI E            = CNL
-      CSI F            = CPL
-      CSI G            = CHA
-123   CSI H            = CUP
-      CSI I            = CHT
-123   CSI J            = ED
- 23   CSI ? J          = DECSED, selective erase in display
-123   CSI K            = EL
- 23   CSI ? K          = DECSEL, selective erase in line
- 23   CSI L            = IL
- 23   CSI M            = DL
- 23   CSI P            = DCH
-      CSI S            = SU
-      CSI T            = SD
- 23   CSI X            = ECH
-      CSI Z            = CBT
-      CSI `            = HPA
-      CSI a            = HPR
-      CSI b            = REP
-123   CSI   c          = DA, device attributes
-123       0            =   DA
- 23   CSI >   c        = DECSDA
- 23         0          =   SDA
-      CSI d            = VPA
-      CSI e            = VPR
-123   CSI f            = HVP
-123   CSI g            = TBC
-123   CSI h            = SM, Set mode
-123   CSI ? h          = DECSM, DEC set mode
-      CSI j            = HPB
-      CSI k            = VPB
-123   CSI l            = RM, Reset mode
-123   CSI ? l          = DECRM, DEC reset mode
-123   CSI m            = SGR, Set Graphic Rendition
-123   CSI   n          = DSR, Device Status Report
- 23       5            =   operating status
- 23       6            =   CPR = cursor position
- 23   CSI ? n          = DECDSR; behaves as DSR but uses CSI ? instead of CSI to respond
- 23   CSI ! p          = DECSTR, soft terminal reset
-  3   CSI ? $ p        = DECRQM, request mode
-      CSI   Sp q       = DECSCUSR (odd numbers blink, even numbers solid)
-          1 or 2       =   block
-          3 or 4       =   underline
-          5 or 6       =   I-beam to left
- 23   CSI " q          = DECSCA, select character attributes
-123   CSI r            = DECSTBM
-      CSI s            = DECSLRM
-      CSI ' }          = DECIC
-      CSI ' ~          = DECDC
+    CSIs
+ 23x   CSI @            = ICH
+123x   CSI A            = CUU
+123x   CSI B            = CUD
+123x   CSI C            = CUF
+123x   CSI D            = CUB
+   x   CSI E            = CNL
+   x   CSI F            = CPL
+   x   CSI G            = CHA
+123x   CSI H            = CUP
+   x   CSI I            = CHT
+123x   CSI J            = ED
+ 23x   CSI ? J          = DECSED, selective erase in display
+123x   CSI K            = EL
+ 23x   CSI ? K          = DECSEL, selective erase in line
+ 23x   CSI L            = IL
+ 23x   CSI M            = DL
+ 23x   CSI P            = DCH
+   x   CSI S            = SU
+   x   CSI T            = SD
+ 23x   CSI X            = ECH
+   x   CSI Z            = CBT
+   x   CSI `            = HPA
+   x   CSI a            = HPR
+   x   CSI b            = REP
+123x   CSI   c          = DA, device attributes
+123        0            =   DA
+ 23x   CSI >   c        = DECSDA
+ 23          0          =   SDA
+   x   CSI d            = VPA
+   x   CSI e            = VPR
+123x   CSI f            = HVP
+123x   CSI g            = TBC
+123x   CSI h            = SM, Set mode
+123x   CSI ? h          = DECSM, DEC set mode
+       CSI j            = HPB
+       CSI k            = VPB
+123x   CSI l            = RM, Reset mode
+123x   CSI ? l          = DECRM, DEC reset mode
+123x   CSI m            = SGR, Set Graphic Rendition
+123x   CSI   n          = DSR, Device Status Report
+ 23x       5            =   operating status
+ 23x       6            =   CPR = cursor position
+ 23x   CSI ? n          = DECDSR; behaves as DSR but uses CSI ? instead of CSI to respond
+ 23x   CSI ! p          = DECSTR, soft terminal reset
+  3x   CSI ? $ p        = DECRQM, request private mode
+   x   CSI   Sp q       = DECSCUSR (odd numbers blink, even numbers solid)
+           1 or 2       =   block
+           3 or 4       =   underline
+           5 or 6       =   I-beam to left
+ 23x   CSI " q          = DECSCA, select character attributes
+123x   CSI r            = DECSTBM
+   x   CSI s            = DECSLRM
+   x   CSI ' }          = DECIC
+   x   CSI ' ~          = DECDC
 
-   OSCs
+    OSCs
 
-      OSC 0;           = Set icon name and title
-      OSC 1;           = Set icon name
-      OSC 2;           = Set title
+   x   OSC 0;           = Set icon name and title
+   x   OSC 1;           = Set icon name
+   x   OSC 2;           = Set title
+   x   OSC 52;          = Selection management
 
-   Standard modes
+    Standard modes
 
- 23   SM 4             = IRM
-123   SM 20            = NLM, linefeed/newline
+ 23x   SM 4             = IRM
+123x   SM 20            = NLM, linefeed/newline
 
-   DEC modes
+    DEC modes
 
-123   DECSM 1          = DECCKM, cursor keys
-123   DECSM 5          = DECSCNM, screen
-123   DECSM 6          = DECOM, origin
-123   DECSM 7          = DECAWM, autowrap
-      DECSM 12         = Cursor blink
- 23   DECSM 25         = DECTCEM, text cursor enable
-      DECSM 69         = DECVSSM, vertical screen split
-      DECSM 1000       = Mouse click/release tracking
-      DECSM 1002       = Mouse click/release/drag tracking
-      DECSM 1003       = Mouse all movements tracking
-      DECSM 1004       = Focus in/out reporting
-      DECSM 1005       = Mouse protocol extended (UTF-8) - not recommended
-      DECSM 1006       = Mouse protocol SGR
-      DECSM 1015       = Mouse protocol rxvt
-      DECSM 1047       = Altscreen
-      DECSM 1048       = Save cursor
-      DECSM 1049       = 1047 + 1048
-      DECSM 2004       = Bracketed paste
+123x   DECSM 1          = DECCKM, cursor keys
+123x   DECSM 5          = DECSCNM, screen
+123x   DECSM 6          = DECOM, origin
+123x   DECSM 7          = DECAWM, autowrap
+   x   DECSM 12         = Cursor blink
+ 23x   DECSM 25         = DECTCEM, text cursor enable
+   x   DECSM 69         = DECVSSM, vertical screen split
+   x   DECSM 1000       = Mouse click/release tracking
+   x   DECSM 1002       = Mouse click/release/drag tracking
+   x   DECSM 1003       = Mouse all movements tracking
+   x   DECSM 1004       = Focus in/out reporting
+   x   DECSM 1005       = Mouse protocol extended (UTF-8) - not recommended
+   x   DECSM 1006       = Mouse protocol SGR
+   x   DECSM 1015       = Mouse protocol rxvt
+   x   DECSM 1047       = Altscreen
+   x   DECSM 1048       = Save cursor
+   x   DECSM 1049       = 1047 + 1048
+   x   DECSM 2004       = Bracketed paste
 
-   Graphic Renditions
+    Graphic Renditions
 
-123   SGR 0            = Reset
-123   SGR 1            = Bold on
-      SGR 3            = Italic on
-123   SGR 4            = Underline single
-      SGR 4:x          = Underline style
-123   SGR 5            = Blink on
-123   SGR 7            = Reverse on
-      SGR 8            = Conceal on
-      SGR 9            = Strikethrough on
-      SGR 10-19        = Select font
-      SGR 21           = Underline double
- 23   SGR 22           = Bold off
-      SGR 23           = Italic off
- 23   SGR 24           = Underline off
- 23   SGR 25           = Blink off
- 23   SGR 27           = Reverse off
-      SGR 28           = Conceal off
-      SGR 29           = Strikethrough off
-      SGR 30-37        = Foreground ANSI
-      SGR 38           = Foreground alternative palette
-      SGR 39           = Foreground default
-      SGR 40-47        = Background ANSI
-      SGR 48           = Background alternative palette
-      SGR 49           = Background default
-      SGR 90-97        = Foreground ANSI high-intensity
-      SGR 100-107      = Background ANSI high-intensity
+123x   SGR 0            = Reset
+123x   SGR 1            = Bold on
+   x   SGR 3            = Italic on
+123x   SGR 4            = Underline single
+       SGR 4:x          = Underline style
+123x   SGR 5            = Blink on
+123x   SGR 7            = Reverse on
+   x   SGR 8            = Conceal on
+   x   SGR 9            = Strikethrough on
+       SGR 10-19        = Select font
+   x   SGR 21           = Underline double
+ 23x   SGR 22           = Bold off
+   x   SGR 23           = Italic off
+ 23x   SGR 24           = Underline off
+ 23x   SGR 25           = Blink off
+ 23x   SGR 27           = Reverse off
+   x   SGR 28           = Conceal off
+   x   SGR 29           = Strikethrough off
+   x   SGR 30-37        = Foreground ANSI
+   x   SGR 38           = Foreground alternative palette
+   x   SGR 39           = Foreground default
+   x   SGR 40-47        = Background ANSI
+   x   SGR 48           = Background alternative palette
+   x   SGR 49           = Background default
+   x   SGR 90-97        = Foreground ANSI high-intensity
+   x   SGR 100-107      = Background ANSI high-intensity
 
 The state storage used by ESC 7 and DECSM 1048/1049 is shared.
 
-   Unimplemented sequences:
+    Unimplemented sequences:
 
 The following sequences are not recognised by libvterm.
 
-123   0x05             = ENQ
-  3   0x11             = DC1 (XON)
-  3   0x13             = DC3 (XOFF)
-12    ESC Z            = DECID, identify terminal
-      DCS $ q          = [DECRQSS]
-  3           " p      =   Request DECSCL
-  3           $ }      =   Request DECSASD
-  3           $ ~      =   Request DECSSDT
- 23   DCS {            = DECDLD, down-line-loadable character set
- 23   DCS |            = DECUDK, user-defined key
- 23   CSI i            = DEC printer control
- 23   CSI " p          = DECSCL, set compatibility level
-1     CSI q            = DECLL, load LEDs
-  3   CSI   $ u        = DECRQTSR, request terminal state report
-  3       1            =   terminal state report
-  3   CSI & u          = DECRQUPSS, request user-preferred supplemental set
-  3   CSI   $ w        = DECRQPSR, request presentation state report
-  3       1            =   cursor information report
-  3       2            =   tab stop report
-1     CSI x            = DECREQTPARM, request terminal parameters
-123   CSI y            = DECTST, invoke confidence test
-  3   CSI $ }          = DECSASD, select active status display
-  3   CSI $ ~          = DECSSDT, select status line type
- 23   SM 2             = KAM, keyboard action
-123   SM 12            = SRM, send/receive
-123   DECSM 2          = DECANM, ANSI/VT52
-123   DECSM 3          = DECCOLM, 132 column
-123   DECSM 4          = DECSCLM, scrolling
-123   DECSM 8          = DECARM, auto-repeat
-12    DECSM 9          = DECINLM, interlace
- 23   DECSM 18         = DECPFF, print form feed
- 23   DECSM 19         = DECPEX, print extent
- 23   DECSM 42         = DECNRCM, national/multinational character
+123x   0x05             = ENQ
+  3    0x11             = DC1 (XON)
+  3    0x13             = DC3 (XOFF)
+   x   ESC % @          = Select default character set
+   x   ESC % G          = Select UTF-8 character set
+   x   ESC 6            = DECBI, Back Index
+12     ESC Z            = DECID, identify terminal
+   x   DCS + Q          = XTGETXRES, Request resource values
+       DCS $ q          = [DECRQSS]
+  3x           " p      =   Request DECSCL
+   x           t        =   Request DECSLPP
+   x           $ |      =   Request DECSCPP
+   x           * |      =   Request DECSLNS
+  3            $ }      =   Request DECSASD
+  3            $ ~      =   Request DECSSDT
+   x   DCS + p          = XTSETTCAP, set termcap/terminfo data
+   x   DCS + q          = XTGETTCAP, request termcap/terminfo
+ 23    DCS {            = DECDLD, down-line-loadable character set
+ 23x   DCS |            = DECUDK, user-defined key
+   x   CSI Sp @         = Shift left columns
+   x   CSI Sp A         = Shift right columns
+   x   CSI # P          = XTPUSHCOLORS, push current dynamic colours to stack
+   x   CSI # Q          = XTPOPCOLORS, pop dynamic colours from stack
+   x   CSI # R          = XTREPORTCOLORS, report current entry on palette stack
+   x   CSI ? S          = XTSMGRAPHICS, set/request graphics attribute
+   x   CSI > T          = XTRMTITLE, reset title mode features
+ 23x   CSI i            = DEC printer control
+   x   CSI > m          = XTMODKEYS, set key modifier options
+   x   CSI > n          = (XTMODKEYS), reset key modifier options
+   x   CSI $ p          = DECRQM, request ANSI mode
+ 23x   CSI " p          = DECSCL, set compatibility level
+   x   CSI > p          = XTSMPOINTER, set resource value pointer mode
+1  x   CSI q            = DECLL, load LEDs
+   x   CSI ? r          = XTRESTORE, restore DEC private mode values
+   x   CSI $ r          = DECCARA, change attributes in rectangular area
+   x   CSI > s          = XTSHIFTESCAPE, set/reset shift-escape options
+   x   CSI ? s          = XTSAVE, save DEC private mode values
+   x   CSI t            = XTWINOPS, window operations
+   x   CSI > t          = XTSMTITLE, set title mode features
+   x   CSI $ t          = DECRARA, reset attributes in rectangular area
+  3    CSI   $ u        = DECRQTSR, request terminal state report
+  3        1            =   terminal state report
+  3    CSI & u          = DECRQUPSS, request user-preferred supplemental set
+   x   CSI $ v          = DECCRA, copy rectangular area
+  3x   CSI   $ w        = DECRQPSR, request presentation state report
+  3x       1            =   cursor information report
+  3x       2            =   tab stop report
+   x   CSI ' w          = DECEFR, enable filter rectangle
+1  x   CSI x            = DECREQTPARM, request terminal parameters
+   x   CSI * x          = DECSACE, select attribute change extent
+   x   CSI $ x          = DECFRA, fill rectangular area
+123    CSI y            = DECTST, invoke confidence test
+   x   CSI $ z          = DECERA, erase rectangular area
+   x   CSI # {          = XTPUSHSGR, push video attributes onto stack
+   x   CSI $ {          = DECSERA, selective erase in rectangular area
+   x   CSI # |          = XTREPORTSGR, report selected graphic rendition
+   x   CSI $ |          = DECSCPP, select columns per page
+   x   CSI # }          = XTPOPSGR, pop video attributes from stack
+  3    CSI $ }          = DECSASD, select active status display
+  3    CSI $ ~          = DECSSDT, select status line type
+ 23    SM 2             = KAM, keyboard action
+123    SM 12            = SRM, send/receive
+123    DECSM 2          = DECANM, ANSI/VT52
+123    DECSM 3          = DECCOLM, 132 column
+123    DECSM 4          = DECSCLM, scrolling
+123    DECSM 8          = DECARM, auto-repeat
+12     DECSM 9          = DECINLM, interlace
+ 23    DECSM 18         = DECPFF, print form feed
+ 23    DECSM 19         = DECPEX, print extent
+ 23    DECSM 42         = DECNRCM, national/multinational character
--- a/src/libvterm/include/vterm.h
+++ b/src/libvterm/include/vterm.h
@@ -17,10 +17,11 @@ extern "C" {
 
 // from stdint.h
 typedef unsigned char		uint8_t;
+typedef unsigned short		uint16_t;
 typedef unsigned int		uint32_t;
 
 #define VTERM_VERSION_MAJOR 0
-#define VTERM_VERSION_MINOR 1
+#define VTERM_VERSION_MINOR 2
 
 #define VTERM_CHECK_VERSION \
         vterm_check_version(VTERM_VERSION_MAJOR, VTERM_VERSION_MINOR)
@@ -267,6 +268,14 @@ enum {
   VTERM_N_PROP_MOUSES
 };
 
+typedef enum {
+  VTERM_SELECTION_CLIPBOARD = (1<<0),
+  VTERM_SELECTION_PRIMARY   = (1<<1),
+  VTERM_SELECTION_SECONDARY = (1<<2),
+  VTERM_SELECTION_SELECT    = (1<<3),
+  VTERM_SELECTION_CUT0      = (1<<4), /* also CUT1 .. CUT7 by bitshifting */
+} VTermSelectionMask;
+
 typedef struct {
   const uint32_t *chars;
   int             width;
@@ -375,6 +384,9 @@ typedef struct {
   int (*csi)(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user);
   int (*osc)(int command, VTermStringFragment frag, void *user);
   int (*dcs)(const char *command, size_t commandlen, VTermStringFragment frag, void *user);
+  int (*apc)(VTermStringFragment frag, void *user);
+  int (*pm)(VTermStringFragment frag, void *user);
+  int (*sos)(VTermStringFragment frag, void *user);
   int (*resize)(int rows, int cols, void *user);
 } VTermParserCallbacks;
 
@@ -419,8 +431,16 @@ typedef struct {
   int (*csi)(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user);
   int (*osc)(int command, VTermStringFragment frag, void *user);
   int (*dcs)(const char *command, size_t commandlen, VTermStringFragment frag, void *user);
+  int (*apc)(VTermStringFragment frag, void *user);
+  int (*pm)(VTermStringFragment frag, void *user);
+  int (*sos)(VTermStringFragment frag, void *user);
 } VTermStateFallbacks;
 
+typedef struct {
+  int (*set)(VTermSelectionMask mask, VTermStringFragment frag, void *user);
+  int (*query)(VTermSelectionMask mask, void *user);
+} VTermSelectionCallbacks;
+
 VTermState *vterm_obtain_state(VTerm *vt);
 
 void  vterm_state_set_callbacks(VTermState *state, const VTermStateCallbacks *callbacks, void *user);
@@ -457,6 +477,11 @@ const VTermLineInfo *vterm_state_get_lin
  */
 void vterm_state_convert_color_to_rgb(const VTermState *state, VTermColor *col);
 
+void vterm_state_set_selection_callbacks(VTermState *state, const VTermSelectionCallbacks *callbacks, void *user,
+    char *buffer, size_t buflen);
+
+void vterm_state_send_selection(VTermState *state, VTermSelectionMask mask, VTermStringFragment frag);
+
 // ------------
 // Screen layer
 // ------------
--- a/src/libvterm/src/mouse.c
+++ b/src/libvterm/src/mouse.c
@@ -89,6 +89,9 @@ void vterm_mouse_button(VTerm *vt, int b
   if (!(state->mouse_flags & MOUSE_WANT_CLICK))
     return;
 
+  if(!state->mouse_flags)
+    return;
+
   if(button < 4) {
     output_mouse(state, button-1, pressed, mod, state->mouse_col, state->mouse_row);
   }
--- a/src/libvterm/src/parser.c
+++ b/src/libvterm/src/parser.c
@@ -77,10 +77,25 @@ static void string_fragment(VTerm *vt, c
       break;
 
     case DCS:
-      if(len && vt->parser.callbacks && vt->parser.callbacks->dcs)
+      if(vt->parser.callbacks && vt->parser.callbacks->dcs)
         (*vt->parser.callbacks->dcs)(vt->parser.v.dcs.command, vt->parser.v.dcs.commandlen, frag, vt->parser.cbdata);
       break;
 
+    case APC:
+      if(vt->parser.callbacks && vt->parser.callbacks->apc)
+        (*vt->parser.callbacks->apc)(frag, vt->parser.cbdata);
+      break;
+
+    case PM:
+      if(vt->parser.callbacks && vt->parser.callbacks->pm)
+        (*vt->parser.callbacks->pm)(frag, vt->parser.cbdata);
+      break;
+
+    case SOS:
+      if(vt->parser.callbacks && vt->parser.callbacks->sos)
+        (*vt->parser.callbacks->sos)(frag, vt->parser.cbdata);
+      break;
+
     case NORMAL:
     case CSI_LEADER:
     case CSI_ARGS:
@@ -112,6 +127,9 @@ size_t vterm_input_write(VTerm *vt, cons
     break;
   case OSC:
   case DCS:
+  case APC:
+  case PM:
+  case SOS:
     string_start = bytes;
     break;
   }
@@ -150,6 +168,9 @@ size_t vterm_input_write(VTerm *vt, cons
       // fallthrough
     }
     else if(c < 0x20) { // other C0
+      if(vt->parser.state == SOS)
+        continue; // All other C0s permitted in SOS
+
       if(vterm_get_special_pty_type() == 2) {
         if(c == 0x08) // BS
           // Set the trick for BS output after a sequence, to delay backspace
@@ -176,7 +197,8 @@ size_t vterm_input_write(VTerm *vt, cons
           ((!IS_STRING_STATE() || c == 0x5c))) {
         c += 0x40;
         c1_allowed = TRUE;
-        string_len -= 1;
+        if(string_len)
+          string_len -= 1;
         vt->parser.in_esc = FALSE;
       }
       else {
@@ -279,6 +301,9 @@ size_t vterm_input_write(VTerm *vt, cons
 string_state:
     case OSC:
     case DCS:
+    case APC:
+    case PM:
+    case SOS:
       if(c == 0x07 || (c1_allowed && c == 0x9c)) {
         string_fragment(vt, string_start, string_len, TRUE);
         ENTER_NORMAL_STATE();
@@ -308,6 +333,12 @@ string_state:
           vt->parser.v.dcs.commandlen = 0;
           ENTER_STATE(DCS_COMMAND);
           break;
+        case 0x98: // SOS
+          vt->parser.string_initial = TRUE;
+          ENTER_STATE(SOS);
+          string_start = bytes + pos + 1;
+          string_len = 0;
+          break;
         case 0x9b: // CSI
           vt->parser.v.csi.leaderlen = 0;
           ENTER_STATE(CSI_LEADER);
@@ -318,6 +349,18 @@ string_state:
           string_start = bytes + pos + 1;
           ENTER_STATE(OSC_COMMAND);
           break;
+        case 0x9e: // PM
+          vt->parser.string_initial = TRUE;
+          ENTER_STATE(PM);
+          string_start = bytes + pos + 1;
+          string_len = 0;
+          break;
+        case 0x9f: // APC
+          vt->parser.string_initial = TRUE;
+          ENTER_STATE(APC);
+          string_start = bytes + pos + 1;
+          string_len = 0;
+          break;
         default:
           do_control(vt, c);
           break;
@@ -340,8 +383,12 @@ string_state:
     }
   }
 
-  if(string_start)
-    string_fragment(vt, string_start, bytes + pos - string_start, FALSE);
+  if(string_start) {
+    size_t string_len = bytes + pos - string_start;
+    if(vt->parser.in_esc)
+      string_len -= 1;
+    string_fragment(vt, string_start, string_len, FALSE);
+  }
 
   return len;
 }
--- a/src/libvterm/src/state.c
+++ b/src/libvterm/src/state.c
@@ -79,6 +79,10 @@ static VTermState *vterm_state_new(VTerm
   state->callbacks = NULL;
   state->cbdata    = NULL;
 
+  state->selection.callbacks = NULL;
+  state->selection.user      = NULL;
+  state->selection.buffer    = NULL;
+
   vterm_state_newpen(state);
 
   state->bold_is_highbright = 0;
@@ -1615,6 +1619,174 @@ static int on_csi(const char *leader, co
   return 1;
 }
 
+static char base64_one(uint8_t b)
+{
+  if(b < 26)
+    return 'A' + b;
+  else if(b < 52)
+    return 'a' + b - 26;
+  else if(b < 62)
+    return '0' + b - 52;
+  else if(b == 62)
+    return '+';
+  else if(b == 63)
+    return '/';
+  return 0;
+}
+
+static uint8_t unbase64one(char c)
+{
+  if(c >= 'A' && c <= 'Z')
+    return c - 'A';
+  else if(c >= 'a' && c <= 'z')
+    return c - 'a' + 26;
+  else if(c >= '0' && c <= '9')
+    return c - '0' + 52;
+  else if(c == '+')
+    return 62;
+  else if(c == '/')
+    return 63;
+
+  return 0xFF;
+}
+
+static void osc_selection(VTermState *state, VTermStringFragment frag)
+{
+  if(frag.initial) {
+    state->tmp.selection.mask = 0;
+    state->tmp.selection.state = SELECTION_INITIAL;
+  }
+
+  while(!state->tmp.selection.state && frag.len) {
+    /* Parse selection parameter */
+    switch(frag.str[0]) {
+      case 'c':
+        state->tmp.selection.mask |= VTERM_SELECTION_CLIPBOARD;
+        break;
+      case 'p':
+        state->tmp.selection.mask |= VTERM_SELECTION_PRIMARY;
+        break;
+      case 'q':
+        state->tmp.selection.mask |= VTERM_SELECTION_SECONDARY;
+        break;
+      case 's':
+        state->tmp.selection.mask |= VTERM_SELECTION_SELECT;
+        break;
+      case '0':
+      case '1':
+      case '2':
+      case '3':
+      case '4':
+      case '5':
+      case '6':
+      case '7':
+        state->tmp.selection.mask |= (VTERM_SELECTION_CUT0 << (frag.str[0] - '0'));
+        break;
+
+      case ';':
+        state->tmp.selection.state = SELECTION_SELECTED;
+        if(!state->tmp.selection.mask)
+          state->tmp.selection.mask = VTERM_SELECTION_SELECT|VTERM_SELECTION_CUT0;
+        break;
+    }
+
+    frag.str++;
+    frag.len--;
+  }
+
+  if(!frag.len)
+    return;
+
+  if(state->tmp.selection.state == SELECTION_SELECTED) {
+    if(frag.str[0] == '?') {
+      state->tmp.selection.state = SELECTION_QUERY;
+    }
+    else {
+      state->tmp.selection.state = SELECTION_SET_INITIAL;
+      state->tmp.selection.recvpartial = 0;
+    }
+  }
+
+  if(state->tmp.selection.state == SELECTION_QUERY) {
+    if(state->selection.callbacks->query)
+      (*state->selection.callbacks->query)(state->tmp.selection.mask, state->selection.user);
+    return;
+  }
+
+  if(state->selection.callbacks->set) {
+    size_t bufcur = 0;
+    char *buffer = state->selection.buffer;
+
+    uint32_t x = 0; /* Current decoding value */
+    int n = 0;      /* Number of sextets consumed */
+
+    if(state->tmp.selection.recvpartial) {
+      n = state->tmp.selection.recvpartial >> 24;
+      x = state->tmp.selection.recvpartial & 0x03FFFF; /* could be up to 18 bits of state in here */
+
+      state->tmp.selection.recvpartial = 0;
+    }
+
+    while((state->selection.buflen - bufcur) >= 3 && frag.len) {
+      if(frag.str[0] == '=') {
+        if(n == 2) {
+          buffer[0] = (x >> 4) & 0xFF;
+          buffer += 1, bufcur += 1;
+        }
+        if(n == 3) {
+          buffer[0] = (x >> 10) & 0xFF;
+          buffer[1] = (x >>  2) & 0xFF;
+          buffer += 2, bufcur += 2;
+        }
+
+        while(frag.len && frag.str[0] == '=')
+          frag.str++, frag.len--;
+
+        n = 0;
+      }
+      else {
+        uint8_t b = unbase64one(frag.str[0]);
+        if(b == 0xFF) {
+          DEBUG_LOG1("base64decode bad input %02X\n", (uint8_t)frag.str[0]);
+        }
+        else {
+          x = (x << 6) | b;
+          n++;
+        }
+        frag.str++, frag.len--;
+
+        if(n == 4) {
+          buffer[0] = (x >> 16) & 0xFF;
+          buffer[1] = (x >>  8) & 0xFF;
+          buffer[2] = (x >>  0) & 0xFF;
+
+          buffer += 3, bufcur += 3;
+          x = 0;
+          n = 0;
+        }
+      }
+
+      if(!frag.len || (state->selection.buflen - bufcur) < 3) {
+        if(bufcur) {
+          (*state->selection.callbacks->set)(state->tmp.selection.mask, (VTermStringFragment){
+              .str     = state->selection.buffer,
+              .len     = bufcur,
+              .initial = state->tmp.selection.state == SELECTION_SET_INITIAL,
+              .final   = frag.final,
+            }, state->selection.user);
+          state->tmp.selection.state = SELECTION_SET;
+        }
+
+        buffer = state->selection.buffer;
+        bufcur = 0;
+      }
+    }
+
+    if(n)
+      state->tmp.selection.recvpartial = (n << 24) | x;
+  }
+}
+
 static int on_osc(int command, VTermStringFragment frag, void *user)
 {
   VTermState *state = user;
@@ -1656,6 +1828,12 @@ static int on_osc(int command, VTermStri
       settermprop_string(state, VTERM_PROP_CURSORCOLOR, frag);
       return 1;
 
+    case 52:
+      if(state->selection.callbacks)
+        osc_selection(state, frag);
+
+      return 1;
+
     default:
       if(state->fallbacks && state->fallbacks->osc)
         if((*state->fallbacks->osc)(command, frag, state->fbdata))
@@ -1718,12 +1896,14 @@ static void request_status_string(VTermS
 
     case 'r':
       // Query DECSTBM
-      vterm_push_output_sprintf_dcs(vt, "1$r%d;%dr", state->scrollregion_top+1, SCROLLREGION_BOTTOM(state));
+      vterm_push_output_sprintf_str(vt, C1_DCS, TRUE,
+          "1$r%d;%dr", state->scrollregion_top+1, SCROLLREGION_BOTTOM(state));
       return;
 
     case 's':
       // Query DECSLRM
-      vterm_push_output_sprintf_dcs(vt, "1$r%d;%ds", SCROLLREGION_LEFT(state)+1, SCROLLREGION_RIGHT(state));
+      vterm_push_output_sprintf_str(vt, C1_DCS, TRUE,
+          "1$r%d;%ds", SCROLLREGION_LEFT(state)+1, SCROLLREGION_RIGHT(state));
       return;
 
     case ' '|('q'<<8): {
@@ -1736,17 +1916,19 @@ static void request_status_string(VTermS
       }
       if(state->mode.cursor_blink)
         reply--;
-      vterm_push_output_sprintf_dcs(vt, "1$r%d q", reply);
+      vterm_push_output_sprintf_str(vt, C1_DCS, TRUE,
+          "1$r%d q", reply);
       return;
     }
 
     case '\"'|('q'<<8):
       // Query DECSCA
-      vterm_push_output_sprintf_dcs(vt, "1$r%d\"q", state->protected_cell ? 1 : 2);
+      vterm_push_output_sprintf_str(vt, C1_DCS, TRUE,
+          "1$r%d\"q", state->protected_cell ? 1 : 2);
       return;
   }
 
-  vterm_push_output_sprintf_dcs(state->vt, "0$r%s", tmp);
+  vterm_push_output_sprintf_str(state->vt, C1_DCS, TRUE, "0$r%s", tmp);
 }
 
 static int on_dcs(const char *command, size_t commandlen, VTermStringFragment frag, void *user)
@@ -1765,6 +1947,42 @@ static int on_dcs(const char *command, s
   return 0;
 }
 
+static int on_apc(VTermStringFragment frag, void *user)
+{
+  VTermState *state = user;
+
+  if(state->fallbacks && state->fallbacks->apc)
+    if((*state->fallbacks->apc)(frag, state->fbdata))
+      return 1;
+
+  /* No DEBUG_LOG because all APCs are unhandled */
+  return 0;
+}
+
+static int on_pm(VTermStringFragment frag, void *user)
+{
+  VTermState *state = user;
+
+  if(state->fallbacks && state->fallbacks->pm)
+    if((*state->fallbacks->pm)(frag, state->fbdata))
+      return 1;
+
+  /* No DEBUG_LOG because all PMs are unhandled */
+  return 0;
+}
+
+static int on_sos(VTermStringFragment frag, void *user)
+{
+  VTermState *state = user;
+
+  if(state->fallbacks && state->fallbacks->sos)
+    if((*state->fallbacks->sos)(frag, state->fbdata))
+      return 1;
+
+  /* No DEBUG_LOG because all SOSs are unhandled */
+  return 0;
+}
+
 static int on_resize(int rows, int cols, void *user)
 {
   VTermState *state = user;
@@ -1866,6 +2084,9 @@ static const VTermParserCallbacks parser
   on_csi, // csi
   on_osc, // osc
   on_dcs, // dcs
+  on_apc, // apc
+  on_pm, // pm
+  on_sos, // sos
   on_resize // resize
 };
 
@@ -1909,6 +2130,8 @@ void vterm_state_reset(VTermState *state
   state->mode.bracketpaste    = 0;
   state->mode.report_focus    = 0;
 
+  state->mouse_flags = 0;
+
   state->vt->mode.ctrl8bit   = 0;
 
   {
@@ -2087,3 +2310,96 @@ const VTermLineInfo *vterm_state_get_lin
 {
   return state->lineinfo + row;
 }
+
+void vterm_state_set_selection_callbacks(VTermState *state, const VTermSelectionCallbacks *callbacks, void *user,
+    char *buffer, size_t buflen)
+{
+  if(buflen && !buffer)
+    buffer = vterm_allocator_malloc(state->vt, buflen);
+
+  state->selection.callbacks = callbacks;
+  state->selection.user      = user;
+  state->selection.buffer    = buffer;
+  state->selection.buflen    = buflen;
+}
+
+void vterm_state_send_selection(VTermState *state, VTermSelectionMask mask, VTermStringFragment frag)
+{
+  VTerm *vt = state->vt;
+
+  if(frag.initial) {
+    /* TODO: support sending more than one mask bit */
+    static char selection_chars[] = "cpqs";
+    int idx;
+    for(idx = 0; idx < 4; idx++)
+      if(mask & (1 << idx))
+        break;
+
+    vterm_push_output_sprintf_str(vt, C1_OSC, FALSE, "52;%c;", selection_chars[idx]);
+
+    state->tmp.selection.sendpartial = 0;
+  }
+
+  if(frag.len) {
+    size_t bufcur = 0;
+    char *buffer = state->selection.buffer;
+
+    uint32_t x = 0;
+    int n = 0;
+
+    if(state->tmp.selection.sendpartial) {
+      n = state->tmp.selection.sendpartial >> 24;
+      x = state->tmp.selection.sendpartial & 0xFFFFFF;
+
+      state->tmp.selection.sendpartial = 0;
+    }
+
+    while((state->selection.buflen - bufcur) >= 4 && frag.len) {
+      x = (x << 8) | frag.str[0];
+      n++;
+      frag.str++, frag.len--;
+
+      if(n == 3) {
+        buffer[0] = base64_one((x >> 18) & 0x3F);
+        buffer[1] = base64_one((x >> 12) & 0x3F);
+        buffer[2] = base64_one((x >>  6) & 0x3F);
+        buffer[3] = base64_one((x >>  0) & 0x3F);
+
+        buffer += 4, bufcur += 4;
+        x = 0;
+        n = 0;
+      }
+
+      if(!frag.len || (state->selection.buflen - bufcur) < 4) {
+        if(bufcur)
+          vterm_push_output_bytes(vt, state->selection.buffer, bufcur);
+
+        buffer = state->selection.buffer;
+        bufcur = 0;
+      }
+    }
+
+    if(n)
+      state->tmp.selection.sendpartial = (n << 24) | x;
+  }
+
+  if(frag.final) {
+    if(state->tmp.selection.sendpartial) {
+      int n      = state->tmp.selection.sendpartial >> 24;
+      uint32_t x = state->tmp.selection.sendpartial & 0xFFFFFF;
+      char *buffer = state->selection.buffer;
+
+      /* n is either 1 or 2 now */
+      x <<= (n == 1) ? 16 : 8;
+
+      buffer[0] = base64_one((x >> 18) & 0x3F);
+      buffer[1] = base64_one((x >> 12) & 0x3F);
+      buffer[2] = (n == 1) ? '=' : base64_one((x >>  6) & 0x3F);
+      buffer[3] = '=';
+
+      vterm_push_output_sprintf_str(vt, 0, TRUE, "%.*s", 4, buffer);
+    }
+    else
+      vterm_push_output_sprintf_str(vt, 0, TRUE, "");
+  }
+}
--- a/src/libvterm/src/vterm.c
+++ b/src/libvterm/src/vterm.c
@@ -200,27 +200,35 @@ INTERNAL void vterm_push_output_sprintf_
   va_end(args);
 }
 
-INTERNAL void vterm_push_output_sprintf_dcs(VTerm *vt, const char *fmt, ...)
+INTERNAL void vterm_push_output_sprintf_str(VTerm *vt, unsigned char ctrl, int term, const char *fmt, ...)
 {
   size_t cur;
   va_list args;
 
-  cur = SNPRINTF(vt->tmpbuffer, vt->tmpbuffer_len,
-      vt->mode.ctrl8bit ? "\x90" : ESC_S "P"); // DCS
+  if(ctrl) {
+    if(ctrl >= 0x80 && !vt->mode.ctrl8bit)
+      cur = SNPRINTF(vt->tmpbuffer, vt->tmpbuffer_len,
+          ESC_S "%c", ctrl - 0x40);
+    else
+      cur = SNPRINTF(vt->tmpbuffer, vt->tmpbuffer_len,
+          "%c", ctrl);
 
-  if(cur >= vt->tmpbuffer_len)
-    return;
-  vterm_push_output_bytes(vt, vt->tmpbuffer, cur);
+    if(cur >= vt->tmpbuffer_len)
+      return;
+    vterm_push_output_bytes(vt, vt->tmpbuffer, cur);
+  }
 
   va_start(args, fmt);
   vterm_push_output_vsprintf(vt, fmt, args);
   va_end(args);
 
-  cur = SNPRINTF(vt->tmpbuffer, vt->tmpbuffer_len,
-      vt->mode.ctrl8bit ? "\x9C" : ESC_S "\\"); // ST
-  if(cur >= vt->tmpbuffer_len)
-    return;
-  vterm_push_output_bytes(vt, vt->tmpbuffer, cur);
+  if(term) {
+    cur = SNPRINTF(vt->tmpbuffer, vt->tmpbuffer_len,
+        vt->mode.ctrl8bit ? "\x9C" : ESC_S "\\"); // ST
+    if(cur >= vt->tmpbuffer_len)
+      return;
+    vterm_push_output_bytes(vt, vt->tmpbuffer, cur);
+  }
 }
 
 size_t vterm_output_get_buffer_size(const VTerm *vt)
--- a/src/libvterm/src/vterm_internal.h
+++ b/src/libvterm/src/vterm_internal.h
@@ -154,7 +154,26 @@ struct VTermState
   /* Temporary state for DECRQSS parsing */
   union {
     char decrqss[4];
+    struct {
+      uint16_t mask;
+      enum {
+        SELECTION_INITIAL,
+        SELECTION_SELECTED,
+        SELECTION_QUERY,
+        SELECTION_SET_INITIAL,
+        SELECTION_SET,
+      } state : 8;
+      uint32_t recvpartial;
+      uint32_t sendpartial;
+    } selection;
   } tmp;
+
+  struct {
+    const VTermSelectionCallbacks *callbacks;
+    void *user;
+    char *buffer;
+    size_t buflen;
+  } selection;
 };
 
 struct VTerm
@@ -181,6 +200,9 @@ struct VTerm
       OSC_COMMAND,
       OSC,
       DCS,
+      APC,
+      PM,
+      SOS,
     } state;
 
     unsigned int in_esc : 1;
@@ -248,7 +270,7 @@ void vterm_push_output_bytes(VTerm *vt, 
 void vterm_push_output_vsprintf(VTerm *vt, const char *format, va_list args);
 void vterm_push_output_sprintf(VTerm *vt, const char *format, ...);
 void vterm_push_output_sprintf_ctrl(VTerm *vt, unsigned char ctrl, const char *fmt, ...);
-void vterm_push_output_sprintf_dcs(VTerm *vt, const char *fmt, ...);
+void vterm_push_output_sprintf_str(VTerm *vt, unsigned char ctrl, int term, const char *fmt, ...);
 
 void vterm_state_free(VTermState *state);
 
--- a/src/libvterm/t/02parser.test
+++ b/src/libvterm/t/02parser.test
@@ -17,15 +17,15 @@ PUSH "\x1f"
 PUSH "\x83"
   control 0x83
 
-PUSH "\x9f"
-  control 0x9f
+PUSH "\x99"
+  control 0x99
 
 !C1 7bit
 PUSH "\e\x43"
   control 0x83
 
-PUSH "\e\x5f"
-  control 0x9f
+PUSH "\e\x59"
+  control 0x99
 
 !High bytes
 PUSH "\xa0\xcc\xfe"
@@ -184,6 +184,12 @@ PUSH "\ePHello\e\\"
 PUSH "\x{90}Hello\x9c"
   dcs ["Hello"]
 
+!Split write of 7bit ST
+PUSH "\ePABC\e"
+  dcs ["ABC"
+PUSH "\\"
+  dcs ]
+
 !Escape cancels DCS, starts Escape
 PUSH "\ePSomething\e9"
   escape "9"
@@ -198,6 +204,48 @@ PUSH "\ePBy\ne\x07"
   control 10
   dcs "e"]
 
+!APC BEL
+PUSH "\e_Hello\x07"
+  apc ["Hello"]
+
+!APC ST (7bit)
+PUSH "\e_Hello\e\\"
+  apc ["Hello"]
+
+!APC ST (8bit)
+PUSH "\x{9f}Hello\x9c"
+  apc ["Hello"]
+
+!PM BEL
+PUSH "\e^Hello\x07"
+  pm ["Hello"]
+
+!PM ST (7bit)
+PUSH "\e^Hello\e\\"
+  pm ["Hello"]
+
+!PM ST (8bit)
+PUSH "\x{9e}Hello\x9c"
+  pm ["Hello"]
+
+!SOS BEL
+PUSH "\eXHello\x07"
+  sos ["Hello"]
+
+!SOS ST (7bit)
+PUSH "\eXHello\e\\"
+  sos ["Hello"]
+
+!SOS ST (8bit)
+PUSH "\x{98}Hello\x9c"
+  sos ["Hello"]
+
+!SOS can contain any C0 or C1 code
+PUSH "\eXABC\x01DEF\e\\"
+  sos ["ABC\x01DEF"]
+PUSH "\eXABC\x99DEF\e\\"
+  sos ["ABC\x{99}DEF"]
+
 !NUL ignored
 PUSH "\x{00}"
 
--- a/src/libvterm/t/17state_mouse.test
+++ b/src/libvterm/t/17state_mouse.test
@@ -170,3 +170,12 @@ PUSH "\e[?1006\$p"
   output "\e[?1006;2\$y"
 PUSH "\e[?1015\$p"
   output "\e[?1015;1\$y"
+
+!Mouse disabled reports nothing
+RESET
+  settermprop 1 true
+  settermprop 2 true
+  settermprop 7 1
+MOUSEMOVE 0,0 0
+MOUSEBTN d 1 0
+MOUSEBTN u 1 0
--- a/src/libvterm/t/29state_fallback.test
+++ b/src/libvterm/t/29state_fallback.test
@@ -17,3 +17,15 @@ PUSH "\e]27;Something\e\\"
 !Unrecognised DCS
 PUSH "\ePz123\e\\"
   dcs ["z123"]
+
+!Unrecognised APC
+PUSH "\e_z123\e\\"
+  apc ["z123"]
+
+!Unrecognised PM
+PUSH "\e^z123\e\\"
+  pm ["z123"]
+
+!Unrecognised SOS
+PUSH "\eXz123\e\\"
+  sos ["z123"]
new file mode 100644
--- /dev/null
+++ b/src/libvterm/t/40state_selection.test
@@ -0,0 +1,55 @@
+INIT
+UTF8 1
+WANTSTATE
+
+!Set clipboard; final chunk len 4
+PUSH "\e]52;c;SGVsbG8s\e\\"
+  selection-set mask=0001 ["Hello,"]
+
+!Set clipboard; final chunk len 3
+PUSH "\e]52;c;SGVsbG8sIHc=\e\\"
+  selection-set mask=0001 ["Hello, w"]
+
+!Set clipboard; final chunk len 2
+PUSH "\e]52;c;SGVsbG8sIHdvcmxkCg==\e\\"
+  selection-set mask=0001 ["Hello, world\n"]
+
+!Set clipboard; split between chunks
+PUSH "\e]52;c;SGVs"
+  selection-set mask=0001 ["Hel"
+PUSH "bG8s\e\\"
+  selection-set mask=0001 "lo,"]
+
+!Set clipboard; split within chunk
+PUSH "\e]52;c;SGVsbG"
+  selection-set mask=0001 ["Hel"
+PUSH "8s\e\\"
+  selection-set mask=0001 "lo,"]
+
+!Query clipboard
+PUSH "\e]52;c;?\e\\"
+  selection-query mask=0001
+
+!Send clipboard; final chunk len 4
+SELECTION 1 ["Hello,"]
+  output "\e]52;c;SGVsbG8s\e\\"
+
+!Send clipboard; final chunk len 3
+SELECTION 1 ["Hello, w"]
+  output "\e]52;c;SGVsbG8sIHc=\e\\"
+
+!Send clipboard; final chunk len 2
+SELECTION 1 ["Hello, world\n"]
+  output "\e]52;c;SGVsbG8sIHdvcmxkCg==\e\\"
+
+!Send clipboard; split between chunks
+SELECTION 1 ["Hel"
+  output "\e]52;c;SGVs"
+SELECTION 1  "lo,"]
+  output "bG8s\e\\"
+
+!Send clipboard; split within chunk
+SELECTION 1 ["Hello"
+  output "\e]52;c;SGVs"
+SELECTION 1 ","]
+  output "bG8s\e\\"
--- a/src/libvterm/t/harness.c
+++ b/src/libvterm/t/harness.c
@@ -13,7 +13,8 @@ static size_t inplace_hex2bytes(char *s)
 
   while(*inpos) {
     unsigned int ch;
-    sscanf(inpos, "%2x", &ch);
+    if(sscanf(inpos, "%2x", &ch) < 1)
+      break;
     *outpos = ch;
     outpos += 1; inpos += 2;
   }
@@ -98,7 +99,7 @@ static void term_output(const char *s, s
 static void printhex(const char *s, size_t len)
 {
   while(len--)
-    printf("%02x", (s++)[0]);
+    printf("%02x", (uint8_t)(s++)[0]);
 }
 
 static int parser_text(const char bytes[], size_t len, void *user UNUSED)
@@ -216,6 +217,57 @@ static int parser_dcs(const char *comman
   return 1;
 }
 
+static int parser_apc(VTermStringFragment frag, void *user UNUSED)
+{
+  printf("apc ");
+
+  if(frag.initial)
+    printf("[");
+
+  printhex(frag.str, frag.len);
+
+  if(frag.final)
+    printf("]");
+
+  printf("\n");
+
+  return 1;
+}
+
+static int parser_pm(VTermStringFragment frag, void *user UNUSED)
+{
+  printf("pm ");
+
+  if(frag.initial)
+    printf("[");
+
+  printhex(frag.str, frag.len);
+
+  if(frag.final)
+    printf("]");
+
+  printf("\n");
+
+  return 1;
+}
+
+static int parser_sos(VTermStringFragment frag, void *user UNUSED)
+{
+  printf("sos ");
+
+  if(frag.initial)
+    printf("[");
+
+  printhex(frag.str, frag.len);
+
+  if(frag.final)
+    printf("]");
+
+  printf("\n");
+
+  return 1;
+}
+
 static VTermParserCallbacks parser_cbs = {
   parser_text, // text
   parser_control, // control
@@ -223,6 +275,9 @@ static VTermParserCallbacks parser_cbs =
   parser_csi, // csi
   parser_osc, // osc
   parser_dcs, // dcs
+  parser_apc, // apc
+  parser_pm, // pm
+  parser_sos, // sos
   NULL // resize
 };
 
@@ -230,7 +285,10 @@ static VTermStateFallbacks fallbacks = {
   parser_control, // control
   parser_csi, // csi
   parser_osc, // osc
-  parser_dcs // dcs
+  parser_dcs, // dcs
+  parser_apc, // dcs
+  parser_pm, // pm
+  parser_sos // sos
 };
 
 /* These callbacks are shared by State and Screen */
@@ -414,6 +472,31 @@ VTermStateCallbacks state_cbs = {
   state_setlineinfo, // setlineinfo
 };
 
+static int selection_set(VTermSelectionMask mask, VTermStringFragment frag, void *user UNUSED)
+{
+  printf("selection-set mask=%04X ", mask);
+  if(frag.initial)
+    printf("[");
+  printhex(frag.str, frag.len);
+  if(frag.final)
+    printf("]");
+  printf("\n");
+
+  return 1;
+}
+
+static int selection_query(VTermSelectionMask mask, void *user UNUSED)
+{
+  printf("selection-query mask=%04X\n", mask);
+
+  return 1;
+}
+
+VTermSelectionCallbacks selection_cbs = {
+  .set   = selection_set,
+  .query = selection_query,
+};
+
 static int want_screen_damage = 0;
 static int want_screen_damage_cells = 0;
 static int screen_damage(VTermRect rect, void *user UNUSED)
@@ -555,6 +638,7 @@ int main(int argc UNUSED, char **argv UN
       if(!state) {
         state = vterm_obtain_state(vt);
         vterm_state_set_callbacks(state, &state_cbs, NULL);
+        vterm_state_set_selection_callbacks(state, &selection_cbs, NULL, NULL, 1024);
         vterm_state_set_bold_highbright(state, 1);
         vterm_state_reset(state, 1);
       }
@@ -768,6 +852,32 @@ int main(int argc UNUSED, char **argv UN
       vterm_mouse_button(vt, button, (press == 'd' || press == 'D'), mod);
     }
 
+    else if(strstartswith(line, "SELECTION ")) {
+      char *linep = line + 10;
+      unsigned int mask;
+      int len;
+      VTermStringFragment frag = { 0 };
+      sscanf(linep, "%x%n", &mask, &len);
+      linep += len;
+      while(linep[0] == ' ')
+        linep++;
+      if(linep[0] == '[') {
+        frag.initial = TRUE;
+        linep++;
+        while(linep[0] == ' ')
+          linep++;
+      }
+      frag.len = inplace_hex2bytes(linep);
+      frag.str = linep;
+      linep += frag.len * 2;
+      while(linep[0] == ' ')
+        linep++;
+      if(linep[0] == ']') {
+        frag.final = TRUE;
+      }
+      vterm_state_send_selection(state, mask, frag);
+    }
+
     else if(strstartswith(line, "DAMAGEMERGE ")) {
       char *linep = line + 12;
       while(linep[0] == ' ')
--- a/src/libvterm/t/run-test.pl
+++ b/src/libvterm/t/run-test.pl
@@ -85,6 +85,11 @@ sub do_line
          my $string = eval($2);
          $line = "$1 " . unpack "H*", $string;
       }
+      elsif( $line =~ m/^(SELECTION \d+) +(\[?)(.*?)(\]?)$/ ) {
+         # we're evil
+         my $string = eval($3);
+         $line = "$1 $2 " . unpack( "H*", $string ) . " $4";
+      }
 
       do_onetest if defined $command;
 
@@ -113,15 +118,18 @@ sub do_line
 
          $line = "$cmd $initial" . join( "", map sprintf("%02x", $_), unpack "C*", length $data ? eval($data) : "" ) . "$final";
       }
-      elsif( $line =~ m/^(escape|dcs) (\[?)(.*?)(\]?)$/ ) {
-         $line = "$1 $2" . join( "", map sprintf("%02x", $_), unpack "C*", eval($3) ) . "$4";
+      elsif( $line =~ m/^(escape|dcs|apc|pm|sos) (\[?)(.*?)(\]?)$/ ) {
+         $line = "$1 $2" . join( "", map sprintf("%02x", $_), unpack "C*", length $3 ? eval($3) : "" ) . "$4";
       }
       elsif( $line =~ m/^putglyph (\S+) (.*)$/ ) {
          $line = "putglyph " . join( ",", map sprintf("%x", $_), eval($1) ) . " $2";
       }
-      elsif( $line =~ m/^(?:movecursor|scrollrect|moverect|erase|damage|sb_pushline|sb_popline|settermprop|setmousefunc) / ) {
+      elsif( $line =~ m/^(?:movecursor|scrollrect|moverect|erase|damage|sb_pushline|sb_popline|settermprop|setmousefunc|selection-query) / ) {
          # no conversion
       }
+      elsif( $line =~ m/^(selection-set) (.*?) (\[?)(.*?)(\]?)$/ ) {
+         $line = "$1 $2 $3" . join( "", map sprintf("%02x", $_), unpack "C*", eval($4) ) . "$5";
+      }
       else {
          warn "Unrecognised test expectation '$line'\n";
       }
--- a/src/libvterm/vterm.pc.in
+++ b/src/libvterm/vterm.pc.in
@@ -1,6 +1,5 @@
-prefix=@PREFIX@
 libdir=@LIBDIR@
-includedir=${prefix}/include
+includedir=@INCDIR@
 
 Name: vterm
 Description: Abstract VT220/Xterm/ECMA-48 emulation library
--- a/src/terminal.c
+++ b/src/terminal.c
@@ -4526,7 +4526,10 @@ static VTermStateFallbacks state_fallbac
   NULL,		// control
   parse_csi,	// csi
   parse_osc,	// osc
-  NULL		// dcs
+  NULL,		// dcs
+  NULL,		// apc
+  NULL,		// pm
+  NULL		// sos
 };
 
 /*
--- a/src/version.c
+++ b/src/version.c
@@ -758,6 +758,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    3666,
+/**/
     3665,
 /**/
     3664,