Skip to content

Commit

Permalink
Merge pull request #630 from timsneath/shell-apis
Browse files Browse the repository at this point in the history
Add extra shell file operation APIs
  • Loading branch information
timsneath committed Dec 30, 2022
2 parents d5fc339 + 2ec16d9 commit 91c562e
Show file tree
Hide file tree
Showing 9 changed files with 351 additions and 1 deletion.
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

0 comments on commit 91c562e

Please sign in to comment.