github.com/gop9/olt@v0.0.0-20200202132135-d956aad50b08/framework/clipboard/clipboard_x11.go (about) 1 // +build linux,!android freebsd 2 3 package clipboard 4 5 import ( 6 "fmt" 7 "github.com/BurntSushi/xgb" 8 "github.com/BurntSushi/xgb/xproto" 9 "os" 10 "time" 11 ) 12 13 const debugClipboardRequests = false 14 15 var X *xgb.Conn 16 var win xproto.Window 17 var clipboardText string 18 var selnotify chan bool 19 20 var clipboardAtom, primaryAtom, textAtom, targetsAtom, atomAtom xproto.Atom 21 var targetAtoms []xproto.Atom 22 var clipboardAtomCache = map[xproto.Atom]string{} 23 24 func Start() { 25 var err error 26 X, err = xgb.NewConnDisplay("") 27 if err != nil { 28 panic(err) 29 } 30 31 selnotify = make(chan bool, 1) 32 33 win, err = xproto.NewWindowId(X) 34 if err != nil { 35 panic(err) 36 } 37 38 setup := xproto.Setup(X) 39 s := setup.DefaultScreen(X) 40 err = xproto.CreateWindowChecked(X, s.RootDepth, win, s.Root, 100, 100, 1, 1, 0, xproto.WindowClassInputOutput, s.RootVisual, 0, []uint32{}).Check() 41 if err != nil { 42 panic(err) 43 } 44 45 clipboardAtom = internAtom(X, "CLIPBOARD") 46 primaryAtom = internAtom(X, "PRIMARY") 47 textAtom = internAtom(X, "UTF8_STRING") 48 targetsAtom = internAtom(X, "TARGETS") 49 atomAtom = internAtom(X, "ATOM") 50 51 targetAtoms = []xproto.Atom{targetsAtom, textAtom} 52 53 go eventLoop() 54 } 55 56 func Set(text string) { 57 clipboardText = text 58 ssoc := xproto.SetSelectionOwnerChecked(X, win, clipboardAtom, xproto.TimeCurrentTime) 59 if err := ssoc.Check(); err != nil { 60 fmt.Fprintf(os.Stderr, "Error setting clipboard: %v", err) 61 } 62 ssoc = xproto.SetSelectionOwnerChecked(X, win, primaryAtom, xproto.TimeCurrentTime) 63 if err := ssoc.Check(); err != nil { 64 fmt.Fprintf(os.Stderr, "Error setting primary selection: %v", err) 65 } 66 } 67 68 func Get() string { 69 return getSelection(clipboardAtom) 70 } 71 72 func GetPrimary() string { 73 return getSelection(primaryAtom) 74 } 75 76 func getSelection(selAtom xproto.Atom) string { 77 csc := xproto.ConvertSelectionChecked(X, win, selAtom, textAtom, selAtom, xproto.TimeCurrentTime) 78 err := csc.Check() 79 if err != nil { 80 fmt.Fprintln(os.Stderr, err) 81 return "" 82 } 83 84 select { 85 case r := <-selnotify: 86 if !r { 87 return "" 88 } 89 gpc := xproto.GetProperty(X, true, win, selAtom, textAtom, 0, 5*1024*1024) 90 gpr, err := gpc.Reply() 91 if err != nil { 92 fmt.Fprintln(os.Stderr, err) 93 return "" 94 } 95 if gpr.BytesAfter != 0 { 96 fmt.Fprintln(os.Stderr, "Clipboard too large") 97 return "" 98 } 99 return string(gpr.Value[:gpr.ValueLen]) 100 case <-time.After(1 * time.Second): 101 fmt.Fprintln(os.Stderr, "Clipboard retrieval failed, timeout") 102 return "" 103 } 104 } 105 106 func eventLoop() { 107 for { 108 e, err := X.WaitForEvent() 109 if err != nil { 110 continue 111 } 112 113 switch e := e.(type) { 114 case xproto.SelectionRequestEvent: 115 if debugClipboardRequests { 116 tgtname := lookupAtom(e.Target) 117 fmt.Fprintln(os.Stderr, "SelectionRequest", e, textAtom, tgtname, "isPrimary:", e.Selection == primaryAtom, "isClipboard:", e.Selection == clipboardAtom) 118 } 119 t := clipboardText 120 121 switch e.Target { 122 case textAtom: 123 if debugClipboardRequests { 124 fmt.Fprintln(os.Stderr, "Sending as text") 125 } 126 cpc := xproto.ChangePropertyChecked(X, xproto.PropModeReplace, e.Requestor, e.Property, textAtom, 8, uint32(len(t)), []byte(t)) 127 err := cpc.Check() 128 if err == nil { 129 sendSelectionNotify(e) 130 } else { 131 fmt.Fprintln(os.Stderr, err) 132 } 133 134 case targetsAtom: 135 if debugClipboardRequests { 136 fmt.Fprintln(os.Stderr, "Sending targets") 137 } 138 buf := make([]byte, len(targetAtoms)*4) 139 for i, atom := range targetAtoms { 140 xgb.Put32(buf[i*4:], uint32(atom)) 141 } 142 143 xproto.ChangePropertyChecked(X, xproto.PropModeReplace, e.Requestor, e.Property, atomAtom, 32, uint32(len(targetAtoms)), buf).Check() 144 if err == nil { 145 sendSelectionNotify(e) 146 } else { 147 fmt.Fprintln(os.Stderr, err) 148 } 149 150 default: 151 if debugClipboardRequests { 152 fmt.Fprintln(os.Stderr, "Skipping") 153 } 154 e.Property = 0 155 sendSelectionNotify(e) 156 } 157 158 case xproto.SelectionNotifyEvent: 159 selnotify <- (e.Property == clipboardAtom) || (e.Property == primaryAtom) 160 } 161 } 162 } 163 164 func lookupAtom(at xproto.Atom) string { 165 if s, ok := clipboardAtomCache[at]; ok { 166 return s 167 } 168 169 reply, err := xproto.GetAtomName(X, at).Reply() 170 if err != nil { 171 panic(err) 172 } 173 174 // If we're here, it means we didn't have the ATOM id cached. So cache it. 175 atomName := string(reply.Name) 176 clipboardAtomCache[at] = atomName 177 return atomName 178 } 179 180 func sendSelectionNotify(e xproto.SelectionRequestEvent) { 181 sn := xproto.SelectionNotifyEvent{ 182 Time: xproto.TimeCurrentTime, 183 Requestor: e.Requestor, 184 Selection: e.Selection, 185 Target: e.Target, 186 Property: e.Property} 187 sec := xproto.SendEventChecked(X, false, e.Requestor, 0, string(sn.Bytes())) 188 err := sec.Check() 189 if err != nil { 190 fmt.Fprintln(os.Stderr, err) 191 } 192 } 193 194 func internAtom(conn *xgb.Conn, n string) xproto.Atom { 195 iac := xproto.InternAtom(conn, true, uint16(len(n)), n) 196 iar, err := iac.Reply() 197 if err != nil { 198 panic(err) 199 } 200 return iar.Atom 201 }