Windows NT 4.0 source code leak
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1245 lines
44 KiB

4 years ago
  1. /********************************************************************\
  2. * playsnd.c
  3. *
  4. * Level 1 kitchen sink DLL sound driver functions
  5. *
  6. * Copyright (c) Microsoft Corporation 1992. All rights reserved
  7. *
  8. *********************************************************************/
  9. #define UNICODE
  10. #include "winmmi.h"
  11. #include "playwav.h"
  12. #include "mci.h"
  13. WSZCODE szSoundSection[] = L"sounds"; // WIN.INI section for sounds
  14. WSZCODE szSystemDefaultSound[] = SOUND_DEFAULT; // Name of the default sound
  15. #define SOUNDNAMELEN 256
  16. STATICDT HANDLE hCurrentSound; // handle to current sound.
  17. extern LPWAVEHDR lpWavHdr; // current playing sound PLAYWAV.C
  18. STATICFN BOOL sndPlaySoundI(LPCWSTR lszSoundName, HMODULE hMod, UINT wFlags);
  19. STATICFN void GetDefaultSound(LPWSTR lszSoundName);
  20. CRITICAL_SECTION SoundCritSec;
  21. #define EnterSound() EnterCriticalSection(&SoundCritSec);
  22. #define LeaveSound() LeaveCriticalSection(&SoundCritSec);
  23. void lsplitpath (LPCTSTR pszSource,
  24. LPTSTR pszDr, LPTSTR pszPath, LPTSTR pszName, LPTSTR pszExt);
  25. void lstrncpy (LPTSTR pszTarget, LPCTSTR pszSource, size_t cch);
  26. void lstrncpyW (LPWSTR pszTarget, LPCWSTR pszSource, size_t cch);
  27. /****************************************************************************/
  28. #ifndef cchLENGTH
  29. #define cchLENGTH(_sz) (sizeof(_sz)/sizeof(_sz[0]))
  30. #endif
  31. #ifndef _MAX_DRIVE
  32. #define _MAX_DRIVE 3
  33. #define _MAX_DIR 260
  34. #define _MAX_EXT 5
  35. #define _MAX_FNAME 260
  36. #endif
  37. /**************************************************************************\
  38. * Sounds are played by the variants of PlaySound (i.e. sndPlaySoundA etc)
  39. * either synchronously or asynchronously. The semantics in each case is
  40. * that there can only be one sound playing at a time, and that we kill any
  41. * sound which is already playing and then start the new one. If the new
  42. * one is sync then we do all the work on the current thread by calling
  43. * sndMessage directly. This includes waiting for the sound to complete.
  44. * If the sound is async then we post a message to mmWndProc and return
  45. * immediately.
  46. *
  47. * The message queue of mmWndProc is the queue of async messages
  48. * still waiting to be played. It contains the following messages
  49. * which are of interest:
  50. * MM_WOM_DONE: call WaveOutNotify
  51. * MM_SND_PLAY: Play an async sound
  52. * MM_SND_ABORT: Put on the queue when a sync wave request comes in.
  53. *
  54. * The calling tree is
  55. *
  56. *
  57. *
  58. *
  59. *
  60. * <---------------------------------------------------
  61. * | called to play sound alias synchronously |
  62. * v |
  63. * (Snd)PlaySound--->PostMessage--->mmWndProc------------------>
  64. * | (MM_SND_PLAY)| |
  65. * | | |(MM_WOM_DONE)
  66. * ----------------------------- |
  67. * | |
  68. * v |
  69. * sndMessage v
  70. * | WaveOutNotify
  71. * v |
  72. * SetCurrentSound |
  73. * | | |
  74. * -------- | |
  75. * | v |
  76. * v soundPlay ----
  77. * soundFree | | | |
  78. * -------- | ----------- |
  79. * | | | |
  80. * v v v v
  81. * soundOpen soundWait soundClose
  82. *
  83. * hwndNotify exists for each process and is a global pseudo-constant.
  84. * It is set when the window is created (typically 1st async sound or
  85. * mci command) and is never altered thereafter. It points in effect to
  86. * mmWndProc. There is no real need for the window to exist. It is a
  87. * convenience to allow messages to be posted and SENT. If there was no
  88. * need to send a message (meaning wait for a reply) we could simply create
  89. * a thread.
  90. *
  91. * When an asynch sound comes in it is just added to the queue. mmWndProc
  92. * will normally find it and call sndMessage. Before calling sndMessage
  93. * mmWndProc peeks at the queue to see if there is any abort message
  94. * pending. If there is it doesn't bother to try to play the sound. It
  95. * means that a SYNC sound has come in since and has pre-empted it. The
  96. * async sound will never play.
  97. *
  98. * SoundMessage critical section:
  99. * This mechanism in itself is not quite enough. It is still possible
  100. * for an async sound to be already heading into sndMessage at the point
  101. * when a sync sound comes in. The two then have a race. To ensure that
  102. * there is a clear winner, all the real work in sndMessage is done
  103. * inside a critical section. We guarantee that we eventually leave
  104. * the section by a try/finally in sndMessage. It is entered and left by
  105. * the EnterSound and LeaveSound macros.
  106. *
  107. * WavHdrCritSec critical section
  108. * The notification uses a global variable lpWavHdr. This is set by
  109. * soundOpen and cleared by soundClose. soundClose may be called
  110. * asynchronously by WaveOutNotify, so all attempts to dereference it
  111. * must be protected by a check that it is non-null and a critical section
  112. * to ensure that it is not nullified between the check and the dereference.
  113. * It is entered and left by the EnterWavHdr and LeaveWavHdr macros.
  114. \**************************************************************************/
  115. STATICFN UINT TransAlias(UINT alias) {
  116. switch (alias) {
  117. case SND_ALIAS_SYSTEMASTERISK: return STR_ALIAS_SYSTEMASTERISK ;
  118. case SND_ALIAS_SYSTEMQUESTION: return STR_ALIAS_SYSTEMQUESTION ;
  119. case SND_ALIAS_SYSTEMHAND: return STR_ALIAS_SYSTEMHAND ;
  120. case SND_ALIAS_SYSTEMEXIT: return STR_ALIAS_SYSTEMEXIT ;
  121. case SND_ALIAS_SYSTEMSTART: return STR_ALIAS_SYSTEMSTART ;
  122. case SND_ALIAS_SYSTEMWELCOME: return STR_ALIAS_SYSTEMWELCOME ;
  123. case SND_ALIAS_SYSTEMEXCLAMATION: return STR_ALIAS_SYSTEMEXCLAMATION ;
  124. case SND_ALIAS_SYSTEMDEFAULT: return STR_ALIAS_SYSTEMDEFAULT ;
  125. default: return alias;
  126. }
  127. }
  128. extern BOOL WinmmRunningInServer; // Are we running in the user/base server?
  129. extern BOOL WaveMapperInitialized; // Wave mapper safely loaded
  130. extern TCHAR gszSchemesRootKey[];
  131. extern TCHAR gszSchemeAppsKey[];
  132. extern TCHAR aszDefault[];
  133. extern TCHAR asz4Format[];
  134. extern TCHAR asz5Format[];
  135. extern TCHAR asz6Format[];
  136. extern TCHAR aszActiveKey[];
  137. extern TCHAR aszBoolOne[];
  138. extern TCHAR aszSetup[]; // REGSTR_PATH_SETUP
  139. extern TCHAR aszValMedia[]; // REGSTR_VAL_MEDIA
  140. extern TCHAR gszDefaultBeepOldAlias[]; // "SystemDefault"
  141. BOOL UseRegistry= FALSE;
  142. TCHAR Keyname[] = TEXT("Control Panel\\Sounds\\");
  143. //--------------------------------------------------------------------------;
  144. BOOL PASCAL sndQueryRegistry (LPCWSTR szScheme,
  145. LPCWSTR szApp,
  146. LPCWSTR szSound,
  147. LPWSTR szBuffer)
  148. {
  149. TCHAR szKey[196];
  150. LONG cbValue;
  151. wsprintfW (szKey, asz5Format, // ("AppEvents\Apps\(app)\(sound)\(scheme)")
  152. gszSchemesRootKey,
  153. gszSchemeAppsKey,
  154. szApp,
  155. szSound,
  156. szScheme);
  157. if (mmRegQueryUserValue (szKey, NULL, MAX_SOUND_ATOM_CHARS, szBuffer))
  158. {
  159. // There's an entry--but make sure it's enabled!
  160. //
  161. wsprintfW (szKey, asz6Format, // "AppEvents\Apps\app\snd\scheme\Active"
  162. (LPCSTR)gszSchemesRootKey,
  163. (LPCSTR)gszSchemeAppsKey,
  164. (LPCSTR)szApp,
  165. szSound,
  166. szScheme,
  167. aszActiveKey);
  168. if (!mmRegQueryUserValue (szKey, NULL, cchLENGTH(szKey), szKey))
  169. {
  170. return TRUE; // Not disabled? Okay.
  171. }
  172. if (!lstrcmpW (szKey, aszBoolOne))
  173. {
  174. return TRUE; // Says it's enabled? Okay.
  175. }
  176. }
  177. return FALSE;
  178. }
  179. /****************************************************************************/
  180. STATICFN BOOL GetSoundAlias(
  181. LPCWSTR lszSoundName,
  182. LPWSTR lszBuffer,
  183. DWORD dwFlags)
  184. /****************************************************************************/
  185. {
  186. BOOL fFound;
  187. LPWSTR lpwstrFilePart;
  188. TCHAR szApp[APP_TYPE_MAX_LENGTH];
  189. TCHAR szScheme[SCH_TYPE_MAX_LENGTH];
  190. LONG cbValue;
  191. TCHAR szTemp[ _MAX_FNAME ];
  192. if ((lszSoundName == NULL) || (lszBuffer == NULL))
  193. return FALSE;
  194. //
  195. // Try to translate the alias (from lszSoundName--it'll be
  196. // ".Default", "MailBeep", etc) into a fully-qualified
  197. // filename. Note that lszSoundName and lszBuffer may point
  198. // to the same address space.
  199. //
  200. // If it's "SystemDefault", play ".Default" instead.
  201. //
  202. fFound = FALSE;
  203. if (!lstrcmp (lszSoundName, gszDefaultBeepOldAlias))
  204. {
  205. lszSoundName = szSystemDefaultSound;
  206. }
  207. if (lstrlen(lszSoundName) < EVT_TYPE_MAX_LENGTH)
  208. {
  209. //
  210. // first determine what application is calling us;
  211. // we'll use ".default" if nothing is apparent, but
  212. // in theory we should be able to differentiate sounds
  213. // on an app by app basis.
  214. //
  215. szApp[0] = TEXT('\0');
  216. if (dwFlags & SND_APPLICATION)
  217. {
  218. if (GetModuleFileName (GetModuleHandle(NULL),
  219. szTemp, sizeof(szTemp)/sizeof(szTemp[0])))
  220. {
  221. lsplitpath (szTemp, NULL, NULL, szApp, NULL);
  222. }
  223. }
  224. if (szApp[0] == TEXT('\0'))
  225. {
  226. lstrcpy(szApp, aszDefault);
  227. }
  228. //
  229. // determine what the current scheme is, and find the
  230. // appropriate sound. Try both the app we queried above,
  231. // and ".Default" if necessary.
  232. //
  233. szScheme[0] = TEXT('\0');
  234. cbValue = cchLENGTH(szScheme);
  235. if (mmRegQueryUserValue (gszSchemesRootKey, NULL, cbValue, szScheme))
  236. {
  237. // Since {lszSoundName} and {lszBuffer} may point to
  238. // the same space, we shouldn't be calling
  239. // sndQueryRegistry() with precisely those parameters.
  240. // Give it a good scratch buffer for an output buffer,
  241. // and only hose {lszBuffer} when we're sure we've got
  242. // a match.
  243. //
  244. if (sndQueryRegistry(szScheme, szApp, lszSoundName, szTemp) ||
  245. sndQueryRegistry(szScheme, aszDefault, lszSoundName, szTemp))
  246. {
  247. lstrcpy (lszBuffer, szTemp);
  248. fFound = TRUE;
  249. }
  250. }
  251. }
  252. //
  253. // Were we able to translate the alias into a valid filename?
  254. //
  255. if (!fFound)
  256. {
  257. // never found a matching alias!
  258. //
  259. return FALSE;
  260. }
  261. lstrcpy (szTemp, lszBuffer);
  262. return TRUE;
  263. }
  264. /****************************************************************************/
  265. STATICFN BOOL PASCAL NEAR GetSoundName(
  266. LPCWSTR lszSoundName,
  267. LPWSTR lszBuffer,
  268. DWORD flags)
  269. /****************************************************************************/
  270. {
  271. int i;
  272. WCHAR szTmpFileName[SOUNDNAMELEN];
  273. LPWSTR lpwstrFilePart;
  274. //
  275. // if the sound is defined in the [sounds] section of WIN.INI
  276. // get it and remove the description, otherwise assume it is a
  277. // file and qualify it.
  278. //
  279. // If we KNOW it is a filename do not look in the INI file
  280. if ((flags & SND_ALIAS) || !(flags & SND_FILENAME)) {
  281. if (!GetSoundAlias ( lszSoundName, lszBuffer, flags)) {
  282. lstrcpy( lszBuffer, lszSoundName );
  283. }
  284. } else {
  285. lstrcpy( lszBuffer, lszSoundName );
  286. }
  287. // UNICODE: Can't use OpenFile with Unicode string name. As we are
  288. // checking to see if the file exists and then copying its fully
  289. // qualified name to lszBuffer, (ie. not opening the file) I will
  290. // use SearchPathW instead.
  291. //
  292. // if (OpenFile(lszBuffer, &of, OF_EXIST | OF_READ | OF_SHARE_DENY_NONE) != HFILE_ERROR) {
  293. // OemToAnsi(of.szPathName, lszBuffer);
  294. // }
  295. lstrcpy( szTmpFileName, lszBuffer );
  296. if (!SearchPathW( NULL, szTmpFileName, L".WAV", SOUNDNAMELEN,
  297. lszBuffer, &lpwstrFilePart )) {
  298. WCHAR szMediaPath[MAX_PATH];
  299. if (mmRegQueryMachineValue (aszSetup, aszValMedia,
  300. cchLENGTH(szMediaPath), szMediaPath)) {
  301. if (!SearchPathW( szMediaPath, szTmpFileName, L".WAV", SOUNDNAMELEN,
  302. lszBuffer, &lpwstrFilePart )) {
  303. return FALSE; // couldn't find the sound file anywhere!
  304. }
  305. }
  306. }
  307. }
  308. /*****************************************************************************
  309. * @doc EXTERNAL
  310. *
  311. * @api BOOL | sndPlaySound | This function plays a waveform
  312. * sound specified by a filename or by an entry in the [sounds] section
  313. * of WIN.INI. If the sound can't be found, it plays the
  314. * default sound specified by the .Default entry in the
  315. * [sounds] section of WIN.INI. If there is no .Default
  316. * entry or if the default sound can't be found, the function
  317. * makes no sound and returns FALSE.
  318. *
  319. * @parm LPCSTR | lpszSoundName | Specifies the name of the sound to play.
  320. * The function searches the [sounds] section of WIN.INI for an entry
  321. * with this name and plays the associated waveform file.
  322. * If no entry by this name exists, then it assumes the name is
  323. * the name of a waveform file. If this parameter is NULL, any
  324. * currently playing sound is stopped.
  325. *
  326. * @parm UINT | wFlags | Specifies options for playing the sound using one
  327. * or more of the following flags:
  328. *
  329. * @flag SND_SYNC | The sound is played synchronously and the
  330. * function does not return until the sound ends.
  331. * @flag SND_ASYNC | The sound is played asynchronously and the
  332. * function returns immediately after beginning the sound. To terminate
  333. * an asynchronously-played sound, call <f sndPlaySound> with
  334. * <p lpszSoundName> set to NULL.
  335. * @flag SND_NODEFAULT | If the sound can't be found, the function
  336. * returns silently without playing the default sound.
  337. * @flag SND_MEMORY | The parameter specified by <p lpszSoundName>
  338. * points to an in-memory image of a waveform sound.
  339. * @flag SND_LOOP | The sound will continue to play repeatedly
  340. * until <f sndPlaySound> is called again with the
  341. * <p lpszSoundName> parameter set to NULL. You must also specify the
  342. * SND_ASYNC flag to loop sounds.
  343. * @flag SND_NOSTOP | If a sound is currently playing, the
  344. * function will immediately return FALSE without playing the requested
  345. * sound.
  346. *
  347. * @rdesc Returns TRUE if the sound is played, otherwise
  348. * returns FALSE.
  349. *
  350. * @comm The sound must fit in available physical memory and be playable
  351. * by an installed waveform audio device driver. The directories
  352. * searched for sound files are, in order: the current directory;
  353. * the Windows directory; the Windows system directory; the directories
  354. * listed in the PATH environment variable; the list of directories
  355. * mapped in a network. See the Windows <f OpenFile> function for
  356. * more information about the directory search order.
  357. *
  358. * If you specify the SND_MEMORY flag, <p lpszSoundName> must point
  359. * to an in-memory image of a waveform sound. If the sound is stored
  360. * as a resource, use <f LoadResource> and <f LockResource> to load
  361. * and lock the resource and get a pointer to it. If the sound is not
  362. * a resource, you must use <f GlobalAlloc> with the GMEM_MOVEABLE and
  363. * GMEM_SHARE flags set and then <f GlobalLock> to allocate and lock
  364. * memory for the sound.
  365. *
  366. * @xref MessageBeep
  367. ****************************************************************************/
  368. BOOL APIENTRY sndPlaySoundW( LPCWSTR szSoundName, UINT wFlags)
  369. {
  370. //
  371. // !!! quick exit for no wave devices !!!
  372. //
  373. if (wTotalWaveOutDevs) {
  374. return sndPlaySoundI(szSoundName, NULL, wFlags);
  375. } else {
  376. return FALSE;
  377. }
  378. }
  379. BOOL APIENTRY sndPlaySoundA( LPCSTR szSoundName, UINT wFlags)
  380. {
  381. return PlaySoundA(szSoundName, NULL, wFlags);
  382. }
  383. BOOL APIENTRY PlaySoundW(LPCWSTR szSoundName, HMODULE hModule, DWORD wFlags)
  384. {
  385. //
  386. // !!! quick exit for no wave devices !!!
  387. //
  388. if (wTotalWaveOutDevs) {
  389. return sndPlaySoundI(szSoundName, hModule, (UINT)wFlags);
  390. }
  391. return FALSE;
  392. }
  393. BOOL APIENTRY PlaySoundA(LPCSTR szSoundName, HMODULE hModule, DWORD wFlags)
  394. {
  395. WCHAR UnicodeStr[256]; // Unicode version of szSoundName
  396. //
  397. // !!! quick exit for no wave devices !!!
  398. //
  399. if (wTotalWaveOutDevs) {
  400. // We do not want to translate szSoundName unless it is a pointer
  401. // to an ascii string. It may be a pointer to an in-memory copy
  402. // of a wave file, or it may identify a resource. If a resource
  403. // then we do want to translate the name. Note that there is an
  404. // overlap between SND_MEMORY and SND_RESOURCE. This is deliberate
  405. // as later on the resource will be loaded - then played from memory.
  406. if ( HIWORD(szSoundName) // Potential to point to an ascii name
  407. // Translate if NOT memory, or a resource
  408. // If the resource is identified by ID - the ID better be <= 0xFFFF
  409. // which applies to lots of other code as well!
  410. && (!(wFlags & SND_MEMORY) || ((wFlags & SND_RESOURCE) == SND_RESOURCE))
  411. ) {
  412. //
  413. // Convert the Unicode sound name into Ascii
  414. //
  415. if (Imbstowcs(UnicodeStr, szSoundName,
  416. sizeof(UnicodeStr) / sizeof(WCHAR)) >=
  417. sizeof(UnicodeStr) / sizeof(WCHAR)) {
  418. return 0;
  419. }
  420. return sndPlaySoundI( UnicodeStr, hModule, (UINT)wFlags );
  421. }
  422. else {
  423. return sndPlaySoundI( (LPWSTR)szSoundName, hModule, (UINT)wFlags );
  424. }
  425. }
  426. return FALSE;
  427. }
  428. /****************************************************************************/
  429. /*
  430. @doc INTERNAL
  431. @func BOOL | sndPlaySoundI | Internal version of <f>sndPlaySound<d> which
  432. resides in the WAVE segment instead.
  433. If the SND_NOSTOP flag is specifed and a wave file is currently
  434. playing, or if for some reason no WINMM window is present, the
  435. function returns failure immediately. The first condition ensures
  436. that a current sound is not interrupted if the flag is set. The
  437. second condition is only in case of some start up error in which
  438. the notification window was not created, or WINMM was not
  439. specified in the [drivers] line, and therefore never loaded.
  440. Next, if the <p>lszSoundName<d> parameter does not represent a memory
  441. file, and it is non-NULL, then it must represent a string. Therefore
  442. the string must be parsed before sending the sound message to the
  443. WINMM window. This is because the WINMM window may reside in a
  444. a different task than the task which is calling the function, and
  445. would most likely have a different current directory.
  446. In this case, the parameter is first checked to determine if it
  447. actually contains anything. For some reason a zero length string
  448. was determined to be able to return TRUE from this function, so that
  449. is checked.
  450. Next the string is checked against INI entries, then parsed.
  451. After parsing the sound name, ensure that a task switch only occurs if
  452. the sound is asynchronous (SND_ASYNC), and a previous sound does not
  453. need to be discarded.
  454. If a task switch is needed, first ensure that intertask messages can
  455. be sent by checking to see that this task is not locked, or that the
  456. notification window is in the current task.
  457. @parm LPCSTR | lszSoundName | Specifies the name of the sound to play.
  458. @parm UINT | wFlags | Specifies options for playing the sound.
  459. @rdesc Returns TRUE if the function was successful, else FALSE if an error
  460. occurred.
  461. */
  462. STATICFN BOOL sndPlaySoundI(
  463. LPCWSTR lszSoundName,
  464. HMODULE hMod,
  465. UINT wFlags)
  466. {
  467. BOOL fPlayReturn;
  468. LPWSTR szSoundName = NULL;
  469. UINT nLength = 0;
  470. WCHAR temp[80]; // Maximum size of a system event sound
  471. V_FLAGS(wFlags, SND_VALIDFLAGS, sndPlaySoundW, FALSE);
  472. if (!(wFlags & SND_MEMORY) && HIWORD(lszSoundName)) {
  473. V_STRING_W(lszSoundName, 128, FALSE);
  474. }
  475. WinAssert(!SND_SYNC); // Because the code relies on SND_ASYNC being non-0
  476. #if DBG
  477. if (wFlags & SND_MEMORY) {
  478. STATICFN SZCODE szFormat[] = "sndPlaySound(%lx) Flags %8x";
  479. dprintf2((szFormat, lszSoundName, wFlags));
  480. } else if (HIWORD(lszSoundName)) {
  481. STATICFN SZCODE szFormat[] = "sndPlaySound(%ls) Flags %8x";
  482. dprintf2((szFormat, lszSoundName, wFlags));
  483. } else if (lszSoundName) {
  484. STATICFN SZCODE szFormat[] = "sndPlaySound(0x%x) Flags %8x";
  485. dprintf2((szFormat, lszSoundName, wFlags));
  486. }
  487. #endif //if DBG
  488. if (((wFlags & SND_NOSTOP) && lpWavHdr) /*** || (NoNotify) ***/) {
  489. dprintf1(("Sound playing, or no notify window"));
  490. return FALSE;
  491. }
  492. //
  493. // Bad things happen in functions like LoadIcon which the ACM CODECs
  494. // call during their initialization if we're on a CSRSS thread so always
  495. // make async calls until we're sure it's initialized.
  496. // The last test makes sure we don't 'or' in the SND_ASYNC flag again
  497. // on the server thread!
  498. //
  499. if ( WinmmRunningInServer && !WaveMapperInitialized &&
  500. SND_ALIAS_ID == (wFlags & SND_ALIAS_ID)) {
  501. wFlags |= SND_ASYNC;
  502. }
  503. // comments here should have been there from day 1 and explain
  504. // the test
  505. //
  506. // if (!hwndNotify && !(!(wFlags & SND_ASYNC) && !lpWavHdr))
  507. // IF no window
  508. // AND NOT (Synchronous, and no sound present)
  509. // == Async OR sound present
  510. // Which meant that if a sound is playing we will attempt to create
  511. // a second thread even if this is a synchronous request. This
  512. // causes havoc when we are called on the server side.
  513. // If this is an asynchronous call we need to create the asynchronous
  514. // thread on which the sound will be played. We should NEVER create
  515. // the thread if this is a synchronous request, irrespective of the
  516. // current state (i.e. sound playing, or no sound playing).
  517. if (wFlags & SND_ASYNC) {
  518. if (!InitAsyncSound()) {
  519. dprintf2(("Having to play synchronously - cannot create notify window"));
  520. wFlags &= ~SND_ASYNC;
  521. if (WinmmRunningInServer) {
  522. return FALSE;
  523. }
  524. }
  525. }
  526. if ( WinmmRunningInServer && (wFlags & SND_ASYNC) ) {
  527. UINT alias; // To check if the incoming alias is SYSTEMDEFAULT
  528. // lszSoundName is NOT a pointer to a filename. It
  529. // is a resource id. Resolve the name from the INI file
  530. // now.
  531. if (SND_ALIAS_ID == (wFlags & SND_ALIAS_ID)) {
  532. nLength = LoadStringW( ghInst,
  533. (alias = TransAlias((UINT)lszSoundName)),
  534. temp, sizeof(temp)/sizeof(WCHAR) );
  535. if (0 == nLength) {
  536. dprintf3(("Could not translate Alias ID"));
  537. return(FALSE);
  538. } else {
  539. dprintf3(("Translated alias %x to sound %ls", lszSoundName, temp));
  540. }
  541. // We cannot switch control immediately to the async thread as that
  542. // thread does not have the correct user impersonation. So rather
  543. // than passing an alias we resolve it here to a filename.
  544. // Later: we should get the async thread to set the correct user
  545. // token and inifilemapping - then we could revert to passing aliases.
  546. // Turn off the ID bit, leave ALIAS on)
  547. wFlags &= ~(SND_ALIAS_ID);
  548. wFlags |= SND_ALIAS;
  549. } else {
  550. //
  551. // BUGBUG: I (RichJ) just stuck this ELSE in here for 3.51, but a
  552. // lack of it should've been causing faults or failed lookups any
  553. // time an async sound was requested from the server, without
  554. // using an ALIAS_ID as the request. Did that just never happen?
  555. //
  556. lstrcpy (temp, lszSoundName);
  557. }
  558. // Translate the alias to a file name
  559. dprintf4(("Calling GetSoundName"));
  560. if (!GetSoundName(temp, base->szSound, SND_ALIAS)) {
  561. //
  562. // Couldn't find the sound file; if there's no default sound,
  563. // then don't play anything (and don't cancel what's
  564. // playing now--for example, what if the MenuPopup event
  565. // has a sound, but the MenuCommand event doesn't?)
  566. //
  567. if (wFlags & SND_NODEFAULT) {
  568. return(FALSE);
  569. }
  570. }
  571. dprintf4(("Set %ls as the sound name", base->szSound));
  572. if (lpWavHdr) { // Sound is playing
  573. dprintf4(("Killing pending sound on server"));
  574. soundFree(NULL); // Kill any pending sound
  575. }
  576. LockMCIGlobal;
  577. dprintf2(("Signalling play of %x",lszSoundName));
  578. base->msg = MM_SND_PLAY;
  579. base->dwFlags = wFlags | SND_FILENAME;
  580. base->dwFlags &= ~(SND_ALIAS_ID | SND_ALIAS);
  581. base->lszSound = lszSoundName;
  582. if (wFlags & SND_NODEFAULT) {
  583. } else {
  584. if (STR_ALIAS_SYSTEMDEFAULT == alias) {
  585. wFlags |= SND_NODEFAULT;
  586. // We will play the default sound, hence there is
  587. // no point in having the NODEFAULT flag off.
  588. dprintf4(("Playing the default sound"));
  589. } else {
  590. // If we cannot find or play the file passed in
  591. // we have to play a default sound. By the time
  592. // we get around to playing a default sound we will
  593. // have lost the ini file mapping. Resolve the name
  594. // now.
  595. dprintf4(("Resolving default sound"));
  596. GetSoundName(szSystemDefaultSound,
  597. base->szDefaultSound,
  598. SND_ALIAS);
  599. }
  600. }
  601. dprintf2(("Setting event"));
  602. SetMCIEvent( hEvent );
  603. dprintf2(("Event set"));
  604. UnlockMCIGlobal;
  605. return(TRUE);
  606. }
  607. if (!(wFlags & SND_MEMORY) && lszSoundName) {
  608. if (!(szSoundName = (LPWSTR)LocalAlloc(LMEM_FIXED, SOUNDNAMELEN * sizeof(WCHAR)))) {
  609. return FALSE;
  610. }
  611. dprintf4(("Allocated szSoundName at %8x", szSoundName));
  612. if (SND_ALIAS_ID == (wFlags & SND_ALIAS_ID)) {
  613. // lszSoundName is NOT a pointer to a filename. It
  614. // is a resource id. Resolve the name from the INI file
  615. // now.
  616. nLength = LoadStringW( ghInst,
  617. (UINT)TransAlias((UINT)lszSoundName),
  618. szSoundName, SOUNDNAMELEN );
  619. if (0 == nLength) {
  620. dprintf3(("Could not translate Alias ID"));
  621. return(FALSE);
  622. }
  623. lszSoundName = szSoundName;
  624. // Turn off the ID bit, leave ALIAS on)
  625. wFlags &= (~SND_ALIAS_ID | SND_ALIAS);
  626. }
  627. if (!*lszSoundName) {
  628. // LATER: STOP any sound that is already playing
  629. LocalFree ((HLOCAL)szSoundName);
  630. return TRUE;
  631. }
  632. if (!GetSoundName(lszSoundName, szSoundName, wFlags)) {
  633. //
  634. // Couldn't find the sound file; if there's no default sound,
  635. // then don't play anything (and don't cancel what's
  636. // playing now--for example, what if the MenuPopup event
  637. // has a sound, but the MenuCommand event doesn't?)
  638. //
  639. if (wFlags & SND_NODEFAULT) {
  640. LocalFree ((HLOCAL)szSoundName);
  641. return TRUE;
  642. }
  643. }
  644. lszSoundName = (LPCWSTR)szSoundName;
  645. nLength = lstrlenW(szSoundName);
  646. } else {
  647. // lszSoundName points to a memory image (if SND_MEMORY)
  648. // or lszSoundName is null. Either way we do not want to
  649. // load a file. OR we may have a resource to load.
  650. HANDLE hResInfo;
  651. HANDLE hResource;
  652. szSoundName = NULL;
  653. if (SND_RESOURCE == (wFlags & SND_RESOURCE)) {
  654. hResInfo = FindResourceW( hMod, lszSoundName, SOUND_RESOURCE_TYPE );
  655. if (hResInfo) {
  656. hResource = LoadResource( hMod, hResInfo);
  657. if (hResource) {
  658. lszSoundName = LockResource(hResource);
  659. } else {
  660. dprintf1(("failed to load resource"));
  661. return(FALSE);
  662. }
  663. } else {
  664. dprintf1(("failed to find resource"));
  665. return(FALSE);
  666. }
  667. // Turn off the resource bit
  668. wFlags &= ~(SND_RESOURCE-SND_MEMORY);
  669. }
  670. }
  671. // This was the old test - replaced with the one below. The
  672. if (szSoundName) {
  673. wFlags |= SND_FREE;
  674. // LocalFree((HANDLE)szSoundName); // Freed by SNDMESSAGE
  675. }
  676. // For a synchronous sound it is valid for a prior async sound to be
  677. // still playing. Before we finally play this new sound we will kill
  678. // the old sound. The code commented out below caused a SYNC sound to
  679. // play asynchronously if a previous sound was still active
  680. if (!(wFlags & SND_ASYNC) /* && !lpWavHdr SOUND IS STILL PLAYING */) {
  681. if (hwndNotify) { // Clear any pending asynchronous sounds
  682. PostMessage(hwndNotify, MM_SND_ABORT, 0, 0);
  683. }
  684. fPlayReturn = sndMessage( (LPWSTR)lszSoundName, wFlags);
  685. } else {
  686. WinAssert(hwndNotify); // At this point we need the window
  687. // Note: in this leg we must free lszSoundName later
  688. dprintf3(("Sending MM_SND_PLAY to hwndNotify"));
  689. fPlayReturn = (BOOL)(LONG)PostMessage(hwndNotify, MM_SND_PLAY, wFlags, (LONG)lszSoundName);
  690. }
  691. return fPlayReturn;
  692. }
  693. /****************************************************************************\
  694. * INTERNAL MatchFile *
  695. * *
  696. * Checks that the file stored on disk matches the cached sound file for *
  697. * date, time and size. If not, then we return FALSE and the cached sound *
  698. * file is not used. If the details do match we return TRUE. Note that the *
  699. * last_write file time is used in case the user has updated the file. *
  700. \****************************************************************************/
  701. STATICFN BOOL MatchFile(PSOUNDFILE pf, LPCWSTR lsz)
  702. {
  703. HANDLE fh;
  704. BOOL result = FALSE;
  705. fh = CreateFileW( lsz,
  706. GENERIC_READ,
  707. FILE_SHARE_READ | FILE_SHARE_WRITE,
  708. NULL,
  709. OPEN_EXISTING,
  710. FILE_ATTRIBUTE_NORMAL,
  711. NULL );
  712. if ((HANDLE)HFILE_ERROR != fh) {
  713. if (pf->Size == GetFileSize(fh, NULL)) {
  714. FILETIME ft;
  715. if (GetFileTime(fh, NULL, NULL, &ft)) {
  716. if (CompareFileTime(&ft, &(pf->ft)) == 0) {
  717. result = TRUE;
  718. }
  719. } else {
  720. dprintf2(("Error %d getting last write time", GetLastError()));
  721. }
  722. }
  723. CloseHandle(fh);
  724. }
  725. return result;
  726. }
  727. /*********************************************************************\
  728. * INTERNAL SetCurrentSound
  729. *
  730. * Called to set the cached sound on this process to <lszSoundName>.
  731. * Before discarding the current sound we check to see if it is the
  732. * same as the one about to be loaded. If so, then it need not be
  733. * read off the disk. To be considered the same not only the filename
  734. * has to match, but the file date, time and size. Note: Windows 3.1
  735. * only matches on the name.
  736. \*********************************************************************/
  737. STATICFN BOOL PASCAL NEAR SetCurrentSound(
  738. LPCWSTR lszSoundName)
  739. {
  740. HANDLE hSound;
  741. BOOL f;
  742. LPWSTR lp;
  743. if (hCurrentSound && (NULL != (lp = GlobalLock(hCurrentSound)))) {
  744. f = lstrcmpiW( ((PSOUNDFILE)lp)->Filename, (LPWSTR)lszSoundName);
  745. if (f == 0 && MatchFile( ((PSOUNDFILE)lp), lszSoundName)) {
  746. GlobalUnlock(hCurrentSound);
  747. dprintf2(("SetCurrentSound - sound already loaded %ls",(LPWSTR)lszSoundName));
  748. return TRUE;
  749. }
  750. GlobalUnlock(hCurrentSound);
  751. }
  752. dprintf2(("SetCurrentSound(%ls)\r\n",lszSoundName));
  753. if (NULL != (hSound = soundLoadFile(lszSoundName))) {
  754. soundFree(hCurrentSound);
  755. hCurrentSound = hSound;
  756. dprintf3(("SetCurrentSound returning TRUE"));
  757. return TRUE;
  758. }
  759. dprintf3(("SetCurrentSound returning FALSE"));
  760. return FALSE;
  761. }
  762. /****************************************************************************/
  763. /*
  764. @doc INTERNAL
  765. @func BOOL | SoundBeep | Called to sound the speaker when the .Default
  766. sound either does not exist or is set to NULL
  767. @rdesc Returns TRUE if the function was successful, else FALSE if an error
  768. occurred.
  769. */
  770. STATICFN BOOL SoundBeep(VOID)
  771. {
  772. BOOL fBeep;
  773. if (WinmmRunningInServer) {
  774. // being played on the server thread. We would not have
  775. // got here unless the user wants beeps to sound
  776. fBeep = TRUE;
  777. } else {
  778. if (!SystemParametersInfo(SPI_GETBEEP, 0, &fBeep, FALSE)) {
  779. // Failed to get hold of beep setting. Should we be
  780. // noisy or quiet? We have to choose one value...
  781. fBeep = TRUE;
  782. }
  783. }
  784. if (fBeep) {
  785. dprintf5(("Sounding the speaker"));
  786. // LATER: Symbolic constant... read from which header file?
  787. return Beep(440, 125);
  788. } else {
  789. dprintf5(("NO speaker sound"));
  790. return(TRUE);
  791. }
  792. }
  793. /****************************************************************************/
  794. /*
  795. @doc INTERNAL
  796. @func BOOL | sndMessage | This function is called in response to an
  797. MM_SND_PLAY message sent to the WINMM window, and attempts to
  798. play the specified file, or dump current sound caching.
  799. If <p>lszSoundName<d> is NULL, any currently cached sound is
  800. discarded, and the function returns success.
  801. If the SND_MEMORY flag is set, then <p>lszSoundName<d> actually
  802. points to a buffer containing a RIFF format WAVE memory file, and
  803. the function attempts to play it. The load function performs
  804. validation on this memory file. Unlike playing sound names,
  805. memory files are not cached for future use.
  806. Otherwise the <p>lszSoundName<d> parameter is actually an INI entry
  807. or file name. The function initially attempts to load that sound,
  808. and if it fails, attempts to load the system default sound. Note of
  809. course that the SND_NODEFAULT flag is first checked to determine if
  810. the default sound is to be played when the original name cannot be
  811. located. If no default is wanted, or the default cannot be located,
  812. the function returns failure. Note that in calling <f>GetSoundName<d>,
  813. the <p>lszSoundName<d> parameter is modified. This function assumes
  814. that the parameter passed has been previously allocated if a string is
  815. passed to this function, and is not the actual user's parameter passed
  816. to <f>sndPlaySound<d>.
  817. @parm LPSTR | lszSoundName | Specifies the name of the sound to play.
  818. @parm UINT | wFlags | Specifies options for playing the sound.
  819. @rdesc Returns TRUE if the function was successful, else FALSE if an error
  820. occurred.
  821. */
  822. #if DBG
  823. UINT CritCount = 0;
  824. UINT CritOwner = 0;
  825. #endif
  826. BOOL FAR PASCAL sndMessage(LPWSTR lszSoundName, UINT wFlags)
  827. {
  828. BOOL fResult;
  829. #if DBG
  830. if (!lszSoundName) {
  831. dprintf3(("sndMessage - sound NULL, Flags %8x", wFlags));
  832. } else {
  833. dprintf3(("sndMessage - sound %ls, Flags %8x", lszSoundName, wFlags));
  834. }
  835. #endif
  836. try {
  837. #if DBG
  838. if (CritCount) {
  839. dprintf2(("Sound critical section owned by %x, thread %x waiting", CritOwner, GetCurrentThreadId()));
  840. }
  841. #endif
  842. EnterSound();
  843. #if DBG
  844. if (!CritCount++) {
  845. CritOwner = GetCurrentThreadId();
  846. dprintf2(("Thread %x entered Sound critical section", CritOwner));
  847. } else {
  848. dprintf2(("Thread %x re-entered Sound critical section, count is %d", CritOwner, CritCount));
  849. }
  850. #endif
  851. if (!lszSoundName) {
  852. // Note that soundFree will stop playing the current sound if
  853. // it is still playing
  854. dprintf4(("Freeing current sound, nothing else to play"));
  855. soundFree(hCurrentSound);
  856. hCurrentSound = NULL;
  857. fResult = TRUE;
  858. goto exit;
  859. }
  860. if (wFlags & SND_MEMORY) {
  861. soundFree(hCurrentSound);
  862. hCurrentSound = soundLoadMemory( (PBYTE)lszSoundName );
  863. } else if (!SetCurrentSound(lszSoundName)) {
  864. if (wFlags & SND_NODEFAULT) {
  865. if (wFlags & SND_FREE) {
  866. dprintf3(("Freeing (1) memory block at %8x",lszSoundName));
  867. LocalFree(lszSoundName);
  868. }
  869. fResult = FALSE;
  870. goto exit;
  871. }
  872. GetDefaultSound(lszSoundName);
  873. // If there is no default sound (.Default == NONE in CPL applet)
  874. // then sound the old beep.
  875. if (!*lszSoundName || !SetCurrentSound(lszSoundName)) {
  876. fResult = SoundBeep();
  877. if (wFlags & SND_FREE) {
  878. dprintf3(("Freeing (2) memory block at %8x",lszSoundName));
  879. LocalFree(lszSoundName);
  880. }
  881. goto exit;
  882. }
  883. }
  884. if (wFlags & SND_FREE) {
  885. dprintf3(("Freeing (3) memory block at %8x",lszSoundName));
  886. LocalFree(lszSoundName);
  887. }
  888. dprintf3(("Calling soundPlay, flags are %8x", wFlags));
  889. fResult = soundPlay(hCurrentSound, wFlags);
  890. dprintf3(("returning from sndMessage"));
  891. exit:;
  892. } finally {
  893. #if DBG
  894. if (!--CritCount) {
  895. dprintf2(("Thread %x relinquishing Sound critical section", CritOwner));
  896. CritOwner = 0;
  897. } else {
  898. dprintf2(("Thread %x leaving Sound critical section, Count is %d", CritOwner, CritCount));
  899. }
  900. #endif
  901. LeaveSound();
  902. } // try
  903. return(fResult);
  904. }
  905. STATICFN void GetDefaultSound(LPWSTR lszSoundName)
  906. {
  907. // It's a shame the default sound cannot be cached. Unfortunately
  908. // the user can change the mapping (alias->different_file) or even
  909. // change the file while keeping the same file name. The only time
  910. // we do not resolve the name from the INI file is when we are
  911. // executing in the server. There may be no ini file mapping in
  912. // existence (but arbitrarily opening a mapping fails if already
  913. // open) so we rely on the default sound filename being preset.
  914. if (!WinmmRunningInServer) {
  915. GetSoundName(szSystemDefaultSound, lszSoundName, SND_ALIAS);
  916. } else {
  917. LockMCIGlobal;
  918. wcscpy(lszSoundName, base->szDefaultSound);
  919. UnlockMCIGlobal;
  920. }
  921. }
  922. void lsplitpath (LPCTSTR pszSource,
  923. LPTSTR pszDrive, LPTSTR pszPath, LPTSTR pszName, LPTSTR pszExt)
  924. {
  925. LPCTSTR pszLastSlash = NULL;
  926. LPCTSTR pszLastDot = NULL;
  927. LPCTSTR pch;
  928. size_t cchCopy;
  929. /*
  930. * NOTE: This routine was snitched out of USERPRI.LIB 'cause the
  931. * one in there doesn't split the extension off the name properly.
  932. *
  933. * We assume that the path argument has the following form, where any
  934. * or all of the components may be missing.
  935. *
  936. * <drive><dir><fname><ext>
  937. *
  938. * and each of the components has the following expected form(s)
  939. *
  940. * drive:
  941. * 0 to _MAX_DRIVE-1 characters, the last of which, if any, is a
  942. * ':'
  943. * dir:
  944. * 0 to _MAX_DIR-1 characters in the form of an absolute path
  945. * (leading '/' or '\') or relative path, the last of which, if
  946. * any, must be a '/' or '\'. E.g -
  947. * absolute path:
  948. * \top\next\last\ ; or
  949. * /top/next/last/
  950. * relative path:
  951. * top\next\last\ ; or
  952. * top/next/last/
  953. * Mixed use of '/' and '\' within a path is also tolerated
  954. * fname:
  955. * 0 to _MAX_FNAME-1 characters not including the '.' character
  956. * ext:
  957. * 0 to _MAX_EXT-1 characters where, if any, the first must be a
  958. * '.'
  959. *
  960. */
  961. // extract drive letter and :, if any
  962. //
  963. if (*(pszSource + _MAX_DRIVE - 2) == TEXT(':'))
  964. {
  965. if (pszDrive)
  966. {
  967. lstrncpy (pszDrive, pszSource, _MAX_DRIVE-1);
  968. pszDrive[ _MAX_DRIVE-1 ] = TEXT('\0');
  969. }
  970. pszSource += _MAX_DRIVE-1;
  971. }
  972. else if (pszDrive)
  973. {
  974. *pszDrive = TEXT('\0');
  975. }
  976. // extract path string, if any. pszSource now points to the first
  977. // character of the path, if any, or the filename or extension, if
  978. // no path was specified. Scan ahead for the last occurence, if
  979. // any, of a '/' or '\' path separator character. If none is found,
  980. // there is no path. We will also note the last '.' character found,
  981. // if any, to aid in handling the extension.
  982. //
  983. for (pch = pszSource; *pch != TEXT('\0'); pch++)
  984. {
  985. if (*pch == TEXT('/') || *pch == TEXT('\\'))
  986. pszLastSlash = pch;
  987. else if (*pch == TEXT('.'))
  988. pszLastDot = pch;
  989. }
  990. // if we found a '\\' or '/', fill in pszPath
  991. //
  992. if (pszLastSlash)
  993. {
  994. if (pszPath)
  995. {
  996. cchCopy = min( _MAX_DIR -1, pszLastSlash -pszSource +1 );
  997. lstrncpy (pszPath, pszSource, cchCopy);
  998. pszPath[ cchCopy ] = 0;
  999. }
  1000. pszSource = pszLastSlash +1;
  1001. }
  1002. else if (pszPath)
  1003. {
  1004. *pszPath = TEXT('\0');
  1005. }
  1006. // extract file name and extension, if any. Path now points to
  1007. // the first character of the file name, if any, or the extension
  1008. // if no file name was given. Dot points to the '.' beginning the
  1009. // extension, if any.
  1010. //
  1011. if (pszLastDot && (pszLastDot >= pszSource))
  1012. {
  1013. // found the marker for an extension -
  1014. // copy the file name up to the '.'.
  1015. //
  1016. if (pszName)
  1017. {
  1018. cchCopy = min( _MAX_DIR-1, pszLastDot -pszSource );
  1019. lstrncpy (pszName, pszSource, cchCopy);
  1020. pszName[ cchCopy ] = 0;
  1021. }
  1022. // now we can get the extension
  1023. //
  1024. if (pszExt)
  1025. {
  1026. lstrncpy (pszExt, pszLastDot, _MAX_EXT -1);
  1027. pszExt[ _MAX_EXT-1 ] = TEXT('\0');
  1028. }
  1029. }
  1030. else
  1031. {
  1032. // found no extension, give empty extension and copy rest of
  1033. // string into fname.
  1034. //
  1035. if (pszName)
  1036. {
  1037. lstrncpy (pszName, pszSource, _MAX_FNAME -1);
  1038. pszName[ _MAX_FNAME -1 ] = TEXT('\0');
  1039. }
  1040. if (pszExt)
  1041. {
  1042. *pszExt = TEXT('\0');
  1043. }
  1044. }
  1045. }
  1046. void lstrncpy (LPTSTR pszTarget, LPCTSTR pszSource, size_t cch)
  1047. {
  1048. size_t ich;
  1049. for (ich = 0; ich < cch; ich++)
  1050. {
  1051. if ((pszTarget[ich] = pszSource[ich]) == TEXT('\0'))
  1052. break;
  1053. }
  1054. }
  1055. void lstrncpyW (LPWSTR pszTarget, LPCWSTR pszSource, size_t cch)
  1056. {
  1057. size_t ich;
  1058. for (ich = 0; ich < cch; ich++)
  1059. {
  1060. if ((pszTarget[ich] = pszSource[ich]) == L'\0')
  1061. break;
  1062. }
  1063. }