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

Last change on this file since 4059 was 4059, checked in by Jean-Yves Lamoureux, 11 years ago
  • Moved socket-related stuff to struct comm
  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 16.9 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 4059 2009-11-29 11:29: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_on_detach)
194        screen_list->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
263        {
264            fprintf(stderr, "Unknown command received: %s\n", buf);
265        }
266    }
267
268    /* No more screens, exit */
269    if (!screen_list->count)
270        return -1;
271
272    /* User requested to exit */
273    if (quit)
274        return -2;
275
276    /* Update each screen canvas */
277    refresh |= update_terms(screen_list);
278
279    /* Launch recurrents if any */
280    refresh |= handle_recurrents(screen_list);
281
282    /* Refresh screen */
283    refresh_screen(screen_list, refresh);
284
285    eof = 1;
286    for (i = 0; i < screen_list->count; i++)
287        if (screen_list->screen[i]->fd >= 0)
288            eof = 0;
289    if (eof)
290        return -3;
291
292    return 0;
293}
294
295static void server_main(struct screen_list *screen_list)
296{
297    screen_list->last_key_time = 0;
298    screen_list->comm.attached = 0;
299    screen_list->command = 0;
300    screen_list->was_in_bell = 0;
301    screen_list->last_refresh_time = 0;
302
303    server_init(screen_list);
304#ifdef USE_PYTHON
305    python_init(screen_list);
306#endif
307
308    for (;;)
309    {
310        if (server_iteration(screen_list))
311            break;
312    }
313
314    detach(screen_list);
315
316    free_screen_list(screen_list);
317
318#ifdef USE_PYTHON
319    python_close(screen_list);
320#endif
321
322    exit(0);
323}
324
325static void refresh_screen(struct screen_list *screen_list, int refresh)
326{
327    if (!screen_list->comm.attached)
328    {
329        /* No need to refresh Don't use the CPU too much Would be better to
330           select on terms + socket */
331        sleep(1);
332    }
333    else
334    {
335        long long unsigned int current_time = get_us();
336        long long int tdiff =
337            (current_time - screen_list->last_refresh_time) / 1000;
338
339        refresh |= screen_list->need_refresh;
340
341        if (screen_list->force_refresh)
342        {
343            wm_refresh(screen_list);
344            refresh = 1;
345        }
346
347        /* Draw lock window */
348        if (screen_list->locked)
349        {
350            /* FIXME don't redraw it each iteration */
351            draw_lock(screen_list);
352            refresh = 1;
353        }
354#ifdef USE_LOCK
355        else if ((current_time - screen_list->last_key_time >=
356                  screen_list->autolock_timeout))
357        {
358            screen_list->locked = 1;
359            refresh = 1;
360        }
361#endif
362        else if ((current_time - screen_list->last_key_time >=
363                  screen_list->screensaver.timeout))
364        {
365            if (!screen_list->screensaver.in_screensaver)
366            {
367                screensaver_init(screen_list);
368                screen_list->screensaver.in_screensaver = 1;
369                set_cursor(0, screen_list);
370            }
371            draw_screensaver(screen_list);
372            refresh = 1;
373        }
374        else if (refresh || screen_list->was_in_bell)
375        {
376            if (tdiff >= screen_list->delay)
377            {
378                refresh_screens(screen_list);
379                set_title(screen_list);
380                refresh = 1;
381            }
382        }
383        if (refresh)
384        {
385            if (tdiff >= screen_list->delay)
386            {
387                request_refresh(screen_list);
388                screen_list->last_refresh_time = current_time;
389                screen_list->need_refresh = 0;
390            }
391            else
392            {
393                debug("Skipping refresh (%lld < %d)", tdiff,
394                      screen_list->delay);
395                screen_list->need_refresh = 1;
396            }
397        }
398    }
399}
400
401static void server_init(struct screen_list *screen_list)
402{
403    int i;
404    debug("Screen list at %p\n", screen_list);
405
406    /* Create socket and bind it */
407    create_socket(screen_list, SOCK_SERVER);
408
409    /* Connect to the client */
410    connect_socket(screen_list, SOCK_CLIENT);
411
412    screen_list->width = screen_list->height = 80;
413
414    /* Create main canvas */
415    screen_list->cv = caca_create_canvas(screen_list->width,
416                                         screen_list->height
417                                         + screen_list->mini * 6
418                                         + screen_list->status);
419
420    if (!screen_list->to_grab && !screen_list->to_start)
421    {
422        add_screen(screen_list,
423                   create_screen(screen_list->width,
424                                 screen_list->height,
425                                 screen_list->default_shell));
426    }
427
428    /* Attach processes */
429    if (screen_list->to_grab)
430    {
431        for (i = 0; screen_list->to_grab[i]; i++)
432        {
433            add_screen(screen_list,
434                       create_screen_grab(screen_list->width,
435                                          screen_list->height,
436                                          screen_list->to_grab[i]));
437        }
438        free(screen_list->to_grab);
439        screen_list->to_grab = NULL;
440    }
441
442    /* Launch command line processes */
443    if (screen_list->to_start)
444    {
445        for (i = 0; screen_list->to_start[i]; i++)
446        {
447            add_screen(screen_list,
448                       create_screen(screen_list->width,
449                                     screen_list->height,
450                                     screen_list->to_start[i]));
451            free(screen_list->to_start[i]);
452        }
453        free(screen_list->to_start);
454        screen_list->to_start = NULL;
455    }
456
457    screen_list->last_key_time = get_us();
458}
459
460static int handle_attach(struct screen_list *screen_list, char *buf)
461{
462    /* If we were attached to someone else, detach first */
463    if (screen_list->comm.attached)
464        detach(screen_list);
465    screen_list->comm.attached = 1;
466    caca_free_canvas(screen_list->cv);
467    screen_list->cv = caca_create_canvas(atoi(buf + 7), atoi(buf + 18));
468    screen_list->delay = atoi(buf + 29);
469    screen_list->changed = 1;
470    return 1;
471}
472
473static int handle_key(struct screen_list *screen_list, unsigned int c,
474                      int refresh)
475{
476    char *str = NULL;
477    int size = 0;
478
479    if (screen_list->help)
480    {
481        return help_handle_key(screen_list, c);
482    }
483#ifdef USE_PYTHON
484    if (screen_list->python_command)
485    {
486        return python_command_handle_key(screen_list, c);
487    }
488#endif
489
490    /* CTRL-A has been pressed before, handle this as a command, except that
491       CTRL-A a sends literal CTRL-A */
492    if (screen_list->command && (c != 'a'))
493    {
494        screen_list->command = 0;
495        refresh |= handle_command_input(screen_list, c);
496    }
497    else
498    {
499        /* Not in command mode */
500        screen_list->last_key_time = get_us();
501        set_cursor(1, screen_list);
502
503        /* Kill screensaver */
504        if (screen_list->screensaver.in_screensaver)
505        {
506            screensaver_kill(screen_list);
507            screen_list->screensaver.in_screensaver = 0;
508            screen_list->changed = 1;
509            refresh = 1;
510            return refresh;
511        }
512        /* Handle lock window */
513        if (screen_list->locked)
514        {
515            refresh |= update_lock(c, screen_list);
516            screen_list->changed = 1;
517        }
518        else if (screen_list->window_list)
519        {
520            refresh |= update_window_list(c, screen_list);
521            screen_list->changed = 1;
522        }
523        else
524        {
525            switch (c)
526            {
527            case 0x01:         // CACA_KEY_CTRL_A:
528                screen_list->command = 1;
529                break;
530            default:
531                /* CTRL-A a sends literal CTRL-A */
532                if (screen_list->command && (c == 'a'))
533                {
534                    c = 0x01;
535                }
536                /* Normal key, convert it if needed */
537                str = convert_input_ansi(&c, &size);
538                /* FIXME check value of r */
539                int r = write(screen_list->screen[screen_list->pty]->fd, str,
540                              size);
541                (void)r;
542                break;
543            }
544        }
545    }
546    return refresh;
547}
548
549int send_ansi_sequence(struct screen_list *screen_list, char *str)
550{
551    debug("Sending ansi '%s'\n", str);
552    return write(screen_list->screen[screen_list->pty]->fd, str, strlen(str));
553}
554
555
556int install_fds(struct screen_list *screen_list)
557{
558    int fd;
559    close(0);
560    close(1);
561    close(2);
562    fd = open("/dev/null", O_RDWR, 0);
563    if (fd < 0)
564    {
565        perror("Failed to open /dev/null");
566        return -2;
567    }
568    dup2(fd, 0);
569#ifndef DEBUG
570    dup2(fd, 1);
571    dup2(fd, 2);
572    if (fd > 2)
573        close(fd);
574#else
575    if (fd != 0)
576        close(fd);
577    screen_list->outfd =
578        open("/tmp/neercs-debug.txt", O_TRUNC | O_RDWR | O_CREAT,
579             S_IRUSR | S_IWUSR);
580    dup2(screen_list->outfd, 1);
581    dup2(screen_list->outfd, 2);
582    if (screen_list->outfd > 2)
583        close(screen_list->outfd);
584#endif
585    return 0;
586}
587
588int start_server(struct screen_list *screen_list)
589{
590    pid_t pid;
591
592    pid = fork();
593    if (pid < 0)
594    {
595        perror("Failed to create child process");
596        return -1;
597    }
598    if (pid == 0)
599    {
600        int r = install_fds(screen_list);
601        if (r)
602            return r;
603        setsid();
604
605        server_main(screen_list);
606        /* Never returns */
607    }
608
609    return 0;
610}
611
612long long get_us(void)
613{
614    struct timeval tv;
615    gettimeofday(&tv, NULL);
616    return (tv.tv_sec * (1000000) + tv.tv_usec);
617}
Note: See TracBrowser for help on using the repository browser.