source: git/src/cavernlog.cc@ 99d6dd9

RELEASE/1.2 debug-ci debug-ci-sanitisers faster-cavernlog log-select main stereo stereo-2025 walls-data walls-data-hanging-as-warning warn-only-for-hanging-survey
Last change on this file since 99d6dd9 was 3d3fb6c, checked in by Olly Betts <olly@…>, 12 years ago

lib/,src/cavernlog.cc: If we fail to start the external editor,
throw up an error box.

  • Property mode set to 100644
File size: 10.5 KB
Line 
1/* cavernlog.cc
2 * Run cavern inside an Aven window
3 *
4 * Copyright (C) 2005,2006,2010,2011,2012,2014 Olly Betts
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20
21#ifdef HAVE_CONFIG_H
22# include <config.h>
23#endif
24
25#include "aven.h"
26#include "cavernlog.h"
27#include "filename.h"
28#include "mainfrm.h"
29#include "message.h"
30
31#include <errno.h>
32#include <stdio.h>
33#include <stdlib.h>
34
35// For select():
36#ifdef HAVE_SYS_SELECT_H
37#include <sys/select.h>
38#endif
39#include <sys/time.h>
40#include <sys/types.h>
41#include <unistd.h>
42
43enum { LOG_REPROCESS = 1234 };
44
45BEGIN_EVENT_TABLE(CavernLogWindow, wxHtmlWindow)
46 EVT_BUTTON(LOG_REPROCESS, CavernLogWindow::OnReprocess)
47 EVT_BUTTON(wxID_OK, CavernLogWindow::OnOK)
48END_EVENT_TABLE()
49
50static wxString escape_for_shell(wxString s, bool protect_dash = false)
51{
52 size_t p = 0;
53#ifdef __WXMSW__
54 bool needs_quotes = false;
55 while (p < s.size()) {
56 wxChar ch = s[p];
57 if (ch < 127) {
58 if (ch == wxT('"')) {
59 s.insert(p, 1, wxT('\\'));
60 ++p;
61 needs_quotes = true;
62 } else if (strchr(" <>&|^", ch)) {
63 needs_quotes = true;
64 }
65 }
66 ++p;
67 }
68 if (needs_quotes) {
69 s.insert(0u, 1, wxT('"'));
70 s += wxT('"');
71 }
72#else
73 if (protect_dash && !s.empty() && s[0u] == '-') {
74 // If the filename starts with a '-', protect it from being
75 // treated as an option by prepending "./".
76 s.insert(0, wxT("./"));
77 p = 2;
78 }
79 while (p < s.size()) {
80 // Exclude a few safe characters which are common in filenames
81 if (!isalnum(s[p]) && strchr("/._-", s[p]) == NULL) {
82 s.insert(p, 1, wxT('\\'));
83 ++p;
84 }
85 ++p;
86 }
87#endif
88 return s;
89}
90
91CavernLogWindow::CavernLogWindow(MainFrm * mainfrm_, wxWindow * parent)
92 : wxHtmlWindow(parent), mainfrm(mainfrm_)
93{
94 int fsize = parent->GetFont().GetPointSize();
95 int sizes[7] = { fsize, fsize, fsize, fsize, fsize, fsize, fsize };
96 SetFonts(wxString(), wxString(), sizes);
97}
98
99void
100CavernLogWindow::OnLinkClicked(const wxHtmlLinkInfo &link)
101{
102 wxString href = link.GetHref();
103 wxString title = link.GetTarget();
104 size_t colon = href.rfind(wxT(':'));
105 if (colon == wxString::npos)
106 return;
107 size_t colon2 = href.rfind(wxT(':'), colon - 1);
108 if (colon2 != wxString::npos) swap(colon, colon2);
109#ifdef __WXMSW__
110 wxString cmd = wxT("notepad $f");
111#elif defined __WXMAC__
112 wxString cmd = wxT("open -t $f");
113#else
114 wxString cmd = wxT("x-terminal-emulator -title $t -e vim +'call cursor($l,$c)' $f");
115 // wxString cmd = wxT("gedit -b $f +$l:$c $f");
116 // wxString cmd = wxT("x-terminal-emulator -title $t -e emacs +$l $f");
117 // wxString cmd = wxT("x-terminal-emulator -title $t -e nano +$l $f");
118 // wxString cmd = wxT("x-terminal-emulator -title $t -e jed -g $l $f");
119#endif
120 const char * p = getenv("SURVEXEDITOR");
121 if (p) {
122 cmd = wxString(p, wxConvUTF8);
123 if (!cmd.find(wxT("$f"))) {
124 cmd += wxT(" $f");
125 }
126 }
127 size_t i = 0;
128 while ((i = cmd.find(wxT('$'), i)) != wxString::npos) {
129 if (++i >= cmd.size()) break;
130 switch ((int)cmd[i]) {
131 case wxT('$'):
132 cmd.erase(i, 1);
133 break;
134 case wxT('f'): {
135 wxString f = escape_for_shell(href.substr(0, colon), true);
136 cmd.replace(i - 1, 2, f);
137 i += f.size() - 1;
138 break;
139 }
140 case wxT('t'): {
141 wxString t = escape_for_shell(title);
142 cmd.replace(i - 1, 2, t);
143 i += t.size() - 1;
144 break;
145 }
146 case wxT('l'): {
147 wxString l = escape_for_shell(href.substr(colon + 1, colon2 - colon - 1));
148 cmd.replace(i - 1, 2, l);
149 i += l.size() - 1;
150 break;
151 }
152 case wxT('c'): {
153 wxString l;
154 if (colon2 == wxString::npos)
155 l = wxT("0");
156 else
157 l = escape_for_shell(href.substr(colon2 + 1));
158 cmd.replace(i - 1, 2, l);
159 i += l.size() - 1;
160 break;
161 }
162 default:
163 ++i;
164 }
165 }
166#ifdef __WXMSW__
167 if (_wsystem(cmd.c_str()) >= 0)
168 return;
169#else
170 if (system(cmd.mb_str()) >= 0)
171 return;
172#endif
173 wxString m;
174 m.Printf(wmsg(/*Couldn’t run external command: “%s”*/17), cmd.c_str());
175 m += wxT(" (");
176 m += wxString(strerror(errno), wxConvUTF8);
177 m += wxT(')');
178 wxGetApp().ReportError(m);
179}
180
181int
182CavernLogWindow::process(const wxString &file)
183{
184 SetFocus();
185 filename = file;
186
187#ifdef __WXMSW__
188 SetEnvironmentVariable(wxT("SURVEX_UTF8"), wxT("1"));
189#else
190 setenv("SURVEX_UTF8", "1", 1);
191#endif
192
193 wxString escaped_file = escape_for_shell(file, true);
194#ifdef __WXMSW__
195 wxString cmd;
196 {
197 DWORD len = 256;
198 wchar_t *buf = NULL;
199 while (1) {
200 DWORD got;
201 buf = (wchar_t*)osrealloc(buf, len * 2);
202 got = GetModuleFileNameW(NULL, buf, len);
203 if (got < len) break;
204 len += len;
205 }
206 /* Strange Win32 nastiness - strip prefix "\\?\" if present */
207 if (wcsncmp(buf, L"\\\\?\\", 4) == 0) buf += 4;
208 wchar_t * slash = wcsrchr(buf, L'\\');
209 if (slash) {
210 cmd.assign(buf, slash - buf + 1);
211 }
212 }
213 cmd += L"cavern";
214 cmd = escape_for_shell(cmd, false);
215#else
216 char *cavern = use_path(msg_exepth(), "cavern");
217 wxString cmd = escape_for_shell(wxString(cavern, wxConvUTF8), false);
218 osfree(cavern);
219#endif
220 cmd += wxT(" -o ");
221 cmd += escaped_file;
222 cmd += wxT(' ');
223 cmd += escaped_file;
224
225#ifdef __WXMSW__
226 FILE * cavern_out = _wpopen(cmd.c_str(), L"r");
227#else
228 FILE * cavern_out = popen(cmd.mb_str(), "r");
229#endif
230 if (!cavern_out) {
231 wxString m;
232 m.Printf(wmsg(/*Couldn’t run external command: “%s”*/17), cmd.c_str());
233 m += wxT(" (");
234 m += wxString(strerror(errno), wxConvUTF8);
235 m += wxT(')');
236 wxGetApp().ReportError(m);
237 return -2;
238 }
239
240 int cavern_fd;
241#ifdef __WXMSW__
242 cavern_fd = _fileno(cavern_out);
243#else
244 cavern_fd = fileno(cavern_out);
245#endif
246 assert(cavern_fd < FD_SETSIZE); // FIXME we shouldn't just assert, but what else to do?
247 wxString cur;
248 int link_count = 0;
249 // We're only guaranteed one character of pushback by ungetc() but we
250 // need two so for portability we implement the second ourselves.
251 int left = EOF;
252 while (!feof(cavern_out)) {
253 fd_set rfds, efds;
254 FD_ZERO(&rfds);
255 FD_SET(cavern_fd, &rfds);
256 FD_ZERO(&efds);
257 FD_SET(cavern_fd, &efds);
258 // Timeout instantly.
259 struct timeval timeout;
260 timeout.tv_sec = 0;
261 timeout.tv_usec = 0;
262 if (select(cavern_fd + 1, &rfds, NULL, &efds, &timeout) == 0) {
263 Update();
264 FD_SET(cavern_fd, &rfds);
265 FD_SET(cavern_fd, &efds);
266 // Set timeout to 0.1 seconds.
267 timeout.tv_sec = 0;
268 timeout.tv_usec = 100000;
269 if (select(cavern_fd + 1, &rfds, NULL, &efds, &timeout) == 0) {
270 wxYield();
271 continue;
272 }
273 }
274 if (!FD_ISSET(cavern_fd, &rfds)) {
275 // Error, which pclose() should report.
276 break;
277 }
278 int ch;
279 if (left == EOF) {
280 ch = GETC(cavern_out);
281 if (ch == EOF) break;
282 } else {
283 ch = left;
284 left = EOF;
285 }
286 // Decode UTF-8 first to avoid security issues with <, >, &, etc
287 // encoded using multibyte encodings.
288 if (ch >= 0xc0 && ch < 0xf0) {
289 int ch1 = GETC(cavern_out);
290 if ((ch1 & 0xc0) != 0x80) {
291 left = ch1;
292 } else if (ch < 0xe0) {
293 /* 2 byte sequence */
294 ch = ((ch & 0x1f) << 6) | (ch1 & 0x3f);
295 } else {
296 /* 3 byte sequence */
297 int ch2 = GETC(cavern_out);
298 if ((ch2 & 0xc0) != 0x80) {
299 ungetc(ch2, cavern_out);
300 left = ch1;
301 } else {
302 ch = ((ch & 0x1f) << 12) | ((ch1 & 0x3f) << 6) | (ch2 & 0x3f);
303 }
304 }
305 }
306
307 switch (ch) {
308 case '\r':
309 // Ignore.
310 break;
311 case '\n': {
312 if (cur.empty()) continue;
313#ifndef __WXMSW__
314 size_t colon = cur.find(':');
315#else
316 // If the path is "C:\path\to\file.svx" then don't split at the
317 // : after the drive letter! FIXME: better to look for ": "?
318 size_t colon = cur.find(':', 2);
319#endif
320 if (colon != wxString::npos && colon < cur.size() - 2) {
321 ++colon;
322 size_t i = colon;
323 while (i < cur.size() - 2 &&
324 cur[i] >= wxT('0') && cur[i] <= wxT('9')) {
325 ++i;
326 }
327 if (i > colon && cur[i] == wxT(':') ) {
328 colon = i;
329 // Check for column number.
330 while (++i < cur.size() - 2 &&
331 cur[i] >= wxT('0') && cur[i] <= wxT('9')) { }
332 if (i > colon + 1 && cur[i] == wxT(':') ) {
333 colon = i;
334 }
335 wxString tag = wxT("<a href=\"");
336 tag.append(cur, 0, colon);
337 while (cur[++i] == wxT(' ')) { }
338 tag += wxT("\" target=\"");
339 tag.append(cur, i, wxString::npos);
340 tag += wxT("\">");
341 cur.insert(0, tag);
342 cur.insert(colon + tag.size(), wxT("</a>"));
343 ++link_count;
344 }
345 }
346
347 // Save the scrollbar positions.
348 int scroll_x = 0, scroll_y = 0;
349 GetViewStart(&scroll_x, &scroll_y);
350
351 cur += wxT("<br>\n");
352 AppendToPage(cur);
353
354 if (!link_count) {
355 // Auto-scroll the window until we've reported a warning or
356 // error.
357 int x, y;
358 GetVirtualSize(&x, &y);
359 int xs, ys;
360 GetClientSize(&xs, &ys);
361 y -= ys;
362 int xu, yu;
363 GetScrollPixelsPerUnit(&xu, &yu);
364 Scroll(scroll_x, y / yu);
365 } else {
366 // Restore the scrollbar positions.
367 Scroll(scroll_x, scroll_y);
368 }
369
370 cur.clear();
371 break;
372 }
373 case '<':
374 cur += wxT("&lt;");
375 break;
376 case '>':
377 cur += wxT("&gt;");
378 break;
379 case '&':
380 cur += wxT("&amp;");
381 break;
382 case '"':
383 cur += wxT("&#22;");
384 continue;
385 default:
386 if (ch >= 128) {
387 cur += wxString::Format(wxT("&#%u;"), ch);
388 } else {
389 cur += (char)ch;
390 }
391 }
392 }
393
394 int retval = pclose(cavern_out);
395 if (retval) {
396 AppendToPage(wxString::Format(wxT("<avenbutton default id=1234 name=\"%s\">"),
397 wmsg(/*Reprocess*/184).c_str()));
398 if (retval == -1) {
399 wxString m = wxT("Problem running cavern: ");
400 m += wxString(strerror(errno), wxConvUTF8);
401 wxGetApp().ReportError(m);
402 return -2;
403 }
404 return -1;
405 }
406 if (link_count) {
407 AppendToPage(wxString::Format(wxT("<avenbutton id=1234 name=\"%s\">"),
408 wmsg(/*Reprocess*/184).c_str()));
409 AppendToPage(wxString::Format(wxT("<avenbutton default id=%d>"), (int)wxID_OK));
410 Update();
411 }
412 return link_count;
413}
414
415void
416CavernLogWindow::OnReprocess(wxCommandEvent & e)
417{
418 SetPage(wxString());
419 if (process(filename) == 0) {
420 OnOK(e);
421 }
422}
423
424void
425CavernLogWindow::OnOK(wxCommandEvent &)
426{
427 mainfrm->InitialiseAfterLoad(filename);
428}
Note: See TracBrowser for help on using the repository browser.