]> git.armaanb.net Git - mmenu.git/blob - mmenu.c
Add installation instructions to README
[mmenu.git] / mmenu.c
1 #include <stdlib.h>
2 #include <stdio.h>
3 #include <stdbool.h>
4 #include <string.h>
5 #include <strings.h>
6
7 #include <Xm/Xm.h>
8 #include <Xm/Text.h>
9 #include <Xm/RowColumn.h>
10 #include <Xm/List.h>
11
12 #include <curl/curl.h>
13
14 struct memstruct {
15         char *memory;
16         size_t size;
17 };
18
19 char *days[] = {"Monday", "Tuesday", "Wednesday", "Thursday", "Friday"};
20
21 int
22 memfail(void)
23 {
24         printf("ERROR: Out of memory\n");
25         exit(2);
26 }
27
28 static size_t
29 memback(void *contents, size_t size, size_t nmemb, void *userp)
30 {
31         size_t realsize = size * nmemb;
32         struct memstruct *mem = (struct memstruct *)userp;
33
34         char *ptr = realloc(mem->memory, mem->size + realsize + 1);
35         if (!ptr) memfail();
36
37         mem->memory = ptr;
38         memcpy(&(mem->memory[mem->size]), contents, realsize);
39         mem->size += realsize;
40         mem->memory[mem->size] = 0;
41
42         return realsize;
43 }
44
45 char *
46 getmenu(char *date)
47 {
48         CURL *curl = curl_easy_init();
49         CURLcode res;
50
51         if (!curl) {
52                 fprintf(stderr, "ERROR: Failed to initialize cURL\n");
53                 exit(1);
54         }
55
56         struct memstruct chunk;
57         chunk.memory = malloc(1); // Grown as needed by the realloc in memback()
58         chunk.size = 0; // No data yet
59
60         const char *baseurl =
61                 "https://nobilis.nobles.edu/skyworld/castlemenu.php?Date=";
62         int len = strlen(baseurl) + strlen(date) + 1;
63         char *url = (char *) malloc(len);
64         snprintf(url, len, "%s%s", baseurl, date);
65         printf("INFO: Using URL %s\n", url);
66         curl_easy_setopt(curl, CURLOPT_URL, url);
67         curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, memback);
68         curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
69         curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&chunk);
70
71         printf("INFO: Fetching webpage\n");
72         res = curl_easy_perform(curl);
73         free(url);
74
75         if (res != CURLE_OK) {
76                 fprintf(stderr,"ERROR: Could not fetch webpage:\n%s\n",
77                                                 curl_easy_strerror(res));
78                 exit(1);
79         }
80
81         curl_easy_cleanup(curl);
82
83         // Parse HTML
84         printf("INFO: Parsing HTML\n");
85         bool intag = false;
86         char *outp = (char *) calloc(1, sizeof(char));
87         if (!outp) memfail();
88
89         // Extract text from between HTML tags
90         int j = 1;
91         char next[2];
92         next[1] = '\0';
93         for (int i = 345; chunk.memory[i]; i++) {
94                 char c = chunk.memory[i];
95                 if (c == '<') intag = true;
96                 if (!intag) {
97                         j++;
98                         outp = (char *) realloc(outp, j);
99                         if (!outp) memfail();
100                         next[0] = c;
101                         strncat(outp, next, 2);
102                 }
103                 if (c == '>') intag = false;
104         }
105
106         free(chunk.memory);
107
108         // Strip empty newlines
109         char *nl = (char *) calloc(1, sizeof(char));
110         if (!nl) memfail();
111
112         j = 1;
113         for (int i = 0; outp[i]; i++) {
114                 j++;
115                 if (!((outp[i] == '\n' && outp[i+1] == '\n') ||
116                                 (outp[i] == ' ' && outp[i+1] == ' '))) {
117                         nl = (char *) realloc(nl, j);
118                         if (!nl) memfail();
119                         next[0] = outp[i+1];
120                         strncat(nl, next, 2);
121                 }
122         }
123
124         free(outp);
125         return nl;
126 }
127
128 void
129 dayback(Widget unused, XtPointer client_data, XtPointer call_data)
130 {
131         (void)unused;
132         Widget disp = (Widget) client_data;
133         XmListCallbackStruct *list_cbs = (XmListCallbackStruct *) call_data;
134         char *nl = getmenu(days[list_cbs->item_position - 1]);
135         char *oldtext = XmTextGetString(disp) ;
136         XmTextReplace((Widget) disp, 0, strlen(oldtext), nl);
137         free(nl);
138         free(oldtext);
139 }
140
141 // Convert an array of strings to an array of compound strings
142 XmStringTable
143 ArgvToXmStringTable (int argc, char **argv)
144 {
145         XmStringTable new =
146                 (XmStringTable) XtMalloc((argc+1) * sizeof (XmString));
147         if (!new) return (XmStringTable) 0;
148
149         new[argc] = (XmString) 0;
150         while (--argc >= 0) {
151                 new[argc] = XmStringGenerate ((XtPointer) argv[argc], "tag2",
152                                 XmCHARSET_TEXT, NULL);
153         }
154
155         return new;
156 }
157
158 int
159 main(int argc, char *argv[])
160 {
161         // Parse CLI arguments
162         bool showUsage = false;
163         if (argc == 2) {
164                 showUsage = true;
165                 for (int i = 0; i < 5; i++) {
166                         if (strcasecmp(argv[1], days[i]) == 0) {
167                                 showUsage = false;
168                                 break;
169                         }
170                 }
171         }
172
173         if (argc > 2 || showUsage) {
174                 printf("Usage: %s [optional: day of the week]\n", argv[0]);
175                 exit(3);
176         }
177
178         // Initialize motif
179         Widget         toplevel, rowcol, daysel, disp;
180         XmStringTable  strs;
181         XtAppContext   app;
182         Arg            args[0];
183
184         XtSetLanguageProc(NULL, NULL, NULL);
185         toplevel = XtVaOpenApplication(&app, "Castle Menu", NULL, 0, &argc, argv,
186                                                                                                                                  NULL, sessionShellWidgetClass, NULL);
187
188         int n = 0;
189         XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++;
190         rowcol = XmCreateRowColumn(toplevel, "rowcol", args, n);
191         XtManageChild(rowcol);
192
193         // Create text widget to display menu
194         n = 0;
195         char *nl = getmenu((argv[1]) ? argv[1] : "");
196         XtSetArg(args[n], XmNvalue, nl); n++;
197         XtSetArg(args[n], XmNeditable, False); n++;
198         XtSetArg(args[n], XmNcolumns, 80); n++;
199         XtSetArg(args[n], XmNrows, 20); n++;
200         XtSetArg(args[n], XmNeditMode, XmMULTI_LINE_EDIT); n++;
201         XtSetArg(args[n], XmNcursorPositionVisible, False); n++;
202         disp = XmCreateScrolledText(rowcol, "text", args, n);
203         XtManageChild(disp);
204         free(nl);
205
206         // Create day selector
207         strs = ArgvToXmStringTable(XtNumber(days), days);
208         daysel = XmCreateScrolledList(rowcol, "list", NULL, 0);
209         XtVaSetValues(daysel, XmNitems, strs, XmNitemCount, XtNumber(days), NULL);
210         XtAddCallback(daysel, XmNbrowseSelectionCallback, dayback, (XtPointer) disp);
211         XtManageChild(daysel);
212
213         // Display everything
214         XtRealizeWidget(toplevel);
215         XtAppMainLoop(app);
216 }