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  }