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

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