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.

716 lines
20 KiB

  1. /*****************************************************************************
  2. *
  3. * DICal.c
  4. *
  5. * Copyright (c) 1996 Microsoft Corporation. All Rights Reserved.
  6. *
  7. * Abstract:
  8. *
  9. * Functions that manage axis ramps and calibration.
  10. *
  11. * Structure names begin with "Joy" for historical reasons.
  12. *
  13. * Contents:
  14. *
  15. * CCal_CookRange
  16. * CCal_RecalcRange
  17. *
  18. *****************************************************************************/
  19. #include "dinputpr.h"
  20. /*****************************************************************************
  21. *
  22. * The sqiffle for this file.
  23. *
  24. *****************************************************************************/
  25. #define sqfl sqflCal
  26. /*****************************************************************************
  27. *
  28. * @doc INTERNAL
  29. *
  30. * @func LONG | CCal_MulDiv |
  31. *
  32. * High-speed MulDiv for Intel x86 boxes. Otherwise, uses
  33. * the standard MulDiv. The values involved are always
  34. * nonnegative.
  35. *
  36. * @parm LONG | lA |
  37. *
  38. * Multiplicand.
  39. *
  40. * @parm LONG | lB |
  41. *
  42. * Multiplier.
  43. *
  44. * @parm LONG | lC |
  45. *
  46. * Denominator.
  47. *
  48. * @returns
  49. *
  50. * lA * lB / lC, with 64-bit intermediate precision.
  51. *
  52. *****************************************************************************/
  53. #if defined(_X86_)
  54. #pragma warning(disable:4035) /* no return value (duh) */
  55. __declspec(naked) LONG EXTERNAL
  56. CCal_MulDiv(LONG lA, LONG lB, LONG lC)
  57. {
  58. lA; lB; lC;
  59. _asm {
  60. mov eax, [esp+4]
  61. mul dword ptr [esp+8]
  62. div dword ptr [esp+12]
  63. ret 12
  64. }
  65. }
  66. #pragma warning(default:4035)
  67. #endif
  68. /*****************************************************************************
  69. *
  70. * @doc INTERNAL
  71. *
  72. * @method void | CCal | CookAxisPOV |
  73. *
  74. * Cook a piece of POV data into one of five defined data.
  75. *
  76. * @cwrap PJOYRANGECONVERT | this
  77. *
  78. * @parm INOUT PLONG | pl |
  79. *
  80. * On entry, contains the raw value. On exit, contains the
  81. * cooked value. (Or the raw value if the axis is raw.)
  82. *
  83. * @returns
  84. *
  85. * None.
  86. *
  87. *****************************************************************************/
  88. #ifdef WINNT
  89. void CookAxisPOV( PJOYRANGECONVERT this, LONG UNALIGNED *pl )
  90. {
  91. LONG l;
  92. /*
  93. * figure out which direction this value indicates...
  94. */
  95. if( (*pl > this->lMinPOV[JOY_POVVAL_FORWARD])
  96. &&(*pl < this->lMaxPOV[JOY_POVVAL_FORWARD]) )
  97. {
  98. l = JOY_POVFORWARD;
  99. }
  100. else if( (*pl > this->lMinPOV[JOY_POVVAL_BACKWARD])
  101. &&(*pl < this->lMaxPOV[JOY_POVVAL_BACKWARD]) )
  102. {
  103. l = JOY_POVBACKWARD;
  104. }
  105. else if( (*pl > this->lMinPOV[JOY_POVVAL_LEFT])
  106. &&(*pl < this->lMaxPOV[JOY_POVVAL_LEFT]) )
  107. {
  108. l = JOY_POVLEFT;
  109. }
  110. else if( (*pl > this->lMinPOV[JOY_POVVAL_RIGHT])
  111. &&(*pl < this->lMaxPOV[JOY_POVVAL_RIGHT]) )
  112. {
  113. l = JOY_POVRIGHT;
  114. }
  115. else
  116. {
  117. l = JOY_POVCENTERED;
  118. }
  119. #if 0
  120. {
  121. TCHAR buf[100];
  122. wsprintf(buf, TEXT("calibrated pov: %d\r\n"), l);
  123. OutputDebugString(buf);
  124. }
  125. #endif
  126. *pl = l;
  127. }
  128. #endif
  129. /*****************************************************************************
  130. *
  131. * @doc INTERNAL
  132. *
  133. * @method void | CCal | CookRange |
  134. *
  135. * Cook a piece of phys data into a range.
  136. *
  137. * @cwrap PJOYRANGECONVERT | this
  138. *
  139. * @parm INOUT PLONG | pl |
  140. *
  141. * On entry, contains the raw value. On exit, contains the
  142. * cooked value. (Or the raw value if the axis is raw.)
  143. *
  144. * @returns
  145. *
  146. * None.
  147. *
  148. *****************************************************************************/
  149. void EXTERNAL
  150. CCal_CookRange(PJOYRANGECONVERT this, LONG UNALIGNED *pl)
  151. {
  152. if (this->fRaw) {
  153. /*
  154. * Nothing to do!
  155. */
  156. }
  157. #ifdef WINNT
  158. else if ( this->fFakeRaw ) {
  159. if( this->dwPmin & 0x80000000 ) {
  160. *pl -= (long)this->dwPmin;
  161. }
  162. }
  163. #endif
  164. else {
  165. #ifdef WINNT
  166. if( this->fPolledPOV ) {
  167. CookAxisPOV( this, pl );
  168. } else
  169. #endif
  170. {
  171. LONG l;
  172. LONG lRc;
  173. PCJOYRAMP prmp;
  174. l = *pl;
  175. /*
  176. * Choose the low or high ramp, depending on which side we're in.
  177. *
  178. * This comparison could've been against Dmax or Dmin or Pc.
  179. * We must use Dmax because we jiggered up the rmpHigh so
  180. * that it rounds properly, so we can't use the flat part
  181. * below rmpHigh.x because it's at the wrong level.
  182. */
  183. if (l < this->rmpHigh.x) {
  184. prmp = &this->rmpLow;
  185. } else {
  186. prmp = &this->rmpHigh;
  187. }
  188. if (l <= prmp->x) {
  189. lRc = 0;
  190. } else {
  191. l -= prmp->x;
  192. if ((DWORD)l < prmp->dx) {
  193. /*
  194. * Note that prmp->dx cannot be zero because it
  195. * is greater than something!
  196. */
  197. lRc = CCal_MulDiv((DWORD)l, prmp->dy, prmp->dx);
  198. } else {
  199. lRc = prmp->dy;
  200. }
  201. }
  202. lRc += prmp->y;
  203. *pl = lRc;
  204. }
  205. }
  206. }
  207. /*****************************************************************************
  208. *
  209. * @doc INTERNAL
  210. *
  211. * @method void | CCal | RecalcRange |
  212. *
  213. * Compute all the values that derive from the user's
  214. * range settings.
  215. *
  216. * Be careful not to create values that will cause us to
  217. * divide by zero later. Fortunately,
  218. * <f CCal_CookRange> never divides by zero due to the
  219. * clever way it was written.
  220. *
  221. * @cwrap PJOYRANGECONVERT | this
  222. *
  223. * @returns
  224. *
  225. * None.
  226. *
  227. *****************************************************************************/
  228. void EXTERNAL
  229. CCal_RecalcRange(PJOYRANGECONVERT this)
  230. {
  231. int dx;
  232. DWORD dwSat;
  233. AssertF(this->dwDz <= RANGEDIVISIONS);
  234. AssertF(this->dwSat <= RANGEDIVISIONS);
  235. AssertF(this->lMin <= this->lC);
  236. AssertF(this->lC <= this->lMax);
  237. dwSat = max(this->dwSat, this->dwDz);
  238. /* Smin - Bottom of saturation range */
  239. dx = CCal_MulDiv(this->dwPc - this->dwPmin, dwSat, RANGEDIVISIONS);
  240. this->rmpLow.x = this->dwPc - dx;
  241. /* Dmin - Bottom of dead zone */
  242. dx = CCal_MulDiv(this->dwPc - this->dwPmin, this->dwDz, RANGEDIVISIONS);
  243. this->rmpLow.dx = (this->dwPc - dx) - this->rmpLow.x;
  244. /*
  245. * Establish the vertical extent of the low end of the ramp.
  246. */
  247. this->rmpLow.y = this->lMin;
  248. this->rmpLow.dy = this->lC - this->lMin;
  249. /* Dmax - Top of the dead zone */
  250. dx = CCal_MulDiv(this->dwPmax - this->dwPc, this->dwDz, RANGEDIVISIONS);
  251. if ( this->dwPmax > this->dwPc+1 ){
  252. this->rmpHigh.x = this->dwPc + dx + 1;
  253. } else {
  254. this->rmpHigh.x = this->dwPc + dx;
  255. RPF("dwPmax == dwPc (%d). Possible a bug.", this->dwPmax);
  256. }
  257. /* Smax - Top of the saturation range */
  258. dx = CCal_MulDiv(this->dwPmax - this->dwPc, dwSat, RANGEDIVISIONS);
  259. this->rmpHigh.dx = (this->dwPc + dx) - this->rmpHigh.x;
  260. /*
  261. * Establish the vertical extent of the high end of the ramp.
  262. *
  263. * If the high end is zero, then the entire ramp is zero.
  264. * Otherwise, put the bottom at +1 so that when the user
  265. * just barely leaves the dead zone, we report a nonzero
  266. * value. Note: If we were really clever, we could use
  267. * a bias to get "round upwards", but it's not worth it.
  268. *
  269. */
  270. if ( (this->lMax > this->lC) && (this->dwPmax > this->dwPc+1) ) {
  271. this->rmpHigh.y = this->lC + 1;
  272. } else {
  273. this->rmpHigh.y = this->lC;
  274. }
  275. this->rmpHigh.dy = this->lMax - this->rmpHigh.y;
  276. #if 0
  277. RPF( "Raw: %d Dead Zone: 0x%08x Saturation: 0x%08x",
  278. this->fRaw, this->dwDz, this->dwSat );
  279. RPF( "Physical min: 0x%08x max: 0x%08x cen: 0x%08x",
  280. this->lMin, this->lMax, this->lC );
  281. RPF( "Logical min: 0x%08x max: 0x%08x cen: 0x%08x",
  282. this->dwPmin, this->dwPmax, this->dwPc );
  283. RPF( "Lo ramp X: 0x%08x dX: 0x%08x Y: 0x%08x dY: 0x%08x",
  284. this->rmpLow.x, this->rmpLow.dx, this->rmpLow.y, this->rmpLow.dy );
  285. RPF( "Hi ramp X: 0x%08x dX: 0x%08x Y: 0x%08x dY: 0x%08x",
  286. this->rmpHigh.x, this->rmpHigh.dx, this->rmpHigh.y, this->rmpHigh.dy );
  287. #endif
  288. }
  289. /*****************************************************************************
  290. *
  291. * @doc INTERNAL
  292. *
  293. * @method HRESULT | CCal | GetProperty |
  294. *
  295. * Read a property from a calibration structure.
  296. *
  297. * The caller is permitted to pass a property that doesn't
  298. * apply to calibration, in which case <c E_NOTIMPL>
  299. * is returned, as it should be.
  300. *
  301. * @cwrap PJOYRANGECONVERT | this
  302. *
  303. * @parm REFGUID | rguid |
  304. *
  305. * The property being retrieved.
  306. *
  307. * @parm IN REFGUID | rguid |
  308. *
  309. * The identity of the property to be obtained.
  310. *
  311. * @parm IN LPDIPROPHEADER | pdiph |
  312. *
  313. * Points to the <t DIPROPHEADER> portion of a structure
  314. * which depends on the property.
  315. *
  316. * @parm IN DWORD | dwVerion
  317. * Version of DirectInput DLL.
  318. *
  319. * @returns
  320. *
  321. * <c S_OK> if the operation completed successfully.
  322. *
  323. * <c E_NOTIMPL> nothing happened. The caller will do
  324. * the default thing in response to <c E_NOTIMPL>.
  325. *
  326. *****************************************************************************/
  327. STDMETHODIMP
  328. CCal_GetProperty(PJOYRANGECONVERT this, REFGUID rguid, LPDIPROPHEADER pdiph, DWORD dwVersion)
  329. {
  330. HRESULT hres;
  331. LPDIPROPRANGE pdiprg = CONTAINING_RECORD(pdiph, DIPROPRANGE, diph);
  332. LPDIPROPDWORD pdipdw = CONTAINING_RECORD(pdiph, DIPROPDWORD, diph);
  333. LPDIPROPCAL pdipcal = CONTAINING_RECORD(pdiph, DIPROPCAL , diph);
  334. EnterProc(CCal::GetProperty, (_ "pxpx", this, rguid, pdiph, dwVersion));
  335. switch ((DWORD)(UINT_PTR)rguid) {
  336. case (DWORD)(UINT_PTR)DIPROP_RANGE:
  337. pdiprg->lMin = this->lMin;
  338. pdiprg->lMax = this->lMax;
  339. hres = S_OK;
  340. break;
  341. case (DWORD)(UINT_PTR)DIPROP_DEADZONE:
  342. pdipdw->dwData = this->dwDz;
  343. hres = S_OK;
  344. break;
  345. case (DWORD)(UINT_PTR)DIPROP_SATURATION:
  346. pdipdw->dwData = this->dwSat;
  347. hres = S_OK;
  348. break;
  349. case (DWORD)(UINT_PTR)DIPROP_CALIBRATIONMODE:
  350. #ifdef WINNT
  351. if( (dwVersion < 0x700) && (dwVersion != 0x5B2) )
  352. {
  353. pdipdw->dwData = this->fFakeRaw;
  354. } else
  355. #endif
  356. {
  357. pdipdw->dwData = this->fRaw;
  358. }
  359. hres = S_OK;
  360. break;
  361. case (DWORD)(UINT_PTR)DIPROP_CALIBRATION:
  362. #ifdef WINNT
  363. if( (dwVersion < 0x700) && (dwVersion != 0x5B2) && (this->dwPmin & 0x8000) )
  364. {
  365. pdipcal->lMin = 0;
  366. pdipcal->lMax = (long)this->dwPmax - (long)this->dwPmin;
  367. pdipcal->lCenter = CCal_Midpoint(pdipcal->lMin, pdipcal->lMax);;
  368. } else
  369. #endif
  370. {
  371. pdipcal->lMin = this->dwPmin;
  372. pdipcal->lMax = this->dwPmax;
  373. pdipcal->lCenter = this->dwPc;
  374. }
  375. hres = S_OK;
  376. break;
  377. default:
  378. hres = E_NOTIMPL;
  379. break;
  380. }
  381. ExitOleProc();
  382. return hres;
  383. }
  384. /*****************************************************************************
  385. *
  386. * @doc INTERNAL
  387. *
  388. * @method HRESULT | CCal | SetCalibration |
  389. *
  390. * The app (hopefully a control panel) is changing the
  391. * calibration.
  392. *
  393. * @cwrap PJOYRANGECONVERT | this
  394. *
  395. * @parm IN LPCDIPROPINFO | ppropi |
  396. *
  397. * Information describing the property being set.
  398. *
  399. * @parm IN LPCDIPROPHEADER | pdiph |
  400. *
  401. * Points to the <t DIPROPHEADER> portion of a structure
  402. * which depends on the property.
  403. *
  404. * @parm HKEY | hkType |
  405. *
  406. * Registry key to use calibration information.
  407. *
  408. * @returns
  409. *
  410. * <c S_OK> if the operation completed successfully.
  411. *
  412. * <c E_NOTIMPL> nothing happened. The caller will do
  413. * the default thing in response to <c E_NOTIMPL>.
  414. *
  415. *****************************************************************************/
  416. STDMETHODIMP
  417. CCal_SetCalibration(PJOYRANGECONVERT this, LPCDIPROPINFO ppropi,
  418. LPCDIPROPHEADER pdiph, HKEY hkType)
  419. {
  420. HRESULT hres;
  421. #ifdef WINNT
  422. if( ppropi->dwDevType == DIDFT_POV ) {
  423. if( this->fPolledPOV ) {
  424. LPCDIPROPCALPOV pdipcalpov = CONTAINING_RECORD(pdiph, DIPROPCALPOV, diph);
  425. if (hkType) {
  426. LPDIPOVCALIBRATION ppov;
  427. HKEY hk;
  428. /*
  429. * We pun a DIPROPCALPOV as a DIPOVCALIBRATION.
  430. */
  431. #define CheckField(f) \
  432. CAssertF(FIELD_OFFSET(DIPROPCALPOV, l##f) - cbX(DIPROPHEADER) == \
  433. FIELD_OFFSET(DIPOVCALIBRATION, l##f))
  434. CheckField(Min);
  435. CheckField(Max);
  436. #undef CheckField
  437. ppov = pvAddPvCb(pdipcalpov, cbX(DIPROPHEADER));
  438. AssertF( !memcmp(ppov->lMin, pdipcalpov->lMin, cbX(DIPOVCALIBRATION)) );
  439. AssertF( !memcmp(ppov->lMax, pdipcalpov->lMax, cbX(DIPOVCALIBRATION)) );
  440. hres = CType_OpenIdSubkey(hkType, ppropi->dwDevType,
  441. DI_KEY_ALL_ACCESS, &hk);
  442. if (SUCCEEDED(hres)) {
  443. /*
  444. * All 0x0's for calibration is our cue to reset
  445. * to default values.
  446. */
  447. if( ppov->lMin[0] == ppov->lMin[1] == ppov->lMin[2] == ppov->lMin[3] == ppov->lMin[4] ==
  448. ppov->lMax[0] == ppov->lMax[1] == ppov->lMax[2] == ppov->lMax[3] == ppov->lMax[4] == 0 )
  449. {
  450. RegDeleteValue(hk, TEXT("Calibration")) ;
  451. } else
  452. {
  453. hres = JoyReg_SetValue(hk, TEXT("Calibration"),
  454. REG_BINARY, ppov,
  455. cbX(DIPOVCALIBRATION));
  456. }
  457. RegCloseKey(hk);
  458. }
  459. } else {
  460. hres = S_FALSE;
  461. }
  462. if (SUCCEEDED(hres)) {
  463. memcpy( this->lMinPOV, pdipcalpov->lMin, cbX(pdipcalpov->lMin) );
  464. memcpy( this->lMaxPOV, pdipcalpov->lMax, cbX(pdipcalpov->lMax) );
  465. }
  466. } else {
  467. hres = E_NOTIMPL;
  468. }
  469. } else
  470. #endif
  471. {
  472. LPCDIPROPCAL pdipcal = CONTAINING_RECORD(pdiph, DIPROPCAL, diph);
  473. if (hkType) {
  474. LPDIOBJECTCALIBRATION pcal;
  475. HKEY hk;
  476. /*
  477. * We pun a DIPROPCAL as a DIOBJECTCALIBRATION.
  478. */
  479. #define CheckField(f) \
  480. CAssertF(FIELD_OFFSET(DIPROPCAL, l##f) - cbX(DIPROPHEADER) == \
  481. FIELD_OFFSET(DIOBJECTCALIBRATION, l##f))
  482. CheckField(Min);
  483. CheckField(Max);
  484. CheckField(Center);
  485. #undef CheckField
  486. pcal = pvAddPvCb(pdipcal, cbX(DIPROPHEADER));
  487. AssertF(pcal->lMin == pdipcal->lMin);
  488. AssertF(pcal->lMax == pdipcal->lMax);
  489. AssertF(pcal->lCenter == pdipcal->lCenter);
  490. hres = CType_OpenIdSubkey(hkType, ppropi->dwDevType,
  491. DI_KEY_ALL_ACCESS, &hk);
  492. if (SUCCEEDED(hres)) {
  493. /*
  494. * All 0x0's for calibration is our cue to reset
  495. * to default values.
  496. */
  497. if( pcal->lMin == pcal->lMax &&
  498. pcal->lCenter == pcal->lMax &&
  499. pcal->lMax == 0x0 )
  500. {
  501. RegDeleteValue(hk, TEXT("Calibration")) ;
  502. } else
  503. {
  504. hres = JoyReg_SetValue(hk, TEXT("Calibration"),
  505. REG_BINARY, pcal,
  506. cbX(DIOBJECTCALIBRATION));
  507. }
  508. RegCloseKey(hk);
  509. }
  510. } else {
  511. hres = S_FALSE;
  512. }
  513. if (SUCCEEDED(hres)) {
  514. this->dwPmin = pdipcal->lMin;
  515. this->dwPmax = pdipcal->lMax;
  516. this->dwPc = pdipcal->lCenter;
  517. CCal_RecalcRange(this);
  518. }
  519. }
  520. return hres;
  521. }
  522. /*****************************************************************************
  523. *
  524. * @doc INTERNAL
  525. *
  526. * @method HRESULT | CCal | SetProperty |
  527. *
  528. * Write a property to a calibration structure.
  529. *
  530. * The caller is permitted to pass a property that doesn't
  531. * apply to calibration, in which case <c E_NOTIMPL>
  532. * is returned, as it should be.
  533. *
  534. * @cwrap PJOYRANGECONVERT | this
  535. *
  536. * @parm IN LPCDIPROPINFO | ppropi |
  537. *
  538. * Information describing the property being set.
  539. *
  540. * @parm IN LPDIPROPHEADER | pdiph |
  541. *
  542. * Points to the <t DIPROPHEADER> portion of a structure
  543. * which depends on the property.
  544. *
  545. * @parm HKEY | hkType |
  546. *
  547. * Registry key to use if setting calibration information.
  548. *
  549. * @parm IN DWORD | dwVerion
  550. * Version of DirectInput DLL.
  551. *
  552. * @returns
  553. *
  554. * <c S_OK> if the operation completed successfully.
  555. *
  556. * <c E_NOTIMPL> nothing happened. The caller will do
  557. * the default thing in response to <c E_NOTIMPL>.
  558. *
  559. *****************************************************************************/
  560. STDMETHODIMP
  561. CCal_SetProperty(PJOYRANGECONVERT this, LPCDIPROPINFO ppropi,
  562. LPCDIPROPHEADER pdiph, HKEY hkType, DWORD dwVersion)
  563. {
  564. HRESULT hres;
  565. LPCDIPROPRANGE pdiprg = (PCV)pdiph;
  566. LPCDIPROPDWORD pdipdw = (PCV)pdiph;
  567. LPDWORD pdw;
  568. EnterProc(CCal::SetProperty, (_ "pxpx", this, ppropi->pguid, pdiph, dwVersion));
  569. switch ((DWORD)(UINT_PTR)ppropi->pguid) {
  570. case (DWORD)(UINT_PTR)DIPROP_RANGE:
  571. if (pdiprg->lMin <= pdiprg->lMax) {
  572. this->lMin = pdiprg->lMin;
  573. this->lMax = pdiprg->lMax;
  574. this->lC = CCal_Midpoint(this->lMin, this->lMax);
  575. CCal_RecalcRange(this);
  576. SquirtSqflPtszV(sqflCal,
  577. TEXT("CCal_SetProperty:DIPROP_RANGE: lMin: %08x, lMax: %08x"),
  578. this->lMin, this->lMax );
  579. hres = S_OK;
  580. } else {
  581. RPF("ERROR DIPROP_RANGE: lMin must be <= lMax");
  582. hres = E_INVALIDARG;
  583. }
  584. break;
  585. case (DWORD)(UINT_PTR)DIPROP_DEADZONE:
  586. pdw = &this->dwDz;
  587. goto finishfraction;
  588. case (DWORD)(UINT_PTR)DIPROP_SATURATION:
  589. pdw = &this->dwSat;
  590. goto finishfraction;
  591. finishfraction:;
  592. if (pdipdw->dwData <= RANGEDIVISIONS) {
  593. *pdw = pdipdw->dwData;
  594. CCal_RecalcRange(this);
  595. hres = S_OK;
  596. } else {
  597. RPF("SetProperty: Value must be 0 .. 10000");
  598. hres = E_INVALIDARG;
  599. }
  600. break;
  601. case (DWORD)(UINT_PTR)DIPROP_CALIBRATIONMODE:
  602. if ((pdipdw->dwData & ~DIPROPCALIBRATIONMODE_VALID) == 0) {
  603. /*
  604. * Some applications don't like negative raw data, so
  605. * we need cook the data for them instead of giving them
  606. * the real raw data. See Manbug: 45898. -qzheng
  607. */
  608. #ifdef WINNT
  609. if( (dwVersion < 0x700) && (dwVersion != 0x5B2) && (this->dwPmin & 0x8000))
  610. {
  611. this->fFakeRaw = pdipdw->dwData;
  612. } else
  613. #endif
  614. {
  615. this->fRaw = pdipdw->dwData;
  616. }
  617. hres = S_OK;
  618. } else {
  619. RPF("ERROR SetProperty: invalid calibration flags");
  620. hres = E_INVALIDARG;
  621. }
  622. break;
  623. case (DWORD)(UINT_PTR)DIPROP_CALIBRATION:
  624. case (DWORD)(UINT_PTR)DIPROP_SPECIFICCALIBRATION:
  625. hres = CCal_SetCalibration(this, ppropi, pdiph, hkType);
  626. break;
  627. default:
  628. hres = E_NOTIMPL;
  629. break;
  630. }
  631. ExitOleProc();
  632. return hres;
  633. }