diff options
-rw-r--r-- | CMakeLists.txt | 4 | ||||
-rw-r--r-- | res/about/version.gmi | 1 | ||||
-rw-r--r-- | src/app.c | 79 | ||||
-rw-r--r-- | src/app.h | 1 |
4 files changed, 76 insertions, 9 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 82d55590..0edfbed2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt | |||
@@ -30,6 +30,7 @@ option (ENABLE_X11_SWRENDER "Use software rendering under X11" OFF) | |||
30 | option (ENABLE_KERNING "Enable kerning in font renderer (slower)" ON) | 30 | option (ENABLE_KERNING "Enable kerning in font renderer (slower)" ON) |
31 | option (ENABLE_RESOURCE_EMBED "Embed resources inside the executable" OFF) | 31 | option (ENABLE_RESOURCE_EMBED "Embed resources inside the executable" OFF) |
32 | option (ENABLE_WINDOWPOS_FIX "Set position after showing window (workaround for SDL bug)" OFF) | 32 | option (ENABLE_WINDOWPOS_FIX "Set position after showing window (workaround for SDL bug)" OFF) |
33 | option (ENABLE_IDLE_SLEEP "While idle, sleep in the main thread instead of waiting for events" ON) | ||
33 | 34 | ||
34 | include (BuildType.cmake) | 35 | include (BuildType.cmake) |
35 | include (res/Embed.cmake) | 36 | include (res/Embed.cmake) |
@@ -219,6 +220,9 @@ if (ENABLE_MPG123 AND MPG123_FOUND) | |||
219 | target_compile_definitions (app PUBLIC LAGRANGE_ENABLE_MPG123=1) | 220 | target_compile_definitions (app PUBLIC LAGRANGE_ENABLE_MPG123=1) |
220 | target_link_libraries (app PUBLIC PkgConfig::MPG123) | 221 | target_link_libraries (app PUBLIC PkgConfig::MPG123) |
221 | endif () | 222 | endif () |
223 | if (ENABLE_IDLE_SLEEP) | ||
224 | target_compile_definitions (app PUBLIC LAGRANGE_IDLE_SLEEP=1) | ||
225 | endif () | ||
222 | target_link_libraries (app PUBLIC the_Foundation::the_Foundation) | 226 | target_link_libraries (app PUBLIC the_Foundation::the_Foundation) |
223 | target_link_libraries (app PUBLIC ${SDL2_LDFLAGS}) | 227 | target_link_libraries (app PUBLIC ${SDL2_LDFLAGS}) |
224 | if (APPLE) | 228 | if (APPLE) |
diff --git a/res/about/version.gmi b/res/about/version.gmi index af758486..5944932f 100644 --- a/res/about/version.gmi +++ b/res/about/version.gmi | |||
@@ -10,6 +10,7 @@ | |||
10 | * 'text/*' content falls back to plain text. | 10 | * 'text/*' content falls back to plain text. |
11 | * Reduced visual artifacts in Unicode box-drawing characters (overlapping/gaps). | 11 | * Reduced visual artifacts in Unicode box-drawing characters (overlapping/gaps). |
12 | * Fixed truncated tab titles when opening tabs in background. | 12 | * Fixed truncated tab titles when opening tabs in background. |
13 | * macOS: Fixed excessive CPU usage while idling. | ||
13 | 14 | ||
14 | ## 0.12 | 15 | ## 0.12 |
15 | * Added MIME hooks: pipe Gemini responses through external programs for arbitrary processing. (See "about:help" for usage.) | 16 | * Added MIME hooks: pipe Gemini responses through external programs for arbitrary processing. (See "about:help" for usage.) |
@@ -89,6 +89,8 @@ static const char *prefsFileName_App_ = "prefs.cfg"; | |||
89 | static const char *stateFileName_App_ = "state.binary"; | 89 | static const char *stateFileName_App_ = "state.binary"; |
90 | static const char *downloadDir_App_ = "~/Downloads"; | 90 | static const char *downloadDir_App_ = "~/Downloads"; |
91 | 91 | ||
92 | static const int idleThreshold_App_ = 1000; /* ms */ | ||
93 | |||
92 | struct Impl_App { | 94 | struct Impl_App { |
93 | iCommandLine args; | 95 | iCommandLine args; |
94 | iString * execPath; | 96 | iString * execPath; |
@@ -100,7 +102,12 @@ struct Impl_App { | |||
100 | iSortedArray tickers; | 102 | iSortedArray tickers; |
101 | uint32_t lastTickerTime; | 103 | uint32_t lastTickerTime; |
102 | uint32_t elapsedSinceLastTicker; | 104 | uint32_t elapsedSinceLastTicker; |
103 | iBool running; | 105 | iBool isRunning; |
106 | #if defined (LAGRANGE_IDLE_SLEEP) | ||
107 | iBool isIdling; | ||
108 | uint32_t lastEventTime; | ||
109 | int sleepTimer; | ||
110 | #endif | ||
104 | iAtomicInt pendingRefresh; | 111 | iAtomicInt pendingRefresh; |
105 | int tabEnum; | 112 | int tabEnum; |
106 | iStringList *launchCommands; | 113 | iStringList *launchCommands; |
@@ -320,6 +327,16 @@ static void saveState_App_(const iApp *d) { | |||
320 | iRelease(f); | 327 | iRelease(f); |
321 | } | 328 | } |
322 | 329 | ||
330 | #if defined (LAGRANGE_IDLE_SLEEP) | ||
331 | static uint32_t checkAsleep_App_(uint32_t interval, void *param) { | ||
332 | iApp *d = param; | ||
333 | SDL_Event ev = { .type = SDL_USEREVENT }; | ||
334 | ev.user.code = asleep_UserEventCode; | ||
335 | SDL_PushEvent(&ev); | ||
336 | return interval; | ||
337 | } | ||
338 | #endif | ||
339 | |||
323 | static void init_App_(iApp *d, int argc, char **argv) { | 340 | static void init_App_(iApp *d, int argc, char **argv) { |
324 | const iBool isFirstRun = !fileExistsCStr_FileInfo(cleanedPath_CStr(dataDir_App_)); | 341 | const iBool isFirstRun = !fileExistsCStr_FileInfo(cleanedPath_CStr(dataDir_App_)); |
325 | d->isFinishedLaunching = iFalse; | 342 | d->isFinishedLaunching = iFalse; |
@@ -349,7 +366,7 @@ static void init_App_(iApp *d, int argc, char **argv) { | |||
349 | #endif | 366 | #endif |
350 | init_Prefs(&d->prefs); | 367 | init_Prefs(&d->prefs); |
351 | setCStr_String(&d->prefs.downloadDir, downloadDir_App_); | 368 | setCStr_String(&d->prefs.downloadDir, downloadDir_App_); |
352 | d->running = iFalse; | 369 | d->isRunning = iFalse; |
353 | d->window = NULL; | 370 | d->window = NULL; |
354 | set_Atomic(&d->pendingRefresh, iFalse); | 371 | set_Atomic(&d->pendingRefresh, iFalse); |
355 | d->mimehooks = new_MimeHooks(); | 372 | d->mimehooks = new_MimeHooks(); |
@@ -358,6 +375,11 @@ static void init_App_(iApp *d, int argc, char **argv) { | |||
358 | d->bookmarks = new_Bookmarks(); | 375 | d->bookmarks = new_Bookmarks(); |
359 | d->tabEnum = 0; /* generates unique IDs for tab pages */ | 376 | d->tabEnum = 0; /* generates unique IDs for tab pages */ |
360 | setThemePalette_Color(d->prefs.theme); | 377 | setThemePalette_Color(d->prefs.theme); |
378 | #if defined (LAGRANGE_IDLE_SLEEP) | ||
379 | d->isIdling = iFalse; | ||
380 | d->lastEventTime = 0; | ||
381 | d->sleepTimer = SDL_AddTimer(1000, checkAsleep_App_, d); | ||
382 | #endif | ||
361 | #if defined (iPlatformApple) | 383 | #if defined (iPlatformApple) |
362 | setupApplication_MacOS(); | 384 | setupApplication_MacOS(); |
363 | #endif | 385 | #endif |
@@ -484,19 +506,25 @@ const iString *debugInfo_App(void) { | |||
484 | } | 506 | } |
485 | 507 | ||
486 | iLocalDef iBool isWaitingAllowed_App_(iApp *d) { | 508 | iLocalDef iBool isWaitingAllowed_App_(iApp *d) { |
509 | #if defined (LAGRANGE_IDLE_SLEEP) | ||
510 | if (d->isIdling) { | ||
511 | return iFalse; | ||
512 | } | ||
513 | #endif | ||
487 | return !value_Atomic(&d->pendingRefresh) && isEmpty_SortedArray(&d->tickers); | 514 | return !value_Atomic(&d->pendingRefresh) && isEmpty_SortedArray(&d->tickers); |
488 | } | 515 | } |
489 | 516 | ||
490 | void processEvents_App(enum iAppEventMode eventMode) { | 517 | void processEvents_App(enum iAppEventMode eventMode) { |
491 | iApp *d = &app_; | 518 | iApp *d = &app_; |
492 | SDL_Event ev; | 519 | SDL_Event ev; |
520 | iBool gotEvents = iFalse; | ||
493 | while ((isWaitingAllowed_App_(d) && eventMode == waitForNewEvents_AppEventMode && | 521 | while ((isWaitingAllowed_App_(d) && eventMode == waitForNewEvents_AppEventMode && |
494 | SDL_WaitEvent(&ev)) || | 522 | SDL_WaitEvent(&ev)) || |
495 | ((!isWaitingAllowed_App_(d) || eventMode == postedEventsOnly_AppEventMode) && | 523 | ((!isWaitingAllowed_App_(d) || eventMode == postedEventsOnly_AppEventMode) && |
496 | SDL_PollEvent(&ev))) { | 524 | SDL_PollEvent(&ev))) { |
497 | switch (ev.type) { | 525 | switch (ev.type) { |
498 | case SDL_QUIT: | 526 | case SDL_QUIT: |
499 | d->running = iFalse; | 527 | d->isRunning = iFalse; |
500 | goto backToMainLoop; | 528 | goto backToMainLoop; |
501 | case SDL_DROPFILE: { | 529 | case SDL_DROPFILE: { |
502 | iBool newTab = iFalse; | 530 | iBool newTab = iFalse; |
@@ -516,6 +544,25 @@ void processEvents_App(enum iAppEventMode eventMode) { | |||
516 | break; | 544 | break; |
517 | } | 545 | } |
518 | default: { | 546 | default: { |
547 | #if defined (LAGRANGE_IDLE_SLEEP) | ||
548 | if (ev.type == SDL_USEREVENT && ev.user.code == asleep_UserEventCode) { | ||
549 | if (SDL_GetTicks() - d->lastEventTime > idleThreshold_App_) { | ||
550 | if (!d->isIdling) { | ||
551 | printf("[App] idling...\n"); | ||
552 | fflush(stdout); | ||
553 | } | ||
554 | d->isIdling = iTrue; | ||
555 | } | ||
556 | continue; | ||
557 | } | ||
558 | d->lastEventTime = SDL_GetTicks(); | ||
559 | if (d->isIdling) { | ||
560 | printf("[App] ...woke up\n"); | ||
561 | fflush(stdout); | ||
562 | } | ||
563 | d->isIdling = iFalse; | ||
564 | #endif | ||
565 | gotEvents = iTrue; | ||
519 | iBool wasUsed = processEvent_Window(d->window, &ev); | 566 | iBool wasUsed = processEvent_Window(d->window, &ev); |
520 | if (!wasUsed) { | 567 | if (!wasUsed) { |
521 | /* There may be a key bindings for this. */ | 568 | /* There may be a key bindings for this. */ |
@@ -539,6 +586,14 @@ void processEvents_App(enum iAppEventMode eventMode) { | |||
539 | } | 586 | } |
540 | } | 587 | } |
541 | } | 588 | } |
589 | #if defined (LAGRANGE_IDLE_SLEEP) | ||
590 | if (d->isIdling && !gotEvents) { | ||
591 | /* This is where we spend most of our time when idle. 60 Hz still quite a lot but we | ||
592 | can't wait too long after the user tries to interact again with the app. In any | ||
593 | case, on macOS SDL_WaitEvent() seems to use 10x more CPU time than sleeping. */ | ||
594 | SDL_Delay(1000 / 60); | ||
595 | } | ||
596 | #endif | ||
542 | backToMainLoop:; | 597 | backToMainLoop:; |
543 | } | 598 | } |
544 | 599 | ||
@@ -586,10 +641,10 @@ static int resizeWatcher_(void *user, SDL_Event *event) { | |||
586 | 641 | ||
587 | static int run_App_(iApp *d) { | 642 | static int run_App_(iApp *d) { |
588 | arrange_Widget(findWidget_App("root")); | 643 | arrange_Widget(findWidget_App("root")); |
589 | d->running = iTrue; | 644 | d->isRunning = iTrue; |
590 | SDL_EventState(SDL_DROPFILE, SDL_ENABLE); /* open files via drag'n'drop */ | 645 | SDL_EventState(SDL_DROPFILE, SDL_ENABLE); /* open files via drag'n'drop */ |
591 | SDL_AddEventWatch(resizeWatcher_, d); | 646 | SDL_AddEventWatch(resizeWatcher_, d); |
592 | while (d->running) { | 647 | while (d->isRunning) { |
593 | processEvents_App(waitForNewEvents_AppEventMode); | 648 | processEvents_App(waitForNewEvents_AppEventMode); |
594 | runTickers_App_(d); | 649 | runTickers_App_(d); |
595 | refresh_App(); | 650 | refresh_App(); |
@@ -600,6 +655,9 @@ static int run_App_(iApp *d) { | |||
600 | 655 | ||
601 | void refresh_App(void) { | 656 | void refresh_App(void) { |
602 | iApp *d = &app_; | 657 | iApp *d = &app_; |
658 | #if defined (LAGRANGE_IDLE_SLEEP) | ||
659 | if (d->isIdling) return; | ||
660 | #endif | ||
603 | destroyPending_Widget(); | 661 | destroyPending_Widget(); |
604 | draw_Window(d->window); | 662 | draw_Window(d->window); |
605 | set_Atomic(&d->pendingRefresh, iFalse); | 663 | set_Atomic(&d->pendingRefresh, iFalse); |
@@ -657,6 +715,9 @@ int run_App(int argc, char **argv) { | |||
657 | 715 | ||
658 | void postRefresh_App(void) { | 716 | void postRefresh_App(void) { |
659 | iApp *d = &app_; | 717 | iApp *d = &app_; |
718 | #if defined (LAGRANGE_IDLE_SLEEP) | ||
719 | d->isIdling = iFalse; | ||
720 | #endif | ||
660 | const iBool wasPending = exchange_Atomic(&d->pendingRefresh, iTrue); | 721 | const iBool wasPending = exchange_Atomic(&d->pendingRefresh, iTrue); |
661 | if (!wasPending) { | 722 | if (!wasPending) { |
662 | SDL_Event ev; | 723 | SDL_Event ev; |
@@ -1085,12 +1146,12 @@ iBool handleCommand_App(const char *cmd) { | |||
1085 | } | 1146 | } |
1086 | else if (equal_Command(cmd, "prefs.sideicon.changed")) { | 1147 | else if (equal_Command(cmd, "prefs.sideicon.changed")) { |
1087 | d->prefs.sideIcon = arg_Command(cmd) != 0; | 1148 | d->prefs.sideIcon = arg_Command(cmd) != 0; |
1088 | refresh_App(); | 1149 | postRefresh_App(); |
1089 | return iTrue; | 1150 | return iTrue; |
1090 | } | 1151 | } |
1091 | else if (equal_Command(cmd, "prefs.hoveroutline.changed")) { | 1152 | else if (equal_Command(cmd, "prefs.hoveroutline.changed")) { |
1092 | d->prefs.hoverOutline = arg_Command(cmd) != 0; | 1153 | d->prefs.hoverOutline = arg_Command(cmd) != 0; |
1093 | refresh_App(); | 1154 | postRefresh_App(); |
1094 | return iTrue; | 1155 | return iTrue; |
1095 | } | 1156 | } |
1096 | else if (equal_Command(cmd, "saturation.set")) { | 1157 | else if (equal_Command(cmd, "saturation.set")) { |
@@ -1373,13 +1434,13 @@ iBool handleCommand_App(const char *cmd) { | |||
1373 | } | 1434 | } |
1374 | else if (equal_Command(cmd, "feeds.update.started")) { | 1435 | else if (equal_Command(cmd, "feeds.update.started")) { |
1375 | setFlags_Widget(findWidget_App("feeds.progress"), hidden_WidgetFlag, iFalse); | 1436 | setFlags_Widget(findWidget_App("feeds.progress"), hidden_WidgetFlag, iFalse); |
1376 | refresh_App(); | 1437 | postRefresh_App(); |
1377 | return iFalse; | 1438 | return iFalse; |
1378 | } | 1439 | } |
1379 | else if (equal_Command(cmd, "feeds.update.finished")) { | 1440 | else if (equal_Command(cmd, "feeds.update.finished")) { |
1380 | setFlags_Widget(findWidget_App("feeds.progress"), hidden_WidgetFlag, iTrue); | 1441 | setFlags_Widget(findWidget_App("feeds.progress"), hidden_WidgetFlag, iTrue); |
1381 | refreshFinished_Feeds(); | 1442 | refreshFinished_Feeds(); |
1382 | refresh_App(); | 1443 | postRefresh_App(); |
1383 | return iFalse; | 1444 | return iFalse; |
1384 | } | 1445 | } |
1385 | else if (equal_Command(cmd, "visited.changed")) { | 1446 | else if (equal_Command(cmd, "visited.changed")) { |
@@ -46,6 +46,7 @@ enum iAppEventMode { | |||
46 | enum iUserEventCode { | 46 | enum iUserEventCode { |
47 | command_UserEventCode = 1, | 47 | command_UserEventCode = 1, |
48 | refresh_UserEventCode = 2, | 48 | refresh_UserEventCode = 2, |
49 | asleep_UserEventCode = 3, | ||
49 | }; | 50 | }; |
50 | 51 | ||
51 | const iString *execPath_App (void); | 52 | const iString *execPath_App (void); |