function modelName=MacModelName % modelName=MacModelName % Return the model name of the Macintosh. % % OSX: Get the computer identifier as reported by open firmware. In the % OSX Psychtoolbox that is provided in the struct returned by % Screen('Computer'). Convert that identifier to Apple's marketing % name for the computer. To do that we use the mapping of identifier to % product names found in (up to and including macOS Mojave): % /System/Library/SystemProfiler/SPPlatformReporter.spreporter/Contents/ % Resources/English.lproj/Localizable.strings. % In macOS Catalina: % /System/Library/SystemProfiler/SPPlatformReporter.spreporter/Contents/ % Resources/en.lproj/Localizable.strings % There is no official Apple-recommended way to uncover the marketing % name. However, this method works and should be reasonably robust. % % See also: Screen('Computer?'), OSName, AppleVersion % HISTORY % % 2004 awi Wrote it. % 1/29/05 dgp Cosmetic. % 3/14/05 dgp Fixed handling of model names that end in "\"". % 3/5/06 awi Added note explaining new bug and how to repair. % 5/29/06 mk Added dumb fix for endian issue on new Intel-Macs. % 9/20/09 mk Don't fail with name 'Unknown' but output raw model name then. % 2/17/21 dgp and Darshan Thapa. macOS Catalina changed the path % of the property list file, so the code now tries both old and % new paths. % NOTES % % As of 10.4.5 Apple has changed the file format in which System Profiler % stores the map of system firmware IDs to computer model names. The map % is now a property list stored in this file: % /System/Library/SystemProfiler/SPPlatformReporter.spreporter/Contents/Resources/SPMachineTypes.plist % % The easiest and most structured way to adapt the PTB to this change would % be to provide a mex function which reads a property list file and returns % a MATLAB structure holding the contents of the property list. Because % functions provided by MacOS would do the work of parsing the XML property % list file, that should be a simple program to write. Otherwise, there % might even already exist XML parsers for MATLAB. if IsOSX % The mapping file is in Unicode format, which MATLAB does not % understand. We can't treat the content as characters without doing % some work first. It would be nice if we could use built-in MATLAB % routines such as fgetl, but no dice. persistent modelNameCache if ~isempty(modelNameCache) modelName=modelNameCache; return end for i=1:2 switch i case 1 % for macOS up to Mojave mappingFileName='/System/Library/SystemProfiler/SPPlatformReporter.spreporter/Contents/Resources/English.lproj/Localizable.strings'; case 2 % for macOS Catalina. (Later versions of macOS not yet tested.) mappingFileName='/System/Library/SystemProfiler/SPPlatformReporter.spreporter/Contents/Resources/en.lproj/Localizable.strings'; end fp=fopen(mappingFileName,'r'); if fp~=-1 break end end if fp==-1 error(['Failed to open name-mapping file: ' mappingFileName]); end fileWords=fread(fp,inf,'uint16'); fclose(fp); % Unicode files start with hex feff or ffef to indicate byte order. % Confirm that it's feff, and then strip it off. if fileWords(1)~=hex2dec('feff'); % Convert fileWords by first casting it to double type, then % kind of endian-swapping. We throw away the least significant % byte value and store the most significant byte as value. This % would discard any former low-bytes. Dont know if this will work. fileWords=floor(double(fileWords)/256); end fileWords=fileWords(2:end); fileCharsRaw=char(fileWords); % Strip out comments. commentsOnFlag=0; fileChars=''; i=1; while i <= length(fileCharsRaw)-2 %we don't care about comment toggles in the last two characters. if commentsOnFlag==0 && fileCharsRaw(i) == '/' && fileCharsRaw(i+1) == '*' commentsOnFlag=1; i=i+2; elseif commentsOnFlag==1 && fileCharsRaw(i) == '*' && fileCharsRaw(i+1) == '/' commentsOnFlag=0; i=i+2; else if ~commentsOnFlag fileChars(end+1)=fileCharsRaw(i); end i=i+1; end end % Break the file into lines. lineBreakChar=char(10); currentLineIndex=1; currentCharIndex=1; fileLines{currentLineIndex}=''; while currentCharIndex <= length(fileChars) if fileChars(currentCharIndex)==lineBreakChar; currentLineIndex=currentLineIndex+1; fileLines{currentLineIndex}=''; else fileLines{currentLineIndex}(end+1)=fileChars(currentCharIndex); end currentCharIndex=currentCharIndex+1; end % Strip out blank lines. blanckLineIndices=find(streq(fileLines, '')); contentLineIndices=setdiff(1:length(fileLines), blanckLineIndices); contentLines={fileLines{contentLineIndices}}; % We now have a cell array of content lines. Consider the stuff we did up to now to be preprocessing. % Ad hoc parsing begins here. % % Use lines left and right of the equals sign to create a dictionary. KEY=1; ENTRY=2; acc=1; dictV1={}; for i=1:length(contentLines) equalLocations=find(contentLines{i}=='='); if length(equalLocations) == 1 dictV1{acc,KEY}=contentLines{i}(1:equalLocations-1); dictV1{acc,ENTRY}=contentLines{i}(equalLocations+1:end); acc=acc+1; elseif length(equalLocations) > 1 error('MacModelName encountered an entry with more than one equals sign and parsing failed'); % elseif length(equalLocations) < 1 % should not happen but ignore it. end end % Intereresting lines look like this: % "PowerBook5,4" = "PowerBook G4 15\""; % Uninteresting lines are distinguished by the absence of quotes on the % left: % number_processors = "Number Of CPUs"; % Filter out uninteresting lines % dictV2={}; acc=1; for i=1:length(dictV1); if find(dictV1{i,KEY}=='"') > 0; dictV2(acc,:)=dictV1(i,:); acc=acc+1; end end % Get the identifier and look it up in the dictionary. Note that the % dictionary key contains more characters than the platform identifier, % such as spaces and quotes, so we check only for membership, not % identity. c=Screen('Computer'); foundEntry=0; for i=1:length(dictV2); if strfind(dictV2{i,KEY}, c.hw.model) foundEntry=1; rawName=dictV2{i,ENTRY}; break; end end if foundEntry surroundingQuoteIndices=strfind(rawName,'"'); modelName=rawName(surroundingQuoteIndices(1)+1:surroundingQuoteIndices(end)-1); else modelName=c.hw.model; end if modelName(end-1)=='\' modelName=modelName([1:end-2 end]); end modelNameCache=modelName; return; end % If we reach this point then the modelName is unknown, because we were called % on an unsupported OS for this function: modelName = 'Unknown - Query unsupported on this OS.'; return;