]> git.sesse.net Git - vlc/commitdiff
- added support for non OLE containers, such as Windows Script Host WScript.CreateObject.
authorDamien Fouilleul <damienf@videolan.org>
Sat, 25 Mar 2006 22:15:33 +0000 (22:15 +0000)
committerDamien Fouilleul <damienf@videolan.org>
Sat, 25 Mar 2006 22:15:33 +0000 (22:15 +0000)
- made time property persistable, use 'StartTime' under HTML
- removed functions with stdcall suffix from exported functions in DLL

activex/Makefile.am
activex/axvlc.def
activex/persistpropbag.cpp
activex/plugin.cpp
activex/plugin.h
activex/test.html
activex/vlccontrol.cpp

index 7d88e355d3a2582a49c8a929bd8ab26023b951e0..0342823f2d513e6167b205f3021acd3357471036 100644 (file)
@@ -81,7 +81,7 @@ libaxvlc_a_DEPENDENCIES = axvlc.def $(DATA_axvlc_rc)
 axvlc$(LIBEXT): $(libaxvlc_a_OBJECTS) \
                      $(libaxvlc_a_DEPENDENCIES) stamp-pic
        $(CXXLINK) $(libaxvlc_a_OBJECTS) $(DATA_axvlc_rc) \
-       -Wl,--enable-stdcall-fixup $(srcdir)/axvlc.def \
+       $(srcdir)/axvlc.def \
          $(LIBRARIES_libvlc) -shared $(LIBRARIES_libvlc) $(LDFLAGS_activex) \
          $(INCLUDED_LIBINTL)
 # Cygwin work-around
index 431a9a1dac648fe8cf0712fab21811eecac58f3b..87e32f930d4f64e0a927f1135ec9e0ae99e01947 100644 (file)
@@ -1,8 +1,7 @@
 LIBRARY AXVLC.DLL
 EXPORTS
-    CLSID_VLCPlugin data
-    DllMain DllMain@12
-    DllCanUnloadNow DllCanUnloadNow@0
-    DllGetClassObject DllGetClassObject@12
-    DllRegisterServer DllRegisterServer@0
-    DllUnregisterServer DllUnregisterServer@0
+    DllMain = DllMain@12
+    DllCanUnloadNow = DllCanUnloadNow@0
+    DllGetClassObject = DllGetClassObject@12
+    DllRegisterServer = DllRegisterServer@0
+    DllUnregisterServer = DllUnregisterServer@0
index 3fd06fbf28856877570401f9f4494f417ea6cc70..5592991bc4190d9797db3f0ebcd79d75d33974b6 100644 (file)
@@ -167,6 +167,14 @@ STDMETHODIMP VLCPersistPropertyBag::Load(LPPROPERTYBAG pPropBag, LPERRORLOG pErr
         _p_instance->setVolume(V_I4(&value));
         VariantClear(&value);
     }
+
+    V_VT(&value) = VT_I4;
+    if( S_OK == pPropBag->Read(OLESTR("starttime"), &value, pErrorLog) )
+    {
+        _p_instance->setTime(V_I4(&value));
+        VariantClear(&value);
+    }
+
     return _p_instance->onLoad();
 };
 
@@ -212,6 +220,11 @@ STDMETHODIMP VLCPersistPropertyBag::Save(LPPROPERTYBAG pPropBag, BOOL fClearDirt
     pPropBag->Write(OLESTR("Volume"), &value);
     VariantClear(&value);
 
+    V_VT(&value) = VT_I4;
+    V_I4(&value) = _p_instance->getTime();
+    pPropBag->Write(OLESTR("StartTime"), &value);
+    VariantClear(&value);
+
     if( fClearDirty )
         _p_instance->setDirty(FALSE);
 
index 41baf606862eec569dd9f91f771973d68b079579..865d2fbd8720ff853b56c5fdd19f930b1541c79d 100644 (file)
@@ -473,6 +473,7 @@ HRESULT VLCPlugin::onInit(void)
         _b_visible = TRUE;
         _b_mute = FALSE;
         _i_volume = 50;
+        _i_time   = 0;
         // set default/preferred size (320x240) pixels in HIMETRIC
         HDC hDC = CreateDevDC(NULL);
         _extent.cx = 320;
@@ -538,7 +539,7 @@ HRESULT VLCPlugin::onLoad(void)
     return S_OK;
 };
 
-HRESULT VLCPlugin::onRun(void)
+HRESULT VLCPlugin::getVLCObject(int *i_vlc)
 {
     if( ! isRunning() )
     {
@@ -613,11 +614,21 @@ HRESULT VLCPlugin::onRun(void)
         char *psz_mrl = CStrFromBSTR(CP_UTF8, _bstr_mrl);
         if( NULL != psz_mrl )
         {
+            char timeBuffer[32];
+            const char *options[1];
+            int   cOptions = 0;
+
+            if( _i_time )
+            {
+                snprintf(timeBuffer, sizeof(timeBuffer), ":start-time=%d", _i_time);
+                options[cOptions++] = timeBuffer;
+            }
             // add default target to playlist
-            VLC_AddTarget(_i_vlc, psz_mrl, NULL, 0, PLAYLIST_APPEND, PLAYLIST_END);
+            VLC_AddTarget(_i_vlc, psz_mrl, options, cOptions, PLAYLIST_APPEND, PLAYLIST_END);
             CoTaskMemFree(psz_mrl);
         }
     }
+    *i_vlc = _i_vlc;
     return S_OK;
 };
 
@@ -796,26 +807,27 @@ HRESULT VLCPlugin::onActivateInPlace(LPMSG lpMesg, HWND hwndParent, LPCRECT lprc
 
     if( _b_usermode )
     {
-        /* run vlc if not done already */
-        HRESULT result = onRun();
+        /* will run vlc if not done already */
+        int i_vlc;
+        HRESULT result = getVLCObject(&i_vlc);
         if( FAILED(result) )
             return result;
 
         /* set internal video width and height */
         vlc_value_t val;
         val.i_int = posRect.right-posRect.left;
-        VLC_VariableSet(_i_vlc, "conf::width", val);
+        VLC_VariableSet(i_vlc, "conf::width", val);
         val.i_int = posRect.bottom-posRect.top;
-        VLC_VariableSet(_i_vlc, "conf::height", val);
+        VLC_VariableSet(i_vlc, "conf::height", val);
 
         /* set internal video parent window */
         /* horrible cast there */
         val.i_int = reinterpret_cast<int>(_videownd);
-        VLC_VariableSet(_i_vlc, "drawable", val);
+        VLC_VariableSet(i_vlc, "drawable", val);
 
-        if( _b_autoplay & (VLC_PlaylistNumberOfItems(_i_vlc) > 0) )
+        if( _b_autoplay & (VLC_PlaylistNumberOfItems(i_vlc) > 0) )
         {
-            VLC_Play(_i_vlc);
+            VLC_Play(i_vlc);
             fireOnPlayEvent();
         }
     }
@@ -872,6 +884,22 @@ void VLCPlugin::setVolume(int volume)
     }
 };
 
+void VLCPlugin::setTime(int seconds)
+{
+    if( seconds < 0 )
+        seconds = 0;
+
+    if( seconds != _i_time )
+    {
+        _i_time = seconds;
+        if( isRunning() )
+        {
+            VLC_TimeSet(_i_vlc, seconds, VLC_FALSE);
+        }
+        setDirty(TRUE);
+    }
+};
+
 void VLCPlugin::setFocus(BOOL fFocus)
 {
     if( fFocus )
index 843bac502fe65f53ed188a647c2d91407bae1625..964c0129f662e6c9659e20acf578de35426843ed 100644 (file)
@@ -115,6 +115,9 @@ public:
     void setVisible(BOOL fVisible);
     BOOL getVisible(void) { return _b_visible; };
 
+    void setTime(int time);
+    int  getTime(void) { return _i_time; };
+
     // control size in HIMETRIC
     inline void setExtent(const SIZEL& extent)
     {
@@ -162,20 +165,19 @@ public:
     inline void setDirty(BOOL dirty) { _b_dirty = dirty; };
 
     inline BOOL isRunning(void) { return 0 != _i_vlc; };
+    HRESULT getVLCObject(int *i_vlc);
+
 
     // control geometry within container
     RECT getPosRect(void) { return _posRect; }; 
     inline HWND getInPlaceWindow(void) const { return _inplacewnd; };
     BOOL isInPlaceActive(void);
 
-    inline int getVLCObject(void) const { return _i_vlc; };
-
     /*
     ** container events
     */
     HRESULT onInit(void);
     HRESULT onLoad(void);
-    HRESULT onRun(void);
     HRESULT onActivateInPlace(LPMSG lpMesg, HWND hwndParent, LPCRECT lprcPosRect, LPCRECT lprcClipRect);
     HRESULT onInPlaceDeactivate(void);
     HRESULT onAmbientChanged(LPUNKNOWN pContainer, DISPID dispID);
@@ -237,10 +239,12 @@ private:
     BOOL _b_autoloop;
     BOOL _b_visible;
     BOOL _b_mute;
-    BOOL _b_dirty;
     int  _i_volume;
+    int  _i_time;
     SIZEL _extent;
     LPPICTURE _p_pict;
+    // indicates whether properties needs persisting
+    BOOL _b_dirty;
 };
 
 #endif
index bf593290f626dd047c3f3c8fa55607337288d89b..c95a6dc28c7463924c5854c35e3695b0f3c2660e 100644 (file)
@@ -22,6 +22,7 @@ Insert VideoLAN.VLCPlugin.1 activex control
 <param name="AutoLoop" value="False" />
 <param name="AutoPlay" value="False" />
 <param name="Volume" value="50" />
+<param name="StartTime" value="0" />
 </OBJECT>
 </TD></TR>
 <TR><TD>
index 39e2fd6899f722c4bf187872ee51318fda814124..2748457968d9b49237acb5912d1c86f451891714 100644 (file)
@@ -76,7 +76,7 @@ STDMETHODIMP VLCControl::GetTypeInfo(UINT iTInfo, LCID lcid, LPTYPEINFO* ppTInfo
     {
         _p_typeinfo->AddRef();
         *ppTInfo = _p_typeinfo;
-        return NO_ERROR;
+        return NOERROR;
     }
     *ppTInfo = NULL;
     return E_NOTIMPL;
@@ -123,38 +123,38 @@ STDMETHODIMP VLCControl::put_Visible(VARIANT_BOOL isVisible)
 
 STDMETHODIMP VLCControl::play(void)
 {
-    int i_vlc = _p_instance->getVLCObject();
-    if( i_vlc )
+    int i_vlc;
+    HRESULT result = _p_instance->getVLCObject(&i_vlc);
+    if( SUCCEEDED(result) )
     {
         VLC_Play(i_vlc);
         _p_instance->fireOnPlayEvent();
-        return NOERROR;
     }
-    return E_UNEXPECTED;
+    return result;
 };
  
 STDMETHODIMP VLCControl::pause(void)
 {
-    int i_vlc = _p_instance->getVLCObject();
-    if( i_vlc )
+    int i_vlc;
+    HRESULT result = _p_instance->getVLCObject(&i_vlc);
+    if( SUCCEEDED(result) )
     {
         VLC_Pause(i_vlc);
         _p_instance->fireOnPauseEvent();
-        return NOERROR;
     }
-    return E_UNEXPECTED;
+    return result;
 };
         
 STDMETHODIMP VLCControl::stop(void)
 {
-    int i_vlc = _p_instance->getVLCObject();
-    if( i_vlc )
+    int i_vlc;
+    HRESULT result = _p_instance->getVLCObject(&i_vlc);
+    if( SUCCEEDED(result) )
     {
         VLC_Stop(i_vlc);
         _p_instance->fireOnStopEvent();
-        return NOERROR;
     }
-    return E_UNEXPECTED;
+    return result;
 };
         
 STDMETHODIMP VLCControl::get_Playing(VARIANT_BOOL *isPlaying)
@@ -162,14 +162,19 @@ STDMETHODIMP VLCControl::get_Playing(VARIANT_BOOL *isPlaying)
     if( NULL == isPlaying )
         return E_POINTER;
 
-    int i_vlc = _p_instance->getVLCObject();
-    if( i_vlc )
+    HRESULT result = NOERROR;
+    if( _p_instance->isRunning() )
     {
-        *isPlaying = VLC_IsPlaying(i_vlc) ? VARIANT_TRUE : VARIANT_FALSE;
-        return NOERROR;
+        int i_vlc;
+        result = _p_instance->getVLCObject(&i_vlc);
+        if( SUCCEEDED(result) )
+        {
+            *isPlaying = VLC_IsPlaying(i_vlc) ? VARIANT_TRUE : VARIANT_FALSE;
+            return NOERROR;
+        }
     }
     *isPlaying = VARIANT_FALSE;
-    return E_UNEXPECTED;
+    return result;
 };
         
 STDMETHODIMP VLCControl::get_Position(float *position)
@@ -177,25 +182,34 @@ STDMETHODIMP VLCControl::get_Position(float *position)
     if( NULL == position )
         return E_POINTER;
 
-    int i_vlc = _p_instance->getVLCObject();
-    if( i_vlc )
+    HRESULT result = E_UNEXPECTED;
+    if( _p_instance->isRunning() )
     {
-        *position = VLC_PositionGet(i_vlc);
-        return NOERROR;
+        int i_vlc;
+        HRESULT result = _p_instance->getVLCObject(&i_vlc);
+        if( SUCCEEDED(result) )
+        {
+            *position = VLC_PositionGet(i_vlc);
+            return NOERROR;
+        }
     }
     *position = 0.0f;
-    return E_UNEXPECTED;
+    return result;
 };
         
 STDMETHODIMP VLCControl::put_Position(float position)
 {
-    int i_vlc = _p_instance->getVLCObject();
-    if( i_vlc )
+    HRESULT result = E_UNEXPECTED;
+    if( _p_instance->isRunning() )
     {
-        VLC_PositionSet(i_vlc, position);
-        return NOERROR;
+        int i_vlc;
+        HRESULT result = _p_instance->getVLCObject(&i_vlc);
+        if( SUCCEEDED(result) )
+        {
+            VLC_PositionSet(i_vlc, position);
+        }
     }
-    return E_UNEXPECTED;
+    return result;
 };
         
 STDMETHODIMP VLCControl::get_Time(int *seconds)
@@ -203,47 +217,57 @@ STDMETHODIMP VLCControl::get_Time(int *seconds)
     if( NULL == seconds )
         return E_POINTER;
 
-    int i_vlc = _p_instance->getVLCObject();
-    if( i_vlc )
+    HRESULT result = NOERROR;
+    if( _p_instance->isRunning() )
     {
-        *seconds = VLC_TimeGet(i_vlc);
-        return NOERROR;
+        int i_vlc;
+        HRESULT result = _p_instance->getVLCObject(&i_vlc);
+        if( SUCCEEDED(result) )
+        {
+            *seconds = VLC_TimeGet(i_vlc);
+        }
     }
-    *seconds = 0;
-    return E_UNEXPECTED;
+    else
+        *seconds = _p_instance->getTime();
+
+    return result;
 };
-        
+     
 STDMETHODIMP VLCControl::put_Time(int seconds)
 {
-    int i_vlc = _p_instance->getVLCObject();
-    if( i_vlc )
-    {
-        VLC_TimeSet(i_vlc, seconds, VLC_FALSE);
-        return NOERROR;
-    }
-    return E_UNEXPECTED;
+    _p_instance->setTime(seconds);
+
+    return NOERROR;
 };
         
 STDMETHODIMP VLCControl::shuttle(int seconds)
 {
-    int i_vlc = _p_instance->getVLCObject();
-    if( i_vlc )
+    HRESULT result = E_UNEXPECTED;
+    if( _p_instance->isRunning() )
     {
-        VLC_TimeSet(i_vlc, seconds, VLC_TRUE);
-        return NOERROR;
+        int i_vlc;
+        result = _p_instance->getVLCObject(&i_vlc);
+        if( SUCCEEDED(result) )
+        {
+            VLC_TimeSet(i_vlc, seconds, VLC_TRUE);
+        }
     }
-    return E_UNEXPECTED;
+    return result;
 };
         
 STDMETHODIMP VLCControl::fullscreen(void)
 {
-    int i_vlc = _p_instance->getVLCObject();
-    if( i_vlc )
+    HRESULT result = E_UNEXPECTED;
+    if( _p_instance->isRunning() )
     {
-        VLC_FullScreen(i_vlc);
-        return NOERROR;
+        int i_vlc;
+        result = _p_instance->getVLCObject(&i_vlc);
+        if( SUCCEEDED(result) )
+        {
+            VLC_FullScreen(i_vlc);
+        }
     }
-    return E_UNEXPECTED;
+    return result;
 };
         
 STDMETHODIMP VLCControl::get_Length(int *seconds)
@@ -251,36 +275,49 @@ STDMETHODIMP VLCControl::get_Length(int *seconds)
     if( NULL == seconds )
         return E_POINTER;
 
-    int i_vlc = _p_instance->getVLCObject();
-    if( i_vlc )
+    HRESULT result = NOERROR;
+    if( _p_instance->isRunning() )
     {
-        *seconds = VLC_LengthGet(i_vlc);
-        return NOERROR;
+        int i_vlc;
+        result = _p_instance->getVLCObject(&i_vlc);
+        if( SUCCEEDED(result) )
+        {
+            *seconds = VLC_LengthGet(i_vlc);
+            return NOERROR;
+        }
     }
     *seconds = 0;
-    return E_UNEXPECTED;
+    return result;
 };
         
 STDMETHODIMP VLCControl::playFaster(void)
 {
-    int i_vlc = _p_instance->getVLCObject();
-    if( i_vlc )
+    HRESULT result = E_UNEXPECTED;
+    if( _p_instance->isRunning() )
     {
-        VLC_SpeedFaster(i_vlc);
-        return NOERROR;
+        int i_vlc;
+        result = _p_instance->getVLCObject(&i_vlc);
+        if( SUCCEEDED(result) )
+        {
+            VLC_SpeedFaster(i_vlc);
+        }
     }
-    return E_UNEXPECTED;
+    return result;
 };
         
 STDMETHODIMP VLCControl::playSlower(void)
 {
-    int i_vlc = _p_instance->getVLCObject();
-    if( i_vlc )
+    HRESULT result = E_UNEXPECTED;
+    if( _p_instance->isRunning() )
     {
-        VLC_SpeedSlower(i_vlc);
-        return NOERROR;
+        int i_vlc;
+        result = _p_instance->getVLCObject(&i_vlc);
+        if( SUCCEEDED(result) )
+        {
+            VLC_SpeedSlower(i_vlc);
+        }
     }
-    return E_UNEXPECTED;
+    return result;
 };
         
 STDMETHODIMP VLCControl::get_Volume(int *volume)
@@ -300,13 +337,13 @@ STDMETHODIMP VLCControl::put_Volume(int volume)
         
 STDMETHODIMP VLCControl::toggleMute(void)
 {
-    int i_vlc = _p_instance->getVLCObject();
-    if( i_vlc )
+    int i_vlc;
+    HRESULT result = _p_instance->getVLCObject(&i_vlc);
+    if( SUCCEEDED(result) )
     {
         VLC_VolumeMute(i_vlc);
-        return NOERROR;
     }
-    return E_UNEXPECTED;
+    return result;
 };
 
 STDMETHODIMP VLCControl::setVariable(BSTR name, VARIANT value)
@@ -314,15 +351,15 @@ STDMETHODIMP VLCControl::setVariable(BSTR name, VARIANT value)
     if( 0 == SysStringLen(name) )
         return E_INVALIDARG;
 
-    int i_vlc = _p_instance->getVLCObject();
-    if( i_vlc )
+    int i_vlc;
+    HRESULT hr = _p_instance->getVLCObject(&i_vlc);
+    if( SUCCEEDED(hr) )
     {
         int codePage = _p_instance->getCodePage();
         char *psz_varname = CStrFromBSTR(codePage, name);
         if( NULL == psz_varname )
             return E_OUTOFMEMORY;
 
-        HRESULT hr = E_INVALIDARG;
         int i_type;
         vlc_value_t val;
         
@@ -378,7 +415,7 @@ STDMETHODIMP VLCControl::setVariable(BSTR name, VARIANT value)
             }
         }
         else {
-            // no defined type, defaults to VARIANT type
+            // no defined type, use type in VARIANT
             hr = NO_ERROR;
             switch( V_VT(&value) )
             {
@@ -415,10 +452,8 @@ STDMETHODIMP VLCControl::setVariable(BSTR name, VARIANT value)
                 CoTaskMemFree(val.psz_string);
         }
         CoTaskMemFree(psz_varname);
-
-        return hr;
     }
-    return E_UNEXPECTED;
+    return hr;
 };
 
 STDMETHODIMP VLCControl::getVariable( BSTR name, VARIANT *value)
@@ -431,15 +466,16 @@ STDMETHODIMP VLCControl::getVariable( BSTR name, VARIANT *value)
     if( 0 == SysStringLen(name) )
         return E_INVALIDARG;
 
-    int i_vlc = _p_instance->getVLCObject();
-    if( i_vlc )
+    int i_vlc;
+    HRESULT hr = _p_instance->getVLCObject(&i_vlc);
+    if( SUCCEEDED(hr) )
     {
         UINT codePage = _p_instance->getCodePage();
         char *psz_varname = CStrFromBSTR(codePage, name);
         if( NULL == psz_varname )
             return E_OUTOFMEMORY;
 
-        HRESULT hr = E_INVALIDARG;
+        hr = E_INVALIDARG;
 
         vlc_value_t val;
         int i_type;
@@ -490,7 +526,7 @@ STDMETHODIMP VLCControl::getVariable( BSTR name, VARIANT *value)
         CoTaskMemFree(psz_varname);
         return hr;
     }
-    return E_UNEXPECTED;
+    return hr;
 };
 
 static void freeTargetOptions(char **cOptions, int cOptionCount)
@@ -703,10 +739,9 @@ STDMETHODIMP VLCControl::addTarget( BSTR uri, VARIANT options, enum VLCPlaylistM
     if( 0 == SysStringLen(uri) )
         return E_INVALIDARG;
 
-    HRESULT hr = E_UNEXPECTED;
-
-    int i_vlc = _p_instance->getVLCObject();
-    if( i_vlc )
+    int i_vlc;
+    HRESULT hr = _p_instance->getVLCObject(&i_vlc);
+    if( SUCCEEDED(hr) )
     {
         char *cUri = CStrFromBSTR(CP_UTF8, uri);
         if( NULL == cUri )
@@ -742,59 +777,64 @@ STDMETHODIMP VLCControl::get_PlaylistIndex(int *index)
     if( NULL == index )
         return E_POINTER;
 
-    int i_vlc = _p_instance->getVLCObject();
-    if( i_vlc )
+    int i_vlc;
+    HRESULT result = _p_instance->getVLCObject(&i_vlc);
+    if( SUCCEEDED(result) )
     {
         *index = VLC_PlaylistIndex(i_vlc);
         return NOERROR;
     }
     *index = 0;
-    return E_UNEXPECTED;
+    return result;
 };
         
 STDMETHODIMP VLCControl::get_PlaylistCount(int *count)
 {
-    int i_vlc = _p_instance->getVLCObject();
-    if( i_vlc )
+    int i_vlc;
+    HRESULT result = _p_instance->getVLCObject(&i_vlc);
+    if( SUCCEEDED(result) )
     {
         *count = VLC_PlaylistNumberOfItems(i_vlc);
         return NOERROR;
     }
     *count = 0;
-    return E_UNEXPECTED;
+    return result;
 };
         
 STDMETHODIMP VLCControl::playlistNext(void)
 {
-    int i_vlc = _p_instance->getVLCObject();
-    if( i_vlc )
+    int i_vlc;
+    HRESULT result = _p_instance->getVLCObject(&i_vlc);
+    if( SUCCEEDED(result) )
     {
         VLC_PlaylistNext(i_vlc);
         return NOERROR;
     }
-    return E_UNEXPECTED;
+    return result;
 };
         
 STDMETHODIMP VLCControl::playlistPrev(void)
 {
-    int i_vlc = _p_instance->getVLCObject();
-    if( i_vlc )
+    int i_vlc;
+    HRESULT result = _p_instance->getVLCObject(&i_vlc);
+    if( SUCCEEDED(result) )
     {
         VLC_PlaylistPrev(i_vlc);
         return NOERROR;
     }
-    return E_UNEXPECTED;
+    return result;
 };
         
 STDMETHODIMP VLCControl::playlistClear(void)
 {
-    int i_vlc = _p_instance->getVLCObject();
-    if( i_vlc )
+    int i_vlc;
+    HRESULT result = _p_instance->getVLCObject(&i_vlc);
+    if( SUCCEEDED(result) )
     {
         VLC_PlaylistClear(i_vlc);
         return NOERROR;
     }
-    return E_UNEXPECTED;
+    return result;
 };
         
 STDMETHODIMP VLCControl::get_VersionInfo(BSTR *version)