Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add extra shell file operation APIs #630

Merged
merged 3 commits into from Dec 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
92 changes: 92 additions & 0 deletions 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<SHQUERYRBINFO>()
..ref.cbSize = sizeOf<SHQUERYRBINFO>();

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<SHFILEOPSTRUCT>()
..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<String> 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.');
}
77 changes: 77 additions & 0 deletions lib/src/constants.dart
Expand Up @@ -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
// -----------------------------------------------------------------------------
Expand Down
78 changes: 78 additions & 0 deletions lib/src/structs.g.dart
Expand Up @@ -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<Uint16> _szDisplayName;

String get szDisplayName {
final charCodes = <int>[];
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<Uint16> _szTypeName;

String get szTypeName {
final charCodes = <int>[];
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<Utf16> pFrom;

external Pointer<Utf16> pTo;

@Uint16()
external int fFlags;

@Int32()
external int fAnyOperationsAborted;

external Pointer hNameMappings;

external Pointer<Utf16> lpszProgressTitle;
}

/// Defines an item identifier.
///
/// {@category Struct}
Expand Down
58 changes: 58 additions & 0 deletions lib/src/win32/shell32.g.dart
Expand Up @@ -216,6 +216,37 @@ final _SHEmptyRecycleBin = _shell32.lookupFunction<
int Function(int hwnd, Pointer<Utf16> pszRootPath,
int dwFlags)>('SHEmptyRecycleBinW');

/// Copies, moves, renames, or deletes a file system object.
///
/// ```c
/// int SHFileOperationW(
/// LPSHFILEOPSTRUCTW lpFileOp
/// );
/// ```
/// {@category shell32}
int SHFileOperation(Pointer<SHFILEOPSTRUCT> lpFileOp) =>
_SHFileOperation(lpFileOp);

final _SHFileOperation = _shell32.lookupFunction<
Int32 Function(Pointer<SHFILEOPSTRUCT> lpFileOp),
int Function(Pointer<SHFILEOPSTRUCT> 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.
///
Expand Down Expand Up @@ -280,6 +311,33 @@ final _SHGetDriveMedia = _shell32.lookupFunction<
int Function(Pointer<Utf16> pszDrive,
Pointer<Uint32> 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<Utf16> pszPath, int dwFileAttributes,
Pointer<SHFILEINFO> psfi, int cbFileInfo, int uFlags) =>
_SHGetFileInfo(pszPath, dwFileAttributes, psfi, cbFileInfo, uFlags);

final _SHGetFileInfo = _shell32.lookupFunction<
IntPtr Function(Pointer<Utf16> pszPath, Uint32 dwFileAttributes,
Pointer<SHFILEINFO> psfi, Uint32 cbFileInfo, Int32 uFlags),
int Function(
Pointer<Utf16> pszPath,
int dwFileAttributes,
Pointer<SHFILEINFO> psfi,
int cbFileInfo,
int uFlags)>('SHGetFileInfoW');

/// Gets the path of a folder identified by a CSIDL value.
///
/// ```c
Expand Down
27 changes: 27 additions & 0 deletions test/api_test.dart
Expand Up @@ -9842,6 +9842,20 @@ void main() {
int dwFlags)>('SHEmptyRecycleBinW');
expect(SHEmptyRecycleBin, isA<Function>());
});
test('Can instantiate SHFileOperation', () {
final shell32 = DynamicLibrary.open('shell32.dll');
final SHFileOperation = shell32.lookupFunction<
Int32 Function(Pointer<SHFILEOPSTRUCT> lpFileOp),
int Function(Pointer<SHFILEOPSTRUCT> lpFileOp)>('SHFileOperationW');
expect(SHFileOperation, isA<Function>());
});
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<Function>());
});
test('Can instantiate SHGetDesktopFolder', () {
final shell32 = DynamicLibrary.open('shell32.dll');
final SHGetDesktopFolder = shell32.lookupFunction<
Expand Down Expand Up @@ -9875,6 +9889,19 @@ void main() {
Pointer<Uint32> pdwMediaContent)>('SHGetDriveMedia');
expect(SHGetDriveMedia, isA<Function>());
});
test('Can instantiate SHGetFileInfo', () {
final shell32 = DynamicLibrary.open('shell32.dll');
final SHGetFileInfo = shell32.lookupFunction<
IntPtr Function(Pointer<Utf16> pszPath, Uint32 dwFileAttributes,
Pointer<SHFILEINFO> psfi, Uint32 cbFileInfo, Int32 uFlags),
int Function(
Pointer<Utf16> pszPath,
int dwFileAttributes,
Pointer<SHFILEINFO> psfi,
int cbFileInfo,
int uFlags)>('SHGetFileInfoW');
expect(SHGetFileInfo, isA<Function>());
});
test('Can instantiate SHGetFolderPath', () {
final shell32 = DynamicLibrary.open('shell32.dll');
final SHGetFolderPath = shell32.lookupFunction<
Expand Down
4 changes: 3 additions & 1 deletion tool/generate.cmd
Expand Up @@ -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

Expand Down