[VCDCONTROLTOOL] Add a GUI app to manage virtual CD-ROM devices
authorPierre Schweitzer <pierre@reactos.org>
Tue, 11 Dec 2018 13:24:49 +0000 (14:24 +0100)
committerPierre Schweitzer <pierre@reactos.org>
Tue, 11 Dec 2018 14:04:10 +0000 (15:04 +0100)
This is just an open sourced version of the tool provided by MS along
with the driver.
It will avoid users fighting with the CLI tool (that was doing the job
though ;-)).

Looks like that: https://twitter.com/HeisSpiter/status/1072482763348107264

Nota 1: it doesn't have an icon
Nota 2: code may be ugly, I'm not a umode guy! ;-)

modules/rosapps/applications/CMakeLists.txt
modules/rosapps/applications/vcdcontroltool/CMakeLists.txt [new file with mode: 0644]
modules/rosapps/applications/vcdcontroltool/lang/en-US.rc [new file with mode: 0755]
modules/rosapps/applications/vcdcontroltool/resource.h [new file with mode: 0644]
modules/rosapps/applications/vcdcontroltool/vcdcontroltool.c [new file with mode: 0644]
modules/rosapps/applications/vcdcontroltool/vcdcontroltool.rc [new file with mode: 0644]

index e42ab38..74dd129 100644 (file)
@@ -8,4 +8,5 @@ add_subdirectory(notevil)
 add_subdirectory(rosinternals)
 add_subdirectory(screensavers)
 add_subdirectory(sysutils)
+add_subdirectory(vcdcontroltool)
 add_subdirectory(winfile)
diff --git a/modules/rosapps/applications/vcdcontroltool/CMakeLists.txt b/modules/rosapps/applications/vcdcontroltool/CMakeLists.txt
new file mode 100644 (file)
index 0000000..b8d9f81
--- /dev/null
@@ -0,0 +1,9 @@
+include_directories(${REACTOS_SOURCE_DIR}/modules/rosapps/drivers/vcdrom)
+
+list(APPEND SOURCE
+    vcdcontroltool.c)
+
+add_executable(vcdcontroltool ${SOURCE} vcdcontroltool.rc)
+set_module_type(vcdcontroltool win32gui UNICODE)
+add_importlibs(vcdcontroltool advapi32 user32 gdi32 comctl32 comdlg32 ntdll msvcrt kernel32)
+add_cd_file(TARGET vcdcontroltool DESTINATION reactos/system32 FOR all)
diff --git a/modules/rosapps/applications/vcdcontroltool/lang/en-US.rc b/modules/rosapps/applications/vcdcontroltool/lang/en-US.rc
new file mode 100755 (executable)
index 0000000..68834ea
--- /dev/null
@@ -0,0 +1,62 @@
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+
+IDD_MAINWINDOW DIALOGEX 0, 0, 320, 200
+STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
+EXSTYLE WS_EX_APPWINDOW
+CAPTION "Virtual CDRom Control Panel"
+FONT 8, "MS Shell Dlg"
+BEGIN
+    PUSHBUTTON "OK", IDC_MAINOK, 211, 179, 50, 14, WS_CHILD | WS_VISIBLE | WS_TABSTOP
+    CONTROL "", IDC_MAINDEVICES, "SysListView32", LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_NOSORTHEADER | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP, 7, 7, 306, 83
+    GROUPBOX "Drive Geometry", IDC_STATIC, 7, 101, 153, 74, BS_GROUPBOX | WS_CHILD | WS_VISIBLE
+    LTEXT "Number of sectors", IDC_STATIC, 15, 115, 60, 9, WS_CHILD | WS_VISIBLE | WS_GROUP
+    LTEXT "Sector Size", IDC_STATIC, 15, 127, 49, 9, WS_CHILD | WS_VISIBLE | WS_GROUP
+    LTEXT "Free Clusters", IDC_STATIC, 15, 139, 49, 9, WS_CHILD | WS_VISIBLE | WS_GROUP
+    LTEXT "Total Clusters", IDC_STATIC, 15, 151, 49, 9, WS_CHILD | WS_VISIBLE | WS_GROUP
+    EDITTEXT IDC_MAINSECTORS, 88, 115, 49, 12, ES_LEFT | ES_AUTOHSCROLL | ES_READONLY | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP
+    EDITTEXT IDC_MAINSIZE, 88, 127, 49, 12, ES_LEFT | ES_AUTOHSCROLL | ES_READONLY | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP
+    EDITTEXT IDC_MAINFREE, 88, 139, 49, 12, ES_LEFT | ES_AUTOHSCROLL | ES_READONLY | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP
+    EDITTEXT IDC_MAINTOTAL, 88, 151, 49, 12, ES_LEFT | ES_AUTOHSCROLL | ES_READONLY | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP
+    PUSHBUTTON "&Add Drive", IDC_MAINADD, 183, 123, 50, 14, WS_CHILD | WS_VISIBLE | WS_TABSTOP
+    PUSHBUTTON "&Remove Drive", IDC_MAINREMOVE, 242, 123, 50, 14, WS_CHILD | WS_VISIBLE | WS_TABSTOP
+    PUSHBUTTON "Mount", IDC_MAINMOUNT, 183, 142, 50, 14, WS_CHILD | WS_VISIBLE | WS_TABSTOP
+    PUSHBUTTON "Eject", IDC_MAINEJECT, 183, 161, 109, 14, WS_CHILD | WS_VISIBLE | WS_TABSTOP
+    PUSHBUTTON "Remount", IDC_MAINREMOUNT, 242, 142, 50, 14, WS_CHILD | WS_VISIBLE | WS_TABSTOP
+    PUSHBUTTON "Driver Control ...", IDC_MAINCONTROL, 183, 105, 109, 14, WS_CHILD | WS_VISIBLE | WS_TABSTOP
+END
+
+IDD_MOUNTWINDOW DIALOG 0, 0, 187, 97
+STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "Special Mount Options"
+FONT 8, "MS Shell Dlg"
+BEGIN
+    PUSHBUTTON "OK", IDC_MOUNTOK, 130, 59, 50, 14, WS_CHILD | WS_VISIBLE | WS_TABSTOP
+    PUSHBUTTON "Cancel", IDC_MOUNTCANCEL, 130, 76, 50, 14, WS_CHILD | WS_VISIBLE | WS_TABSTOP
+    EDITTEXT IDC_MOUNTIMAGE, 7, 17, 173, 14, ES_LEFT | ES_AUTOHSCROLL | ES_READONLY | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP
+    LTEXT "Image to be mounted", IDC_STATIC, 7, 7, 173, 10, SS_LEFT | SS_SUNKEN | WS_CHILD | WS_VISIBLE | WS_GROUP
+    AUTOCHECKBOX "Suppress UDF", IDC_MOUNTUDF, 7, 40, 114, 10, BS_FLAT | WS_CHILD | WS_VISIBLE | WS_TABSTOP
+    AUTOCHECKBOX "Suppress Joliet", IDC_MOUNTJOLIET, 7, 56, 111, 9, BS_FLAT | WS_CHILD | WS_VISIBLE | WS_TABSTOP
+    AUTOCHECKBOX "Persistent mount", IDC_MOUNTPERSIST, 7, 71, 111, 9, BS_FLAT | WS_CHILD | WS_VISIBLE | WS_TABSTOP
+END
+
+IDD_DRIVERWINDOW DIALOG 0, 0, 189, 123
+STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "Virtual CD-ROM Driver Control"
+FONT 8, "MS Shell Dlg"
+BEGIN
+    PUSHBUTTON "OK", IDC_DRIVEROK, 43, 98, 103, 14, WS_CHILD | WS_VISIBLE | WS_TABSTOP
+    PUSHBUTTON "Install Driver ...", IDC_DRIVERINSTALL, 43, 61, 51, 14, WS_CHILD | WS_VISIBLE | WS_TABSTOP
+    PUSHBUTTON "Start", IDC_DRIVERSTART, 43, 79, 51, 14, WS_CHILD | WS_VISIBLE | WS_TABSTOP
+    PUSHBUTTON  "Stop", IDC_DRIVERSTOP, 96, 79, 50, 14, WS_CHILD | WS_VISIBLE | WS_TABSTOP
+    PUSHBUTTON  "Remove Driver", IDC_DRIVERREMOVE, 96, 61, 50, 14, WS_CHILD | WS_VISIBLE | WS_TABSTOP
+    EDITTEXT IDC_DRIVERINFO, 7, 7, 175, 50, ES_CENTER | ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL | ES_READONLY | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP
+END
+
+STRINGTABLE
+BEGIN
+    IDS_DRIVE       "Drive"
+    IDS_MAPPEDIMAGE "Mapped Image"
+    IDS_NOMOUNTED   "No Image Mounted. Last one was - %s"
+    IDS_NONE        "None"
+    IDS_FILTER      "All Supported Image (*.udf; *.cdfs; *.jo; *.iso; *.rock)\0*.udf;*.cdfs;*.jo;*.iso;*.rock\0"
+END
diff --git a/modules/rosapps/applications/vcdcontroltool/resource.h b/modules/rosapps/applications/vcdcontroltool/resource.h
new file mode 100644 (file)
index 0000000..6ce5ed2
--- /dev/null
@@ -0,0 +1,37 @@
+#pragma once
+
+#define IDD_MAINWINDOW    200
+#define IDD_MOUNTWINDOW   201
+#define IDD_DRIVERWINDOW  202
+
+#define IDC_STATIC        -1
+#define IDC_MAINOK        1001
+#define IDC_MAINDEVICES   1002
+#define IDC_MAINSECTORS   1003
+#define IDC_MAINSIZE      1004
+#define IDC_MAINFREE      1005
+#define IDC_MAINTOTAL     1006
+#define IDC_MAINADD       1007
+#define IDC_MAINREMOVE    1008
+#define IDC_MAINMOUNT     1009
+#define IDC_MAINEJECT     1010
+#define IDC_MAINREMOUNT   1011
+#define IDC_MAINCONTROL   1012
+#define IDC_MOUNTOK       1013
+#define IDC_MOUNTCANCEL   1014
+#define IDC_MOUNTIMAGE    1015
+#define IDC_MOUNTUDF      1016
+#define IDC_MOUNTJOLIET   1017
+#define IDC_MOUNTPERSIST  1018
+#define IDC_DRIVEROK      1019
+#define IDC_DRIVERINSTALL 1020
+#define IDC_DRIVERSTART   1021
+#define IDC_DRIVERSTOP    1022
+#define IDC_DRIVERREMOVE  1023
+#define IDC_DRIVERINFO    1024
+
+#define IDS_DRIVE         2000
+#define IDS_MAPPEDIMAGE   2001
+#define IDS_NOMOUNTED     2002
+#define IDS_NONE          2003
+#define IDS_FILTER        2004
diff --git a/modules/rosapps/applications/vcdcontroltool/vcdcontroltool.c b/modules/rosapps/applications/vcdcontroltool/vcdcontroltool.c
new file mode 100644 (file)
index 0000000..bd7ca6c
--- /dev/null
@@ -0,0 +1,920 @@
+/*
+ * PROJECT:     ReactOS Virtual CD Control Tool
+ * LICENSE:     GPL - See COPYING in the top level directory
+ * FILE:        modules/rosapps/applications/vcdcontroltool/vcdcontroltool.c
+ * PURPOSE:     main dialog implementation
+ * COPYRIGHT:   Copyright 2018 Pierre Schweitzer
+ *
+ */
+
+#define WIN32_NO_STATUS
+#include <stdarg.h>
+#include <windef.h>
+#include <winbase.h>
+#include <winuser.h>
+#include <wingdi.h>
+#include <winsvc.h>
+#include <commctrl.h>
+#include <commdlg.h>
+#include <wchar.h>
+#include <ndk/rtltypes.h>
+#include <ndk/rtlfuncs.h>
+
+#include <vcdioctl.h>
+#define IOCTL_CDROM_BASE FILE_DEVICE_CD_ROM
+#define IOCTL_CDROM_EJECT_MEDIA CTL_CODE(IOCTL_CDROM_BASE, 0x0202, METHOD_BUFFERED, FILE_READ_ACCESS)
+
+#include "resource.h"
+
+HWND hWnd;
+HWND hMountWnd;
+HWND hDriverWnd;
+HINSTANCE hInstance;
+/* FIXME: to improve, ugly hack */
+WCHAR wMountLetter;
+
+static
+HANDLE
+OpenMaster(VOID)
+{
+    /* Just open the device */
+    return CreateFile(L"\\\\.\\\\VirtualCdRom", GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
+                      NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+}
+
+static
+HANDLE
+OpenLetter(WCHAR Letter)
+{
+    WCHAR Device[255];
+
+    /* Make name */
+    wsprintf(Device, L"\\\\.\\%c:", Letter);
+
+    /* And open */
+    return CreateFile(Device, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
+                      NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+}
+
+static
+VOID
+RefreshDevicesList(WCHAR Letter)
+{
+    HWND hListView;
+    WCHAR szFormat[50];
+    WCHAR szText[MAX_PATH + 50];
+    WCHAR szImage[MAX_PATH];
+    HANDLE hMaster, hLet;
+    DWORD BytesRead, i;
+    DRIVES_LIST Drives;
+    BOOLEAN Res;
+    IMAGE_PATH Image;
+    LVITEMW lvItem;
+    LRESULT lResult;
+    INT iSelected;
+
+    /* Get our list view */
+    hListView = GetDlgItem(hWnd, IDC_MAINDEVICES);
+
+    /* Purge it */
+    SendMessage(hListView, LVM_DELETEALLITEMS, 0, 0);
+
+    /* Now, query the driver for all the devices */
+    hMaster = OpenMaster();
+    if (hMaster != INVALID_HANDLE_VALUE)
+    {
+        Res = DeviceIoControl(hMaster, IOCTL_VCDROM_ENUMERATE_DRIVES, NULL, 0, &Drives, sizeof(Drives), &BytesRead, NULL);
+        CloseHandle(hMaster);
+
+        if (Res)
+        {
+            /* Loop to add all the devices to the list */
+            iSelected = -1;
+            for (i = 0; i < Drives.Count; ++i)
+            {
+                /* We'll query device one by one */
+                hLet = OpenLetter(Drives.Drives[i]);
+                if (hLet != INVALID_HANDLE_VALUE)
+                {
+                    /* Get info about the mounted image */
+                    Res = DeviceIoControl(hLet, IOCTL_VCDROM_GET_IMAGE_PATH, NULL, 0, &Image, sizeof(Image), &BytesRead, NULL);
+                    if (Res)
+                    {
+                        /* First of all, add our driver letter to the list */
+                        ZeroMemory(&lvItem, sizeof(LVITEMW));
+                        lvItem.mask = LVIF_TEXT;
+                        lvItem.pszText = szText;
+                        lvItem.iItem = i;
+                        szText[0] = Drives.Drives[i];
+                        szText[1] = L':';
+                        szText[2] = 0;
+
+                        /* If it worked, we'll complete with the info about the device:
+                         * (mounted? which image?)
+                         */
+                        lResult = SendMessage(hListView, LVM_INSERTITEM, 0, (LPARAM)&lvItem);
+                        if (lResult != -1)
+                        {
+                            /* If it matches arg, that's the letter to select at the end */
+                            if (Drives.Drives[i] == Letter)
+                            {
+                                iSelected = lResult;
+                            }
+
+                            /* We'll fill second column with info */
+                            lvItem.iSubItem = 1;
+
+                            /* Gather the image path */
+                            if (Image.Length != 0)
+                            {
+                                memcpy(szImage, Image.Path, Image.Length);
+                                szImage[(Image.Length / sizeof(WCHAR))] = L'\0';
+                            }
+
+                            /* It's not mounted... */
+                            if (Image.Mounted == 0)
+                            {
+                                /* If we don't have an image, set default text instead */
+                                if (Image.Length == 0)
+                                {
+                                    szImage[0] = 0;
+                                    LoadString(hInstance, IDS_NONE, szImage, sizeof(szImage) / sizeof(WCHAR));
+                                    szImage[(sizeof(szImage) / sizeof(WCHAR)) - 1] = L'\0';
+                                }
+
+                                /* Display the last known image */
+                                szFormat[0] = 0;
+                                LoadString(hInstance, IDS_NOMOUNTED, szFormat, sizeof(szFormat) / sizeof(WCHAR));
+                                szFormat[(sizeof(szFormat) / sizeof(WCHAR)) - 1] = L'\0';
+
+                                swprintf(szText, szFormat, szImage);
+                                lvItem.pszText = szText;
+                            }
+                            else
+                            {
+                                /* Mounted, just display the image path */
+                                lvItem.pszText = szImage;
+                            }
+
+                            /* Set text */
+                            SendMessage(hListView, LVM_SETITEM, lResult, (LPARAM)&lvItem);
+                        }
+                    }
+
+                    /* Don't leak our device */
+                    CloseHandle(hLet);
+                }
+            }
+
+            /* If we had something to select, then just do it */
+            if (iSelected != -1)
+            {
+                ZeroMemory(&lvItem, sizeof(LVITEMW));
+
+                lvItem.mask = LVIF_STATE;
+                lvItem.iItem = iSelected;
+                lvItem.state = LVIS_FOCUSED | LVIS_SELECTED;
+                lvItem.stateMask = LVIS_FOCUSED | LVIS_SELECTED;
+                SendMessage(hListView, LVM_SETITEMSTATE, iSelected, (LPARAM)&lvItem);
+            }
+        }
+    }
+}
+
+INT_PTR
+QueryDriverInfo(HWND hDlg)
+{
+    DWORD dwSize;
+    SC_HANDLE hMgr, hSvc;
+    LPQUERY_SERVICE_CONFIGW pConfig;
+    WCHAR szText[2 * MAX_PATH];
+    HWND hControl;
+
+    hDriverWnd = hDlg;
+
+    /* Open service manager */
+    hMgr = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT);
+    if (hMgr != NULL)
+    {
+        /* Open our service */
+        hSvc = OpenService(hMgr, L"Vcdrom", SERVICE_QUERY_CONFIG);
+        if (hSvc != NULL)
+        {
+            /* Probe its config size */
+            if (!QueryServiceConfig(hSvc, NULL, 0, &dwSize) &&
+                GetLastError() == ERROR_INSUFFICIENT_BUFFER)
+            {
+                /* And get its config */
+                pConfig = HeapAlloc(GetProcessHeap(), 0, dwSize);
+
+                if (QueryServiceConfig(hSvc, pConfig, dwSize, &dwSize))
+                {
+                    /* Display name & driver */
+                    wsprintf(szText, L"%s:\n(%s)", pConfig->lpDisplayName, pConfig->lpBinaryPathName);
+                    hControl = GetDlgItem(hDriverWnd, IDC_DRIVERINFO);
+                    SendMessage(hControl, WM_SETTEXT, 0, (LPARAM)szText);
+                }
+
+                HeapFree(GetProcessHeap(), 0, pConfig);
+            }
+
+            CloseServiceHandle(hSvc);
+        }
+
+        CloseServiceHandle(hMgr);
+    }
+
+    /* FIXME: we don't allow uninstall/install */
+    {
+        hControl = GetDlgItem(hDriverWnd, IDC_DRIVERINSTALL);
+        EnableWindow(hControl, FALSE);
+        hControl = GetDlgItem(hDriverWnd, IDC_DRIVERREMOVE);
+        EnableWindow(hControl, FALSE);
+    }
+
+    /* Display our sub window */
+    ShowWindow(hDlg, SW_SHOW);
+
+    return TRUE;
+}
+
+static
+VOID
+StartDriver(VOID)
+{
+    SC_HANDLE hMgr, hSvc;
+
+    /* Open the SC manager */
+    hMgr = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT);
+    if (hMgr != NULL)
+    {
+        /* Open the service matching our driver */
+        hSvc = OpenService(hMgr, L"Vcdrom", SERVICE_START);
+        if (hSvc != NULL)
+        {
+            /* Start it */
+            /* FIXME: improve */
+            StartService(hSvc, 0, NULL);
+
+            CloseServiceHandle(hSvc);
+
+            /* Refresh the list in case there were persistent mounts */
+            RefreshDevicesList(0);
+        }
+
+        CloseServiceHandle(hMgr);
+    }
+}
+
+static
+VOID
+StopDriver(VOID)
+{
+    SC_HANDLE hMgr, hSvc;
+    SERVICE_STATUS Status;
+
+    /* Open the SC manager */
+    hMgr = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT);
+    if (hMgr != NULL)
+    {
+        /* Open the service matching our driver */
+        hSvc = OpenService(hMgr, L"Vcdrom", SERVICE_STOP);
+        if (hSvc != NULL)
+        {
+            /* Stop it */
+            /* FIXME: improve */
+            ControlService(hSvc, SERVICE_CONTROL_STOP, &Status);
+
+            CloseServiceHandle(hSvc);
+
+            /* Refresh the list to clear it */
+            RefreshDevicesList(0);
+        }
+
+        CloseServiceHandle(hMgr);
+    }
+}
+
+static
+INT_PTR
+HandleDriverCommand(WPARAM wParam,
+                    LPARAM lParam)
+{
+    WORD Msg;
+
+    /* Dispatch the message for the controls we manage */
+    Msg = LOWORD(wParam);
+    switch (Msg)
+    {
+        case IDC_DRIVEROK:
+            DestroyWindow(hDriverWnd);
+            return TRUE;
+
+        case IDC_DRIVERSTART:
+            StartDriver();
+            return TRUE;
+
+        case IDC_DRIVERSTOP:
+            StopDriver();
+            return TRUE;
+    }
+
+    return FALSE;
+}
+
+static
+INT_PTR
+CALLBACK
+DriverDialogProc(HWND hDlg,
+                 UINT Message,
+                 WPARAM wParam,
+                 LPARAM lParam)
+{
+    /* Dispatch the message */
+    switch (Message)
+    {
+        case WM_INITDIALOG:
+            return QueryDriverInfo(hDlg);
+
+        case WM_COMMAND:
+            return HandleDriverCommand(wParam, lParam);
+
+        case WM_CLOSE:
+            return DestroyWindow(hDlg);
+    }
+
+    return FALSE;
+}
+
+static
+VOID
+DriverControl(VOID)
+{
+    /* Just create a new window with our driver control dialog */
+    CreateDialogParamW(hInstance,
+                       MAKEINTRESOURCE(IDD_DRIVERWINDOW),
+                       NULL,
+                       DriverDialogProc,
+                       0);
+}
+
+static
+INT_PTR
+SetMountFileName(HWND hDlg,
+                 LPARAM lParam)
+{
+    HWND hEditText;
+
+    hMountWnd = hDlg;
+
+    /* Set the file name that was passed when creating dialog */
+    hEditText = GetDlgItem(hMountWnd, IDC_MOUNTIMAGE);
+    SendMessage(hEditText, WM_SETTEXT, 0, lParam);
+
+    /* FIXME: we don't support persistent mounts yet*/
+    {
+        hEditText = GetDlgItem(hMountWnd, IDC_MOUNTPERSIST);
+        EnableWindow(hEditText, FALSE);
+    }
+
+    /* Show our window */
+    ShowWindow(hDlg, SW_SHOW);
+
+    return TRUE;
+}
+
+FORCEINLINE
+DWORD
+Min(DWORD a, DWORD b)
+{
+    return (a > b ? b : a);
+}
+
+static
+VOID
+PerformMount(VOID)
+{
+    HWND hControl;
+    WCHAR szFileName[MAX_PATH];
+    MOUNT_PARAMETERS MountParams;
+    UNICODE_STRING NtPathName;
+    HANDLE hLet;
+    DWORD BytesRead;
+
+    /* Zero our input structure */
+    ZeroMemory(&MountParams, sizeof(MOUNT_PARAMETERS));
+
+    /* Do we have to suppress UDF? */
+    hControl = GetDlgItem(hMountWnd, IDC_MOUNTUDF);
+    if (SendMessage(hControl, BM_GETCHECK, 0, 0) == BST_CHECKED)
+    {
+        MountParams.Flags |= MOUNT_FLAG_SUPP_UDF;
+    }
+
+    /* Do we have to suppress Joliet? */
+    hControl = GetDlgItem(hMountWnd, IDC_MOUNTJOLIET);
+    if (SendMessage(hControl, BM_GETCHECK, 0, 0) == BST_CHECKED)
+    {
+        MountParams.Flags |= MOUNT_FLAG_SUPP_JOLIET;
+    }
+
+    /* Get the file name */
+    hControl = GetDlgItem(hMountWnd, IDC_MOUNTIMAGE);
+    GetWindowText(hControl, szFileName, sizeof(szFileName) / sizeof(WCHAR));
+
+    /* Get NT path for the driver */
+    if (RtlDosPathNameToNtPathName_U(szFileName, &NtPathName, NULL, NULL))
+    {
+        /* Copy it in the parameter structure */
+        wcsncpy(MountParams.Path, NtPathName.Buffer, 255);
+        MountParams.Length = Min(NtPathName.Length, 255 * sizeof(WCHAR));
+        RtlFreeHeap(RtlGetProcessHeap(), 0, NtPathName.Buffer);
+
+        /* Open the device */
+        hLet = OpenLetter(wMountLetter);
+        if (hLet != INVALID_HANDLE_VALUE)
+        {
+            /* And issue the mount IOCTL */
+            DeviceIoControl(hLet, IOCTL_VCDROM_MOUNT_IMAGE, &MountParams, sizeof(MountParams), NULL, 0, &BytesRead, NULL);
+
+            CloseHandle(hLet);
+
+            /* Refresh the list so that our mount appears */
+            RefreshDevicesList(0);
+        }
+    }
+
+    DestroyWindow(hMountWnd);
+}
+
+static
+INT_PTR
+HandleMountCommand(WPARAM wParam,
+                   LPARAM lParam)
+{
+    WORD Msg;
+
+    /* Dispatch the message for the controls we manage */
+    Msg = LOWORD(wParam);
+    switch (Msg)
+    {
+        case IDC_MOUNTCANCEL:
+            DestroyWindow(hMountWnd);
+            return TRUE;
+
+        case IDC_MOUNTOK:
+            PerformMount();
+            return TRUE;
+    }
+
+    return FALSE;
+}
+
+static
+INT_PTR
+CALLBACK
+MountDialogProc(HWND hDlg,
+                UINT Message,
+                WPARAM wParam,
+                LPARAM lParam)
+{
+    /* Dispatch the message */
+    switch (Message)
+    {
+        case WM_INITDIALOG:
+            return SetMountFileName(hDlg, lParam);
+
+        case WM_COMMAND:
+            return HandleMountCommand(wParam, lParam);
+
+        case WM_CLOSE:
+            return DestroyWindow(hDlg);
+    }
+
+    return FALSE;
+}
+
+static
+VOID
+AddDrive(VOID)
+{
+    WCHAR Letter;
+    BOOLEAN Res;
+    DWORD BytesRead;
+    HANDLE hMaster;
+
+    /* Open the driver */
+    hMaster = OpenMaster();
+    if (hMaster != INVALID_HANDLE_VALUE)
+    {
+        /* Issue the create IOCTL */
+        Res = DeviceIoControl(hMaster, IOCTL_VCDROM_CREATE_DRIVE, NULL, 0, &Letter, sizeof(WCHAR), &BytesRead, NULL);
+        CloseHandle(hMaster);
+
+        /* If it failed, reset the drive letter */
+        if (!Res)
+        {
+            Letter = 0;
+        }
+
+        /* Refresh devices list. If it succeed, we pass the created drive letter
+         * So that, user can directly click on "mount" to mount an image, without
+         * needing to select appropriate device.
+         */
+        RefreshDevicesList(Letter);
+    }
+}
+
+static
+WCHAR
+GetSelectedDriveLetter(VOID)
+{
+    INT iItem;
+    HWND hListView;
+    LVITEM lvItem;
+    WCHAR szText[255];
+
+    /* Get the select device in the list view */
+    hListView = GetDlgItem(hWnd, IDC_MAINDEVICES);
+    iItem = SendMessage(hListView, LVM_GETNEXTITEM, -1, LVNI_SELECTED);
+    /* If there's one... */
+    if (iItem != -1)
+    {
+        ZeroMemory(&lvItem, sizeof(LVITEM));
+        lvItem.pszText = szText;
+        lvItem.cchTextMax = sizeof(szText) / sizeof(WCHAR);
+        szText[0] = 0;
+
+        /* Get the item text, it will be the drive letter */
+        SendMessage(hListView, LVM_GETITEMTEXT, iItem, (LPARAM)&lvItem);
+        return szText[0];
+    }
+
+    /* Nothing selected */
+    return 0;
+}
+
+static
+VOID
+MountImage(VOID)
+{
+    WCHAR szFilter[255];
+    WCHAR szFileName[MAX_PATH];
+    OPENFILENAMEW ImageOpen;
+
+    /* Get the selected drive letter
+     * FIXME: I make it global, because I don't know how to pass
+     * it properly to the later involved functions.
+     * Feel free to improve (without breaking ;-))
+     */
+    wMountLetter = GetSelectedDriveLetter();
+    /* We can only mount if we have a device */
+    if (wMountLetter != 0)
+    {
+        /* First of all, we need an image to mount */
+        ZeroMemory(&ImageOpen, sizeof(OPENFILENAMEW));
+
+        ImageOpen.lStructSize = sizeof(ImageOpen);
+        ImageOpen.hwndOwner = NULL;
+        ImageOpen.lpstrFilter = szFilter;
+        ImageOpen.lpstrFile = szFileName;
+        ImageOpen.nMaxFile = MAX_PATH;
+        ImageOpen.Flags = OFN_EXPLORER| OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
+
+        /* Get our filter (only supported images) */
+        szFileName[0] = 0;
+        szFilter[0] = 0;
+        LoadString(hInstance, IDS_FILTER, szFilter, sizeof(szFilter) / sizeof(WCHAR));
+        szFilter[(sizeof(szFilter) / sizeof(WCHAR)) - 1] = L'\0';
+
+        /* Get the image name */
+        if (!GetOpenFileName(&ImageOpen))
+        {
+            /* The user canceled... */
+            return;
+        }
+
+        /* Start the mount dialog, so that user can select mount options */
+        CreateDialogParamW(hInstance,
+                           MAKEINTRESOURCE(IDD_MOUNTWINDOW),
+                           NULL,
+                           MountDialogProc,
+                           (LPARAM)szFileName);
+    }
+}
+
+static
+VOID
+RemountImage(VOID)
+{
+    WCHAR Letter;
+    HANDLE hLet;
+    DWORD BytesRead;
+
+    /* Get the select drive letter */
+    Letter = GetSelectedDriveLetter();
+    if (Letter != 0)
+    {
+        /* Open it */
+        hLet = OpenLetter(Letter);
+        if (hLet != INVALID_HANDLE_VALUE)
+        {
+            /* And ask the driver for a remount */
+            DeviceIoControl(hLet, IOCTL_STORAGE_LOAD_MEDIA, NULL, 0, NULL, 0, &BytesRead, NULL);
+
+            CloseHandle(hLet);
+
+            /* Refresh the list, to display the fact the image is now mounted.
+             * Make sure it's selected as it was previously selected.
+             */
+            RefreshDevicesList(Letter);
+        }
+    }
+}
+
+static
+VOID
+EjectDrive(VOID)
+{
+    WCHAR Letter;
+    HANDLE hLet;
+    DWORD BytesRead;
+
+    /* Get the select drive letter */
+    Letter = GetSelectedDriveLetter();
+    if (Letter != 0)
+    {
+        /* Open it */
+        hLet = OpenLetter(Letter);
+        if (hLet != INVALID_HANDLE_VALUE)
+        {
+            /* And ask the driver for an ejection */
+            DeviceIoControl(hLet, IOCTL_CDROM_EJECT_MEDIA, NULL, 0, NULL, 0, &BytesRead, NULL);
+
+            CloseHandle(hLet);
+
+            /* Refresh the list, to display the fact the image is now unmounted but
+             * still known by the driver
+             * Make sure it's selected as it was previously selected.
+             */
+            RefreshDevicesList(Letter);
+        }
+    }
+}
+
+static
+VOID
+RemoveDrive(VOID)
+{
+    WCHAR Letter;
+    HANDLE hLet;
+    DWORD BytesRead;
+
+    /* Get the select drive letter */
+    Letter = GetSelectedDriveLetter();
+    if (Letter != 0)
+    {
+        /* Open it */
+        hLet = OpenLetter(Letter);
+        if (hLet != INVALID_HANDLE_VALUE)
+        {
+            /* And ask the driver for a deletion */
+            DeviceIoControl(hLet, IOCTL_VCDROM_DELETE_DRIVE, NULL, 0, NULL, 0, &BytesRead, NULL);
+
+            CloseHandle(hLet);
+
+            /* Refresh the list, to make the device disappear */
+            RefreshDevicesList(0);
+        }
+    }
+}
+
+static
+INT_PTR
+HandleCommand(WPARAM wParam,
+              LPARAM lParam)
+{
+    WORD Msg;
+
+    /* Dispatch the message for the controls we manage */
+    Msg = LOWORD(wParam);
+    switch (Msg)
+    {
+        case IDC_MAINCONTROL:
+            DriverControl();
+            return TRUE;
+
+        case IDC_MAINOK:
+            DestroyWindow(hWnd);
+            return TRUE;
+
+        case IDC_MAINADD:
+            AddDrive();
+            return TRUE;
+
+        case IDC_MAINMOUNT:
+            MountImage();
+            return TRUE;
+
+        case IDC_MAINREMOUNT:
+            RemountImage();
+            return TRUE;
+
+        case IDC_MAINEJECT:
+            EjectDrive();
+            return TRUE;
+
+        case IDC_MAINREMOVE:
+            RemoveDrive();
+            return TRUE;
+    }
+
+    return FALSE;
+}
+
+static
+VOID ResetStats(VOID)
+{
+    HWND hEditText;
+    static const WCHAR szText[] = { L'0', 0 };
+
+    /* Simply set '0' in all the edittext controls we
+     * manage regarding statistics.
+     */
+    hEditText = GetDlgItem(hWnd, IDC_MAINSECTORS);
+    SendMessage(hEditText, WM_SETTEXT, 0, (LPARAM)szText);
+
+    hEditText = GetDlgItem(hWnd, IDC_MAINSIZE);
+    SendMessage(hEditText, WM_SETTEXT, 0, (LPARAM)szText);
+
+    hEditText = GetDlgItem(hWnd, IDC_MAINFREE);
+    SendMessage(hEditText, WM_SETTEXT, 0, (LPARAM)szText);
+
+    hEditText = GetDlgItem(hWnd, IDC_MAINTOTAL);
+    SendMessage(hEditText, WM_SETTEXT, 0, (LPARAM)szText);
+}
+
+static
+INT_PTR
+HandleNotify(LPARAM lParam)
+{
+    WCHAR Letter;
+    LPNMHDR NmHdr;
+    WCHAR szText[255];
+    HWND hEditText;
+    DWORD ClusterSector, SectorSize, FreeClusters, Clusters, Sectors;
+
+    NmHdr = (LPNMHDR)lParam;
+
+    /* We only want notifications on click on our devices list */
+    if (NmHdr->code == NM_CLICK &&
+        NmHdr->idFrom == IDC_MAINDEVICES)
+    {
+        /* Get the newly selected device */
+        Letter = GetSelectedDriveLetter();
+        if (Letter != 0)
+        {
+            /* Setup its name */
+            szText[0] = Letter;
+            szText[1] = L':';
+            szText[2] = 0;
+
+            /* And get its capacities */
+            if (GetDiskFreeSpace(szText, &ClusterSector, &SectorSize, &FreeClusters, &Clusters))
+            {
+                /* Nota: the application returns the total amount of clusters and sectors
+                 * So, compute it
+                 */
+                Sectors = ClusterSector * Clusters;
+
+                /* And now, update statistics about the device */
+                hEditText = GetDlgItem(hWnd, IDC_MAINSECTORS);
+                wsprintf(szText, L"%ld", Sectors);
+                SendMessage(hEditText, WM_SETTEXT, 0, (LPARAM)szText);
+
+                hEditText = GetDlgItem(hWnd, IDC_MAINSIZE);
+                wsprintf(szText, L"%ld", SectorSize);
+                SendMessage(hEditText, WM_SETTEXT, 0, (LPARAM)szText);
+
+                hEditText = GetDlgItem(hWnd, IDC_MAINFREE);
+                wsprintf(szText, L"%ld", FreeClusters);
+                SendMessage(hEditText, WM_SETTEXT, 0, (LPARAM)szText);
+
+                hEditText = GetDlgItem(hWnd, IDC_MAINTOTAL);
+                wsprintf(szText, L"%ld", Clusters);
+                SendMessage(hEditText, WM_SETTEXT, 0, (LPARAM)szText);
+
+                return TRUE;
+            }
+        }
+
+        /* We failed somewhere, make sure we're at 0 */
+        ResetStats();
+
+        return TRUE;
+    }
+
+    return FALSE;
+}
+
+static
+INT_PTR
+CreateListViewColumns(HWND hDlg)
+{
+    WCHAR szText[255];
+    LVCOLUMNW lvColumn;
+    HWND hListView;
+
+    hWnd = hDlg;
+    hListView = GetDlgItem(hDlg, IDC_MAINDEVICES);
+
+    /* Select the whole line, not just the first column */
+    SendMessage(hListView, LVM_SETEXTENDEDLISTVIEWSTYLE, 0, LVS_EX_FULLROWSELECT);
+
+    /* Set up the first column */
+    ZeroMemory(&lvColumn, sizeof(LVCOLUMNW));
+    lvColumn.pszText = szText;
+    lvColumn.mask = LVCF_FMT | LVCF_TEXT | LVCF_SUBITEM | LVCF_WIDTH;
+    lvColumn.fmt = LVCFMT_LEFT;
+    lvColumn.cx = 100;
+    szText[0] = 0;
+    LoadString(hInstance, IDS_DRIVE, szText, sizeof(szText) / sizeof(WCHAR));
+    szText[(sizeof(szText) / sizeof(WCHAR)) - 1] = L'\0';
+    SendMessage(hListView, LVM_INSERTCOLUMNW, 0, (LPARAM)&lvColumn);
+
+    /* Set up the second column */
+    szText[0] = 0;
+    lvColumn.cx = 350;
+    LoadString(hInstance, IDS_MAPPEDIMAGE, szText, sizeof(szText) / sizeof(WCHAR));
+    szText[(sizeof(szText) / sizeof(WCHAR)) - 1] = L'\0';
+    SendMessage(hListView, LVM_INSERTCOLUMNW, 1, (LPARAM)&lvColumn);
+
+    /* Make sure stats are at 0 */
+    ResetStats();
+
+    /* And populate our device list */
+    RefreshDevicesList(0);
+
+    return TRUE;
+}
+
+static
+INT_PTR
+CALLBACK
+MainDialogProc(HWND hDlg,
+               UINT Message,
+               WPARAM wParam,
+               LPARAM lParam)
+{
+    /* Dispatch the message */
+    switch (Message)
+    {
+        case WM_INITDIALOG:
+            return CreateListViewColumns(hDlg);
+
+        case WM_COMMAND:
+            return HandleCommand(wParam, lParam);
+
+        case WM_NOTIFY:
+            return HandleNotify(lParam);
+
+        case WM_CLOSE:
+            return DestroyWindow(hDlg);
+
+        case WM_DESTROY:
+            PostQuitMessage(0);
+            return TRUE;
+    }
+
+    return FALSE;
+}
+
+INT
+WINAPI
+wWinMain(HINSTANCE hInst,
+         HINSTANCE hPrev,
+         LPWSTR Cmd,
+         int iCmd)
+{
+    MSG Msg;
+
+    hInstance = hInst;
+
+    /* Just start our main window */
+    hWnd = CreateDialogParamW(hInst,
+                              MAKEINTRESOURCE(IDD_MAINWINDOW),
+                              NULL,
+                              MainDialogProc,
+                              0);
+    /* And dispatch messages in case of a success */
+    if (hWnd != NULL)
+    {
+        while (GetMessageW(&Msg, NULL, 0, 0) != 0)
+        {
+            TranslateMessage(&Msg);
+            DispatchMessageW(&Msg);
+        }
+    }
+
+    return 0;
+}
diff --git a/modules/rosapps/applications/vcdcontroltool/vcdcontroltool.rc b/modules/rosapps/applications/vcdcontroltool/vcdcontroltool.rc
new file mode 100644 (file)
index 0000000..d52dea4
--- /dev/null
@@ -0,0 +1,21 @@
+#include <windef.h>
+#include <winuser.h>
+#include <commctrl.h>
+
+#include "resource.h"
+
+LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
+
+#define REACTOS_STR_FILE_DESCRIPTION  "Virtual CD Control Tool"
+#define REACTOS_STR_INTERNAL_NAME     "vcdcontroltool"
+#define REACTOS_STR_ORIGINAL_FILENAME "vcdcontroltool.exe"
+#include <reactos/version.rc>
+
+#include <reactos/manifest_exe.rc>
+
+/* UTF-8 */
+#pragma code_page(65001)
+
+#ifdef LANGUAGE_EN_US
+    #include "lang/en-US.rc"
+#endif