chiark / gitweb /
Shun time(), since on Linux it is not monotonic with gettimeofday().
[disorder] / disobedience / client.c
1 /*
2  * This file is part of DisOrder.
3  * Copyright (C) 2006, 2007, 2008 Richard Kettlewell
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  * 
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 /** @file disobedience/client.c
19  * @brief GLIB integration for @ref lib/eclient.c client
20  */
21
22 #include "disobedience.h"
23
24 /** @brief GSource subclass for disorder_eclient */
25 struct eclient_source {
26   GSource gsource;
27   disorder_eclient *client;
28   time_t last_poll;
29   GPollFD pollfd;
30 };
31
32 /** @brief Called before FDs are polled to choose a timeout.
33  *
34  * We ask for a 3s timeout and every 10s or so we force a dispatch. 
35  */
36 static gboolean gtkclient_prepare(GSource *source,
37                                   gint *timeout) {
38   const struct eclient_source *esource = (struct eclient_source *)source;
39   D(("gtkclient_prepare"));
40   if(xtime(0) > esource->last_poll + 10)
41     return TRUE;                /* timed out */
42   *timeout = 3000/*milliseconds*/;
43   return FALSE;                 /* please poll */
44 }
45
46 /** @brief Check whether we're ready to dispatch */
47 static gboolean gtkclient_check(GSource *source) {
48   const struct eclient_source *esource = (struct eclient_source *)source;
49   D(("gtkclient_check fd=%d events=%x revents=%x",
50      esource->pollfd.fd, esource->pollfd.events, esource->pollfd.revents));
51   return esource->pollfd.fd != -1 && esource->pollfd.revents != 0;
52 }
53
54 /** @brief Called after poll() (or otherwise) to dispatch an event */
55 static gboolean gtkclient_dispatch(GSource *source,
56                                    GSourceFunc attribute((unused)) callback,
57                                    gpointer attribute((unused)) user_data) {
58   struct eclient_source *esource = (struct eclient_source *)source;
59   unsigned revents,  mode;
60
61   D(("gtkclient_dispatch"));
62   revents = esource->pollfd.revents & esource->pollfd.events;
63   mode = 0;
64   if(revents & (G_IO_IN|G_IO_HUP|G_IO_ERR))
65     mode |= DISORDER_POLL_READ;
66   if(revents & (G_IO_OUT|G_IO_HUP|G_IO_ERR))
67     mode |= DISORDER_POLL_WRITE;
68   xtime(&esource->last_poll);
69   disorder_eclient_polled(esource->client, mode);
70   return TRUE;                          /* ??? not documented */
71 }
72
73 /** @brief Table of callbacks for GSource subclass */
74 static const GSourceFuncs sourcefuncs = {
75   gtkclient_prepare,
76   gtkclient_check,
77   gtkclient_dispatch,
78   0,
79   0,
80   0,
81 };
82
83 /** @brief Tell the mainloop what we need */
84 static void gtkclient_poll(void *u,
85                            disorder_eclient attribute((unused)) *c,
86                            int fd, unsigned mode) {
87   struct eclient_source *esource = u;
88   GSource *source = u;
89
90   D(("gtkclient_poll fd=%d mode=%x", fd, mode));
91   /* deconfigure the source if currently configured */
92   if(esource->pollfd.fd != -1) {
93     D(("calling g_source_remove_poll"));
94     g_source_remove_poll(source, &esource->pollfd);
95     esource->pollfd.fd = -1;
96     esource->pollfd.events = 0;
97   }
98   /* install new settings */
99   if(fd != -1 && mode) {
100     esource->pollfd.fd = fd;
101     esource->pollfd.events = 0;
102     if(mode & DISORDER_POLL_READ)
103       esource->pollfd.events |= G_IO_IN | G_IO_HUP | G_IO_ERR;
104     if(mode & DISORDER_POLL_WRITE)
105       esource->pollfd.events |= G_IO_OUT | G_IO_ERR;
106     /* reconfigure the source */
107     D(("calling g_source_add_poll"));
108     g_source_add_poll(source, &esource->pollfd);
109   }
110 }
111
112 /** @brief Report a communication-level error
113  *
114  * Any operations still outstanding are automatically replied by the underlying
115  * @ref lib/eclient.c code.
116  */
117 static void gtkclient_comms_error(void attribute((unused)) *u,
118                                   const char *msg) {
119   D(("gtkclient_comms_error %s", msg));
120   gtk_label_set_text(GTK_LABEL(report_label), msg);
121 }
122
123 /** @brief Report a protocol-level error
124  *
125  * The error will not be retried.  We offer a callback to the submitter of the
126  * original command and if none is supplied we drop the error message in the
127  * status bar.
128  */
129 static void gtkclient_protocol_error(void attribute((unused)) *u,
130                                      void attribute((unused)) *v,
131                                      int attribute((unused)) code,
132                                      const char *msg) {
133   D(("gtkclient_protocol_error %s", msg));
134   gtk_label_set_text(GTK_LABEL(report_label), msg);
135 }
136
137 /** @brief Report callback from eclient */
138 static void gtkclient_report(void attribute((unused)) *u,
139                              const char *msg) {
140   if(!msg)
141     /* We're idle - clear the report line */
142     gtk_label_set_text(GTK_LABEL(report_label), "");
143 }
144
145 /** @brief Repoort an unhandled protocol-level error to the user */
146 void popup_protocol_error(int attribute((unused)) code,
147                           const char *msg) {
148   gtk_label_set_text(GTK_LABEL(report_label), msg);
149   popup_msg(GTK_MESSAGE_ERROR, msg);
150 }
151
152 /** @brief Table of eclient callbacks */
153 static const disorder_eclient_callbacks gtkclient_callbacks = {
154   gtkclient_comms_error,
155   gtkclient_protocol_error,
156   gtkclient_poll,
157   gtkclient_report
158 };
159
160 /** @brief Create a @ref disorder_eclient using the GLib main loop */
161 disorder_eclient *gtkclient(void) {
162   GSource *source;
163   struct eclient_source *esource;
164
165   D(("gtkclient"));
166   source = g_source_new((GSourceFuncs *)&sourcefuncs,
167                         sizeof (struct eclient_source));
168   esource = (struct eclient_source *)source;
169   esource->pollfd.fd = -1;
170   esource->client = disorder_eclient_new(&gtkclient_callbacks, source);
171   if(!esource->client) {
172     g_source_destroy(source);
173     return 0;
174   }
175   g_source_attach(source, 0);
176   return esource->client;
177 }
178
179 /*
180 Local Variables:
181 c-basic-offset:2
182 comment-column:40
183 fill-column:79
184 indent-tabs-mode:nil
185 End:
186 */