diff --git a/.travis.yml b/.travis.yml
index 5bd5ae3..23f21d8 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,20 +1,22 @@
 language: go
 
+os:
+ - linux
+ - osx
+ - windows
+
 go:
- - go1.4.3
- - go1.5.4
- - go1.6.4
- - go1.7.6
- - go1.8.7
- - go1.9.4
- - go1.10
+ - go1.13.x
+ - go1.x
+
+services:
+ - xvfb
 
 before_install:
  - export DISPLAY=:99.0
- - sh -e /etc/init.d/xvfb start
 
 script:
- - sudo apt-get install xsel
+ - if [ "$TRAVIS_OS_NAME" = "linux" ]; then sudo apt-get install xsel; fi
  - go test -v .
- - sudo apt-get install xclip
+ - if [ "$TRAVIS_OS_NAME" = "linux" ]; then sudo apt-get install xclip; fi
  - go test -v .
diff --git a/clipboard_plan9.go b/clipboard_plan9.go
new file mode 100644
index 0000000..9d2fef4
--- /dev/null
+++ b/clipboard_plan9.go
@@ -0,0 +1,42 @@
+// Copyright 2013 @atotto. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build plan9
+
+package clipboard
+
+import (
+	"os"
+	"io/ioutil"
+)
+
+func readAll() (string, error) {
+	f, err := os.Open("/dev/snarf")
+	if err != nil {
+		return "", err
+	}
+	defer f.Close()
+
+	str, err := ioutil.ReadAll(f)
+	if err != nil {
+		return "", err
+	}
+	
+	return string(str), nil
+}
+
+func writeAll(text string) error {
+	f, err := os.OpenFile("/dev/snarf", os.O_WRONLY, 0666)
+	if err != nil {
+		return err
+	}
+	defer f.Close()
+	
+	_, err = f.Write([]byte(text))
+	if err != nil {
+		return err
+	}
+	
+	return nil
+}
diff --git a/clipboard_unix.go b/clipboard_unix.go
index 8f5e23c..d9f6a56 100644
--- a/clipboard_unix.go
+++ b/clipboard_unix.go
@@ -15,14 +15,17 @@ import (
 const (
 	xsel               = "xsel"
 	xclip              = "xclip"
-	wlcopy = "wl-copy"
-	wlpaste = "wl-paste"
+	powershellExe      = "powershell.exe"
+	clipExe            = "clip.exe"
+	wlcopy             = "wl-copy"
+	wlpaste            = "wl-paste"
 	termuxClipboardGet = "termux-clipboard-get"
 	termuxClipboardSet = "termux-clipboard-set"
 )
 
 var (
 	Primary bool
+	trimDos bool
 
 	pasteCmdArgs []string
 	copyCmdArgs  []string
@@ -33,8 +36,11 @@ var (
 	xclipPasteArgs = []string{xclip, "-out", "-selection", "clipboard"}
 	xclipCopyArgs  = []string{xclip, "-in", "-selection", "clipboard"}
 
+	powershellExePasteArgs = []string{powershellExe, "Get-Clipboard"}
+	clipExeCopyArgs        = []string{clipExe}
+
 	wlpasteArgs = []string{wlpaste, "--no-newline"}
-	wlcopyArgs = []string{wlcopy}
+	wlcopyArgs  = []string{wlcopy}
 
 	termuxPasteArgs = []string{termuxClipboardGet}
 	termuxCopyArgs  = []string{termuxClipboardSet}
@@ -44,8 +50,8 @@ var (
 
 func init() {
 	if os.Getenv("WAYLAND_DISPLAY") != "" {
-		pasteCmdArgs = wlpasteArgs;
-		copyCmdArgs = wlcopyArgs;
+		pasteCmdArgs = wlpasteArgs
+		copyCmdArgs = wlcopyArgs
 
 		if _, err := exec.LookPath(wlcopy); err == nil {
 			if _, err := exec.LookPath(wlpaste); err == nil {
@@ -77,6 +83,16 @@ func init() {
 		}
 	}
 
+	pasteCmdArgs = powershellExePasteArgs
+	copyCmdArgs = clipExeCopyArgs
+	trimDos = true
+
+	if _, err := exec.LookPath(clipExe); err == nil {
+		if _, err := exec.LookPath(powershellExe); err == nil {
+			return
+		}
+	}
+
 	Unsupported = true
 }
 
@@ -103,7 +119,11 @@ func readAll() (string, error) {
 	if err != nil {
 		return "", err
 	}
-	return string(out), nil
+	result := string(out)
+	if trimDos && len(result) > 1 {
+		result = result[:len(result)-2]
+	}
+	return result, nil
 }
 
 func writeAll(text string) error {
diff --git a/clipboard_windows.go b/clipboard_windows.go
index 4b4aedb..253bb93 100644
--- a/clipboard_windows.go
+++ b/clipboard_windows.go
@@ -7,6 +7,7 @@
 package clipboard
 
 import (
+	"runtime"
 	"syscall"
 	"time"
 	"unsafe"
@@ -18,12 +19,13 @@ const (
 )
 
 var (
-	user32           = syscall.MustLoadDLL("user32")
-	openClipboard    = user32.MustFindProc("OpenClipboard")
-	closeClipboard   = user32.MustFindProc("CloseClipboard")
-	emptyClipboard   = user32.MustFindProc("EmptyClipboard")
-	getClipboardData = user32.MustFindProc("GetClipboardData")
-	setClipboardData = user32.MustFindProc("SetClipboardData")
+	user32                     = syscall.MustLoadDLL("user32")
+	isClipboardFormatAvailable = user32.MustFindProc("IsClipboardFormatAvailable")
+	openClipboard              = user32.MustFindProc("OpenClipboard")
+	closeClipboard             = user32.MustFindProc("CloseClipboard")
+	emptyClipboard             = user32.MustFindProc("EmptyClipboard")
+	getClipboardData           = user32.MustFindProc("GetClipboardData")
+	setClipboardData           = user32.MustFindProc("SetClipboardData")
 
 	kernel32     = syscall.NewLazyDLL("kernel32")
 	globalAlloc  = kernel32.NewProc("GlobalAlloc")
@@ -50,19 +52,27 @@ func waitOpenClipboard() error {
 }
 
 func readAll() (string, error) {
+	// LockOSThread ensure that the whole method will keep executing on the same thread from begin to end (it actually locks the goroutine thread attribution).
+	// Otherwise if the goroutine switch thread during execution (which is a common practice), the OpenClipboard and CloseClipboard will happen on two different threads, and it will result in a clipboard deadlock.
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+	if formatAvailable, _, err := isClipboardFormatAvailable.Call(cfUnicodetext); formatAvailable == 0 {
+		return "", err
+	}
 	err := waitOpenClipboard()
 	if err != nil {
 		return "", err
 	}
-	defer closeClipboard.Call()
 
 	h, _, err := getClipboardData.Call(cfUnicodetext)
 	if h == 0 {
+		_, _, _ = closeClipboard.Call()
 		return "", err
 	}
 
 	l, _, err := globalLock.Call(h)
 	if l == 0 {
+		_, _, _ = closeClipboard.Call()
 		return "", err
 	}
 
@@ -70,21 +80,31 @@ func readAll() (string, error) {
 
 	r, _, err := globalUnlock.Call(h)
 	if r == 0 {
+		_, _, _ = closeClipboard.Call()
 		return "", err
 	}
 
+	closed, _, err := closeClipboard.Call()
+	if closed == 0 {
+		return "", err
+	}
 	return text, nil
 }
 
 func writeAll(text string) error {
+	// LockOSThread ensure that the whole method will keep executing on the same thread from begin to end (it actually locks the goroutine thread attribution).
+	// Otherwise if the goroutine switch thread during execution (which is a common practice), the OpenClipboard and CloseClipboard will happen on two different threads, and it will result in a clipboard deadlock.
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
 	err := waitOpenClipboard()
 	if err != nil {
 		return err
 	}
-	defer closeClipboard.Call()
 
 	r, _, err := emptyClipboard.Call(0)
 	if r == 0 {
+		_, _, _ = closeClipboard.Call()
 		return err
 	}
 
@@ -94,6 +114,7 @@ func writeAll(text string) error {
 	// been allocated using the function with the GMEM_MOVEABLE flag."
 	h, _, err := globalAlloc.Call(gmemMoveable, uintptr(len(data)*int(unsafe.Sizeof(data[0]))))
 	if h == 0 {
+		_, _, _ = closeClipboard.Call()
 		return err
 	}
 	defer func() {
@@ -104,25 +125,33 @@ func writeAll(text string) error {
 
 	l, _, err := globalLock.Call(h)
 	if l == 0 {
+		_, _, _ = closeClipboard.Call()
 		return err
 	}
 
 	r, _, err = lstrcpy.Call(l, uintptr(unsafe.Pointer(&data[0])))
 	if r == 0 {
+		_, _, _ = closeClipboard.Call()
 		return err
 	}
 
 	r, _, err = globalUnlock.Call(h)
 	if r == 0 {
 		if err.(syscall.Errno) != 0 {
+			_, _, _ = closeClipboard.Call()
 			return err
 		}
 	}
 
 	r, _, err = setClipboardData.Call(cfUnicodetext, h)
 	if r == 0 {
+		_, _, _ = closeClipboard.Call()
 		return err
 	}
 	h = 0 // suppress deferred cleanup
+	closed, _, err := closeClipboard.Call()
+	if closed == 0 {
+		return err
+	}
 	return nil
 }
diff --git a/debian/changelog b/debian/changelog
index 1f12b03..10af911 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+golang-github-atotto-clipboard (0.1.4-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Mon, 14 Mar 2022 02:31:27 -0000
+
 golang-github-atotto-clipboard (0.1.2-1) unstable; urgency=medium
 
   * Team upload.