Source code of Windows XP (NT5)
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.

1271 lines
44 KiB

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