Merge pull request #3561 from onesounds/250520-FixFilesConfig

Improve File Manager Configuration Error Handling
This commit is contained in:
DB P 2025-05-21 20:49:11 +09:00 committed by GitHub
commit ac614696b1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 337 additions and 136 deletions

View file

@ -226,8 +226,8 @@ namespace Flow.Launcher.Infrastructure.UserSettings
new()
{
Name = "Files",
Path = "Files",
DirectoryArgument = "-select \"%d\"",
Path = "Files-Stable",
DirectoryArgument = "\"%d\"",
FileArgument = "-select \"%f\""
}
};

View file

@ -98,6 +98,10 @@ namespace Flow.Launcher
.AddTransient<SettingsPanePluginStoreViewModel>()
.AddTransient<SettingsPaneProxyViewModel>()
.AddTransient<SettingsPaneThemeViewModel>()
// Use transient instance for dialog view models because
// settings will change and we need to recreate them
.AddTransient<SelectBrowserViewModel>()
.AddTransient<SelectFileManagerViewModel>()
).Build();
Ioc.Default.ConfigureServices(host.Services);
}

View file

@ -366,6 +366,7 @@
<!-- FileManager Setting Dialog -->
<system:String x:Key="fileManagerWindow">Select File Manager</system:String>
<system:String x:Key="fileManager_learnMore">Learn more</system:String>
<system:String x:Key="fileManager_tips">Please specify the file location of the file manager you using and add arguments as required. The &quot;%d&quot; represents the directory path to open for, used by the Arg for Folder field and for commands opening specific directories. The &quot;%f&quot; represents the file path to open for, used by the Arg for File field and for commands opening specific files.</system:String>
<system:String x:Key="fileManager_tips2">For example, if the file manager uses a command such as &quot;totalcmd.exe /A c:\windows&quot; to open the c:\windows directory, the File Manager Path will be totalcmd.exe, and the Arg For Folder will be /A &quot;%d&quot;. Certain file managers like QTTabBar may just require a path to be supplied, in this instance use &quot;%d&quot; as the File Manager Path and leave the rest of the fileds blank.</system:String>
<system:String x:Key="fileManager_name">File Manager</system:String>
@ -373,6 +374,8 @@
<system:String x:Key="fileManager_path">File Manager Path</system:String>
<system:String x:Key="fileManager_directory_arg">Arg For Folder</system:String>
<system:String x:Key="fileManager_file_arg">Arg For File</system:String>
<system:String x:Key="fileManagerPathNotFound">The file manager '{0}' could not be located at '{1}'. Would you like to continue?</system:String>
<system:String x:Key="fileManagerPathError">File Manager Path Error</system:String>
<!-- DefaultBrowser Setting Dialog -->
<system:String x:Key="defaultBrowserTitle">Default Web Browser</system:String>
@ -462,6 +465,14 @@
<system:String x:Key="reportWindow_upload_log">1. Upload log file: {0}</system:String>
<system:String x:Key="reportWindow_copy_below">2. Copy below exception message</system:String>
<!-- File Open Error -->
<system:String x:Key="fileManagerNotFoundTitle">File Manager Error</system:String>
<system:String x:Key="fileManagerNotFound">
The specified file manager could not be found. Please check the Custom File Manager setting under Settings > General.
</system:String>
<system:String x:Key="errorTitle">Error</system:String>
<system:String x:Key="folderOpenError">An error occurred while opening the folder. {0}</system:String>
<!-- General Notice -->
<system:String x:Key="pleaseWait">Please wait...</system:String>

View file

@ -22,9 +22,6 @@ namespace Flow.Launcher
InitializeComponent();
}
public static MessageBoxResult Show(string messageBoxText)
=> Show(messageBoxText, string.Empty, MessageBoxButton.OK, MessageBoxImage.None, MessageBoxResult.OK);
public static MessageBoxResult Show(
string messageBoxText,
string caption = "",

View file

@ -32,11 +32,14 @@ using Flow.Launcher.ViewModel;
using JetBrains.Annotations;
using Squirrel;
using Stopwatch = Flow.Launcher.Infrastructure.Stopwatch;
using System.ComponentModel;
namespace Flow.Launcher
{
public class PublicAPIInstance : IPublicAPI, IRemovable
{
private static readonly string ClassName = nameof(PublicAPIInstance);
private readonly Settings _settings;
private readonly MainViewModel _mainVM;
@ -316,40 +319,63 @@ namespace Flow.Launcher
public void OpenDirectory(string DirectoryPath, string FileNameOrFilePath = null)
{
using var explorer = new Process();
var explorerInfo = _settings.CustomExplorer;
var explorerPath = explorerInfo.Path.Trim().ToLowerInvariant();
var targetPath = FileNameOrFilePath is null
? DirectoryPath
: Path.IsPathRooted(FileNameOrFilePath)
? FileNameOrFilePath
: Path.Combine(DirectoryPath, FileNameOrFilePath);
try
{
using var explorer = new Process();
var explorerInfo = _settings.CustomExplorer;
var explorerPath = explorerInfo.Path.Trim().ToLowerInvariant();
var targetPath = FileNameOrFilePath is null
? DirectoryPath
: Path.IsPathRooted(FileNameOrFilePath)
? FileNameOrFilePath
: Path.Combine(DirectoryPath, FileNameOrFilePath);
if (Path.GetFileNameWithoutExtension(explorerPath) == "explorer")
{
// Windows File Manager
// We should ignore and pass only the path to Shell to prevent zombie explorer.exe processes
explorer.StartInfo = new ProcessStartInfo
if (Path.GetFileNameWithoutExtension(explorerPath) == "explorer")
{
FileName = targetPath, // Not explorer, Only path.
UseShellExecute = true // Must be true to open folder
};
}
else
{
// Custom File Manager
explorer.StartInfo = new ProcessStartInfo
// Windows File Manager
explorer.StartInfo = new ProcessStartInfo
{
FileName = targetPath,
UseShellExecute = true
};
}
else
{
FileName = explorerInfo.Path.Replace("%d", DirectoryPath),
UseShellExecute = true,
Arguments = FileNameOrFilePath is null
? explorerInfo.DirectoryArgument.Replace("%d", DirectoryPath)
: explorerInfo.FileArgument
.Replace("%d", DirectoryPath)
.Replace("%f", targetPath)
};
// Custom File Manager
explorer.StartInfo = new ProcessStartInfo
{
FileName = explorerInfo.Path.Replace("%d", DirectoryPath),
UseShellExecute = true,
Arguments = FileNameOrFilePath is null
? explorerInfo.DirectoryArgument.Replace("%d", DirectoryPath)
: explorerInfo.FileArgument
.Replace("%d", DirectoryPath)
.Replace("%f", targetPath)
};
}
explorer.Start();
}
catch (Win32Exception ex) when (ex.NativeErrorCode == 2)
{
LogError(ClassName, "File Manager not found");
ShowMsgBox(
string.Format(GetTranslation("fileManagerNotFound"), ex.Message),
GetTranslation("fileManagerNotFoundTitle"),
MessageBoxButton.OK,
MessageBoxImage.Error
);
}
catch (Exception ex)
{
LogException(ClassName, "Failed to open folder", ex);
ShowMsgBox(
string.Format(GetTranslation("folderOpenError"), ex.Message),
GetTranslation("errorTitle"),
MessageBoxButton.OK,
MessageBoxImage.Error
);
}
explorer.Start();
}
private void OpenUri(Uri uri, bool? inPrivate = null)

View file

@ -6,10 +6,11 @@
xmlns:local="clr-namespace:Flow.Launcher"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="http://schemas.modernwpf.com/2019"
xmlns:vm="clr-namespace:Flow.Launcher.ViewModel"
Title="{DynamicResource defaultBrowserTitle}"
Width="550"
d:DataContext="{d:DesignInstance vm:SelectBrowserViewModel}"
Background="{DynamicResource PopuBGColor}"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Foreground="{DynamicResource PopupTextColor}"
ResizeMode="NoResize"
SizeToContent="Height"
@ -97,11 +98,11 @@
</ComboBox>
<Button
Margin="10 0 0 0"
Click="btnAdd_Click"
Command="{Binding AddCommand}"
Content="{DynamicResource add}" />
<Button
Margin="10 0 0 0"
Click="btnDelete_Click"
Command="{Binding DeleteCommand}"
Content="{DynamicResource delete}"
IsEnabled="{Binding CustomBrowser.Editable}" />

View file

@ -1,36 +1,18 @@
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;
using System.Windows;
using System.Windows.Controls;
using CommunityToolkit.Mvvm.ComponentModel;
using Flow.Launcher.Infrastructure.UserSettings;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.ViewModel;
namespace Flow.Launcher
{
[INotifyPropertyChanged]
public partial class SelectBrowserWindow : Window
{
private readonly Settings _settings;
private readonly SelectBrowserViewModel _viewModel;
private int selectedCustomBrowserIndex;
public int SelectedCustomBrowserIndex
public SelectBrowserWindow()
{
get => selectedCustomBrowserIndex;
set
{
selectedCustomBrowserIndex = value;
OnPropertyChanged(nameof(CustomBrowser));
}
}
public ObservableCollection<CustomBrowserViewModel> CustomBrowsers { get; set; }
public CustomBrowserViewModel CustomBrowser => CustomBrowsers[SelectedCustomBrowserIndex];
public SelectBrowserWindow(Settings settings)
{
_settings = settings;
CustomBrowsers = new ObservableCollection<CustomBrowserViewModel>(_settings.CustomBrowserList.Select(x => x.Copy()));
SelectedCustomBrowserIndex = _settings.CustomBrowserIndex;
_viewModel = Ioc.Default.GetRequiredService<SelectBrowserViewModel>();
DataContext = _viewModel;
InitializeComponent();
}
@ -41,33 +23,20 @@ namespace Flow.Launcher
private void btnDone_Click(object sender, RoutedEventArgs e)
{
_settings.CustomBrowserList = CustomBrowsers.ToList();
_settings.CustomBrowserIndex = SelectedCustomBrowserIndex;
Close();
}
private void btnAdd_Click(object sender, RoutedEventArgs e)
{
CustomBrowsers.Add(new()
if (_viewModel.SaveSettings())
{
Name = "New Profile"
});
SelectedCustomBrowserIndex = CustomBrowsers.Count - 1;
}
private void btnDelete_Click(object sender, RoutedEventArgs e)
{
CustomBrowsers.RemoveAt(SelectedCustomBrowserIndex--);
Close();
}
}
private void btnBrowseFile_Click(object sender, RoutedEventArgs e)
{
Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog();
var result = dlg.ShowDialog();
if (result == true)
var selectedFilePath = _viewModel.SelectFile();
if (!string.IsNullOrEmpty(selectedFilePath))
{
TextBox path = (TextBox)(((FrameworkElement)sender).Parent as FrameworkElement).FindName("PathTextBox");
path.Text = dlg.FileName;
var path = (TextBox)(((FrameworkElement)sender).Parent as FrameworkElement).FindName("PathTextBox");
path.Text = selectedFilePath;
path.Focus();
((Button)sender).Focus();
}

View file

@ -6,10 +6,11 @@
xmlns:local="clr-namespace:Flow.Launcher"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="http://schemas.modernwpf.com/2019"
xmlns:vm="clr-namespace:Flow.Launcher.ViewModel"
Title="{DynamicResource fileManagerWindow}"
Width="600"
d:DataContext="{d:DesignInstance vm:SelectFileManagerViewModel}"
Background="{DynamicResource PopuBGColor}"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Foreground="{DynamicResource PopupTextColor}"
ResizeMode="NoResize"
SizeToContent="Height"
@ -73,9 +74,17 @@
<TextBlock Margin="0 14 0 0" FontSize="14">
<TextBlock Text="{DynamicResource fileManager_tips2}" TextWrapping="WrapWithOverflow" />
</TextBlock>
<TextBlock Margin="0 14 0 0" VerticalAlignment="Center">
<Hyperlink NavigateUri="https://www.flowlauncher.com/docs/#/filemanager" RequestNavigate="Hyperlink_RequestNavigate">
<TextBlock FontSize="14" Text="{DynamicResource fileManager_learnMore}" />
</Hyperlink>
</TextBlock>
</StackPanel>
<StackPanel Margin="14 28 0 0" Orientation="Horizontal">
<Rectangle
Height="1"
Margin="0 20 0 20"
Fill="{StaticResource SeparatorForeground}" />
<StackPanel Margin="14 0 0 0" Orientation="Horizontal">
<TextBlock
Grid.Column="1"
HorizontalAlignment="Left"
@ -99,11 +108,11 @@
</ComboBox>
<Button
Margin="10 0 0 0"
Click="btnAdd_Click"
Command="{Binding AddCommand}"
Content="{DynamicResource add}" />
<Button
Margin="10 0 0 0"
Click="btnDelete_Click"
Command="{Binding DeleteCommand}"
Content="{DynamicResource delete}"
IsEnabled="{Binding CustomExplorer.Editable}" />
@ -111,7 +120,7 @@
<Rectangle
Height="1"
Margin="0 20 0 12"
Fill="{StaticResource Color03B}" />
Fill="{StaticResource SeparatorForeground}" />
<StackPanel
Margin="0 0 0 0"
HorizontalAlignment="Stretch"

View file

@ -1,38 +1,19 @@
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows;
using System.Windows.Controls;
using CommunityToolkit.Mvvm.ComponentModel;
using Flow.Launcher.Infrastructure.UserSettings;
using System.Windows.Navigation;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.ViewModel;
namespace Flow.Launcher
{
[INotifyPropertyChanged]
public partial class SelectFileManagerWindow : Window
{
private readonly Settings _settings;
private readonly SelectFileManagerViewModel _viewModel;
private int selectedCustomExplorerIndex;
public int SelectedCustomExplorerIndex
public SelectFileManagerWindow()
{
get => selectedCustomExplorerIndex;
set
{
selectedCustomExplorerIndex = value;
OnPropertyChanged(nameof(CustomExplorer));
}
}
public ObservableCollection<CustomExplorerViewModel> CustomExplorers { get; set; }
public CustomExplorerViewModel CustomExplorer => CustomExplorers[SelectedCustomExplorerIndex];
public SelectFileManagerWindow(Settings settings)
{
_settings = settings;
CustomExplorers = new ObservableCollection<CustomExplorerViewModel>(_settings.CustomExplorerList.Select(x => x.Copy()));
SelectedCustomExplorerIndex = _settings.CustomExplorerIndex;
_viewModel = Ioc.Default.GetRequiredService<SelectFileManagerViewModel>();
DataContext = _viewModel;
InitializeComponent();
}
@ -43,33 +24,26 @@ namespace Flow.Launcher
private void btnDone_Click(object sender, RoutedEventArgs e)
{
_settings.CustomExplorerList = CustomExplorers.ToList();
_settings.CustomExplorerIndex = SelectedCustomExplorerIndex;
Close();
}
private void btnAdd_Click(object sender, RoutedEventArgs e)
{
CustomExplorers.Add(new()
if (_viewModel.SaveSettings())
{
Name = "New Profile"
});
SelectedCustomExplorerIndex = CustomExplorers.Count - 1;
Close();
}
}
private void btnDelete_Click(object sender, RoutedEventArgs e)
private void Hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs e)
{
CustomExplorers.RemoveAt(SelectedCustomExplorerIndex--);
_viewModel.OpenUrl(e.Uri.AbsoluteUri);
e.Handled = true;
}
private void btnBrowseFile_Click(object sender, RoutedEventArgs e)
{
Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog();
var result = dlg.ShowDialog();
if (result == true)
var selectedFilePath = _viewModel.SelectFile();
if (!string.IsNullOrEmpty(selectedFilePath))
{
TextBox path = (TextBox)(((FrameworkElement)sender).Parent as FrameworkElement).FindName("PathTextBox");
path.Text = dlg.FileName;
var path = (TextBox)(((FrameworkElement)sender).Parent as FrameworkElement).FindName("PathTextBox");
path.Text = selectedFilePath;
path.Focus();
((Button)sender).Focus();
}

View file

@ -335,14 +335,14 @@ public partial class SettingsPaneGeneralViewModel : BaseModel
[RelayCommand]
private void SelectFileManager()
{
var fileManagerChangeWindow = new SelectFileManagerWindow(Settings);
var fileManagerChangeWindow = new SelectFileManagerWindow();
fileManagerChangeWindow.ShowDialog();
}
[RelayCommand]
private void SelectBrowser()
{
var browserWindow = new SelectBrowserWindow(Settings);
var browserWindow = new SelectBrowserWindow();
browserWindow.ShowDialog();
}
}

View file

@ -0,0 +1,74 @@
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;
using CommunityToolkit.Mvvm.Input;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
namespace Flow.Launcher.ViewModel;
public partial class SelectBrowserViewModel : BaseModel
{
private readonly Settings _settings;
private int selectedCustomBrowserIndex;
public int SelectedCustomBrowserIndex
{
get => selectedCustomBrowserIndex;
set
{
selectedCustomBrowserIndex = value;
OnPropertyChanged(nameof(CustomBrowser));
}
}
public ObservableCollection<CustomBrowserViewModel> CustomBrowsers { get; }
public CustomBrowserViewModel CustomBrowser => CustomBrowsers[SelectedCustomBrowserIndex];
public SelectBrowserViewModel(Settings settings)
{
_settings = settings;
CustomBrowsers = new ObservableCollection<CustomBrowserViewModel>(_settings.CustomBrowserList.Select(x => x.Copy()));
SelectedCustomBrowserIndex = _settings.CustomBrowserIndex;
}
public bool SaveSettings()
{
_settings.CustomBrowserList = CustomBrowsers.ToList();
_settings.CustomBrowserIndex = SelectedCustomBrowserIndex;
return true;
}
internal string SelectFile()
{
var dlg = new Microsoft.Win32.OpenFileDialog();
var result = dlg.ShowDialog();
if (result == true)
return dlg.FileName;
return string.Empty;
}
[RelayCommand]
private void Add()
{
CustomBrowsers.Add(new()
{
Name = "New Profile"
});
SelectedCustomBrowserIndex = CustomBrowsers.Count - 1;
}
[RelayCommand]
private void Delete()
{
var currentIndex = SelectedCustomBrowserIndex;
if (currentIndex >= 0 && currentIndex < CustomBrowsers.Count)
{
CustomBrowsers.RemoveAt(currentIndex);
SelectedCustomBrowserIndex = currentIndex > 0 ? currentIndex - 1 : 0;
}
}
}

View file

@ -0,0 +1,136 @@
using System;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Windows;
using CommunityToolkit.Mvvm.Input;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
namespace Flow.Launcher.ViewModel;
public partial class SelectFileManagerViewModel : BaseModel
{
private readonly Settings _settings;
private int selectedCustomExplorerIndex;
public int SelectedCustomExplorerIndex
{
get => selectedCustomExplorerIndex;
set
{
if (selectedCustomExplorerIndex != value)
{
selectedCustomExplorerIndex = value;
OnPropertyChanged(nameof(CustomExplorer));
}
}
}
public ObservableCollection<CustomExplorerViewModel> CustomExplorers { get; }
public CustomExplorerViewModel CustomExplorer => CustomExplorers[SelectedCustomExplorerIndex];
public SelectFileManagerViewModel(Settings settings)
{
_settings = settings;
CustomExplorers = new ObservableCollection<CustomExplorerViewModel>(_settings.CustomExplorerList.Select(x => x.Copy()));
SelectedCustomExplorerIndex = _settings.CustomExplorerIndex;
}
public bool SaveSettings()
{
// Check if the selected file manager path is valid
if (!IsFileManagerValid(CustomExplorer.Path))
{
var result = App.API.ShowMsgBox(
string.Format(App.API.GetTranslation("fileManagerPathNotFound"),
CustomExplorer.Name, CustomExplorer.Path),
App.API.GetTranslation("fileManagerPathError"),
MessageBoxButton.YesNo,
MessageBoxImage.Warning);
if (result == MessageBoxResult.No)
{
return false;
}
}
_settings.CustomExplorerList = CustomExplorers.ToList();
_settings.CustomExplorerIndex = SelectedCustomExplorerIndex;
return true;
}
private static bool IsFileManagerValid(string path)
{
if (string.Equals(path, "explorer", StringComparison.OrdinalIgnoreCase))
return true;
if (Path.IsPathRooted(path))
{
return File.Exists(path);
}
try
{
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "where",
Arguments = path,
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true
}
};
process.Start();
string output = process.StandardOutput.ReadToEnd();
process.WaitForExit();
return !string.IsNullOrEmpty(output);
}
catch
{
return false;
}
}
internal void OpenUrl(string absoluteUri)
{
App.API.OpenUrl(absoluteUri);
}
internal string SelectFile()
{
var dlg = new Microsoft.Win32.OpenFileDialog();
var result = dlg.ShowDialog();
if (result == true)
return dlg.FileName;
return string.Empty;
}
[RelayCommand]
private void Add()
{
CustomExplorers.Add(new()
{
Name = "New Profile"
});
SelectedCustomExplorerIndex = CustomExplorers.Count - 1;
}
[RelayCommand]
private void Delete()
{
var currentIndex = SelectedCustomExplorerIndex;
if (currentIndex >= 0 && currentIndex < CustomExplorers.Count)
{
CustomExplorers.RemoveAt(currentIndex);
SelectedCustomExplorerIndex = currentIndex > 0 ? currentIndex - 1 : 0;
}
}
}