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  }