summaryrefslogtreecommitdiffstats
path: root/historic/selection/selection.c
blob: 058936fc5b339129a03396da64238919a948ea2d (plain) (blame)
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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
/* implement copying and pasting in Linux virtual consoles */
/* Andrew Haylett, 17th June 1993 */
/* Wed Feb 15 09:33:16 1995, faith@cs.unc.edu changed tty0 to console, since
   most systems don't have a tty0 any more. */

#include <unistd.h>
#include <stdio.h>
#include <termios.h>
#include <fcntl.h>
#include <signal.h>
#include <errno.h>
#include <sys/kd.h>
#include <sys/ioctl.h>
#include <sys/time.h>

#include "mouse.h"

extern int ms_copy_button, ms_paste_button;

static const int SCALE = 10;
static const long CLICK_INTERVAL = 250;	/* msec */
static const char *console = "/dev/console";

typedef enum { character = 0, word = 1, line = 2 } sel_mode;

static int open_console(const int mode);
static void set_sel(const int xs, const int ys, const int xe,
                    const int ye, const sel_mode mode);
static void paste(void);
static long interval(const struct timeval *t1, const struct timeval *t2);
static int check_mode(void);

int
main(int argc, char *argv[])
{
    struct ms_event ev;
    struct winsize win;
    struct timeval tv1, tv2;
    int xs, ys, xe, ye, x1, y1, fd, clicks = 0;
    sel_mode mode;
    
    fd = open_console(O_RDONLY);
    ioctl(fd, TIOCGWINSZ, &win);
    close(fd);
    if (! win.ws_col || ! win.ws_row)
    {
    	fprintf(stderr, "selection: zero screen dimension, assuming 80x25.\n");
    	win.ws_col = 80;
    	win.ws_row = 25;
    }

    ms_params(argc, argv);

    if (ms_init(win.ws_col * SCALE - 1, win.ws_row * SCALE - 1))
	exit(1);

    if (fork() > 0)
	exit(0);
    setsid();

    gettimeofday(&tv1, (struct timezone *)NULL);

restart:
    while (1)
    {
	if (check_mode())
	    goto restart;
	if (get_ms_event(&ev))
	    exit(1);
	if (ev.ev_butstate == ms_copy_button)
	{
	    ++clicks;
	    gettimeofday(&tv2, (struct timezone *)NULL);
	    xs = ev.ev_x / SCALE + 1;
	    ys = ev.ev_y / SCALE + 1;
	    if (interval(&tv1, &tv2) < CLICK_INTERVAL && clicks == 1)
	    {
	    	mode = word;
		set_sel(xs, ys, xs, ys, mode);
	    }
	    else if (interval(&tv1, &tv2) < CLICK_INTERVAL && clicks == 2)
	    {
	    	mode = line;
		set_sel(xs, ys, xs, ys, mode);
	    }
	    else
	    {
	    	mode = character;
		clicks = 0;
		do	/* wait for left button up */
		{
		    if (check_mode())
		    	goto restart;
		    if (get_ms_event(&ev))
		    	exit(1);
		} while (ev.ev_butstate);
		x1 = y1 = 0;
		do	/* track start selection until left button down */
		{
		    xs = ev.ev_x / SCALE + 1;
		    ys = ev.ev_y / SCALE + 1;
		    if (xs != x1 || ys != y1)
		    {
			set_sel(xs, ys, xs, ys, mode);
			x1 = xs; y1 = ys;
		    }
		    if (check_mode())
		    	goto restart;
		    if (get_ms_event(&ev))
		    	exit(1);
		} while (ev.ev_butstate != ms_copy_button);
	    }
	    x1 = y1 = 0;
	    gettimeofday(&tv1, (struct timezone *)NULL);
	    do	/* track end selection until left button up */
	    {
		xe = ev.ev_x / SCALE + 1;
		ye = ev.ev_y / SCALE + 1;
		if (xe != x1 || ye != y1)
		{
		    set_sel(xs, ys, xe, ye, mode);
		    x1 = xe; y1 = ye;
		}
		if (check_mode())
		    goto restart;
		if (get_ms_event(&ev))
		    exit(1);
	    } while (ev.ev_butstate == ms_copy_button);
	} else if (ev.ev_butstate == ms_paste_button)
	{	/* paste selection */
	    paste();
	    do	/* wait for right button up */
	    {
	    	if (check_mode())
	    	    goto restart;
		if (get_ms_event(&ev))
		    exit(1);
	    } while (ev.ev_butstate);
	    gettimeofday(&tv1, (struct timezone *)NULL);
	    clicks = 0;
	}
    }
}

/* We have to keep opening and closing the console because (a) /dev/tty0
   changed its behaviour at some point such that the current VC is fixed
   after the open(), rather than being re-evaluated at each write(), and (b)
   because we seem to lose our grip on /dev/tty? after someone logs in if
   this is run from /etc/rc. */

static int
open_console(const int mode)
{
    int fd;

    if ((fd = open(console, mode)) < 0)
    {
    	perror("selection: open_console()");
    	exit(1);
    }
    return fd;
}

/* mark selected text on screen. */
static void
set_sel(const int xs, const int ys,
        const int xe, const int ye, const sel_mode mode)
{
    unsigned char buf[sizeof(char) + 5 * sizeof(short)];
    unsigned short *arg = (unsigned short *)(buf + 1);
    int fd;

    buf[0] = 2;

    arg[0] = xs;
    arg[1] = ys;
    arg[2] = xe;
    arg[3] = ye;
    arg[4] = mode;

    fd = open_console(O_WRONLY);
    if (ioctl(fd, TIOCLINUX, buf) < 0)
    {
	perror("selection: ioctl(..., TIOCLINUX, ...)");
	exit(1);
    }
    close(fd);
}

/* paste contents of selection buffer into console. */
static void
paste(void)
{
    char c = 3;
    int fd;

    fd = open_console(O_WRONLY);
    if (ioctl(fd, TIOCLINUX, &c) < 0)
    {
	perror("selection: ioctl(..., TIOCLINUX, ...)");
	exit(1);
    }
    close(fd);
}

/* evaluate interval between times. */
static long
interval(const struct timeval *t1, const struct timeval *t2)
{
    return (t2->tv_sec  - t1->tv_sec)  * 1000
         + (t2->tv_usec - t1->tv_usec) / 1000;
}

/* Check whether console is in graphics mode; if so, wait until it isn't. */
static int
check_mode(void)
{
    int fd, ch = 0;
    long kd_mode;

    do
    {
	fd = open_console(O_RDONLY);
	if (ioctl(fd, KDGETMODE, &kd_mode) < 0)
	{
	    perror("selection: ioctl(..., KDGETMODE, ...)");
	    exit(1);
	}
	close(fd);
	if (kd_mode != KD_TEXT)
	{
	    ++ch;
	    sleep(2);
	}
    } while (kd_mode != KD_TEXT);
    return (ch > 0);
}