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.
46 #include <sys/types.h>
52 #include <X11/extensions/shape.h>
56 #include "dockapp/dockapp.h"
59 static ServerConfig config
;
61 /***********************************************************************
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)
85 for (i
= 0, cx
= CHAR_X
, cy
= CHAR_Y
; i
< 128; ++i
, ++cx
, ++cy
) {
87 *cx
= CHAR_SYMNUM_X
; /* 1st SYMNUM is the space */
89 } else if (i
>= 'a') {
90 *cx
= CHAR_LCALPHA_X
+ CHAR_WIDTH
* (i
- 'a');
95 } else if (i
>= 'A') {
96 *cx
= CHAR_UCALPHA_X
+ CHAR_WIDTH
* (i
- 'A');
101 } else if (i
>= ' ') {
102 *cx
= CHAR_SYMNUM_X
+ CHAR_WIDTH
* (i
- ' ');
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
],
117 CHAR_WIDTH
, CHAR_HEIGHT
);
123 /***********************************************************************
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 /***********************************************************************
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
,
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
[] = {
185 static void draw_pbars (void)
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);
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
) {
205 sprintf (pct
, "%02lu%%",
206 100L * j
->progress
/ j
->prog_max
);
208 draw_string (pct
, PBAR_X
+ 1, PBAR_Y
[i
] + 1);
211 strncpy (err
, j
->error
, 8);
213 draw_string (err
, PBAR_X
+ 1, PBAR_Y
[i
] + 1);
216 dockapp_copy_pixmap (
218 PBAR_X
+ PBAR_LENGTH
- BTN_WIDTH
, PBAR_Y
[i
],
219 BTN_WIDTH
, BTN_HEIGHT
);
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
233 static int init_shmem (void)
238 if ((shmid
= shmget (IPC_PRIVATE
, sizeof *shmem
, SHM_R
| SHM_W
)) < 0) {
239 error_sys ("could not allocate shared memory segment [shmget()]");
243 if ((shmem
= shmat (shmid
, 0, 0)) == (void *) -1) {
244 error_sys ("could not attach shared memory segment [shmat()]");
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;
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
)
273 error_sys ("could not create child process [fork()]");
275 } else if (f
== 0) { /* child */
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
);
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)
310 for (i
= 0; i
< MAX_ACTIVE_JOBS
; ++i
) {
311 switch (shmem
->jobs
[i
].status
) {
312 default: /* job occupying slot */
316 /* aha. see if there is anything queued up */
317 if (!job_queue_depth
)
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
]))
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
)
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
) {
353 /* Job has already completed. */
356 /* Job has already failed; this simply clears it
358 j
->status
= J_COMPLETE
;
361 /* just to keep the compiler warnings at bay */
365 ++j
->stop_request
; /* Request job termination. */
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. */
377 ((job_queue
+ job_queue_depth
) - jp
) * sizeof (Job
*));
383 /* Job not found. This is not an error, as we assume that the
384 * job has already died off.
389 /***********************************************************************
390 * client_*(): Issue a response back to the client.
392 static void client_error (FILE *fp
, const char *fmt
, ...)
397 fprintf (fp
, RESPONSE_ERROR
" ");
398 vfprintf (fp
, fmt
, 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
)
425 status
= "UNKNOWN: Internal Error!";
429 status
= "INIT: Waiting to start";
433 status
= "RUNNING: Currently retrieving";
437 status
= "PAUSED: Download suspended";
441 status
= "STOPPING: Got stop request";
445 status
= "COMPLETE: Download complete";
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");
467 j
->job_id
= next_job_id
++;
469 debug ("Accepted job...");
472 job_queue
[job_queue_depth
++] = j
;
478 static Job
*init_job (Request
*req
, FILE *errfp
)
481 const char *base_first
;
482 const char *base_last
;
485 Job
*j
= malloc (sizeof (Job
));
487 client_error (errfp
, "Dockapp out of memory!");
491 STRCPY_TO_ARRAY (j
->source_url
, req
->source_url
);
494 j
->progress
= j
->prog_max
= 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
;
508 STRCPY_TO_ARRAY (j
->options
.proxy
, req
->proxy
);
510 if (req
->follow
!= -1)
511 j
->options
.follow
= req
->follow
;
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
;
520 STRCPY_TO_ARRAY (j
->options
.referer
, req
->referer
);
522 if (req
->include
!= -1)
523 j
->options
.include
= req
->include
;
526 STRCPY_TO_ARRAY (j
->options
.interface
, req
->interface
);
529 STRCPY_TO_ARRAY (j
->options
.proxy_auth
, req
->proxy_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
)
540 base_first
= base_last
;
541 while (*base_first
!= '/' && base_first
> j
->source_url
)
543 base_sz
= base_last
- base_first
;
544 ++base_first
; /* get it past that initial slash */
547 /* Uh, oh... invalid source_url anyway... give up. */
548 client_error (errfp
, "Invalid URL '%s'", j
->source_url
);
552 debug ("baselen %d", base_sz
);
554 /* If no display-name was provided, use the basename.
557 STRCPY_TO_ARRAY (j
->options
.display
, req
->display
);
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.
573 if (req
->save_to
[0] == '/') {
574 debug ("Reqest contained absolute dir.");
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
581 "Download output pathname too long");
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
);
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.");
604 error_sys ("could not stat(`%s')", j
->options
.save_to
);
605 client_error (errfp
, "Failed when checking pathname '%s'",
610 /* If it's a directory name, append the basename from above and
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!");
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
) {
631 error_sys ("could not stat(`%s')", j
->options
.save_to
);
632 client_error (errfp
, "Failed when checking pathname '%s'",
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
) {
642 "File '%s' exists and --overwrite not specified",
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
;
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 ()))
665 client_error (errfp
, "File '%s' exists and is not writable.\n",
675 /***********************************************************************
676 * process_*(): Implementations of each server command.
678 static void process_get (
679 FILE *fp
, char *argnames
[], char *argvalues
[])
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");
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");
703 req
.source_url
= *av
;
705 } else if (strcasecmp (*an
, ARG_GET_DISPLAY
) == 0) {
708 } else if (strcasecmp (*an
, ARG_GET_SAVE_TO
) == 0) {
711 } else if (strcasecmp (*an
, ARG_GET_CONTINUE_FROM
) == 0) {
713 req
.continue_from
= strtoul (*av
, &end
, 0);
716 ARG_GET_CONTINUE_FROM
": must be an integer");
720 } else if (strcasecmp (*an
, ARG_GET_OVERWRITE
) == 0) {
723 } else if (strcasecmp (*an
, ARG_GET_PROXY
) == 0) {
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) {
735 } else if (strcasecmp (*an
, ARG_GET_REFERER
) == 0) {
738 } else if (strcasecmp (*an
, ARG_GET_INCLUDE
) == 0) {
741 } else if (strcasecmp (*an
, ARG_GET_INTERFACE
) == 0) {
744 } else if (strcasecmp (*an
, ARG_GET_PROXY_AUTH
) == 0) {
745 req
.proxy_auth
= *av
;
747 } else if (strcasecmp (*an
, ARG_GET_AUTH
) == 0) {
751 client_error (fp
, "Unknown parameter '%s'", *an
);
756 job
= init_job (&req
, fp
);
760 if (insert_job (job
)) {
761 client_error (fp
, "Invalid job parameters");
764 client_job_accepted (fp
, job
->job_id
);
769 static void process_cancel (
770 FILE *fp
, char *argnames
[], char *argvalues
[])
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);
781 client_error (fp
, "Unknown parameter '%s'", *an
);
788 CMD_CANCEL
" requires the argument " ARG_CANCEL_JOBID
);
792 if (cancel_job (job_id
))
793 client_error (fp
, "Cancel failed");
795 client_job_canceled (fp
, job_id
);
799 void process_list (FILE *fp
, char *argnames
[], char *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,
824 static void process_request (FILE *fp
)
826 char command
[MAXCMDLEN
];
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!");
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....
844 arg
= command
+ strcspn (command
, " \t\r\n");
846 arg
+= strspn (arg
, " \t");
848 while (*arg
&& *arg
!= '\n' && *arg
!= '\r') {
849 if (nargs
> MAXCMDARGS
- 1) {
850 client_error (fp
, "Too many arguments!");
854 argnames
[nargs
] = arg
;
856 if (!(arg
= strchr (arg
, '('))) {
857 client_error (fp
, "Argument missing value");
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
!= ')') {
871 client_error (fp
, "Argument missing closing ), "
872 "ended with \\ by itself");
876 /* strlen(arg+1)+1 = strlen(arg)-1+1 = strlen(arg) */
877 memmove (arg
, arg
+ 1, strlen (arg
));
883 client_error (fp
, "Argument missing closing )");
889 arg
+= strspn (arg
, " \t");
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
);
905 client_error (fp
, "Unknown command");
909 /***********************************************************************
910 * on_iq_ready(): invoked by the dockapp lib when there are connections
913 static dockapp_rv_t
on_iq_ready (void *unused0
, short unused1
)
920 debug ("on_iq_ready");
922 if ((fp
= iq_server_accept ())) {
923 process_request (fp
);
931 static int init_grim_reaper (void)
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");
949 static dockapp_rv_t
on_click_pbar (
950 void *cbdata
, int x_unused
, int y_unused
)
952 int which
= (intptr_t)cbdata
;
956 debug ("got a click on pbar %d", which
);
958 if (bar_selected
== which
) {
959 /* Selected bar gets deselected. */
963 bar_selected
= which
;
969 static dockapp_rv_t
on_click_stop (
970 void *cbdata
, int x_unused
, int y_unused
)
972 int which
= (intptr_t)cbdata
;
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
;
986 static dockapp_rv_t
on_periodic_callback (void *cbdata
)
990 if (process_queue ())
998 static dockapp_rv_t
on_got_selection (void *cbdata
, const char *str
)
1005 debug ("on_got_selection >> %s", str
);
1007 if (strlen (str
) > MAXURL
) {
1008 error ("rejecting job submission: URL too long!");
1012 clear_request (&req
);
1014 req
.source_url
= str
;
1016 j
= init_job (&req
, stderr
);
1021 debug ("submitting job for [%s]...", j
->source_url
);
1023 if (insert_job (j
)) {
1025 debug ("insert_job rejected it!");
1031 static dockapp_rv_t
on_middle_click (void *cbdata
, int x
, int y
)
1037 debug ("on_middle_click");
1039 dockapp_request_selection_string (on_got_selection
, 0);
1045 /* This is the main routine for the dock app (the first instance
1048 int server (int argc
, char **argv
)
1052 config_server (argc
, argv
, &config
);
1054 if (init_grim_reaper ())
1060 if (iq_server_init ())
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
,
1072 on_click_stop
, (void *)i
);
1074 dockapp_add_clickregion (
1076 PBAR_LENGTH
, PBAR_HEIGHT
,
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);