diff options
Diffstat (limited to 'server/Windows')
27 files changed, 4309 insertions, 0 deletions
diff --git a/server/Windows/CMakeLists.txt b/server/Windows/CMakeLists.txt new file mode 100644 index 0000000..2744b65 --- /dev/null +++ b/server/Windows/CMakeLists.txt @@ -0,0 +1,128 @@ +# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP Windows Server cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+set(MODULE_NAME "wfreerdp-server")
+set(MODULE_PREFIX "FREERDP_SERVER_WINDOWS")
+
+include (WarnUnmaintained)
+warn_unmaintained(${MODULE_NAME})
+
+include_directories(.)
+
+set(${MODULE_PREFIX}_SRCS
+ wf_update.c
+ wf_update.h
+ wf_dxgi.c
+ wf_dxgi.h
+ wf_input.c
+ wf_input.h
+ wf_interface.c
+ wf_interface.h
+ wf_mirage.c
+ wf_mirage.h
+ wf_peer.c
+ wf_peer.h
+ wf_settings.c
+ wf_settings.h
+ wf_info.c
+ wf_info.h)
+
+if(CHANNEL_RDPSND AND NOT WITH_RDPSND_DSOUND)
+ set(${MODULE_PREFIX}_SRCS
+ ${${MODULE_PREFIX}_SRCS}
+ wf_rdpsnd.c
+ wf_rdpsnd.h
+ wf_wasapi.c
+ wf_wasapi.h
+ )
+endif()
+
+if(CHANNEL_RDPSND AND WITH_RDPSND_DSOUND)
+ set(${MODULE_PREFIX}_SRCS
+ ${${MODULE_PREFIX}_SRCS}
+ wf_rdpsnd.c
+ wf_rdpsnd.h
+ wf_directsound.c
+ wf_directsound.h
+ )
+endif()
+
+# On windows create dll version information.
+# Vendor, product and year are already set in top level CMakeLists.txt
+set (RC_VERSION_MAJOR ${FREERDP_VERSION_MAJOR})
+set (RC_VERSION_MINOR ${FREERDP_VERSION_MINOR})
+set (RC_VERSION_BUILD ${FREERDP_VERSION_REVISION})
+if(WITH_SERVER_INTERFACE)
+ set (RC_VERSION_FILE "${CMAKE_SHARED_LIBRARY_PREFIX}${MODULE_NAME}${FREERDP_API_VERSION}${CMAKE_SHARED_LIBRARY_SUFFIX}" )
+else()
+ set (RC_VERSION_FILE "${MODULE_NAME}${CMAKE_EXECUTABLE_SUFFIX}" )
+endif()
+
+configure_file(
+ ${PROJECT_SOURCE_DIR}/cmake/WindowsDLLVersion.rc.in
+ ${CMAKE_CURRENT_BINARY_DIR}/version.rc
+ @ONLY)
+
+set (${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_SRCS} ${CMAKE_CURRENT_BINARY_DIR}/version.rc)
+
+if(WITH_SERVER_INTERFACE)
+ add_library(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
+ set_target_properties(${MODULE_NAME} PROPERTIES OUTPUT_NAME ${MODULE_NAME}${FREERDP_API_VERSION})
+ if (WITH_LIBRARY_VERSIONING)
+ set_target_properties(${MODULE_NAME} PROPERTIES VERSION ${FREERDP_VERSION} SOVERSION ${FREERDP_API_VERSION})
+ endif()
+ target_include_directories(${MODULE_NAME} INTERFACE $<INSTALL_INTERFACE:include>)
+else()
+ set(${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_SRCS} cli/wfreerdp.c cli/wfreerdp.h)
+ add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
+ if (WITH_BINARY_VERSIONING)
+ set_target_properties(${MODULE_NAME} PROPERTIES OUTPUT_NAME "${MODULE_NAME}${FREERDP_API_VERSION}")
+ endif()
+endif()
+
+
+if(NOT CMAKE_WINDOWS_VERSION STREQUAL "WINXP")
+ set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} d3d11 dxgi)
+endif()
+
+set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} dsound)
+set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} freerdp-server freerdp)
+
+target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS})
+
+if(WITH_SERVER_INTERFACE)
+ install(TARGETS ${MODULE_NAME} COMPONENT libraries
+ ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
+
+ if (WITH_DEBUG_SYMBOLS AND MSVC AND BUILD_SHARED_LIBS)
+ install(FILES ${PROJECT_BINARY_DIR}/${MODULE_NAME}${FREERDP_VERSION_MAJOR}.pdb DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT symbols)
+ endif()
+else()
+ install(TARGETS ${MODULE_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT server)
+
+ if (WITH_DEBUG_SYMBOLS AND MSVC)
+ install(FILES ${PROJECT_BINARY_DIR}/${MODULE_NAME}.pdb DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT symbols)
+ endif()
+endif()
+
+if(WITH_SERVER_INTERFACE)
+ add_subdirectory(cli)
+endif()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Server/Windows")
diff --git a/server/Windows/ModuleOptions.cmake b/server/Windows/ModuleOptions.cmake new file mode 100644 index 0000000..69d7596 --- /dev/null +++ b/server/Windows/ModuleOptions.cmake @@ -0,0 +1,4 @@ + +set(FREERDP_SERVER_NAME "wfreerdp-server") +set(FREERDP_SERVER_PLATFORM "Windows") +set(FREERDP_SERVER_VENDOR "FreeRDP") diff --git a/server/Windows/cli/CMakeLists.txt b/server/Windows/cli/CMakeLists.txt new file mode 100644 index 0000000..e125ac3 --- /dev/null +++ b/server/Windows/cli/CMakeLists.txt @@ -0,0 +1,60 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP Windows Server (CLI) cmake build script +# +# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com> +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set(MODULE_NAME "wfreerdp-server-cli") +set(OUTPUT_NAME " wfreerdp-server") +set(MODULE_PREFIX "FREERDP_SERVER_WINDOWS_CLI") + +include_directories(..) + +set(${MODULE_PREFIX}_SRCS + wfreerdp.c + wfreerdp.h) + +# On windows create dll version information. +# Vendor, product and year are already set in top level CMakeLists.txt +set (RC_VERSION_MAJOR ${FREERDP_VERSION_MAJOR}) +set (RC_VERSION_MINOR ${FREERDP_VERSION_MINOR}) +set (RC_VERSION_BUILD ${FREERDP_VERSION_REVISION}) +set (RC_VERSION_FILE "${MODULE_NAME}${CMAKE_EXECUTABLE_SUFFIX}" ) + +configure_file( + ${PROJECT_SOURCE_DIR}/cmake/WindowsDLLVersion.rc.in + ${CMAKE_CURRENT_BINARY_DIR}/version.rc + @ONLY) + +set (${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_SRCS} ${CMAKE_CURRENT_BINARY_DIR}/version.rc) + +add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS}) + +if (WITH_BINARY_VERSIONING) + set_target_properties(${MODULE_NAME} PROPERTIES OUTPUT_NAME "${OUTPUT_NAME}${FREERDP_API_VERSION}") +else() + set_target_properties(${MODULE_NAME} PROPERTIES OUTPUT_NAME "${OUTPUT_NAME}") +endif() + +set(${MODULE_PREFIX}_LIBS wfreerdp-server) + +target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS}) + +install(TARGETS ${MODULE_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT server) + +if (WITH_DEBUG_SYMBOLS AND MSVC) + install(FILES ${PROJECT_BINARY_DIR}/${OUTPUT_NAME}.pdb DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT symbols) +endif() + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Server/Windows") diff --git a/server/Windows/cli/wfreerdp.c b/server/Windows/cli/wfreerdp.c new file mode 100644 index 0000000..efc1bf5 --- /dev/null +++ b/server/Windows/cli/wfreerdp.c @@ -0,0 +1,171 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Windows Server + * + * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <freerdp/config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> + +#include <winpr/tchar.h> +#include <winpr/windows.h> + +#include "wf_interface.h" + +#include "wfreerdp.h" + +#include <freerdp/server/server-common.h> +#include <freerdp/log.h> +#define TAG SERVER_TAG("windows") + +int IDcount = 0; + +BOOL CALLBACK moncb(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) +{ + WLog_DBG(TAG, "%d\t(%ld, %ld), (%ld, %ld)", IDcount, lprcMonitor->left, lprcMonitor->top, + lprcMonitor->right, lprcMonitor->bottom); + IDcount++; + return TRUE; +} + +int main(int argc, char* argv[]) +{ + freerdp_server_warn_unmaintained(argc, argv); + + BOOL screen_selected = FALSE; + int index = 1; + wfServer* server; + server = wfreerdp_server_new(); + set_screen_id(0); + // handle args + errno = 0; + + while (index < argc) + { + // first the args that will cause the program to terminate + if (strcmp("--list-screens", argv[index]) == 0) + { + int width; + int height; + int bpp; + WLog_INFO(TAG, "Detecting screens..."); + WLog_INFO(TAG, "ID\tResolution\t\tName (Interface)"); + + for (int i = 0;; i++) + { + _TCHAR name[128] = { 0 }; + if (get_screen_info(i, name, ARRAYSIZE(name), &width, &height, &bpp) != 0) + { + if ((width * height * bpp) == 0) + continue; + + WLog_INFO(TAG, "%d\t%dx%dx%d\t", i, width, height, bpp); + WLog_INFO(TAG, "%s", name); + } + else + { + break; + } + } + + { + int vscreen_w; + int vscreen_h; + vscreen_w = GetSystemMetrics(SM_CXVIRTUALSCREEN); + vscreen_h = GetSystemMetrics(SM_CYVIRTUALSCREEN); + WLog_INFO(TAG, ""); + EnumDisplayMonitors(NULL, NULL, moncb, 0); + IDcount = 0; + WLog_INFO(TAG, "Virtual Screen = %dx%d", vscreen_w, vscreen_h); + } + + return 0; + } + + if (strcmp("--screen", argv[index]) == 0) + { + UINT32 val; + screen_selected = TRUE; + index++; + + if (index == argc) + { + WLog_INFO(TAG, "missing screen id parameter"); + return 0; + } + + val = strtoul(argv[index], NULL, 0); + + if ((errno != 0) || (val > UINT32_MAX)) + return -1; + + set_screen_id(val); + index++; + } + + if (index == argc - 1) + { + UINT32 val = strtoul(argv[index], NULL, 0); + + if ((errno != 0) || (val > UINT32_MAX)) + return -1; + + server->port = val; + break; + } + } + + if (screen_selected == FALSE) + { + int width; + int height; + int bpp; + WLog_INFO(TAG, "screen id not provided. attempting to detect..."); + WLog_INFO(TAG, "Detecting screens..."); + WLog_INFO(TAG, "ID\tResolution\t\tName (Interface)"); + + for (int i = 0;; i++) + { + _TCHAR name[128] = { 0 }; + if (get_screen_info(i, name, ARRAYSIZE(name), &width, &height, &bpp) != 0) + { + if ((width * height * bpp) == 0) + continue; + + WLog_INFO(TAG, "%d\t%dx%dx%d\t", i, width, height, bpp); + WLog_INFO(TAG, "%s", name); + set_screen_id(i); + break; + } + else + { + break; + } + } + } + + WLog_INFO(TAG, "Starting server"); + wfreerdp_server_start(server); + WaitForSingleObject(server->thread, INFINITE); + WLog_INFO(TAG, "Stopping server"); + wfreerdp_server_stop(server); + wfreerdp_server_free(server); + return 0; +} diff --git a/server/Windows/cli/wfreerdp.h b/server/Windows/cli/wfreerdp.h new file mode 100644 index 0000000..017106d --- /dev/null +++ b/server/Windows/cli/wfreerdp.h @@ -0,0 +1,25 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Windows Server + * + * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_SERVER_WIN_FREERDP_H +#define FREERDP_SERVER_WIN_FREERDP_H + +#include <freerdp/freerdp.h> + +#endif /* FREERDP_SERVER_WIN_FREERDP_H */ diff --git a/server/Windows/wf_directsound.c b/server/Windows/wf_directsound.c new file mode 100644 index 0000000..441397a --- /dev/null +++ b/server/Windows/wf_directsound.c @@ -0,0 +1,219 @@ +#include "wf_directsound.h" +#include "wf_interface.h" +#include "wf_info.h" +#include "wf_rdpsnd.h" + +#include <winpr/windows.h> + +#define INITGUID +#include <initguid.h> +#include <objbase.h> + +#define CINTERFACE 1 +#include <mmsystem.h> +#include <dsound.h> + +#include <freerdp/log.h> +#define TAG SERVER_TAG("windows") + +IDirectSoundCapture8* cap; +IDirectSoundCaptureBuffer8* capBuf; +DSCBUFFERDESC dscbd; +DWORD lastPos; +wfPeerContext* latestPeer; + +int wf_rdpsnd_set_latest_peer(wfPeerContext* peer) +{ + latestPeer = peer; + return 0; +} + +int wf_directsound_activate(RdpsndServerContext* context) +{ + HRESULT hr; + wfInfo* wfi; + HANDLE hThread; + + LPDIRECTSOUNDCAPTUREBUFFER pDSCB; + + wfi = wf_info_get_instance(); + if (!wfi) + { + WLog_ERR(TAG, "Failed to wfi instance"); + return 1; + } + WLog_DBG(TAG, "RDPSND (direct sound) Activated"); + hr = DirectSoundCaptureCreate8(NULL, &cap, NULL); + + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to create sound capture device"); + return 1; + } + + WLog_INFO(TAG, "Created sound capture device"); + dscbd.dwSize = sizeof(DSCBUFFERDESC); + dscbd.dwFlags = 0; + dscbd.dwBufferBytes = wfi->agreed_format->nAvgBytesPerSec; + dscbd.dwReserved = 0; + dscbd.lpwfxFormat = wfi->agreed_format; + dscbd.dwFXCount = 0; + dscbd.lpDSCFXDesc = NULL; + + hr = cap->lpVtbl->CreateCaptureBuffer(cap, &dscbd, &pDSCB, NULL); + + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to create capture buffer"); + } + + WLog_INFO(TAG, "Created capture buffer"); + hr = pDSCB->lpVtbl->QueryInterface(pDSCB, &IID_IDirectSoundCaptureBuffer8, (LPVOID*)&capBuf); + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to QI capture buffer"); + } + WLog_INFO(TAG, "Created IDirectSoundCaptureBuffer8"); + pDSCB->lpVtbl->Release(pDSCB); + lastPos = 0; + + if (!(hThread = CreateThread(NULL, 0, wf_rdpsnd_directsound_thread, latestPeer, 0, NULL))) + { + WLog_ERR(TAG, "Failed to create direct sound thread"); + return 1; + } + CloseHandle(hThread); + + return 0; +} + +static DWORD WINAPI wf_rdpsnd_directsound_thread(LPVOID lpParam) +{ + HRESULT hr; + DWORD beg = 0; + DWORD end = 0; + DWORD diff, rate; + wfPeerContext* context; + wfInfo* wfi; + + VOID* pbCaptureData = NULL; + DWORD dwCaptureLength = 0; + VOID* pbCaptureData2 = NULL; + DWORD dwCaptureLength2 = 0; + VOID* pbPlayData = NULL; + DWORD dwReadPos = 0; + LONG lLockSize = 0; + + wfi = wf_info_get_instance(); + if (!wfi) + { + WLog_ERR(TAG, "Failed get instance"); + return 1; + } + + context = (wfPeerContext*)lpParam; + rate = 1000 / 24; + WLog_INFO(TAG, "Trying to start capture"); + hr = capBuf->lpVtbl->Start(capBuf, DSCBSTART_LOOPING); + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to start capture"); + } + WLog_INFO(TAG, "Capture started"); + + while (1) + { + + end = GetTickCount(); + diff = end - beg; + + if (diff < rate) + { + Sleep(rate - diff); + } + + beg = GetTickCount(); + + if (wf_rdpsnd_lock() > 0) + { + // check for main exit condition + if (wfi->snd_stop == TRUE) + { + wf_rdpsnd_unlock(); + break; + } + + hr = capBuf->lpVtbl->GetCurrentPosition(capBuf, NULL, &dwReadPos); + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to get read pos"); + wf_rdpsnd_unlock(); + break; + } + + lLockSize = dwReadPos - lastPos; // dscbd.dwBufferBytes; + if (lLockSize < 0) + lLockSize += dscbd.dwBufferBytes; + + // WLog_DBG(TAG, "Last, read, lock = [%"PRIu32", %"PRIu32", %"PRId32"]\n", lastPos, + // dwReadPos, lLockSize); + + if (lLockSize == 0) + { + wf_rdpsnd_unlock(); + continue; + } + + hr = capBuf->lpVtbl->Lock(capBuf, lastPos, lLockSize, &pbCaptureData, &dwCaptureLength, + &pbCaptureData2, &dwCaptureLength2, 0L); + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to lock sound capture buffer"); + wf_rdpsnd_unlock(); + break; + } + + // fwrite(pbCaptureData, 1, dwCaptureLength, pFile); + // fwrite(pbCaptureData2, 1, dwCaptureLength2, pFile); + + // FIXME: frames = bytes/(bytespersample * channels) + + context->rdpsnd->SendSamples(context->rdpsnd, pbCaptureData, dwCaptureLength / 4, + (UINT16)(beg & 0xffff)); + context->rdpsnd->SendSamples(context->rdpsnd, pbCaptureData2, dwCaptureLength2 / 4, + (UINT16)(beg & 0xffff)); + + hr = capBuf->lpVtbl->Unlock(capBuf, pbCaptureData, dwCaptureLength, pbCaptureData2, + dwCaptureLength2); + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to unlock sound capture buffer"); + wf_rdpsnd_unlock(); + return 0; + } + + // TODO keep track of location in buffer + lastPos += dwCaptureLength; + lastPos %= dscbd.dwBufferBytes; + lastPos += dwCaptureLength2; + lastPos %= dscbd.dwBufferBytes; + + wf_rdpsnd_unlock(); + } + } + + WLog_INFO(TAG, "Trying to stop sound capture"); + hr = capBuf->lpVtbl->Stop(capBuf); + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to stop capture"); + } + + WLog_INFO(TAG, "Capture stopped"); + capBuf->lpVtbl->Release(capBuf); + cap->lpVtbl->Release(cap); + + lastPos = 0; + + return 0; +} diff --git a/server/Windows/wf_directsound.h b/server/Windows/wf_directsound.h new file mode 100644 index 0000000..01c1bdd --- /dev/null +++ b/server/Windows/wf_directsound.h @@ -0,0 +1,13 @@ +#ifndef FREERDP_SERVER_WIN_DSOUND_H +#define FREERDP_SERVER_WIN_DSOUND_H + +#include <freerdp/server/rdpsnd.h> +#include "wf_interface.h" + +int wf_rdpsnd_set_latest_peer(wfPeerContext* peer); + +int wf_directsound_activate(RdpsndServerContext* context); + +DWORD WINAPI wf_rdpsnd_directsound_thread(LPVOID lpParam); + +#endif /* FREERDP_SERVER_WIN_DSOUND_H */ diff --git a/server/Windows/wf_dxgi.c b/server/Windows/wf_dxgi.c new file mode 100644 index 0000000..899cb55 --- /dev/null +++ b/server/Windows/wf_dxgi.c @@ -0,0 +1,486 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Windows Server + * + * Copyright 2012 Corey Clayton <can.of.tuna@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <freerdp/config.h> + +#include "wf_interface.h" + +#ifdef WITH_DXGI_1_2 + +#define CINTERFACE + +#include <D3D11.h> +#include <dxgi1_2.h> + +#include <tchar.h> +#include "wf_dxgi.h" + +#include <freerdp/log.h> +#define TAG SERVER_TAG("windows") + +/* Driver types supported */ +D3D_DRIVER_TYPE DriverTypes[] = { + D3D_DRIVER_TYPE_HARDWARE, + D3D_DRIVER_TYPE_WARP, + D3D_DRIVER_TYPE_REFERENCE, +}; +UINT NumDriverTypes = ARRAYSIZE(DriverTypes); + +/* Feature levels supported */ +D3D_FEATURE_LEVEL FeatureLevels[] = { D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1, + D3D_FEATURE_LEVEL_10_0, D3D_FEATURE_LEVEL_9_1 }; + +UINT NumFeatureLevels = ARRAYSIZE(FeatureLevels); + +D3D_FEATURE_LEVEL FeatureLevel; + +ID3D11Device* gDevice = NULL; +ID3D11DeviceContext* gContext = NULL; +IDXGIOutputDuplication* gOutputDuplication = NULL; +ID3D11Texture2D* gAcquiredDesktopImage = NULL; + +IDXGISurface* surf; +ID3D11Texture2D* sStage; + +DXGI_OUTDUPL_FRAME_INFO FrameInfo; + +int wf_dxgi_init(wfInfo* wfi) +{ + gAcquiredDesktopImage = NULL; + + if (wf_dxgi_createDevice(wfi) != 0) + { + return 1; + } + + if (wf_dxgi_getDuplication(wfi) != 0) + { + return 1; + } + + return 0; +} + +int wf_dxgi_createDevice(wfInfo* wfi) +{ + HRESULT status; + UINT DriverTypeIndex; + + for (DriverTypeIndex = 0; DriverTypeIndex < NumDriverTypes; ++DriverTypeIndex) + { + status = D3D11CreateDevice(NULL, DriverTypes[DriverTypeIndex], NULL, 0, FeatureLevels, + NumFeatureLevels, D3D11_SDK_VERSION, &gDevice, &FeatureLevel, + &gContext); + if (SUCCEEDED(status)) + break; + + WLog_INFO(TAG, "D3D11CreateDevice returned [%ld] for Driver Type %d", status, + DriverTypes[DriverTypeIndex]); + } + + if (FAILED(status)) + { + WLog_ERR(TAG, "Failed to create device in InitializeDx"); + return 1; + } + + return 0; +} + +int wf_dxgi_getDuplication(wfInfo* wfi) +{ + HRESULT status; + UINT dTop, i = 0; + DXGI_OUTPUT_DESC desc = { 0 }; + IDXGIOutput* pOutput; + IDXGIDevice* DxgiDevice = NULL; + IDXGIAdapter* DxgiAdapter = NULL; + IDXGIOutput* DxgiOutput = NULL; + IDXGIOutput1* DxgiOutput1 = NULL; + + status = gDevice->lpVtbl->QueryInterface(gDevice, &IID_IDXGIDevice, (void**)&DxgiDevice); + + if (FAILED(status)) + { + WLog_ERR(TAG, "Failed to get QI for DXGI Device"); + return 1; + } + + status = DxgiDevice->lpVtbl->GetParent(DxgiDevice, &IID_IDXGIAdapter, (void**)&DxgiAdapter); + DxgiDevice->lpVtbl->Release(DxgiDevice); + DxgiDevice = NULL; + + if (FAILED(status)) + { + WLog_ERR(TAG, "Failed to get parent DXGI Adapter"); + return 1; + } + + pOutput = NULL; + + while (DxgiAdapter->lpVtbl->EnumOutputs(DxgiAdapter, i, &pOutput) != DXGI_ERROR_NOT_FOUND) + { + DXGI_OUTPUT_DESC* pDesc = &desc; + + status = pOutput->lpVtbl->GetDesc(pOutput, pDesc); + + if (FAILED(status)) + { + WLog_ERR(TAG, "Failed to get description"); + return 1; + } + + WLog_INFO(TAG, "Output %u: [%s] [%d]", i, pDesc->DeviceName, pDesc->AttachedToDesktop); + + if (pDesc->AttachedToDesktop) + dTop = i; + + pOutput->lpVtbl->Release(pOutput); + ++i; + } + + dTop = wfi->screenID; + + status = DxgiAdapter->lpVtbl->EnumOutputs(DxgiAdapter, dTop, &DxgiOutput); + DxgiAdapter->lpVtbl->Release(DxgiAdapter); + DxgiAdapter = NULL; + + if (FAILED(status)) + { + WLog_ERR(TAG, "Failed to get output"); + return 1; + } + + status = + DxgiOutput->lpVtbl->QueryInterface(DxgiOutput, &IID_IDXGIOutput1, (void**)&DxgiOutput1); + DxgiOutput->lpVtbl->Release(DxgiOutput); + DxgiOutput = NULL; + + if (FAILED(status)) + { + WLog_ERR(TAG, "Failed to get IDXGIOutput1"); + return 1; + } + + status = + DxgiOutput1->lpVtbl->DuplicateOutput(DxgiOutput1, (IUnknown*)gDevice, &gOutputDuplication); + DxgiOutput1->lpVtbl->Release(DxgiOutput1); + DxgiOutput1 = NULL; + + if (FAILED(status)) + { + if (status == DXGI_ERROR_NOT_CURRENTLY_AVAILABLE) + { + WLog_ERR( + TAG, + "There is already the maximum number of applications using the Desktop Duplication " + "API running, please close one of those applications and then try again."); + return 1; + } + + WLog_ERR(TAG, "Failed to get duplicate output. Status = %ld", status); + return 1; + } + + return 0; +} + +int wf_dxgi_cleanup(wfInfo* wfi) +{ + if (wfi->framesWaiting > 0) + { + wf_dxgi_releasePixelData(wfi); + } + + if (gAcquiredDesktopImage) + { + gAcquiredDesktopImage->lpVtbl->Release(gAcquiredDesktopImage); + gAcquiredDesktopImage = NULL; + } + + if (gOutputDuplication) + { + gOutputDuplication->lpVtbl->Release(gOutputDuplication); + gOutputDuplication = NULL; + } + + if (gContext) + { + gContext->lpVtbl->Release(gContext); + gContext = NULL; + } + + if (gDevice) + { + gDevice->lpVtbl->Release(gDevice); + gDevice = NULL; + } + + return 0; +} + +int wf_dxgi_nextFrame(wfInfo* wfi, UINT timeout) +{ + HRESULT status = 0; + UINT i = 0; + UINT DataBufferSize = 0; + BYTE* DataBuffer = NULL; + IDXGIResource* DesktopResource = NULL; + + if (wfi->framesWaiting > 0) + { + wf_dxgi_releasePixelData(wfi); + } + + if (gAcquiredDesktopImage) + { + gAcquiredDesktopImage->lpVtbl->Release(gAcquiredDesktopImage); + gAcquiredDesktopImage = NULL; + } + + status = gOutputDuplication->lpVtbl->AcquireNextFrame(gOutputDuplication, timeout, &FrameInfo, + &DesktopResource); + + if (status == DXGI_ERROR_WAIT_TIMEOUT) + { + return 1; + } + + if (FAILED(status)) + { + if (status == DXGI_ERROR_ACCESS_LOST) + { + WLog_ERR(TAG, "Failed to acquire next frame with status=%ld", status); + WLog_ERR(TAG, "Trying to reinitialize due to ACCESS LOST..."); + + if (gAcquiredDesktopImage) + { + gAcquiredDesktopImage->lpVtbl->Release(gAcquiredDesktopImage); + gAcquiredDesktopImage = NULL; + } + + if (gOutputDuplication) + { + gOutputDuplication->lpVtbl->Release(gOutputDuplication); + gOutputDuplication = NULL; + } + + wf_dxgi_getDuplication(wfi); + + return 1; + } + else + { + WLog_ERR(TAG, "Failed to acquire next frame with status=%ld", status); + status = gOutputDuplication->lpVtbl->ReleaseFrame(gOutputDuplication); + + if (FAILED(status)) + { + WLog_ERR(TAG, "Failed to release frame with status=%ld", status); + } + + return 1; + } + } + + status = DesktopResource->lpVtbl->QueryInterface(DesktopResource, &IID_ID3D11Texture2D, + (void**)&gAcquiredDesktopImage); + DesktopResource->lpVtbl->Release(DesktopResource); + DesktopResource = NULL; + + if (FAILED(status)) + { + return 1; + } + + wfi->framesWaiting = FrameInfo.AccumulatedFrames; + + if (FrameInfo.AccumulatedFrames == 0) + { + status = gOutputDuplication->lpVtbl->ReleaseFrame(gOutputDuplication); + + if (FAILED(status)) + { + WLog_ERR(TAG, "Failed to release frame with status=%ld", status); + } + } + + return 0; +} + +int wf_dxgi_getPixelData(wfInfo* wfi, BYTE** data, int* pitch, RECT* invalid) +{ + HRESULT status; + D3D11_BOX Box; + DXGI_MAPPED_RECT mappedRect; + D3D11_TEXTURE2D_DESC tDesc; + + tDesc.Width = (invalid->right - invalid->left); + tDesc.Height = (invalid->bottom - invalid->top); + tDesc.MipLevels = 1; + tDesc.ArraySize = 1; + tDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + tDesc.SampleDesc.Count = 1; + tDesc.SampleDesc.Quality = 0; + tDesc.Usage = D3D11_USAGE_STAGING; + tDesc.BindFlags = 0; + tDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; + tDesc.MiscFlags = 0; + + Box.top = invalid->top; + Box.left = invalid->left; + Box.right = invalid->right; + Box.bottom = invalid->bottom; + Box.front = 0; + Box.back = 1; + + status = gDevice->lpVtbl->CreateTexture2D(gDevice, &tDesc, NULL, &sStage); + + if (FAILED(status)) + { + WLog_ERR(TAG, "Failed to create staging surface"); + exit(1); + return 1; + } + + gContext->lpVtbl->CopySubresourceRegion(gContext, (ID3D11Resource*)sStage, 0, 0, 0, 0, + (ID3D11Resource*)gAcquiredDesktopImage, 0, &Box); + + status = sStage->lpVtbl->QueryInterface(sStage, &IID_IDXGISurface, (void**)&surf); + + if (FAILED(status)) + { + WLog_ERR(TAG, "Failed to QI staging surface"); + exit(1); + return 1; + } + + surf->lpVtbl->Map(surf, &mappedRect, DXGI_MAP_READ); + + if (FAILED(status)) + { + WLog_ERR(TAG, "Failed to map staging surface"); + exit(1); + return 1; + } + + *data = mappedRect.pBits; + *pitch = mappedRect.Pitch; + + return 0; +} + +int wf_dxgi_releasePixelData(wfInfo* wfi) +{ + HRESULT status; + + surf->lpVtbl->Unmap(surf); + surf->lpVtbl->Release(surf); + surf = NULL; + sStage->lpVtbl->Release(sStage); + sStage = NULL; + + status = gOutputDuplication->lpVtbl->ReleaseFrame(gOutputDuplication); + + if (FAILED(status)) + { + WLog_ERR(TAG, "Failed to release frame"); + return 1; + } + + wfi->framesWaiting = 0; + + return 0; +} + +int wf_dxgi_getInvalidRegion(RECT* invalid) +{ + HRESULT status; + UINT dirty; + UINT BufSize; + RECT* pRect; + BYTE* DirtyRects; + UINT DataBufferSize = 0; + BYTE* DataBuffer = NULL; + + if (FrameInfo.AccumulatedFrames == 0) + { + return 1; + } + + if (FrameInfo.TotalMetadataBufferSize) + { + + if (FrameInfo.TotalMetadataBufferSize > DataBufferSize) + { + if (DataBuffer) + { + free(DataBuffer); + DataBuffer = NULL; + } + + DataBuffer = (BYTE*)malloc(FrameInfo.TotalMetadataBufferSize); + + if (!DataBuffer) + { + DataBufferSize = 0; + WLog_ERR(TAG, "Failed to allocate memory for metadata"); + exit(1); + } + + DataBufferSize = FrameInfo.TotalMetadataBufferSize; + } + + BufSize = FrameInfo.TotalMetadataBufferSize; + + status = gOutputDuplication->lpVtbl->GetFrameMoveRects( + gOutputDuplication, BufSize, (DXGI_OUTDUPL_MOVE_RECT*)DataBuffer, &BufSize); + + if (FAILED(status)) + { + WLog_ERR(TAG, "Failed to get frame move rects"); + return 1; + } + + DirtyRects = DataBuffer + BufSize; + BufSize = FrameInfo.TotalMetadataBufferSize - BufSize; + + status = gOutputDuplication->lpVtbl->GetFrameDirtyRects(gOutputDuplication, BufSize, + (RECT*)DirtyRects, &BufSize); + + if (FAILED(status)) + { + WLog_ERR(TAG, "Failed to get frame dirty rects"); + return 1; + } + dirty = BufSize / sizeof(RECT); + + pRect = (RECT*)DirtyRects; + + for (UINT i = 0; i < dirty; ++i) + { + UnionRect(invalid, invalid, pRect); + ++pRect; + } + } + + return 0; +} + +#endif diff --git a/server/Windows/wf_dxgi.h b/server/Windows/wf_dxgi.h new file mode 100644 index 0000000..560aec0 --- /dev/null +++ b/server/Windows/wf_dxgi.h @@ -0,0 +1,41 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Windows Server + * + * Copyright 2012 Corey Clayton <can.of.tuna@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_SERVER_WIN_DXGI_H +#define FREERDP_SERVER_WIN_DXGI_H + +#include "wf_interface.h" + +int wf_dxgi_init(wfInfo* context); + +int wf_dxgi_createDevice(wfInfo* context); + +int wf_dxgi_getDuplication(wfInfo* context); + +int wf_dxgi_cleanup(wfInfo* context); + +int wf_dxgi_nextFrame(wfInfo* context, UINT timeout); + +int wf_dxgi_getPixelData(wfInfo* context, BYTE** data, int* pitch, RECT* invalid); + +int wf_dxgi_releasePixelData(wfInfo* context); + +int wf_dxgi_getInvalidRegion(RECT* invalid); + +#endif /* FREERDP_SERVER_WIN_DXGI_H */ diff --git a/server/Windows/wf_info.c b/server/Windows/wf_info.c new file mode 100644 index 0000000..7ec754b --- /dev/null +++ b/server/Windows/wf_info.c @@ -0,0 +1,402 @@ +/** + * FreeRDP: A Remote Desktop Protocol Client + * FreeRDP Windows Server + * + * Copyright 2012 Corey Clayton <can.of.tuna@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <freerdp/config.h> + +#include <stdlib.h> + +#include <freerdp/build-config.h> + +#include <winpr/tchar.h> +#include <winpr/windows.h> + +#include "wf_info.h" +#include "wf_update.h" +#include "wf_mirage.h" +#include "wf_dxgi.h" + +#include <freerdp/log.h> +#define TAG SERVER_TAG("windows") + +#define SERVER_KEY "Software\\" FREERDP_VENDOR_STRING "\\" FREERDP_PRODUCT_STRING "\\Server" + +static wfInfo* wfInfoInstance = NULL; +static int _IDcount = 0; + +BOOL wf_info_lock(wfInfo* wfi) +{ + DWORD dRes; + dRes = WaitForSingleObject(wfi->mutex, INFINITE); + + switch (dRes) + { + case WAIT_ABANDONED: + case WAIT_OBJECT_0: + return TRUE; + + case WAIT_TIMEOUT: + return FALSE; + + case WAIT_FAILED: + WLog_ERR(TAG, "wf_info_lock failed with 0x%08lX", GetLastError()); + return FALSE; + } + + return FALSE; +} + +BOOL wf_info_try_lock(wfInfo* wfi, DWORD dwMilliseconds) +{ + DWORD dRes; + dRes = WaitForSingleObject(wfi->mutex, dwMilliseconds); + + switch (dRes) + { + case WAIT_ABANDONED: + case WAIT_OBJECT_0: + return TRUE; + + case WAIT_TIMEOUT: + return FALSE; + + case WAIT_FAILED: + WLog_ERR(TAG, "wf_info_try_lock failed with 0x%08lX", GetLastError()); + return FALSE; + } + + return FALSE; +} + +BOOL wf_info_unlock(wfInfo* wfi) +{ + if (!ReleaseMutex(wfi->mutex)) + { + WLog_ERR(TAG, "wf_info_unlock failed with 0x%08lX", GetLastError()); + return FALSE; + } + + return TRUE; +} + +wfInfo* wf_info_init() +{ + wfInfo* wfi; + wfi = (wfInfo*)calloc(1, sizeof(wfInfo)); + + if (wfi != NULL) + { + HKEY hKey; + LONG status; + DWORD dwType; + DWORD dwSize; + DWORD dwValue; + wfi->mutex = CreateMutex(NULL, FALSE, NULL); + + if (wfi->mutex == NULL) + { + WLog_ERR(TAG, "CreateMutex error: %lu", GetLastError()); + free(wfi); + return NULL; + } + + wfi->updateSemaphore = CreateSemaphore(NULL, 0, 32, NULL); + + if (!wfi->updateSemaphore) + { + WLog_ERR(TAG, "CreateSemaphore error: %lu", GetLastError()); + CloseHandle(wfi->mutex); + free(wfi); + return NULL; + } + + wfi->updateThread = CreateThread(NULL, 0, wf_update_thread, wfi, CREATE_SUSPENDED, NULL); + + if (!wfi->updateThread) + { + WLog_ERR(TAG, "Failed to create update thread"); + CloseHandle(wfi->mutex); + CloseHandle(wfi->updateSemaphore); + free(wfi); + return NULL; + } + + wfi->peers = + (freerdp_peer**)calloc(FREERDP_SERVER_WIN_INFO_MAXPEERS, sizeof(freerdp_peer*)); + + if (!wfi->peers) + { + WLog_ERR(TAG, "Failed to allocate memory for peer"); + CloseHandle(wfi->mutex); + CloseHandle(wfi->updateSemaphore); + CloseHandle(wfi->updateThread); + free(wfi); + return NULL; + } + + // Set FPS + wfi->framesPerSecond = FREERDP_SERVER_WIN_INFO_DEFAULT_FPS; + status = + RegOpenKeyExA(HKEY_LOCAL_MACHINE, SERVER_KEY, 0, KEY_READ | KEY_WOW64_64KEY, &hKey); + + if (status == ERROR_SUCCESS) + { + if (RegQueryValueEx(hKey, _T("FramesPerSecond"), NULL, &dwType, (BYTE*)&dwValue, + &dwSize) == ERROR_SUCCESS) + wfi->framesPerSecond = dwValue; + } + + RegCloseKey(hKey); + // Set input toggle + wfi->input_disabled = FALSE; + status = + RegOpenKeyExA(HKEY_LOCAL_MACHINE, SERVER_KEY, 0, KEY_READ | KEY_WOW64_64KEY, &hKey); + + if (status == ERROR_SUCCESS) + { + if (RegQueryValueEx(hKey, _T("DisableInput"), NULL, &dwType, (BYTE*)&dwValue, + &dwSize) == ERROR_SUCCESS) + { + if (dwValue != 0) + wfi->input_disabled = TRUE; + } + } + + RegCloseKey(hKey); + } + + return wfi; +} + +wfInfo* wf_info_get_instance() +{ + if (wfInfoInstance == NULL) + wfInfoInstance = wf_info_init(); + + return wfInfoInstance; +} + +BOOL wf_info_peer_register(wfInfo* wfi, wfPeerContext* context) +{ + int peerId = 0; + + if (!wfi || !context) + return FALSE; + + if (!wf_info_lock(wfi)) + return FALSE; + + if (wfi->peerCount == FREERDP_SERVER_WIN_INFO_MAXPEERS) + goto fail_peer_count; + + context->info = wfi; + + if (!(context->updateEvent = CreateEvent(NULL, TRUE, FALSE, NULL))) + goto fail_update_event; + + // get the offset of the top left corner of selected screen + EnumDisplayMonitors(NULL, NULL, wf_info_monEnumCB, 0); + _IDcount = 0; +#ifdef WITH_DXGI_1_2 + + if (wfi->peerCount == 0) + if (wf_dxgi_init(wfi) != 0) + goto fail_driver_init; + +#else + + if (!wf_mirror_driver_activate(wfi)) + goto fail_driver_init; + +#endif + + // look through the array of peers until an empty slot + for (int i = 0; i < FREERDP_SERVER_WIN_INFO_MAXPEERS; ++i) + { + // empty index will be our peer id + if (wfi->peers[i] == NULL) + { + peerId = i; + break; + } + } + + wfi->peers[peerId] = ((rdpContext*)context)->peer; + wfi->peers[peerId]->pId = peerId; + wfi->peerCount++; + WLog_INFO(TAG, "Registering Peer: id=%d #=%d", peerId, wfi->peerCount); + wf_info_unlock(wfi); + wfreerdp_server_peer_callback_event(peerId, FREERDP_SERVER_WIN_SRV_CALLBACK_EVENT_CONNECT); + return TRUE; +fail_driver_init: + CloseHandle(context->updateEvent); + context->updateEvent = NULL; +fail_update_event: +fail_peer_count: + context->socketClose = TRUE; + wf_info_unlock(wfi); + return FALSE; +} + +void wf_info_peer_unregister(wfInfo* wfi, wfPeerContext* context) +{ + if (wf_info_lock(wfi)) + { + int peerId; + peerId = ((rdpContext*)context)->peer->pId; + wfi->peers[peerId] = NULL; + wfi->peerCount--; + CloseHandle(context->updateEvent); + WLog_INFO(TAG, "Unregistering Peer: id=%d, #=%d", peerId, wfi->peerCount); +#ifdef WITH_DXGI_1_2 + + if (wfi->peerCount == 0) + wf_dxgi_cleanup(wfi); + +#endif + wf_info_unlock(wfi); + wfreerdp_server_peer_callback_event(peerId, + FREERDP_SERVER_WIN_SRV_CALLBACK_EVENT_DISCONNECT); + } +} + +BOOL wf_info_have_updates(wfInfo* wfi) +{ +#ifdef WITH_DXGI_1_2 + + if (wfi->framesWaiting == 0) + return FALSE; + +#else + + if (wfi->nextUpdate == wfi->lastUpdate) + return FALSE; + +#endif + return TRUE; +} + +void wf_info_update_changes(wfInfo* wfi) +{ +#ifdef WITH_DXGI_1_2 + wf_dxgi_nextFrame(wfi, wfi->framesPerSecond * 1000); +#else + GETCHANGESBUF* buf; + buf = (GETCHANGESBUF*)wfi->changeBuffer; + wfi->nextUpdate = buf->buffer->counter; +#endif +} + +void wf_info_find_invalid_region(wfInfo* wfi) +{ +#ifdef WITH_DXGI_1_2 + wf_dxgi_getInvalidRegion(&wfi->invalid); +#else + GETCHANGESBUF* buf; + buf = (GETCHANGESBUF*)wfi->changeBuffer; + + for (ULONG i = wfi->lastUpdate; i != wfi->nextUpdate; i = (i + 1) % MAXCHANGES_BUF) + { + LPRECT lpR = &buf->buffer->pointrect[i].rect; + + // need to make sure we only get updates from the selected screen + if ((lpR->left >= wfi->servscreen_xoffset) && + (lpR->right <= (wfi->servscreen_xoffset + wfi->servscreen_width)) && + (lpR->top >= wfi->servscreen_yoffset) && + (lpR->bottom <= (wfi->servscreen_yoffset + wfi->servscreen_height))) + { + UnionRect(&wfi->invalid, &wfi->invalid, lpR); + } + else + { + continue; + } + } + +#endif + + if (wfi->invalid.left < 0) + wfi->invalid.left = 0; + + if (wfi->invalid.top < 0) + wfi->invalid.top = 0; + + if (wfi->invalid.right >= wfi->servscreen_width) + wfi->invalid.right = wfi->servscreen_width - 1; + + if (wfi->invalid.bottom >= wfi->servscreen_height) + wfi->invalid.bottom = wfi->servscreen_height - 1; + + // WLog_DBG(TAG, "invalid region: (%"PRId32", %"PRId32"), (%"PRId32", %"PRId32")", + // wfi->invalid.left, wfi->invalid.top, wfi->invalid.right, wfi->invalid.bottom); +} + +void wf_info_clear_invalid_region(wfInfo* wfi) +{ + wfi->lastUpdate = wfi->nextUpdate; + SetRectEmpty(&wfi->invalid); +} + +void wf_info_invalidate_full_screen(wfInfo* wfi) +{ + SetRect(&wfi->invalid, 0, 0, wfi->servscreen_width, wfi->servscreen_height); +} + +BOOL wf_info_have_invalid_region(wfInfo* wfi) +{ + return IsRectEmpty(&wfi->invalid); +} + +void wf_info_getScreenData(wfInfo* wfi, long* width, long* height, BYTE** pBits, int* pitch) +{ + *width = (wfi->invalid.right - wfi->invalid.left); + *height = (wfi->invalid.bottom - wfi->invalid.top); +#ifdef WITH_DXGI_1_2 + wf_dxgi_getPixelData(wfi, pBits, pitch, &wfi->invalid); +#else + { + long offset; + GETCHANGESBUF* changes; + changes = (GETCHANGESBUF*)wfi->changeBuffer; + *width += 1; + *height += 1; + offset = (4 * wfi->invalid.left) + (wfi->invalid.top * wfi->virtscreen_width * 4); + *pBits = ((BYTE*)(changes->Userbuffer)) + offset; + *pitch = wfi->virtscreen_width * 4; + } +#endif +} + +BOOL CALLBACK wf_info_monEnumCB(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, + LPARAM dwData) +{ + wfInfo* wfi; + wfi = wf_info_get_instance(); + + if (!wfi) + return FALSE; + + if (_IDcount == wfi->screenID) + { + wfi->servscreen_xoffset = lprcMonitor->left; + wfi->servscreen_yoffset = lprcMonitor->top; + } + + _IDcount++; + return TRUE; +} diff --git a/server/Windows/wf_info.h b/server/Windows/wf_info.h new file mode 100644 index 0000000..82b1781 --- /dev/null +++ b/server/Windows/wf_info.h @@ -0,0 +1,46 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Windows Server + * + * Copyright 2012 Corey Clayton <can.of.tuna@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_SERVER_WIN_INFO_H +#define FREERDP_SERVER_WIN_INFO_H + +#include "wf_interface.h" + +#define FREERDP_SERVER_WIN_INFO_DEFAULT_FPS 24 +#define FREERDP_SERVER_WIN_INFO_MAXPEERS 32 + +BOOL wf_info_lock(wfInfo* wfi); +BOOL wf_info_try_lock(wfInfo* wfi, DWORD dwMilliseconds); +BOOL wf_info_unlock(wfInfo* wfi); + +wfInfo* wf_info_get_instance(void); +BOOL wf_info_peer_register(wfInfo* wfi, wfPeerContext* context); +void wf_info_peer_unregister(wfInfo* wfi, wfPeerContext* context); + +BOOL wf_info_have_updates(wfInfo* wfi); +void wf_info_update_changes(wfInfo* wfi); +void wf_info_find_invalid_region(wfInfo* wfi); +void wf_info_clear_invalid_region(wfInfo* wfi); +void wf_info_invalidate_full_screen(wfInfo* wfi); +BOOL wf_info_have_invalid_region(wfInfo* wfi); +void wf_info_getScreenData(wfInfo* wfi, long* width, long* height, BYTE** pBits, int* pitch); +BOOL CALLBACK wf_info_monEnumCB(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, + LPARAM dwData); + +#endif /* FREERDP_SERVER_WIN_INFO_H */ diff --git a/server/Windows/wf_input.c b/server/Windows/wf_input.c new file mode 100644 index 0000000..a9fdd45 --- /dev/null +++ b/server/Windows/wf_input.c @@ -0,0 +1,223 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Windows Server + * + * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <freerdp/config.h> + +#include <winpr/windows.h> + +#include "wf_input.h" +#include "wf_info.h" + +BOOL wf_peer_keyboard_event(rdpInput* input, UINT16 flags, UINT8 code) +{ + INPUT keyboard_event; + WINPR_UNUSED(input); + keyboard_event.type = INPUT_KEYBOARD; + keyboard_event.ki.wVk = 0; + keyboard_event.ki.wScan = code; + keyboard_event.ki.dwFlags = KEYEVENTF_SCANCODE; + keyboard_event.ki.dwExtraInfo = 0; + keyboard_event.ki.time = 0; + + if (flags & KBD_FLAGS_RELEASE) + keyboard_event.ki.dwFlags |= KEYEVENTF_KEYUP; + + if (flags & KBD_FLAGS_EXTENDED) + keyboard_event.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY; + + SendInput(1, &keyboard_event, sizeof(INPUT)); + return TRUE; +} + +BOOL wf_peer_unicode_keyboard_event(rdpInput* input, UINT16 flags, UINT16 code) +{ + INPUT keyboard_event; + WINPR_UNUSED(input); + keyboard_event.type = INPUT_KEYBOARD; + keyboard_event.ki.wVk = 0; + keyboard_event.ki.wScan = code; + keyboard_event.ki.dwFlags = KEYEVENTF_UNICODE; + keyboard_event.ki.dwExtraInfo = 0; + keyboard_event.ki.time = 0; + + if (flags & KBD_FLAGS_RELEASE) + keyboard_event.ki.dwFlags |= KEYEVENTF_KEYUP; + + SendInput(1, &keyboard_event, sizeof(INPUT)); + return TRUE; +} + +BOOL wf_peer_mouse_event(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y) +{ + INPUT mouse_event = { 0 }; + float width, height; + WINPR_UNUSED(input); + + WINPR_ASSERT(input); + mouse_event.type = INPUT_MOUSE; + + if (flags & PTR_FLAGS_WHEEL) + { + mouse_event.mi.dwFlags = MOUSEEVENTF_WHEEL; + mouse_event.mi.mouseData = flags & WheelRotationMask; + + if (flags & PTR_FLAGS_WHEEL_NEGATIVE) + mouse_event.mi.mouseData *= -1; + + SendInput(1, &mouse_event, sizeof(INPUT)); + } + else + { + wfInfo* wfi; + wfi = wf_info_get_instance(); + + if (!wfi) + return FALSE; + + // width and height of primary screen (even in multimon setups + width = (float)GetSystemMetrics(SM_CXSCREEN); + height = (float)GetSystemMetrics(SM_CYSCREEN); + x += wfi->servscreen_xoffset; + y += wfi->servscreen_yoffset; + mouse_event.mi.dx = (LONG)((float)x * (65535.0f / width)); + mouse_event.mi.dy = (LONG)((float)y * (65535.0f / height)); + mouse_event.mi.dwFlags = MOUSEEVENTF_ABSOLUTE; + + if (flags & PTR_FLAGS_MOVE) + { + mouse_event.mi.dwFlags |= MOUSEEVENTF_MOVE; + SendInput(1, &mouse_event, sizeof(INPUT)); + } + + mouse_event.mi.dwFlags = MOUSEEVENTF_ABSOLUTE; + + if (flags & PTR_FLAGS_BUTTON1) + { + if (flags & PTR_FLAGS_DOWN) + mouse_event.mi.dwFlags |= MOUSEEVENTF_LEFTDOWN; + else + mouse_event.mi.dwFlags |= MOUSEEVENTF_LEFTUP; + + SendInput(1, &mouse_event, sizeof(INPUT)); + } + else if (flags & PTR_FLAGS_BUTTON2) + { + if (flags & PTR_FLAGS_DOWN) + mouse_event.mi.dwFlags |= MOUSEEVENTF_RIGHTDOWN; + else + mouse_event.mi.dwFlags |= MOUSEEVENTF_RIGHTUP; + + SendInput(1, &mouse_event, sizeof(INPUT)); + } + else if (flags & PTR_FLAGS_BUTTON3) + { + if (flags & PTR_FLAGS_DOWN) + mouse_event.mi.dwFlags |= MOUSEEVENTF_MIDDLEDOWN; + else + mouse_event.mi.dwFlags |= MOUSEEVENTF_MIDDLEUP; + + SendInput(1, &mouse_event, sizeof(INPUT)); + } + } + + return TRUE; +} + +BOOL wf_peer_extended_mouse_event(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y) +{ + if ((flags & PTR_XFLAGS_BUTTON1) || (flags & PTR_XFLAGS_BUTTON2)) + { + INPUT mouse_event = { 0 }; + mouse_event.type = INPUT_MOUSE; + + if (flags & PTR_FLAGS_MOVE) + { + float width, height; + wfInfo* wfi; + wfi = wf_info_get_instance(); + + if (!wfi) + return FALSE; + + // width and height of primary screen (even in multimon setups + width = (float)GetSystemMetrics(SM_CXSCREEN); + height = (float)GetSystemMetrics(SM_CYSCREEN); + x += wfi->servscreen_xoffset; + y += wfi->servscreen_yoffset; + mouse_event.mi.dx = (LONG)((float)x * (65535.0f / width)); + mouse_event.mi.dy = (LONG)((float)y * (65535.0f / height)); + mouse_event.mi.dwFlags = MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE; + SendInput(1, &mouse_event, sizeof(INPUT)); + } + + mouse_event.mi.dx = mouse_event.mi.dy = mouse_event.mi.dwFlags = 0; + + if (flags & PTR_XFLAGS_DOWN) + mouse_event.mi.dwFlags |= MOUSEEVENTF_XDOWN; + else + mouse_event.mi.dwFlags |= MOUSEEVENTF_XUP; + + if (flags & PTR_XFLAGS_BUTTON1) + mouse_event.mi.mouseData = XBUTTON1; + else if (flags & PTR_XFLAGS_BUTTON2) + mouse_event.mi.mouseData = XBUTTON2; + + SendInput(1, &mouse_event, sizeof(INPUT)); + } + else + { + wf_peer_mouse_event(input, flags, x, y); + } + + return TRUE; +} + +BOOL wf_peer_keyboard_event_dummy(rdpInput* input, UINT16 flags, UINT8 code) +{ + WINPR_UNUSED(input); + WINPR_UNUSED(flags); + WINPR_UNUSED(code); + return TRUE; +} + +BOOL wf_peer_unicode_keyboard_event_dummy(rdpInput* input, UINT16 flags, UINT16 code) +{ + WINPR_UNUSED(input); + WINPR_UNUSED(flags); + WINPR_UNUSED(code); + return TRUE; +} + +BOOL wf_peer_mouse_event_dummy(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y) +{ + WINPR_UNUSED(input); + WINPR_UNUSED(flags); + WINPR_UNUSED(x); + WINPR_UNUSED(y); + return TRUE; +} + +BOOL wf_peer_extended_mouse_event_dummy(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y) +{ + WINPR_UNUSED(input); + WINPR_UNUSED(flags); + WINPR_UNUSED(x); + WINPR_UNUSED(y); + return TRUE; +} diff --git a/server/Windows/wf_input.h b/server/Windows/wf_input.h new file mode 100644 index 0000000..8123652 --- /dev/null +++ b/server/Windows/wf_input.h @@ -0,0 +1,36 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Windows Server + * + * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_SERVER_WIN_INPUT_H +#define FREERDP_SERVER_WIN_INPUT_H + +#include "wf_interface.h" + +BOOL wf_peer_keyboard_event(rdpInput* input, UINT16 flags, UINT8 code); +BOOL wf_peer_unicode_keyboard_event(rdpInput* input, UINT16 flags, UINT16 code); +BOOL wf_peer_mouse_event(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y); +BOOL wf_peer_extended_mouse_event(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y); + +// dummy versions +BOOL wf_peer_keyboard_event_dummy(rdpInput* input, UINT16 flags, UINT8 code); +BOOL wf_peer_unicode_keyboard_event_dummy(rdpInput* input, UINT16 flags, UINT16 code); +BOOL wf_peer_mouse_event_dummy(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y); +BOOL wf_peer_extended_mouse_event_dummy(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y); + +#endif /* FREERDP_SERVER_WIN_INPUT_H */ diff --git a/server/Windows/wf_interface.c b/server/Windows/wf_interface.c new file mode 100644 index 0000000..37923bf --- /dev/null +++ b/server/Windows/wf_interface.c @@ -0,0 +1,341 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Windows Server + * + * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * Copyright 2012 Corey Clayton <can.of.tuna@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <freerdp/config.h> + +#include <winpr/tchar.h> +#include <winpr/windows.h> +#include <winpr/winsock.h> +#include <winpr/assert.h> + +#include <freerdp/freerdp.h> +#include <freerdp/listener.h> +#include <freerdp/constants.h> +#include <freerdp/channels/wtsvc.h> +#include <freerdp/channels/channels.h> +#include <freerdp/build-config.h> + +#include "wf_peer.h" +#include "wf_settings.h" +#include "wf_info.h" + +#include "wf_interface.h" + +#include <freerdp/log.h> +#define TAG SERVER_TAG("windows") + +#define SERVER_KEY "Software\\" FREERDP_VENDOR_STRING "\\" FREERDP_PRODUCT_STRING "\\Server" + +static cbCallback cbEvent = NULL; + +int get_screen_info(int id, _TCHAR* name, size_t length, int* width, int* height, int* bpp) +{ + DISPLAY_DEVICE dd = { 0 }; + + dd.cb = sizeof(DISPLAY_DEVICE); + + if (EnumDisplayDevices(NULL, id, &dd, 0) != 0) + { + HDC dc; + + if (name != NULL) + _stprintf_s(name, length, _T("%s (%s)"), dd.DeviceName, dd.DeviceString); + + dc = CreateDC(dd.DeviceName, NULL, NULL, NULL); + *width = GetDeviceCaps(dc, HORZRES); + *height = GetDeviceCaps(dc, VERTRES); + *bpp = GetDeviceCaps(dc, BITSPIXEL); + // ReleaseDC(NULL, dc); + DeleteDC(dc); + } + else + { + return 0; + } + + return 1; +} + +void set_screen_id(int id) +{ + wfInfo* wfi; + + wfi = wf_info_get_instance(); + if (!wfi) + return; + wfi->screenID = id; + + return; +} + +static DWORD WINAPI wf_server_main_loop(LPVOID lpParam) +{ + freerdp_listener* instance; + wfInfo* wfi; + + wfi = wf_info_get_instance(); + if (!wfi) + { + WLog_ERR(TAG, "Failed to get instance"); + return -1; + } + + wfi->force_all_disconnect = FALSE; + + instance = (freerdp_listener*)lpParam; + WINPR_ASSERT(instance); + WINPR_ASSERT(instance->GetEventHandles); + WINPR_ASSERT(instance->CheckFileDescriptor); + + while (wfi->force_all_disconnect == FALSE) + { + DWORD status; + HANDLE handles[MAXIMUM_WAIT_OBJECTS] = { 0 }; + DWORD count = instance->GetEventHandles(instance, handles, ARRAYSIZE(handles)); + + if (count == 0) + { + WLog_ERR(TAG, "Failed to get FreeRDP file descriptor"); + break; + } + + status = WaitForMultipleObjects(count, handles, FALSE, INFINITE); + if (status == WAIT_FAILED) + { + WLog_ERR(TAG, "WaitForMultipleObjects failed"); + break; + } + + if (instance->CheckFileDescriptor(instance) != TRUE) + { + WLog_ERR(TAG, "Failed to check FreeRDP file descriptor"); + break; + } + } + + WLog_INFO(TAG, "wf_server_main_loop terminating"); + instance->Close(instance); + + return 0; +} + +BOOL wfreerdp_server_start(wfServer* server) +{ + freerdp_listener* instance; + + server->instance = freerdp_listener_new(); + server->instance->PeerAccepted = wf_peer_accepted; + instance = server->instance; + + wf_settings_read_dword(HKEY_LOCAL_MACHINE, SERVER_KEY, _T("DefaultPort"), &server->port); + + if (!instance->Open(instance, NULL, (UINT16)server->port)) + return FALSE; + + if (!(server->thread = CreateThread(NULL, 0, wf_server_main_loop, (void*)instance, 0, NULL))) + return FALSE; + + return TRUE; +} + +BOOL wfreerdp_server_stop(wfServer* server) +{ + wfInfo* wfi; + + wfi = wf_info_get_instance(); + if (!wfi) + return FALSE; + WLog_INFO(TAG, "Stopping server"); + wfi->force_all_disconnect = TRUE; + server->instance->Close(server->instance); + return TRUE; +} + +wfServer* wfreerdp_server_new() +{ + WSADATA wsaData; + wfServer* server; + + if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) + return NULL; + + server = (wfServer*)calloc(1, sizeof(wfServer)); + + if (server) + { + server->port = 3389; + } + + WTSRegisterWtsApiFunctionTable(FreeRDP_InitWtsApi()); + + cbEvent = NULL; + + return server; +} + +void wfreerdp_server_free(wfServer* server) +{ + free(server); + + WSACleanup(); +} + +BOOL wfreerdp_server_is_running(wfServer* server) +{ + DWORD tStatus; + BOOL bRet; + + bRet = GetExitCodeThread(server->thread, &tStatus); + if (bRet == 0) + { + WLog_ERR(TAG, "Error in call to GetExitCodeThread"); + return FALSE; + } + + if (tStatus == STILL_ACTIVE) + return TRUE; + return FALSE; +} + +UINT32 wfreerdp_server_num_peers() +{ + wfInfo* wfi; + + wfi = wf_info_get_instance(); + if (!wfi) + return -1; + return wfi->peerCount; +} + +UINT32 wfreerdp_server_get_peer_hostname(int pId, wchar_t* dstStr) +{ + wfInfo* wfi; + freerdp_peer* peer; + + wfi = wf_info_get_instance(); + if (!wfi) + return 0; + peer = wfi->peers[pId]; + + if (peer) + { + UINT32 sLen; + + sLen = strnlen_s(peer->hostname, 50); + swprintf(dstStr, 50, L"%hs", peer->hostname); + return sLen; + } + else + { + WLog_WARN(TAG, "nonexistent peer id=%d", pId); + return 0; + } +} + +BOOL wfreerdp_server_peer_is_local(int pId) +{ + wfInfo* wfi; + freerdp_peer* peer; + + wfi = wf_info_get_instance(); + if (!wfi) + return FALSE; + peer = wfi->peers[pId]; + + if (peer) + { + return peer->local; + } + else + { + return FALSE; + } +} + +BOOL wfreerdp_server_peer_is_connected(int pId) +{ + wfInfo* wfi; + freerdp_peer* peer; + + wfi = wf_info_get_instance(); + if (!wfi) + return FALSE; + peer = wfi->peers[pId]; + + if (peer) + { + return peer->connected; + } + else + { + return FALSE; + } +} + +BOOL wfreerdp_server_peer_is_activated(int pId) +{ + wfInfo* wfi; + freerdp_peer* peer; + + wfi = wf_info_get_instance(); + if (!wfi) + return FALSE; + peer = wfi->peers[pId]; + + if (peer) + { + return peer->activated; + } + else + { + return FALSE; + } +} + +BOOL wfreerdp_server_peer_is_authenticated(int pId) +{ + wfInfo* wfi; + freerdp_peer* peer; + + wfi = wf_info_get_instance(); + if (!wfi) + return FALSE; + peer = wfi->peers[pId]; + + if (peer) + { + return peer->authenticated; + } + else + { + return FALSE; + } +} + +void wfreerdp_server_register_callback_event(cbCallback cb) +{ + cbEvent = cb; +} + +void wfreerdp_server_peer_callback_event(int pId, UINT32 eType) +{ + if (cbEvent) + cbEvent(pId, eType); +} diff --git a/server/Windows/wf_interface.h b/server/Windows/wf_interface.h new file mode 100644 index 0000000..5fcbad4 --- /dev/null +++ b/server/Windows/wf_interface.h @@ -0,0 +1,140 @@ +/** + * FreeRDP: A Remote Desktop Protocol Client + * FreeRDP Windows Server + * + * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * Copyright 2012 Corey Clayton <can.of.tuna@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_SERVER_WIN_INTERFACE_H +#define FREERDP_SERVER_WIN_INTERFACE_H + +#include <winpr/windows.h> + +#include <freerdp/api.h> +#include <freerdp/freerdp.h> +#include <freerdp/listener.h> + +#include <freerdp/freerdp.h> +#include <freerdp/codec/rfx.h> + +#include <freerdp/server/rdpsnd.h> + +#if _WIN32_WINNT >= 0x0602 +#define WITH_DXGI_1_2 1 +#endif + +#define FREERDP_SERVER_WIN_SRV_CALLBACK_EVENT_CONNECT 1 +#define FREERDP_SERVER_WIN_SRV_CALLBACK_EVENT_DISCONNECT 2 +#define FREERDP_SERVER_WIN_SRV_CALLBACK_EVENT_ACTIVATE 4 +#define FREERDP_SERVER_WIN_SRV_CALLBACK_EVENT_AUTH 8 + +typedef struct wf_info wfInfo; +typedef struct wf_peer_context wfPeerContext; + +struct wf_info +{ + wStream* s; + + // screen and monitor info + int screenID; + int virtscreen_width; + int virtscreen_height; + int servscreen_width; + int servscreen_height; + int servscreen_xoffset; + int servscreen_yoffset; + + int frame_idx; + int bitsPerPixel; + HDC driverDC; + int peerCount; + int activePeerCount; + void* changeBuffer; + int framesPerSecond; + LPTSTR deviceKey; + TCHAR deviceName[32]; + freerdp_peer** peers; + BOOL mirrorDriverActive; + UINT framesWaiting; + + HANDLE snd_mutex; + BOOL snd_stop; + AUDIO_FORMAT* agreed_format; + + RECT invalid; + HANDLE mutex; + BOOL updatePending; + HANDLE updateEvent; + HANDLE updateThread; + HANDLE updateSemaphore; + RFX_CONTEXT* rfx_context; + unsigned long lastUpdate; + unsigned long nextUpdate; + SURFACE_BITS_COMMAND cmd; + + BOOL input_disabled; + BOOL force_all_disconnect; +}; + +struct wf_peer_context +{ + rdpContext _p; + + wfInfo* info; + int frame_idx; + HANDLE updateEvent; + BOOL socketClose; + HANDLE socketEvent; + HANDLE socketThread; + HANDLE socketSemaphore; + + HANDLE vcm; + RdpsndServerContext* rdpsnd; +}; + +struct wf_server +{ + DWORD port; + HANDLE thread; + freerdp_listener* instance; +}; +typedef struct wf_server wfServer; + +typedef void(__stdcall* cbCallback)(int, UINT32); + +FREERDP_API int get_screen_info(int id, _TCHAR* name, size_t length, int* w, int* h, int* b); +FREERDP_API void set_screen_id(int id); + +FREERDP_API BOOL wfreerdp_server_start(wfServer* server); +FREERDP_API BOOL wfreerdp_server_stop(wfServer* server); + +FREERDP_API wfServer* wfreerdp_server_new(void); +FREERDP_API void wfreerdp_server_free(wfServer* server); + +FREERDP_API BOOL wfreerdp_server_is_running(wfServer* server); + +FREERDP_API UINT32 wfreerdp_server_num_peers(void); +FREERDP_API UINT32 wfreerdp_server_get_peer_hostname(int pId, wchar_t* dstStr); +FREERDP_API BOOL wfreerdp_server_peer_is_local(int pId); +FREERDP_API BOOL wfreerdp_server_peer_is_connected(int pId); +FREERDP_API BOOL wfreerdp_server_peer_is_activated(int pId); +FREERDP_API BOOL wfreerdp_server_peer_is_authenticated(int pId); + +FREERDP_API void wfreerdp_server_register_callback_event(cbCallback cb); + +void wfreerdp_server_peer_callback_event(int pId, UINT32 eType); + +#endif /* FREERDP_SERVER_WIN_INTERFACE_H */ diff --git a/server/Windows/wf_mirage.c b/server/Windows/wf_mirage.c new file mode 100644 index 0000000..524ff6e --- /dev/null +++ b/server/Windows/wf_mirage.c @@ -0,0 +1,361 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Windows Server + * + * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * Copyright 2012-2013 Corey Clayton <can.of.tuna@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <winpr/tchar.h> +#include <winpr/windows.h> + +#include "wf_mirage.h" + +#include <freerdp/log.h> +#define TAG SERVER_TAG("Windows.mirror") + +#define DEVICE_KEY_PREFIX _T("\\Registry\\Machine\\") +/* +This function will iterate over the loaded display devices until it finds +the mirror device we want to load. If found, it will then copy the registry +key corresponding to the device to the wfi and returns TRUE. Otherwise +the function returns FALSE. +*/ +BOOL wf_mirror_driver_find_display_device(wfInfo* wfi) +{ + BOOL result; + BOOL devFound; + DWORD deviceNumber; + DISPLAY_DEVICE deviceInfo; + devFound = FALSE; + deviceNumber = 0; + deviceInfo.cb = sizeof(deviceInfo); + + while (result = EnumDisplayDevices(NULL, deviceNumber, &deviceInfo, 0)) + { + if (_tcscmp(deviceInfo.DeviceString, _T("Mirage Driver")) == 0) + { + int deviceKeyLength; + int deviceKeyPrefixLength; + deviceKeyPrefixLength = _tcslen(DEVICE_KEY_PREFIX); + + if (_tcsnicmp(deviceInfo.DeviceKey, DEVICE_KEY_PREFIX, deviceKeyPrefixLength) == 0) + { + deviceKeyLength = _tcslen(deviceInfo.DeviceKey) - deviceKeyPrefixLength; + wfi->deviceKey = (LPTSTR)malloc((deviceKeyLength + 1) * sizeof(TCHAR)); + + if (!wfi->deviceKey) + return FALSE; + + _tcsncpy_s(wfi->deviceKey, deviceKeyLength + 1, + &deviceInfo.DeviceKey[deviceKeyPrefixLength], deviceKeyLength); + } + + _tcsncpy_s(wfi->deviceName, 32, deviceInfo.DeviceName, _tcslen(deviceInfo.DeviceName)); + return TRUE; + } + + deviceNumber++; + } + + return FALSE; +} + +/** + * This function will attempt to access the the windows registry using the device + * key stored in the current wfi. It will attempt to read the value of the + * "Attach.ToDesktop" subkey and will return TRUE if the value is already set to + * val. If unable to read the subkey, this function will return FALSE. If the + * subkey is not set to val it will then attempt to set it to val and return TRUE. If + * unsuccessful or an unexpected value is encountered, the function returns + * FALSE. + */ + +BOOL wf_mirror_driver_display_device_attach(wfInfo* wfi, DWORD mode) +{ + HKEY hKey; + LONG status; + DWORD dwType; + DWORD dwSize; + DWORD dwValue; + status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, wfi->deviceKey, 0, KEY_ALL_ACCESS | KEY_WOW64_64KEY, + &hKey); + + if (status != ERROR_SUCCESS) + { + WLog_DBG(TAG, "Error opening RegKey: status=0x%08lX", status); + + if (status == ERROR_ACCESS_DENIED) + WLog_DBG(TAG, "access denied. Do you have admin privleges?"); + + return FALSE; + } + + dwSize = sizeof(DWORD); + status = RegQueryValueEx(hKey, _T("Attach.ToDesktop"), NULL, &dwType, (BYTE*)&dwValue, &dwSize); + + if (status != ERROR_SUCCESS) + { + WLog_DBG(TAG, "Error querying RegKey: status=0x%08lX", status); + + if (status == ERROR_ACCESS_DENIED) + WLog_DBG(TAG, "access denied. Do you have admin privleges?"); + + return FALSE; + } + + if (dwValue ^ mode) // only if we want to change modes + { + dwValue = mode; + dwSize = sizeof(DWORD); + status = RegSetValueEx(hKey, _T("Attach.ToDesktop"), 0, REG_DWORD, (BYTE*)&dwValue, dwSize); + + if (status != ERROR_SUCCESS) + { + WLog_DBG(TAG, "Error writing registry key: %ld", status); + + if (status == ERROR_ACCESS_DENIED) + WLog_DBG(TAG, "access denied. Do you have admin privleges?"); + + WLog_DBG(TAG, ""); + return FALSE; + } + } + + return TRUE; +} + +void wf_mirror_driver_print_display_change_status(LONG status) +{ + TCHAR disp_change[64]; + + switch (status) + { + case DISP_CHANGE_SUCCESSFUL: + _tcscpy(disp_change, _T("DISP_CHANGE_SUCCESSFUL")); + break; + + case DISP_CHANGE_BADDUALVIEW: + _tcscpy(disp_change, _T("DISP_CHANGE_BADDUALVIEW")); + break; + + case DISP_CHANGE_BADFLAGS: + _tcscpy(disp_change, _T("DISP_CHANGE_BADFLAGS")); + break; + + case DISP_CHANGE_BADMODE: + _tcscpy(disp_change, _T("DISP_CHANGE_BADMODE")); + break; + + case DISP_CHANGE_BADPARAM: + _tcscpy(disp_change, _T("DISP_CHANGE_BADPARAM")); + break; + + case DISP_CHANGE_FAILED: + _tcscpy(disp_change, _T("DISP_CHANGE_FAILED")); + break; + + case DISP_CHANGE_NOTUPDATED: + _tcscpy(disp_change, _T("DISP_CHANGE_NOTUPDATED")); + break; + + case DISP_CHANGE_RESTART: + _tcscpy(disp_change, _T("DISP_CHANGE_RESTART")); + break; + + default: + _tcscpy(disp_change, _T("DISP_CHANGE_UNKNOWN")); + break; + } + + if (status != DISP_CHANGE_SUCCESSFUL) + WLog_ERR(TAG, "ChangeDisplaySettingsEx() failed with %s (%ld)", disp_change, status); + else + WLog_INFO(TAG, "ChangeDisplaySettingsEx() succeeded with %s (%ld)", disp_change, status); +} + +/** + * This function will attempt to apply the currently configured display settings + * in the registry to the display driver. It will return TRUE if successful + * otherwise it returns FALSE. + * If mode is MIRROR_UNLOAD then the the driver will be asked to remove itself. + */ + +BOOL wf_mirror_driver_update(wfInfo* wfi, int mode) +{ + BOOL status; + DWORD* extHdr; + WORD drvExtraSaved; + DEVMODE* deviceMode; + LONG disp_change_status; + DWORD dmf_devmodewext_magic_sig = 0xDF20C0DE; + + if ((mode != MIRROR_LOAD) && (mode != MIRROR_UNLOAD)) + { + WLog_DBG(TAG, "Invalid mirror mode!"); + return FALSE; + } + + deviceMode = (DEVMODE*)malloc(sizeof(DEVMODE) + EXT_DEVMODE_SIZE_MAX); + + if (!deviceMode) + return FALSE; + + deviceMode->dmDriverExtra = 2 * sizeof(DWORD); + extHdr = (DWORD*)((BYTE*)&deviceMode + sizeof(DEVMODE)); + extHdr[0] = dmf_devmodewext_magic_sig; + extHdr[1] = 0; + drvExtraSaved = deviceMode->dmDriverExtra; + memset(deviceMode, 0, sizeof(DEVMODE) + EXT_DEVMODE_SIZE_MAX); + deviceMode->dmSize = sizeof(DEVMODE); + deviceMode->dmDriverExtra = drvExtraSaved; + + if (mode == MIRROR_LOAD) + { + wfi->virtscreen_width = GetSystemMetrics(SM_CXVIRTUALSCREEN); + wfi->virtscreen_height = GetSystemMetrics(SM_CYVIRTUALSCREEN); + deviceMode->dmPelsWidth = wfi->virtscreen_width; + deviceMode->dmPelsHeight = wfi->virtscreen_height; + deviceMode->dmBitsPerPel = wfi->bitsPerPixel; + deviceMode->dmPosition.x = wfi->servscreen_xoffset; + deviceMode->dmPosition.y = wfi->servscreen_yoffset; + } + + deviceMode->dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT | DM_POSITION; + _tcsncpy_s(deviceMode->dmDeviceName, 32, wfi->deviceName, _tcslen(wfi->deviceName)); + disp_change_status = + ChangeDisplaySettingsEx(wfi->deviceName, deviceMode, NULL, CDS_UPDATEREGISTRY, NULL); + status = (disp_change_status == DISP_CHANGE_SUCCESSFUL) ? TRUE : FALSE; + + if (!status) + wf_mirror_driver_print_display_change_status(disp_change_status); + + return status; +} + +BOOL wf_mirror_driver_map_memory(wfInfo* wfi) +{ + int status; + wfi->driverDC = CreateDC(wfi->deviceName, NULL, NULL, NULL); + + if (wfi->driverDC == NULL) + { + WLog_ERR(TAG, "Could not create device driver context!"); + { + LPVOID lpMsgBuf; + DWORD dw = GetLastError(); + FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&lpMsgBuf, 0, + NULL); + // Display the error message and exit the process + WLog_ERR(TAG, "CreateDC failed on device [%s] with error %lu: %s", wfi->deviceName, dw, + lpMsgBuf); + LocalFree(lpMsgBuf); + } + return FALSE; + } + + wfi->changeBuffer = calloc(1, sizeof(GETCHANGESBUF)); + + if (!wfi->changeBuffer) + return FALSE; + + status = ExtEscape(wfi->driverDC, dmf_esc_usm_pipe_map, 0, 0, sizeof(GETCHANGESBUF), + (LPSTR)wfi->changeBuffer); + + if (status <= 0) + { + WLog_ERR(TAG, "Failed to map shared memory from the driver! code %d", status); + return FALSE; + } + + return TRUE; +} + +/* Unmap the shared memory and release the DC */ + +BOOL wf_mirror_driver_cleanup(wfInfo* wfi) +{ + int status; + status = ExtEscape(wfi->driverDC, dmf_esc_usm_pipe_unmap, sizeof(GETCHANGESBUF), + (LPSTR)wfi->changeBuffer, 0, 0); + + if (status <= 0) + { + WLog_ERR(TAG, "Failed to unmap shared memory from the driver! code %d", status); + } + + if (wfi->driverDC != NULL) + { + status = DeleteDC(wfi->driverDC); + + if (status == 0) + { + WLog_ERR(TAG, "Failed to release DC!"); + } + } + + free(wfi->changeBuffer); + return TRUE; +} + +BOOL wf_mirror_driver_activate(wfInfo* wfi) +{ + if (!wfi->mirrorDriverActive) + { + WLog_DBG(TAG, "Activating Mirror Driver"); + + if (wf_mirror_driver_find_display_device(wfi) == FALSE) + { + WLog_DBG(TAG, "Could not find dfmirage mirror driver! Is it installed?"); + return FALSE; + } + + if (wf_mirror_driver_display_device_attach(wfi, 1) == FALSE) + { + WLog_DBG(TAG, "Could not attach display device!"); + return FALSE; + } + + if (wf_mirror_driver_update(wfi, MIRROR_LOAD) == FALSE) + { + WLog_DBG(TAG, "could not update system with new display settings!"); + return FALSE; + } + + if (wf_mirror_driver_map_memory(wfi) == FALSE) + { + WLog_DBG(TAG, "Unable to map memory for mirror driver!"); + return FALSE; + } + + wfi->mirrorDriverActive = TRUE; + } + + return TRUE; +} + +void wf_mirror_driver_deactivate(wfInfo* wfi) +{ + if (wfi->mirrorDriverActive) + { + WLog_DBG(TAG, "Deactivating Mirror Driver"); + wf_mirror_driver_cleanup(wfi); + wf_mirror_driver_display_device_attach(wfi, 0); + wf_mirror_driver_update(wfi, MIRROR_UNLOAD); + wfi->mirrorDriverActive = FALSE; + } +} diff --git a/server/Windows/wf_mirage.h b/server/Windows/wf_mirage.h new file mode 100644 index 0000000..a03f0b9 --- /dev/null +++ b/server/Windows/wf_mirage.h @@ -0,0 +1,219 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Windows Server + * + * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * Copyright 2012-2013 Corey Clayton <can.of.tuna@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_SERVER_WIN_MIRAGE_H +#define FREERDP_SERVER_WIN_MIRAGE_H + +#include "wf_interface.h" + +enum +{ + MIRROR_LOAD = 0, + MIRROR_UNLOAD = 1 +}; + +enum +{ + DMF_ESCAPE_BASE_1_VB = 1030, + DMF_ESCAPE_BASE_2_VB = 1026, + DMF_ESCAPE_BASE_3_VB = 24 +}; + +#ifdef _WIN64 + +#define CLIENT_64BIT 0x8000 + +enum +{ + DMF_ESCAPE_BASE_1 = CLIENT_64BIT | DMF_ESCAPE_BASE_1_VB, + DMF_ESCAPE_BASE_2 = CLIENT_64BIT | DMF_ESCAPE_BASE_2_VB, + DMF_ESCAPE_BASE_3 = CLIENT_64BIT | DMF_ESCAPE_BASE_3_VB, +}; + +#else + +enum +{ + DMF_ESCAPE_BASE_1 = DMF_ESCAPE_BASE_1_VB, + DMF_ESCAPE_BASE_2 = DMF_ESCAPE_BASE_2_VB, + DMF_ESCAPE_BASE_3 = DMF_ESCAPE_BASE_3_VB, +}; + +#endif + +typedef enum +{ + dmf_esc_qry_ver_info = DMF_ESCAPE_BASE_2 + 0, + dmf_esc_usm_pipe_map = DMF_ESCAPE_BASE_1 + 0, + dmf_esc_usm_pipe_unmap = DMF_ESCAPE_BASE_1 + 1, + dmf_esc_test = DMF_ESCAPE_BASE_1 + 20, + dmf_esc_usm_pipe_mapping_test = DMF_ESCAPE_BASE_1 + 21, + dmf_esc_pointer_shape_get = DMF_ESCAPE_BASE_3, + +} dmf_escape; + +#define CLIP_LIMIT 50 +#define MAXCHANGES_BUF 20000 + +typedef enum +{ + dmf_dfo_IGNORE = 0, + dmf_dfo_FROM_SCREEN = 1, + dmf_dfo_FROM_DIB = 2, + dmf_dfo_TO_SCREEN = 3, + dmf_dfo_SCREEN_SCREEN = 11, + dmf_dfo_BLIT = 12, + dmf_dfo_SOLIDFILL = 13, + dmf_dfo_BLEND = 14, + dmf_dfo_TRANS = 15, + dmf_dfo_PLG = 17, + dmf_dfo_TEXTOUT = 18, + dmf_dfo_Ptr_Shape = 19, + dmf_dfo_Ptr_Engage = 48, + dmf_dfo_Ptr_Avert = 49, + dmf_dfn_assert_on = 64, + dmf_dfn_assert_off = 65, +} dmf_UpdEvent; + +#define NOCACHE 1 +#define OLDCACHE 2 +#define NEWCACHE 3 + +typedef struct +{ + ULONG type; + RECT rect; +#ifndef DFMIRAGE_LEAN + RECT origrect; + POINT point; + ULONG color; + ULONG refcolor; +#endif +} CHANGES_RECORD; + +typedef CHANGES_RECORD* PCHANGES_RECORD; + +typedef struct +{ + ULONG counter; + CHANGES_RECORD pointrect[MAXCHANGES_BUF]; +} CHANGES_BUF; + +#define EXT_DEVMODE_SIZE_MAX 3072 +#define DMF_PIPE_SEC_SIZE_DEFAULT ALIGN64K(sizeof(CHANGES_BUF)) + +typedef struct +{ + CHANGES_BUF* buffer; + PVOID Userbuffer; +} GETCHANGESBUF; + +#define dmf_sprb_ERRORMASK 0x07FF +#define dmf_sprb_STRICTSESSION_AFF 0x1FFF + +typedef enum +{ + dmf_sprb_internal_error = 0x0001, + dmf_sprb_miniport_gen_error = 0x0004, + dmf_sprb_memory_alloc_failed = 0x0008, + dmf_sprb_pipe_buff_overflow = 0x0010, + dmf_sprb_pipe_buff_insufficient = 0x0020, + dmf_sprb_pipe_not_ready = 0x0040, + dmf_sprb_gdi_err = 0x0100, + dmf_sprb_owner_died = 0x0400, + dmf_sprb_tgtwnd_gone = 0x0800, + dmf_sprb_pdev_detached = 0x2000, +} dmf_session_prob_status; + +#define DMF_ESC_RET_FAILF 0x80000000 +#define DMF_ESC_RET_SSTMASK 0x0000FFFF +#define DMF_ESC_RET_IMMMASK 0x7FFF0000 + +typedef enum +{ + dmf_escret_generic_ok = 0x00010000, + dmf_escret_bad_state = 0x00100000, + dmf_escret_access_denied = 0x00200000, + dmf_escret_bad_buffer_size = 0x00400000, + dmf_escret_internal_err = 0x00800000, + dmf_escret_out_of_memory = 0x02000000, + dmf_escret_already_connected = 0x04000000, + dmf_escret_oh_boy_too_late = 0x08000000, + dmf_escret_bad_window = 0x10000000, + dmf_escret_drv_ver_higher = 0x20000000, + dmf_escret_drv_ver_lower = 0x40000000, +} dmf_esc_retcode; + +typedef struct +{ + ULONG cbSize; + ULONG app_actual_version; + ULONG display_minreq_version; + ULONG connect_options; +} Esc_dmf_Qvi_IN; + +enum +{ + esc_qvi_prod_name_max = 16, +}; + +#define ESC_QVI_PROD_MIRAGE "MIRAGE" +#define ESC_QVI_PROD_QUASAR "QUASAR" + +typedef struct +{ + ULONG cbSize; + ULONG display_actual_version; + ULONG miniport_actual_version; + ULONG app_minreq_version; + ULONG display_buildno; + ULONG miniport_buildno; + char prod_name[esc_qvi_prod_name_max]; +} Esc_dmf_Qvi_OUT; + +typedef struct +{ + ULONG cbSize; + char* pDstBmBuf; + ULONG nDstBmBufSize; +} Esc_dmf_pointer_shape_get_IN; + +typedef struct +{ + ULONG cbSize; + POINTL BmSize; + char* pMaskBm; + ULONG nMaskBmSize; + char* pColorBm; + ULONG nColorBmSize; + char* pColorBmPal; + ULONG nColorBmPalEntries; +} Esc_dmf_pointer_shape_get_OUT; + +BOOL wf_mirror_driver_find_display_device(wfInfo* wfi); +BOOL wf_mirror_driver_display_device_attach(wfInfo* wfi, DWORD mode); +BOOL wf_mirror_driver_update(wfInfo* wfi, int mode); +BOOL wf_mirror_driver_map_memory(wfInfo* wfi); +BOOL wf_mirror_driver_cleanup(wfInfo* wfi); + +BOOL wf_mirror_driver_activate(wfInfo* wfi); +void wf_mirror_driver_deactivate(wfInfo* wfi); + +#endif /* FREERDP_SERVER_WIN_MIRAGE_H */ diff --git a/server/Windows/wf_peer.c b/server/Windows/wf_peer.c new file mode 100644 index 0000000..4342a3b --- /dev/null +++ b/server/Windows/wf_peer.c @@ -0,0 +1,414 @@ +/** + * FreeRDP: A Remote Desktop Protocol Client + * FreeRDP Windows Server + * + * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * Copyright 2012 Corey Clayton <can.of.tuna@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <freerdp/config.h> + +#include <winpr/assert.h> +#include <winpr/tchar.h> +#include <winpr/stream.h> +#include <winpr/windows.h> + +#include <freerdp/listener.h> +#include <freerdp/codec/rfx.h> +#include <freerdp/build-config.h> +#include <freerdp/crypto/certificate.h> + +#include "wf_info.h" +#include "wf_input.h" +#include "wf_mirage.h" +#include "wf_update.h" +#include "wf_settings.h" +#include "wf_rdpsnd.h" + +#include "wf_peer.h" +#include <freerdp/peer.h> + +#include <freerdp/log.h> +#define TAG SERVER_TAG("windows") + +#define SERVER_KEY "Software\\" FREERDP_VENDOR_STRING "\\" FREERDP_PRODUCT_STRING + +static DWORD WINAPI wf_peer_main_loop(LPVOID lpParam); + +static BOOL wf_peer_context_new(freerdp_peer* client, rdpContext* ctx) +{ + wfPeerContext* context = (wfPeerContext*)ctx; + WINPR_ASSERT(context); + + if (!(context->info = wf_info_get_instance())) + return FALSE; + + context->vcm = WTSOpenServerA((LPSTR)client->context); + + if (!context->vcm || context->vcm == INVALID_HANDLE_VALUE) + return FALSE; + + if (!wf_info_peer_register(context->info, context)) + { + WTSCloseServer(context->vcm); + context->vcm = NULL; + return FALSE; + } + + return TRUE; +} + +static void wf_peer_context_free(freerdp_peer* client, rdpContext* ctx) +{ + wfPeerContext* context = (wfPeerContext*)ctx; + WINPR_ASSERT(context); + + wf_info_peer_unregister(context->info, context); + + if (context->rdpsnd) + { + wf_rdpsnd_lock(); + context->info->snd_stop = TRUE; + rdpsnd_server_context_free(context->rdpsnd); + wf_rdpsnd_unlock(); + } + + WTSCloseServer(context->vcm); +} + +static BOOL wf_peer_init(freerdp_peer* client) +{ + client->ContextSize = sizeof(wfPeerContext); + client->ContextNew = wf_peer_context_new; + client->ContextFree = wf_peer_context_free; + return freerdp_peer_context_new(client); +} + +static BOOL wf_peer_post_connect(freerdp_peer* client) +{ + wfInfo* wfi; + rdpSettings* settings; + wfPeerContext* context; + + WINPR_ASSERT(client); + + context = (wfPeerContext*)client->context; + WINPR_ASSERT(context); + + wfi = context->info; + WINPR_ASSERT(wfi); + + settings = client->context->settings; + WINPR_ASSERT(settings); + + if ((get_screen_info(wfi->screenID, NULL, 0, &wfi->servscreen_width, &wfi->servscreen_height, + &wfi->bitsPerPixel) == 0) || + (wfi->servscreen_width == 0) || (wfi->servscreen_height == 0) || (wfi->bitsPerPixel == 0)) + { + WLog_ERR(TAG, "postconnect: error getting screen info for screen %d", wfi->screenID); + WLog_ERR(TAG, "\t%dx%dx%d", wfi->servscreen_height, wfi->servscreen_width, + wfi->bitsPerPixel); + return FALSE; + } + + if ((freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth) != wfi->servscreen_width) || + (freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight) != wfi->servscreen_height)) + { + /* + WLog_DBG(TAG, "Client requested resolution %"PRIu32"x%"PRIu32", but will resize to %dx%d", + freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth), + freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight), wfi->servscreen_width, + wfi->servscreen_height); + */ + if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, wfi->servscreen_width) || + !freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, wfi->servscreen_height) || + !freerdp_settings_set_uint32(settings, FreeRDP_ColorDepth, wfi->bitsPerPixel)) + return FALSE; + + WINPR_ASSERT(client->context->update); + WINPR_ASSERT(client->context->update->DesktopResize); + client->context->update->DesktopResize(client->context); + } + + if (WTSVirtualChannelManagerIsChannelJoined(context->vcm, "rdpsnd")) + { + wf_peer_rdpsnd_init(context); /* Audio Output */ + } + + return TRUE; +} + +static BOOL wf_peer_activate(freerdp_peer* client) +{ + wfInfo* wfi; + wfPeerContext* context = (wfPeerContext*)client->context; + wfi = context->info; + client->activated = TRUE; + wf_update_peer_activate(wfi, context); + wfreerdp_server_peer_callback_event(((rdpContext*)context)->peer->pId, + FREERDP_SERVER_WIN_SRV_CALLBACK_EVENT_ACTIVATE); + return TRUE; +} + +static BOOL wf_peer_logon(freerdp_peer* client, const SEC_WINNT_AUTH_IDENTITY* identity, + BOOL automatic) +{ + wfreerdp_server_peer_callback_event(((rdpContext*)client->context)->peer->pId, + FREERDP_SERVER_WIN_SRV_CALLBACK_EVENT_AUTH); + return TRUE; +} + +static BOOL wf_peer_synchronize_event(rdpInput* input, UINT32 flags) +{ + return TRUE; +} + +BOOL wf_peer_accepted(freerdp_listener* instance, freerdp_peer* client) +{ + HANDLE hThread; + + if (!(hThread = CreateThread(NULL, 0, wf_peer_main_loop, client, 0, NULL))) + return FALSE; + + CloseHandle(hThread); + return TRUE; +} + +static DWORD WINAPI wf_peer_socket_listener(LPVOID lpParam) +{ + wfPeerContext* context; + freerdp_peer* client = (freerdp_peer*)lpParam; + + WINPR_ASSERT(client); + WINPR_ASSERT(client->GetEventHandles); + WINPR_ASSERT(client->CheckFileDescriptor); + + context = (wfPeerContext*)client->context; + WINPR_ASSERT(context); + + while (1) + { + DWORD status; + HANDLE handles[MAXIMUM_WAIT_OBJECTS] = { 0 }; + DWORD count = client->GetEventHandles(client, handles, ARRAYSIZE(handles)); + + if (count == 0) + { + WLog_ERR(TAG, "Failed to get FreeRDP file descriptor"); + break; + } + + status = WaitForMultipleObjects(count, handles, FALSE, INFINITE); + if (status == WAIT_FAILED) + { + WLog_ERR(TAG, "WaitForMultipleObjects failed"); + break; + } + + SetEvent(context->socketEvent); + WaitForSingleObject(context->socketSemaphore, INFINITE); + + if (context->socketClose) + break; + } + + return 0; +} + +static BOOL wf_peer_read_settings(freerdp_peer* client) +{ + rdpSettings* settings; + + WINPR_ASSERT(client); + WINPR_ASSERT(client->context); + + settings = client->context->settings; + WINPR_ASSERT(settings); + + char* CertificateFile = NULL; + if (!wf_settings_read_string_ascii(HKEY_LOCAL_MACHINE, SERVER_KEY, _T("CertificateFile"), + &(CertificateFile))) + CertificateFile = _strdup("server.crt"); + + rdpCertificate* cert = freerdp_certificate_new_from_file(CertificateFile); + free(CertificateFile); + if (!cert) + return FALSE; + + if (!freerdp_settings_set_pointer_len(settings, FreeRDP_RdpServerCertificate, cert, 1)) + return FALSE; + + char* PrivateKeyFile = NULL; + if (!wf_settings_read_string_ascii(HKEY_LOCAL_MACHINE, SERVER_KEY, _T("PrivateKeyFile"), + &(PrivateKeyFile))) + PrivateKeyFile = _strdup("server.key"); + + rdpPrivateKey* key = freerdp_key_new_from_file(PrivateKeyFile); + free(PrivateKeyFile); + + if (!key) + return FALSE; + + if (!freerdp_settings_set_pointer_len(settings, FreeRDP_RdpServerRsaKey, key, 1)) + return FALSE; + + return TRUE; +} + +DWORD WINAPI wf_peer_main_loop(LPVOID lpParam) +{ + wfInfo* wfi; + DWORD nCount; + DWORD status; + HANDLE handles[32]; + rdpSettings* settings; + wfPeerContext* context; + freerdp_peer* client = (freerdp_peer*)lpParam; + + if (!wf_peer_init(client)) + goto fail_peer_init; + + WINPR_ASSERT(client->context); + + settings = client->context->settings; + WINPR_ASSERT(settings); + + if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteFxCodec, TRUE)) + goto fail_peer_init; + if (!freerdp_settings_set_uint32(settings, FreeRDP_ColorDepth, 32)) + goto fail_peer_init; + if (!freerdp_settings_set_bool(settings, FreeRDP_NSCodec, FALSE)) + goto fail_peer_init; + if (!freerdp_settings_set_bool(settings, FreeRDP_JpegCodec, FALSE)) + goto fail_peer_init; + + if (!wf_peer_read_settings(client)) + goto fail_peer_init; + + client->PostConnect = wf_peer_post_connect; + client->Activate = wf_peer_activate; + client->Logon = wf_peer_logon; + + WINPR_ASSERT(client->context->input); + client->context->input->SynchronizeEvent = wf_peer_synchronize_event; + client->context->input->KeyboardEvent = wf_peer_keyboard_event; + client->context->input->UnicodeKeyboardEvent = wf_peer_unicode_keyboard_event; + client->context->input->MouseEvent = wf_peer_mouse_event; + client->context->input->ExtendedMouseEvent = wf_peer_extended_mouse_event; + + WINPR_ASSERT(client->Initialize); + if (!client->Initialize(client)) + goto fail_client_initialize; + + context = (wfPeerContext*)client->context; + + if (context->socketClose) + goto fail_socked_closed; + + wfi = context->info; + + if (wfi->input_disabled) + { + WLog_INFO(TAG, "client input is disabled"); + client->context->input->KeyboardEvent = wf_peer_keyboard_event_dummy; + client->context->input->UnicodeKeyboardEvent = wf_peer_unicode_keyboard_event_dummy; + client->context->input->MouseEvent = wf_peer_mouse_event_dummy; + client->context->input->ExtendedMouseEvent = wf_peer_extended_mouse_event_dummy; + } + + if (!(context->socketEvent = CreateEvent(NULL, TRUE, FALSE, NULL))) + goto fail_socket_event; + + if (!(context->socketSemaphore = CreateSemaphore(NULL, 0, 1, NULL))) + goto fail_socket_semaphore; + + if (!(context->socketThread = CreateThread(NULL, 0, wf_peer_socket_listener, client, 0, NULL))) + goto fail_socket_thread; + + WLog_INFO(TAG, "We've got a client %s", client->local ? "(local)" : client->hostname); + nCount = 0; + handles[nCount++] = context->updateEvent; + handles[nCount++] = context->socketEvent; + + while (1) + { + status = WaitForMultipleObjects(nCount, handles, FALSE, INFINITE); + + if ((status == WAIT_FAILED) || (status == WAIT_TIMEOUT)) + { + WLog_ERR(TAG, "WaitForMultipleObjects failed"); + break; + } + + if (WaitForSingleObject(context->updateEvent, 0) == 0) + { + if (client->activated) + wf_update_peer_send(wfi, context); + + ResetEvent(context->updateEvent); + ReleaseSemaphore(wfi->updateSemaphore, 1, NULL); + } + + if (WaitForSingleObject(context->socketEvent, 0) == 0) + { + if (client->CheckFileDescriptor(client) != TRUE) + { + WLog_ERR(TAG, "Failed to check peer file descriptor"); + context->socketClose = TRUE; + } + + ResetEvent(context->socketEvent); + ReleaseSemaphore(context->socketSemaphore, 1, NULL); + + if (context->socketClose) + break; + } + + // force disconnect + if (wfi->force_all_disconnect == TRUE) + { + WLog_INFO(TAG, "Forcing Disconnect -> "); + break; + } + + /* FIXME: we should wait on this, instead of calling it every time */ + if (WTSVirtualChannelManagerCheckFileDescriptor(context->vcm) != TRUE) + break; + } + + WLog_INFO(TAG, "Client %s disconnected.", client->local ? "(local)" : client->hostname); + + if (WaitForSingleObject(context->updateEvent, 0) == 0) + { + ResetEvent(context->updateEvent); + ReleaseSemaphore(wfi->updateSemaphore, 1, NULL); + } + + wf_update_peer_deactivate(wfi, context); + client->Disconnect(client); +fail_socket_thread: + CloseHandle(context->socketSemaphore); + context->socketSemaphore = NULL; +fail_socket_semaphore: + CloseHandle(context->socketEvent); + context->socketEvent = NULL; +fail_socket_event: +fail_socked_closed: +fail_client_initialize: + freerdp_peer_context_free(client); +fail_peer_init: + freerdp_peer_free(client); + return 0; +} diff --git a/server/Windows/wf_peer.h b/server/Windows/wf_peer.h new file mode 100644 index 0000000..19d823c --- /dev/null +++ b/server/Windows/wf_peer.h @@ -0,0 +1,29 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Windows Server + * + * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_SERVER_WIN_PEER_H +#define FREERDP_SERVER_WIN_PEER_H + +#include "wf_interface.h" + +#include <freerdp/listener.h> + +BOOL wf_peer_accepted(freerdp_listener* instance, freerdp_peer* client); + +#endif /* FREERDP_SERVER_WIN_PEER_H */ diff --git a/server/Windows/wf_rdpsnd.c b/server/Windows/wf_rdpsnd.c new file mode 100644 index 0000000..b313c35 --- /dev/null +++ b/server/Windows/wf_rdpsnd.c @@ -0,0 +1,152 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Windows Server (Audio Output) + * + * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * Copyright 2013 Corey Clayton <can.of.tuna@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <freerdp/config.h> + +#include <stdio.h> +#include <stdlib.h> + +#include <winpr/windows.h> +#include <freerdp/server/server-common.h> + +#include "wf_rdpsnd.h" +#include "wf_info.h" + +#ifdef WITH_RDPSND_DSOUND + +#include "wf_directsound.h" + +#else + +#include "wf_wasapi.h" + +#endif + +#include <freerdp/log.h> +#define TAG SERVER_TAG("windows") + +static void wf_peer_rdpsnd_activated(RdpsndServerContext* context) +{ + wfInfo* wfi; + wfi = wf_info_get_instance(); + wfi->agreed_format = NULL; + WLog_DBG(TAG, "Client supports the following %d formats:", context->num_client_formats); + + for (size_t i = 0; i < context->num_client_formats; i++) + { + // TODO: improve the way we agree on a format + for (size_t j = 0; j < context->num_server_formats; j++) + { + if ((context->client_formats[i].wFormatTag == context->server_formats[j].wFormatTag) && + (context->client_formats[i].nChannels == context->server_formats[j].nChannels) && + (context->client_formats[i].nSamplesPerSec == + context->server_formats[j].nSamplesPerSec)) + { + WLog_DBG(TAG, "agreed on format!"); + wfi->agreed_format = (AUDIO_FORMAT*)&context->server_formats[j]; + break; + } + } + + if (wfi->agreed_format != NULL) + break; + } + + if (wfi->agreed_format == NULL) + { + WLog_ERR(TAG, "Could not agree on a audio format with the server"); + return; + } + + context->SelectFormat(context, i); + context->SetVolume(context, 0x7FFF, 0x7FFF); +#ifdef WITH_RDPSND_DSOUND + wf_directsound_activate(context); +#else + wf_wasapi_activate(context); +#endif +} + +int wf_rdpsnd_lock() +{ + DWORD dRes; + wfInfo* wfi; + wfi = wf_info_get_instance(); + dRes = WaitForSingleObject(wfi->snd_mutex, INFINITE); + + switch (dRes) + { + case WAIT_ABANDONED: + case WAIT_OBJECT_0: + return TRUE; + break; + + case WAIT_TIMEOUT: + return FALSE; + break; + + case WAIT_FAILED: + WLog_ERR(TAG, "wf_rdpsnd_lock failed with 0x%08lX", GetLastError()); + return -1; + break; + } + + return -1; +} + +int wf_rdpsnd_unlock() +{ + wfInfo* wfi; + wfi = wf_info_get_instance(); + + if (ReleaseMutex(wfi->snd_mutex) == 0) + { + WLog_DBG(TAG, "wf_rdpsnd_unlock failed with 0x%08lX", GetLastError()); + return -1; + } + + return TRUE; +} + +BOOL wf_peer_rdpsnd_init(wfPeerContext* context) +{ + wfInfo* wfi = wf_info_get_instance(); + + if (!wfi) + return FALSE; + + if (!(wfi->snd_mutex = CreateMutex(NULL, FALSE, NULL))) + return FALSE; + + context->rdpsnd = rdpsnd_server_context_new(context->vcm); + context->rdpsnd->rdpcontext = &context->_p; + context->rdpsnd->data = context; + context->rdpsnd->num_server_formats = + server_rdpsnd_get_formats(&context->rdpsnd->server_formats); + + if (context->rdpsnd->num_server_formats > 0) + context->rdpsnd->src_format = &context->rdpsnd->server_formats[0]; + + context->rdpsnd->Activated = wf_peer_rdpsnd_activated; + context->rdpsnd->Initialize(context->rdpsnd, TRUE); + wf_rdpsnd_set_latest_peer(context); + wfi->snd_stop = FALSE; + return TRUE; +} diff --git a/server/Windows/wf_rdpsnd.h b/server/Windows/wf_rdpsnd.h new file mode 100644 index 0000000..88e631d --- /dev/null +++ b/server/Windows/wf_rdpsnd.h @@ -0,0 +1,33 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Windows Server (Audio Output) + * + * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_SERVER_WIN_RDPSND_H +#define FREERDP_SERVER_WIN_RDPSND_H + +#include <freerdp/freerdp.h> +#include <freerdp/listener.h> +#include <freerdp/server/rdpsnd.h> + +#include "wf_interface.h" + +int wf_rdpsnd_lock(void); +int wf_rdpsnd_unlock(void); +BOOL wf_peer_rdpsnd_init(wfPeerContext* context); + +#endif /* FREERDP_SERVER_WIN_RDPSND_H */ diff --git a/server/Windows/wf_settings.c b/server/Windows/wf_settings.c new file mode 100644 index 0000000..63d2327 --- /dev/null +++ b/server/Windows/wf_settings.c @@ -0,0 +1,102 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Windows Server + * + * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <freerdp/config.h> + +#include <winpr/tchar.h> +#include <winpr/windows.h> + +#include "wf_settings.h" + +BOOL wf_settings_read_dword(HKEY key, LPCSTR subkey, LPTSTR name, DWORD* value) +{ + HKEY hKey; + LONG status; + DWORD dwType; + DWORD dwSize; + DWORD dwValue; + + status = RegOpenKeyExA(key, subkey, 0, KEY_READ | KEY_WOW64_64KEY, &hKey); + + if (status == ERROR_SUCCESS) + { + dwSize = sizeof(DWORD); + + status = RegQueryValueEx(hKey, name, NULL, &dwType, (BYTE*)&dwValue, &dwSize); + + if (status == ERROR_SUCCESS) + *value = dwValue; + + RegCloseKey(hKey); + + return (status == ERROR_SUCCESS) ? TRUE : FALSE; + } + + return FALSE; +} + +BOOL wf_settings_read_string_ascii(HKEY key, LPCSTR subkey, LPTSTR name, char** value) +{ + HKEY hKey; + int length; + LONG status; + DWORD dwType; + DWORD dwSize; + char* strA; + TCHAR* strX = NULL; + + status = RegOpenKeyExA(key, subkey, 0, KEY_READ | KEY_WOW64_64KEY, &hKey); + + if (status != ERROR_SUCCESS) + return FALSE; + + status = RegQueryValueEx(hKey, name, NULL, &dwType, NULL, &dwSize); + + if (status == ERROR_SUCCESS) + { + strX = (LPTSTR)malloc(dwSize + sizeof(TCHAR)); + if (!strX) + return FALSE; + status = RegQueryValueEx(hKey, name, NULL, &dwType, (BYTE*)strX, &dwSize); + + if (status != ERROR_SUCCESS) + { + free(strX); + RegCloseKey(hKey); + return FALSE; + } + } + + if (strX) + { +#ifdef UNICODE + length = WideCharToMultiByte(CP_UTF8, 0, strX, lstrlenW(strX), NULL, 0, NULL, NULL); + strA = (char*)malloc(length + 1); + WideCharToMultiByte(CP_UTF8, 0, strX, lstrlenW(strX), strA, length, NULL, NULL); + strA[length] = '\0'; + free(strX); +#else + strA = (char*)strX; +#endif + *value = strA; + return TRUE; + } + + return FALSE; +} diff --git a/server/Windows/wf_settings.h b/server/Windows/wf_settings.h new file mode 100644 index 0000000..40e25aa --- /dev/null +++ b/server/Windows/wf_settings.h @@ -0,0 +1,28 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Windows Server + * + * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_SERVER_WIN_SETTINGS_H +#define FREERDP_SERVER_WIN_SETTINGS_H + +#include "wf_interface.h" + +BOOL wf_settings_read_dword(HKEY key, LPCSTR subkey, LPTSTR name, DWORD* value); +BOOL wf_settings_read_string_ascii(HKEY key, LPCSTR subkey, LPTSTR name, char** value); + +#endif /* FREERDP_SERVER_WIN_SETTINGS_H */ diff --git a/server/Windows/wf_update.c b/server/Windows/wf_update.c new file mode 100644 index 0000000..06d60c9 --- /dev/null +++ b/server/Windows/wf_update.c @@ -0,0 +1,251 @@ +/** + * FreeRDP: A Remote Desktop Protocol Client + * FreeRDP Windows Server + * + * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * Copyright 2012 Corey Clayton <can.of.tuna@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <freerdp/config.h> + +#include <stdio.h> + +#include <winpr/windows.h> + +#include <freerdp/freerdp.h> +#include <freerdp/listener.h> + +#include "wf_peer.h" +#include "wf_info.h" +#include "wf_mirage.h" + +#include "wf_update.h" + +#include <freerdp/log.h> +#define TAG SERVER_TAG("windows") + +DWORD WINAPI wf_update_thread(LPVOID lpParam) +{ + DWORD fps; + wfInfo* wfi; + DWORD beg, end; + DWORD diff, rate; + wfi = (wfInfo*)lpParam; + fps = wfi->framesPerSecond; + rate = 1000 / fps; + + while (1) + { + beg = GetTickCount(); + + if (wf_info_lock(wfi) > 0) + { + if (wfi->activePeerCount > 0) + { + wf_info_update_changes(wfi); + + if (wf_info_have_updates(wfi)) + { + wf_update_encode(wfi); + // WLog_DBG(TAG, "Start of parallel sending"); + int index = 0; + + for (int peerindex = 0; peerindex < wfi->peerCount; peerindex++) + { + for (; index < FREERDP_SERVER_WIN_INFO_MAXPEERS; index++) + { + if (wfi->peers[index] && wfi->peers[index]->activated) + { + // WLog_DBG(TAG, "Setting event for %d of %d", index + 1, + // wfi->activePeerCount); + SetEvent(((wfPeerContext*)wfi->peers[index]->context)->updateEvent); + } + } + } + + for (int index = 0; index < wfi->activePeerCount; index++) + { + // WLog_DBG(TAG, "Waiting for %d of %d", index + 1, wfi->activePeerCount); + // WaitForSingleObject(wfi->updateSemaphore, INFINITE); + WaitForSingleObject(wfi->updateSemaphore, 1000); + } + + // WLog_DBG(TAG, "End of parallel sending"); + wf_info_clear_invalid_region(wfi); + } + } + + wf_info_unlock(wfi); + } + + end = GetTickCount(); + diff = end - beg; + + if (diff < rate) + { + Sleep(rate - diff); + } + } + + // WLog_DBG(TAG, "Exiting Update Thread"); + return 0; +} + +void wf_update_encode(wfInfo* wfi) +{ + RFX_RECT rect; + long height, width; + BYTE* pDataBits = NULL; + int stride; + SURFACE_BITS_COMMAND* cmd; + wf_info_find_invalid_region(wfi); + cmd = &wfi->cmd; + Stream_SetPosition(wfi->s, 0); + wf_info_getScreenData(wfi, &width, &height, &pDataBits, &stride); + rect.x = 0; + rect.y = 0; + rect.width = (UINT16)width; + rect.height = (UINT16)height; + // WLog_DBG(TAG, "x:%"PRId32" y:%"PRId32" w:%ld h:%ld", wfi->invalid.left, wfi->invalid.top, + // width, height); + Stream_Clear(wfi->s); + + if (!(rfx_compose_message(wfi->rfx_context, wfi->s, &rect, 1, pDataBits, width, height, + stride))) + { + return; + } + + wfi->frame_idx = rfx_context_get_frame_idx(wfi->rfx_context); + cmd->destLeft = wfi->invalid.left; + cmd->destTop = wfi->invalid.top; + cmd->destRight = wfi->invalid.left + width; + cmd->destBottom = wfi->invalid.top + height; + cmd->bmp.bpp = 32; + cmd->bmp.codecID = 3; + cmd->bmp.width = width; + cmd->bmp.height = height; + cmd->bmp.bitmapDataLength = Stream_GetPosition(wfi->s); + cmd->bmp.bitmapData = Stream_Buffer(wfi->s); +} + +void wf_update_peer_send(wfInfo* wfi, wfPeerContext* context) +{ + freerdp_peer* client; + + WINPR_ASSERT(wfi); + WINPR_ASSERT(context); + + client = ((rdpContext*)context)->peer; + WINPR_ASSERT(client); + + /* This happens when the RemoteFX encoder state is reset */ + + if (wfi->frame_idx == 1) + context->frame_idx = 0; + + /* + * When a new client connects, it is possible that old frames from + * from a previous encoding state remain. Those frames should be discarded + * as they will cause an error condition in mstsc. + */ + + if ((context->frame_idx + 1) != wfi->frame_idx) + { + /* This frame is meant to be discarded */ + if (context->frame_idx == 0) + return; + + /* This is an unexpected error condition */ + WLog_DBG(TAG, "Unexpected Frame Index: Actual: %d Expected: %d", wfi->frame_idx, + context->frame_idx + 1); + } + + WINPR_ASSERT(client->context); + WINPR_ASSERT(client->context->settings); + WINPR_ASSERT(client->context->update); + WINPR_ASSERT(client->context->update->SurfaceBits); + + wfi->cmd.bmp.codecID = + freerdp_settings_get_uint32(client->context->settings, FreeRDP_RemoteFxCodecId); + client->context->update->SurfaceBits(client->context, &wfi->cmd); + context->frame_idx++; +} + +void wf_update_encoder_reset(wfInfo* wfi) +{ + if (wf_info_lock(wfi) > 0) + { + WLog_DBG(TAG, "Resetting encoder"); + + if (wfi->rfx_context) + { + rfx_context_reset(wfi->rfx_context, wfi->servscreen_width, wfi->servscreen_height); + } + else + { + /* TODO: pass ThreadingFlags somehow */ + wfi->rfx_context = rfx_context_new(TRUE); + rfx_context_set_mode(wfi->rfx_context, RLGR3); + rfx_context_reset(wfi->rfx_context, wfi->servscreen_width, wfi->servscreen_height); + rfx_context_set_pixel_format(wfi->rfx_context, PIXEL_FORMAT_BGRA32); + wfi->s = Stream_New(NULL, 0xFFFF); + } + + wf_info_invalidate_full_screen(wfi); + wf_info_unlock(wfi); + } +} + +void wf_update_peer_activate(wfInfo* wfi, wfPeerContext* context) +{ + if (wf_info_lock(wfi) > 0) + { + if (wfi->activePeerCount < 1) + { +#ifndef WITH_DXGI_1_2 + wf_mirror_driver_activate(wfi); +#endif + ResumeThread(wfi->updateThread); + } + + wf_update_encoder_reset(wfi); + wfi->activePeerCount++; + WLog_DBG(TAG, "Activating Peer Updates: %d", wfi->activePeerCount); + wf_info_unlock(wfi); + } +} + +void wf_update_peer_deactivate(wfInfo* wfi, wfPeerContext* context) +{ + if (wf_info_lock(wfi) > 0) + { + freerdp_peer* client = ((rdpContext*)context)->peer; + + if (client->activated) + { + if (wfi->activePeerCount <= 1) + { + wf_mirror_driver_deactivate(wfi); + } + + client->activated = FALSE; + wfi->activePeerCount--; + WLog_DBG(TAG, "Deactivating Peer Updates: %d", wfi->activePeerCount); + } + + wf_info_unlock(wfi); + } +} diff --git a/server/Windows/wf_update.h b/server/Windows/wf_update.h new file mode 100644 index 0000000..47553af --- /dev/null +++ b/server/Windows/wf_update.h @@ -0,0 +1,37 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Windows Server + * + * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_SERVER_WIN_UPDATE_H +#define FREERDP_SERVER_WIN_UPDATE_H + +#include "wf_interface.h" + +void wf_update_encode(wfInfo* wfi); +void wf_update_send(wfInfo* wfi); + +DWORD WINAPI wf_update_thread(LPVOID lpParam); + +void wf_update_begin(wfInfo* wfi); +void wf_update_peer_send(wfInfo* wfi, wfPeerContext* context); +void wf_update_end(wfInfo* wfi); + +void wf_update_peer_activate(wfInfo* wfi, wfPeerContext* context); +void wf_update_peer_deactivate(wfInfo* wfi, wfPeerContext* context); + +#endif /* FREERDP_SERVER_WIN_UPDATE_H */ diff --git a/server/Windows/wf_wasapi.c b/server/Windows/wf_wasapi.c new file mode 100644 index 0000000..3925f99 --- /dev/null +++ b/server/Windows/wf_wasapi.c @@ -0,0 +1,333 @@ + +#include "wf_wasapi.h" +#include "wf_info.h" + +#include <initguid.h> +#include <mmdeviceapi.h> +#include <functiondiscoverykeys_devpkey.h> +#include <audioclient.h> + +#include <freerdp/log.h> +#define TAG SERVER_TAG("windows") + +//#define REFTIMES_PER_SEC 10000000 +//#define REFTIMES_PER_MILLISEC 10000 + +#define REFTIMES_PER_SEC 100000 +#define REFTIMES_PER_MILLISEC 100 + +//#define REFTIMES_PER_SEC 50000 +//#define REFTIMES_PER_MILLISEC 50 + +#ifndef __MINGW32__ +DEFINE_GUID(CLSID_MMDeviceEnumerator, 0xBCDE0395, 0xE52F, 0x467C, 0x8E, 0x3D, 0xC4, 0x57, 0x92, + 0x91, 0x69, 0x2E); +DEFINE_GUID(IID_IMMDeviceEnumerator, 0xA95664D2, 0x9614, 0x4F35, 0xA7, 0x46, 0xDE, 0x8D, 0xB6, 0x36, + 0x17, 0xE6); +DEFINE_GUID(IID_IAudioClient, 0x1cb9ad4c, 0xdbfa, 0x4c32, 0xb1, 0x78, 0xc2, 0xf5, 0x68, 0xa7, 0x03, + 0xb2); +DEFINE_GUID(IID_IAudioCaptureClient, 0xc8adbd64, 0xe71e, 0x48a0, 0xa4, 0xde, 0x18, 0x5c, 0x39, 0x5c, + 0xd3, 0x17); +#endif + +LPWSTR devStr = NULL; +wfPeerContext* latestPeer = NULL; + +int wf_rdpsnd_set_latest_peer(wfPeerContext* peer) +{ + latestPeer = peer; + return 0; +} + +int wf_wasapi_activate(RdpsndServerContext* context) +{ + wchar_t* pattern = L"Stereo Mix"; + HANDLE hThread; + + wf_wasapi_get_device_string(pattern, &devStr); + + if (devStr == NULL) + { + WLog_ERR(TAG, "Failed to match for output device! Disabling rdpsnd."); + return 1; + } + + WLog_DBG(TAG, "RDPSND (WASAPI) Activated"); + if (!(hThread = CreateThread(NULL, 0, wf_rdpsnd_wasapi_thread, latestPeer, 0, NULL))) + { + WLog_ERR(TAG, "CreateThread failed"); + return 1; + } + CloseHandle(hThread); + + return 0; +} + +int wf_wasapi_get_device_string(LPWSTR pattern, LPWSTR* deviceStr) +{ + HRESULT hr; + IMMDeviceEnumerator* pEnumerator = NULL; + IMMDeviceCollection* pCollection = NULL; + IMMDevice* pEndpoint = NULL; + IPropertyStore* pProps = NULL; + LPWSTR pwszID = NULL; + unsigned int count; + + CoInitialize(NULL); + hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, &IID_IMMDeviceEnumerator, + (void**)&pEnumerator); + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to cocreate device enumerator"); + exit(1); + } + + hr = pEnumerator->lpVtbl->EnumAudioEndpoints(pEnumerator, eCapture, DEVICE_STATE_ACTIVE, + &pCollection); + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to create endpoint collection"); + exit(1); + } + + pCollection->lpVtbl->GetCount(pCollection, &count); + WLog_INFO(TAG, "Num endpoints: %u", count); + + if (count == 0) + { + WLog_ERR(TAG, "No endpoints!"); + exit(1); + } + + for (unsigned int i = 0; i < count; ++i) + { + PROPVARIANT nameVar; + PropVariantInit(&nameVar); + + hr = pCollection->lpVtbl->Item(pCollection, i, &pEndpoint); + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to get endpoint %u", i); + exit(1); + } + + hr = pEndpoint->lpVtbl->GetId(pEndpoint, &pwszID); + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to get endpoint ID"); + exit(1); + } + + hr = pEndpoint->lpVtbl->OpenPropertyStore(pEndpoint, STGM_READ, &pProps); + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to open property store"); + exit(1); + } + + hr = pProps->lpVtbl->GetValue(pProps, &PKEY_Device_FriendlyName, &nameVar); + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to get device friendly name"); + exit(1); + } + + // do this a more reliable way + if (wcscmp(pattern, nameVar.pwszVal) < 0) + { + unsigned int devStrLen; + WLog_INFO(TAG, "Using sound ouput endpoint: [%s] (%s)", nameVar.pwszVal, pwszID); + // WLog_INFO(TAG, "matched %d characters", wcscmp(pattern, nameVar.pwszVal); + devStrLen = wcslen(pwszID); + *deviceStr = (LPWSTR)calloc(devStrLen + 1, 2); + if (!deviceStr) + return -1; + wcscpy_s(*deviceStr, devStrLen + 1, pwszID); + } + CoTaskMemFree(pwszID); + pwszID = NULL; + PropVariantClear(&nameVar); + + pProps->lpVtbl->Release(pProps); + pProps = NULL; + + pEndpoint->lpVtbl->Release(pEndpoint); + pEndpoint = NULL; + } + + pCollection->lpVtbl->Release(pCollection); + pCollection = NULL; + + pEnumerator->lpVtbl->Release(pEnumerator); + pEnumerator = NULL; + CoUninitialize(); + + return 0; +} + +DWORD WINAPI wf_rdpsnd_wasapi_thread(LPVOID lpParam) +{ + IMMDeviceEnumerator* pEnumerator = NULL; + IMMDevice* pDevice = NULL; + IAudioClient* pAudioClient = NULL; + IAudioCaptureClient* pCaptureClient = NULL; + WAVEFORMATEX* pwfx = NULL; + HRESULT hr; + REFERENCE_TIME hnsRequestedDuration = REFTIMES_PER_SEC; + REFERENCE_TIME hnsActualDuration; + UINT32 bufferFrameCount; + UINT32 numFramesAvailable; + UINT32 packetLength = 0; + UINT32 dCount = 0; + BYTE* pData; + + wfPeerContext* context; + wfInfo* wfi; + + wfi = wf_info_get_instance(); + context = (wfPeerContext*)lpParam; + + CoInitialize(NULL); + hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, &IID_IMMDeviceEnumerator, + (void**)&pEnumerator); + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to cocreate device enumerator"); + exit(1); + } + + hr = pEnumerator->lpVtbl->GetDevice(pEnumerator, devStr, &pDevice); + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to cocreate get device"); + exit(1); + } + + hr = pDevice->lpVtbl->Activate(pDevice, &IID_IAudioClient, CLSCTX_ALL, NULL, + (void**)&pAudioClient); + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to activate audio client"); + exit(1); + } + + hr = pAudioClient->lpVtbl->GetMixFormat(pAudioClient, &pwfx); + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to get mix format"); + exit(1); + } + + pwfx->wFormatTag = wfi->agreed_format->wFormatTag; + pwfx->nChannels = wfi->agreed_format->nChannels; + pwfx->nSamplesPerSec = wfi->agreed_format->nSamplesPerSec; + pwfx->nAvgBytesPerSec = wfi->agreed_format->nAvgBytesPerSec; + pwfx->nBlockAlign = wfi->agreed_format->nBlockAlign; + pwfx->wBitsPerSample = wfi->agreed_format->wBitsPerSample; + pwfx->cbSize = wfi->agreed_format->cbSize; + + hr = pAudioClient->lpVtbl->Initialize(pAudioClient, AUDCLNT_SHAREMODE_SHARED, 0, + hnsRequestedDuration, 0, pwfx, NULL); + + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to initialize the audio client"); + exit(1); + } + + hr = pAudioClient->lpVtbl->GetBufferSize(pAudioClient, &bufferFrameCount); + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to get buffer size"); + exit(1); + } + + hr = pAudioClient->lpVtbl->GetService(pAudioClient, &IID_IAudioCaptureClient, + (void**)&pCaptureClient); + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to get the capture client"); + exit(1); + } + + hnsActualDuration = (UINT32)REFTIMES_PER_SEC * bufferFrameCount / pwfx->nSamplesPerSec; + + hr = pAudioClient->lpVtbl->Start(pAudioClient); + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to start capture"); + exit(1); + } + + dCount = 0; + + while (wfi->snd_stop == FALSE) + { + DWORD flags; + + Sleep(hnsActualDuration / REFTIMES_PER_MILLISEC / 2); + + hr = pCaptureClient->lpVtbl->GetNextPacketSize(pCaptureClient, &packetLength); + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to get packet length"); + exit(1); + } + + while (packetLength != 0) + { + hr = pCaptureClient->lpVtbl->GetBuffer(pCaptureClient, &pData, &numFramesAvailable, + &flags, NULL, NULL); + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to get buffer"); + exit(1); + } + + // Here we are writing the audio data + // not sure if this flag is ever set by the system; msdn is not clear about it + if (!(flags & AUDCLNT_BUFFERFLAGS_SILENT)) + context->rdpsnd->SendSamples(context->rdpsnd, pData, packetLength, + (UINT16)(GetTickCount() & 0xffff)); + + hr = pCaptureClient->lpVtbl->ReleaseBuffer(pCaptureClient, numFramesAvailable); + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to release buffer"); + exit(1); + } + + hr = pCaptureClient->lpVtbl->GetNextPacketSize(pCaptureClient, &packetLength); + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to get packet length"); + exit(1); + } + } + } + + pAudioClient->lpVtbl->Stop(pAudioClient); + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to stop audio client"); + exit(1); + } + + CoTaskMemFree(pwfx); + + if (pEnumerator != NULL) + pEnumerator->lpVtbl->Release(pEnumerator); + + if (pDevice != NULL) + pDevice->lpVtbl->Release(pDevice); + + if (pAudioClient != NULL) + pAudioClient->lpVtbl->Release(pAudioClient); + + if (pCaptureClient != NULL) + pCaptureClient->lpVtbl->Release(pCaptureClient); + + CoUninitialize(); + + return 0; +} diff --git a/server/Windows/wf_wasapi.h b/server/Windows/wf_wasapi.h new file mode 100644 index 0000000..da9c7dc --- /dev/null +++ b/server/Windows/wf_wasapi.h @@ -0,0 +1,15 @@ +#ifndef FREERDP_SERVER_WIN_WASAPI_H +#define FREERDP_SERVER_WIN_WASAPI_H + +#include <freerdp/server/rdpsnd.h> +#include "wf_interface.h" + +int wf_rdpsnd_set_latest_peer(wfPeerContext* peer); + +int wf_wasapi_activate(RdpsndServerContext* context); + +int wf_wasapi_get_device_string(LPWSTR pattern, LPWSTR* deviceStr); + +DWORD WINAPI wf_rdpsnd_wasapi_thread(LPVOID lpParam); + +#endif /* FREERDP_SERVER_WIN_WASAPI_H */ |