This is mCD_Controller.m in view mode; [Download] [Up]
#import "mCD_Controller.h" #import "PrefsController.h" #import "StandardInfo.h" #import "CD_DBase.subproj/CD_DBase.h" void fixCatNum(struct rsc_media_catnum_reply *); @implementation mCD_Controller /* implement a periodic update */ void Trigger(DPSTimedEntry teNum, double new, id target) { [target EventLoop]; } - EventLoopInit { teNum = DPSAddTimedEntry(updatePeriod, (DPSTimedEntryProc)Trigger, self, NX_BASETHRESHOLD); return self; } - EventLoop { int devReady; struct timeval timed_cd_Tval; if ( (cd_fd != 0) && !do_timed_updates ) { devReady = do_testunitready(cd_fd, &timed_cd_Tval, &tur_Ereply); if ( !devReady ) { /* Hmm, will this do what I want? */ [self loadCD:self]; } } if ( do_timed_updates ) [self updateCdStatus:self]; return self; } - appDidInit:sender { if ( 0 != geteuid() ) { id nonRootErrorPanel; nonRootErrorPanel = [NXApp loadNibSection:"NonRoot.nib" owner:NXApp]; [NXApp runModalFor:nonRootErrorPanel]; exit(0); } [standardInfo initForAppUsingNib:"mCD_Info.nib"]; [globPrefs initGlobalPreferences:self mainPanel:mainPanel ]; /* NOTE: in NS-3.2, it seems that setAltIncrementValue will work * backwards, at least for vertical sliders. I still want * to have it though, it's better backwards than not at all */ [leftVolumeSliderID setAltIncrementValue: 1.0]; [rightVolumeSliderID setAltIncrementValue: 1.0]; [pauseButtonID setShowsStateBy:NX_CONTENTS | NX_CHANGEBACKGROUND]; cd_fd = 0; [loadButtonID setEnabled:YES]; [playButtonID setEnabled:NO]; [pauseButtonID setEnabled:NO]; [stopButtonID setEnabled:NO]; [unloadButtonID setEnabled:NO]; [curTrackID setStringValue: "--"]; [endTrackID setStringValue: "--"]; [trackPlayTimeID setStringValue: "-:--:--"]; [trackRemTimeID setStringValue: "-:--:--"]; [discPlayTimeID setStringValue: "-:--:--"]; [discRemTimeID setStringValue: "-:--:--"]; [trackInfoID selectAll:self]; [trackInfoID replaceSel: ""]; [mainPanel makeKeyAndOrderFront:self]; /* start up the periodic update of the cd status */ do_timed_updates = NO; /* initial testing */ updatePeriod = 1.0; /* every 1 second */ updatePeriod = 0.5; /* crazy times for testing purposes */ updatePeriod = 0.99; /* a little less crazy... */ [self EventLoopInit]; /* if the preferences are indeed set for a CD-ROM drive, then * try to load a music CD, and if it's already in there go * to display it. Note that this will not (yet) automatically * prompt to load in a CD if there isn't one there. */ if ( [self openCdFd:FROM_CD_APP_DID_INIT] ) { [self displayCdInfo]; } return self; } - appWillTerminate:sender { /* the purpose of this method is to handle "Quit" processing */ BOOL wantUnload, abortQuit; int devReady, alertResult; if ( cd_fd == 0 ) return self; /* no device open, nothing to do */ /* assume an unload of the CD drive will be wanted. If the device * is ready (ie, there's a disc in it), and if the disc is playing, * then prompt the user to find out of it's really wanted. If the * drive has no CD, or the CD isn't playing, then always do an * unload to prevent later confusion. */ wantUnload = YES; /* assume an unload will be wanted */ abortQuit = NO; /* and the Quit won't be aborted */ devReady = do_testunitready(cd_fd, &cd_Tval, &tur_Ereply); if ( !devReady ) { /* there is a disc in the drive */ if (cd_curpos.rsc_audio_status == RSC_ASTAT_PLAYING ) { alertResult = NXRunAlertPanel("mCD Quitting", "There is a music CD still playing. Should it be unloaded during quit?", "Yes", "No", "Cancel Quit", rawDevName); switch ( alertResult ) { case NX_ALERTALTERNATE: wantUnload = NO; break; case NX_ALERTOTHER: wantUnload = NO; abortQuit = YES; break; } } } if (wantUnload) [self unloadCD:self]; else { /* user wants the music CD left in the drive, playing, but * we should at least enable the media-removal button (on * the drive) to work again */ do_preventremoval_1e(cd_fd, NO, &cd_Ereply); /* ie, allow removal */ } if (abortQuit) return NULL; return self; /* anything non-null, so app will terminate */ } - (int)appPowerOffIn:(int)ms andSave:(int)aFlag { /* the purpose of this method is to handle logout processing, * which is not handled by the appWillTerminate method. I * don't really care about power-off processing, but this * seems to be the only way to handle logout processing */ /* this method is described *very* briefly in the pre-3.0 * concepts documentation. While it works in NS-3.x (on * motorola hardware, at least), I suspect NeXT is phasing * it out. If this routine was doing any significant amount * of work, it might need to send the Workspace manager a: - (int)extendPowerOffBy:(int)requestedMs actual:(int *)actualMs; * message to get more time to do the processing. */ /* clean up after ourselves as quickly as possible. this is * basically a trimmed-down version of "unload" processing */ int err; if ( cd_fd == 0 ) return 0; /* no device currently, ignore */ do_preventremoval_1e(cd_fd, NO, &cd_Ereply); /* ie, allow removal */ do_eject_1b(cd_fd, &cd_Tval, &cd_Ereply); err = ioctl(cd_fd, DKIOCEJECT, NULL); close( cd_fd ); cd_fd = 0; return 0 ; /* not sure what this method needs to return... */ } - updateCdStatus:sender { BOOL needUpdate, indexChanged; int tempHour, tempMin, tempSec; int lastCdIndex, lastAbsSecond; char buff[80]; if ( cd_fd == 0 ) return self; /* no device yet, ignore */ needUpdate = indexChanged = NO; do_readcurrentposition_42(cd_fd, &cd_curpos, &rcp_Ereply); switch (cd_curpos.rsc_audio_status) { case RSC_ASTAT_PAUSED: case RSC_ASTAT_PLAYING: break; case RSC_ASTAT_PLAYCOMPLETE: if ( [globPrefs consoleDebugMsgs] ) printf("mCD debug: play complete\n"); [self stopCD:self]; break; case RSC_ASTAT_PLAYABORTED: if ( [globPrefs consoleDebugMsgs] ) printf("mCD debug: play aborted\n"); /* presumably not wanted: [self stopCD:self]; */ break; case RSC_ASTAT_NONE: break; } lastCdIndex = curCdIndex; curCdIndex = (cd_curpos.track * 100) + cd_curpos.index; if ( lastCdIndex != curCdIndex ) needUpdate = indexChanged = YES; lastAbsSecond = curAbsSecond; curAbsSecond = (cd_curpos.abs_hour * 3600) + (cd_curpos.abs_min * 60) + cd_curpos.abs_sec; if ( lastAbsSecond != curAbsSecond ) needUpdate = YES; if ( needUpdate ) { [mainPanel disableFlushWindow]; if ( cd_curpos.index < 2 ) { sprintf (buff, "%d", cd_curpos.track); } else { sprintf (buff, "%d.%d", cd_curpos.track, cd_curpos.index); } [curTrackID setStringValue: buff]; if ( (cd_curpos.index == 0) && (cd_curpos.rel_sec > 0) ) { sprintf (buff, "- %02d:%02d", cd_curpos.rel_min, cd_curpos.rel_sec); } else { sprintf (buff, "%d:%02d:%02d", cd_curpos.rel_hour, cd_curpos.rel_min, cd_curpos.rel_sec); } [trackPlayTimeID setStringValue: buff]; switch (cd_curpos.rsc_audio_status) { case RSC_ASTAT_PLAYING: /* the CD is playing right now... */ if ( (indexChanged) && (cd_curpos.index + cd_curpos.rel_sec > 0) && [globPrefs consoleDebugMsgs] ) { printf("mCD debug: index %d.%d started %d:%02d:%02d-%02d into CD", cd_curpos.track, cd_curpos.index, cd_curpos.abs_hour, cd_curpos.abs_min, cd_curpos.abs_sec, cd_curpos.abs_frame); if ((cd_curpos.rel_hour > 0) || (cd_curpos.rel_min > 0) || (cd_curpos.rel_sec > 0)) { if ( cd_curpos.index != 0 ) { printf(", %d:%02d:%02d into the song", cd_curpos.rel_hour, cd_curpos.rel_min, cd_curpos.rel_sec); } else { printf(", -%02d:%02d before the song", cd_curpos.rel_min, cd_curpos.rel_sec); } } printf("\n"); } tempSec = toc.info[cd_curpos.track].elapsedSec - ( (cd_curpos.rel_hour * 3600) + (cd_curpos.rel_min * 60) + cd_curpos.rel_sec ); tempMin = tempSec / 60; tempSec = tempSec % 60; tempHour = tempMin / 60; tempMin = tempMin % 60; sprintf (buff, "%d:%02d:%02d", tempHour, tempMin, tempSec); [trackRemTimeID setStringValue: buff]; tempSec = toc.info[100].elapsedSec - ( (cd_curpos.abs_hour * 3600) + (cd_curpos.abs_min * 60) + cd_curpos.abs_sec ); tempMin = tempSec / 60; tempSec = tempSec % 60; tempHour = tempMin / 60; tempMin = tempMin % 60; sprintf (buff, "%d:%02d:%02d", tempHour, tempMin, tempSec); [discRemTimeID setStringValue: buff]; break; } sprintf (buff, "%d:%02d:%02d", cd_curpos.abs_hour, cd_curpos.abs_min, cd_curpos.abs_sec); [discPlayTimeID setStringValue: buff]; [mainPanel reenableFlushWindow]; [mainPanel flushWindowIfNeeded]; } return self; } - ejectCD:sender { int devReady; char buff[80]; if ( cd_fd == 0 ) return self; /* no device yet, ignore */ devReady = do_testunitready(cd_fd, &cd_Tval, &tur_Ereply); if ( devReady ) return self; /* device is not ready, ignore */ do_timed_updates = NO; /* initial testing */ /* zap a few display fields to reduce confusion */ [trackInfoID selectAll:self]; [trackInfoID replaceSel: ""]; [curTrackID setStringValue: "--"]; [endTrackID setIntValue: toc.lastCDtrack]; [trackPlayTimeID setStringValue: "-:--:--"]; [trackRemTimeID setStringValue: "-:--:--"]; [discPlayTimeID setStringValue: "-:--:--"]; if( toc.info[100].min >= 60 ) { toc.info[100].hour++ ; toc.info[100].min -= 60; } sprintf (buff, "%u:%02u:%02u", toc.info[100].hour, toc.info[100].min, toc.info[100].sec); [discRemTimeID setStringValue: buff]; do_preventremoval_1e(cd_fd, NO, &cd_Ereply); /* ie, allow removal */ do_eject_1b(cd_fd, &cd_Tval, &cd_Ereply); return self; } - unloadCD:sender { int err; if ( cd_fd == 0 ) return self; /* no device yet, ignore */ /* (note that we can do the unload processing even if there is * no disc in the drive, so we don't testunitready here ) */ [self ejectCD:self]; /* the difference between ejecting and unloading is the following commands: */ err = ioctl(cd_fd, DKIOCEJECT, NULL); close( cd_fd ); cd_fd = 0; [loadButtonID setEnabled:YES]; [playButtonID setEnabled:NO]; [pauseButtonID setEnabled:NO]; [stopButtonID setEnabled:NO]; [unloadButtonID setEnabled:NO]; return self; } - loadCD:sender { if ( [self openCdFd:FROM_LOAD_CD_REQUEST] ) { [self displayCdInfo]; } return self; } - displayCdInfo { int track; BOOL isAudio; /* as opposed to computer-data track */ char buff[120]; NXStream *songInfoStream; [self updateCdStatus:self]; switch (cd_curpos.rsc_audio_status) { case RSC_ASTAT_PAUSED: case RSC_ASTAT_PLAYING: break; case RSC_ASTAT_PLAYCOMPLETE: case RSC_ASTAT_PLAYABORTED: case RSC_ASTAT_NONE: do_spinup_1b(cd_fd, &cd_Tval, &cd_Ereply); } do_readtoc_43(cd_fd, &toc, &cd_Ereply); [self fillTocTitles]; [curTrackID setIntValue: 0]; [endTrackID setIntValue: toc.lastCDtrack]; switch (cd_curpos.rsc_audio_status) { case RSC_ASTAT_PAUSED: /* have to skip over this for now, due to the odd behavior * of the CD-ROM drive that NeXT used to sell, which * indicates it's paused if it's not actively playing and * if a play operation didn't just complete. This part * could be done on a drive-specific basis, once the code * is better organized. */ /* do nothing but ensure pause button is right */ /* [pauseButtonID setIntValue:1]; */ break; case RSC_ASTAT_PLAYING: /* do nothing but ensure pause button is right */ [pauseButtonID setIntValue:0]; break; case RSC_ASTAT_PLAYCOMPLETE: case RSC_ASTAT_PLAYABORTED: case RSC_ASTAT_NONE: break; } [trackPlayTimeID setStringValue: "-:--:--"]; [trackRemTimeID setStringValue: "-:--:--"]; if( toc.info[100].min >= 60 ) { toc.info[100].hour++ ; toc.info[100].min -= 60; } sprintf (buff, "%u:%02u:%02u", toc.info[100].hour, toc.info[100].min, toc.info[100].sec); [discRemTimeID setStringValue: buff]; if ( ! toc.discTitle ) [cdTitleID setStringValue:"title of CD is not known"]; else { tempTitle[0] = '\0'; if ( toc.discPerformer ) { strcat(tempTitle, toc.discPerformer); strcat(tempTitle, " - "); } strcat(tempTitle, toc.discTitle); [cdTitleID setStringValue: tempTitle]; } /* display the table of contents to the window, as rich-text */ songInfoStream = NXOpenMemory(NULL, 0, NX_READWRITE); NXPrintf(songInfoStream, "{\\rtf0\\ansi{\\fonttbl\\f0\\fmodern Ohlfs;"); NXPrintf(songInfoStream, "\\f1\\fnil Times-Roman;}\n"); NXPrintf(songInfoStream, "\\pard\\tx460\\tx1060\\tx1260"); NXPrintf(songInfoStream, "\\f0\\b0\\i0\\ulnone\\fs18"); NXPrintf(songInfoStream, "\\fi-1260\\li1260\\fc0\\cf0\n"); for ( track = toc.firstCDtrack; track <= toc.lastCDtrack; track++ ) { isAudio= YES; if ( toc.info[track].control & DATA_TRACK ) isAudio = NO; NXPrintf(songInfoStream, "\\f0\\fs18%3d)\t", track); if ( isAudio ) { NXPrintf(songInfoStream, "%02d:%02d", toc.info[track].elapsedSec / 60, toc.info[track].elapsedSec % 60); } else { NXPrintf(songInfoStream, "==:=="); } NXPrintf(songInfoStream, "\t-\t\\f1\\fs24 "); if ( toc.info[track].trackTitle ) NXPrintf(songInfoStream, "%s", toc.info[track].trackTitle); else if (isAudio) NXPrintf(songInfoStream, "???"); else NXPrintf(songInfoStream, "[data track]"); if ( track != toc.lastCDtrack ) NXPrintf(songInfoStream, "\\\n"); } NXPrintf(songInfoStream, "\n}\n"); NXSeek(songInfoStream, 0, NX_FROMSTART); [trackInfoID selectAll:self]; [trackInfoID replaceSelWithRichText: songInfoStream]; NXCloseMemory(songInfoStream, NX_FREEBUFFER); return self; } - pauseCD:sender { /* note that this method is called from both the pause * button and a menu item. Due to the menu item, this * has to directly set the state of the button (instead * of just sending a message to "sender"). */ do_readcurrentposition_42(cd_fd, &cd_curpos, &rcp_Ereply); switch (cd_curpos.rsc_audio_status) { case RSC_ASTAT_PLAYING: /* it is playing, switch it to pause */ do_pauseaudio_4b(cd_fd, 1, &cd_Ereply); [pauseButtonID setIntValue:1]; break; case RSC_ASTAT_PAUSED: /* it is paused, switch it to playing */ do_pauseaudio_4b(cd_fd, 0, &cd_Ereply); [pauseButtonID setIntValue:0]; break; case RSC_ASTAT_PLAYCOMPLETE: case RSC_ASTAT_PLAYABORTED: case RSC_ASTAT_NONE: /* do nothing but ensure pause button is right */ [pauseButtonID setIntValue:0]; break; } return self; } - playCD:sender { int sTrack = toc.firstAtrack, eTrack = toc.lastAtrack; [self playTracks:sTrack to: eTrack]; return self; } - playTrackRange: sender { /* target for the play-range button in the select track range panel */ int sTrack, eTrack; sTrack = [newStartTrackID intValue]; eTrack = [newEndTrackID intValue]; [self playTracks:sTrack to: eTrack]; [trackRangePanel performClose:self]; return self; } - setLeftVolume:sender { [self setPlayVolumes:[sender intValue] :-1]; return self; } - setRightVolume:sender { [self setPlayVolumes:-1 :[sender intValue]]; return self; } - setPlayVolumes:(int) leftVol :(int) rightVol { int devReady; if ( cd_fd == 0 ) return self; /* no device yet, ignore */ devReady = do_testunitready(cd_fd, &cd_Tval, &tur_Ereply); if ( devReady ) return self; /* device is not ready, ignore */ if ( [globPrefs separateVolumes] || ((leftVol < 0) && (rightVol < 0))) { /* need to know current volume settings */ do_modesense_pc_E(cd_fd, &cd_volumes, &cd_Ereply); if (leftVol < 0) leftVol = cd_volumes.ch0_vol; if (rightVol < 0) rightVol = cd_volumes.ch1_vol; } else { if (leftVol < 0) { cd_volumes.ch0_vol = leftVol = rightVol; [leftVolumeSliderID setIntValue:leftVol]; } if (rightVol < 0) { cd_volumes.ch1_vol = rightVol = leftVol; [rightVolumeSliderID setIntValue:rightVol]; } } do_modeselect_pc_E(cd_fd, leftVol, rightVol, &cd_Ereply); return self; } /* the following used by PrefController */ - getVolumes:(int *)leftVolPtr :(int *)rightVolPtr { do_modesense_pc_E(cd_fd, &cd_volumes, &cd_Ereply); *leftVolPtr = cd_volumes.ch0_vol; *rightVolPtr = cd_volumes.ch1_vol; return self; } - showSelectTrackRange:sender { [newStartTrackID setIntValue: toc.firstAtrack]; [newEndTrackID setIntValue: toc.lastAtrack]; [trackRangePanel makeKeyAndOrderFront:self]; [newStartTrackID selectText:self]; return self; } - goNextTrack:sender { int sTrack, eTrack; if ( cd_curpos.track >= toc.lastAtrack ) { return self; } sTrack = cd_curpos.track + 1; eTrack = toc.lastAtrack; /* last audio track */ [self playTracks:sTrack to: eTrack]; return self; } - goPreviousTrack:sender { int sTrack, eTrack; /* goes to the beginning of the current track if we're more than a few seconds into it, otherwise the previous track */ sTrack = cd_curpos.track; if ( (0 == cd_curpos.rel_hour) && (0 == cd_curpos.rel_min) && (5 > cd_curpos.rel_sec) ) { sTrack = cd_curpos.track - 1; } if ( sTrack < toc.firstAtrack ) sTrack = toc.firstAtrack; eTrack = toc.lastAtrack; /* last audio track */ [self playTracks:sTrack to: eTrack]; return self; } /* you might ask, why does this go thru all the trouble of * finding starting and ending times and doing a playaudio_msf * instead of just playing a track range? * * The answer is that I want flexibility such that the CD database * can *change* the start and end times for a track, for those * CD's which were manufactured with the wrong starting times. */ - playTracks:(int)startTrack to: (int) endTrack { struct pa_msf tst_start = {0, 0, 0}; struct pa_msf tst_end = {0, 0, 0}; int aeTrack; int devReady; if ( cd_fd == 0 ) return self; /* no device currently, ignore */ devReady = do_testunitready(cd_fd, &cd_Tval, &tur_Ereply); if ( devReady ) return self; /* device is not ready, ignore */ /* calc the track after the end track*/ if ( endTrack >= toc.lastCDtrack ) aeTrack = 100; else aeTrack = endTrack + 1; /* note that do_playaudio_msf_47 has no hours variable... */ tst_start.min = (toc.info[startTrack].hour * 60) + toc.info[startTrack].min; tst_start.sec = toc.info[startTrack].sec; tst_start.frame = toc.info[startTrack].frame; tst_end.min = (toc.info[aeTrack].hour * 60) + toc.info[aeTrack].min; tst_end.sec = toc.info[aeTrack].sec; tst_end.frame = toc.info[aeTrack].frame; if ( tst_end.frame > 0 ) tst_end.frame--; else { tst_end.frame = 74; if ( tst_end.sec > 0 ) tst_end.sec--; else { tst_end.sec = 59; if ( tst_end.min > 0 ) tst_end.min--; } } if ( [globPrefs consoleDebugMsgs] ) { printf("mCD debug: start (%d) %02u:%02u-%02u", startTrack, tst_start.min, tst_start.sec, tst_start.frame); printf(" end (%d) %02u:%02u-%02u\n", endTrack, tst_end.min, tst_end.sec, tst_end.frame); } do_playaudio_msf_47(cd_fd, tst_start, tst_end, &cd_Ereply); /* make sure the volumes are set right (doing a playaudio command * causes the CD to play at full volume, even if modesense shows * that some other volume level has been set) */ do_modesense_pc_E(cd_fd, &cd_volumes, &cd_Ereply); [leftVolumeSliderID setIntValue: cd_volumes.ch0_vol]; [rightVolumeSliderID setIntValue: cd_volumes.ch1_vol]; [self setPlayVolumes:cd_volumes.ch0_vol :cd_volumes.ch1_vol]; /* ensure pause button is set right */ [pauseButtonID setIntValue:0]; return self; } - stopCD:sender { int devReady; char buff[40]; if ( cd_fd == 0 ) return self; /* no device yet, ignore */ devReady = do_testunitready(cd_fd, &cd_Tval, &tur_Ereply); if ( devReady ) return self; /* device is not ready, ignore */ do_rezerounit_01(cd_fd, &cd_Ereply); do_stopunit_1b(cd_fd, &cd_Tval, &cd_Ereply); curCdIndex = -1; curAbsSecond = -1; /* probably should reset time-displaying fields differently */ [trackRemTimeID setStringValue: "-:--:--"]; if( toc.info[100].min >= 60 ) { toc.info[100].hour++ ; toc.info[100].min -= 60; } sprintf (buff, "%u:%02u:%02u", toc.info[100].hour, toc.info[100].min, toc.info[100].sec); [discRemTimeID setStringValue: buff]; return self; } - copyMcdEntry:sender { int track; BOOL isAudio; NXStream *mstream; id pboard; NXAtom ptypes[1]; /* note: probably should check that there really is an mCD entry * to copy at this point in time... */ mstream = NXOpenMemory(NULL, 0, NX_READWRITE); if( toc.info[100].min >= 60 ) { toc.info[100].hour++ ; toc.info[100].min -= 60; } NXPrintf(mstream, "%u:%02u:%02u\t", toc.info[100].hour, toc.info[100].min, toc.info[100].sec); if ( ! toc.discTitle ) NXPrintf(mstream, "**artist unknown**\t- ** title unknown **"); else { if ( ! toc.discPerformer ) NXPrintf(mstream, "**artist unknown**"); else NXPrintf(mstream, "%s", toc.discPerformer); NXPrintf(mstream, "\t- %s", toc.discTitle); } if ( toc.discCatNum ) NXPrintf(mstream, "\tUPC=%s", toc.discCatNum); NXPrintf(mstream, "\n"); /* include the table of contents */ for ( track = toc.firstCDtrack; track <= toc.lastCDtrack; track++ ) { isAudio= YES; if ( toc.info[track].control & DATA_TRACK ) isAudio = NO; NXPrintf(mstream, "%4d)\t", track); if ( isAudio ) { NXPrintf(mstream, "%02d:%02d\t- ", toc.info[track].elapsedSec / 60, toc.info[track].elapsedSec % 60); } else { NXPrintf(mstream, "==:==\t- "); } if ( toc.info[track].trackTitle ) NXPrintf(mstream, "%s", toc.info[track].trackTitle); else if (isAudio) NXPrintf(mstream, "???"); else NXPrintf(mstream, "[data track]"); NXPrintf(mstream, "\n"); } NXPrintf(mstream, "\n"); pboard = [Pasteboard new]; ptypes[0] = NXAsciiPboardType; [pboard declareTypes:ptypes num:1 owner:self]; [pboard writeType:NXAsciiPboardType fromStream:mstream]; NXCloseMemory(mstream, NX_FREEBUFFER); return self; } - copyMcdEntryAsObjC:sender { u_int key; int track; BOOL isAudio; NXStream *mstream; id pboard; NXAtom ptypes[1]; /* note: probably should check that there really is an mCD entry * to copy at this point in time... */ mstream = NXOpenMemory(NULL, 0, NX_READWRITE); key = [cd_dbase indexKey:&toc]; NXPrintf(mstream, "#define %s %10u /* key parts = %d.%02u.%02d.%d %d */\n", "0000_none_0000", key, toc.info[100].elapsedSec, toc.info[100].frame, toc.lastCDtrack, toc.info[1].elapsedSec, toc.info[toc.lastCDtrack].elapsedSec); if( toc.info[100].min >= 60 ) { toc.info[100].hour++ ; toc.info[100].min -= 60; } NXPrintf(mstream, " if ( cdKey == %s ) { /* %u:%02u:%02u-%02u */\n", "0000_none_0000", toc.info[100].hour, toc.info[100].min, toc.info[100].sec, toc.info[100].frame); if ( ! toc.discPerformer ) NXPrintf(mstream, "\ttocPtr->discPerformer = \"performer??\";\n"); else NXPrintf(mstream, "\ttocPtr->discPerformer = \"%s\";\n", toc.discPerformer); if ( ! toc.discTitle ) NXPrintf(mstream, "\ttocPtr->discTitle = \"title??\";\n"); else NXPrintf(mstream, "\ttocPtr->discTitle = \"%s\";\n", toc.discTitle); if ( toc.discCatNum ) NXPrintf(mstream, "\ttocPtr->discCatNum = \"%s\";\n", toc.discCatNum); #ifdef ADD_SKIPSONGS NXPrintf(mstream, "# if !defined(SKIPSONGS_\n"); #endif /* include the table of contents */ for ( track = toc.firstCDtrack; track <= toc.lastCDtrack; track++ ) { isAudio= YES; if ( toc.info[track].control & DATA_TRACK ) isAudio = NO; NXPrintf(mstream, "\tSET_CDtt(%2d, %02d.%02d, \"", track, toc.info[track].elapsedSec / 60, toc.info[track].elapsedSec % 60); if ( toc.info[track].trackTitle ) NXPrintf(mstream, "%s", toc.info[track].trackTitle); else if (!isAudio) NXPrintf(mstream, "[data track]"); NXPrintf(mstream, "\");\n"); } /* finish off the entry */ #ifdef ADD_SKIPSONGS NXPrintf(mstream, "# endif\n"); #endif NXPrintf(mstream, "\t}\n"); pboard = [Pasteboard new]; ptypes[0] = NXAsciiPboardType; [pboard declareTypes:ptypes num:1 owner:self]; [pboard writeType:NXAsciiPboardType fromStream:mstream]; NXCloseMemory(mstream, NX_FREEBUFFER); return self; } - pasteMcdEntry:sender { return self; } - (BOOL)openCdFd:(BOOL)tryToLoad { int devReady; strcpy(rawDevName, [globPrefs rawDeviceName]); if ( cd_fd > 0 ) close( cd_fd ); if (tryToLoad == FROM_LOAD_CD_REQUEST) { cd_fd = open( rawDevName, O_RDONLY ); if ( cd_fd < 0 ) { NXRunAlertPanel(0, "Error return from open() for device %s", 0, 0, 0, rawDevName); cd_fd = 0; return NO; } } else { cd_fd = open( rawDevName, O_RDONLY | O_NDELAY ); if ( cd_fd < 0 ) { cd_fd = 0; return NO; } } /* should check the result of do_inquiry to see which model * of CD-ROM drive is there (so this would know which SCSI * commands can be sent to the drive) */ do_inquiry(cd_fd, &cd_Inq, &cd_Ereply); if ( cd_Inq.ir_devicetype != DEVTYPE_CDROM ) { if (tryToLoad == FROM_LOAD_CD_REQUEST) { NXRunAlertPanel(0, "Device %s does not seem to be a CD-ROM drive", 0, 0, 0, rawDevName); } else { printf("Device %s does not seem to be a CD-ROM drive\n", rawDevName); } close( cd_fd ); return NO; } devReady = do_testunitready(cd_fd, &cd_Tval, &tur_Ereply); if ( cd_Ereply.er_sensekey == SENSE_UNITATTENTION ) { /* drive is signalling attention because there is a new disc * in the drive since the last time it was checked. In this * context (loading in a CD), we don't care that it's new */ devReady = do_testunitready(cd_fd, &cd_Tval, &tur_Ereply); } if ( (tryToLoad == FROM_LOAD_CD_REQUEST) && (devReady == -1) && (cd_Ereply.er_sensekey == SENSE_NOSENSE) ) { /* I don't understand what the culprit is that forces this * special case. It is needed on my NS/Intel box, but only * on the *first* time the "Load" button is selected after * the system is rebooted. For some reason, "loads" at that * point will not succeed unless an unload is first done on * the device... */ int err; printf("mCD; device not ready, unloading and reloading the CD\n"); err = ioctl(cd_fd, DKIOCEJECT, NULL); close( cd_fd ); cd_fd = 0; cd_fd = open( rawDevName, O_RDONLY ); if ( cd_fd < 0 ) { NXRunAlertPanel(0, "Error return from second open() on device %s", 0, 0, 0, rawDevName); cd_fd = 0; return NO; } devReady = do_testunitready(cd_fd, &cd_Tval, &tur_Ereply); } if ( devReady ) { if (tryToLoad == FROM_LOAD_CD_REQUEST) { printf("mCD: Device %s is not ready (%d, %d)\n", rawDevName, devReady, cd_Ereply.er_sensekey); /* the cd_fd is not freed here, on purpose */ /* ------- * Hmm. I think I'll change my mind on that. I forget * why I didn't want to free it, but now I think I do * at least for one case. */ if ((devReady == -1) && (cd_Ereply.er_sensekey == SENSE_NOSENSE)) { int err; printf("mCD; device not ready, unloading the CD\n"); err = ioctl(cd_fd, DKIOCEJECT, NULL); close( cd_fd ); cd_fd = 0; } } else { /* freeing cd_fd in this case might be a preference option * to add someday... */ close( cd_fd ); } return NO; } if (tryToLoad == FROM_CD_APP_DID_INIT) { /* need to think more about under what circumstances the current * volumes should be set from global preference values */ int leftInt, rightInt; [globPrefs getVolumes:&leftInt:&rightInt]; cd_volumes.ch0_vol = leftInt; cd_volumes.ch1_vol = rightInt; [self setPlayVolumes:cd_volumes.ch0_vol :cd_volumes.ch1_vol]; } else { do_modesense_pc_E(cd_fd, &cd_volumes, &cd_Ereply); } [leftVolumeSliderID setIntValue: cd_volumes.ch0_vol]; [rightVolumeSliderID setIntValue: cd_volumes.ch1_vol]; curCdIndex = -1; curAbsSecond = -1; /* do_preventremoval_1e(cd_fd, YES, &cd_Ereply); */ do_timed_updates = YES; /* initial testing */ [loadButtonID setEnabled:NO]; [playButtonID setEnabled:YES]; [pauseButtonID setEnabled:YES]; [stopButtonID setEnabled:YES]; [unloadButtonID setEnabled:YES]; return YES; /* successfully setup */ } - fillTocTitles { u_int key; struct rsc_media_catnum_reply cd_catnum; char origCatNumber[MEDIA_CATNUM_LENGTH]; BOOL switchUPC; key = [cd_dbase indexKey:&toc]; setreuid(-1, geteuid()); /* switch to real uid */ [cd_dbase fillTocTitles:&toc givenKey:key]; setreuid(-1, 0); /* back to being root */ /* check out the media catalog number, if this CD has one */ switch (cd_curpos.rsc_audio_status) { case RSC_ASTAT_PLAYING: /* don't read the media catalog number. Trying to read * it will stop the CD on the Apple CD300 and NeXT * drives. [on the Toshiba drive, it doesn't stop the * music, and in fact the command sometimes won't work * return the catalog number unless the CD *is* playing!] */ if ( !toc.discCatNum && [globPrefs consoleDebugMsgs] ) { printf("mCD info: didn't check for media catalog number\n"); } break; case RSC_ASTAT_PAUSED: /* it'd be nice to take the same precaution when the cd-rom * drive is paused, but it seems that the CD-ROM drive that * NeXT used to sell (a Sony mechanism) returns "paused" at * times when it's not really paused. The Apple CD300 (a * newer Sony mechanism) doesn't seem to have this problem. */ case RSC_ASTAT_PLAYCOMPLETE: case RSC_ASTAT_PLAYABORTED: case RSC_ASTAT_NONE: switchUPC = NO; do_readmediacatnum_42(cd_fd, &cd_catnum, &cd_Ereply); if ( cd_catnum.catnum_isSet ) { memcpy(origCatNumber, cd_catnum.media_catnum, MEDIA_CATNUM_LENGTH); fixCatNum(&cd_catnum); if (!toc.discCatNum) switchUPC = YES; else { if ( strncmp(toc.discCatNum, cd_catnum.media_catnum, MEDIA_CATNUM_LENGTH)) { /* the UPC in our database does not match the UPC this * drive found on the CD itself. First have to check * for lame NeXT-CD-ROM case (which misses 3 chars) */ if ( cd_catnum.media_catnum[MEDIA_CATNUM_LENGTH-1] != '?') switchUPC = YES; else { if ( strncmp(toc.discCatNum,cd_catnum.media_catnum, MEDIA_CATNUM_LENGTH-3)) switchUPC = YES; } } if ( switchUPC && [globPrefs consoleDebugMsgs]) { /* tell the user of mismatch */ int i; u_char *cptr; printf("mCD info: original UPC = x'"); cptr = &(origCatNumber[0]); printf("%.2X", *cptr++); /* ignore comp. warning */ for (i=1; i<MEDIA_CATNUM_LENGTH; i++) { printf(" %.2X", *cptr++); /* ignore comp. warning */ } printf("'\n"); printf("mCD info: fixed up UPC = \"%.15s\"\n", cd_catnum.media_catnum); printf("mCD info: DBase return = \"%15s\"\n", toc.discCatNum); printf("mCD info: currently using the fixed upCD's catalog number\n"); } } } if ( switchUPC ) { memcpy(holdCatNumber, cd_catnum.media_catnum, MEDIA_CATNUM_LENGTH); holdCatNumber[MEDIA_CATNUM_LENGTH+1] = '\0'; toc.discCatNum = &(holdCatNumber[0]); } /* Redoing the "spinup" here seems to reduce the problem * of getting a unit attn when do_readmediacatnum finds * a media code on the music CD */ do_spinup_1b(cd_fd, &cd_Tval, &cd_Ereply); break; } return self; } @end /* I've been testing this program on three different models of CD-ROM * drives. Each one returns the catalog number in a different format. * In the case of the CD-ROM drive NeXT used to sell, it doesn't even * return the whole number. Do the best we can to map the three * formats (that I know of so far) into a common format. * * Apple CD300 UPC = '3030 3735 3939 3235 3739 3432 3130 30' * DEC-Toshiba UPC = '0000 0705 0909 0205 0709 0402 0100 00' * NeXT CD-ROM UPC = '0075 9925 7942 0000 0000 0000 0000 00' * * Not sure if this routine should really be in the scsi_cd subproj... */ void fixCatNum(catNumPtr) struct rsc_media_catnum_reply *catNumPtr; { char *tempPtr, *endPtr; BOOL all_char, all_dec; all_char = YES; /* all bytes are characters '0' to '9' */ all_dec = YES; /* all bytes are 0x00 to 0x09 */ tempPtr = &(catNumPtr->media_catnum[0]); endPtr = tempPtr + MEDIA_CATNUM_LENGTH; for (; tempPtr < endPtr; tempPtr++) { if (*tempPtr < '0' || *tempPtr > '9') all_char = NO; if (*tempPtr > 0x09) all_dec = NO; } if (all_dec) { /* all decimal, change them to all-characters */ tempPtr = catNumPtr->media_catnum; for (; tempPtr < endPtr; tempPtr++) *tempPtr += 0x30; all_dec = NO; all_char = YES; } if (all_char) return; /* the NeXT format, have to expand it, going right-to-left */ /* [done the brute-force way, and assumes MEDIA_CATNUM_LENGTH = 15] */ catNumPtr->media_catnum[14] = (catNumPtr->media_catnum[7] >> 4) + 0x30; catNumPtr->media_catnum[13] = (catNumPtr->media_catnum[6] & 0x0F) + 0x30; catNumPtr->media_catnum[12] = (catNumPtr->media_catnum[6] >> 4) + 0x30; catNumPtr->media_catnum[11] = (catNumPtr->media_catnum[5] & 0x0F) + 0x30; catNumPtr->media_catnum[10] = (catNumPtr->media_catnum[5] >> 4) + 0x30; catNumPtr->media_catnum[9] = (catNumPtr->media_catnum[4] & 0x0F) + 0x30; catNumPtr->media_catnum[8] = (catNumPtr->media_catnum[4] >> 4) + 0x30; catNumPtr->media_catnum[7] = (catNumPtr->media_catnum[3] & 0x0F) + 0x30; catNumPtr->media_catnum[6] = (catNumPtr->media_catnum[3] >> 4) + 0x30; catNumPtr->media_catnum[5] = (catNumPtr->media_catnum[2] & 0x0F) + 0x30; catNumPtr->media_catnum[4] = (catNumPtr->media_catnum[2] >> 4) + 0x30; catNumPtr->media_catnum[3] = (catNumPtr->media_catnum[1] & 0x0F) + 0x30; catNumPtr->media_catnum[2] = (catNumPtr->media_catnum[1] >> 4) + 0x30; catNumPtr->media_catnum[1] = (catNumPtr->media_catnum[0] & 0x0F) + 0x30; catNumPtr->media_catnum[0] = (catNumPtr->media_catnum[0] >> 4) + 0x30; /* and after all that, we probably have three zeros at the end, * which most-likely means we've lost the last three digits. * Put in question-marks, I guess. */ if ( (catNumPtr->media_catnum[14] == '0') && (catNumPtr->media_catnum[13] == '0') && (catNumPtr->media_catnum[12] == '0') ) { catNumPtr->media_catnum[12] = '?'; catNumPtr->media_catnum[13] = '?'; catNumPtr->media_catnum[14] = '?'; } }
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.