github.com/jmigpin/editor@v1.6.0/driver/xdriver/copypaste/paste.go (about) 1 package copypaste 2 3 import ( 4 "fmt" 5 "log" 6 "math" 7 "strings" 8 "time" 9 10 "github.com/BurntSushi/xgb" 11 "github.com/BurntSushi/xgb/xproto" 12 "github.com/jmigpin/editor/driver/xdriver/xutil" 13 "github.com/jmigpin/editor/util/syncutil" 14 "github.com/jmigpin/editor/util/uiutil/event" 15 ) 16 17 type Paste struct { 18 conn *xgb.Conn 19 win xproto.Window 20 sw *syncutil.WaitForSet // selectionnotify 21 pw *syncutil.WaitForSet // propertynotify 22 } 23 24 func NewPaste(conn *xgb.Conn, win xproto.Window) (*Paste, error) { 25 if err := xutil.LoadAtoms(conn, &PasteAtoms, false); err != nil { 26 return nil, err 27 } 28 p := &Paste{ 29 conn: conn, 30 win: win, 31 } 32 p.sw = syncutil.NewWaitForSet() 33 p.pw = syncutil.NewWaitForSet() 34 return p, nil 35 } 36 37 //---------- 38 39 func (p *Paste) Get(index event.ClipboardIndex) (string, error) { 40 switch index { 41 case event.CIPrimary: 42 return p.request(PasteAtoms.Primary) 43 case event.CIClipboard: 44 return p.request(PasteAtoms.Clipboard) 45 default: 46 return "", fmt.Errorf("unhandled index") 47 } 48 } 49 50 //---------- 51 52 func (p *Paste) request(selection xproto.Atom) (string, error) { 53 // TODO: handle timestamps to force only one paste at a time? 54 55 p.sw.Start(1500 * time.Millisecond) 56 p.requestData(selection) 57 v, err := p.sw.WaitForSet() 58 if err != nil { 59 return "", err 60 } 61 ev := v.(*xproto.SelectionNotifyEvent) 62 63 //log.Printf("%#v", ev) 64 65 return p.extractData(ev) 66 } 67 68 func (p *Paste) requestData(selection xproto.Atom) { 69 _ = xproto.ConvertSelection( 70 p.conn, 71 p.win, 72 selection, 73 PasteAtoms.Utf8String, // target/type 74 PasteAtoms.XSelData, // property 75 xproto.TimeCurrentTime) 76 } 77 78 //---------- 79 80 func (p *Paste) OnSelectionNotify(ev *xproto.SelectionNotifyEvent) { 81 // not a a paste event 82 switch ev.Property { 83 case xproto.AtomNone, PasteAtoms.XSelData: 84 default: 85 return 86 } 87 88 err := p.sw.Set(ev) 89 if err != nil { 90 log.Print(fmt.Errorf("onselectionnotify: %w", err)) 91 } 92 } 93 94 //---------- 95 96 func (p *Paste) OnPropertyNotify(ev *xproto.PropertyNotifyEvent) { 97 // not a a paste event 98 switch ev.Atom { 99 case PasteAtoms.XSelData: // property used on requestData() 100 default: 101 return 102 } 103 104 //log.Printf("%#v", ev) 105 106 err := p.pw.Set(ev) 107 if err != nil { 108 //log.Print(errors.Wrap(err, "onpropertynotify")) 109 } 110 } 111 112 //---------- 113 114 func (p *Paste) extractData(ev *xproto.SelectionNotifyEvent) (string, error) { 115 switch ev.Property { 116 case xproto.AtomNone: 117 // nothing to paste (no owner exists) 118 return "", nil 119 case PasteAtoms.XSelData: 120 if ev.Target != PasteAtoms.Utf8String { 121 s, _ := xutil.GetAtomName(p.conn, ev.Target) 122 return "", fmt.Errorf("paste: unexpected type: %v %v", ev.Target, s) 123 } 124 return p.extractData3(ev) 125 default: 126 return "", fmt.Errorf("unhandled property: %v", ev.Property) 127 } 128 } 129 130 func (p *Paste) extractData3(ev *xproto.SelectionNotifyEvent) (string, error) { 131 w := []string{} 132 incrMode := false 133 for { 134 cookie := xproto.GetProperty( 135 p.conn, 136 true, // delete 137 ev.Requestor, 138 ev.Property, // property that contains the data 139 ev.Target, // type 140 0, // long offset 141 math.MaxUint32) // long length 142 reply, err := cookie.Reply() 143 if err != nil { 144 return "", err 145 } 146 147 if reply.Type == PasteAtoms.Utf8String { 148 str := string(reply.Value) 149 w = append(w, str) 150 151 if incrMode { 152 if reply.ValueLen == 0 { 153 xproto.DeleteProperty(p.conn, ev.Requestor, ev.Property) 154 break 155 } 156 } else { 157 break 158 } 159 } 160 161 // incr mode 162 // https://tronche.com/gui/x/icccm/sec-2.html#s-2.7.2 163 if reply.Type == PasteAtoms.Incr { 164 incrMode = true 165 xproto.DeleteProperty(p.conn, ev.Requestor, ev.Property) 166 continue 167 } 168 if incrMode { 169 err := p.waitForPropertyNewValue(ev) 170 if err != nil { 171 return "", err 172 } 173 continue 174 } 175 } 176 177 return strings.Join(w, ""), nil 178 } 179 180 func (p *Paste) waitForPropertyNewValue(ev *xproto.SelectionNotifyEvent) error { 181 for { 182 p.pw.Start(1500 * time.Millisecond) 183 v, err := p.pw.WaitForSet() 184 if err != nil { 185 return err 186 } 187 pev := v.(*xproto.PropertyNotifyEvent) 188 if pev.Atom == ev.Property && pev.State == xproto.PropertyNewValue { 189 return nil 190 } 191 } 192 } 193 194 //---------- 195 196 var PasteAtoms struct { 197 Primary xproto.Atom `loadAtoms:"PRIMARY"` 198 Clipboard xproto.Atom `loadAtoms:"CLIPBOARD"` 199 XSelData xproto.Atom `loadAtoms:"XSEL_DATA"` 200 Incr xproto.Atom `loadAtoms:"INCR"` 201 202 Utf8String xproto.Atom `loadAtoms:"UTF8_STRING"` 203 }