summaryrefslogtreecommitdiff
path: root/src/app.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/app.c')
-rw-r--r--src/app.c296
1 files changed, 264 insertions, 32 deletions
diff --git a/src/app.c b/src/app.c
index 670adee2..dc61c103 100644
--- a/src/app.c
+++ b/src/app.c
@@ -1,3 +1,25 @@
1/* Copyright 2020 Jaakko Keränen <jaakko.keranen@iki.fi>
2
3Redistribution and use in source and binary forms, with or without
4modification, are permitted provided that the following conditions are met:
5
61. Redistributions of source code must retain the above copyright notice, this
7 list of conditions and the following disclaimer.
82. Redistributions in binary form must reproduce the above copyright notice,
9 this list of conditions and the following disclaimer in the documentation
10 and/or other materials provided with the distribution.
11
12THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
13ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
14WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
15DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
16ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
17(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
18LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
19ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
20(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
21SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
22
1#include "app.h" 23#include "app.h"
2#include "bookmarks.h" 24#include "bookmarks.h"
3#include "embedded.h" 25#include "embedded.h"
@@ -25,6 +47,7 @@
25#include <the_Foundation/time.h> 47#include <the_Foundation/time.h>
26#include <SDL_events.h> 48#include <SDL_events.h>
27#include <SDL_render.h> 49#include <SDL_render.h>
50#include <SDL_timer.h>
28#include <SDL_video.h> 51#include <SDL_video.h>
29 52
30#include <stdio.h> 53#include <stdio.h>
@@ -32,26 +55,26 @@
32#include <errno.h> 55#include <errno.h>
33 56
34#if defined (iPlatformApple) && !defined (iPlatformIOS) 57#if defined (iPlatformApple) && !defined (iPlatformIOS)
35# include "ui/macos.h" 58# include "macos.h"
36#endif 59#endif
37 60
38iDeclareType(App) 61iDeclareType(App)
39 62
40#if defined (iPlatformApple) 63#if defined (iPlatformApple)
41#define EMB_BIN "../../Resources/resources.bin" 64#define EMB_BIN "../../Resources/resources.binary"
42static const char *dataDir_App_ = "~/Library/Application Support/fi.skyjake.Lagrange"; 65static const char *dataDir_App_ = "~/Library/Application Support/fi.skyjake.Lagrange";
43#endif 66#endif
44#if defined (iPlatformMsys) 67#if defined (iPlatformMsys)
45#define EMB_BIN "../resources.bin" 68#define EMB_BIN "../resources.binary"
46static const char *dataDir_App_ = "~/AppData/Roaming/fi.skyjake.Lagrange"; 69static const char *dataDir_App_ = "~/AppData/Roaming/fi.skyjake.Lagrange";
47#endif 70#endif
48#if defined (iPlatformLinux) 71#if defined (iPlatformLinux)
49#define EMB_BIN "../../share/lagrange/resources.bin" 72#define EMB_BIN "../../share/lagrange/resources.binary"
50#define EMB_BIN2 "../resources.bin" /* try from build dir as well */
51static const char *dataDir_App_ = "~/.config/lagrange"; 73static const char *dataDir_App_ = "~/.config/lagrange";
52#endif 74#endif
75#define EMB_BIN2 "../resources.binary" /* fallback from build/executable dir */
53static const char *prefsFileName_App_ = "prefs.cfg"; 76static const char *prefsFileName_App_ = "prefs.cfg";
54static const char *stateFileName_App_ = "state.bin"; 77static const char *stateFileName_App_ = "state.binary";
55 78
56struct Impl_App { 79struct Impl_App {
57 iCommandLine args; 80 iCommandLine args;
@@ -60,6 +83,8 @@ struct Impl_App {
60 iBookmarks * bookmarks; 83 iBookmarks * bookmarks;
61 iWindow * window; 84 iWindow * window;
62 iSortedArray tickers; 85 iSortedArray tickers;
86 uint32_t lastTickerTime;
87 uint32_t elapsedSinceLastTicker;
63 iBool running; 88 iBool running;
64 iBool pendingRefresh; 89 iBool pendingRefresh;
65 int tabEnum; 90 int tabEnum;
@@ -70,6 +95,9 @@ struct Impl_App {
70 float uiScale; 95 float uiScale;
71 int zoomPercent; 96 int zoomPercent;
72 enum iColorTheme theme; 97 enum iColorTheme theme;
98 iBool useSystemTheme;
99 iString gopherProxy;
100 iString httpProxy;
73}; 101};
74 102
75static iApp app_; 103static iApp app_;
@@ -99,21 +127,32 @@ const iString *dateStr_(const iDate *date) {
99static iString *serializePrefs_App_(const iApp *d) { 127static iString *serializePrefs_App_(const iApp *d) {
100 iString *str = new_String(); 128 iString *str = new_String();
101 const iSidebarWidget *sidebar = findWidget_App("sidebar"); 129 const iSidebarWidget *sidebar = findWidget_App("sidebar");
130 appendFormat_String(str, "window.retain arg:%d\n", d->retainWindowSize);
102 if (d->retainWindowSize) { 131 if (d->retainWindowSize) {
103 int w, h, x, y; 132 int w, h, x, y;
104 SDL_GetWindowSize(d->window->win, &w, &h); 133 SDL_GetWindowSize(d->window->win, &w, &h);
105 SDL_GetWindowPosition(d->window->win, &x, &y); 134 SDL_GetWindowPosition(d->window->win, &x, &y);
135#if defined (iPlatformLinux)
136 /* Workaround for window position being unaffected by decorations on creation. */ {
137 int bl, bt;
138 SDL_GetWindowBordersSize(d->window->win, &bt, &bl, NULL, NULL);
139 x -= bl;
140 y -= bt;
141 }
142#endif
106 appendFormat_String(str, "window.setrect width:%d height:%d coord:%d %d\n", w, h, x, y); 143 appendFormat_String(str, "window.setrect width:%d height:%d coord:%d %d\n", w, h, x, y);
107 appendFormat_String(str, "sidebar.width arg:%d\n", width_SidebarWidget(sidebar)); 144 appendFormat_String(str, "sidebar.width arg:%d\n", width_SidebarWidget(sidebar));
108 } 145 }
109 appendFormat_String(str, "retainwindow arg:%d\n", d->retainWindowSize);
110 if (isVisible_Widget(constAs_Widget(sidebar))) { 146 if (isVisible_Widget(constAs_Widget(sidebar))) {
111 appendCStr_String(str, "sidebar.toggle\n"); 147 appendCStr_String(str, "sidebar.toggle\n");
112 } 148 }
113 appendFormat_String(str, "sidebar.mode arg:%d\n", mode_SidebarWidget(sidebar)); 149 appendFormat_String(str, "sidebar.mode arg:%d\n", mode_SidebarWidget(sidebar));
114 appendFormat_String(str, "uiscale arg:%f\n", uiScale_Window(d->window)); 150 appendFormat_String(str, "uiscale arg:%f\n", uiScale_Window(d->window));
115 appendFormat_String(str, "zoom.set arg:%d\n", d->zoomPercent); 151 appendFormat_String(str, "zoom.set arg:%d\n", d->zoomPercent);
116 appendFormat_String(str, "theme.set arg:%d\n", d->theme); 152 appendFormat_String(str, "theme.set arg:%d auto:1\n", d->theme);
153 appendFormat_String(str, "ostheme arg:%d\n", d->useSystemTheme);
154 appendFormat_String(str, "proxy.gopher address:%s\n", cstr_String(&d->gopherProxy));
155 appendFormat_String(str, "proxy.http address:%s\n", cstr_String(&d->httpProxy));
117 return str; 156 return str;
118} 157}
119 158
@@ -130,7 +169,7 @@ static void loadPrefs_App_(iApp *d) {
130 iString *str = readString_File(f); 169 iString *str = readString_File(f);
131 const iRangecc src = range_String(str); 170 const iRangecc src = range_String(str);
132 iRangecc line = iNullRange; 171 iRangecc line = iNullRange;
133 while (nextSplit_Rangecc(&src, "\n", &line)) { 172 while (nextSplit_Rangecc(src, "\n", &line)) {
134 iString cmdStr; 173 iString cmdStr;
135 initRange_String(&cmdStr, line); 174 initRange_String(&cmdStr, line);
136 const char *cmd = cstr_String(&cmdStr); 175 const char *cmd = cstr_String(&cmdStr);
@@ -231,9 +270,12 @@ static void saveState_App_(const iApp *d) {
231static void init_App_(iApp *d, int argc, char **argv) { 270static void init_App_(iApp *d, int argc, char **argv) {
232 init_CommandLine(&d->args, argc, argv); 271 init_CommandLine(&d->args, argc, argv);
233 init_SortedArray(&d->tickers, sizeof(iTicker), cmp_Ticker_); 272 init_SortedArray(&d->tickers, sizeof(iTicker), cmp_Ticker_);
273 d->lastTickerTime = SDL_GetTicks();
274 d->elapsedSinceLastTicker = 0;
234 d->commandEcho = checkArgument_CommandLine(&d->args, "echo") != NULL; 275 d->commandEcho = checkArgument_CommandLine(&d->args, "echo") != NULL;
235 d->initialWindowRect = init_Rect(-1, -1, 800, 500); 276 d->initialWindowRect = init_Rect(-1, -1, 800, 500);
236 d->theme = dark_ColorTheme; 277 d->theme = dark_ColorTheme;
278 d->useSystemTheme = iTrue;
237 d->running = iFalse; 279 d->running = iFalse;
238 d->window = NULL; 280 d->window = NULL;
239 d->retainWindowSize = iTrue; 281 d->retainWindowSize = iTrue;
@@ -243,15 +285,17 @@ static void init_App_(iApp *d, int argc, char **argv) {
243 d->visited = new_Visited(); 285 d->visited = new_Visited();
244 d->bookmarks = new_Bookmarks(); 286 d->bookmarks = new_Bookmarks();
245 d->tabEnum = 0; /* generates unique IDs for tab pages */ 287 d->tabEnum = 0; /* generates unique IDs for tab pages */
288 init_String(&d->gopherProxy);
289 init_String(&d->httpProxy);
246 setThemePalette_Color(d->theme); 290 setThemePalette_Color(d->theme);
247 loadPrefs_App_(d); 291 loadPrefs_App_(d);
248 load_Visited(d->visited, dataDir_App_); 292 load_Visited(d->visited, dataDir_App_);
249 load_Bookmarks(d->bookmarks, dataDir_App_); 293 load_Bookmarks(d->bookmarks, dataDir_App_);
250#if defined (iHaveLoadEmbed) 294#if defined (iHaveLoadEmbed)
251 /* Load the resources from a file. */ { 295 /* Load the resources from a file. */ {
252 if (!load_Embed(concatPath_CStr(cstr_String(execPath_App()), "../resources.bin"))) { 296 if (!load_Embed(concatPath_CStr(cstr_String(execPath_App()), EMB_BIN))) {
253 if (!load_Embed(concatPath_CStr(cstr_String(execPath_App()), EMB_BIN))) { 297 if (!load_Embed(concatPath_CStr(cstr_String(execPath_App()), EMB_BIN2))) {
254 fprintf(stderr, "failed to load resources.bin: %s\n", strerror(errno)); 298 fprintf(stderr, "failed to load resources: %s\n", strerror(errno));
255 exit(-1); 299 exit(-1);
256 } 300 }
257 } 301 }
@@ -269,6 +313,8 @@ static void init_App_(iApp *d, int argc, char **argv) {
269static void deinit_App(iApp *d) { 313static void deinit_App(iApp *d) {
270 saveState_App_(d); 314 saveState_App_(d);
271 savePrefs_App_(d); 315 savePrefs_App_(d);
316 deinit_String(&d->httpProxy);
317 deinit_String(&d->gopherProxy);
272 save_Bookmarks(d->bookmarks, dataDir_App_); 318 save_Bookmarks(d->bookmarks, dataDir_App_);
273 delete_Bookmarks(d->bookmarks); 319 delete_Bookmarks(d->bookmarks);
274 save_Visited(d->visited, dataDir_App_); 320 save_Visited(d->visited, dataDir_App_);
@@ -288,12 +334,17 @@ const iString *dataDir_App(void) {
288 return collect_String(cleanedCStr_Path(dataDir_App_)); 334 return collect_String(cleanedCStr_Path(dataDir_App_));
289} 335}
290 336
337iLocalDef iBool isWaitingAllowed_App_(const iApp *d) {
338 return !d->pendingRefresh && isEmpty_SortedArray(&d->tickers);
339}
340
291void processEvents_App(enum iAppEventMode eventMode) { 341void processEvents_App(enum iAppEventMode eventMode) {
292 iApp *d = &app_; 342 iApp *d = &app_;
293 SDL_Event ev; 343 SDL_Event ev;
294 while ( 344 while ((isWaitingAllowed_App_(d) && eventMode == waitForNewEvents_AppEventMode &&
295 (!d->pendingRefresh && eventMode == waitForNewEvents_AppEventMode && SDL_WaitEvent(&ev)) || 345 SDL_WaitEvent(&ev)) ||
296 ((d->pendingRefresh || eventMode == postedEventsOnly_AppEventMode) && SDL_PollEvent(&ev))) { 346 ((!isWaitingAllowed_App_(d) || eventMode == postedEventsOnly_AppEventMode) &&
347 SDL_PollEvent(&ev))) {
297 switch (ev.type) { 348 switch (ev.type) {
298 case SDL_QUIT: 349 case SDL_QUIT:
299 d->running = iFalse; 350 d->running = iFalse;
@@ -325,12 +376,17 @@ backToMainLoop:;
325} 376}
326 377
327static void runTickers_App_(iApp *d) { 378static void runTickers_App_(iApp *d) {
379 const uint32_t now = SDL_GetTicks();
380 d->elapsedSinceLastTicker = (d->lastTickerTime ? now - d->lastTickerTime : 0);
381 d->lastTickerTime = now;
382 if (isEmpty_SortedArray(&d->tickers)) {
383 d->lastTickerTime = 0;
384 return;
385 }
328 /* Tickers may add themselves again, so we'll run off a copy. */ 386 /* Tickers may add themselves again, so we'll run off a copy. */
329 iSortedArray *pending = copy_SortedArray(&d->tickers); 387 iSortedArray *pending = copy_SortedArray(&d->tickers);
330 clear_SortedArray(&d->tickers); 388 clear_SortedArray(&d->tickers);
331 if (!isEmpty_SortedArray(pending)) { 389 postRefresh_App();
332 postRefresh_App();
333 }
334 iConstForEach(Array, i, &pending->values) { 390 iConstForEach(Array, i, &pending->values) {
335 const iTicker *ticker = i.value; 391 const iTicker *ticker = i.value;
336 if (ticker->callback) { 392 if (ticker->callback) {
@@ -338,6 +394,9 @@ static void runTickers_App_(iApp *d) {
338 } 394 }
339 } 395 }
340 delete_SortedArray(pending); 396 delete_SortedArray(pending);
397 if (isEmpty_SortedArray(&d->tickers)) {
398 d->lastTickerTime = 0;
399 }
341} 400}
342 401
343static int run_App_(iApp *d) { 402static int run_App_(iApp *d) {
@@ -345,8 +404,8 @@ static int run_App_(iApp *d) {
345 d->running = iTrue; 404 d->running = iTrue;
346 SDL_EventState(SDL_DROPFILE, SDL_ENABLE); /* open files via drag'n'drop */ 405 SDL_EventState(SDL_DROPFILE, SDL_ENABLE); /* open files via drag'n'drop */
347 while (d->running) { 406 while (d->running) {
348 runTickers_App_(d);
349 processEvents_App(waitForNewEvents_AppEventMode); 407 processEvents_App(waitForNewEvents_AppEventMode);
408 runTickers_App_(d);
350 refresh_App(); 409 refresh_App();
351 recycle_Garbage(); 410 recycle_Garbage();
352 } 411 }
@@ -364,6 +423,10 @@ iBool isRefreshPending_App(void) {
364 return app_.pendingRefresh; 423 return app_.pendingRefresh;
365} 424}
366 425
426uint32_t elapsedSinceLastTicker_App(void) {
427 return app_.elapsedSinceLastTicker;
428}
429
367int zoom_App(void) { 430int zoom_App(void) {
368 return app_.zoomPercent; 431 return app_.zoomPercent;
369} 432}
@@ -372,6 +435,17 @@ enum iColorTheme colorTheme_App(void) {
372 return app_.theme; 435 return app_.theme;
373} 436}
374 437
438const iString *schemeProxy_App(iRangecc scheme) {
439 iApp *d = &app_;
440 if (equalCase_Rangecc(scheme, "gopher")) {
441 return &d->gopherProxy;
442 }
443 if (equalCase_Rangecc(scheme, "http") || equalCase_Rangecc(scheme, "https")) {
444 return &d->httpProxy;
445 }
446 return NULL;
447}
448
375int run_App(int argc, char **argv) { 449int run_App(int argc, char **argv) {
376 init_App_(&app_, argc, argv); 450 init_App_(&app_, argc, argv);
377 const int rc = run_App_(&app_); 451 const int rc = run_App_(&app_);
@@ -394,7 +468,12 @@ void postRefresh_App(void) {
394} 468}
395 469
396void postCommand_App(const char *command) { 470void postCommand_App(const char *command) {
471 iAssert(command);
397 SDL_Event ev; 472 SDL_Event ev;
473 if (*command == '!') {
474 /* Global command; this is global context so just ignore. */
475 command++;
476 }
398 ev.user.type = SDL_USEREVENT; 477 ev.user.type = SDL_USEREVENT;
399 ev.user.code = command_UserEventCode; 478 ev.user.code = command_UserEventCode;
400 ev.user.windowID = get_Window() ? SDL_GetWindowID(get_Window()->win) : 0; 479 ev.user.windowID = get_Window() ? SDL_GetWindowID(get_Window()->win) : 0;
@@ -424,6 +503,7 @@ iAny *findWidget_App(const char *id) {
424void addTicker_App(void (*ticker)(iAny *), iAny *context) { 503void addTicker_App(void (*ticker)(iAny *), iAny *context) {
425 iApp *d = &app_; 504 iApp *d = &app_;
426 insert_SortedArray(&d->tickers, &(iTicker){ context, ticker }); 505 insert_SortedArray(&d->tickers, &(iTicker){ context, ticker });
506 postRefresh_App();
427} 507}
428 508
429iGmCerts *certs_App(void) { 509iGmCerts *certs_App(void) {
@@ -450,13 +530,25 @@ static iBool handlePrefsCommands_(iWidget *d, const char *cmd) {
450 if (equal_Command(cmd, "prefs.dismiss") || equal_Command(cmd, "preferences")) { 530 if (equal_Command(cmd, "prefs.dismiss") || equal_Command(cmd, "preferences")) {
451 setUiScale_Window(get_Window(), 531 setUiScale_Window(get_Window(),
452 toFloat_String(text_InputWidget(findChild_Widget(d, "prefs.uiscale")))); 532 toFloat_String(text_InputWidget(findChild_Widget(d, "prefs.uiscale"))));
453 postCommandf_App("retainwindow arg:%d", 533 postCommandf_App("window.retain arg:%d",
454 isSelected_Widget(findChild_Widget(d, "prefs.retainwindow"))); 534 isSelected_Widget(findChild_Widget(d, "prefs.retainwindow")));
535 postCommandf_App("ostheme arg:%d",
536 isSelected_Widget(findChild_Widget(d, "prefs.ostheme")));
537 postCommandf_App("proxy.http address:%s",
538 cstr_String(text_InputWidget(findChild_Widget(d, "prefs.proxy.http"))));
539 postCommandf_App("proxy.gopher address:%s",
540 cstr_String(text_InputWidget(findChild_Widget(d, "prefs.proxy.gopher"))));
455 destroy_Widget(d); 541 destroy_Widget(d);
456 return iTrue; 542 return iTrue;
457 } 543 }
458 else if (equal_Command(cmd, "theme.changed")) { 544 else if (equal_Command(cmd, "prefs.ostheme.changed")) {
545 postCommandf_App("ostheme arg:%d", arg_Command(cmd));
546 }
547 else if (equal_Command(cmd, "theme.changed")) {
459 updatePrefsThemeButtons_(d); 548 updatePrefsThemeButtons_(d);
549 if (!argLabel_Command(cmd, "auto")) {
550 setToggle_Widget(findChild_Widget(d, "prefs.ostheme"), iFalse);
551 }
460 } 552 }
461 return iFalse; 553 return iFalse;
462} 554}
@@ -502,18 +594,81 @@ iDocumentWidget *newTab_App(const iDocumentWidget *duplicateOf) {
502 return doc; 594 return doc;
503} 595}
504 596
597static iBool handleIdentityCreationCommands_(iWidget *dlg, const char *cmd) {
598 iApp *d = &app_;
599 if (equal_Command(cmd, "ident.accept") || equal_Command(cmd, "cancel")) {
600 if (equal_Command(cmd, "ident.accept")) {
601 const iString *commonName = text_InputWidget (findChild_Widget(dlg, "ident.common"));
602 const iString *email = text_InputWidget (findChild_Widget(dlg, "ident.email"));
603 const iString *userId = text_InputWidget (findChild_Widget(dlg, "ident.userid"));
604 const iString *domain = text_InputWidget (findChild_Widget(dlg, "ident.domain"));
605 const iString *organization = text_InputWidget (findChild_Widget(dlg, "ident.org"));
606 const iString *country = text_InputWidget (findChild_Widget(dlg, "ident.country"));
607 const iBool isTemp = isSelected_Widget(findChild_Widget(dlg, "ident.temp"));
608 if (isEmpty_String(commonName)) {
609 makeMessage_Widget(orange_ColorEscape "MISSING INFO",
610 "A \"Common name\" must be specified.");
611 return iTrue;
612 }
613 iDate until;
614 /* Validate the date. */ {
615 iZap(until);
616 unsigned int val[6];
617 iDate today;
618 initCurrent_Date(&today);
619 const int n =
620 sscanf(cstr_String(text_InputWidget(findChild_Widget(dlg, "ident.until"))),
621 "%04u-%u-%u %u:%u:%u",
622 &val[0], &val[1], &val[2], &val[3], &val[4], &val[5]);
623 if (n <= 0 || val[0] < (unsigned) today.year) {
624 makeMessage_Widget(orange_ColorEscape "INVALID DATE",
625 "Please check the \"Valid until\" date. Examples:\n"
626 "\u2022 2030\n"
627 "\u2022 2025-06-30\n"
628 "\u2022 2021-12-31 23:59:59");
629 return iTrue;
630 }
631 until.year = val[0];
632 until.month = n >= 2 ? val[1] : 1;
633 until.day = n >= 3 ? val[2] : 1;
634 until.hour = n >= 4 ? val[3] : 0;
635 until.minute = n >= 5 ? val[4] : 0;
636 until.second = n == 6 ? val[5] : 0;
637 /* In the past? */ {
638 iTime now, t;
639 initCurrent_Time(&now);
640 init_Time(&t, &until);
641 if (cmp_Time(&t, &now) <= 0) {
642 makeMessage_Widget(orange_ColorEscape "INVALID DATE",
643 "Expiration date must be in the future.");
644 return iTrue;
645 }
646 }
647 }
648 /* The input seems fine. */
649 newIdentity_GmCerts(d->certs, isTemp ? temporary_GmIdentityFlag : 0,
650 until, commonName, email, userId, domain, organization, country);
651 postCommandf_App("sidebar.mode arg:%d show:1", identities_SidebarMode);
652 postCommand_App("idents.changed");
653 }
654 destroy_Widget(dlg);
655 return iTrue;
656 }
657 return iFalse;
658}
659
505iBool handleCommand_App(const char *cmd) { 660iBool handleCommand_App(const char *cmd) {
506 iApp *d = &app_; 661 iApp *d = &app_;
507 if (equal_Command(cmd, "retainwindow")) { 662 if (equal_Command(cmd, "window.retain")) {
508 d->retainWindowSize = arg_Command(cmd); 663 d->retainWindowSize = arg_Command(cmd);
509 return iTrue; 664 return iTrue;
510 } 665 }
511 else if (equal_Command(cmd, "open")) { 666 else if (equal_Command(cmd, "open")) {
512 const iString *url = collect_String(newCStr_String(suffixPtr_Command(cmd, "url"))); 667 const iString *url = collectNewCStr_String(suffixPtr_Command(cmd, "url"));
513 iUrl parts; 668 iUrl parts;
514 init_Url(&parts, url); 669 init_Url(&parts, url);
515 if (equalCase_Rangecc(&parts.protocol, "http") || 670 if (isEmpty_String(&d->httpProxy) &&
516 equalCase_Rangecc(&parts.protocol, "https")) { 671 (equalCase_Rangecc(parts.scheme, "http") || equalCase_Rangecc(parts.scheme, "https"))) {
517 openInDefaultBrowser_App(url); 672 openInDefaultBrowser_App(url);
518 return iTrue; 673 return iTrue;
519 } 674 }
@@ -523,8 +678,9 @@ iBool handleCommand_App(const char *cmd) {
523 } 678 }
524 iHistory *history = history_DocumentWidget(doc); 679 iHistory *history = history_DocumentWidget(doc);
525 const iBool isHistory = argLabel_Command(cmd, "history") != 0; 680 const iBool isHistory = argLabel_Command(cmd, "history") != 0;
681 int redirectCount = argLabel_Command(cmd, "redirect");
526 if (!isHistory) { 682 if (!isHistory) {
527 if (argLabel_Command(cmd, "redirect")) { 683 if (redirectCount) {
528 replace_History(history, url); 684 replace_History(history, url);
529 } 685 }
530 else { 686 else {
@@ -533,6 +689,7 @@ iBool handleCommand_App(const char *cmd) {
533 } 689 }
534 visitUrl_Visited(d->visited, url); 690 visitUrl_Visited(d->visited, url);
535 setInitialScroll_DocumentWidget(doc, argfLabel_Command(cmd, "scroll")); 691 setInitialScroll_DocumentWidget(doc, argfLabel_Command(cmd, "scroll"));
692 setRedirectCount_DocumentWidget(doc, redirectCount);
536 setUrlFromCache_DocumentWidget(doc, url, isHistory); 693 setUrlFromCache_DocumentWidget(doc, url, isHistory);
537 } 694 }
538 else if (equal_Command(cmd, "document.request.cancelled")) { 695 else if (equal_Command(cmd, "document.request.cancelled")) {
@@ -601,9 +758,14 @@ iBool handleCommand_App(const char *cmd) {
601 else if (equal_Command(cmd, "preferences")) { 758 else if (equal_Command(cmd, "preferences")) {
602 iWidget *dlg = makePreferences_Widget(); 759 iWidget *dlg = makePreferences_Widget();
603 updatePrefsThemeButtons_(dlg); 760 updatePrefsThemeButtons_(dlg);
761 setToggle_Widget(findChild_Widget(dlg, "prefs.ostheme"), d->useSystemTheme);
604 setToggle_Widget(findChild_Widget(dlg, "prefs.retainwindow"), d->retainWindowSize); 762 setToggle_Widget(findChild_Widget(dlg, "prefs.retainwindow"), d->retainWindowSize);
605 setText_InputWidget(findChild_Widget(dlg, "prefs.uiscale"), 763 setText_InputWidget(findChild_Widget(dlg, "prefs.uiscale"),
606 collectNewFormat_String("%g", uiScale_Window(d->window))); 764 collectNewFormat_String("%g", uiScale_Window(d->window)));
765 setText_InputWidget(findChild_Widget(dlg, "prefs.proxy.http"),
766 schemeProxy_App(range_CStr("http")));
767 setText_InputWidget(findChild_Widget(dlg, "prefs.proxy.gopher"),
768 schemeProxy_App(range_CStr("gopher")));
607 setCommandHandler_Widget(dlg, handlePrefsCommands_); 769 setCommandHandler_Widget(dlg, handlePrefsCommands_);
608 } 770 }
609 else if (equal_Command(cmd, "navigate.home")) { 771 else if (equal_Command(cmd, "navigate.home")) {
@@ -633,17 +795,46 @@ iBool handleCommand_App(const char *cmd) {
633 } 795 }
634 else if (equal_Command(cmd, "bookmark.add")) { 796 else if (equal_Command(cmd, "bookmark.add")) {
635 iDocumentWidget *doc = document_App(); 797 iDocumentWidget *doc = document_App();
636 add_Bookmarks(d->bookmarks, url_DocumentWidget(doc), 798 makeBookmarkCreation_Widget(url_DocumentWidget(doc),
637 bookmarkTitle_DocumentWidget(doc), 799 bookmarkTitle_DocumentWidget(doc),
638 collectNew_String(), 800 siteIcon_GmDocument(document_DocumentWidget(doc)));
639 siteIcon_GmDocument(document_DocumentWidget(doc))); 801 return iTrue;
640 postCommand_App("bookmarks.changed"); 802 }
803 else if (equal_Command(cmd, "ident.new")) {
804 iWidget *dlg = makeIdentityCreation_Widget();
805 setCommandHandler_Widget(dlg, handleIdentityCreationCommands_);
641 return iTrue; 806 return iTrue;
642 } 807 }
643 else if (equal_Command(cmd, "theme.set")) { 808 else if (equal_Command(cmd, "theme.set")) {
809 const int isAuto = argLabel_Command(cmd, "auto");
644 d->theme = arg_Command(cmd); 810 d->theme = arg_Command(cmd);
811 if (!isAuto) {
812 postCommand_App("ostheme arg:0");
813 }
645 setThemePalette_Color(d->theme); 814 setThemePalette_Color(d->theme);
646 postCommand_App("theme.changed"); 815 postCommandf_App("theme.changed auto:%d", isAuto);
816 return iTrue;
817 }
818 else if (equal_Command(cmd, "ostheme")) {
819 d->useSystemTheme = arg_Command(cmd);
820 return iTrue;
821 }
822 else if (equal_Command(cmd, "os.theme.changed")) {
823 if (d->useSystemTheme) {
824 const int dark = argLabel_Command(cmd, "dark");
825 const int contrast = argLabel_Command(cmd, "contrast");
826 postCommandf_App("theme.set arg:%d auto:1",
827 dark ? (contrast ? pureBlack_ColorTheme : dark_ColorTheme)
828 : (contrast ? pureWhite_ColorTheme : light_ColorTheme));
829 }
830 return iFalse;
831 }
832 else if (equal_Command(cmd, "proxy.gopher")) {
833 setCStr_String(&d->gopherProxy, suffixPtr_Command(cmd, "address"));
834 return iTrue;
835 }
836 else if (equal_Command(cmd, "proxy.http")) {
837 setCStr_String(&d->httpProxy, suffixPtr_Command(cmd, "address"));
647 return iTrue; 838 return iTrue;
648 } 839 }
649 else { 840 else {
@@ -666,3 +857,44 @@ void openInDefaultBrowser_App(const iString *url) {
666 start_Process(proc); 857 start_Process(proc);
667 iRelease(proc); 858 iRelease(proc);
668} 859}
860
861void revealPath_App(const iString *path) {
862#if defined (iPlatformApple)
863 const char *scriptPath = concatPath_CStr(dataDir_App_, "revealfile.scpt");
864 iFile *f = newCStr_File(scriptPath);
865 if (open_File(f, writeOnly_FileMode | text_FileMode)) {
866 /* AppleScript to select a specific file. */
867 write_File(f, collect_Block(newCStr_Block("on run argv\n"
868 " tell application \"Finder\"\n"
869 " activate\n"
870 " reveal POSIX file (item 1 of argv) as text\n"
871 " end tell\n"
872 "end run\n")));
873 close_File(f);
874 iProcess *proc = new_Process();
875 setArguments_Process(
876 proc,
877 iClob(newStringsCStr_StringList(
878 "/usr/bin/osascript", scriptPath, cstr_String(path), NULL)));
879 start_Process(proc);
880 iRelease(proc);
881 }
882 iRelease(f);
883#elif defined (iPlatformLinux)
884 iFileInfo *inf = iClob(new_FileInfo(path));
885 iRangecc target;
886 if (isDirectory_FileInfo(inf)) {
887 target = range_String(path);
888 }
889 else {
890 target = dirName_Path(path);
891 }
892 iProcess *proc = new_Process();
893 setArguments_Process(
894 proc, iClob(newStringsCStr_StringList("/usr/bin/xdg-open", cstr_Rangecc(target), NULL)));
895 start_Process(proc);
896 iRelease(proc);
897#else
898 iAssert(0 /* File revealing not implemented on this platform */);
899#endif
900}