Skip to content

Commit 91b803d

Browse files
Feature: Open in Terminal RichCommand (#11445)
1 parent cb3c308 commit 91b803d

File tree

8 files changed

+169
-23
lines changed

8 files changed

+169
-23
lines changed
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
using CommunityToolkit.Mvvm.ComponentModel;
2+
using CommunityToolkit.Mvvm.DependencyInjection;
3+
using Files.App.Commands;
4+
using Files.App.Contexts;
5+
using Files.App.Extensions;
6+
using System;
7+
using System.ComponentModel;
8+
using System.Diagnostics;
9+
using System.Linq;
10+
using System.Text;
11+
using System.Threading.Tasks;
12+
using Windows.Storage;
13+
using Windows.System;
14+
15+
namespace Files.App.Actions
16+
{
17+
internal class OpenTerminalAction : ObservableObject, IAction
18+
{
19+
private readonly IContentPageContext context = Ioc.Default.GetRequiredService<IContentPageContext>();
20+
21+
public virtual string Label { get; } = "OpenTerminal".GetLocalizedResource();
22+
23+
public virtual HotKey HotKey { get; } = new((VirtualKey)192, VirtualKeyModifiers.Control);
24+
25+
public RichGlyph Glyph { get; } = new("\uE756");
26+
27+
private bool isExecutable;
28+
public bool IsExecutable => isExecutable;
29+
30+
public OpenTerminalAction()
31+
{
32+
isExecutable = GetIsExecutable();
33+
context.PropertyChanged += Context_PropertyChanged;
34+
}
35+
36+
public Task ExecuteAsync()
37+
{
38+
var terminalStartInfo = GetProcessStartInfo();
39+
if (terminalStartInfo is not null)
40+
{
41+
App.Window.DispatcherQueue.TryEnqueue(() =>
42+
{
43+
try
44+
{
45+
Process.Start(terminalStartInfo);
46+
}
47+
catch (Win32Exception)
48+
{
49+
}
50+
});
51+
}
52+
53+
return Task.CompletedTask;
54+
}
55+
56+
protected virtual ProcessStartInfo? GetProcessStartInfo()
57+
{
58+
var paths = GetPaths();
59+
if (paths.Length is 0)
60+
return null;
61+
62+
var path = paths[0] + (paths[0].EndsWith('\\') ? "\\" : "");
63+
64+
var args = new StringBuilder($"-d \"{path}\"");
65+
for (int i = 1; i < paths.Length; i++)
66+
{
67+
path = paths[i] + (paths[i].EndsWith('\\') ? "\\" : "");
68+
args.Append($" ; nt -d \"{path}\"");
69+
}
70+
71+
return new()
72+
{
73+
FileName = "wt.exe",
74+
Arguments = args.ToString()
75+
};
76+
}
77+
78+
protected string[] GetPaths()
79+
{
80+
if (context.HasSelection)
81+
{
82+
return context.SelectedItems!
83+
.Where(item => item.PrimaryItemAttribute is StorageItemTypes.Folder)
84+
.Select(item => item.ItemPath)
85+
.ToArray();
86+
}
87+
else if (context.Folder is not null)
88+
return new string[1] { context.Folder.ItemPath };
89+
90+
return Array.Empty<string>();
91+
}
92+
93+
private bool GetIsExecutable()
94+
{
95+
if (context.PageType is ContentPageTypes.None or ContentPageTypes.Home or ContentPageTypes.RecycleBin or ContentPageTypes.ZipFolder)
96+
return false;
97+
98+
var isFolderNull = context.Folder is null;
99+
100+
if (!context.HasSelection && isFolderNull)
101+
return false;
102+
103+
if (context.SelectedItems.Count > Constants.Actions.MaxSelectedItems)
104+
return false;
105+
106+
return context.SelectedItems.Any(item => item.PrimaryItemAttribute is StorageItemTypes.Folder) || !isFolderNull;
107+
}
108+
109+
private void Context_PropertyChanged(object? sender, PropertyChangedEventArgs e)
110+
{
111+
switch (e.PropertyName)
112+
{
113+
case nameof(IContentPageContext.PageType):
114+
case nameof(IContentPageContext.Folder):
115+
case nameof(IContentPageContext.SelectedItems):
116+
SetProperty(ref isExecutable, GetIsExecutable(), nameof(IsExecutable));
117+
break;
118+
}
119+
}
120+
}
121+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using Files.App.Commands;
2+
using Files.App.Extensions;
3+
using System.Diagnostics;
4+
using Windows.System;
5+
6+
namespace Files.App.Actions
7+
{
8+
internal class OpenTerminalAsAdminAction : OpenTerminalAction
9+
{
10+
public override string Label { get; } = "OpenTerminalAsAdmin".GetLocalizedResource();
11+
12+
public override HotKey HotKey { get; } = new((VirtualKey)192, VirtualKeyModifiers.Control | VirtualKeyModifiers.Shift);
13+
14+
protected override ProcessStartInfo? GetProcessStartInfo()
15+
{
16+
var startInfo = base.GetProcessStartInfo();
17+
if (startInfo is not null)
18+
{
19+
startInfo.Verb = "runas";
20+
startInfo.UseShellExecute = true;
21+
}
22+
23+
return startInfo;
24+
}
25+
}
26+
}

src/Files.App/Commands/CommandCodes.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ public enum CommandCodes
6161
RotateLeft,
6262
RotateRight,
6363

64+
// Open
65+
OpenTerminal,
66+
OpenTerminalAsAdmin,
67+
6468
// Layout
6569
LayoutDecreaseSize,
6670
LayoutIncreaseSize,

src/Files.App/Commands/Manager/CommandManager.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ internal class CommandManager : ICommandManager
6464
public IRichCommand DecompressArchiveToChildFolder => commands[CommandCodes.DecompressArchiveToChildFolder];
6565
public IRichCommand RotateLeft => commands[CommandCodes.RotateLeft];
6666
public IRichCommand RotateRight => commands[CommandCodes.RotateRight];
67+
public IRichCommand OpenTerminal => commands[CommandCodes.OpenTerminal];
68+
public IRichCommand OpenTerminalAsAdmin => commands[CommandCodes.OpenTerminalAsAdmin];
6769
public IRichCommand LayoutDecreaseSize => commands[CommandCodes.LayoutDecreaseSize];
6870
public IRichCommand LayoutIncreaseSize => commands[CommandCodes.LayoutIncreaseSize];
6971
public IRichCommand LayoutDetails => commands[CommandCodes.LayoutDetails];
@@ -168,6 +170,8 @@ public CommandManager()
168170
[CommandCodes.DecompressArchiveToChildFolder] = new DecompressArchiveToChildFolderAction(),
169171
[CommandCodes.RotateLeft] = new RotateLeftAction(),
170172
[CommandCodes.RotateRight] = new RotateRightAction(),
173+
[CommandCodes.OpenTerminal] = new OpenTerminalAction(),
174+
[CommandCodes.OpenTerminalAsAdmin] = new OpenTerminalAsAdminAction(),
171175
[CommandCodes.LayoutDecreaseSize] = new LayoutDecreaseSizeAction(),
172176
[CommandCodes.LayoutIncreaseSize] = new LayoutIncreaseSizeAction(),
173177
[CommandCodes.LayoutDetails] = new LayoutDetailsAction(),

src/Files.App/Commands/Manager/ICommandManager.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ public interface ICommandManager : IEnumerable<IRichCommand>
5555
IRichCommand RotateLeft { get; }
5656
IRichCommand RotateRight { get; }
5757

58+
IRichCommand OpenTerminal { get; }
59+
IRichCommand OpenTerminalAsAdmin { get; }
60+
5861
IRichCommand LayoutDecreaseSize { get; }
5962
IRichCommand LayoutIncreaseSize { get; }
6063
IRichCommand LayoutDetails { get; }

src/Files.App/Constants.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,5 +217,10 @@ public static class GitHub
217217
public const string PrivacyPolicyUrl = @"https://github.com/files-community/Files/blob/main/Privacy.md";
218218
public const string SupportUsUrl = @"https://github.com/sponsors/yaira2";
219219
}
220+
221+
public static class Actions
222+
{
223+
public const int MaxSelectedItems = 5;
224+
}
220225
}
221226
}

src/Files.App/Strings/en-US/Resources.resw

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2589,6 +2589,12 @@
25892589
<data name="ApplyToAllConflictingItems" xml:space="preserve">
25902590
<value>Apply this action to all conflicting items</value>
25912591
</data>
2592+
<data name="OpenTerminal" xml:space="preserve">
2593+
<value>Open in terminal</value>
2594+
</data>
2595+
<data name="OpenTerminalAsAdmin" xml:space="preserve">
2596+
<value>Open in terminal as administrator</value>
2597+
</data>
25922598
<data name="Save" xml:space="preserve">
25932599
<value>Save</value>
25942600
</data>

src/Files.App/Views/BaseShellPage.cs

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,10 @@
2828
using System;
2929
using System.Collections.Generic;
3030
using System.ComponentModel;
31-
using System.Diagnostics;
3231
using System.Linq;
3332
using System.Runtime.CompilerServices;
3433
using System.Threading;
3534
using System.Threading.Tasks;
36-
using Windows.Storage;
3735
using Windows.System;
3836
using Windows.UI.Core;
3937
using SortDirection = Files.Shared.Enums.SortDirection;
@@ -279,27 +277,6 @@ protected void ShellPage_PreviewKeyDown(object sender, KeyRoutedEventArgs args)
279277

280278
switch (c: ctrl, s: shift, a: alt, t: tabInstance, k: args.Key)
281279
{
282-
// Ctrl + ` (accent key), open terminal
283-
case (true, _, false, true, (VirtualKey)192):
284-
285-
// Check if there is a folder selected, if not use the current directory.
286-
string path = FilesystemViewModel.WorkingDirectory;
287-
if (SlimContentPage?.SelectedItem?.PrimaryItemAttribute == StorageItemTypes.Folder)
288-
path = SlimContentPage.SelectedItem.ItemPath;
289-
290-
var terminalStartInfo = new ProcessStartInfo()
291-
{
292-
FileName = "wt.exe",
293-
Arguments = $"-d \"{path}\"",
294-
Verb = shift ? "runas" : "",
295-
UseShellExecute = true
296-
};
297-
DispatcherQueue.TryEnqueue(() => Process.Start(terminalStartInfo));
298-
299-
args.Handled = true;
300-
301-
break;
302-
303280
// Ctrl + space, toggle media playback
304281
case (true, false, false, true, VirtualKey.Space):
305282

0 commit comments

Comments
 (0)