forked from stumpwm/stumpwm
-
Notifications
You must be signed in to change notification settings - Fork 0
/
selection.lisp
143 lines (126 loc) · 5.99 KB
/
selection.lisp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
;; Copyright (C) 2003-2008 Shawn Betts
;;
;; This file is part of stumpwm.
;;
;; stumpwm is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2, or (at your option)
;; any later version.
;; stumpwm is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this software; see the file COPYING. If not, see
;; <http://www.gnu.org/licenses/>.
;; Commentary:
;;
;; Handle the X selection.
;;
;; Code:
(in-package #:stumpwm)
(export '(get-x-selection
set-x-selection
*default-selections*))
(defvar *default-selections* '(:primary)
#.(format
nil "~@{~A~%~}"
"A keyword or list, one of:"
":primary or '(:primary) uses only the \"primary\" selection"
":clipboard or '(:clipboard) uses only the \"clipboard\" selection"
"Both can be specified in a list like '(:primary :clipboard). In this case,"
"set-x-selection will clobber both, and get-x-selection will default to the first item."))
(defun export-selection (selection)
(let* ((screen (current-screen))
(selwin (screen-focus-window (current-screen)))
(root (screen-root screen)))
(xlib:set-selection-owner *display* selection selwin)
(unless (xlib:window-equal (xlib:selection-owner *display* selection) selwin)
(error "Can't set selection owner"))
;; also set the cut buffer for completeness. Note that this always sets cut
;; buffer 0.
(xlib:change-property root
:cut-buffer0
(sb-ext:string-to-octets
(getf *x-selection* selection)
:external-format :utf-8)
:utf8_string 8
:mode :replace)))
(defmacro multiselect (selection &body body)
"Put the x selection into multiple selection places."
`(call-with-multiselect ,selection (lambda (,selection) ,@body)))
(defun call-with-multiselect (selection fn)
"Helper function for multiselect."
(let ((selection (if (listp selection) selection (list selection))))
(mapc fn selection)))
(defun set-x-selection (text &optional (selection *default-selections*))
"Set the X11 selection string to @var{string}."
(multiselect selection
(setf (getf *x-selection* selection) text)
(export-selection selection)))
(defun send-selection (requestor property selection target time)
(dformat 1 "send-selection ~s ~s ~s ~s ~s~%" requestor property selection target time)
(case target
;; they're requesting what targets are available
(:targets
(xlib:change-property requestor
property
(mapcar (lambda (x)
(xlib:intern-atom *display* x))
'(:targets :string :utf8_string))
:atom
32
:mode :replace))
;; send them a string
(:string
(xlib:change-property requestor property (getf *x-selection* selection)
:string 8 :mode :replace :transform #'xlib:char->card8))
(:utf8_string
(xlib:change-property requestor property (sb-ext:string-to-octets
(getf *x-selection* selection)
:external-format :utf-8)
target 8 :mode :replace))
;; we don't know how to handle anything else
(t
(setf property nil)))
(xlib:send-event requestor :selection-notify nil
:display *display*
:window requestor
:selection selection
:property property
:target target
:time time)
(xlib:display-finish-output *display*))
(defun get-x-selection (&optional timeout (selection *default-selections*))
"Return the x selection no matter what client own it."
(let ((selection (if (listp selection) (car selection) selection)))
(labels ((wait-for-selection (&rest event-slots &key display event-key &allow-other-keys)
(declare (ignore display))
(when (eq event-key :selection-notify)
(destructuring-bind (&key window property &allow-other-keys) event-slots
(if property
(utf8-to-string (xlib:get-property window property :type :utf8_string :result-type 'vector :delete-p t))
"")))))
(or (getf *x-selection* selection)
(progn
(xlib:convert-selection selection :utf8_string (screen-input-window (current-screen)) :stumpwm-selection)
;; Note: this may spend longer than timeout in this loop but it will eventually return.
(let ((time (get-internal-real-time)))
(loop for ret = (xlib:process-event *display* :handler #'wait-for-selection :timeout timeout :discard-p nil)
when (or ret
(> (/ (- time (get-internal-real-time)) internal-time-units-per-second)
timeout))
;; make sure we return a string
return (or ret ""))))))))
;;; Commands
;;; FIXME: These two commands are basically useless. See issue #673 for details.
(defcommand putsel (string) ((:rest "text: "))
"Stuff the string @var{string} into the x selection."
(set-x-selection string))
(defcommand getsel () ()
"Echo the X selection."
(message "~a" (get-x-selection)))
(defcommand copy-last-message () ()
"Copy the last message displayed into the X selection"
(when (screen-last-msg (current-screen))
(set-x-selection (uncolorify (format nil "~{~a~^~%~}" (car (screen-last-msg (current-screen))))))))