1 /* $NetBSD: selection.c,v 1.9 2005/06/02 09:49:31 lukem Exp $ */
4 * Copyright (c) 2002, 2003, 2004, 2007 The NetBSD Foundation, Inc.
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by Julio M. Merino Vidal.
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. The name authors may not be used to endorse or promote products
16 * derived from this software without specific prior written
19 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS
20 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
23 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
25 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
27 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
28 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 #include <sys/cdefs.h>
35 __RCSID("$NetBSD: selection.c,v 1.9 2005/06/02 09:49:31 lukem Exp $");
38 #include <sys/ioctl.h>
40 #include <sys/types.h>
42 #include <dev/wscons/wsconsio.h>
53 #include "pathnames.h"
56 /* ---------------------------------------------------------------------- */
59 * Public interface exported by the `selection' mode.
62 int selection_startup(struct mouse
*m
);
63 int selection_cleanup(void);
64 void selection_wsmouse_event(struct wscons_event
);
65 void selection_wscons_event(struct wscons_event
, int);
66 void selection_poll_timeout(void);
68 struct mode_bootstrap Selection_Mode
= {
72 selection_wsmouse_event
,
73 selection_wscons_event
,
74 selection_poll_timeout
77 /* ---------------------------------------------------------------------- */
80 * Structures used in this module only.
83 /* The `selarea' structure is used to describe a selection in the screen.
84 It also holds a copy of the selected text. */
86 size_t sa_x1
; /* Start column */
87 size_t sa_y1
; /* Start row */
88 size_t sa_x2
; /* End column */
89 size_t sa_y2
; /* End row */
90 size_t sa_startoff
; /* Absolute offset of start position */
91 size_t sa_endoff
; /* Absolute offset of end position */
92 size_t sa_buflen
; /* Length of selected text */
93 char *sa_buf
; /* A copy of the selected text */
96 /* The `selmouse' structure extends the `mouse' structure adding all fields
97 required for this module to work. */
99 struct mouse
*sm_mouse
; /* Pointer to parent structure */
101 int sm_ttyfd
; /* Active TTY file descriptor */
103 size_t sm_x
; /* Mouse pointer column */
104 size_t sm_y
; /* Mouse pointer row */
105 size_t sm_max_x
; /* Maximun column allowed */
106 size_t sm_max_y
; /* Maximun row allowed */
108 size_t sm_slowdown_x
; /* X axis slowdown */
109 size_t sm_slowdown_y
; /* Y axis slowdown */
110 size_t sm_count_x
; /* Number of X movements skipped */
111 size_t sm_count_y
; /* Number of Y movements skipped */
113 int sm_visible
; /* Whether pointer is visible or not */
114 int sm_selecting
; /* Whether we are selecting or not */
116 int sm_but_select
; /* Button number to select an area */
117 int sm_but_paste
; /* Button number to paste buffer */
120 /* ---------------------------------------------------------------------- */
126 static struct selmouse Selmouse
;
127 static struct selarea Selarea
;
128 static int Initialized
= 0;
130 /* ---------------------------------------------------------------------- */
133 * Prototypes for functions private to this module.
136 static void cursor_hide(void);
137 static void cursor_show(void);
138 static void open_tty(int);
139 static void char_invert(size_t, size_t);
140 static void *alloc_sel(size_t);
141 static char *fill_buf(char *, size_t, size_t, size_t);
142 static size_t row_length(size_t);
143 static void selarea_copy_text(void);
144 static void selarea_start(void);
145 static void selarea_end(void);
146 static void selarea_calculate(void);
147 static void selarea_hide(void);
148 static void selarea_show(void);
149 static void selarea_paste(void);
151 /* ---------------------------------------------------------------------- */
153 /* Mode initialization. Reads configuration, checks if the kernel has
154 * support for mouse pointer and opens required files. */
156 selection_startup(struct mouse
*m
)
159 struct wsdisplay_char ch
;
163 log_warnx("selection mode already initialized");
167 (void)memset(&Selmouse
, 0, sizeof(struct selmouse
));
168 Selmouse
.sm_mouse
= m
;
170 conf
= config_get_mode("selection");
171 Selmouse
.sm_slowdown_x
= block_get_propval_int(conf
, "slowdown_x", 0);
172 Selmouse
.sm_slowdown_y
= block_get_propval_int(conf
, "slowdown_y", 3);
173 if (block_get_propval_int(conf
, "lefthanded", 0)) {
174 Selmouse
.sm_but_select
= 2;
175 Selmouse
.sm_but_paste
= 0;
177 Selmouse
.sm_but_select
= 0;
178 Selmouse
.sm_but_paste
= 2;
181 /* Open current tty */
182 (void)ioctl(Selmouse
.sm_mouse
->m_statfd
, WSDISPLAYIO_GETACTIVESCREEN
,
184 Selmouse
.sm_ttyfd
= -1;
187 /* Check if the kernel has character functions */
189 if (ioctl(Selmouse
.sm_ttyfd
, WSDISPLAYIO_GETWSCHAR
, &ch
) < 0) {
190 (void)close(Selmouse
.sm_ttyfd
);
191 log_warn("ioctl(WSDISPLAYIO_GETWSCHAR) failed");
195 assert(Selmouse
.sm_max_y
!= 0); /* Initialized by open_tty above. */
196 assert(Selmouse
.sm_max_x
!= 0); /* Initialized by open_tty above. */
197 Selmouse
.sm_y
= Selmouse
.sm_max_y
/ 2;
198 Selmouse
.sm_x
= Selmouse
.sm_max_x
/ 2;
199 Selmouse
.sm_count_y
= 0;
200 Selmouse
.sm_count_x
= 0;
201 Selmouse
.sm_visible
= 0;
202 Selmouse
.sm_selecting
= 0;
208 /* ---------------------------------------------------------------------- */
212 selection_cleanup(void)
216 if (Selmouse
.sm_ttyfd
>= 0)
217 (void)close(Selmouse
.sm_ttyfd
);
221 /* ---------------------------------------------------------------------- */
223 /* Parse wsmouse events. Both motion and button events are handled. The
224 * former move the mouse across the screen and the later create a new
225 * selection or paste the buffer. */
227 selection_wsmouse_event(struct wscons_event evt
)
230 if (IS_MOTION_EVENT(evt
.type
)) {
231 if (Selmouse
.sm_selecting
)
236 case WSCONS_EVENT_MOUSE_DELTA_X
:
237 if (Selmouse
.sm_count_x
>= Selmouse
.sm_slowdown_x
) {
238 Selmouse
.sm_count_x
= 0;
241 else if (Selmouse
.sm_x
!= 0)
243 if (Selmouse
.sm_x
> Selmouse
.sm_max_x
)
244 Selmouse
.sm_x
= Selmouse
.sm_max_x
;
246 Selmouse
.sm_count_x
++;
249 case WSCONS_EVENT_MOUSE_DELTA_Y
:
250 if (Selmouse
.sm_count_y
>= Selmouse
.sm_slowdown_y
) {
251 Selmouse
.sm_count_y
= 0;
254 else if (Selmouse
.sm_y
!= 0)
256 if (Selmouse
.sm_y
> Selmouse
.sm_max_y
)
257 Selmouse
.sm_y
= Selmouse
.sm_max_y
;
259 Selmouse
.sm_count_y
++;
262 case WSCONS_EVENT_MOUSE_DELTA_Z
:
266 log_warnx("unknown event");
269 if (Selmouse
.sm_selecting
)
273 } else if (IS_BUTTON_EVENT(evt
.type
)) {
275 case WSCONS_EVENT_MOUSE_UP
:
276 if (evt
.value
== Selmouse
.sm_but_select
) {
283 case WSCONS_EVENT_MOUSE_DOWN
:
284 if (evt
.value
== Selmouse
.sm_but_select
) {
285 /* Start selection */
289 } else if (evt
.value
== Selmouse
.sm_but_paste
) {
290 /* Paste selection */
297 log_warnx("unknown button event");
302 /* ---------------------------------------------------------------------- */
304 /* Parse wscons status events. */
306 selection_wscons_event(struct wscons_event evt
, int preclose
)
310 case WSCONS_EVENT_SCREEN_SWITCH
:
312 if (Selmouse
.sm_selecting
)
316 if (!Selmouse
.sm_mouse
->m_disabled
)
320 if (Selmouse
.sm_selecting
)
328 /* ---------------------------------------------------------------------- */
330 /* Device polling has timed out, so we hide the mouse to avoid further
331 * console pollution. */
333 selection_poll_timeout(void)
336 if (!Selmouse
.sm_selecting
)
340 /* ---------------------------------------------------------------------- */
342 /* Hides the mouse pointer, if visible. */
347 if (Selmouse
.sm_visible
) {
348 char_invert(Selmouse
.sm_y
, Selmouse
.sm_x
);
349 Selmouse
.sm_visible
= 0;
353 /* ---------------------------------------------------------------------- */
355 /* Shows the mouse pointer, if not visible. */
360 if (!Selmouse
.sm_visible
) {
361 char_invert(Selmouse
.sm_y
, Selmouse
.sm_x
);
362 Selmouse
.sm_visible
= 1;
366 /* ---------------------------------------------------------------------- */
368 /* Opens the specified TTY device, used when switching consoles.
369 * Takes care to adjust the pointer status to make sense within the new
370 * console, whose dimensions may differ from the previous one. */
377 if (Selmouse
.sm_ttyfd
>= 0)
378 (void)close(Selmouse
.sm_ttyfd
);
380 (void)snprintf(buf
, sizeof(buf
), _PATH_TTYPREFIX
"%d", ttyno
);
381 Selmouse
.sm_ttyfd
= open(buf
, O_RDONLY
| O_NONBLOCK
);
382 if (Selmouse
.sm_ttyfd
< 0)
383 log_warnx("cannot open %s", buf
);
385 /* Get terminal size and update the maximum cursor coordinates. */
386 if (ioctl(Selmouse
.sm_ttyfd
, TIOCGWINSZ
, &ws
) < 0) {
387 log_warn("cannot get terminal size");
388 /* Some defaults; not guaranteed to be correct but we
389 * cannot do better. */
390 Selmouse
.sm_max_y
= 24;
391 Selmouse
.sm_max_x
= 79;
393 Selmouse
.sm_max_y
= ws
.ws_row
- 1;
394 Selmouse
.sm_max_x
= ws
.ws_col
- 1;
397 /* Adjust current mouse position in case the terminal's size has
399 if (Selmouse
.sm_x
> Selmouse
.sm_max_x
)
400 Selmouse
.sm_x
= Selmouse
.sm_max_x
;
401 if (Selmouse
.sm_y
> Selmouse
.sm_max_y
)
402 Selmouse
.sm_y
= Selmouse
.sm_max_y
;
405 /* ---------------------------------------------------------------------- */
407 /* Flips the background and foreground colors of the specified screen
410 char_invert(size_t row
, size_t col
)
413 struct wsdisplay_char ch
;
418 if (ioctl(Selmouse
.sm_ttyfd
, WSDISPLAYIO_GETWSCHAR
, &ch
) == -1) {
419 log_warn("ioctl(WSDISPLAYIO_GETWSCHAR) failed");
424 ch
.foreground
= ch
.background
;
427 if (ioctl(Selmouse
.sm_ttyfd
, WSDISPLAYIO_PUTWSCHAR
, &ch
) == -1)
428 log_warn("ioctl(WSDISPLAYIO_PUTWSCHAR) failed");
431 /* ---------------------------------------------------------------------- */
433 /* Allocates memory for a selection. This function is very simple but is
434 * used to get a consistent warning message. */
436 alloc_sel(size_t len
)
442 log_warn("cannot allocate memory for selection (%lu bytes)",
447 /* ---------------------------------------------------------------------- */
449 /* Copies a region of a line inside the buffer pointed by `ptr'. */
451 fill_buf(char *ptr
, size_t row
, size_t col
, size_t end
)
453 struct wsdisplay_char ch
;
456 for (ch
.col
= col
; ch
.col
< end
; ch
.col
++) {
457 if (ioctl(Selmouse
.sm_ttyfd
, WSDISPLAYIO_GETWSCHAR
,
459 log_warn("ioctl(WSDISPLAYIO_GETWSCHAR) failed");
468 /* ---------------------------------------------------------------------- */
470 /* Scans the specified line and checks its length. Characters at the end
471 * of it which match isspace() are discarded. */
473 row_length(size_t row
)
475 struct wsdisplay_char ch
;
477 ch
.col
= Selmouse
.sm_max_x
;
480 if (ioctl(Selmouse
.sm_ttyfd
, WSDISPLAYIO_GETWSCHAR
, &ch
) == -1)
481 log_warn("ioctl(WSDISPLAYIO_GETWSCHAR) failed");
483 } while (isspace((unsigned char)ch
.letter
) && ch
.col
>= 0);
487 /* ---------------------------------------------------------------------- */
489 /* Copies all the text selected to the selection buffer. Whitespace at
490 * end of lines is trimmed. */
492 selarea_copy_text(void)
497 ptr
= NULL
; /* XXXGCC -Wuninitialized */
499 if (Selarea
.sa_y1
== Selarea
.sa_y2
) {
500 /* Selection is one row */
501 l
= row_length(Selarea
.sa_y1
);
502 if (Selarea
.sa_x1
> l
)
503 /* Selection is after last character,
504 * therefore it is empty */
507 if (Selarea
.sa_x2
> l
)
510 alloc_sel(Selarea
.sa_x2
- Selarea
.sa_x1
+ 1);
514 ptr
= fill_buf(ptr
, Selarea
.sa_y1
, Selarea
.sa_x1
,
519 /* Selection is multiple rows */
521 alloc_sel(Selarea
.sa_endoff
- Selarea
.sa_startoff
+ 1);
525 /* Calculate and copy first line */
526 l
= row_length(Selarea
.sa_y1
);
527 if (Selarea
.sa_x1
< l
) {
528 ptr
= fill_buf(ptr
, Selarea
.sa_y1
, Selarea
.sa_x1
, l
);
532 /* Copy mid lines if there are any */
533 if ((Selarea
.sa_y2
- Selarea
.sa_y1
) > 1) {
534 for (r
= Selarea
.sa_y1
+ 1; r
<= Selarea
.sa_y2
- 1;
536 ptr
= fill_buf(ptr
, r
, 0, row_length(r
));
541 /* Calculate and copy end line */
542 l
= row_length(Selarea
.sa_y2
);
543 if (Selarea
.sa_x2
< l
)
545 ptr
= fill_buf(ptr
, Selarea
.sa_y2
, 0, l
);
549 if (Selarea
.sa_buf
!= NULL
) {
550 free(Selarea
.sa_buf
);
551 Selarea
.sa_buf
= NULL
;
555 Selarea
.sa_buf
= str
;
556 Selarea
.sa_buflen
= ptr
- str
;
560 /* ---------------------------------------------------------------------- */
562 /* Starts a selection. */
567 if (Selarea
.sa_buf
!= NULL
) {
568 free(Selarea
.sa_buf
);
569 Selarea
.sa_buf
= NULL
;
572 Selarea
.sa_y1
= Selmouse
.sm_y
;
573 Selarea
.sa_x1
= Selmouse
.sm_x
;
575 Selmouse
.sm_selecting
= 1;
578 /* ---------------------------------------------------------------------- */
580 /* Ends a selection. Highlighted text is copied to the buffer. */
588 /* Invert sel coordinates if needed */
589 if (Selarea
.sa_x1
> Selarea
.sa_x2
) {
591 Selarea
.sa_x2
= Selarea
.sa_x1
;
594 if (Selarea
.sa_y1
> Selarea
.sa_y2
) {
596 Selarea
.sa_y2
= Selarea
.sa_y1
;
601 Selmouse
.sm_selecting
= 0;
604 /* ---------------------------------------------------------------------- */
606 /* Calculates selection absolute positions in the screen buffer. */
608 selarea_calculate(void)
612 i
= Selmouse
.sm_max_x
+ 1;
613 Selarea
.sa_y2
= Selmouse
.sm_y
;
614 Selarea
.sa_x2
= Selmouse
.sm_x
;
615 Selarea
.sa_startoff
= Selarea
.sa_y1
* i
+ Selarea
.sa_x1
;
616 Selarea
.sa_endoff
= Selarea
.sa_y2
* i
+ Selarea
.sa_x2
;
618 if (Selarea
.sa_startoff
> Selarea
.sa_endoff
) {
619 i
= Selarea
.sa_endoff
;
620 Selarea
.sa_endoff
= Selarea
.sa_startoff
;
621 Selarea
.sa_startoff
= i
;
625 /* ---------------------------------------------------------------------- */
627 /* Hides the highlighted region, returning it to normal colors. */
633 for (i
= Selarea
.sa_startoff
; i
<= Selarea
.sa_endoff
; i
++)
637 /* ---------------------------------------------------------------------- */
639 /* Highlights the selected region. */
646 for (i
= Selarea
.sa_startoff
; i
<= Selarea
.sa_endoff
; i
++)
650 /* ---------------------------------------------------------------------- */
652 /* Pastes selected text into the active console. */
658 if (Selarea
.sa_buf
== NULL
)
660 for (i
= 0; i
< Selarea
.sa_buflen
; i
++)
661 if (ioctl(Selmouse
.sm_ttyfd
, TIOCSTI
,
662 &Selarea
.sa_buf
[i
]) == -1)
663 log_warn("ioctl(TIOCSTI)");