macutils.inc 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563
  1. {
  2. $Id$
  3. This file is part of the Free Pascal run time library.
  4. Copyright (c) 2004 by Olle Raab
  5. Some utilities specific for Mac OS.
  6. Modified portions from Peter N. Lewis (PNL Libraries). Thanks !
  7. See the file COPYING.FPC, included in this distribution,
  8. for details about the copyright.
  9. This program is distributed in the hope that it will be useful,
  10. but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  12. **********************************************************************}
  13. {NOTE: This file requires the following global variables to be declared:
  14. workingDirectorySpec: FSSpec;}
  15. function FourCharCodeToLongword(fourcharcode: Shortstring): Longword;
  16. begin
  17. FourCharCodeToLongword:=
  18. (ord(fourcharcode[1]) shl 24) or
  19. (ord(fourcharcode[2]) shl 16) or
  20. (ord(fourcharcode[3]) shl 8) or
  21. (ord(fourcharcode[4]))
  22. end;
  23. function BitIsSet(arg: Longint; bitnr: Integer): Boolean;
  24. begin
  25. BitIsSet:= (arg and (1 shl bitnr)) <> 0;
  26. end;
  27. { Converts MacOS specific error codes to the correct FPC error code.
  28. All non zero MacOS errors corresponds to a nonzero FPC error.}
  29. Function MacOSErr2RTEerr(err: OSErr): Integer;
  30. var
  31. res: Integer;
  32. begin
  33. if err = noErr then { Else it will go through all the cases }
  34. res:= 0
  35. else case err of
  36. dirFulErr, { Directory full }
  37. dskFulErr { disk full }
  38. :res:=101;
  39. nsvErr { no such volume }
  40. :res:=3;
  41. ioErr, { I/O error (bummers) }
  42. bdNamErr { there may be no bad names in the final system! }
  43. :res:=1; //TODO Exchange to something better
  44. fnOpnErr { File not open }
  45. :res:=103;
  46. eofErr, { End of file }
  47. posErr { tried to position to before start of file (r/w) }
  48. :res:=100;
  49. mFulErr { memory full (open) or file won't fit (load) }
  50. :res:=1; //TODO Exchange to something better
  51. tmfoErr { too many files open}
  52. :res:=4;
  53. fnfErr { File not found }
  54. :res:=2;
  55. wPrErr { diskette is write protected. }
  56. :res:=150;
  57. fLckdErr { file is locked }
  58. :res:=5;
  59. vLckdErr { volume is locked }
  60. :res:=150;
  61. fBsyErr { File is busy (delete) }
  62. :res:=5;
  63. dupFNErr { duplicate filename (rename) }
  64. :res:=5;
  65. opWrErr { file already open with with write permission }
  66. :res:=5;
  67. rfNumErr, { refnum error }
  68. gfpErr { get file position error }
  69. :res:=1; //TODO Exchange to something better
  70. volOffLinErr { volume not on line error (was Ejected) }
  71. :res:=152;
  72. permErr { permissions error (on file open) }
  73. :res:=5;
  74. volOnLinErr{ drive volume already on-line at MountVol }
  75. :res:=1; //TODO Exchange to something other
  76. nsDrvErr { no such drive (tried to mount a bad drive num) }
  77. :res:=1; //TODO Perhaps exchange to something better
  78. noMacDskErr, { not a mac diskette (sig bytes are wrong) }
  79. extFSErr { volume in question belongs to an external fs }
  80. :res:=157; //TODO Perhaps exchange to something better
  81. fsRnErr, { file system internal error:during rename the old
  82. entry was deleted but could not be restored. }
  83. badMDBErr { bad master directory block }
  84. :res:=1; //TODO Exchange to something better
  85. wrPermErr { write permissions error }
  86. :res:=5;
  87. dirNFErr { Directory not found }
  88. :res:=3;
  89. tmwdoErr { No free WDCB available }
  90. :res:=1; //TODO Exchange to something better
  91. badMovErr { Move into offspring error }
  92. :res:=5;
  93. wrgVolTypErr { Wrong volume type error [operation not
  94. supported for MFS] }
  95. :res:=1; //TODO Exchange to something better
  96. volGoneErr { Server volume has been disconnected. }
  97. :res:=152;
  98. diffVolErr { files on different volumes }
  99. :res:=17;
  100. catChangedErr { the catalog has been modified }
  101. { OR comment: when searching with PBCatSearch }
  102. :res:=1; //TODO Exchange to something other
  103. afpAccessDenied, { Insufficient access privileges for operation }
  104. afpDenyConflict { Specified open/deny modes conflict with current open modes }
  105. :res:=5;
  106. afpNoMoreLocks { Maximum lock limit reached }
  107. :res:=5;
  108. afpRangeNotLocked, { Tried to unlock range that was not locked by user }
  109. afpRangeOverlap { Some or all of range already locked by same user }
  110. :res:=1; //TODO Exchange to something better
  111. afpObjectTypeErr { File/Directory specified where Directory/File expected }
  112. :res:=3;
  113. afpCatalogChanged { OR comment: when searching with PBCatSearch }
  114. :res:=1; //TODO Exchange to something other
  115. afpSameObjectErr
  116. :res:=5; //TODO Exchange to something better
  117. memFullErr { Not enough room in heap zone }
  118. :res:=203;
  119. else
  120. res := 1; //TODO Exchange to something better
  121. end;
  122. MacOSErr2RTEerr:= res;
  123. end;
  124. {Translates a unix or dos path to a mac path. Even a mac path can be input, }
  125. {then it is returned as is. A trailing directory separator in input}
  126. {will result in a trailing mac directory separator. For absolute paths, the }
  127. {parameter mpw affects how the root volume is denoted. If mpw is true, }
  128. {the path is intended for use in MPW, and the environment variable Boot is}
  129. {prepended. Otherwise the actual boot volume name is appended.}
  130. {All kinds of paths are attempted to be translated, except the unusal }
  131. {dos construct: a relative path on a certain drive like : C:xxx\yyy}
  132. function TranslatePathToMac (const path: string; mpw: Boolean): string;
  133. function GetVolumeIdentifier: string;
  134. var
  135. s: Str255;
  136. dummy: Integer;
  137. err: OSErr;
  138. begin
  139. if mpw then
  140. GetVolumeIdentifier := '{Boot}'
  141. else
  142. GetVolumeIdentifier := macosBootVolumeName;
  143. end;
  144. var
  145. slashPos, oldpos, newpos, oldlen, maxpos: Longint;
  146. begin
  147. oldpos := 1;
  148. slashPos := Pos('/', path);
  149. if (slashPos <> 0) then {its a unix path}
  150. begin
  151. if slashPos = 1 then
  152. begin {its a full path}
  153. oldpos := 2;
  154. TranslatePathToMac := GetVolumeIdentifier;
  155. end
  156. else {its a partial path}
  157. TranslatePathToMac := ':';
  158. end
  159. else
  160. begin
  161. slashPos := Pos('\', path);
  162. if (slashPos <> 0) then {its a dos path}
  163. begin
  164. if slashPos = 1 then
  165. begin {its a full path, without drive letter}
  166. oldpos := 2;
  167. TranslatePathToMac := GetVolumeIdentifier;
  168. end
  169. else if (Length(path) >= 2) and (path[2] = ':') then {its a full path, with drive letter}
  170. begin
  171. oldpos := 4;
  172. TranslatePathToMac := GetVolumeIdentifier;
  173. end
  174. else {its a partial path}
  175. TranslatePathToMac := ':';
  176. end;
  177. end;
  178. if (slashPos <> 0) then {its a unix or dos path}
  179. begin
  180. {Translate "/../" to "::" , "/./" to ":" and "/" to ":" }
  181. newpos := Length(TranslatePathToMac);
  182. oldlen := Length(path);
  183. SetLength(TranslatePathToMac, newpos + oldlen); {It will be no longer than what is already}
  184. {prepended plus length of path.}
  185. maxpos := Length(TranslatePathToMac); {Get real maxpos, can be short if String is ShortString}
  186. {There is never a slash in the beginning, because either it was an absolute path, and then the}
  187. {drive and slash was removed, or it was a relative path without a preceding slash.}
  188. while oldpos <= oldlen do
  189. begin
  190. {Check if special dirs, ./ or ../ }
  191. if path[oldPos] = '.' then
  192. if (oldpos + 1 <= oldlen) and (path[oldPos + 1] = '.') then
  193. begin
  194. if (oldpos + 2 > oldlen) or (path[oldPos + 2] in ['/', '\']) then
  195. begin
  196. {It is "../" or ".." translates to ":" }
  197. if newPos = maxPos then
  198. begin {Shouldn't actually happen, but..}
  199. Exit('');
  200. end;
  201. newPos := newPos + 1;
  202. TranslatePathToMac[newPos] := ':';
  203. oldPos := oldPos + 3;
  204. continue; {Start over again}
  205. end;
  206. end
  207. else if (oldpos + 1 > oldlen) or (path[oldPos + 1] in ['/', '\']) then
  208. begin
  209. {It is "./" or "." ignor it }
  210. oldPos := oldPos + 2;
  211. continue; {Start over again}
  212. end;
  213. {Collect file or dir name}
  214. while (oldpos <= oldlen) and not (path[oldPos] in ['/', '\']) do
  215. begin
  216. if newPos = maxPos then
  217. begin {Shouldn't actually happen, but..}
  218. Exit('');
  219. end;
  220. newPos := newPos + 1;
  221. TranslatePathToMac[newPos] := path[oldPos];
  222. oldPos := oldPos + 1;
  223. end;
  224. {When we come here there is either a slash or we are at the end.}
  225. if (oldpos <= oldlen) then
  226. begin
  227. if newPos = maxPos then
  228. begin {Shouldn't actually happen, but..}
  229. Exit('');
  230. end;
  231. newPos := newPos + 1;
  232. TranslatePathToMac[newPos] := ':';
  233. oldPos := oldPos + 1;
  234. end;
  235. end;
  236. SetLength(TranslatePathToMac, newpos);
  237. end
  238. else if (path = '.') then
  239. TranslatePathToMac := ':'
  240. else if (path = '..') then
  241. TranslatePathToMac := '::'
  242. else
  243. TranslatePathToMac := path; {its a mac path}
  244. end;
  245. {Concats the relative or full path path1 and the relative path path2.}
  246. function ConcatMacPath (path1, path2: string): string;
  247. begin
  248. if Pos(':', path1) = 0 then {its partial}
  249. Insert(':', path1, 1); {because otherwise it would be interpreted}
  250. {as a full path, when path2 is appended.}
  251. if path1[Length(path1)] = ':' then
  252. begin
  253. if path2[1] = ':' then
  254. begin
  255. Delete(path1, Length(path1), 1);
  256. ConcatMacPath := Concat(path1, path2)
  257. end
  258. else
  259. ConcatMacPath := Concat(path1, path2)
  260. end
  261. else
  262. begin
  263. if path2[1] = ':' then
  264. ConcatMacPath := Concat(path1, path2)
  265. else
  266. ConcatMacPath := Concat(path1, ':', path2)
  267. end;
  268. end;
  269. function IsMacFullPath (const path: string): Boolean;
  270. begin
  271. if Pos(':', path) = 0 then {its partial}
  272. IsMacFullPath := false
  273. else if path[1] = ':' then
  274. IsMacFullPath := false
  275. else
  276. IsMacFullPath := true
  277. end;
  278. function IsDirectory (var spec: FSSpec): Boolean;
  279. var
  280. err: OSErr;
  281. paramBlock: CInfoPBRec;
  282. begin
  283. with paramBlock do
  284. begin
  285. ioVRefNum := spec.vRefNum;
  286. ioDirID := spec.parID;
  287. ioNamePtr := @spec.name;
  288. ioFDirIndex := 0;
  289. err := PBGetCatInfoSync(@paramBlock);
  290. if err = noErr then
  291. IsDirectory := (paramBlock.ioFlAttrib and $10) <> 0
  292. else
  293. IsDirectory := false;
  294. end;
  295. end;
  296. {Gives the path for a given file or directory. If parent is true,
  297. a path to the directory, where the file or directory is located,
  298. is returned. Functioning even with System 6.}
  299. function FSpGetFullPath (spec: FSSpec; var fullPath: AnsiString;
  300. parent: Boolean): OSErr;
  301. var
  302. res: OSErr;
  303. pb: CInfoPBRec;
  304. begin
  305. res := noErr;
  306. if spec.parID = fsRtParID then { The object is a volume }
  307. begin
  308. if not parent then
  309. begin
  310. { Add a colon to make it a full pathname }
  311. fullPath:= spec.name + ':';
  312. end
  313. else
  314. begin
  315. fullPath:= '';
  316. res:= afpObjectTypeErr; {to have something close to this error.}
  317. end;
  318. end
  319. else
  320. begin
  321. { The object isn't a volume }
  322. { Add the object name }
  323. if not parent then
  324. fullPath:= spec.name
  325. else
  326. fullPath:= '';
  327. { Get the ancestor directory names }
  328. pb.ioNamePtr := @spec.name;
  329. pb.ioVRefNum := spec.vRefNum;
  330. pb.ioDrParID := spec.parID;
  331. repeat { loop until we have an error or find the root directory }
  332. begin
  333. pb.ioFDirIndex := -1;
  334. pb.ioDrDirID := pb.ioDrParID;
  335. res := PBGetCatInfoSync(@pb);
  336. if res = noErr then
  337. begin
  338. { Append colon to directory name }
  339. spec.name := spec.name + ':';
  340. { Add directory name to fullPathHandle }
  341. fullPath:= spec.name + fullPath;
  342. end
  343. end
  344. until not ((res = noErr) and (pb.ioDrDirID <> fsRtDirID));
  345. end;
  346. FSpGetFullPath := res;
  347. end;
  348. function PathArgToFSSpec(s: string; var spec: FSSpec): Integer;
  349. var
  350. err: OSErr;
  351. begin
  352. if pathTranslation then
  353. s := TranslatePathToMac(s, false);
  354. err:= FSMakeFSSpec(workingDirectorySpec.vRefNum,
  355. workingDirectorySpec.parID, s, spec);
  356. PathArgToFSSpec := MacOSErr2RTEerr(err);
  357. end;
  358. function PathArgToFullPath(s: string; var fullpath: AnsiString): Integer;
  359. var
  360. err: OSErr;
  361. res: Integer;
  362. spec: FSSpec;
  363. begin
  364. res:= PathArgToFSSpec(s, spec);
  365. if (res = 0) or (res = 2) then
  366. begin
  367. err:= FSpGetFullPath(spec, fullpath, false);
  368. PathArgToFullPath:= MacOSErr2RTEerr(err);
  369. end
  370. else
  371. PathArgToFullPath:=res;
  372. end;
  373. function GetVolumeName(vRefNum: Integer; var volName: String): OSErr;
  374. var
  375. pb: HParamBlockRec;
  376. begin
  377. pb.ioNamePtr := @volName;
  378. pb.ioVRefNum := vRefNum;
  379. pb.ioVolIndex := 0;
  380. PBHGetVInfoSync(@pb);
  381. volName:= volName + ':';
  382. GetVolumeName:= pb.ioResult;
  383. end;
  384. function GetWorkingDirectoryVRefNum: Integer;
  385. begin
  386. GetWorkingDirectoryVRefNum:= workingDirectorySpec.vRefNum;
  387. end;
  388. function GetVolInfo (var name: Str63; var vrn: integer; index: integer; var CrDate: longint): OSErr;
  389. var
  390. pb: ParamBlockRec;
  391. oe: OSErr;
  392. begin
  393. if (name <> '') and (name[length(name)] <> ':') then begin
  394. name := concat(name, ':');
  395. end;
  396. pb.ioNamePtr := @name;
  397. pb.ioVRefNum := vrn;
  398. pb.ioVolIndex := index;
  399. oe := PBGetVInfoSync(@pb);
  400. if oe = noErr then begin
  401. vrn := pb.ioVRefNum;
  402. CrDate := pb.ioVCrDate;
  403. end;
  404. GetVolInfo := oe;
  405. end;
  406. {Checks that fs really is an application with the specified creator}
  407. function ConfirmApplicationExists (creator: OSType; var fs: FSSpec): OSErr;
  408. var
  409. err: OSErr;
  410. info: FInfo;
  411. begin
  412. err := HGetFInfo(fs.vRefNum, fs.parID, fs.name, info);
  413. if err = noErr then begin
  414. if (info.fdType <> FourCharCodeToLongword('APPL')) or (info.fdCreator <> creator) then begin
  415. err := fnfErr;
  416. end;
  417. end;
  418. ConfirmApplicationExists := err;
  419. end;
  420. {Find an application with the given creator, in any of the mounted volumes.}
  421. function FindApplication (creator: OSType; var fs: FSSpec): OSErr;
  422. var
  423. i: integer;
  424. pbdt: DTPBRec;
  425. crdate: longint;
  426. oe: OSErr;
  427. found: Boolean;
  428. begin
  429. found := false;
  430. if (macosSystemVersion >= $0700) then begin
  431. i := 1;
  432. repeat
  433. fs.vRefNum := 0;
  434. {Get info for volume i}
  435. oe := GetVolInfo(fs.name, fs.vRefNum, i, crdate);
  436. i := i + 1;
  437. if oe = noErr then begin
  438. with pbdt do begin
  439. fs.name := '';
  440. ioNamePtr := @fs.name;
  441. ioVRefNum := fs.vRefNum;
  442. {Get the desktop database for this volume}
  443. oe := PBDTGetPath(@pbdt);
  444. if oe = noErr then begin
  445. ioFileCreator := creator;
  446. {Look first for the "default" (newest) application file}
  447. ioIndex := 0;
  448. oe := PBDTGetAPPLSync(@pbdt);
  449. if oe = noErr then begin
  450. fs.parID := pbdt.ioAPPLParID;
  451. found := ConfirmApplicationExists(creator,fs)=noErr;
  452. end;
  453. {If not ok, look for older ones.}
  454. if not found then begin
  455. ioIndex := 1;
  456. repeat
  457. oe := PBDTGetAPPLSync(@pbdt);
  458. if oe = noErr then begin
  459. fs.parID := pbdt.ioAPPLParID;
  460. found := ConfirmApplicationExists(creator,fs)=noErr;
  461. end;
  462. ioIndex := ioIndex + 1;
  463. until found or (oe <> noErr);
  464. end;
  465. end;
  466. end;
  467. oe := noErr;
  468. end;
  469. until found or (oe <> noErr);
  470. end;
  471. if found then begin
  472. oe := noErr;
  473. end else begin
  474. oe := fnfErr;
  475. fs.vRefNum := 0;
  476. fs.parID := 2;
  477. fs.name := '';
  478. end;
  479. FindApplication := oe;
  480. end;
  481. function LaunchFSSpec (tofront: Boolean; const applicationFileSpec: FSSpec): OSErr;
  482. var
  483. launchThis: LaunchParamBlockRec;
  484. begin
  485. launchThis.launchAppSpec := @applicationFileSpec;
  486. launchThis.launchAppParameters := nil;
  487. launchThis.launchBlockID := extendedBlock;
  488. launchThis.launchEPBLength := extendedBlockLen;
  489. launchThis.launchFileFlags := 0;
  490. launchThis.launchControlFlags := launchContinue or launchNoFileFlags;
  491. if not tofront then begin
  492. launchThis.launchControlFlags := launchThis.launchControlFlags or launchDontSwitch;
  493. end;
  494. LaunchFSSpec:= LaunchApplication(@launchThis);
  495. end;