diff --git a/Flow.Launcher.Core/Plugin/PluginConfig.cs b/Flow.Launcher.Core/Plugin/PluginConfig.cs index ea2119e60..dd6517a7f 100644 --- a/Flow.Launcher.Core/Plugin/PluginConfig.cs +++ b/Flow.Launcher.Core/Plugin/PluginConfig.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.IO; @@ -45,8 +45,56 @@ namespace Flow.Launcher.Core.Plugin } } } - - return allPluginMetadata; + + (List uniqueList, List duplicateList) = GetUniqueLatestPluginMetadata(allPluginMetadata); + + duplicateList + .ForEach( + x => Log.Warn("PluginConfig", + string.Format("Duplicate plugin name: {0}, id: {1}, version: {2} " + + "not loaded due to version not the highest of the duplicates", + x.Name, x.ID, x.Version), + "GetUniqueLatestPluginMetadata")); + + return uniqueList; + } + + internal static (List, List) GetUniqueLatestPluginMetadata(List allPluginMetadata) + { + var duplicate_list = new List(); + var unique_list = new List(); + + var duplicateGroups = allPluginMetadata.GroupBy(x => x.ID).Where(g => g.Count() > 1).Select(y => y).ToList(); + + foreach (var metadata in allPluginMetadata) + { + var duplicatesExist = false; + foreach (var group in duplicateGroups) + { + if (metadata.ID == group.Key) + { + duplicatesExist = true; + + // If metadata's version greater than each duplicate's version, CompareTo > 0 + var count = group.Where(x => metadata.Version.CompareTo(x.Version) > 0).Count(); + + // Only add if the meatadata's version is the highest of all duplicates in the group + if (count == group.Count() - 1) + { + unique_list.Add(metadata); + } + else + { + duplicate_list.Add(metadata); + } + } + } + + if (!duplicatesExist) + unique_list.Add(metadata); + } + + return (unique_list, duplicate_list); } private static PluginMetadata GetPluginMetadata(string pluginDirectory) diff --git a/Flow.Launcher.Infrastructure/Constant.cs b/Flow.Launcher.Infrastructure/Constant.cs index 081dd9909..cd49217a4 100644 --- a/Flow.Launcher.Infrastructure/Constant.cs +++ b/Flow.Launcher.Infrastructure/Constant.cs @@ -35,6 +35,10 @@ namespace Flow.Launcher.Infrastructure public const string DefaultTheme = "Win11Light"; + public const string Light = "Light"; + public const string Dark = "Dark"; + public const string System = "System"; + public const string Themes = "Themes"; public const string Settings = "Settings"; public const string Logs = "Logs"; diff --git a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs index 3abdaf01f..908284bb9 100644 --- a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs +++ b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs @@ -29,6 +29,15 @@ namespace Flow.Launcher.Plugin /// void RestartApp(); + /// + /// Run a shell command + /// + /// The command or program to run + /// the shell type to run, e.g. powershell.exe + /// Thrown when unable to find the file specified in the command + /// Thrown when error occurs during the execution of the command + void ShellRun(string cmd, string filename = "cmd.exe"); + /// /// Save everything, all of Flow Launcher and plugins' data and settings /// diff --git a/Flow.Launcher.Plugin/Result.cs b/Flow.Launcher.Plugin/Result.cs index 171b30c26..833ada9cd 100644 --- a/Flow.Launcher.Plugin/Result.cs +++ b/Flow.Launcher.Plugin/Result.cs @@ -50,7 +50,7 @@ namespace Flow.Launcher.Plugin /// /// Delegate to Get Image Source /// - public IconDelegate Icon { get; set; } + public IconDelegate Icon; /// /// Information for Glyph Icon diff --git a/Flow.Launcher.Plugin/SharedCommands/ShellCommand.cs b/Flow.Launcher.Plugin/SharedCommands/ShellCommand.cs index c5d43a3d9..a2eea19a7 100644 --- a/Flow.Launcher.Plugin/SharedCommands/ShellCommand.cs +++ b/Flow.Launcher.Plugin/SharedCommands/ShellCommand.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -60,17 +60,39 @@ namespace Flow.Launcher.Plugin.SharedCommands return sb.ToString(); } - public static ProcessStartInfo SetProcessStartInfo(this string fileName, string workingDirectory = "", string arguments = "", string verb = "") + public static ProcessStartInfo SetProcessStartInfo(this string fileName, string workingDirectory = "", string arguments = "", string verb = "", bool createNoWindow = false) { var info = new ProcessStartInfo { FileName = fileName, WorkingDirectory = workingDirectory, Arguments = arguments, - Verb = verb + Verb = verb, + CreateNoWindow = createNoWindow }; return info; } + + /// + /// Runs a windows command using the provided ProcessStartInfo + /// + /// Thrown when unable to find the file specified in the command + /// Thrown when error occurs during the execution of the command + public static void Execute(ProcessStartInfo info) + { + Execute(Process.Start, info); + } + + /// + /// Runs a windows command using the provided ProcessStartInfo using a custom execute command function + /// + /// allows you to pass in a custom command execution function + /// Thrown when unable to find the file specified in the command + /// Thrown when error occurs during the execution of the command + public static void Execute(Func startProcess, ProcessStartInfo info) + { + startProcess(info); + } } } diff --git a/Flow.Launcher.Test/PluginLoadTest.cs b/Flow.Launcher.Test/PluginLoadTest.cs new file mode 100644 index 000000000..d6ba48f19 --- /dev/null +++ b/Flow.Launcher.Test/PluginLoadTest.cs @@ -0,0 +1,92 @@ +using NUnit.Framework; +using Flow.Launcher.Core.Plugin; +using Flow.Launcher.Plugin; +using System.Collections.Generic; +using System.Linq; + +namespace Flow.Launcher.Test +{ + [TestFixture] + class PluginLoadTest + { + [Test] + public void GivenDuplicatePluginMetadatasWhenLoadedThenShouldReturnOnlyUniqueList() + { + // Given + var duplicateList = new List + { + new PluginMetadata + { + ID = "CEA0TYUC6D3B4085823D60DC76F28855", + Version = "1.0.0" + }, + new PluginMetadata + { + ID = "CEA0TYUC6D3B4085823D60DC76F28855", + Version = "1.0.1" + }, + new PluginMetadata + { + ID = "CEA0TYUC6D3B4085823D60DC76F28855", + Version = "1.0.2" + }, + new PluginMetadata + { + ID = "CEA0TYUC6D3B4085823D60DC76F28855", + Version = "1.0.0" + }, + new PluginMetadata + { + ID = "CEA0TYUC6D3B4085823D60DC76F28855", + Version = "1.0.0" + }, + new PluginMetadata + { + ID = "ABC0TYUC6D3B7855823D60DC76F28855", + Version = "1.0.0" + }, + new PluginMetadata + { + ID = "ABC0TYUC6D3B7855823D60DC76F28855", + Version = "1.0.0" + } + }; + + // When + (var unique, var duplicates) = PluginConfig.GetUniqueLatestPluginMetadata(duplicateList); + + // Then + Assert.True(unique.FirstOrDefault().ID == "CEA0TYUC6D3B4085823D60DC76F28855" && unique.FirstOrDefault().Version == "1.0.2"); + Assert.True(unique.Count() == 1); + + Assert.False(duplicates.Any(x => x.Version == "1.0.2" && x.ID == "CEA0TYUC6D3B4085823D60DC76F28855")); + Assert.True(duplicates.Count() == 6); + } + + [Test] + public void GivenDuplicatePluginMetadatasWithNoUniquePluginWhenLoadedThenShouldReturnEmptyList() + { + // Given + var duplicateList = new List + { + new PluginMetadata + { + ID = "CEA0TYUC6D3B7855823D60DC76F28855", + Version = "1.0.0" + }, + new PluginMetadata + { + ID = "CEA0TYUC6D3B7855823D60DC76F28855", + Version = "1.0.0" + } + }; + + // When + (var unique, var duplicates) = PluginConfig.GetUniqueLatestPluginMetadata(duplicateList); + + // Then + Assert.True(unique.Count() == 0); + Assert.True(duplicates.Count() == 2); + } + } +} diff --git a/Flow.Launcher.Test/Plugins/PluginInitTest.cs b/Flow.Launcher.Test/Plugins/PluginInitTest.cs deleted file mode 100644 index 299a837ee..000000000 --- a/Flow.Launcher.Test/Plugins/PluginInitTest.cs +++ /dev/null @@ -1,17 +0,0 @@ -using NUnit.Framework; -using Flow.Launcher.Core.Plugin; -using Flow.Launcher.Infrastructure.Exception; - -namespace Flow.Launcher.Test.Plugins -{ - - [TestFixture] - public class PluginInitTest - { - [Test] - public void PublicAPIIsNullTest() - { - //Assert.Throws(typeof(Flow.LauncherFatalException), () => PluginManager.Initialize(null)); - } - } -} diff --git a/Flow.Launcher/ActionKeywords.xaml b/Flow.Launcher/ActionKeywords.xaml index 3d0e00459..e94aac9f6 100644 --- a/Flow.Launcher/ActionKeywords.xaml +++ b/Flow.Launcher/ActionKeywords.xaml @@ -4,12 +4,12 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="{DynamicResource actionKeywordsTitle}" Width="450" - Height="345" Background="{DynamicResource PopuBGColor}" Foreground="{DynamicResource PopupTextColor}" Icon="Images\app.png" Loaded="ActionKeyword_OnLoaded" ResizeMode="NoResize" + SizeToContent="Height" WindowStartupLocation="CenterScreen"> @@ -91,7 +91,7 @@ FontWeight="SemiBold" Foreground="{DynamicResource Color05B}" /> - + diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml index 9b2827d5e..c0458b06b 100644 --- a/Flow.Launcher/Languages/en.xaml +++ b/Flow.Launcher/Languages/en.xaml @@ -16,6 +16,7 @@ Exit Close Game Mode + Suspend the use of Hotkeys. Flow Launcher Settings @@ -55,15 +56,17 @@ Off Action keyword Setting Action keyword - Current action keyword: - New action keyword: - Current Priority: - New Priority: + Current action keyword + New action keyword + Current Priority + New Priority Priority Plugin Directory Author: Init time: Query time: + | Version + Website @@ -85,7 +88,6 @@ Theme Folder Open Theme Folder Dark Mode - System settings will take effect from the next run System Default Light Dark diff --git a/Flow.Launcher/Languages/ko.xaml b/Flow.Launcher/Languages/ko.xaml index 649895cea..adcb8fd3d 100644 --- a/Flow.Launcher/Languages/ko.xaml +++ b/Flow.Launcher/Languages/ko.xaml @@ -1,7 +1,8 @@ - - + + 핫키 등록 실패: {0} {0}을 실행할 수 없습니다. Flow Launcher 플러그인 파일 형식이 유효하지 않습니다. @@ -14,8 +15,10 @@ 정보 종료 닫기 + 게임 모드 + 핫키 사용을 일시중단합니다. - + Flow Launcher 설정 일반 포터블 모드 @@ -33,41 +36,48 @@ 표시할 결과 수 전체화면 모드에서는 핫키 무시 게이머라면 켜는 것을 추천합니다. + 기본 파일관리자 + 폴더를 열 때 사용할 파일관리자를 선택하세요. Python 디렉토리 자동 업데이트 선택 시작 시 Flow Launcher 숨김 트레이 아이콘 숨기기 - 쿼리 검색 정확도 - 검색 결과가 좀 더 정확해집니다. + 쿼리 검색 정밀도 + 검색 결과에 필요한 최소 매치 점수를 변경합니다. 항상 Pinyin 사용 Pinyin을 사용하여 검색할 수 있습니다. Pinyin(병음)은 로마자 중국어 입력 방식입니다. 반투명 흐림 효과를 사용하는 경우, 그림자 효과를 쓸 수 없습니다. - + 플러그인 플러그인 더 찾아보기 - On - Off + + 액션 키워드 + 현재 액션 키워드 + 새 액션 키워드 현재 중요도: 새 중요도: 중요도 - 플러그인 디렉토리 - 저자 + 플러그인 폴더 + 제작자 초기화 시간: 쿼리 시간: - - - + | 버전 + 웹사이트 + + + 플러그인 스토어 새로고침 설치 - + 테마 - 테마 더 찾아보기 + 테마 갤러리 + 테마 제작 안내 Hi There 쿼리 상자 글꼴 결과 항목 글꼴 @@ -77,8 +87,17 @@ {0} 테마 로드에 실패했습니다. 기본 테마로 변경합니다. 테마 폴더 테마 폴더 열기 + 다크 모드 + System settings will take effect from the next run + 시스템 기본 + 밝게 + 어둡게 + 소리 효과 + 검색창을 열 때 작은 소리를 재생합니다. + 애니메이션 + 일부 UI에 애니메이션을 사용합니다. - + 핫키 Flow Launcher 핫키 Flow Launcher를 열 때 사용할 단축키를 입력합니다. @@ -87,7 +106,7 @@ 단축키 표시 결과창에서 결과 선택 단축키를 표시합니다. 사용자지정 쿼리 핫키 - Query + 쿼리 삭제 편집 추가 @@ -95,10 +114,11 @@ {0} 플러그인 핫키를 삭제하시겠습니까? 그림자 효과 그림자 효과는 GPU를 사용합니다. 컴퓨터 퍼포먼스가 제한적인 경우 사용을 추천하지 않습니다. + 창 넓이 플루언트 아이콘 사용 결과 및 일부 메뉴에서 플루언트 아이콘을 사용합니다. - + HTTP 프록시 HTTP 프록시 켜기 HTTP 서버 @@ -114,26 +134,41 @@ 프록시 설정 정상 프록시 연결 실패 - + 정보 웹사이트 + Github + 문서 버전 Flow Launcher를 {0}번 실행했습니다. 업데이트 확인 새 버전({0})이 있습니다. Flow Launcher를 재시작하세요. 업데이트 확인을 실패했습니다. api.github.com로의 연결 또는 프록시 설정을 확인해주세요. - 업데이트 다운로드에 실패했습니다. github-cloud.s3.amazonaws.com의 연결 또는 프록시 설정을 확인해주세요. + 업데이트 다운로드에 실패했습니다. github-cloud.s3.amazonaws.com의 연결 또는 프록시 설정을 확인해주세요. 수동 다운로드를 하려면 https://github.com/Flow-Launcher/Flow.Launcher/releases 으로 방문하세요. - 릴리즈 노트: - 사용 팁: + 릴리즈 노트 + 사용 팁 + 개발자도구 + 설정 폴더 + 로그 폴더 - + + 파일관리자 선택 + 사용하려는 파일관리자를 선택하고 필요한 경우 인수를 추가하세요. 기본 인수는 "%d" 이며 해당 위치에 경로가 입력됩니다. 예를들어 "totalcmd.exe /A c:\windows"와 같은 명령이 필요한 경우, 인수는 /A "%d" 입니다. + "%f"는 특정 파일의 경로를 나타냅니다. 파일관리자에서 선택한 파일/폴더의 위치를 강조하는 기능에서 사용됩니다. 이 인수는 "파일경로 인수" 항목에서만 사용할 수 있습니다. 파일관리자에 해당 기능이 없거나 잘 모를 경우 "%d" 인수를 사용할 수 있습니다. + 파일관리자 + 프로필 이름 + 파일관리자 경로 + 폴더경로 인수 + 파일경로 인수 + + 중요도 변경 - 높은 수를 넣을수록 상위 결과에 표시됩니다. 5를 시도해보세요. 다른 플러그인 보다 결과를 낮추고 싶다면, 그보다 낮은 수를 입력하세요. + 높은 수를 넣을수록 상위 결과에 표시됩니다. 5를 시도해보세요. 다른 플러그인 보다 결과를 낮춰 표시하고 싶다면, 그보다 낮은 수를 입력하세요. 중요도에 올바른 정수를 입력하세요. - + 예전 액션 키워드 새 액션 키워드 취소 @@ -143,19 +178,20 @@ 새 액션 키워드가 할당된 플러그인이 이미 있습니다. 다른 액션 키워드를 입력하세요. 성공 성공적으로 완료했습니다. - 액션 키워드를 지정하지 않으려면 *를 사용하세요. + 플러그인을 시작하는데 필요한 액션 키워드를 입력하세요. 액션 키워드를 지정하지 않으려면 *를 사용하세요. 이 경우 키워드를 입력하지 않아도 동작합니다. - + 커스텀 플러그인 핫키 + 단축키를 지정하여 특정 쿼리를 자동으로 입력할 수 있습니다. 사용하고 싶은 단축키를 눌러 지정한 후, 사용할 쿼리를 입력하세요. 미리보기 핫키를 사용할 수 없습니다. 다른 핫키를 입력하세요. 플러그인 핫키가 유효하지 않습니다. 업데이트 - + 핫키를 사용할 수 없습니다. - + 버전 시간 수정을 위해 애플리케이션이 어떻게 충돌했는지 알려주세요. @@ -170,17 +206,19 @@ 보고서를 정상적으로 보냈습니다. 보고서를 보내지 못했습니다. Flow Launcher에 문제가 발생했습니다. - - + + 잠시 기다려주세요... - + 새 업데이트 확인 중 새 Flow Launcher 버전({0})을 사용할 수 있습니다. 이미 가장 최신 버전의 Flow Launcher를 사용중입니다. 업데이트 발견 업데이트 중... - Flow Launcher가 유저 정보 데이터를 새버전으로 옮길 수 없습니다. - 프로필 데이터 폴더를 수동으로 {0} 에서 {1}로 옮겨주세요. + + Flow Launcher가 유저 정보 데이터를 새버전으로 옮길 수 없습니다. + 프로필 데이터 폴더를 수동으로 {0} 에서 {1}로 옮겨주세요. + 새 업데이트 소프트웨어 업데이트를 설치하는 중에 오류가 발생했습니다. 업데이트 diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index 718606ff7..8afc07439 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -16,6 +16,7 @@ using ContextMenuStrip = System.Windows.Forms.ContextMenuStrip; using DragEventArgs = System.Windows.DragEventArgs; using KeyEventArgs = System.Windows.Input.KeyEventArgs; using NotifyIcon = System.Windows.Forms.NotifyIcon; +using Flow.Launcher.Infrastructure; namespace Flow.Launcher { @@ -210,6 +211,7 @@ namespace Flow.Launcher exit.Click += (o, e) => Close(); contextMenu.Items.Add(header); contextMenu.Items.Add(open); + gamemode.ToolTip = InternationalizationManager.Instance.GetTranslation("GameModeToolTip"); contextMenu.Items.Add(gamemode); contextMenu.Items.Add(settings); contextMenu.Items.Add(exit); @@ -480,16 +482,14 @@ namespace Flow.Launcher public void InitializeDarkMode() { - if (_settings.DarkMode == "Light") + if (_settings.DarkMode == Constant.Light) { ModernWpf.ThemeManager.Current.ApplicationTheme = ModernWpf.ApplicationTheme.Light; } - else if (_settings.DarkMode == "Dark") + else if (_settings.DarkMode == Constant.Dark) { ModernWpf.ThemeManager.Current.ApplicationTheme = ModernWpf.ApplicationTheme.Dark; } - else - { } } } } \ No newline at end of file diff --git a/Flow.Launcher/PriorityChangeWindow.xaml b/Flow.Launcher/PriorityChangeWindow.xaml index 408f8a3cd..8fb27c470 100644 --- a/Flow.Launcher/PriorityChangeWindow.xaml +++ b/Flow.Launcher/PriorityChangeWindow.xaml @@ -104,15 +104,18 @@ diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs index 51484e53e..49788bfac 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Net; @@ -14,6 +14,7 @@ using Flow.Launcher.Infrastructure.Image; using Flow.Launcher.Plugin; using Flow.Launcher.ViewModel; using Flow.Launcher.Plugin.SharedModels; +using Flow.Launcher.Plugin.SharedCommands; using System.Threading; using System.IO; using Flow.Launcher.Infrastructure.Http; @@ -106,6 +107,14 @@ namespace Flow.Launcher }); } + public void ShellRun(string cmd, string filename = "cmd.exe") + { + var args = filename == "cmd.exe" ? $"/C {cmd}" : $"{cmd}"; + + var startInfo = ShellCommand.SetProcessStartInfo(filename, arguments: args, createNoWindow: true); + ShellCommand.Execute(startInfo); + } + public void StartLoadingBar() => _mainVM.ProgressBarVisibility = Visibility.Visible; public void StopLoadingBar() => _mainVM.ProgressBarVisibility = Visibility.Collapsed; diff --git a/Flow.Launcher/SelectFileManagerWindow.xaml b/Flow.Launcher/SelectFileManagerWindow.xaml index 6092c1da1..7380baa6a 100644 --- a/Flow.Launcher/SelectFileManagerWindow.xaml +++ b/Flow.Launcher/SelectFileManagerWindow.xaml @@ -239,21 +239,19 @@