diff --git a/example/recycle_bin.dart b/example/recycle_bin.dart new file mode 100644 index 0000000000..76afb8014f --- /dev/null +++ b/example/recycle_bin.dart @@ -0,0 +1,92 @@ +// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +// Explores the Windows Recycle Bin. + +// ignore_for_file: constant_identifier_names + +import 'dart:ffi'; +import 'dart:io'; + +import 'package:ffi/ffi.dart'; +import 'package:win32/win32.dart'; + +class RecycleBinInfo { + final int itemCount; + final int totalSizeInBytes; + + const RecycleBinInfo(this.itemCount, this.totalSizeInBytes); +} + +RecycleBinInfo queryRecycleBin(String rootPath) { + final pszRootPath = rootPath.toNativeUtf16(); + final pSHQueryRBInfo = calloc() + ..ref.cbSize = sizeOf(); + + try { + final hr = SHQueryRecycleBin(pszRootPath, pSHQueryRBInfo); + if (hr != S_OK) throw WindowsException(hr); + + return RecycleBinInfo( + pSHQueryRBInfo.ref.i64NumItems, pSHQueryRBInfo.ref.i64Size); + } finally { + free(pszRootPath); + free(pSHQueryRBInfo); + } +} + +String getTempFileName() { + final lpPathName = '.'.toNativeUtf16(); + final lpPrefixString = 'dart'.toNativeUtf16(); + final lpTempFileName = wsalloc(MAX_PATH); + + try { + final result = + GetTempFileName(lpPathName, lpPrefixString, 0, lpTempFileName); + if (result == 0) throw 'Unable to create filename'; + + return lpTempFileName.toDartString(); + } finally { + free(lpPathName); + free(lpPrefixString); + free(lpTempFileName); + } +} + +bool recycleFile(String file) { + final hwnd = GetActiveWindow(); + final pFrom = [file].toWideCharArray(); + final lpFileOp = calloc() + ..ref.hwnd = hwnd + ..ref.wFunc = FO_DELETE + ..ref.pFrom = pFrom + ..ref.pTo = nullptr + ..ref.fFlags = FOF_ALLOWUNDO; + + try { + final result = SHFileOperation(lpFileOp); + return result == 0; + } finally { + free(pFrom); + free(lpFileOp); + } +} + +void main(List args) { + final info = queryRecycleBin('c:\\'); + print('There are ${info.itemCount} items in the ' + 'Recycle Bin on the C: drive.'); + + final tempFile = getTempFileName(); + print('Creating temporary file $tempFile'); + File(tempFile) + .writeAsStringSync('With time involved, everything is temporary.'); + + print('Sending temporary file $tempFile to the Recycle Bin.'); + recycleFile(tempFile); + + final newInfo = queryRecycleBin('c:\\'); + print('There now are ${newInfo.itemCount} items in the ' + 'Recycle Bin on the C: drive.'); +} diff --git a/lib/src/constants.dart b/lib/src/constants.dart index 428133336f..98673b522f 100644 --- a/lib/src/constants.dart +++ b/lib/src/constants.dart @@ -7637,6 +7637,83 @@ const SHUTDOWN_RESTARTAPPS = 0x0000080; /// options for the shutdown. const SHUTDOWN_HYBRID = 0x0000200; +// ----------------------------------------------------------------------------- +// Shell File operation constants +// ----------------------------------------------------------------------------- + +/// Move the files specified in pFrom to the location specified in pTo. +const FO_MOVE = 0x0001; + +/// Copy the files specified in the pFrom member to the location specified in +/// the pTo member. +const FO_COPY = 0x0002; + +/// Delete the files specified in pFrom. +const FO_DELETE = 0x0003; + +/// Rename the file specified in pFrom. You cannot use this flag to rename +/// multiple files with a single function call. Use FO_MOVE instead. +const FO_RENAME = 0x0004; + +/// The pTo member specifies multiple destination files (one for each source +/// file in pFrom) rather than one directory where all source files are to be +/// deposited. +const FOF_MULTIDESTFILES = 0x0001; + +/// Do not display a progress dialog box. +const FOF_SILENT = 0x0004; + +/// Give the file being operated on a new name in a move, copy, or rename +/// operation if a file with the target name already exists at the destination. +const FOF_RENAMEONCOLLISION = 0x0008; + +/// Respond with Yes to All for any dialog box that is displayed. +const FOF_NOCONFIRMATION = 0x0010; + +/// If FOF_RENAMEONCOLLISION is specified and any files were renamed, assign a +/// name mapping object that contains their old and new names to the +/// hNameMappings member. This object must be freed using SHFreeNameMappings +/// when it is no longer needed. +const FOF_WANTMAPPINGHANDLE = 0x0020; + +/// Preserve undo information, if possible. +const FOF_ALLOWUNDO = 0x0040; + +/// Perform the operation only on files (not on folders) if a wildcard file name +/// (.) is specified. +const FOF_FILESONLY = 0x0080; + +/// Display a progress dialog box but do not show individual file names as they +/// are operated on. +const FOF_SIMPLEPROGRESS = 0x0100; + +/// Do not ask the user to confirm the creation of a new directory if the +/// operation requires one to be created. +const FOF_NOCONFIRMMKDIR = 0x0200; + +/// Do not display a dialog to the user if an error occurs. +const FOF_NOERRORUI = 0x0400; + +/// Do not copy the security attributes of the file. The destination file +/// receives the security attributes of its new folder. +const FOF_NOCOPYSECURITYATTRIBS = 0x0800; + +/// Only perform the operation in the local directory. Do not operate +/// recursively into subdirectories, which is the default behavior. +const FOF_NORECURSION = 0x1000; + +/// Do not move connected files as a group. Only move the specified files. +const FOF_NO_CONNECTED_ELEMENTS = 0x2000; + +/// Send a warning if a file is being permanently destroyed during a delete +/// operation rather than recycled. This flag partially overrides +/// FOF_NOCONFIRMATION. +const FOF_WANTNUKEWARNING = 0x4000; + +/// Perform the operation silently, presenting no UI to the user. +const FOF_NO_UI = + FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_NOCONFIRMMKDIR; + // ----------------------------------------------------------------------------- // Shell_NotifyIcon uFlags constants // ----------------------------------------------------------------------------- diff --git a/lib/src/structs.g.dart b/lib/src/structs.g.dart index 83ab21f1f1..2361a0947e 100644 --- a/lib/src/structs.g.dart +++ b/lib/src/structs.g.dart @@ -7298,6 +7298,84 @@ class SHELL_ITEM_RESOURCE extends Struct { } } +/// Contains information about a file object. +/// +/// {@category Struct} +class SHFILEINFO extends Struct { + @IntPtr() + external int hIcon; + + @Int32() + external int iIcon; + + @Uint32() + external int dwAttributes; + + @Array(260) + external Array _szDisplayName; + + String get szDisplayName { + final charCodes = []; + for (var i = 0; i < 260; i++) { + if (_szDisplayName[i] == 0x00) break; + charCodes.add(_szDisplayName[i]); + } + return String.fromCharCodes(charCodes); + } + + set szDisplayName(String value) { + final stringToStore = value.padRight(260, '\x00'); + for (var i = 0; i < 260; i++) { + _szDisplayName[i] = stringToStore.codeUnitAt(i); + } + } + + @Array(80) + external Array _szTypeName; + + String get szTypeName { + final charCodes = []; + for (var i = 0; i < 80; i++) { + if (_szTypeName[i] == 0x00) break; + charCodes.add(_szTypeName[i]); + } + return String.fromCharCodes(charCodes); + } + + set szTypeName(String value) { + final stringToStore = value.padRight(80, '\x00'); + for (var i = 0; i < 80; i++) { + _szTypeName[i] = stringToStore.codeUnitAt(i); + } + } +} + +/// Contains information that the SHFileOperation function uses to perform +/// file operations. +/// +/// {@category Struct} +class SHFILEOPSTRUCT extends Struct { + @IntPtr() + external int hwnd; + + @Uint32() + external int wFunc; + + external Pointer pFrom; + + external Pointer pTo; + + @Uint16() + external int fFlags; + + @Int32() + external int fAnyOperationsAborted; + + external Pointer hNameMappings; + + external Pointer lpszProgressTitle; +} + /// Defines an item identifier. /// /// {@category Struct} diff --git a/lib/src/win32/shell32.g.dart b/lib/src/win32/shell32.g.dart index cc423d4e39..427f8fda61 100644 --- a/lib/src/win32/shell32.g.dart +++ b/lib/src/win32/shell32.g.dart @@ -216,6 +216,37 @@ final _SHEmptyRecycleBin = _shell32.lookupFunction< int Function(int hwnd, Pointer pszRootPath, int dwFlags)>('SHEmptyRecycleBinW'); +/// Copies, moves, renames, or deletes a file system object. +/// +/// ```c +/// int SHFileOperationW( +/// LPSHFILEOPSTRUCTW lpFileOp +/// ); +/// ``` +/// {@category shell32} +int SHFileOperation(Pointer lpFileOp) => + _SHFileOperation(lpFileOp); + +final _SHFileOperation = _shell32.lookupFunction< + Int32 Function(Pointer lpFileOp), + int Function(Pointer lpFileOp)>('SHFileOperationW'); + +/// Frees a file name mapping object that was retrieved by the +/// SHFileOperation function. +/// +/// ```c +/// void SHFreeNameMappings( +/// HANDLE hNameMappings +/// ); +/// ``` +/// {@category shell32} +void SHFreeNameMappings(int hNameMappings) => + _SHFreeNameMappings(hNameMappings); + +final _SHFreeNameMappings = _shell32.lookupFunction< + Void Function(IntPtr hNameMappings), + void Function(int hNameMappings)>('SHFreeNameMappings'); + /// Retrieves the IShellFolder interface for the desktop folder, which is /// the root of the Shell's namespace. /// @@ -280,6 +311,33 @@ final _SHGetDriveMedia = _shell32.lookupFunction< int Function(Pointer pszDrive, Pointer pdwMediaContent)>('SHGetDriveMedia'); +/// Retrieves information about an object in the file system, such as a +/// file, folder, directory, or drive root. +/// +/// ```c +/// DWORD_PTR SHGetFileInfoW( +/// LPCWSTR pszPath, +/// DWORD dwFileAttributes, +/// SHFILEINFOW *psfi, +/// UINT cbFileInfo, +/// UINT uFlags +/// ); +/// ``` +/// {@category shell32} +int SHGetFileInfo(Pointer pszPath, int dwFileAttributes, + Pointer psfi, int cbFileInfo, int uFlags) => + _SHGetFileInfo(pszPath, dwFileAttributes, psfi, cbFileInfo, uFlags); + +final _SHGetFileInfo = _shell32.lookupFunction< + IntPtr Function(Pointer pszPath, Uint32 dwFileAttributes, + Pointer psfi, Uint32 cbFileInfo, Int32 uFlags), + int Function( + Pointer pszPath, + int dwFileAttributes, + Pointer psfi, + int cbFileInfo, + int uFlags)>('SHGetFileInfoW'); + /// Gets the path of a folder identified by a CSIDL value. /// /// ```c diff --git a/test/api_test.dart b/test/api_test.dart index 10b0058773..79f4fa803a 100644 --- a/test/api_test.dart +++ b/test/api_test.dart @@ -9842,6 +9842,20 @@ void main() { int dwFlags)>('SHEmptyRecycleBinW'); expect(SHEmptyRecycleBin, isA()); }); + test('Can instantiate SHFileOperation', () { + final shell32 = DynamicLibrary.open('shell32.dll'); + final SHFileOperation = shell32.lookupFunction< + Int32 Function(Pointer lpFileOp), + int Function(Pointer lpFileOp)>('SHFileOperationW'); + expect(SHFileOperation, isA()); + }); + test('Can instantiate SHFreeNameMappings', () { + final shell32 = DynamicLibrary.open('shell32.dll'); + final SHFreeNameMappings = shell32.lookupFunction< + Void Function(IntPtr hNameMappings), + void Function(int hNameMappings)>('SHFreeNameMappings'); + expect(SHFreeNameMappings, isA()); + }); test('Can instantiate SHGetDesktopFolder', () { final shell32 = DynamicLibrary.open('shell32.dll'); final SHGetDesktopFolder = shell32.lookupFunction< @@ -9875,6 +9889,19 @@ void main() { Pointer pdwMediaContent)>('SHGetDriveMedia'); expect(SHGetDriveMedia, isA()); }); + test('Can instantiate SHGetFileInfo', () { + final shell32 = DynamicLibrary.open('shell32.dll'); + final SHGetFileInfo = shell32.lookupFunction< + IntPtr Function(Pointer pszPath, Uint32 dwFileAttributes, + Pointer psfi, Uint32 cbFileInfo, Int32 uFlags), + int Function( + Pointer pszPath, + int dwFileAttributes, + Pointer psfi, + int cbFileInfo, + int uFlags)>('SHGetFileInfoW'); + expect(SHGetFileInfo, isA()); + }); test('Can instantiate SHGetFolderPath', () { final shell32 = DynamicLibrary.open('shell32.dll'); final SHGetFolderPath = shell32.lookupFunction< diff --git a/tool/generate.cmd b/tool/generate.cmd index 9c92d5187e..8c81e02376 100644 --- a/tool/generate.cmd +++ b/tool/generate.cmd @@ -17,7 +17,9 @@ call dart test echo Running generated file tests... cd ..\.. rem Now should be in win32 -call dart test + +rem Single threaded increases chances of detecting a segfault test failure +call dart test --concurrency=1 --test-randomize-ordering-seed=random goto end diff --git a/tool/generator/data/win32_functions.json b/tool/generator/data/win32_functions.json index 4d8ff309e3..b28d40c9f7 100644 --- a/tool/generator/data/win32_functions.json +++ b/tool/generator/data/win32_functions.json @@ -4921,6 +4921,14 @@ "prototype": "SHSTDAPI SHEmptyRecycleBinW(\n HWND hwnd,\n LPCWSTR pszRootPath,\n DWORD dwFlags\n);", "comment": "Empties the Recycle Bin on the specified drive." }, + "SHFileOperation": { + "prototype": "int SHFileOperationW(\n LPSHFILEOPSTRUCTW lpFileOp\n);", + "comment": "Copies, moves, renames, or deletes a file system object." + }, + "SHFreeNameMappings": { + "prototype": "void SHFreeNameMappings(\n HANDLE hNameMappings\n);", + "comment": "Frees a file name mapping object that was retrieved by the SHFileOperation function." + }, "SHGetDesktopFolder": { "prototype": "SHSTDAPI SHGetDesktopFolder(\n IShellFolder **ppshf\n);", "comment": "Retrieves the IShellFolder interface for the desktop folder, which is the root of the Shell's namespace." @@ -4933,6 +4941,10 @@ "prototype": "HRESULT SHGetDriveMedia(\n PCWSTR pszDrive,\n DWORD *pdwMediaContent\n);", "comment": "Returns the type of media that is in the given drive." }, + "SHGetFileInfo": { + "prototype": "DWORD_PTR SHGetFileInfoW(\n LPCWSTR pszPath,\n DWORD dwFileAttributes,\n SHFILEINFOW *psfi,\n UINT cbFileInfo,\n UINT uFlags\n);", + "comment": "Retrieves information about an object in the file system, such as a file, folder, directory, or drive root." + }, "SHGetFolderPath": { "prototype": "SHFOLDERAPI SHGetFolderPathW(\n HWND hwnd,\n int csidl,\n HANDLE hToken,\n DWORD dwFlags,\n LPWSTR pszPath\n);", "comment": "Gets the path of a folder identified by a CSIDL value." diff --git a/tool/generator/data/win32_structs.json b/tool/generator/data/win32_structs.json index 0e1dabb745..4cfb9d5982 100644 --- a/tool/generator/data/win32_structs.json +++ b/tool/generator/data/win32_structs.json @@ -308,6 +308,8 @@ "Windows.Win32.UI.Shell.PropertiesSystem.PROPERTYKEY": "Specifies the FMTID/PID identifier that programmatically identifies a property.", "Windows.Win32.UI.Shell.SHELLEXECUTEINFOW": "Contains information used by ShellExecuteEx.", "Windows.Win32.UI.Shell.SHELL_ITEM_RESOURCE": "Defines Shell item resource.", + "Windows.Win32.UI.Shell.SHFILEINFOW": "Contains information about a file object.", + "Windows.Win32.UI.Shell.SHFILEOPSTRUCTW": "Contains information that the SHFileOperation function uses to perform file operations.", "Windows.Win32.UI.Shell.SHQUERYRBINFO": "Contains the size and item count information retrieved by the SHQueryRecycleBin function.", "Windows.Win32.UI.WindowsAndMessaging.ACCEL": "Defines an accelerator key used in an accelerator table.", "Windows.Win32.UI.WindowsAndMessaging.ALTTABINFO": "Contains status information for the application-switching (ALT+TAB) window.", diff --git a/tool/struct_sizes/struct_sizes.cpp b/tool/struct_sizes/struct_sizes.cpp index 91df4f9470..a9735bbbe4 100644 --- a/tool/struct_sizes/struct_sizes.cpp +++ b/tool/struct_sizes/struct_sizes.cpp @@ -306,6 +306,8 @@ void main() printf(" 'SERVENT': %zu,\n", sizeof(SERVENT)); printf(" 'SHELLEXECUTEINFO': %zu,\n", sizeof(SHELLEXECUTEINFOW)); printf(" 'SHELL_ITEM_RESOURCE': %zu,\n", sizeof(SHELL_ITEM_RESOURCE)); + printf(" 'SHFILEINFO': %zu,\n", sizeof(SHFILEINFOW)); + printf(" 'SHFILEOPSTRUCT': %zu,\n", sizeof(SHFILEOPSTRUCTW)); printf(" 'SHITEMID': %zu,\n", sizeof(SHITEMID)); printf(" 'SHQUERYRBINFO': %zu,\n", sizeof(SHQUERYRBINFO)); printf(" 'SIZE': %zu,\n", sizeof(SIZE));