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

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