5

I'm running this emacs

GNU Emacs 25.2.2 (x86_64-pc-linux-gnu) of 2017-09-22, modified by Debian

directly in a Gnome terminal by calling emacs, which is actually a link to /etc/alternatives/emacs. This is on an Ubuntu 18.04 Linux.

Emacs copy function, C-c in cua mode, doesn't copy into the clipboard. I followed this advice and installed this package

M-x package-install RET xclip RET

and it still didn't work. Now I have added a simple function to my .emacs file :

(defun copy-region-to-clipboard ()
  "Copies region to X-clipboard invoking `xclip' in shell"
  (interactive)
  (shell-command-on-region
   (region-beginning)
   (region-end)
   "xclip -i -selection clipboard"))

(global-set-key (kbd "<f1>") 'copy-region-to-clipboard)

which does work. But I have to press F1 and Emacs won't return control of the cursor until cancelling with C-g. After cancelling with C-g, the text disappears from the clipboard.

So the order of operation is

Select-region 
    -> F1 
        ->  Move focus to other window and paste 
            -> Move focus back to Emacs and C-g before continuing

I wonder if there is a tweak to this program that will allow xclip to fork asynchronously with necessary input data and return control to Emacs? And then somehow chain this new function async-copy-region-to-clipboard onto the end of the C-c functionality?


  • Edit: Adding & to end of command: xclip -i -selection clipboard &, does not change behavior - Emacs still waits.

  • Edit With the help of the excellent selected answer I tried using xsel instead of xclip and found that xsel doesn't block like xclip does. This is long known behavior of xclip. Also discussed here.

xclip could also be used for this purpose. Unlike xsel, it works better when printing a raw bitstream that does not fit the current locale. Nevertheless, it is neater to use xsel because xclip does not close STDOUT after it has read from the tmux buffer. As such, tmux does not know that the copy task has completed, and continues to wait for xclip to terminate, thereby rendering tmux unresponsive. A workaround is to redirect {{ic|STDOUT} to /dev/null:

However, I found that neither of these worked for the Emacs solution, and Emacs still hung:

xclip -i -selection clipboard > /dev/null
xclip -i -selection clipboard > /dev/null &

My final working code is:

;; .emacs
...
(defun copy-region-to-clipboard ()
  "Copies region to X-clipboard invoking `xsel' in shell"
  (interactive)
  (if (and(use-region-p) (not (display-graphic-p)))
      (shell-command-on-region
       (region-beginning)
       (region-end)
       "xsel -i -b"
       )))

(global-set-key (kbd "<f1>") 'copy-region-to-clipboard)

(defun workaround-copy-region-as-kill ()
  "Run `some-command' and `some-other-command' in sequence."
  (interactive)
  (when (and(use-region-p) (not (display-graphic-p)))
    (copy-region-to-clipboard)
    (copy-region-as-kill 0 0 t)))

(defun workaround-kill-region  ()
  "Run `some-command' and `some-other-command' in sequence."
  (interactive)
  (when (and(use-region-p) (not (display-graphic-p)))
    (copy-region-to-clipboard)
    (kill-region 0 0 t)))

;; Note: 'workaround-copy-region-as-kill' is REPLACING 'copy-region-as-kill'
;; in cua--cua-keys-keymap
;; so we call 'copy-region-as-kill' from the end of it. 
(define-key cua--cua-keys-keymap (kbd "C-c <timeout>") 'workaround-copy-region-as-kill)
(define-key cua--cua-keys-keymap (kbd "C-k <timeout>") 'workaround-kill-region)

;;(global-set-key (kbd "C-c") 'workaround-copy-region-as-kill) FAIL!

The technique of mapping C-c <timeout> was provided by this answer to my follow-on question.

3
  • Why do you use the terminal version of emacs?
    – Tobias
    Commented Mar 7, 2019 at 10:29
  • It starts up and it exits instantly. Overall, I'm using less and faster keystrokes going between the terminal emacs (C-x C-z, fg) than I was using terminal mode inside emacs - and experiencing less emacs buffer bloat. ----- Also, the same emacs usage pattern carries over to working on a remote through an ssh connection inside a terminal. I was previously using tramp for that, but there were problems with that: time, switching between sudo and non-sudo mode, confusion between local and remote file paths. Commented Mar 7, 2019 at 17:33
  • @Tobias Although it's true clipping on (windowless) remote isn't going to work with the method described above. Instead mouse drag would be required. This is a point of concession to your viewpoint. Commented Mar 7, 2019 at 17:48

1 Answer 1

5

I'm also a loyal emacs-nw user, and have struggled with this over the recent emacs versions. What works for me, and without any need to C-g, follows. Note that it has a safety check that is linux-specific, so in a linux environment, if there is no X-display associated, it will try to operate on the lowest-numbered available X-display instead of silently crashing. Also, it uses the linux program xsel instead of xclip:

  (defun my-copy-to-xclipboard(arg)
    (interactive "P")
    (cond
      ((not (use-region-p))
       (error "Nothing to yank to X-clipboard"))
      ((not (display-graphic-p))
       (let ((x-display
               (or (getenv "DISPLAY")
                   (when (getenv "TMUX")
                     (shell-command-to-string
                       "tmux_display=$(ps e $(pgrep -f \"tmux attach\") |\
                        grep -o \"DISPLAY=[^ ]*\") && printf ${tmux_display##*=}"))
                   (shell-command-to-string
                 "cd /tmp/.X11-unix && for x in X*; do printf \":${x#X}\"; break; done")))
             exit-code)
         (if (not (string-match ":" x-display))
           (error "No X-display found.")
          (cond
           ((zerop (setq exit-code
                     (shell-command-on-region (region-beginning) (region-end)
                       (format "xsel --display %s -i -b" x-display)))))
           ((= 127 exit-code)
            (error "Is program `xsel' installed?"))
           (*
            (error "xsel exited with code %s" exit-code))))))
      ((display-graphic-p)
         (condition-case err
           (progn
             (call-interactively 'clipboard-kill-ring-save))
           (error "Clipboard-failure."))))
    (message "Region yanked to X-clipboard")
    (when arg
      (kill-region  (region-beginning) (region-end)))
    (deactivate-mark))

  (defun my-cut-to-xclipboard()
    (interactive)
    (my-copy-to-xclipboard t))

  (defun my-paste-from-xclipboard()
    "Uses shell command `xsel -o' to paste from x-clipboard. With
  one prefix arg, pastes from X-PRIMARY, and with two prefix args,
  pastes from X-SECONDARY."
    (interactive)
    (if (display-graphic-p)
      (clipboard-yank)
     (let*
       ((opt (prefix-numeric-value current-prefix-arg))
        (opt (cond
         ((=  1 opt) "b")
         ((=  4 opt) "p")
         ((= 16 opt) "s"))))
      (insert (shell-command-to-string (concat "xsel -o -" opt))))))

  (global-set-key (kbd "C-c C-w") 'my-cut-to-xclipboard)
  (global-set-key (kbd "C-c M-w") 'my-copy-to-xclipboard)
  (global-set-key (kbd "C-c C-y") 'my-paste-from-xclipboard)

EDIT #1: Minor change to cond clause. Changed t (when (display-graphic-p)..) to just((display-graphic-p)`

EDIT #2: Better checking of $DISPLAY, including handling cases of running emacsclient within tmux.

EDIT #3: Improve checking of $DISPLAY to handle ssh cases (thanks: comment from @ishmael)

9
  • Very helpful, thanks. Using xsel turned out to be the key, and there is a story behind that. Commented Mar 7, 2019 at 21:29
  • I am now looking at the /tmp directory of a remote Ubuntu 16.04 sever not running X, and there is nothing for /tmp/X*. This part of your answer I don't understand. Commented Mar 7, 2019 at 21:36
  • @CraigHicks: That's the point. Those /tmp files should exist If and only if an X-server is running. Thus, if your emacs is -nw, ie. `(not display-graphic-p), but there is an X-server, and thus an X-clipboard, find the x-display id and send the data to that display's x-clipboard. This may mess up in cases of multiple users accounts operating multiple workstations ("seats" in X11 terminology) on the host machine; maybe it needs to additionally test for matching UID's for the x-display Commented Mar 7, 2019 at 21:56
  • Interestingly, on my desktop Ubuntu 18.04 linux system, there is also nothing corresponding to /tmp/X*. I see processes: /usr/bin/Xwayland and /usr/lib/xorg/Xorg running. Commented Mar 7, 2019 at 22:08
  • @CraigHicks: Don't know what to tell you. That would be a separate question for someone else to answer. Commented Mar 7, 2019 at 22:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.