github.com/jmigpin/editor@v1.6.0/driver/xdriver/copypaste/copy.go (about) 1 package copypaste 2 3 // https://tronche.com/gui/x/icccm/ 4 5 import ( 6 "bytes" 7 "encoding/binary" 8 "fmt" 9 "log" 10 11 "github.com/BurntSushi/xgb" 12 "github.com/BurntSushi/xgb/xproto" 13 "github.com/jmigpin/editor/driver/xdriver/xutil" 14 "github.com/jmigpin/editor/util/uiutil/event" 15 ) 16 17 type Copy struct { 18 conn *xgb.Conn 19 win xproto.Window 20 reply chan *xproto.SelectionNotifyEvent 21 22 // Data to transfer 23 clipboardStr string 24 primaryStr string 25 } 26 27 func NewCopy(conn *xgb.Conn, win xproto.Window) (*Copy, error) { 28 c := &Copy{conn: conn, win: win} 29 if err := xutil.LoadAtoms(conn, &CopyAtoms, false); err != nil { 30 return nil, err 31 } 32 return c, nil 33 } 34 35 //---------- 36 37 func (c *Copy) Set(i event.ClipboardIndex, str string) error { 38 switch i { 39 case event.CIPrimary: 40 c.primaryStr = str 41 return c.set(CopyAtoms.Primary) 42 case event.CIClipboard: 43 c.clipboardStr = str 44 return c.set(CopyAtoms.Clipboard) 45 } 46 panic("unhandled index") 47 } 48 func (c *Copy) set(selection xproto.Atom) error { 49 t := xproto.Timestamp(xproto.TimeCurrentTime) 50 c1 := xproto.SetSelectionOwnerChecked(c.conn, c.win, selection, t) 51 if err := c1.Check(); err != nil { 52 return err 53 } 54 55 //// ensure the owner was set 56 //c2 := xproto.GetSelectionOwner(c.conn, selection) 57 //r, err := c2.Reply() 58 //if err != nil { 59 // return err 60 //} 61 //if r.Owner != c.win { 62 // return fmt.Errorf("unable to get selection ownership") 63 //} 64 65 return nil 66 } 67 68 //---------- 69 70 // Another application is asking for the data 71 func (c *Copy) OnSelectionRequest(ev *xproto.SelectionRequestEvent) error { 72 //// DEBUG 73 //target, _ := xutil.GetAtomName(c.conn, ev.Target) 74 //sel, _ := xutil.GetAtomName(c.conn, ev.Selection) 75 //prop, _ := xutil.GetAtomName(c.conn, ev.Property) 76 //log.Printf("on selection request: %v %v %v", target, sel, prop) 77 78 switch ev.Target { 79 case CopyAtoms.String, 80 CopyAtoms.Utf8String, 81 CopyAtoms.Text, 82 CopyAtoms.TextPlain, 83 CopyAtoms.TextPlainCharsetUtf8: 84 if err := c.transferBytes(ev); err != nil { 85 return err 86 } 87 case CopyAtoms.Targets: 88 if err := c.transferTargets(ev); err != nil { 89 return err 90 } 91 default: 92 // DEBUG 93 //c.debugRequest(ev) 94 95 // try to transfer bytes anyway 96 if err := c.transferBytes(ev); err != nil { 97 return err 98 } 99 } 100 return nil 101 } 102 103 func (c *Copy) debugRequest(ev *xproto.SelectionRequestEvent) { 104 // atom name 105 name, err := xutil.GetAtomName(c.conn, ev.Target) 106 if err != nil { 107 log.Printf("cpcopy selectionrequest atom name for target: %v", err) 108 } 109 // debug 110 log.Printf("cpcopy: non-standard external request for type %v %q\n", ev.Target, name) 111 } 112 113 //---------- 114 115 func (c *Copy) transferBytes(ev *xproto.SelectionRequestEvent) error { 116 var b []byte 117 switch ev.Selection { 118 case CopyAtoms.Primary: 119 b = []byte(c.primaryStr) 120 case CopyAtoms.Clipboard: 121 b = []byte(c.clipboardStr) 122 default: 123 return fmt.Errorf("unhandled selection: %v", ev.Selection) 124 } 125 126 // change property on the requestor 127 c1 := xproto.ChangePropertyChecked( 128 c.conn, 129 xproto.PropModeReplace, 130 ev.Requestor, // requestor window 131 ev.Property, // property 132 ev.Target, 133 8, // format 134 uint32(len(b)), 135 b) 136 if err := c1.Check(); err != nil { 137 return err 138 } 139 140 // notify the server 141 sne := xproto.SelectionNotifyEvent{ 142 Requestor: ev.Requestor, 143 Selection: ev.Selection, 144 Target: ev.Target, 145 Property: ev.Property, 146 Time: ev.Time, 147 } 148 c2 := xproto.SendEventChecked( 149 c.conn, 150 false, 151 sne.Requestor, 152 xproto.EventMaskNoEvent, 153 string(sne.Bytes())) 154 return c2.Check() 155 } 156 157 //---------- 158 159 // testing: $ xclip -o -target TARGETS -selection primary 160 161 func (c *Copy) transferTargets(ev *xproto.SelectionRequestEvent) error { 162 targets := []xproto.Atom{ 163 CopyAtoms.Targets, 164 CopyAtoms.String, 165 CopyAtoms.Utf8String, 166 CopyAtoms.Text, 167 CopyAtoms.TextPlain, 168 CopyAtoms.TextPlainCharsetUtf8, 169 } 170 171 tbuf := new(bytes.Buffer) 172 for _, t := range targets { 173 binary.Write(tbuf, binary.LittleEndian, t) 174 } 175 176 // change property on the requestor 177 c1 := xproto.ChangePropertyChecked( 178 c.conn, 179 xproto.PropModeReplace, 180 ev.Requestor, // requestor window 181 ev.Property, // property 182 CopyAtoms.Atom, // (would not work in some cases with ev.Target) 183 32, // format 184 uint32(len(targets)), 185 tbuf.Bytes()) 186 if err := c1.Check(); err != nil { 187 return err 188 } 189 190 // notify the server 191 sne := xproto.SelectionNotifyEvent{ 192 Requestor: ev.Requestor, 193 Selection: ev.Selection, 194 Target: ev.Target, 195 Property: ev.Property, 196 Time: ev.Time, 197 } 198 c2 := xproto.SendEventChecked( 199 c.conn, 200 false, 201 sne.Requestor, 202 xproto.EventMaskNoEvent, 203 string(sne.Bytes())) 204 return c2.Check() 205 } 206 207 //---------- 208 209 // Another application now owns the selection. 210 func (c *Copy) OnSelectionClear(ev *xproto.SelectionClearEvent) { 211 switch ev.Selection { 212 case CopyAtoms.Primary: 213 c.primaryStr = "" 214 case CopyAtoms.Clipboard: 215 c.clipboardStr = "" 216 } 217 } 218 219 //---------- 220 221 var CopyAtoms struct { 222 Atom xproto.Atom `loadAtoms:"ATOM"` 223 Primary xproto.Atom `loadAtoms:"PRIMARY"` 224 Clipboard xproto.Atom `loadAtoms:"CLIPBOARD"` 225 Targets xproto.Atom `loadAtoms:"TARGETS"` 226 227 Utf8String xproto.Atom `loadAtoms:"UTF8_STRING"` 228 String xproto.Atom `loadAtoms:"STRING"` 229 Text xproto.Atom `loadAtoms:"TEXT"` 230 TextPlain xproto.Atom `loadAtoms:"text/plain"` 231 TextPlainCharsetUtf8 xproto.Atom `loadAtoms:"text/plain;charset=utf-8"` 232 }