// The contents of this file are subject to the BOINC Public License // Version 1.0 (the "License"); you may not use this file except in // compliance with the License. You may obtain a copy of the License at // http://boinc.berkeley.edu/license_1.0.txt // // Software distributed under the License is distributed on an "AS IS" // basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the // License for the specific language governing rights and limitations // under the License. // // The Original Code is the Berkeley Open Infrastructure for Network Computing. // // The Initial Developer of the Original Code is the SETI@home project. // Portions created by the SETI@home project are Copyright (C) 2002 // University of California at Berkeley. All Rights Reserved. // // Contributor(s): // #include #include #include // project includes --------------------------------------------------------- #include "mac_main.h" #include "mac_join.h" #include "mac_about.h" #include "mac_prefs.h" #include "account.h" #include "client_state.h" #include "log_flags.h" #include "message.h" #include "gui_titles.h" #include "util.h" // statics/globals (internal only) ------------------------------------------ WindowRef boincWindow; EventLoopTimerRef boincTimer,boincIdleTimer; EventLoopTimerUPP boincTimerUPP,boincIdleUPP; EventHandlerUPP appCommandProcessor,winCommandProcessor; WindowPtr boincAboutWindow; Point oldMouse; KeyMap oldKeys; UInt32 user_idle_since,user_idle_length; vector msgs; ControlRef tabControl; int winXPos, winYPos, winWidth, winHeight; int tabList[] = {5, 129, 130, 131, 132, 133}; // Tab UserPane IDs int lastTabIndex = 1; MenuRef dockMenu, fileMenu; MenuItemIndex dockSuspendIndex, dockResumeIndex; MenuItemIndex fileSuspendIndex, fileResumeIndex; bool user_requested_exit = false; ControlRef boincDataBrowsers[MAX_LIST_ID]; DataBrowserPropertyType column_types[MAX_LIST_ID][MAX_COLS] = { {kDataBrowserTextType, kDataBrowserTextType, kDataBrowserTextType, kDataBrowserTextType, kDataBrowserProgressBarType, NULL, NULL}, {kDataBrowserTextType, kDataBrowserTextType, kDataBrowserTextType, kDataBrowserTextType, kDataBrowserProgressBarType, kDataBrowserTextType, kDataBrowserTextType}, {kDataBrowserTextType, kDataBrowserTextType, kDataBrowserProgressBarType, kDataBrowserTextType, kDataBrowserTextType, kDataBrowserTextType, NULL}, {kDataBrowserTextType, kDataBrowserDateTimeType, kDataBrowserTextType, NULL, NULL, NULL, NULL} }; DataBrowserItemDataUPP carbonItemCallbacks[MAX_LIST_ID] = {BOINCCarbonProjectCallback, BOINCCarbonWorkCallback, BOINCCarbonTransferCallback, BOINCCarbonMessageCallback}; void Syncronize(ControlRef prog, vector* vect); // -------------------------------------------------------------------------- OSStatus InitMainWindow(void) { OSStatus err; Handle boincMenuBar; ControlID ctrlID; IBNibRef boincNibRef; int i,n; DataBrowserCallbacks callbacks; InitCursor(); // Search for the "boinc" .nib file err = CreateNibReference(CFSTR("boinc"), &boincNibRef); if ( err != noErr ) { fprintf(stderr, "Can't load boinc.nib. Err: %d\n", (int)err); ExitToShell(); } // Init Menus err = CreateMenuBarFromNib(boincNibRef, CFSTR("MainMenu"), &boincMenuBar); if ( err != noErr ) { fprintf(stderr, "Can't load MenuBar. Err: %d\n", (int)err); ExitToShell(); } err = CreateWindowFromNib(boincNibRef, CFSTR("Client Window"), &boincWindow); if (err != noErr) { fprintf(stderr, "Can't load Window. Err: %d\n", (int)err); ExitToShell(); } ReadBOINCPreferences(); // Enable the preferences item EnableMenuCommand(NULL, kHICommandPreferences); // Add the columns to the data browsers for (i=0;i 0) { if (user_idle_length > 60 * gstate.global_prefs.idle_time_to_run) { // Is there a defined constant we can use instead of 60? return true; } else { return false; } } else { return true; } } //////////////////////////////////////////////////////// // BOINCPollLoopProcessor // //////////////////////////////////////////////////////// pascal void BOINCPollLoopProcessor(EventLoopTimerRef inTimer, void* timeData) { gstate.user_idle = CheckIfIdle(); // While we still have something to do we keep processing, // otherwise, we go back to sleep while (gstate.do_something()) { if (gstate.time_to_exit()) { break; } } Syncronize( boincDataBrowsers[0], (vector*)(&gstate.projects) ); Syncronize( boincDataBrowsers[1], (vector*)(&gstate.results) ); Syncronize( boincDataBrowsers[2], (vector*)(&gstate.pers_xfers->pers_file_xfers) ); Syncronize( boincDataBrowsers[3], (vector*)(&msgs) ); GUIRedraw(); fflush(stdout); } ///////////////////////////////////////////////////// // BOINCIdleDetect // ///////////////////////////////////////////////////// pascal void BOINCIdleDetect(EventLoopTimerRef inTimer, void* timeData) { Point curMouse; KeyMap curKeys; int i; UInt32 curTime; curTime = TickCount(); // See if the mouse has moved GetMouse( &curMouse ); if (curMouse.h != oldMouse.h || curMouse.v != oldMouse.v) { user_idle_since = curTime; } oldMouse.h = curMouse.h; oldMouse.v = curMouse.v; // See if user has pressed the mouse button if (Button()) user_idle_since = curTime; // See if any keys have been pressed GetKeys( curKeys ); for (i=0;i<4;i++) { if (oldKeys[i] != curKeys[i]) { user_idle_since = curTime; } oldKeys[i] = curKeys[i]; } user_idle_length = curTime - user_idle_since; } ////////////////////////////////////////////////////////////////////////////////// // SuspendBOINC // ////////////////////////////////////////////////////////////////////////////////// void SuspendBOINC( bool suspend ) { gstate.suspend_requested = suspend; if (suspend) { DisableMenuItem( dockMenu, dockSuspendIndex ); DisableMenuItem( fileMenu, fileSuspendIndex ); EnableMenuItem( dockMenu, dockResumeIndex ); EnableMenuItem( fileMenu, fileResumeIndex ); } else { DisableMenuItem( dockMenu, dockResumeIndex ); DisableMenuItem( fileMenu, fileResumeIndex ); EnableMenuItem( dockMenu, dockSuspendIndex ); EnableMenuItem( fileMenu, fileSuspendIndex ); } } ////////////////////////////////////////////////////////////////////////////////// // MainAppEventHandler // ////////////////////////////////////////////////////////////////////////////////// pascal OSStatus MainAppEventHandler(EventHandlerCallRef appHandler, EventRef theEvent, void* appData) { #pragma unused (appHandler, appData) HICommand aCommand; OSStatus result; switch(GetEventClass(theEvent)) { case kEventClassCommand: result = GetEventParameter(theEvent, kEventParamDirectObject, typeHICommand, NULL, sizeof(HICommand), NULL, &aCommand); switch (aCommand.commandID) { case kHICommandQuit: QuitApplicationEventLoop(); result = noErr; break; case kHICommandPreferences: // 'pref' // Open prefs dialog //CreatePrefsDialog(); result = noErr; break; case kBOINCCommandJoin: // 'join' char new_master_url[256], new_auth[256]; // Open join dialog if (CreateJoinDialog( new_master_url, new_auth ) ) { gstate.add_project( new_master_url, new_auth ); } result = noErr; break; case kBOINCCommandSuspend: // 'susp' // Suspend processing SuspendBOINC(true); result = noErr; break; case kBOINCCommandResume: // 'resm' // Resume processing SuspendBOINC(false); result = noErr; break; case kHICommandAbout: // 'abou' // Open About window CreateAboutWindow(); result = noErr; break; default: result = eventNotHandledErr; break; } break; default: result = eventNotHandledErr; break; } return result; } ////////////////////////////////////////////////////////////////////////////////// // MainWinEventHandler // ////////////////////////////////////////////////////////////////////////////////// pascal OSStatus MainWinEventHandler(EventHandlerCallRef appHandler, EventRef theEvent, void* appData) { #pragma unused (appHandler, appData) OSStatus result; UInt32 attributes; Rect winRect; switch(GetEventClass(theEvent)) { case kEventClassWindow: GetEventParameter(theEvent, kEventParamAttributes, typeUInt32, NULL, sizeof(UInt32), NULL, &attributes); switch(GetEventKind(theEvent)) { case kEventWindowBoundsChanged: GetEventParameter(theEvent, kEventParamCurrentBounds, typeQDRectangle, NULL, sizeof(Rect), NULL, &winRect); winXPos = winRect.left; winYPos = winRect.top; winWidth = winRect.right - winRect.left; winHeight = winRect.bottom - winRect.top; if (attributes & kWindowBoundsChangeSizeChanged) { SizeControl( tabControl, winWidth, winHeight-12 ); for (int i=0;i<4;i++ ) SizeControl( boincDataBrowsers[i], winWidth-30-9, winHeight-49-30 ); } break; default: result = eventNotHandledErr; break; } break; default: result = eventNotHandledErr; break; } return result; } // -------------------------------------------------------------------------- OSStatus mac_setup (void) { OSStatus err; err = InitMainWindow(); if (err != noErr) return err; RunApplicationEventLoop(); return true; } // -------------------------------------------------------------------------- void mac_cleanup (void) { RemoveEventLoopTimer(boincTimer); DisposeEventLoopTimerUPP(boincTimerUPP); DisposeEventHandlerUPP(appCommandProcessor); } // -------------------------------------------------------------------------- int main(int argc, char** argv) { read_log_flags(); gstate.parse_cmdline(argc, argv); if (gstate.init()) return -1; // mac_setup won't return until the main application loop has quit if (!mac_setup()) return -1; // Afterwards, we clean up and exit gstate.cleanup_and_exit(); SaveBOINCPreferences(); mac_cleanup(); } // Check if we're running OS 8/9 or OS X /*pm_features = PMFeatures(); // If the we can get the battery info from this API, do it if ((pm_features>>canGetBatteryTime)&0x01) { err = BatteryStatus( &a, &b ); if (err != noErr); // do something } else fprintf( stderr, "%d %d", a, b ); exit(0);*/ // -------------------------------------------------------------------------- void show_message(PROJECT *p, char* message, int priority) { MESSAGE *new_msg; new_msg = (MESSAGE*)malloc(sizeof(MESSAGE)); GetDateTime( &new_msg->timestamp ); safe_strncpy( new_msg->msg, message, sizeof(new_msg->msg) ); if (p) safe_strncpy( new_msg->project, p->get_project_name(), sizeof(new_msg->msg) ); else safe_strncpy( new_msg->project, "BOINC", sizeof(new_msg->msg) ); msgs.push_back(new_msg); } // int add_new_project() { return 0; } // ---------------------------------------------------------------------- // Show the selected pane, hide the others. void SelectItemOfTabControl(ControlRef myTabControl) { ControlRef userPane; ControlRef selectedPane = NULL; ControlID controlID; UInt16 i; SInt16 index = GetControlValue(myTabControl); lastTabIndex = index; controlID.signature = TAB_SIGNATURE; for (i = 1; i < tabList[0] + 1; i++) { controlID.id = tabList[i]; GetControlByID(GetControlOwner(myTabControl), &controlID, &userPane); if (i == index) { selectedPane = userPane; SetControlVisibility(boincDataBrowsers[i-1],true,true); } else { SetControlVisibility(userPane,false,false); SetControlVisibility(boincDataBrowsers[i-1],false,false); DisableControl(userPane); } } if (selectedPane != NULL) { EnableControl(selectedPane); SetControlVisibility(selectedPane, true, true); } SizeControl( tabControl, winWidth, winHeight-12 ); Draw1Control(myTabControl); for (int i=0;i<4;i++ ) SizeControl( boincDataBrowsers[i], winWidth-30-9, winHeight-49-30 ); } // ---------------------------------------------------------------------- // Listen to events. Only switch if the tab value has changed. pascal OSStatus TabEventHandler(EventHandlerCallRef inHandlerRef, EventRef inEvent, void *inUserData) { OSStatus result = eventNotHandledErr; ControlRef theControl; ControlID controlID; GetEventParameter(inEvent, kEventParamDirectObject, typeControlRef, NULL, sizeof( ControlRef ), NULL, &theControl ); GetControlID(theControl, &controlID); // If this event didn't trigger a tab change, give somebody else a chance to handle it. if (controlID.id == TAB_ID && GetControlValue(theControl) != lastTabIndex) { result = noErr; SelectItemOfTabControl(theControl); } return result; } // ---------------------------------------------------------------------- void InstallTabHandler(WindowRef window) { EventTypeSpec controlSpec = { kEventClassControl, kEventControlHit }; // event class, event kind ControlID controlID; // Find the tab control and install an event handler on it. controlID.signature = TAB_SIGNATURE; controlID.id = TAB_ID; GetControlByID(window, &controlID, &tabControl); InstallEventHandler(GetControlEventTarget(tabControl), NewEventHandlerUPP( TabEventHandler ), 1, &controlSpec, 0, NULL); //Select the active tab to start with. lastTabIndex = GetControlValue(tabControl); SelectItemOfTabControl(tabControl); } // Force a redraw of the GUI elements // void GUIRedraw(void) { DataBrowserItemID items; items = kDataBrowserNoItem; for (int i=0;iget_project_name(), sizeof(buf) ); break; case 1: // Account name safe_strncpy( buf, p->user_name, sizeof(buf) ); break; case 2: // Total credit sprintf( buf, "%0.2f", p->user_total_credit); break; case 3: // Average credit sprintf( buf, "%0.2f", p->user_expavg_credit); break; case 4: // Resource share for(i = 0; i < gstate.projects.size(); i ++) { totalres += gstate.projects[i]->resource_share; } if (totalres <= 0) SetDataBrowserItemDataValue( itemData, 100 ); else SetDataBrowserItemDataValue( itemData, (int)((100 * p->resource_share)/totalres) ); break; default: break; } err = SetDataBrowserItemDataText( itemData, CFStringCreateWithCString(CFAllocatorGetDefault(), buf, kCFStringEncodingMacRoman)); } return noErr; } OSStatus BOINCCarbonWorkCallback( ControlRef browser, DataBrowserItemID itemID, DataBrowserPropertyID property, DataBrowserItemDataRef itemData, Boolean changeValue) { OSStatus err; char buf[256]; RESULT *re = (RESULT*)itemID; ACTIVE_TASK* at; int cpuhour, cpumin, cpusec; if (!changeValue) { sprintf( buf, " " ); switch (property) { case 0: // Project name safe_strncpy( buf, re->project->project_name, sizeof(buf) ); break; case 1: // App name safe_strncpy( buf, re->app->name, sizeof(buf) ); break; case 2: // Workunit name safe_strncpy( buf, re->name, sizeof(buf) ); break; case 3: // CPU time double cur_cpu; at = gstate.lookup_active_task_by_result(re); if (at) { cur_cpu = at->current_cpu_time; } else { cur_cpu = 0; } cpuhour = (int)(cur_cpu / (60 * 60)); cpumin = (int)(cur_cpu / 60) % 60; cpusec = (int)(cur_cpu) % 60; sprintf( buf, "%02d:%02d:%02d", cpuhour, cpumin, cpusec); break; case 4: // Progress at = gstate.lookup_active_task_by_result(re); if(!at) { SetDataBrowserItemDataValue(itemData, 0); } else { SetDataBrowserItemDataValue(itemData, (int)(at->fraction_done * 100)); } break; case 5: // Time to completion double tocomp; if(!at || at->fraction_done == 0) { tocomp = gstate.estimate_cpu_time(*re->wup); } else { tocomp = at->est_time_to_completion(); } cpuhour = (int)(tocomp / (60 * 60)); cpumin = (int)(tocomp / 60) % 60; cpusec = (int)(tocomp) % 60; sprintf( buf, "%02d:%02d:%02d", cpuhour, cpumin, cpusec); break; case 6: // Status switch(re->state) { case RESULT_NEW: safe_strncpy(buf, g_szMiscItems[0], sizeof(buf)); break; case RESULT_FILES_DOWNLOADING: safe_strncpy(buf, g_szMiscItems[9], sizeof(buf)); break; case RESULT_FILES_DOWNLOADED: if (at) safe_strncpy(buf, g_szMiscItems[1], sizeof(buf)); else safe_strncpy(buf, g_szMiscItems[2], sizeof(buf)); break; case RESULT_COMPUTE_DONE: safe_strncpy(buf, g_szMiscItems[3], sizeof(buf)); break; case RESULT_FILES_UPLOADING: safe_strncpy(buf, g_szMiscItems[8], sizeof(buf)); break; default: if (re->server_ack) { safe_strncpy(buf, g_szMiscItems[5], sizeof(buf)); break; } else if (re->ready_to_ack) { safe_strncpy(buf, g_szMiscItems[4], sizeof(buf)); break; } else { safe_strncpy(buf, g_szMiscItems[6], sizeof(buf)); break; } } break; } err = SetDataBrowserItemDataText( itemData, CFStringCreateWithCString(CFAllocatorGetDefault(), buf, kCFStringEncodingMacRoman)); } return noErr; } OSStatus BOINCCarbonTransferCallback( ControlRef browser, DataBrowserItemID itemID, DataBrowserPropertyID property, DataBrowserItemDataRef itemData, Boolean changeValue) { OSStatus err; char buf[256]; PERS_FILE_XFER *fi = (PERS_FILE_XFER*)itemID; double xSent,xtime; int xhour, xmin, xsec; if (!changeValue) { xSent = 0; if( fi->fip->generated_locally ) { xSent = fi->fip->upload_offset; } else { char pathname[256]; get_pathname(fi->fip, pathname); file_size(pathname, xSent); } sprintf( buf, " " ); switch (property) { case 0: // Project name safe_strncpy( buf, fi->fip->project->project_name, sizeof(buf) ); break; case 1: // File name safe_strncpy( buf, fi->fip->name, sizeof(buf) ); break; case 2: // Transfer progress SetDataBrowserItemDataValue(itemData, (int)(100.0 * xSent / fi->fip->nbytes)); break; case 3: // Size sprintf( buf,"%0.0f/%0.0fKB", xSent / 1024.0, fi->fip->nbytes / 1024.0); break; case 4: // Time xtime = fi->time_so_far; xhour = (int)(xtime / (60 * 60)); xmin = (int)(xtime / 60) % 60; xsec = (int)(xtime) % 60; sprintf(buf,"%02d:%02d:%02d", xhour, xmin, xsec); break; case 5: // Status if (fi->next_request_time > time(0)) { xtime = fi->next_request_time-time(0); xhour = (int)(xtime / (60 * 60)); xmin = (int)(xtime / 60) % 60; xsec = (int)(xtime) % 60; sprintf(buf,"%s %02d:%02d:%02d", g_szMiscItems[10], xhour, xmin, xsec); } else if (fi->fip->status == ERR_GIVEUP_DOWNLOAD) { safe_strncpy( buf, g_szMiscItems[11], sizeof(buf) ); } else if (fi->fip->status == ERR_GIVEUP_UPLOAD) { safe_strncpy( buf, g_szMiscItems[12], sizeof(buf) ); } else { safe_strncpy( buf, fi->fip->generated_locally?g_szMiscItems[8]:g_szMiscItems[9], sizeof(buf) ); } break; } err = SetDataBrowserItemDataText( itemData, CFStringCreateWithCString(CFAllocatorGetDefault(), buf, kCFStringEncodingMacRoman)); } return noErr; } OSStatus BOINCCarbonMessageCallback( ControlRef browser, DataBrowserItemID itemID, DataBrowserPropertyID property, DataBrowserItemDataRef itemData, Boolean changeValue) { OSStatus err; char buf[256]; MESSAGE *msg = (MESSAGE*)itemID; if (!changeValue) { switch (property) { case 0: // Project safe_strncpy( buf, msg->project, sizeof(buf) ); err = SetDataBrowserItemDataText( itemData, CFStringCreateWithCString(CFAllocatorGetDefault(), buf, kCFStringEncodingMacRoman)); break; case 1: // Time SetDataBrowserItemDataDateTime( itemData, msg->timestamp ); break; case 2: // File name safe_strncpy( buf, msg->msg, sizeof(buf) ); err = SetDataBrowserItemDataText( itemData, CFStringCreateWithCString(CFAllocatorGetDefault(), buf, kCFStringEncodingMacRoman)); break; default: safe_strncpy( buf, " ", sizeof(buf) ); err = SetDataBrowserItemDataText( itemData, CFStringCreateWithCString(CFAllocatorGetDefault(), buf, kCFStringEncodingMacRoman)); break; } } return noErr; } ////////// // arguments: prog: pointer to a progress list control // vect: pointer to a vector of pointers // returns: void // function: first, goes through the vector and adds items to the list // control for any pointers it does not already contain, then // goes through the list control and removes any pointers the // vector does not contain. void Syncronize(ControlRef prog, vector* vect) { unsigned int i,j; Handle items; OSStatus err; DataBrowserItemID duff[1]; items = NewHandle(0); err = GetDataBrowserItems(prog, kDataBrowserNoItem, true, 0, items); // add items to list that are not already in it for(i = 0; i < vect->size(); i ++) { void* item = (*vect)[i]; bool contained = false; for(j = 0; j < (GetHandleSize(items) / sizeof(DataBrowserItemID)); j ++) { if(item == items[j]) { contained = true; break; } } if(!contained) { duff[0] = (long unsigned int)item; err = AddDataBrowserItems( prog, kDataBrowserNoItem, 1, duff, kDataBrowserItemNoProperty ); } } // remove items from list that are not in vector // now just set the pointer to NULL but leave the item in the list for(i = 0; i < (GetHandleSize(items) / sizeof(DataBrowserItemID)); i ++) { void* item = items[i]; bool contained = false; for(j = 0; j < vect->size(); j ++) { if(item == (*vect)[j]) { contained = true; break; } } if(!contained) { duff[0] = (long unsigned int)item; err = RemoveDataBrowserItems( prog, kDataBrowserNoItem, 1, duff, kDataBrowserItemNoProperty ); } } } OSStatus SaveBOINCPreferences( void ) { unsigned int i, n; char buf[256]; UInt32 ncols; // Set up the preferences CFPreferencesSetAppValue(CFSTR("mainWindowXPos"), CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &winXPos), kCFPreferencesCurrentApplication); CFPreferencesSetAppValue(CFSTR("mainWindowYPos"), CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &winYPos), kCFPreferencesCurrentApplication); CFPreferencesSetAppValue(CFSTR("mainWindowWidth"), CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &winWidth), kCFPreferencesCurrentApplication); CFPreferencesSetAppValue(CFSTR("mainWindowHeight"), CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &winHeight), kCFPreferencesCurrentApplication); CFPreferencesSetAppValue(CFSTR("mainWindowCurTab"), CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &lastTabIndex), kCFPreferencesCurrentApplication); for (i=0;i<4;i++) { GetDataBrowserTableViewColumnCount(boincDataBrowsers[i],&ncols); for (n=0;n