diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-09-02 11:25:03 +0300 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-09-02 11:25:03 +0300 |
commit | 1e548eac8c63922315f89c96436b622615c488c7 (patch) | |
tree | d2c36fdaaa0bcc42cbc34bb804de5a7201dba2b1 /src/app.c | |
parent | 8ed2faadc4ca2497fc3b2e9d2eae6d40921de918 (diff) | |
parent | e2b5ea14d25dbbb62a1e827803e67c30df79c6a1 (diff) |
Merge branch 'master' of skyjake.fi:skyjake/lagrange
Diffstat (limited to 'src/app.c')
-rw-r--r-- | src/app.c | 296 |
1 files changed, 264 insertions, 32 deletions
@@ -1,3 +1,25 @@ | |||
1 | /* Copyright 2020 Jaakko Keränen <jaakko.keranen@iki.fi> | ||
2 | |||
3 | Redistribution and use in source and binary forms, with or without | ||
4 | modification, are permitted provided that the following conditions are met: | ||
5 | |||
6 | 1. Redistributions of source code must retain the above copyright notice, this | ||
7 | list of conditions and the following disclaimer. | ||
8 | 2. 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 | |||
12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | ||
13 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
14 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
15 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR | ||
16 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
17 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
18 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON | ||
19 | ANY 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 | ||
21 | SOFTWARE, 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 | ||
38 | iDeclareType(App) | 61 | iDeclareType(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" |
42 | static const char *dataDir_App_ = "~/Library/Application Support/fi.skyjake.Lagrange"; | 65 | static 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" |
46 | static const char *dataDir_App_ = "~/AppData/Roaming/fi.skyjake.Lagrange"; | 69 | static 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 */ | ||
51 | static const char *dataDir_App_ = "~/.config/lagrange"; | 73 | static const char *dataDir_App_ = "~/.config/lagrange"; |
52 | #endif | 74 | #endif |
75 | #define EMB_BIN2 "../resources.binary" /* fallback from build/executable dir */ | ||
53 | static const char *prefsFileName_App_ = "prefs.cfg"; | 76 | static const char *prefsFileName_App_ = "prefs.cfg"; |
54 | static const char *stateFileName_App_ = "state.bin"; | 77 | static const char *stateFileName_App_ = "state.binary"; |
55 | 78 | ||
56 | struct Impl_App { | 79 | struct 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 | ||
75 | static iApp app_; | 103 | static iApp app_; |
@@ -99,21 +127,32 @@ const iString *dateStr_(const iDate *date) { | |||
99 | static iString *serializePrefs_App_(const iApp *d) { | 127 | static 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) { | |||
231 | static void init_App_(iApp *d, int argc, char **argv) { | 270 | static 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) { | |||
269 | static void deinit_App(iApp *d) { | 313 | static 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 | ||
337 | iLocalDef iBool isWaitingAllowed_App_(const iApp *d) { | ||
338 | return !d->pendingRefresh && isEmpty_SortedArray(&d->tickers); | ||
339 | } | ||
340 | |||
291 | void processEvents_App(enum iAppEventMode eventMode) { | 341 | void 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 | ||
327 | static void runTickers_App_(iApp *d) { | 378 | static 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 | ||
343 | static int run_App_(iApp *d) { | 402 | static 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 | ||
426 | uint32_t elapsedSinceLastTicker_App(void) { | ||
427 | return app_.elapsedSinceLastTicker; | ||
428 | } | ||
429 | |||
367 | int zoom_App(void) { | 430 | int 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 | ||
438 | const 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 | |||
375 | int run_App(int argc, char **argv) { | 449 | int 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 | ||
396 | void postCommand_App(const char *command) { | 470 | void 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) { | |||
424 | void addTicker_App(void (*ticker)(iAny *), iAny *context) { | 503 | void 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 | ||
429 | iGmCerts *certs_App(void) { | 509 | iGmCerts *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 | ||
597 | static 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 | |||
505 | iBool handleCommand_App(const char *cmd) { | 660 | iBool 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 | |||
861 | void 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 | } | ||