Set window user data using separate function
[profile/ivi/weston.git] / clients / terminal.c
1 /*
2  * Copyright © 2008 Kristian Høgsberg
3  *
4  * Permission to use, copy, modify, distribute, and sell this software and its
5  * documentation for any purpose is hereby granted without fee, provided that
6  * the above copyright notice appear in all copies and that both that copyright
7  * notice and this permission notice appear in supporting documentation, and
8  * that the name of the copyright holders not be used in advertising or
9  * publicity pertaining to distribution of the software without specific,
10  * written prior permission.  The copyright holders make no representations
11  * about the suitability of this software for any purpose.  It is provided "as
12  * is" without express or implied warranty.
13  *
14  * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
15  * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
16  * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
17  * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
18  * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
19  * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
20  * OF THIS SOFTWARE.
21  */
22
23 #include <stdint.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <fcntl.h>
28 #include <unistd.h>
29 #include <math.h>
30 #include <time.h>
31 #include <pty.h>
32 #include <ctype.h>
33 #include <cairo.h>
34 #include <glib.h>
35 #include <linux/input.h>
36 #include <cairo-drm.h>
37
38 #include "wayland-util.h"
39 #include "wayland-client.h"
40 #include "wayland-glib.h"
41
42 #include "window.h"
43
44 static int option_fullscreen;
45
46 #define MOD_SHIFT       0x01
47 #define MOD_ALT         0x02
48 #define MOD_CTRL        0x04
49
50 struct terminal {
51         struct window *window;
52         struct display *display;
53         char *data;
54         int width, height, start, row, column;
55         int fd, master;
56         GIOChannel *channel;
57         uint32_t modifiers;
58         char escape[64];
59         int escape_length;
60         int state;
61         int margin;
62         int fullscreen;
63         int focused;
64         struct color_scheme *color_scheme;
65         cairo_font_extents_t extents;
66 };
67
68 static char *
69 terminal_get_row(struct terminal *terminal, int row)
70 {
71         int index;
72
73         index = (row + terminal->start) % terminal->height;
74
75         return &terminal->data[index * (terminal->width + 1)];
76 }
77
78 static void
79 terminal_resize(struct terminal *terminal, int width, int height)
80 {
81         size_t size;
82         char *data;
83         int i, l, total_rows, start;
84
85         if (terminal->width == width && terminal->height == height)
86                 return;
87
88         size = (width + 1) * height;
89         data = malloc(size);
90         memset(data, 0, size);
91         if (terminal->data) {
92                 if (width > terminal->width)
93                         l = terminal->width;
94                 else
95                         l = width;
96
97                 if (terminal->height > height) {
98                         total_rows = height;
99                         start = terminal->height - height;
100                 } else {
101                         total_rows = terminal->height;
102                         start = 0;
103                 }
104
105                 for (i = 0; i < total_rows; i++)
106                         memcpy(data + (width + 1) * i,
107                                terminal_get_row(terminal, i), l);
108
109                 free(terminal->data);
110         }
111
112         terminal->width = width;
113         terminal->height = height;
114         terminal->data = data;
115
116         if (terminal->row >= terminal->height)
117                 terminal->row = terminal->height - 1;
118         if (terminal->column >= terminal->width)
119                 terminal->column = terminal->width - 1;
120         terminal->start = 0;
121 }
122
123 struct color_scheme { struct { double r, g, b, a; } fg, bg; }
124         matrix_colors = { { 0, 0.7, 0, 1 }, { 0, 0, 0, 0.9 } },
125         jbarnes_colors = { { 1, 1, 1, 1 }, { 0, 0, 0, 1 } };
126
127 static void
128 terminal_draw_contents(struct terminal *terminal)
129 {
130         struct rectangle rectangle;
131         cairo_t *cr;
132         cairo_font_extents_t extents;
133         int i, top_margin, side_margin;
134         cairo_surface_t *surface;
135         double d;
136
137         window_get_child_rectangle(terminal->window, &rectangle);
138
139         surface = display_create_surface(terminal->display, &rectangle);
140         cr = cairo_create(surface);
141         cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
142         cairo_set_source_rgba(cr,
143                               terminal->color_scheme->bg.r,
144                               terminal->color_scheme->bg.g,
145                               terminal->color_scheme->bg.b,
146                               terminal->color_scheme->bg.a);
147         cairo_paint(cr);
148         cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
149         cairo_set_source_rgba(cr,
150                               terminal->color_scheme->fg.r,
151                               terminal->color_scheme->fg.g,
152                               terminal->color_scheme->fg.b,
153                               terminal->color_scheme->fg.a);
154
155         cairo_select_font_face (cr, "mono",
156                                 CAIRO_FONT_SLANT_NORMAL,
157                                 CAIRO_FONT_WEIGHT_NORMAL);
158         cairo_set_font_size(cr, 14);
159
160         cairo_font_extents(cr, &extents);
161         side_margin = (rectangle.width - terminal->width * extents.max_x_advance) / 2;
162         top_margin = (rectangle.height - terminal->height * extents.height) / 2;
163
164         for (i = 0; i < terminal->height; i++) {
165                 cairo_move_to(cr, side_margin,
166                               top_margin + extents.ascent + extents.height * i);
167                 cairo_show_text(cr, terminal_get_row(terminal, i));
168         }
169
170         d = terminal->focused ? 0 : 0.5;
171
172         cairo_set_line_width(cr, 1);
173         cairo_move_to(cr, side_margin + terminal->column * extents.max_x_advance + d,
174                       top_margin + terminal->row * extents.height + d);
175         cairo_rel_line_to(cr, extents.max_x_advance - 2 * d, 0);
176         cairo_rel_line_to(cr, 0, extents.height - 2 * d);
177         cairo_rel_line_to(cr, -extents.max_x_advance + 2 * d, 0);
178         cairo_close_path(cr);
179
180         if (terminal->focused)
181                 cairo_fill(cr);
182         else
183                 cairo_stroke(cr);
184
185         cairo_destroy(cr);
186
187         window_copy_surface(terminal->window,
188                             &rectangle,
189                             surface);
190
191         cairo_surface_destroy(surface);
192 }
193
194 static void
195 terminal_draw(struct terminal *terminal)
196 {
197         struct rectangle rectangle;
198         int32_t width, height;
199
200         window_get_child_rectangle(terminal->window, &rectangle);
201
202         width = (rectangle.width - 2 * terminal->margin) /
203                 (int32_t) terminal->extents.max_x_advance;
204         height = (rectangle.height - 2 * terminal->margin) /
205                 (int32_t) terminal->extents.height;
206         terminal_resize(terminal, width, height);
207
208         if (!terminal->fullscreen) {
209                 rectangle.width = terminal->width *
210                         terminal->extents.max_x_advance + 2 * terminal->margin;
211                 rectangle.height = terminal->height *
212                         terminal->extents.height + 2 * terminal->margin;
213                 window_set_child_size(terminal->window, &rectangle);
214         }
215
216         window_draw(terminal->window);
217         terminal_draw_contents(terminal);
218         window_commit(terminal->window, 0);
219 }
220
221 static void
222 redraw_handler(struct window *window, void *data)
223 {
224         struct terminal *terminal = data;
225
226         terminal_draw(terminal);
227 }
228
229 #define STATE_NORMAL 0
230 #define STATE_ESCAPE 1
231
232 static void
233 terminal_data(struct terminal *terminal, const char *data, size_t length);
234
235 static void
236 handle_escape(struct terminal *terminal)
237 {
238         char *row, *p;
239         int i, count;
240         int args[10], set[10] = { 0, };
241
242         terminal->escape[terminal->escape_length++] = '\0';
243         i = 0;
244         p = &terminal->escape[2];
245         while ((isdigit(*p) || *p == ';') && i < 10) {
246                 if (*p == ';') {
247                         p++;
248                         i++;
249                 } else {
250                         args[i] = strtol(p, &p, 10);
251                         set[i] = 1;
252                 }
253         }
254         
255         switch (*p) {
256         case 'A':
257                 count = set[0] ? args[0] : 1;
258                 if (terminal->row - count >= 0)
259                         terminal->row -= count;
260                 else
261                         terminal->row = 0;
262                 break;
263         case 'B':
264                 count = set[0] ? args[0] : 1;
265                 if (terminal->row + count < terminal->height)
266                         terminal->row += count;
267                 else
268                         terminal->row = terminal->height;
269                 break;
270         case 'C':
271                 count = set[0] ? args[0] : 1;
272                 if (terminal->column + count < terminal->width)
273                         terminal->column += count;
274                 else
275                         terminal->column = terminal->width;
276                 break;
277         case 'D':
278                 count = set[0] ? args[0] : 1;
279                 if (terminal->column - count >= 0)
280                         terminal->column -= count;
281                 else
282                         terminal->column = 0;
283                 break;
284         case 'J':
285                 row = terminal_get_row(terminal, terminal->row);
286                 memset(&row[terminal->column], 0, terminal->width - terminal->column);
287                 for (i = terminal->row + 1; i < terminal->height; i++)
288                         memset(terminal_get_row(terminal, i), 0, terminal->width);
289                 break;
290         case 'G':
291                 if (set[0])
292                         terminal->column = args[0] - 1;
293                 break;
294         case 'H':
295         case 'f':
296                 terminal->row = set[0] ? args[0] - 1 : 0;
297                 terminal->column = set[1] ? args[1] - 1 : 0;
298                 break;
299         case 'K':
300                 row = terminal_get_row(terminal, terminal->row);
301                 memset(&row[terminal->column], 0, terminal->width - terminal->column);
302                 break;
303         case 'm':
304                 /* color, blink, bold etc*/
305                 break;
306         case '?':
307                 if (strcmp(p, "?25l") == 0) {
308                         /* hide cursor */
309                 } else if (strcmp(p, "?25h") == 0) {
310                         /* show cursor */
311                 }
312                 break;
313         default:
314                 terminal_data(terminal,
315                               terminal->escape + 1,
316                               terminal->escape_length - 2);
317                 break;
318         }       
319 }
320
321 static void
322 terminal_data(struct terminal *terminal, const char *data, size_t length)
323 {
324         int i;
325         char *row;
326
327         for (i = 0; i < length; i++) {
328                 row = terminal_get_row(terminal, terminal->row);
329
330                 if (terminal->state == STATE_ESCAPE) {
331                         terminal->escape[terminal->escape_length++] = data[i];
332                         if (terminal->escape_length == 2 && data[i] != '[') {
333                                 /* Bad escape sequence. */
334                                 terminal->state = STATE_NORMAL;
335                                 goto cancel_escape;
336                         }
337
338                         if (isalpha(data[i])) {
339                                 terminal->state = STATE_NORMAL;
340                                 handle_escape(terminal);
341                         } 
342                         continue;
343                 }
344
345         cancel_escape:
346                 switch (data[i]) {
347                 case '\r':
348                         terminal->column = 0;
349                         break;
350                 case '\n':
351                         terminal->column = 0;
352                         if (terminal->row + 1 < terminal->height) {
353                                 terminal->row++;
354                         } else {
355                                 terminal->start++;
356                                 if (terminal->start == terminal->height)
357                                         terminal->start = 0;
358                                 memset(terminal_get_row(terminal, terminal->row),
359                                                         0, terminal->width);
360                         }
361
362                         break;
363                 case '\t':
364                         memset(&row[terminal->column], ' ', -terminal->column & 7);
365                         terminal->column = (terminal->column + 7) & ~7;
366                         break;
367                 case '\e':
368                         terminal->state = STATE_ESCAPE;
369                         terminal->escape[0] = '\e';
370                         terminal->escape_length = 1;
371                         break;
372                 case '\b':
373                         if (terminal->column > 0)
374                                 terminal->column--;
375                         break;
376                 case '\a':
377                         /* Bell */
378                         break;
379                 default:
380                         if (terminal->column < terminal->width)
381                                 row[terminal->column++] = data[i] < 32 ? data[i] + 64 : data[i];
382                         break;
383                 }
384         }
385
386         window_schedule_redraw(terminal->window);
387 }
388
389 static void
390 key_handler(struct window *window, uint32_t key, uint32_t unicode,
391             uint32_t state, uint32_t modifiers, void *data)
392 {
393         struct terminal *terminal = data;
394         char ch = unicode;
395
396         switch (key) {
397         case KEY_F11:
398                 if (!state)
399                         break;
400                 terminal->fullscreen ^= 1;
401                 window_set_fullscreen(window, terminal->fullscreen);
402                 window_schedule_redraw(terminal->window);
403                 break;
404         default:
405                 if (state && unicode)
406                         write(terminal->master, &ch, 1);
407                 break;
408         }
409 }
410
411 static void
412 keyboard_focus_handler(struct window *window,
413                        struct wl_input_device *device, void *data)
414 {
415         struct terminal *terminal = data;
416
417         terminal->focused = (device != NULL);
418         window_schedule_redraw(terminal->window);
419 }
420
421 static struct terminal *
422 terminal_create(struct display *display, int fullscreen)
423 {
424         struct terminal *terminal;
425         cairo_surface_t *surface;
426         cairo_t *cr;
427
428         terminal = malloc(sizeof *terminal);
429         if (terminal == NULL)
430                 return terminal;
431
432         memset(terminal, 0, sizeof *terminal);
433         terminal->fullscreen = fullscreen;
434         terminal->color_scheme = &jbarnes_colors;
435         terminal->window = window_create(display, "Wayland Terminal",
436                                          500, 100, 500, 400);
437         terminal->display = display;
438         terminal->margin = 5;
439
440         window_set_fullscreen(terminal->window, terminal->fullscreen);
441         window_set_user_data(terminal->window, terminal);
442         window_set_redraw_handler(terminal->window, redraw_handler);
443
444         window_set_key_handler(terminal->window, key_handler);
445         window_set_keyboard_focus_handler(terminal->window,
446                                           keyboard_focus_handler);
447
448         surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 0, 0);
449         cr = cairo_create(surface);
450         cairo_select_font_face (cr, "mono",
451                                 CAIRO_FONT_SLANT_NORMAL,
452                                 CAIRO_FONT_WEIGHT_NORMAL);
453         cairo_set_font_size(cr, 14);
454         cairo_font_extents(cr, &terminal->extents);
455         cairo_destroy(cr);
456         cairo_surface_destroy(surface);
457
458         terminal_draw(terminal);
459
460         return terminal;
461 }
462
463 static gboolean
464 io_handler(GIOChannel   *source,
465            GIOCondition  condition,
466            gpointer      data)
467 {
468         struct terminal *terminal = data;
469         gchar buffer[256];
470         gsize bytes_read;
471         GError *error = NULL;
472
473         g_io_channel_read_chars(source, buffer, sizeof buffer,
474                                 &bytes_read, &error);
475
476         terminal_data(terminal, buffer, bytes_read);
477
478         return TRUE;
479 }
480
481 static int
482 terminal_run(struct terminal *terminal, const char *path)
483 {
484         int master;
485         pid_t pid;
486
487         pid = forkpty(&master, NULL, NULL, NULL);
488         if (pid == 0) {
489                 setenv("TERM", "vt100", 1);
490                 if (execl(path, path, NULL)) {
491                         printf("exec failed: %m\n");
492                         exit(EXIT_FAILURE);
493                 }
494         } else if (pid < 0) {
495                 fprintf(stderr, "failed to fork and create pty (%m).\n");
496                 return -1;
497         }
498
499         terminal->master = master;
500         terminal->channel = g_io_channel_unix_new(master);
501         fcntl(master, F_SETFL, O_NONBLOCK);
502         g_io_add_watch(terminal->channel, G_IO_IN,
503                        io_handler, terminal);
504
505         return 0;
506 }
507
508 static const GOptionEntry option_entries[] = {
509         { "fullscreen", 'f', 0, G_OPTION_ARG_NONE,
510           &option_fullscreen, "Run in fullscreen mode" },
511         { NULL }
512 };
513
514 int main(int argc, char *argv[])
515 {
516         struct display *d;
517         struct terminal *terminal;
518
519         d = display_create(&argc, &argv, option_entries);
520
521         terminal = terminal_create(d, option_fullscreen);
522         if (terminal_run(terminal, "/bin/bash"))
523                 exit(EXIT_FAILURE);
524
525         display_run(d);
526
527         return 0;
528 }