// This file is part of BOINC. // http://boinc.berkeley.edu // Copyright (C) 2022 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License // as published by the Free Software Foundation, // either version 3 of the License, or (at your option) any later version. // // BOINC is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with BOINC. If not, see . // // gfx_cleanup.mm // // Used by screensaver to work around a bug in OS 10.15 Catalina // - Detects when ScreensaverEngine exits without calling [ScreenSaverView stopAnimation] // - If that happens, it sends RPC to BOINC client to kill current graphics app. // Note: this can rarely happen in earlier versions, but is of main concern under // OS 10.13 and later, where it can cause an ugly white full-screen display // // Called by CScreensaver via popen(path, "w") // #import #include #include #include #include "gui_rpc_client.h" #include "util.h" #include "mac_util.h" #include "shmem.h" #define CREATE_LOG 0 #define USE_TIMER 0 #if CREATE_LOG void print_to_log_file(const char *format, ...); #else #define print_to_log_file(...) #endif pid_t parentPid; int GFX_PidFromScreensaver = 0; pthread_t MonitorParentThread = 0; bool quit_MonitorParentThread = false; struct ss_shmem_data { pid_t gfx_pid; int gfx_slot; }; static struct ss_shmem_data* ss_shmem = NULL; #if USE_TIMER time_t startTime = 0; time_t endTime = 0; time_t elapsedTime = 0; #endif void killGfxApp(pid_t thePID) { #if 0 print_to_log_file("in gfx_cleanup: killGfxApp()"); kill(thePID, SIGKILL); #else char buf[256]; char userName[64]; RPC_CLIENT *rpc; int retval; std::string msg; chdir("/Library/Application Support/BOINC Data"); safe_strcpy(buf, ""); read_gui_rpc_password(buf, msg); rpc = new RPC_CLIENT; if (rpc->init(NULL)) { // Initialize communications with Core Client fprintf(stderr, "in gfx_cleanup: killGfxApp(): rpc->init(NULL) failed"); return; } if (strlen(buf)) { retval = rpc->authorize(buf); if (retval) { fprintf(stderr, "in gfx_cleanup: killGfxApp(): authorization failure: %d\n", retval); rpc->close(); return; } } CFStringRef cf_gUserName = SCDynamicStoreCopyConsoleUser(NULL, NULL, NULL); CFStringGetCString(cf_gUserName, userName, sizeof(userName), kCFStringEncodingUTF8); retval = rpc->run_graphics_app("stop", thePID, userName); print_to_log_file("in gfx_cleanup: killGfxApp(): rpc->run_graphics_app(stop) returned retval=%d", retval); // Wait until graphics app has exited before closing our own black fullscreen // window to prevent an ugly white flash [see comment in main() below]. int i; pid_t p = 0; for (i=0; i<100; ++i) { boinc_sleep(0.1); p = thePID; // On OS 10.15+ (Catalina), it might be more efficient to get this from shared memory retval = rpc->run_graphics_app("test", p, userName); if (retval || (p==0)) break; } print_to_log_file("in gfx_cleanup: killGfxApp(%d): rpc->run_graphics_app(test) returned pid %d, retval %d when i = %d", thePID, p, retval, i); // Graphics apps called by screensaver or Manager (via Show // Graphics button) now write files in their slot directory as // the logged in user, not boinc_master. This ugly hack tells // BOINC client to fix all ownerships in this slot directory char shmem_name[MAXPATHLEN]; snprintf(shmem_name, sizeof(shmem_name), "/tmp/boinc_ss_%s", userName); retval = attach_shmem_mmap(shmem_name, (void**)&ss_shmem); if (ss_shmem) { rpc->run_graphics_app("stop", ss_shmem->gfx_slot, ""); ss_shmem->gfx_slot = -1; } rpc->close(); #endif return; } void * MonitorParent(void* param) { print_to_log_file("in gfx_cleanup: Starting MonitorParent"); while (true) { boinc_sleep(0.25); // Test every 1/4 second if (getppid() != parentPid) { if (GFX_PidFromScreensaver) { killGfxApp(GFX_PidFromScreensaver); } if (quit_MonitorParentThread) { return 0; } print_to_log_file("in gfx_cleanup: parent died, exiting (child) after handling %d",GFX_PidFromScreensaver); #if USE_TIMER endTime = time(NULL); elapsedTime = endTime - startTime; print_to_log_file("elapsed time=%d", (int) elapsedTime); #endif exit(0); } } } NSWindow* myWindow; @interface AppDelegate : NSObject { } @end @implementation AppDelegate - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { int retval __attribute__((unused)) = 0; char buf[256]; retval = pthread_create(&MonitorParentThread, NULL, MonitorParent, 0); while (true) { fgets(buf, sizeof(buf), stdin); print_to_log_file("in gfx_cleanup: parent sent %d to child buf=%s", GFX_PidFromScreensaver, buf); if (feof(stdin)) { print_to_log_file("in gfx_cleanup: got eof"); break; } if (ferror(stdin) && (errno != EINTR)) { fprintf(stderr, "in gfx_cleanup: fgets got error %d %s", errno, strerror(errno)); break; } if (!strcmp(buf, "Quit\n")) { break; } GFX_PidFromScreensaver = atoi(buf); print_to_log_file("in gfx_cleanup: parent sent %d to child buf=%s", GFX_PidFromScreensaver, buf); } if (GFX_PidFromScreensaver) { killGfxApp(GFX_PidFromScreensaver); } quit_MonitorParentThread = true; // [NSApp stop:self]; exit(0); } @end int main(int argc, char* argv[]) { print_to_log_file("Entered gfx_cleanup"); #if USE_TIMER startTime = time(NULL); #endif parentPid = getppid(); bool cover_gfx_window = (compareOSVersionTo(10, 13) >= 0); // Create shared app instance [NSApplication sharedApplication]; // Because prpject graphics applications under OS 10.13+ draw to an IOSurface, // the application's own window is white, but is normally covered by the // ScreensaverEngine's window. If the ScreensaverEngine exits without first // calling [ScreenSaverView stopAnimation], the white fullscreen window will // briefly be visible until we kill the graphics app, causing an ugly and // annoying white flash. So we hide that with our own black fullscreen window // to prevent the white flash. if (cover_gfx_window) { NSArray *allScreens = [NSScreen screens]; NSRect windowRect = [ allScreens[0] frame ]; NSUInteger windowStyle = NSWindowStyleMaskBorderless; NSWindow *myWindow = [[NSWindow alloc] initWithContentRect:windowRect styleMask:windowStyle backing:NSBackingStoreBuffered defer:NO]; [myWindow setBackgroundColor:[NSColor blackColor]]; [ myWindow setLevel:kCGOverlayWindowLevel ]; // slightly above the graphics app's window [myWindow orderFrontRegardless]; } AppDelegate *myDelegate = [[AppDelegate alloc] init]; [ NSApp setDelegate:myDelegate]; [NSApp run]; print_to_log_file("exiting gfx_cleanup after handling %d",GFX_PidFromScreensaver); #if USE_TIMER endTime = time(NULL); elapsedTime = endTime - startTime; print_to_log_file("elapsed time=%d", (int) elapsedTime); #endif return 0; } // print_to_log_file.c #if CREATE_LOG #include #include #include #include #include #ifdef unix //#include #endif void strip_cr(char *buf); // print_to_log_file - use for debugging. // prints time stamp plus a formatted string to log file. // calling syntax: same as printf. void print_to_log_file(const char *format, ...) { FILE *f; va_list args; char buf[256]; time_t t; #if 0 strcpy(buf, "/Library/Application Support/test_log.txt"); #else strcpy(buf, getenv("HOME")); strcat(buf, "/Library/Application Support/BOINC/test_log_gfx_cleanup.txt"); #endif f = fopen(buf, "a"); // f = fopen("/Library/Games_Demo/test_log.txt", "a"); if (!f) return; time(&t); strcpy(buf, asctime(localtime(&t))); strip_cr(buf); fputs(buf, f); fputs(" ", f); va_start(args, format); vfprintf(f, format, args); va_end(args); fputs("\n", f); fclose(f); } void strip_cr(char *buf) { char *theCR; theCR = strrchr(buf, '\n'); if (theCR) *theCR = '\0'; theCR = strrchr(buf, '\r'); if (theCR) *theCR = '\0'; } #endif // CREATE_LOG #ifdef __cplusplus extern "C" { #endif void PrintBacktrace(void) { // Dummy routine to satisfy linker } #ifdef __cplusplus } #endif