// Copyright © 2017-2026 QL-Win Contributors // // This file is part of QuickLook program. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . using System; using System.Diagnostics; using System.Runtime.InteropServices; using System.Threading; namespace QuickLook.Helpers; /// /// The actual Recycle Bin emptying routine used by the hidden `--empty-recycle-bin` /// command line mode of QuickLook.exe. The plugin launches that mode as a detached /// process, so emptying keeps running independently of the viewer window and of /// whether another file gets previewed. /// public static class RecycleBinCleaner { /// /// Cross-process mutex which prevents two helper processes (for example, one /// started by a movie-preview instance and one by the main instance) from /// running a concurrent empty operation. /// public const string EmptyingMutexName = @"QuickLook.RecycleBin.Emptying"; [Flags] private enum RecycleBinFlags : uint { SHERB_NOCONFIRMATION = 0x00000001, SHERB_NOPROGRESSUI = 0x00000002, SHERB_NOSOUND = 0x00000004, } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] private struct SHQUERYRBINFO { public uint cbSize; public ulong i64Size; public ulong i64NumItems; } [DllImport("shell32.dll", CharSet = CharSet.Unicode)] private static extern int SHEmptyRecycleBin(IntPtr hwnd, string pszRootPath, RecycleBinFlags dwFlags); [DllImport("shell32.dll", CharSet = CharSet.Unicode)] private static extern int SHQueryRecycleBin(string pszRootPath, ref SHQUERYRBINFO pSHQueryRBInfo); /// /// Empties all Recycle Bin locations without any user interface. /// /// /// true when the Recycle Bin was emptied (or another helper process was already /// doing it); false when Windows reported a failure. /// public static bool EmptyRecycleBin() { return EmptyRecycleBin(out _); } /// /// Empties all Recycle Bin locations without any user interface. /// /// The HRESULT returned by SHEmptyRecycleBin (0 on success). /// /// true when the Recycle Bin was emptied (or another helper process was already /// doing it); false when Windows reported a failure. /// public static bool EmptyRecycleBin(out int hresult) { hresult = 0; Mutex mutex = null; var ownsMutex = false; try { mutex = new Mutex(false, EmptyingMutexName); try { // Do not wait: a second helper should exit immediately instead of // starting a concurrent empty operation. ownsMutex = mutex.WaitOne(0); } catch (AbandonedMutexException) { // The previous helper terminated while emptying. Take over. ownsMutex = true; } if (!ownsMutex) { // Another helper is already emptying the Recycle Bin. return true; } // SHEmptyRecycleBin returns E_UNEXPECTED (0x8000FFFF) when the Recycle Bin is // already empty; that is not a failure for our background job. if (TryGetRecycleBinInfo(out _, out var itemCount) && itemCount == 0) { hresult = 0; return true; } hresult = SHEmptyRecycleBin( IntPtr.Zero, null, RecycleBinFlags.SHERB_NOCONFIRMATION | RecycleBinFlags.SHERB_NOPROGRESSUI | RecycleBinFlags.SHERB_NOSOUND); // S_FALSE (1) means "nothing was deleted"; both results are a success for us. return hresult == 0 || hresult == 1; } catch (Exception ex) { Debug.WriteLine(ex); hresult = Marshal.GetHRForException(ex); return false; } finally { if (ownsMutex) { try { mutex.ReleaseMutex(); } catch (Exception ex) { Debug.WriteLine(ex); } } mutex?.Dispose(); } } /// /// Queries the current Recycle Bin size and item count. /// /// Total size in bytes. /// Number of items. /// true when the query succeeded. public static bool TryGetRecycleBinInfo(out ulong totalSize, out ulong totalCount) { totalSize = 0; totalCount = 0; try { var info = new SHQUERYRBINFO { cbSize = (uint)Marshal.SizeOf(typeof(SHQUERYRBINFO)), }; if (SHQueryRecycleBin(null, ref info) != 0) // S_OK (0) return false; totalSize = info.i64Size; totalCount = info.i64NumItems; return true; } catch (Exception ex) { Debug.WriteLine(ex); return false; } } }