source: neercs/trunk/src/server.c

Last change on this file was 4796, checked in by Pascal Terjan, 9 years ago

Do not send the updated canvas if we failed sending the UPDATE command

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 19.6 KB
Line 
1/*
2 *  neercs        console-based window manager
3 *  Copyright (c) 2006-2010 Sam Hocevar <sam@hocevar.net>
4 *                2008-2010 Jean-Yves Lamoureux <jylam@lnxscene.org>
5 *                2008-2010 Pascal Terjan <pterjan@linuxfr.org>
6 *                All Rights Reserved
7 *
8 *  This program is free software. It comes without any warranty, to
9 *  the extent permitted by applicable law. You can redistribute it
10 *  and/or modify it under the terms of the Do What The Fuck You Want
11 *  To Public License, Version 2, as published by Sam Hocevar. See
12 *  http://sam.zoy.org/wtfpl/COPYING for more details.
13 */
14
15#include "config.h"
16
17#include <stdio.h>
18#include <string.h>
19#include <stdlib.h>
20#include <unistd.h>
21#include <fcntl.h>
22#include <signal.h>
23#include <sys/ioctl.h>
24#include <sys/socket.h>
25#include <sys/types.h>
26#include <sys/wait.h>
27#include <sys/time.h>
28#include <time.h>
29#include <pwd.h>
30
31#include <errno.h>
32#include <caca.h>
33
34#include "neercs.h"
35
36static void server_init(struct screen_list *screen_list);
37static void refresh_screen(struct screen_list *screen_list, int refresh);
38static int handle_key(struct screen_list *screen_list, unsigned int c,
39                      int refresh);
40static int handle_attach(struct screen_list *screen_list, char *buf);
41
42static int send_to_client(const char *msg, int size,
43                          struct screen_list *screen_list)
44{
45    int ret;
46    if (!screen_list->comm.socket[SOCK_CLIENT])
47        connect_socket(screen_list, SOCK_CLIENT);
48    if (!screen_list->comm.socket[SOCK_CLIENT])
49        ret = -1;
50    else
51        ret = write(screen_list->comm.socket[SOCK_CLIENT], msg, size);
52    if (ret < 0 && errno != EAGAIN)
53    {
54        fprintf(stderr, "Failed to send message to client: %s\n",
55                strerror(errno));
56        if (screen_list->comm.attached)
57            detach(screen_list);
58    }
59    return ret;
60}
61
62static int set_title(struct screen_list *screen_list)
63{
64    char buf[1024];
65    int bytes;
66    char *title = NULL;
67
68    if (screen_list->comm.attached)
69    {
70        if (screen_list->pty < screen_list->count &&
71            screen_list->screen[screen_list->pty]->title)
72            title = screen_list->screen[screen_list->pty]->title;
73        else
74            title = PACKAGE_STRING;
75    }
76
77    if (screen_list->title)
78    {
79        if (!strcmp(screen_list->title, title))
80            return 0;
81        free(screen_list->title);
82    }
83
84    screen_list->title = strdup(title);
85
86    bytes = snprintf(buf, sizeof(buf) - 1, "TITLE %s", title);
87    buf[bytes] = '\0';
88    return send_to_client(buf, strlen(buf), screen_list);
89}
90
91static int set_cursor(int state, struct screen_list *screen_list)
92{
93    char buf[16];
94    int bytes;
95
96    bytes = snprintf(buf, sizeof(buf) - 1, "CURSOR %d", state);
97    buf[bytes] = '\0';
98
99    return send_to_client(buf, strlen(buf), screen_list);
100}
101
102static int request_refresh(struct screen_list *screen_list)
103{
104    int ndirty = caca_get_dirty_rect_count(screen_list->cv);
105    if (!ndirty)
106        return 0;
107    if (!screen_list->comm.socket[SOCK_CLIENT])
108        connect_socket(screen_list, SOCK_CLIENT);
109    if (screen_list->comm.socket[SOCK_CLIENT])
110    {
111        size_t bufsize, towrite;
112        ssize_t written, ret;
113        socklen_t optlen = sizeof(bufsize);
114        size_t bytes;
115        void *buf;
116        char buf2[32];
117        int x, y, i;
118
119        getsockopt(screen_list->comm.socket[SOCK_CLIENT], SOL_SOCKET,
120                   SO_SNDBUF, &bufsize, &optlen);
121        bufsize /= 2;
122        debug("bufsize=%d", bufsize);
123
124        for (i = 0; i < ndirty; i++)
125        {
126            int w, h;
127            caca_get_dirty_rect(screen_list->cv, i, &x, &y, &w, &h);
128            debug("dirty @%d,%d %dx%d [%dx%d]", x, y, w, h,
129                  caca_get_canvas_width(screen_list->cv),
130                  caca_get_canvas_height(screen_list->cv));
131            buf =
132                caca_export_area_to_memory(screen_list->cv, x, y, w, h, "caca",
133                                           &bytes);
134            debug("Requesting refresh for %d", bytes);
135            towrite = bytes;
136            written = 0;
137            sprintf(buf2, "UPDATE %10d %10d", x, y);
138            ret = send_to_client(buf2, strlen(buf2) + 1, screen_list);
139            if (ret < 29)
140            {
141                free(buf);
142                return -1;
143            }
144            /* Block to write the end of the message */
145            fcntl(screen_list->comm.socket[SOCK_CLIENT], F_SETFL, 0);
146            while (towrite > 0)
147            {
148                ssize_t n;
149                debug("Wrote %d, %d remaining", written, towrite);
150                n = send_to_client((char *)buf + written,
151                                   towrite > bufsize ? bufsize : towrite,
152                                   screen_list);
153                if (n < 0)
154                {
155                    debug("Can't refresh (%s), with %d bytes (out of %d)",
156                          strerror(errno),
157                          towrite > bufsize ? bufsize : towrite, towrite);
158                    return -1;
159                }
160                written += n;
161                towrite -= n;
162            }
163            fcntl(screen_list->comm.socket[SOCK_CLIENT], F_SETFL, O_NONBLOCK);
164            free(buf);
165        }
166        sprintf(buf2, "REFRESH %10d %10d", caca_get_cursor_x(screen_list->cv),
167                caca_get_cursor_y(screen_list->cv));
168        /* FIXME check value of r */
169        int r = send_to_client(buf2, strlen(buf2) + 1, screen_list);
170        (void)r;
171        caca_clear_dirty_rect_list(screen_list->cv);
172    }
173    return 0;
174}
175
176int detach(struct screen_list *screen_list)
177{
178    screen_list->comm.attached = 0;
179    if (screen_list->lock.lock_on_detach)
180        screen_list->lock.locked = 1;
181    if (screen_list->comm.socket[SOCK_CLIENT])
182    {
183        send_to_client("DETACH", 6, screen_list);
184        close(screen_list->comm.socket[SOCK_CLIENT]);
185        screen_list->comm.socket[SOCK_CLIENT] = 0;
186    }
187    return 0;
188}
189
190static int server_iteration(struct screen_list *screen_list)
191{
192    int i;
193    int eof = 0, refresh;
194
195    int quit = 0;
196    ssize_t n;
197    char buf[128];
198
199    /* Read program output */
200    refresh = update_screens_contents(screen_list);
201
202    /* Check if we got something from the client */
203    while (screen_list->comm.socket[SOCK_SERVER]
204           && (n =
205               read(screen_list->comm.socket[SOCK_SERVER], buf,
206                    sizeof(buf) - 1)) > 0)
207    {
208        buf[n] = 0;
209        debug("Received command %s", buf);
210        if (!strncmp("ATTACH ", buf, 7))
211        {
212            refresh |= handle_attach(screen_list, buf);
213        }
214        else if (!strncmp("QUIT", buf, 4))
215        {
216            quit = 1;
217        }
218        else if (!strncmp("DELAY ", buf, 6))
219        {
220            /* FIXME check the length before calling atoi */
221            screen_list->delay = atoi(buf + 6);
222        }
223        else if (!strncmp("RESIZE ", buf, 7))
224        {
225            caca_free_canvas(screen_list->cv);
226            /* FIXME check the length before calling atoi */
227            screen_list->cv =
228                caca_create_canvas(atoi(buf + 7), atoi(buf + 18));
229            screen_list->changed = 1;
230            refresh = 1;
231        }
232        else if (!strncmp("KEY ", buf, 4))
233        {
234            unsigned int c = atoi(buf + 4);
235            refresh |= handle_key(screen_list, c, refresh);
236        }
237        else if (!strncmp("MOUSEP ", buf, 6))
238        {
239            debug("Got mouse press '%s'\n", buf);
240            int x, y, b;
241            x = 1 + atoi(buf + 7) - screen_list->screen[screen_list->pty]->x;
242            y = 1 + atoi(buf + 18) - screen_list->screen[screen_list->pty]->y;
243            b = atoi(buf + 28);
244
245            switch (screen_list->screen[screen_list->pty]->report_mouse)
246            {
247            case MOUSE_VT200:
248            case MOUSE_VT200_HIGHLIGHT:
249            case MOUSE_BTN_EVENT:
250            case MOUSE_ANY_EVENT:
251                sprintf(buf, "\x1b[M%c%c%c", 32 + (b - 1), x + 32, y + 32);
252                debug("mousea send ESC[M %d %d %d", (b - 1), x + 32, y + 32);
253                send_ansi_sequence(screen_list, buf);
254                break;
255            case MOUSE_X10:
256                sprintf(buf, "\x1b[M%c%c%c", 32 + (b - 1), 32 + x, 32 + y);
257                debug("mousex send ESC[M %d %d %d", 32 + (b - 1), 32 + x,
258                      32 + y);
259                send_ansi_sequence(screen_list, buf);
260                break;
261            case MOUSE_NONE:
262                break;
263
264            }
265        }
266        else if (!strncmp("MOUSER ", buf, 6))
267        {
268            debug("Got mouse release '%s'\n", buf);
269            int x, y, b;
270            x = 1 + atoi(buf + 7) - screen_list->screen[screen_list->pty]->x;
271            y = 1 + atoi(buf + 18) - screen_list->screen[screen_list->pty]->y;
272            b = atoi(buf + 28);
273
274            switch (screen_list->screen[screen_list->pty]->report_mouse)
275            {
276            case MOUSE_VT200:
277            case MOUSE_VT200_HIGHLIGHT:
278            case MOUSE_BTN_EVENT:
279            case MOUSE_ANY_EVENT:
280                sprintf(buf, "\x1b[M%c%c%c", 32 + 3, x + 32, y + 32);
281                send_ansi_sequence(screen_list, buf);
282                break;
283            case MOUSE_X10:
284                sprintf(buf, "\x1b[M%c%c%c", 32 + 3, 32 + x, 32 + y);
285                send_ansi_sequence(screen_list, buf);
286                break;
287            case MOUSE_NONE:
288                break;
289            }
290        }
291
292        else if (!strncmp("MOUSEM ", buf, 6))
293        {
294            debug("Got mouse motion '%s'\n", buf);
295            int x, y, b;
296            x = 1 + atoi(buf + 7) - screen_list->screen[screen_list->pty]->x;
297            y = 1 + atoi(buf + 18) - screen_list->screen[screen_list->pty]->y;
298            b = atoi(buf + 28);
299
300            switch (screen_list->screen[screen_list->pty]->report_mouse)
301            {
302            case MOUSE_X10:    // X10 reports mouse clicks only
303                if (b)
304                {
305                    sprintf(buf, "\x1b[M%c%c%c", 32 + (b - 1), x + 32, y + 32);
306                    send_ansi_sequence(screen_list, buf);
307                }
308                break;
309            case MOUSE_VT200:
310            case MOUSE_VT200_HIGHLIGHT:
311            case MOUSE_BTN_EVENT:
312                if (b)
313                {
314                    sprintf(buf, "\x1b[M%c%c%c", 32 + (b - 1), x + 32, y + 32);
315                    send_ansi_sequence(screen_list, buf);
316                }
317            case MOUSE_ANY_EVENT:
318                sprintf(buf, "\x1b[M%c%c%c", 32 + (b - 1), x + 32, y + 32);
319                send_ansi_sequence(screen_list, buf);
320                break;
321            case MOUSE_NONE:
322                break;
323            }
324        }
325        else
326        {
327            fprintf(stderr, "Unknown command received: %s\n", buf);
328        }
329    }
330
331    /* No more screens, exit */
332    if (!screen_list->count)
333        return -1;
334
335    /* User requested to exit */
336    if (quit)
337        return -2;
338
339    /* Update each screen canvas */
340    refresh |= update_terms(screen_list);
341
342    /* Launch recurrents if any */
343    refresh |= handle_recurrents(screen_list);
344
345    /* Refresh screen */
346    refresh_screen(screen_list, refresh);
347
348    eof = 1;
349    for (i = 0; i < screen_list->count; i++)
350        if (screen_list->screen[i]->fd >= 0)
351            eof = 0;
352    if (eof)
353        return -3;
354
355    return 0;
356}
357
358static void server_main(struct screen_list *screen_list)
359{
360    screen_list->last_key_time = 0;
361    screen_list->comm.attached = 0;
362    screen_list->command = 0;
363    screen_list->was_in_bell = 0;
364    screen_list->last_refresh_time = 0;
365
366    server_init(screen_list);
367#ifdef USE_PYTHON
368    python_init(screen_list);
369#endif
370
371    for (;;)
372    {
373        if (server_iteration(screen_list))
374            break;
375    }
376
377    detach(screen_list);
378
379    free_screen_list(screen_list);
380
381#ifdef USE_PYTHON
382    python_close(screen_list);
383#endif
384
385    exit(0);
386}
387
388static void refresh_screen(struct screen_list *screen_list, int refresh)
389{
390    if (!screen_list->comm.attached)
391    {
392        /* No need to refresh Don't use the CPU too much Would be better to
393           select on terms + socket */
394        sleep(1);
395    }
396    else
397    {
398        long long unsigned int current_time = get_us();
399        long long int tdiff =
400            (current_time - screen_list->last_refresh_time) / 1000;
401
402        refresh |= screen_list->need_refresh;
403
404        if (screen_list->force_refresh)
405        {
406            wm_refresh(screen_list);
407            refresh = 1;
408        }
409
410        /* Draw lock window */
411        if (screen_list->lock.locked)
412        {
413            /* FIXME don't redraw it each iteration */
414            draw_lock(screen_list);
415            refresh = 1;
416        }
417#ifdef USE_LOCK
418        else if ((current_time - screen_list->last_key_time >=
419                  screen_list->lock.autolock_timeout))
420        {
421            screen_list->lock.locked = 1;
422            refresh = 1;
423        }
424#endif
425        else if ((current_time - screen_list->last_key_time >=
426                  screen_list->screensaver.timeout))
427        {
428            if (!screen_list->screensaver.in_screensaver)
429            {
430                screensaver_init(screen_list);
431                screen_list->screensaver.in_screensaver = 1;
432                set_cursor(0, screen_list);
433            }
434            draw_screensaver(screen_list);
435            refresh = 1;
436        }
437        else if (refresh || screen_list->was_in_bell)
438        {
439            if (tdiff >= screen_list->delay)
440            {
441                refresh_screens(screen_list);
442                set_title(screen_list);
443                refresh = 1;
444            }
445        }
446        if (refresh)
447        {
448            if (tdiff >= screen_list->delay)
449            {
450                request_refresh(screen_list);
451                screen_list->last_refresh_time = current_time;
452                screen_list->need_refresh = 0;
453            }
454            else
455            {
456                debug("Skipping refresh (%lld < %d)", tdiff,
457                      screen_list->delay);
458                screen_list->need_refresh = 1;
459            }
460        }
461    }
462}
463
464static void server_init(struct screen_list *screen_list)
465{
466    int i;
467    debug("Screen list at %p\n", screen_list);
468
469    /* Create socket and bind it */
470    create_socket(screen_list, SOCK_SERVER);
471
472    /* Connect to the client */
473    connect_socket(screen_list, SOCK_CLIENT);
474
475    screen_list->width = screen_list->height = 80;
476
477    /* Create main canvas */
478    screen_list->cv = caca_create_canvas(screen_list->width,
479                                         screen_list->height
480                                         + screen_list->modals.mini * 6
481                                         + screen_list->modals.status);
482
483    if (!screen_list->sys.to_grab && !screen_list->sys.to_start)
484    {
485        add_screen(screen_list,
486                   create_screen(screen_list->width,
487                                 screen_list->height,
488                                 screen_list->sys.default_shell));
489    }
490
491    /* Attach processes */
492    if (screen_list->sys.to_grab)
493    {
494        for (i = 0; screen_list->sys.to_grab[i]; i++)
495        {
496            add_screen(screen_list,
497                       create_screen_grab(screen_list->width,
498                                          screen_list->height,
499                                          screen_list->sys.to_grab[i]));
500        }
501        free(screen_list->sys.to_grab);
502        screen_list->sys.to_grab = NULL;
503    }
504
505    /* Launch command line processes */
506    if (screen_list->sys.to_start)
507    {
508        for (i = 0; screen_list->sys.to_start[i]; i++)
509        {
510            add_screen(screen_list,
511                       create_screen(screen_list->width,
512                                     screen_list->height,
513                                     screen_list->sys.to_start[i]));
514            free(screen_list->sys.to_start[i]);
515        }
516        free(screen_list->sys.to_start);
517        screen_list->sys.to_start = NULL;
518    }
519
520    screen_list->last_key_time = get_us();
521}
522
523static int handle_attach(struct screen_list *screen_list, char *buf)
524{
525    /* If we were attached to someone else, detach first */
526    if (screen_list->comm.attached)
527        detach(screen_list);
528    screen_list->comm.attached = 1;
529    caca_free_canvas(screen_list->cv);
530    screen_list->cv = caca_create_canvas(atoi(buf + 7), atoi(buf + 18));
531    screen_list->delay = atoi(buf + 29);
532    screen_list->changed = 1;
533    return 1;
534}
535
536static int handle_key(struct screen_list *screen_list, unsigned int c,
537                      int refresh)
538{
539    char *str = NULL;
540    int size = 0;
541
542    if (screen_list->modals.help)
543    {
544        return help_handle_key(screen_list, c);
545    }
546#ifdef USE_PYTHON
547    if (screen_list->modals.python_command)
548    {
549        return python_command_handle_key(screen_list, c);
550    }
551#endif
552
553    /* CTRL-A has been pressed before, handle this as a command, except that
554       CTRL-A a sends literal CTRL-A */
555    if (screen_list->command && (c != 'a'))
556    {
557        screen_list->command = 0;
558        refresh |= handle_command_input(screen_list, c);
559    }
560    else
561    {
562        /* Not in command mode */
563        screen_list->last_key_time = get_us();
564        set_cursor(1, screen_list);
565
566        /* Kill screensaver */
567        if (screen_list->screensaver.in_screensaver)
568        {
569            screensaver_kill(screen_list);
570            screen_list->screensaver.in_screensaver = 0;
571            screen_list->changed = 1;
572            refresh = 1;
573            return refresh;
574        }
575        /* Handle lock window */
576        if (screen_list->lock.locked)
577        {
578            refresh |= update_lock(c, screen_list);
579            screen_list->changed = 1;
580        }
581        else if (screen_list->modals.window_list)
582        {
583            refresh |= update_window_list(c, screen_list);
584            screen_list->changed = 1;
585        }
586        else
587        {
588            switch (c)
589            {
590            case 0x01:         // CACA_KEY_CTRL_A:
591                screen_list->command = 1;
592                break;
593            default:
594                /* CTRL-A a sends literal CTRL-A */
595                if (screen_list->command && (c == 'a'))
596                {
597                    c = 0x01;
598                }
599                /* Normal key, convert it if needed */
600                str = convert_input_ansi(&c, &size);
601                /* FIXME check value of r */
602                int r = write(screen_list->screen[screen_list->pty]->fd, str,
603                              size);
604                (void)r;
605                break;
606            }
607        }
608    }
609    return refresh;
610}
611
612int send_ansi_sequence(struct screen_list *screen_list, char *str)
613{
614    debug("Sending ansi '%s'\n", str);
615    return write(screen_list->screen[screen_list->pty]->fd, str, strlen(str));
616}
617
618
619int install_fds(struct screen_list *screen_list)
620{
621    int fd;
622    close(0);
623    close(1);
624    close(2);
625    fd = open("/dev/null", O_RDWR, 0);
626    if (fd < 0)
627    {
628        perror("Failed to open /dev/null");
629        return -2;
630    }
631    dup2(fd, 0);
632#ifndef DEBUG
633    dup2(fd, 1);
634    dup2(fd, 2);
635    if (fd > 2)
636        close(fd);
637#else
638    if (fd != 0)
639        close(fd);
640    screen_list->outfd =
641        open("/tmp/neercs-debug.txt", O_TRUNC | O_RDWR | O_CREAT,
642             S_IRUSR | S_IWUSR);
643    dup2(screen_list->outfd, 1);
644    dup2(screen_list->outfd, 2);
645    if (screen_list->outfd > 2)
646        close(screen_list->outfd);
647#endif
648    return 0;
649}
650
651int start_server(struct screen_list *screen_list)
652{
653    pid_t pid;
654
655    pid = fork();
656    if (pid < 0)
657    {
658        perror("Failed to create child process");
659        return -1;
660    }
661    if (pid == 0)
662    {
663        int r = install_fds(screen_list);
664        if (r)
665            return r;
666        setsid();
667
668        server_main(screen_list);
669        /* Never returns */
670    }
671
672    return 0;
673}
674
675long long get_us(void)
676{
677    struct timeval tv;
678    gettimeofday(&tv, NULL);
679    return (tv.tv_sec * (1000000) + tv.tv_usec);
680}
Note: See TracBrowser for help on using the repository browser.