source: neercs/trunk/src/server.c @ 4098

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