wmshutdown: Include xpm icon in source rather than installing into a data directory...
[dockapps.git] / wmget / server.c
blobf71ce8ce4854978b8a41742209139175ec745381
1 /*
2 wmget - A background download manager as a Window Maker dock app
3 Copyright (c) 2001-2003 Aaron Trickey <aaron@amtrickey.net>
5 Permission is hereby granted, free of charge, to any person
6 obtaining a copy of this software and associated documentation files
7 (the "Software"), to deal in the Software without restriction,
8 including without limitation the rights to use, copy, modify, merge,
9 publish, distribute, sublicense, and/or sell copies of the Software,
10 and to permit persons to whom the Software is furnished to do so,
11 subject to the following conditions:
13 The above copyright notice and this permission notice shall be
14 included in all copies or substantial portions of the Software.
16 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
20 CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21 TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22 SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 ********************************************************************
25 server.c - Manage the dock app display, accept & spawn jobs
27 When ``wmget --dock'' is invoked, main() calls server(), defined
28 below. This initializes the dock app window, the shared memory
29 segment, and the job input queue, and then enters the main loop
30 whereby it accepts X events (such as redraws or clicks), accepts
31 job requests (from request(), in request.c), and monitors shared
32 memory, updating the display as necessary. It forks off children to
33 handle accepted jobs; see retrieve.c.
36 #include <stdlib.h>
37 #include <stdio.h>
38 #include <string.h>
39 #include <stdarg.h>
40 #include <unistd.h>
41 #include <fcntl.h>
42 #include <errno.h>
43 #include <signal.h>
44 #include <time.h>
45 #include <sys/shm.h>
46 #include <sys/types.h>
47 #include <sys/stat.h>
50 #include <X11/Xlib.h>
51 #include <X11/xpm.h>
52 #include <X11/extensions/shape.h>
54 #include "wmget.h"
55 #include "wmget.xpm"
56 #include "dockapp/dockapp.h"
59 static ServerConfig config;
61 /***********************************************************************
62 * Text Drawing
63 * The various CHAR_* consts locate and dimension the chars on the xpm.
64 * Call init_font() to set up the CHAR_X and CHAR_Y tables, then
65 * draw_string() to put text on the xpm.
67 static const int CHAR_WIDTH = 6;
68 static const int CHAR_HEIGHT = 7;
70 static const int CHAR_UCALPHA_X = 1;
71 static const int CHAR_UCALPHA_Y = 85;
72 static const int CHAR_LCALPHA_X = 1;
73 static const int CHAR_LCALPHA_Y = 95;
74 static const int CHAR_SYMNUM_X = 1;
75 static const int CHAR_SYMNUM_Y = 105;
77 static int CHAR_X[128];
78 static int CHAR_Y[128];
80 static void init_font (void)
82 int i;
83 int *cx, *cy;
85 for (i = 0, cx = CHAR_X, cy = CHAR_Y; i < 128; ++i, ++cx, ++cy) {
86 if (i > 'z') {
87 *cx = CHAR_SYMNUM_X; /* 1st SYMNUM is the space */
88 *cy = CHAR_SYMNUM_Y;
89 } else if (i >= 'a') {
90 *cx = CHAR_LCALPHA_X + CHAR_WIDTH * (i - 'a');
91 *cy = CHAR_LCALPHA_Y;
92 } else if (i > 'Z') {
93 *cx = CHAR_SYMNUM_X;
94 *cy = CHAR_SYMNUM_Y;
95 } else if (i >= 'A') {
96 *cx = CHAR_UCALPHA_X + CHAR_WIDTH * (i - 'A');
97 *cy = CHAR_UCALPHA_Y;
98 } else if (i > '9') {
99 *cx = CHAR_SYMNUM_X;
100 *cy = CHAR_SYMNUM_Y;
101 } else if (i >= ' ') {
102 *cx = CHAR_SYMNUM_X + CHAR_WIDTH * (i - ' ');
103 *cy = CHAR_SYMNUM_Y;
104 } else {
105 *cx = CHAR_SYMNUM_X;
106 *cy = CHAR_SYMNUM_Y;
111 static void draw_string (const char *str, int x, int y)
113 for ( ; *str; ++str) {
114 dockapp_overlay_pixmap (
115 CHAR_X[(int)*str], CHAR_Y[(int)*str],
116 x, y,
117 CHAR_WIDTH, CHAR_HEIGHT);
118 x += CHAR_WIDTH;
123 /***********************************************************************
124 * Button Widgets
127 static const int BTN_PAUSE_X = 128;
128 static const int BTN_STOP_X = 147;
129 static const int BTN_Y = 37;
130 static const int BTN_WIDTH = 19;
131 static const int BTN_HEIGHT = 9;
134 /***********************************************************************
135 * Progress Bars
138 /* Coords and dimensions refer to the pbars, excluding the borders
139 * which make up the ``ditches''
141 static const int PBAR_Y[4] = {
148 static const int PBAR_X = 3;
150 /* These are the graphics for the bars themselves.
152 static const int PBAR_FULL_X = 67;
153 static const int PBAR_FULL_Y = 37;
155 static const int PBAR_EMPTY_X = 67;
156 static const int PBAR_EMPTY_Y = 47;
158 static const int PBAR_LENGTH = 58;
159 static const int PBAR_HEIGHT = 9;
161 static int bar_selected = -1;
163 static void draw_pbar (int trough_x, int trough_y, int value, int max)
165 int width = ((unsigned long) PBAR_LENGTH * value) / max;
167 dockapp_copy_pixmap (
168 PBAR_FULL_X, PBAR_FULL_Y,
169 trough_x, trough_y,
170 width, PBAR_HEIGHT);
172 dockapp_copy_pixmap (
173 PBAR_EMPTY_X, PBAR_EMPTY_Y,
174 trough_x + width, trough_y,
175 PBAR_LENGTH - width, PBAR_HEIGHT);
178 static const char *const DEFAULT_TEXT[] = {
179 " wmget",
185 static void draw_pbars (void)
187 int i;
189 for (i = 0; i < 4; ++i) {
190 Job *j = &shmem->jobs[i];
192 if (j->status == J_EMPTY) {
193 draw_pbar (PBAR_X, PBAR_Y[i], 0, 1);
194 draw_string (DEFAULT_TEXT[i], PBAR_X + 1, PBAR_Y[i] + 1);
195 continue;
198 if (i == bar_selected) {
199 /* percentage (or error) + stop button */
201 draw_pbar (PBAR_X, PBAR_Y[i], 0, 1);
203 if (j->status != J_FAILED) {
204 char pct[4];
205 sprintf (pct, "%02lu%%",
206 100L * j->progress / j->prog_max);
208 draw_string (pct, PBAR_X + 1, PBAR_Y[i] + 1);
209 } else {
210 char err[9];
211 strncpy (err, j->error, 8);
212 err[8] = '\0';
213 draw_string (err, PBAR_X + 1, PBAR_Y[i] + 1);
216 dockapp_copy_pixmap (
217 BTN_STOP_X, BTN_Y,
218 PBAR_X + PBAR_LENGTH - BTN_WIDTH, PBAR_Y[i],
219 BTN_WIDTH, BTN_HEIGHT);
220 } else {
221 /* name + scrollbar, or error */
222 draw_pbar (PBAR_X, PBAR_Y[i], j->progress, j->prog_max);
223 draw_string (j->options.display, PBAR_X + 1, PBAR_Y[i] + 1);
228 /***********************************************************************
229 * Shared memory segment: global pointer, constructor
231 Shmem *shmem = 0;
233 static int init_shmem (void)
235 int shmid;
236 int i;
238 if ((shmid = shmget (IPC_PRIVATE, sizeof *shmem, SHM_R | SHM_W)) < 0) {
239 error_sys ("could not allocate shared memory segment [shmget()]");
240 return 1;
243 if ((shmem = shmat (shmid, 0, 0)) == (void *) -1) {
244 error_sys ("could not attach shared memory segment [shmat()]");
245 return 1;
248 for (i = 0; i < 4; ++i) {
249 shmem->jobs[i].status = J_EMPTY;
250 shmem->jobs[i].options.display[0] = 0;
251 shmem->jobs[i].progress = 0;
252 shmem->jobs[i].prog_max = 0;
255 return 0;
258 /***********************************************************************
259 * start_job(): Spawn a new process and call retrieve() in there.
260 * Note that `j' must be in shared memory.
262 static int start_job (Job *j)
264 int f;
266 j->prog_max = 1;
267 j->progress = 0;
268 j->status = J_INIT;
269 j->stop_request = 0;
271 f = fork ();
272 if (f < 0) {
273 error_sys ("could not create child process [fork()]");
274 return 1;
275 } else if (f == 0) { /* child */
276 retrieve (j);
278 if (j->status == J_FAILED) {
279 /* Sleep until user acks the error.
281 while (!j->stop_request) {
282 struct timespec sleeptime = { 0, 100000000L };
283 nanosleep (&sleeptime, NULL);
287 j->status = J_EMPTY;
288 exit (0);
291 return 0;
294 /***********************************************************************
295 * The Job Queue. Okay, this is a little cheesy right now.
297 static Job *job_queue[MAX_QUEUED_JOBS] = { 0 };
298 static size_t job_queue_depth = 0;
299 static job_id_t next_job_id = 1; /* Job id 0 is never valid */
302 /***********************************************************************
303 * process_queue(): If a job has finished, pull it from its slot.
304 * If a slot is open, pull the next job from the queue.
306 static int process_queue (void)
308 size_t i;
310 for (i = 0; i < MAX_ACTIVE_JOBS; ++i) {
311 switch (shmem->jobs[i].status) {
312 default: /* job occupying slot */
313 continue;
315 case J_EMPTY:
316 /* aha. see if there is anything queued up */
317 if (!job_queue_depth)
318 continue;
320 shmem->jobs[i] = *job_queue[--job_queue_depth];
322 free (job_queue[job_queue_depth]);
324 debug ("Pulled new active job %lu off queue",
325 shmem->jobs[i].job_id);
327 if (start_job (&shmem->jobs[i]))
328 return 1;
330 continue;
334 return 0;
337 /***********************************************************************
338 * cancel_job(): Cancel a job. If it's running, stop it; if it's
339 * in the queue, dequeue it; if it's nowhere, do nothing.
341 static int cancel_job (job_id_t job_id)
343 Job *j;
344 Job **jp;
346 /* First search the active jobs. */
347 for (j = shmem->jobs; j < shmem->jobs + MAX_ACTIVE_JOBS; ++j) {
348 if (j->job_id == job_id) {
349 switch (j->status) {
350 case J_EMPTY:
351 case J_STOPPING:
352 case J_COMPLETE:
353 /* Job has already completed. */
354 return 0;
355 case J_FAILED:
356 /* Job has already failed; this simply clears it
357 * out. */
358 j->status = J_COMPLETE;
359 return 0;
360 default:
361 /* just to keep the compiler warnings at bay */
362 break;
365 ++j->stop_request; /* Request job termination. */
367 return 0;
371 /* Okay, now search the pending queue. */
372 for (jp = job_queue; jp < job_queue + job_queue_depth; ++jp) {
373 if (*jp && (*jp)->job_id == job_id) {
374 /* Simply delete it from the queue. */
375 --job_queue_depth;
376 memmove (jp, jp + 1,
377 ((job_queue + job_queue_depth) - jp) * sizeof (Job *));
379 return 0;
383 /* Job not found. This is not an error, as we assume that the
384 * job has already died off.
386 return 0;
389 /***********************************************************************
390 * client_*(): Issue a response back to the client.
392 static void client_error (FILE *fp, const char *fmt, ...)
394 va_list ap;
395 va_start (ap, fmt);
397 fprintf (fp, RESPONSE_ERROR " ");
398 vfprintf (fp, fmt, ap);
399 va_end (ap);
402 static void client_job_accepted (FILE *fp, job_id_t job_id)
404 fprintf (fp, RESPONSE_JOB_ACCEPTED " %lu\n", job_id);
407 static void client_job_canceled (FILE *fp, job_id_t job_id)
409 fprintf (fp, RESPONSE_JOB_CANCELED " %lu\n", job_id);
412 static void client_list_header (FILE *fp)
414 debug ("client_list_header()");
416 fprintf (fp, RESPONSE_LIST_COMING "\n");
419 static void client_list_job (FILE *fp, Job *j)
421 const char *status;
423 switch (j->status) {
424 default:
425 status = "UNKNOWN: Internal Error!";
426 break;
428 case J_INIT:
429 status = "INIT: Waiting to start";
430 break;
432 case J_RUNNING:
433 status = "RUNNING: Currently retrieving";
434 break;
436 case J_PAUSED:
437 status = "PAUSED: Download suspended";
438 break;
440 case J_STOPPING:
441 status = "STOPPING: Got stop request";
442 break;
444 case J_COMPLETE:
445 status = "COMPLETE: Download complete";
446 break;
448 case J_FAILED:
449 status = j->error;
450 break;
453 fprintf (fp, "Job %lu [%9s]: %lu/%lu %s\n%s => %s\n\n",
454 j->job_id, j->options.display, j->progress, j->prog_max,
455 status, j->source_url, j->options.save_to);
459 static int insert_job (Job *j)
461 if (job_queue_depth >= MAX_QUEUED_JOBS) {
462 error ("Job queue full");
463 free (j);
464 return 1;
467 j->job_id = next_job_id++;
469 debug ("Accepted job...");
470 debug_dump_job (j);
472 job_queue[job_queue_depth++] = j;
474 return 0;
478 static Job *init_job (Request *req, FILE *errfp)
480 struct stat st;
481 const char *base_first;
482 const char *base_last;
483 size_t base_sz;
485 Job *j = malloc (sizeof (Job));
486 if (!j) {
487 client_error (errfp, "Dockapp out of memory!");
488 return 0;
491 STRCPY_TO_ARRAY (j->source_url, req->source_url);
493 j->status = J_INIT;
494 j->progress = j->prog_max = 0;
495 j->stop_request = 0;
496 j->options = config.job_defaults;
498 /* Copy over any applicable options---except save_to and display,
499 * which merit special consideration below.
501 if (req->overwrite != -1)
502 j->options.overwrite = req->overwrite;
504 if (req->continue_from != -1)
505 j->options.continue_from = req->continue_from;
507 if (req->proxy)
508 STRCPY_TO_ARRAY (j->options.proxy, req->proxy);
510 if (req->follow != -1)
511 j->options.follow = req->follow;
513 if (req->user_agent)
514 STRCPY_TO_ARRAY (j->options.user_agent, req->user_agent);
516 if (req->use_ascii != -1)
517 j->options.use_ascii = req->use_ascii;
519 if (req->referer)
520 STRCPY_TO_ARRAY (j->options.referer, req->referer);
522 if (req->include != -1)
523 j->options.include = req->include;
525 if (req->interface)
526 STRCPY_TO_ARRAY (j->options.interface, req->interface);
528 if (req->proxy_auth)
529 STRCPY_TO_ARRAY (j->options.proxy_auth, req->proxy_auth);
531 if (req->auth)
532 STRCPY_TO_ARRAY (j->options.auth, req->auth);
534 /* Extract the "base name" (last slash-delimited component) of the
535 * source URL for future use.
537 base_last = j->source_url + strlen (j->source_url) - 1;
538 while (*base_last == '/' && base_last > j->source_url)
539 --base_last;
540 base_first = base_last;
541 while (*base_first != '/' && base_first > j->source_url)
542 --base_first;
543 base_sz = base_last - base_first;
544 ++base_first; /* get it past that initial slash */
546 if (base_sz == 0) {
547 /* Uh, oh... invalid source_url anyway... give up. */
548 client_error (errfp, "Invalid URL '%s'", j->source_url);
549 goto RETURN_NULL;
552 debug ("baselen %d", base_sz);
554 /* If no display-name was provided, use the basename.
556 if (req->display) {
557 STRCPY_TO_ARRAY (j->options.display, req->display);
558 } else {
559 size_t n = base_sz;
560 if (n > sizeof j->options.display - 1)
561 n = sizeof j->options.display - 1;
562 strncpy (j->options.display, base_first, n);
563 j->options.display[n] = '\0';
564 debug ("display was empty... set it to %s", j->options.display);
568 /* If there was a save-to location provided, copy it into the job.
569 * If it's a relative path, make it relative to the download
570 * directory. If it wasn't given, just copy the download dir.
572 if (req->save_to) {
573 if (req->save_to[0] == '/') {
574 debug ("Reqest contained absolute dir.");
575 } else {
576 STRCPY_TO_ARRAY (j->options.save_to,
577 config.job_defaults.save_to);
578 if (strlen (j->options.save_to) + strlen (req->save_to) + 2
579 > MAXPATHLEN) {
580 client_error (errfp,
581 "Download output pathname too long");
582 goto RETURN_NULL;
584 strcat (j->options.save_to, "/");
585 strcat (j->options.save_to, req->save_to);
587 debug ("Resolved output to '%s'", j->options.save_to);
589 } else {
590 STRCPY_TO_ARRAY (j->options.save_to,
591 config.job_defaults.save_to);
592 debug ("Defaulted output to '%s'", j->options.save_to);
596 /* Now we've got something... let's see what it is...
598 if (stat (j->options.save_to, &st)) {
599 if (errno == ENOENT) {
600 /* Name of a file which doesn't exist... ready to save. */
601 debug ("Target does not exist.");
602 return j;
604 error_sys ("could not stat(`%s')", j->options.save_to);
605 client_error (errfp, "Failed when checking pathname '%s'",
606 j->options.save_to);
607 goto RETURN_NULL;
610 /* If it's a directory name, append the basename from above and
611 * re-stat.
613 if (S_ISDIR (st.st_mode)) {
614 int offset = strlen (j->options.save_to);
615 debug ("Is a directory.");
616 if (offset + base_sz + 2 > sizeof j->options.save_to) {
617 client_error (errfp, "Save-to path too long!");
618 goto RETURN_NULL;
621 j->options.save_to[offset] = '/';
622 strncpy (j->options.save_to + offset + 1, base_first, base_sz);
623 j->options.save_to[offset + 1 + base_sz] = '\0';
625 debug ("Extended to %s", j->options.save_to);
627 if (stat (j->options.save_to, &st)) {
628 if (errno == ENOENT) {
629 return j;
631 error_sys ("could not stat(`%s')", j->options.save_to);
632 client_error (errfp, "Failed when checking pathname '%s'",
633 j->options.save_to);
634 goto RETURN_NULL;
638 /* If we're here, it's not a directory but it exists. */
639 debug ("%s Exists!", j->options.save_to);
640 if (!j->options.overwrite && !j->options.continue_from) {
641 client_error (errfp,
642 "File '%s' exists and --overwrite not specified",
643 j->options.save_to);
644 goto RETURN_NULL;
647 /* For continuations, get the file length. If the file does not
648 * exist, just disable continuation; this is not an error.
649 * (Continuation may now be permanently enabled in the RC file.)
651 if (j->options.continue_from) {
652 if (S_ISREG (st.st_mode)) {
653 j->options.continue_from = st.st_size;
654 } else {
655 j->options.continue_from = 0;
659 /* Finally, check permissions */
660 if ((st.st_mode & S_IWOTH)
661 || ((st.st_mode & S_IWGRP) && st.st_gid == getegid ())
662 || ((st.st_mode & S_IWUSR) && st.st_uid == geteuid ()))
663 return j;
665 client_error (errfp, "File '%s' exists and is not writable.\n",
666 j->options.save_to);
668 RETURN_NULL:
669 free (j);
671 return 0;
675 /***********************************************************************
676 * process_*(): Implementations of each server command.
678 static void process_get (
679 FILE *fp, char *argnames[], char *argvalues[])
681 char **an, **av;
682 Request req;
683 Job *job;
685 debug ("process_get()");
687 /* Don't waste the user's time if we're full already... */
688 if (job_queue_depth >= MAX_QUEUED_JOBS) {
689 client_error (fp, "Job queue full");
690 return;
693 /* Empty out the request object... */
694 clear_request (&req);
696 /* And parse the args... */
697 for (an = argnames, av = argvalues; *an && *av; ++an, ++av) {
698 if (strcasecmp (*an, ARG_GET_SOURCE_URL) == 0) {
699 if (strlen (*av) > MAXURL) {
700 client_error (fp, "Source URL too long");
701 return;
703 req.source_url = *av;
705 } else if (strcasecmp (*an, ARG_GET_DISPLAY) == 0) {
706 req.display = *av;
708 } else if (strcasecmp (*an, ARG_GET_SAVE_TO) == 0) {
709 req.save_to = *av;
711 } else if (strcasecmp (*an, ARG_GET_CONTINUE_FROM) == 0) {
712 char *end;
713 req.continue_from = strtoul (*av, &end, 0);
714 if (*end) {
715 client_error (fp,
716 ARG_GET_CONTINUE_FROM ": must be an integer");
717 return;
720 } else if (strcasecmp (*an, ARG_GET_OVERWRITE) == 0) {
721 req.overwrite = 1;
723 } else if (strcasecmp (*an, ARG_GET_PROXY) == 0) {
724 req.proxy = *av;
726 } else if (strcasecmp (*an, ARG_GET_FOLLOW) == 0) {
727 req.follow = atoi (*av);
729 } else if (strcasecmp (*an, ARG_GET_UA) == 0) {
730 req.user_agent = *av;
732 } else if (strcasecmp (*an, ARG_GET_USE_ASCII) == 0) {
733 req.use_ascii = 1;
735 } else if (strcasecmp (*an, ARG_GET_REFERER) == 0) {
736 req.referer = *av;
738 } else if (strcasecmp (*an, ARG_GET_INCLUDE) == 0) {
739 req.include = 1;
741 } else if (strcasecmp (*an, ARG_GET_INTERFACE) == 0) {
742 req.interface = *av;
744 } else if (strcasecmp (*an, ARG_GET_PROXY_AUTH) == 0) {
745 req.proxy_auth = *av;
747 } else if (strcasecmp (*an, ARG_GET_AUTH) == 0) {
748 req.auth = *av;
750 } else {
751 client_error (fp, "Unknown parameter '%s'", *an);
752 return;
756 job = init_job (&req, fp);
757 if (!job)
758 return;
760 if (insert_job (job)) {
761 client_error (fp, "Invalid job parameters");
762 free (job);
763 } else {
764 client_job_accepted (fp, job->job_id);
769 static void process_cancel (
770 FILE *fp, char *argnames[], char *argvalues[])
772 char **an, **av;
773 job_id_t job_id = 0; /* job id 0 is never valid */
775 debug ("process_cancel()");
777 for (an = argnames, av = argvalues; *an && *av; ++an, ++av) {
778 if (strcasecmp (*an, ARG_CANCEL_JOBID) == 0) {
779 job_id = strtoul (*av, 0, 0);
780 } else {
781 client_error (fp, "Unknown parameter '%s'", *an);
782 return;
786 if (job_id == 0) {
787 client_error (fp,
788 CMD_CANCEL " requires the argument " ARG_CANCEL_JOBID);
789 return;
792 if (cancel_job (job_id))
793 client_error (fp, "Cancel failed");
794 else
795 client_job_canceled (fp, job_id);
799 void process_list (FILE *fp, char *argnames[], char *argvalues[])
801 Job *j, **jp;
803 (void)argnames;
804 (void)argvalues;
806 debug ("process_list()");
808 client_list_header (fp);
810 /* First list the active jobs. */
811 for (j = shmem->jobs; j < shmem->jobs + MAX_ACTIVE_JOBS; ++j)
812 if (j->status != J_EMPTY)
813 client_list_job (fp, j);
815 /* Then the waiting jobs. */
816 for (jp = job_queue; jp < job_queue + job_queue_depth; ++jp)
817 client_list_job (fp, *jp);
820 /***********************************************************************
821 * process_request(): Accept a command and parameters, process it,
822 * and reply.
824 static void process_request (FILE *fp)
826 char command[MAXCMDLEN];
827 char *arg;
828 int nargs;
830 char *argnames[MAXCMDARGS + 1];
831 char *argvalues[MAXCMDARGS + 1];
833 /* A command line always comes first. */
834 if (!fgets (command, sizeof command - 1, fp)) {
835 debug ("No command on pipe!");
836 return;
839 /* Arguments come after whitespace. Note that not all commands have
840 * args. Each argument looks like this: PARAMETERNAME(VALUE).
841 * Hey, don't tell me *none* of you have ever done AS/400 CL....
843 nargs = 0;
844 arg = command + strcspn (command, " \t\r\n");
845 *arg++ = 0;
846 arg += strspn (arg, " \t");
848 while (*arg && *arg != '\n' && *arg != '\r') {
849 if (nargs > MAXCMDARGS - 1) {
850 client_error (fp, "Too many arguments!");
851 return;
854 argnames[nargs] = arg;
856 if (!(arg = strchr (arg, '('))) {
857 client_error (fp, "Argument missing value");
858 return;
861 *arg++ = 0;
863 argvalues[nargs] = arg;
865 /* Arguments are terminated by a ), of course, but they may
866 * also contain characters (such as )) quoted by \.
868 while (*arg && *arg != ')') {
869 if (*arg == '\\') {
870 if (!arg[1]) {
871 client_error (fp, "Argument missing closing ), "
872 "ended with \\ by itself");
873 return;
876 /* strlen(arg+1)+1 = strlen(arg)-1+1 = strlen(arg) */
877 memmove (arg, arg + 1, strlen (arg));
879 ++arg;
882 if (!arg) {
883 client_error (fp, "Argument missing closing )");
884 return;
887 *arg++ = 0;
889 arg += strspn (arg, " \t");
891 ++nargs;
894 argnames[nargs] = 0;
895 argvalues[nargs] = 0;
897 /* Got a valid command/argument set. Process it. */
898 if (strcasecmp (command, CMD_GET) == 0)
899 process_get (fp, argnames, argvalues);
900 else if (strcasecmp (command, CMD_CANCEL) == 0)
901 process_cancel (fp, argnames, argvalues);
902 else if (strcasecmp (command, CMD_LIST) == 0)
903 process_list (fp, argnames, argvalues);
904 else
905 client_error (fp, "Unknown command");
909 /***********************************************************************
910 * on_iq_ready(): invoked by the dockapp lib when there are connections
911 * pending on the iq
913 static dockapp_rv_t on_iq_ready (void *unused0, short unused1)
915 FILE *fp;
917 (void)unused0;
918 (void)unused1;
920 debug ("on_iq_ready");
922 if ((fp = iq_server_accept ())) {
923 process_request (fp);
925 fclose (fp);
928 return dockapp_ok;
931 static int init_grim_reaper (void)
933 struct sigaction sa;
935 sa.sa_handler = SIG_IGN;
936 sigemptyset (&sa.sa_mask);
937 sa.sa_flags = SA_NOCLDSTOP | SA_RESTART;
938 /* Obsolete - sa.sa_restorer = 0; */
940 if (sigaction (SIGCHLD, &sa, 0)) {
941 error_sys ("sigaction(SIGCHLD) failed");
942 return 1;
945 return 0;
949 static dockapp_rv_t on_click_pbar (
950 void *cbdata, int x_unused, int y_unused)
952 int which = (intptr_t)cbdata;
953 (void)x_unused;
954 (void)y_unused;
956 debug ("got a click on pbar %d", which);
958 if (bar_selected == which) {
959 /* Selected bar gets deselected. */
960 bar_selected = -1;
962 } else {
963 bar_selected = which;
966 return dockapp_ok;
969 static dockapp_rv_t on_click_stop (
970 void *cbdata, int x_unused, int y_unused)
972 int which = (intptr_t)cbdata;
973 (void)x_unused;
974 (void)y_unused;
976 debug ("got a click on stop %d", which);
978 if (bar_selected == which) {
979 /* got a stop request (only works on selected pbar) */
980 ++shmem->jobs[which].stop_request;
983 return dockapp_ok;
986 static dockapp_rv_t on_periodic_callback (void *cbdata)
988 (void)cbdata;
990 if (process_queue ())
991 return dockapp_exit;
993 draw_pbars ();
995 return dockapp_ok;
998 static dockapp_rv_t on_got_selection (void *cbdata, const char *str)
1000 Request req;
1001 Job *j;
1003 (void)cbdata;
1005 debug ("on_got_selection >> %s", str);
1007 if (strlen (str) > MAXURL) {
1008 error ("rejecting job submission: URL too long!");
1009 return dockapp_ok;
1012 clear_request (&req);
1014 req.source_url = str;
1016 j = init_job (&req, stderr);
1017 if (!j) {
1018 return dockapp_ok;
1021 debug ("submitting job for [%s]...", j->source_url);
1023 if (insert_job (j)) {
1024 free (j);
1025 debug ("insert_job rejected it!");
1028 return dockapp_ok;
1031 static dockapp_rv_t on_middle_click (void *cbdata, int x, int y)
1033 (void)cbdata;
1034 (void)x;
1035 (void)y;
1037 debug ("on_middle_click");
1039 dockapp_request_selection_string (on_got_selection, 0);
1041 return dockapp_ok;
1045 /* This is the main routine for the dock app (the first instance
1046 * started)
1048 int server (int argc, char **argv)
1050 intptr_t i;
1052 config_server (argc, argv, &config);
1054 if (init_grim_reaper ())
1055 return 1;
1057 if (init_shmem ())
1058 return 1;
1060 if (iq_server_init ())
1061 return 1;
1063 init_font ();
1065 dockapp_init_gui ("wmget", argv, wmget_xpm);
1067 for (i = 0; i < 4; ++i) {
1068 dockapp_add_clickregion (
1069 PBAR_X + PBAR_LENGTH - BTN_WIDTH, PBAR_Y[i],
1070 PBAR_LENGTH, PBAR_HEIGHT,
1071 Button1Mask,
1072 on_click_stop, (void *)i);
1074 dockapp_add_clickregion (
1075 PBAR_X, PBAR_Y[i],
1076 PBAR_LENGTH, PBAR_HEIGHT,
1077 Button1Mask,
1078 on_click_pbar, (void *)i);
1081 dockapp_add_clickregion (
1082 0, 0, 64, 64, Button2Mask, on_middle_click, 0);
1084 dockapp_add_pollfd (iq_get_listen_fd (), POLLIN, on_iq_ready, 0);
1086 dockapp_set_periodic_callback (400, on_periodic_callback, 0);
1088 /* Perform one initial refresh.
1090 on_periodic_callback (0);
1092 dockapp_run ();
1094 return 0;