patch 9.1.1485: missing Wayland clipboard support

Problem:  missing Wayland clipboard support
Solution: make it work (Foxe Chen)

fixes: #5157
closes: #17097

Signed-off-by: Foxe Chen <chen.foxe@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
diff --git a/src/testdir/Make_all.mak b/src/testdir/Make_all.mak
index 1cbede6..4972487 100644
--- a/src/testdir/Make_all.mak
+++ b/src/testdir/Make_all.mak
@@ -102,6 +102,7 @@
 	test_cindent \
 	test_cjk_linebreak \
 	test_clientserver \
+	test_clipmethod \
 	test_close_count \
 	test_cmd_lists \
 	test_cmdline \
@@ -344,6 +345,7 @@
 	test_vimscript \
 	test_virtualedit \
 	test_visual \
+	test_wayland \
 	test_winbar \
 	test_winbuf_close \
 	test_window_cmd \
@@ -388,6 +390,7 @@
 	test_cindent.res \
 	test_cjk_linebreak.res \
 	test_clientserver.res \
+	test_clipmethod.res \
 	test_close_count.res \
 	test_cmd_lists.res \
 	test_cmdline.res \
@@ -596,6 +599,7 @@
 	test_vimscript.res \
 	test_virtualedit.res \
 	test_visual.res \
+	test_wayland.res \
 	test_winbar.res \
 	test_winbuf_close.res \
 	test_window_cmd.res \
diff --git a/src/testdir/gen_opt_test.vim b/src/testdir/gen_opt_test.vim
index 5b9616f..78a783e 100644
--- a/src/testdir/gen_opt_test.vim
+++ b/src/testdir/gen_opt_test.vim
@@ -115,6 +115,7 @@
       \ 'winminheight': [[0, 1], [-1]],
       \ 'winminwidth': [[0, 1, 10], [-1]],
       \ 'winwidth': [[1, 10, 999], [-1, 0]],
+      \ 'wltimeoutlen': [[1, 10, 999],[-1]],
       \
       "\ string options
       \ 'ambiwidth': [['', 'single', 'double'], ['xxx']],
@@ -148,6 +149,7 @@
       \		'autoselectplus', 'autoselectml', 'html', 'exclude:vimdisplay',
       \		'autoselect,unnamed', 'unnamed,exclude:.*'],
       \		['xxx', 'exclude:\\ze*', 'exclude:\\%(']],
+      \ 'clipmethod': [['wayland', 'x11', 'wayland,x11', ''],['xxx', '--', 'wayland,,', ',x11']],
       \ 'colorcolumn': [['', '8', '+2', '1,+1,+3'], ['xxx', '-a', '1,', '1;']],
       \ 'comments': [['', 'b:#', 'b:#,:%'], ['xxx', '-']],
       \ 'commentstring': [['', '/*\ %s\ */'], ['xxx']],
diff --git a/src/testdir/test_clipmethod.vim b/src/testdir/test_clipmethod.vim
new file mode 100644
index 0000000..7573ce2
--- /dev/null
+++ b/src/testdir/test_clipmethod.vim
@@ -0,0 +1,169 @@
+" Tests for clipmethod
+
+source check.vim
+source shared.vim
+source window_manager.vim
+
+CheckFeature clipboard_working
+CheckFeature xterm_clipboard
+CheckFeature wayland_clipboard
+CheckUnix
+
+" Test if no available clipmethod sets v:clipmethod to none and deinits clipboard
+func Test_no_clipmethod_sets_v_clipmethod_none()
+  CheckNotGui
+
+  set clipmethod=
+  call assert_equal("none", v:clipmethod)
+  call assert_equal(0, has('clipboard_working'))
+endfunc
+
+" Test if method chosen is in line with clipmethod order
+func Test_clipmethod_order()
+  CheckNotGui
+
+  set cpm=wayland,x11
+
+  let l:wayland_display = StartWaylandCompositor()
+
+  let $WAYLAND_DISPLAY = l:wayland_display
+  exe 'wlrestore ' .. l:wayland_display
+
+  call assert_equal("wayland", v:clipmethod)
+
+  :wlrestore 1239
+  clipreset
+
+  call assert_equal("x11", v:clipmethod)
+
+  :xrestore 1239
+  clipreset
+
+  call assert_equal("none", v:clipmethod)
+  call assert_equal(0, has('clipboard_working'))
+
+  exe ":wlrestore " . $WAYLAND_DISPLAY
+  exe ":xrestore " . $DISPLAY
+  clipreset
+
+  call assert_equal("wayland", v:clipmethod)
+  call assert_equal(1, has('clipboard_working'))
+
+  set cpm=x11
+
+  call assert_equal("x11", v:clipmethod)
+
+  set cpm=wayland
+
+  call assert_equal("wayland", v:clipmethod)
+
+  call EndWaylandCompositor(l:wayland_display)
+endfunc
+
+" Test if clipmethod is set to 'none' when gui is started
+func Test_clipmethod_is_none_when_gui()
+  CheckCanRunGui
+
+  let lines =<< trim END
+    set cpm=wayland,x11
+    call writefile([v:clipmethod != ""], 'Cbdscript')
+    gui -f
+    call writefile([v:clipmethod], 'Cbdscript', 'a')
+    clipreset
+    call writefile([v:clipmethod], 'Cbdscript', 'a')
+    quit
+  END
+
+  call writefile(lines, 'Cbdscript', 'D')
+  call system($'{GetVimCommand()} -S Cbdscript')
+  call assert_equal(['1', 'none', 'none'], readfile('Cbdscript'))
+endfunc
+
+" Test if :clipreset switches methods when current one doesn't work
+func Test_clipreset_switches()
+  CheckNotGui
+  CheckFeature clientserver
+  CheckXServer
+  CheckWaylandCompositor
+
+  let l:wayland_display = StartWaylandCompositor()
+
+  set cpm=wayland,x11
+
+  exe 'wlrestore ' .. l:wayland_display
+
+  call assert_equal(l:wayland_display, v:wayland_display)
+  call assert_equal("wayland", v:clipmethod)
+
+  call EndWaylandCompositor(l:wayland_display)
+
+  " wlrestore updates clipmethod as well
+  wlrestore!
+
+  call assert_equal("", v:wayland_display)
+  call assert_equal("x11", v:clipmethod)
+
+  " Do the same but kill a X11 server
+
+  " X11 error handling relies on longjmp magic, but essentially if the X server
+  " is killed then it will simply abandon the current commands, making the test
+  " hang.
+
+  " This will only happen for commands given from the command line, which
+  " is why we cannot just directly call Vim or use the actual Vim instance thats
+  " doing all the testing, since main_loop() is never executed.
+
+  " Therefore we should start a separate Vim instance and communicate with it
+  " remotely, so we can execute the actual testing stuff with main_loop()
+  " running.
+
+  let l:lines =<< trim END
+    set cpm=x11
+    source shared.vim
+
+    func Test()
+      clipreset
+
+      if v:clipmethod ==# 'none'
+        return 1
+      endif
+      return 0
+    endfunc
+
+    func DoIt()
+      call WaitFor(function('Test'))
+
+      if v:clipmethod == 'none'
+        call writefile(['SUCCESS'], 'Xtest')
+      else
+        call writefile(['FAIL'], 'Xtest')
+      endif
+      quitall
+    endfunc
+  END
+  call writefile(l:lines, 'Xtester', 'D')
+
+  let l:xdisplay = StartXServer()
+
+  let l:name = 'XVIMTEST'
+  let l:cmd = GetVimCommand() .. ' -S Xtester --servername ' .. l:name
+  let l:job = job_start(l:cmd, { 'stoponexit': 'kill', 'out_io': 'null'})
+
+  call WaitForAssert({-> assert_equal("run", job_status(l:job))})
+  call WaitForAssert({-> assert_match(l:name, serverlist())})
+
+  " Change x server to the one that will be killed, then block until
+  " v:clipmethod is none.
+  call remote_send(l:name, ":xrestore " .. l:xdisplay ..
+        \ ' | call DoIt()' .. "\<CR>")
+
+  call EndXServer(l:xdisplay)
+
+  call WaitFor({-> filereadable('Xtest')})
+
+  " For some reason readfile sometimes returns an empty list despite the file
+  " existing, this why WaitForAssert() is used.
+  call WaitForAssert({-> assert_equal(['SUCCESS'], readfile('Xtest'))}, 1000)
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_options.vim b/src/testdir/test_options.vim
index e85dd6a..cb5636e 100644
--- a/src/testdir/test_options.vim
+++ b/src/testdir/test_options.vim
@@ -523,6 +523,13 @@
   if exists('+clipboard')
     call assert_match('unnamed', getcompletion('set clipboard=', 'cmdline')[1])
   endif
+  if exists('+clipmethod')
+    if has('unix') || has('vms')
+      call assert_match('wayland', getcompletion('set clipmethod=', 'cmdline')[1])
+    else
+      call assert_match('wayland', getcompletion('set clipmethod=', 'cmdline')[0])
+    endif
+  endif
   call assert_equal('.', getcompletion('set complete=', 'cmdline')[1])
   call assert_equal('menu', getcompletion('set completeopt=', 'cmdline')[1])
   call assert_equal('keyword', getcompletion('set completefuzzycollect=', 'cmdline')[0])
@@ -2593,6 +2600,7 @@
   endif
   if has('clipboard_working')
     call add(optlist, ['clipboard', 'unnamed', 'a123'])
+    call add(optlist, ['clipmethod', 'wayland', 'a123'])
   endif
   if has('win32')
     call add(optlist, ['completeslash', 'slash', 'a123'])
diff --git a/src/testdir/test_registers.vim b/src/testdir/test_registers.vim
index b597ab3..3615e1e 100644
--- a/src/testdir/test_registers.vim
+++ b/src/testdir/test_registers.vim
@@ -1092,20 +1092,20 @@
 endfunc
 
 " Check for W23 with a Vim with clipboard support,
-" but when the connection to the X11 server does not work
+" but when there is no available clipmethod
 func Test_clipboard_regs_not_working2()
   CheckNotMac
   CheckRunVimInTerminal
   CheckFeature clipboard
-  let display=$DISPLAY
-  unlet $DISPLAY
+  set clipmethod=
   " Run in a separate Vim instance because changing 'encoding' may cause
   " trouble for later tests.
   let lines =<< trim END
-      unlet $DISPLAY
+      set clipmethod=
       call setline(1, 'abcdefg')
       let a=execute(':norm! "+yy')
       call writefile([a], 'Xclipboard_result.txt')
+      set clipmethod&
   END
   call writefile(lines, 'XTest_clipboard', 'D')
   let buf = RunVimInTerminal('-S XTest_clipboard', {})
@@ -1113,7 +1113,7 @@
   call StopVimInTerminal(buf)
   let result = readfile('Xclipboard_result.txt')
   call assert_match("^\\nW23:", result[0])
-  let $DISPLAY=display
+  set clipmethod&
 endfunc
 
 " This caused use-after-free
diff --git a/src/testdir/test_startup.vim b/src/testdir/test_startup.vim
index 6d43487..c56173e 100644
--- a/src/testdir/test_startup.vim
+++ b/src/testdir/test_startup.vim
@@ -559,7 +559,7 @@
   CheckUnix
   CheckNotGui
 
-  for opt in ['-Y', '--does-not-exist']
+  for opt in ['-K', '--does-not-exist']
     let out = split(system(GetVimCommand() .. ' ' .. opt), "\n")
     call assert_equal(1, v:shell_error)
     call assert_match('^VIM - Vi IMproved .* (.*)$',              out[0])
diff --git a/src/testdir/test_wayland.vim b/src/testdir/test_wayland.vim
new file mode 100644
index 0000000..ef4d8fb
--- /dev/null
+++ b/src/testdir/test_wayland.vim
@@ -0,0 +1,663 @@
+source check.vim
+source shared.vim
+source window_manager.vim
+
+CheckFeature wayland
+CheckFeature wayland_clipboard
+CheckUnix
+CheckFeature job
+CheckWaylandCompositor
+CheckNotGui
+
+if !executable('wl-paste') || !executable('wl-copy')
+  throw "Skipped: wl-clipboard is not available"
+endif
+
+" Process will be killed when the test ends
+let s:global_wayland_display = StartWaylandCompositor()
+let s:old_wayland_display = $WAYLAND_DISPLAY
+
+" For some reason if $WAYLAND_DISPLAY is set in the global namespace (not in a
+" function), it won't actually be set if $WAYLAND_DISPLAY was not set before
+" (such as in a CI environment) ? Solution is to just set it before the code of
+" every test function
+func s:PreTest()
+  let $WAYLAND_DISPLAY=s:global_wayland_display
+  exe 'wlrestore ' .. $WAYLAND_DISPLAY
+
+  set cpm=wayland
+endfunc
+
+func s:SetupFocusStealing()
+  if !executable('wayland-info')
+    throw "Skipped: wayland-info program not available"
+  endif
+
+  " Starting a headless compositor won't expose a keyboard capability for its
+  " seat, so we must use the user's existing Wayland session if they are in one.
+  let $WAYLAND_DISPLAY = s:old_wayland_display
+
+  exe 'wlrestore ' .. $WAYLAND_DISPLAY
+
+  " Check if we have keyboard capability for seat
+  if system("wayland-info -i wl_seat | grep capabilities") !~? "keyboard"
+    throw "Skipped: seat does not have keyboard"
+  endif
+
+  let $VIM_WAYLAND_FORCE_FS=1
+  wlrestore!
+endfunc
+
+func s:UnsetupFocusStealing()
+  unlet $VIM_WAYLAND_FORCE_FS
+endfunc
+
+" Need X connection for tests that use client server communication
+func s:CheckXConnection()
+  if has('x11')
+    try
+      call remote_send('xxx', '')
+    catch
+      if v:exception =~ 'E240:'
+        throw 'Skipped: no connection to the X server'
+      endif
+      " ignore other errors
+    endtry
+  endif
+endfunc
+
+func s:EndRemoteVim(name, job)
+  eval remote_send(a:name, "\<Esc>:qa!\<CR>")
+  try
+    call WaitForAssert({-> assert_equal("dead", job_status(a:job))})
+  finally
+    if job_status(a:job) != 'dead'
+      call assert_report('Vim instance did not exit')
+      call job_stop(a:job, 'kill')
+    endif
+  endtry
+endfunc
+
+func Test_wayland_startup()
+  call s:PreTest()
+  call s:CheckXConnection()
+
+  let l:name = 'WLVIMTEST'
+  let l:cmd = GetVimCommand() .. ' --servername ' .. l:name
+  let l:job = job_start(cmd, {'stoponexit': 'kill', 'out_io': 'null'})
+
+  call WaitForAssert({-> assert_equal("run", job_status(l:job))})
+  call WaitForAssert({-> assert_match(name, serverlist())})
+
+  call WaitForAssert({-> assert_equal($WAYLAND_DISPLAY,
+        \ remote_expr(l:name, 'v:wayland_display'))})
+
+  call s:EndRemoteVim(l:name, l:job)
+
+  " When $WAYLAND_DISPLAY is invalid
+  let l:job = job_start(cmd, { 'stoponexit': 'kill', 'out_io': 'null',
+        \ 'env': {'WAYLAND_DISPLAY': 'UNKNOWN'}})
+
+  call WaitForAssert({-> assert_equal("run", job_status(l:job))})
+  call WaitForAssert({-> assert_match(name, serverlist())})
+
+  call assert_equal('', remote_expr(l:name, 'v:wayland_display'))
+  call s:EndRemoteVim(l:name, l:job)
+endfunc
+
+func Test_wayland_wlrestore()
+  call s:PreTest()
+
+  let l:wayland_display = StartWaylandCompositor()
+  let l:env_cmd = 'WAYLAND_DISPLAY=' .. l:wayland_display .. ' '
+
+  exe "wlrestore " .. l:wayland_display
+
+  call assert_equal(l:wayland_display, v:wayland_display)
+
+  " Check if calling wlrestore without arguments uses the existing wayland
+  " display.
+  wlrestore!
+  call assert_equal(l:wayland_display, v:wayland_display)
+
+  " If called with invalid display
+  wlrestore IDONTEXIST
+  call assert_equal("", v:wayland_display)
+
+  wlrestore!
+  call assert_equal("", v:wayland_display)
+
+  exe "wlrestore " .. l:wayland_display
+  call assert_equal(l:wayland_display, v:wayland_display)
+
+  " Actually check if connected display is different in case of regression with
+  " v:wayland_display
+  call system('wl-copy "1"')
+  call system(l:env_cmd .. 'wl-copy "2"')
+
+  call assert_equal('2', getreg('+'))
+
+  " Check if wlrestore doesn't disconnect the display if not nessecary by seeing
+  " if Vim doesn't lose the selection
+  call setreg('+', 'testing', 'c')
+
+  wlrestore
+  call assert_match('_VIM_TEXT', system(l:env_cmd .. 'wl-paste -l'))
+
+  " Forcibly disconnect and reconnect the display
+  wlrestore!
+  call assert_notmatch('_VIM_TEXT', system(l:env_cmd .. 'wl-paste -l'))
+
+  call EndWaylandCompositor(l:wayland_display)
+endfunc
+
+" Test behaviour when wayland display connection is lost
+func Test_wayland_connection_lost()
+  call s:PreTest()
+
+  let l:wayland_display = StartWaylandCompositor()
+  let l:env_cmd = 'WAYLAND_DISPLAY=' .. l:wayland_display .. ' '
+
+  exe "wlrestore " .. l:wayland_display
+
+  call system(l:env_cmd .. 'wl-copy test')
+
+  call assert_equal(l:wayland_display, v:wayland_display)
+  call assert_equal('test', getreg('+'))
+
+  call EndWaylandCompositor(l:wayland_display)
+
+  call assert_equal('', getreg('+'))
+  call assert_fails('put +', 'E353:')
+  call assert_fails('yank +', 'E1548:')
+endfunc
+
+" Basic paste tests
+func Test_wayland_paste()
+  call s:PreTest()
+
+  " Prevent 'Register changed while using it' error, guessing this works because
+  " it makes Vim lose the selection?
+  wlrestore!
+
+  " Regular selection
+  new
+
+  call system('wl-copy "TESTING"')
+  put +
+
+  call assert_equal("TESTING", getline(2))
+
+  call system('printf "LINE1\nLINE2\nLINE3" | wl-copy -n')
+  put +
+
+  call assert_equal(["LINE1", "LINE2", "LINE3"], getline(3, 5))
+  bw!
+
+  " Primary selection
+  new
+
+  call system('wl-copy -p "TESTING"')
+  put *
+
+  call assert_equal("TESTING", getline(2))
+
+  call system('printf "LINE1\nLINE2\nLINE3" | wl-copy -p')
+  put *
+
+  call assert_equal(["LINE1", "LINE2", "LINE3"], getline(3, 5))
+
+  bw!
+
+  " Check behaviour when selecton is cleared (empty)
+  call system('wl-copy --clear')
+  call assert_fails('put +', 'E353:')
+endfunc
+
+" Basic yank/copy tests
+func Test_wayland_yank()
+  call s:PreTest()
+
+  wlrestore!
+
+  new
+
+  call setline(1, 'testing')
+  yank +
+
+  call assert_equal("testing\n", system('wl-paste -n'))
+
+  call setline(2, 'testing2')
+  call setline(3, 'testing3')
+  exe '1,3yank +'
+
+  call assert_equal("testing\ntesting2\ntesting3\n", system('wl-paste -n'))
+
+  bw!
+
+  " Primary selection
+  new
+
+  call setline(1, 'testing')
+  yank *
+
+  call assert_equal("testing\n", system('wl-paste -p -n'))
+
+  call setline(2, 'testing2')
+  call setline(3, 'testing3')
+  exe '1,3yank *'
+
+  call assert_equal("testing\ntesting2\ntesting3\n", system('wl-paste -p -n'))
+
+  bw!
+endfunc
+
+
+" Check if correct mime types are advertised when we own the selection
+func Test_wayland_mime_types_correct()
+  call s:PreTest()
+
+  let l:mimes = [
+        \ '_VIMENC_TEXT',
+        \ '_VIM_TEXT',
+        \ 'text/plain;charset=utf-8',
+        \ 'text/plain',
+        \ 'UTF8_STRING',
+        \ 'STRING',
+        \ 'TEXT'
+        \ ]
+
+  call setreg('+', 'text', 'c')
+
+  for mime in split(system('wl-paste -l'), "\n")
+    if index(l:mimes, mime) == -1
+      call assert_report("'" .. mime .. "' is not a supported mime type")
+    endif
+  endfor
+
+  call setreg('*', 'text', 'c')
+
+  for mime in split(system('wl-paste -p -l'), "\n")
+    if index(l:mimes, mime) == -1
+      call assert_report("'" .. mime .. "' is not a supported mime type")
+    endif
+  endfor
+endfunc
+
+" Test if the _VIM_TEXT and _VIMENC_TEXT formats are correct:
+" _VIM_TEXT: preserves motion type (line/char/block wise)
+" _VIMENC_TEXT: same but also indicates the encoding type
+func Test_wayland_paste_vim_format_correct()
+  call s:PreTest()
+
+  " Vim doesn't support null characters in strings, so we use the -v flag of the
+  " cat program to show them in a printable way, if it is available.
+  call system("cat -v")
+  if v:shell_error != 0
+    throw 'Skipped: cat program does not have -v command-line flag'
+  endif
+
+  set encoding=utf-8
+
+  let l:GetSel = {type -> system('wl-paste -t ' .. type .. ' | cat -v')}
+  let l:GetSelP = {type -> system('wl-paste -p -t ' .. type .. ' | cat -v')}
+
+  " Regular selection
+  call setreg('+', 'text', 'c')
+  call assert_equal("^@text", l:GetSel('_VIM_TEXT'))
+  call setreg('+', 'text', 'c')
+  call assert_equal("^@utf-8^@text", l:GetSel('_VIMENC_TEXT'))
+
+  call setreg('+', 'text', 'l')
+  call assert_equal("^Atext\n", l:GetSel('_VIM_TEXT'))
+  call setreg('+', 'text', 'l')
+  call assert_equal("^Autf-8^@text\n",l:GetSel('_VIMENC_TEXT'))
+
+  call setreg('+', 'text', 'b')
+  call assert_equal("^Btext\n", l:GetSel('_VIM_TEXT'))
+  call setreg('+', 'text', 'b')
+  call assert_equal("^Butf-8^@text\n", l:GetSel('_VIMENC_TEXT'))
+
+  " Primary selection
+  call setreg('*', 'text', 'c')
+  call assert_equal("^@text", l:GetSelP('_VIM_TEXT'))
+  call setreg('*', 'text', 'c')
+  call assert_equal("^@utf-8^@text", l:GetSelP('_VIMENC_TEXT'))
+
+  call setreg('*', 'text', 'l')
+  call assert_equal("^Atext\n", l:GetSelP('_VIM_TEXT'))
+  call setreg('*', 'text', 'l')
+  call assert_equal("^Autf-8^@text\n",l:GetSelP('_VIMENC_TEXT'))
+
+  call setreg('*', 'text', 'b')
+  call assert_equal("^Btext\n", l:GetSelP('_VIM_TEXT'))
+  call setreg('*', 'text', 'b')
+  call assert_equal("^Butf-8^@text\n", l:GetSelP('_VIMENC_TEXT'))
+
+  set encoding&
+endfunc
+
+" Test checking if * and + registers are not the same
+func Test_wayland_plus_star_not_same()
+  call s:PreTest()
+  new
+
+  call system('wl-copy "regular"')
+  call system('wl-copy -p "primary"')
+
+  call assert_notequal(getreg('+'), getreg('*'))
+
+  " Check if when we are the source client
+  call setreg('+', 'REGULAR')
+  call setreg('*', 'PRIMARY')
+
+  call assert_notequal(system('wl-paste -p'), system('wl-paste'))
+
+  bw!
+endfunc
+
+" Test if autoselect option in 'clipboard' works properly for wayland
+func Test_wayland_autoselect_works()
+  call s:PreTest()
+  call s:CheckXConnection()
+
+  let l:lines =<< trim END
+  set cpm=wayland
+  set clipboard=autoselect
+
+  new
+  call setline(1, 'LINE 1')
+  call setline(2, 'LINE 2')
+  call setline(3, 'LINE 3')
+
+  call cursor(1, 1)
+  END
+
+  call writefile(l:lines, 'Wltester', 'D')
+
+  let l:name = 'WLVIMTEST'
+  let l:cmd = GetVimCommand() .. ' -S Wltester --servername ' .. l:name
+  let l:job = job_start(cmd, {'stoponexit': 'kill', 'out_io': 'null'})
+
+  call WaitForAssert({-> assert_equal("run", job_status(l:job))})
+  call WaitForAssert({-> assert_match(name, serverlist())})
+  1
+  call remote_send(l:name, "ve")
+  call WaitForAssert({-> assert_equal('LINE', system('wl-paste -p -n'))})
+
+  call remote_send(l:name, "w")
+  call WaitForAssert({-> assert_equal('LINE 1', system('wl-paste -p -n'))})
+
+  call remote_send(l:name, "V")
+  call WaitForAssert({-> assert_equal("LINE 1\n", system('wl-paste -p -n'))})
+
+  " Reset cursor
+  call remote_send(l:name, "\<Esc>:call cursor(1, 1)\<CR>")
+  call WaitForAssert({-> assert_equal("LINE 1\n", system('wl-paste -p -n'))})
+
+  " Test visual block mode
+  call remote_send(l:name, "\<C-q>jjj") " \<C-v> doesn't seem to work but \<C-q>
+                                        " does...
+
+  call WaitForAssert({-> assert_equal("L\nL\nL\n", system('wl-paste -p -n'))})
+
+  eval remote_send(l:name, "\<Esc>:qa!\<CR>")
+
+  try
+    call WaitForAssert({-> assert_equal("dead", job_status(l:job))})
+  finally
+    if job_status(l:job) != 'dead'
+      call assert_report('Vim instance did not exit')
+      call job_stop(l:job, 'kill')
+    endif
+  endtry
+endfunc
+
+" Check if the -Y flag works properly
+func Test_no_wayland_connect_cmd_flag()
+  call s:PreTest()
+  call s:CheckXConnection()
+
+  let l:name = 'WLFLAGVIMTEST'
+  let l:cmd = GetVimCommand() .. ' -Y --servername ' .. l:name
+  let l:job = job_start(cmd, {'stoponexit': 'kill', 'out_io': 'null'})
+
+  call WaitForAssert({-> assert_equal("run", job_status(l:job))})
+  call WaitForAssert({-> assert_match(name, serverlist())})
+
+  call WaitForAssert({->assert_equal('',
+        \ remote_expr(l:name, 'v:wayland_display'))})
+
+  call remote_send(l:name, ":wlrestore\<CR>")
+  call WaitForAssert({-> assert_equal('',
+        \ remote_expr(l:name, 'v:wayland_display'))})
+
+  call remote_send(l:name, ":wlrestore " .. $WAYLAND_DISPLAY .. "\<CR>")
+  call WaitForAssert({-> assert_equal('',
+        \ remote_expr(l:name, 'v:wayland_display'))})
+
+  call remote_send(l:name, ":wlrestore IDONTEXIST\<CR>")
+  call WaitForAssert({-> assert_equal('',
+        \ remote_expr(l:name, 'v:wayland_display'))})
+
+  eval remote_send(l:name, "\<Esc>:qa!\<CR>")
+  try
+    call WaitForAssert({-> assert_equal("dead", job_status(l:job))})
+  finally
+    if job_status(l:job) != 'dead'
+      call assert_report('Vim instance did not exit')
+      call job_stop(l:job, 'kill')
+    endif
+  endtry
+endfunc
+
+" Test behaviour when we do something like suspend Vim
+func Test_wayland_become_inactive()
+  call s:PreTest()
+  call s:CheckXConnection()
+
+  let l:name = 'WLLOSEVIMTEST'
+  let l:cmd = GetVimCommand() .. ' --servername ' .. l:name
+  let l:job = job_start(cmd, {
+        \ 'stoponexit': 'kill',
+        \ 'out_io': 'null',
+        \ })
+
+  call WaitForAssert({-> assert_equal("run", job_status(l:job))})
+  call WaitForAssert({-> assert_match(name, serverlist())})
+
+  call remote_send(l:name, "iSOME TEXT\<Esc>\"+yy")
+
+  call WaitForAssert({-> assert_equal("SOME TEXT\n",
+        \ system('wl-paste -n'))})
+
+  call remote_send(l:name, "\<C-z>")
+
+  call WaitForAssert({-> assert_equal("Nothing is copied\n",
+        \ system('wl-paste -n'))})
+
+  eval remote_send(l:name, "\<Esc>:qa!\<CR>")
+  try
+    call WaitForAssert({-> assert_equal("dead", job_status(l:job))})
+  finally
+    if job_status(l:job) != 'dead'
+      call assert_report('Vim instance did not exit')
+      call job_stop(l:job, 'kill')
+    endif
+  endtry
+endfunc
+
+" Test wlseat option
+func Test_wayland_seat()
+  call s:PreTest()
+
+  " Don't know a way to create a virtual seat so just test using the existing
+  " one only
+  set wlseat=
+
+  call system('wl-copy "TESTING"')
+  call assert_equal('TESTING', getreg('+'))
+
+  set wlseat=UNKNOWN
+
+  call assert_equal('', getreg('+'))
+
+  set wlseat=idontexist
+
+  call assert_equal('', getreg('+'))
+
+  set wlseat=
+
+  call assert_equal('TESTING', getreg('+'))
+
+  set wlseat&
+endfunc
+
+" Test focus stealing
+func Test_wayland_focus_steal()
+  call s:PreTest()
+  call s:SetupFocusStealing()
+
+  call system('wl-copy regular')
+
+  call assert_equal('regular', getreg('+'))
+
+  call system('wl-copy -p primary')
+
+  call assert_equal('primary', getreg('*'))
+
+  call setreg('+', 'REGULAR')
+
+  call assert_equal('REGULAR', system('wl-paste -n'))
+
+  call setreg('*', 'PRIMARY')
+
+  call assert_equal('PRIMARY', system('wl-paste -p -n'))
+
+  call s:UnsetupFocusStealing()
+endfunc
+
+" Test when environment is not suitable for Wayland
+func Test_wayland_bad_environment()
+  call s:PreTest()
+  call s:CheckXConnection()
+
+  unlet $WAYLAND_DISPLAY
+
+  let l:old = $XDG_RUNTIME_DIR
+  unlet $XDG_RUNTIME_DIR
+
+  let l:name = 'WLVIMTEST'
+  let l:cmd = GetVimCommand() .. ' --servername ' .. l:name
+  let l:job = job_start(cmd, {
+        \ 'stoponexit': 'kill',
+        \ 'out_io': 'null',
+        \ })
+
+  call WaitForAssert({-> assert_equal("run", job_status(l:job))})
+  call WaitForAssert({-> assert_match(name, serverlist())})
+
+  call WaitForAssert({-> assert_equal('',
+        \ remote_expr(l:name, 'v:wayland_display'))})
+
+  eval remote_send(l:name, "\<Esc>:qa!\<CR>")
+  try
+    call WaitForAssert({-> assert_equal("dead", job_status(l:job))})
+  finally
+    if job_status(l:job) != 'dead'
+      call assert_report('Vim instance did not exit')
+      call job_stop(l:job, 'kill')
+    endif
+  endtry
+
+  let $XDG_RUNTIME_DIR = l:old
+endfunc
+
+" Test if Vim still works properly after losing the selection
+func Test_wayland_lost_selection()
+  call s:PreTest()
+
+  call setreg('+', 'regular')
+  call setreg('*', 'primary')
+
+  call assert_equal('regular', getreg('+'))
+  call assert_equal('primary', getreg('*'))
+
+  call system('wl-copy overwrite')
+  call system('wl-copy -p overwrite')
+
+  call assert_equal('overwrite', getreg('+'))
+  call assert_equal('overwrite', getreg('*'))
+
+  call setreg('+', 'regular')
+  call setreg('*', 'primary')
+
+  call assert_equal('regular', getreg('+'))
+  call assert_equal('primary', getreg('*'))
+
+  " Test focus stealing
+  call s:SetupFocusStealing()
+
+  call setreg('+', 'regular')
+  call setreg('*', 'primary')
+
+  call assert_equal('regular', getreg('+'))
+  call assert_equal('primary', getreg('*'))
+
+  call system('wl-copy overwrite')
+  call system('wl-copy -p overwrite')
+
+  call assert_equal('overwrite', getreg('+'))
+  call assert_equal('overwrite', getreg('*'))
+
+  call setreg('+', 'regular')
+  call setreg('*', 'primary')
+
+  call assert_equal('regular', getreg('+'))
+  call assert_equal('primary', getreg('*'))
+
+  call s:UnsetupFocusStealing()
+endfunc
+
+" Test when there are no supported mime types for the selecftion
+func Test_wayland_no_mime_types_supported()
+  call s:PreTest()
+
+  wlrestore!
+
+  call system('wl-copy -t image/png testing')
+
+  call assert_equal('', getreg('+'))
+  call assert_fails('put +', 'E353:')
+endfunc
+
+" Test behaviour with large selections in terms of data size
+func Test_wayland_handle_large_data()
+  call s:PreTest()
+
+  call writefile([''], 'data_file', 'D')
+  call writefile([''], 'data_file_cmp', 'D')
+  call system('yes c | head -5000000 > data_file') " ~ 10 MB
+  call system('wl-copy -t TEXT < data_file')
+
+  edit data_file_cmp
+
+  put! +
+
+  write
+
+  call system('truncate -s -1 data_file_cmp') " Remove newline at the end
+  call system('cmp --silent data_file data_file_cmp')
+  call assert_equal(0, v:shell_error)
+
+  " copy the text
+  call feedkeys('gg0v$G"+yy', 'x')
+  call system('wl-paste -n -t TEXT > data_file')
+
+  call system('cmp --silent data_file data_file_cmp')
+  call assert_equal(0, v:shell_error)
+
+  bw!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/window_manager.vim b/src/testdir/window_manager.vim
new file mode 100644
index 0000000..39e643e
--- /dev/null
+++ b/src/testdir/window_manager.vim
@@ -0,0 +1,110 @@
+source check.vim
+source shared.vim
+
+CheckFeature job
+CheckUnix
+
+let g:xdisplay_num = 100
+
+" Each key is the display name and its value is the compositor/wm job
+let s:wayland_displays = {}
+let s:x11_displays = {}
+
+command -nargs=0 CheckWaylandCompositor call CheckWaylandCompositor()
+command -nargs=0 CheckXServer call CheckXServer()
+
+func CheckWaylandCompositor()
+  CheckFeature wayland
+
+  if executable("labwc") != 1
+    throw "Skipped: labwc is not available"
+  endif
+endfunc
+
+func CheckXServer()
+  CheckFeature x11
+
+  if executable("Xvfb") != 1
+    throw "Skipped: Xvfb is not available"
+  endif
+  if executable("xdpyinfo") != 1
+    throw "Skipped: xdpyinfo is not available"
+  endif
+endfunc
+
+func s:StartCompositorOutput(channel, msg)
+  let l:display = matchstr(a:msg, 'WAYLAND_DISPLAY=\zs.\+')
+
+  if !empty(l:display)
+    let s:wayland_display_name = l:display
+  endif
+endfunc
+
+func s:StartCompositorExit(job, status)
+    if s:wayland_display_name == ""
+      throw "Error: Wayland compositor exited when starting up"
+    endif
+endfunc
+
+func StartWaylandCompositor()
+  let s:wayland_display_name = ""
+
+  let l:wayland_compositor_job = job_start(
+        \ ['labwc', '-c', 'NONE', '-d'], {
+        \ 'err_io': 'pipe',
+        \ 'err_cb': function('s:StartCompositorOutput'),
+        \ 'err_mode': 'nl',
+        \ 'exit_cb': function('s:StartCompositorExit'),
+        \ 'env': { 'WLR_BACKENDS': 'headless' }
+        \ })
+
+  call WaitForAssert({-> assert_equal("run",
+        \ job_status(l:wayland_compositor_job))})
+  call WaitForAssert({-> assert_match('.\+', s:wayland_display_name)})
+
+  let s:wayland_displays[s:wayland_display_name] = l:wayland_compositor_job
+
+  return s:wayland_display_name
+endfunc
+
+func EndWaylandCompositor(display)
+  let l:job = s:wayland_displays[a:display]
+
+  call job_stop(l:job, 'term')
+
+  " Block until compositor is actually gone
+  call WaitForAssert({-> assert_equal("dead", job_status(l:job))})
+
+  unlet s:wayland_displays[a:display]
+endfunc
+
+" Start a separate X11 server instance
+func StartXServer()
+  let l:xdisplay = ':' .. g:xdisplay_num
+
+  let l:x11_server_job = job_start(['Xvfb', l:xdisplay], {})
+
+  call WaitForAssert({-> assert_equal("run", job_status(l:x11_server_job))})
+  " Check if server is ready. Not sure if this is the best way though...
+  call WaitFor({-> system("DISPLAY=" .. l:xdisplay .. " xdpyinfo 2> /dev/null")
+        \ =~? '.\+'})
+
+  g:xdisplay_num += 1
+
+  let s:x11_displays[l:xdisplay] = l:x11_server_job
+
+  return l:xdisplay
+endfunc
+
+func EndXServer(display)
+  let l:job = s:x11_displays[a:display]
+
+  call job_stop(l:job)
+
+  " Block until X server is actually gone
+  call WaitForAssert({-> assert_equal("dead", job_status(l:job))})
+
+  unlet s:x11_displays[a:display]
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab