-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Feature: Added experimental terminal integration #13631
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
gave92
wants to merge
79
commits into
files-community:main
Choose a base branch
from
gave92:terminal
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
79 commits
Select commit
Hold shift + click to select a range
2643352
[WIP]
gave92 d550200
[WIP]
gave92 958d508
[WIP]
gave92 1baa326
Initial work
gave92 b8a91fd
Add build files
gave92 afe2401
Added credits to source files
gave92 225cd5c
Add build files for xterm.js
gave92 d302f30
Implement copy and resize
gave92 f05501f
Set background and hide double scrollbars
gave92 907f783
Toggle button & cleanup
gave92 5328496
Start from current folder
gave92 aa841c9
Sync folder to cmd
gave92 b4f00e1
Implemented up sync button
gave92 1dbdf7c
Show up/dw buttons only when terminal is open
gave92 2607f5f
Enable profile selection
gave92 9544c81
Updated comments
gave92 775914c
Test theme change
gave92 3bde3c1
Place classes in project structure
gave92 c5e6f72
Added missing attribution
gave92 7d304b2
Fix accessibility test (temporary)
gave92 3957fdd
Fix webview scrollbars in dark mode
gave92 a064939
Moved Terminal under Utils
gave92 90e1a89
Removed extra colon
gave92 ae07ac7
Fix Release build
gave92 3128532
Set background color in html
gave92 7d5e092
Merge branch 'main' of https://github.com/files-community/Files into …
gave92 835cd75
Fix buttons for wsl
gave92 8699561
Fix webview flashing white
gave92 a5e9913
Merge branch 'main' of https://github.com/files-community/Files into …
gave92 d86d444
Merge branch 'main' of https://github.com/files-community/Files into …
gave92 62ebf9a
Remove extra commands
gave92 500ba21
Improve scrollbar
gave92 fe01e53
Use windows style scrollbars
gave92 4a09f5c
Code suggestions
gave92 c52b100
Switch native methods to CsWin32
gave92 532c70b
Merge branch 'main' of https://github.com/files-community/Files into …
gave92 092560e
Merge branch 'main' of https://github.com/files-community/Files into …
gave92 9ac9c59
Merge branch 'main' of https://github.com/files-community/Files into …
58b5043
Added settings under experimental
ffb57c7
Merge branch 'main' into terminal
yaira2 9df32f4
WIP
57376d0
Call AddWebAllowedObject after navigation
gave92 d2f437d
Multiple terminals
gave92 d08a384
Merge branch 'main' of https://github.com/files-community/Files into …
gave92 2ae01a1
Hide pane when no terminals
gave92 5da01eb
Switch to CsWin32 for process api
gave92 d7d11c3
Add terminal model
gave92 a11b318
Minor change
gave92 940f5cc
Merge branch 'main' into terminal
gave92 3195de9
Minor change
gave92 729f2dc
Merge branch 'terminal' of https://github.com/gave92/files-uwp into t…
gave92 087c81b
Merge branch 'main' of https://github.com/files-community/Files into …
gave92 608b7be
Merge branch 'main' into terminal
yaira2 d71e607
Added margin
yaira2 f688fbe
Merge branch 'main' into terminal
yaira2 4eff052
Merge branch 'main' of https://github.com/files-community/Files into …
gave92 c5caf90
Merge branch 'main' into terminal
gave92 1b41f66
Use WebView2Ex for CornerRadius
gave92 a257fc8
Merge branch 'main' of https://github.com/files-community/Files into …
gave92 f219539
Merge branch 'terminal' of https://github.com/gave92/files-uwp into t…
gave92 82e9087
Readded Newtonsoft.Json dep
gave92 b25fe52
Merge branch 'main' into terminal
yaira2 4a6d925
Revert "Use WebView2Ex for CornerRadius"
gave92 a223806
Stupid solution n.1
gave92 327544c
Merge branch 'terminal' of https://github.com/gave92/files-uwp into t…
gave92 91d2378
Merge branch 'main' into terminal
yaira2 8ef70a0
Merge branch 'main' of https://github.com/files-community/Files into …
gave92 12316cb
Merge branch 'terminal' of https://github.com/gave92/files-uwp into t…
gave92 bb3d854
Switched WebView2Extensions to System.Text.Json
gave92 62148d4
Merge branch 'main' into terminal
yaira2 00ca8ca
Merge branch 'main' into terminal
yaira2 d6e432e
Switch status bar with terminal
yaira2 8141d37
Merge branch 'main' of https://github.com/files-community/Files into …
gave92 6bfe9d5
Merge branch 'terminal' of https://github.com/gave92/files-uwp into t…
gave92 7068a9e
Merge branch 'main' into terminal
gave92 bf9ddce
Merge branch 'main' of https://github.com/files-community/Files into …
gave92 c56f00a
Merge branch 'terminal' of https://github.com/gave92/files-uwp into t…
gave92 7a2a827
Remove duplicates from NativeMethods.txt
gave92 d11a561
Merge branch 'main' into terminal
gave92 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
using Microsoft.UI.Xaml.Controls; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
|
||
namespace Files.App.Data.Models | ||
{ | ||
public class TerminalModel : IDisposable | ||
{ | ||
public string Id { get; init; } | ||
public string Name { get; init; } | ||
public Control Control { get; init; } | ||
|
||
public void Dispose() | ||
{ | ||
(Control as IDisposable)?.Dispose(); | ||
} | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,222 @@ | ||
// Copyright (c) Mahmoud Al-Qudsi, NeoSmart Technoogies. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
using CommunityToolkit.WinUI; | ||
using Microsoft.UI.Xaml.Controls; | ||
using Microsoft.Web.WebView2.Core; | ||
using System.Reflection; | ||
using System.Text; | ||
using System.Text.Encodings.Web; | ||
using Windows.Foundation; | ||
|
||
namespace Files.App.Extensions | ||
{ | ||
using WebViewMessageReceivedHandler = TypedEventHandler<WebView2, CoreWebView2WebMessageReceivedEventArgs>; | ||
|
||
/// <summary> | ||
/// Code modified from https://gist.github.com/mqudsi/ceb4ecee76eb4c32238a438664783480 | ||
/// </summary> | ||
public static class WebView2Extensions | ||
{ | ||
public static void Navigate(this WebView2 webview, Uri url) | ||
{ | ||
webview.Source = url; | ||
} | ||
|
||
private enum PropertyAction | ||
{ | ||
Read = 0, | ||
Write = 1, | ||
} | ||
|
||
private struct WebMessage | ||
{ | ||
public Guid Guid { get; set; } | ||
} | ||
|
||
private struct MethodWebMessage | ||
{ | ||
public long Id { get; set; } | ||
public string Method { get; set; } | ||
public string Args { get; set; } | ||
} | ||
|
||
private struct PropertyWebMessage | ||
{ | ||
public long Id { get; set; } | ||
public string Property { get; set; } | ||
public PropertyAction Action { get; set; } | ||
public string Value { get; set; } | ||
} | ||
|
||
public static List<WebViewMessageReceivedHandler> _handlers = new(); | ||
public static async Task AddWebAllowedObject<T>(this WebView2 webview, string name, T @object) | ||
{ | ||
var sb = new StringBuilder(); | ||
sb.AppendLine($"window.{name} = {{ "); | ||
|
||
var methodsGuid = Guid.NewGuid(); | ||
var methodInfo = typeof(T).GetMethods(BindingFlags.Public | BindingFlags.Instance); | ||
var methods = new Dictionary<string, MethodInfo>(methodInfo.Length); | ||
foreach (var method in methodInfo) | ||
{ | ||
var functionName = $"{char.ToLower(method.Name[0])}{method.Name.Substring(1)}"; | ||
sb.AppendLine($@"{functionName}: function() {{ window.chrome.webview.postMessage(JSON.stringify({{ guid: ""{methodsGuid}"", id: this._callbackIndex++, method: ""{functionName}"", args: JSON.stringify([...arguments]) }})); const promise = new Promise((accept, reject) => this._callbacks.set(this._callbackIndex, {{ accept: accept, reject: reject }})); return promise; }},"); | ||
methods.Add(functionName, method); | ||
} | ||
|
||
var propertiesGuid = Guid.NewGuid(); | ||
var propertyInfo = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance); | ||
var properties = new Dictionary<string, PropertyInfo>(propertyInfo.Length); | ||
//foreach (var property in propertyInfo) | ||
//{ | ||
// var propertyName = $"{char.ToLower(property.Name[0])}{property.Name.Substring(1)}"; | ||
// if (property.CanRead) | ||
// { | ||
// sb.AppendLine($@"get {propertyName}() {{ window.chrome.webview.postMessage(JSON.stringify({{ guid: ""{propertiesGuid}"", id: this._callbackIndex++, property: ""{propertyName}"", action: ""{(int) PropertyAction.Read}"" }})); const promise = new Promise((accept, reject) => this._callbacks.set(this._callbackIndex, {{ accept: accept, reject: reject }})); return promise; }},"); | ||
// } | ||
// if (property.CanWrite) | ||
// { | ||
// sb.AppendLine($@"set {propertyName}(value) {{ window.chrome.webview.postMessage(JSON.stringify({{ guid: ""{propertiesGuid}"", id: this._callbackIndex++, property: ""{propertyName}"", action: ""{(int)PropertyAction.Write}"", value: JSON.stringify(value) }})); const promise = new Promise((accept, reject) => this._callbacks.set(this._callbackIndex, {{ accept: accept, reject: reject }})); return promise; }},"); | ||
// } | ||
// properties[propertyName] = property; | ||
//} | ||
|
||
// Add a map<int, (promiseAccept, promiseReject)> to the object used to resolve results | ||
sb.AppendLine($@"_callbacks: new Map(),"); | ||
// And a shared counter to index into that map | ||
sb.Append($@"_callbackIndex: 0,"); | ||
|
||
sb.AppendLine("}"); | ||
|
||
try | ||
{ | ||
//await webview.ExecuteScriptAsync($"try {{ {sb} }} catch (ex) {{ console.error(ex); }}").AsTask(); | ||
await webview.ExecuteScriptAsync($"{sb}").AsTask(); | ||
} | ||
catch (Exception ex) | ||
{ | ||
// So we can see it in the JS debugger | ||
} | ||
|
||
var handler = (WebViewMessageReceivedHandler)(async (_, e) => | ||
{ | ||
var message = JsonSerializer.Deserialize<WebMessage>(e.TryGetWebMessageAsString(), new JsonSerializerOptions() { PropertyNameCaseInsensitive = true }); | ||
if (message.Guid == methodsGuid) | ||
{ | ||
|
||
var methodMessage = JsonSerializer.Deserialize<MethodWebMessage>(e.TryGetWebMessageAsString(), new JsonSerializerOptions() { PropertyNameCaseInsensitive = true }); | ||
var method = methods[methodMessage.Method]; | ||
try | ||
{ | ||
var args = JsonSerializer.Deserialize<JsonElement[]>(methodMessage.Args).Zip(method.GetParameters(), (val, args) => new { val, args.ParameterType }).Select(item => item.val.Deserialize(item.ParameterType)); | ||
var result = method.Invoke(@object, args.ToArray()); | ||
if (result is object) | ||
{ | ||
var resultType = result.GetType(); | ||
dynamic task = null; | ||
if (resultType.Name.StartsWith("TaskToAsyncOperationAdapter") | ||
|| resultType.IsInstanceOfType(typeof(IAsyncInfo))) | ||
{ | ||
// IAsyncOperation that needs to be converted to a task first | ||
if (resultType.GenericTypeArguments.Length > 0) | ||
{ | ||
var asTask = typeof(WindowsRuntimeSystemExtensions) | ||
.GetMethods(BindingFlags.Public | BindingFlags.Static) | ||
.Where(method => method.GetParameters().Length == 1 | ||
&& method.Name == "AsTask" | ||
&& method.ToString().Contains("Windows.Foundation.IAsyncOperation`1[TResult]")) | ||
.FirstOrDefault(); | ||
|
||
//var asTask = typeof(WindowsRuntimeSystemExtensions) | ||
// .GetMethod(nameof(WindowsRuntimeSystemExtensions.AsTask), | ||
// new[] { typeof(IAsyncOperation<>).MakeGenericType(resultType.GenericTypeArguments[0]) } | ||
// ); | ||
|
||
asTask = asTask.MakeGenericMethod(resultType.GenericTypeArguments[0]); | ||
task = (Task)asTask.Invoke(null, new[] { result }); | ||
} | ||
else | ||
{ | ||
task = WindowsRuntimeSystemExtensions.AsTask((dynamic)result); | ||
} | ||
} | ||
else | ||
{ | ||
var awaiter = resultType.GetMethod(nameof(Task.GetAwaiter)); | ||
if (awaiter is object) | ||
{ | ||
task = result; | ||
} | ||
} | ||
if (task is object) | ||
{ | ||
result = await task; | ||
} | ||
} | ||
var json = JsonSerializer.Serialize(result, new JsonSerializerOptions() { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping }); ; | ||
await webview.ExecuteScriptAsync($@"{name}._callbacks.get({methodMessage.Id}).accept(JSON.parse({json})); {name}._callbacks.delete({methodMessage.Id});"); | ||
} | ||
catch (Exception ex) | ||
{ | ||
var json = JsonSerializer.Serialize(ex, new JsonSerializerOptions() { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping }); | ||
await webview.ExecuteScriptAsync($@"{name}._callbacks.get({methodMessage.Id}).reject(JSON.parse({json})); {name}._callbacks.delete({methodMessage.Id});"); | ||
//throw; | ||
} | ||
} | ||
else if (message.Guid == propertiesGuid) | ||
{ | ||
var propertyMessage = JsonSerializer.Deserialize<PropertyWebMessage>(e.TryGetWebMessageAsString(), new JsonSerializerOptions() { PropertyNameCaseInsensitive = true }); | ||
var property = properties[propertyMessage.Property]; | ||
try | ||
{ | ||
object result; | ||
if (propertyMessage.Action == PropertyAction.Read) | ||
{ | ||
result = property.GetValue(@object); | ||
} | ||
else | ||
{ | ||
var value = JsonSerializer.Deserialize(propertyMessage.Value, property.PropertyType); | ||
property.SetValue(@object, value); | ||
result = new object(); | ||
} | ||
|
||
var json = JsonSerializer.Serialize(result, new JsonSerializerOptions() { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping }); | ||
await webview.ExecuteScriptAsync($@"{name}._callbacks.get({propertyMessage.Id}).accept(JSON.parse({json})); {name}._callbacks.delete({propertyMessage.Id});"); | ||
} | ||
catch (Exception ex) | ||
{ | ||
//var json = JsonSerializer.Serialize(ex, new JsonSerializerOptions() { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping }); | ||
//await webview.ExecuteScriptAsync($@"{name}._callbacks.get({propertyMessage.Id}).reject(JSON.parse({json})); {name}._callbacks.delete({propertyMessage.Id});"); | ||
//throw; | ||
} | ||
} | ||
}); | ||
|
||
_handlers.Add(handler); | ||
webview.WebMessageReceived += handler; | ||
} | ||
|
||
public static async Task<string> InvokeScriptAsync(this WebView2 webview, string function, params object[] args) | ||
{ | ||
var array = JsonSerializer.Serialize(args, new JsonSerializerOptions() { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping }); | ||
string result = null; | ||
// Tested and checked: this dispatch is required, even though the web view is in a different process | ||
await webview.DispatcherQueue.EnqueueAsync(async () => | ||
{ | ||
var script = $"{function}(...{array});"; | ||
try | ||
{ | ||
result = await webview.ExecuteScriptAsync(script).AsTask(); | ||
result = JsonSerializer.Deserialize<string>(result); | ||
} | ||
catch (Exception ex) | ||
{ | ||
} | ||
}, Microsoft.UI.Dispatching.DispatcherQueuePriority.Normal); | ||
|
||
return result; | ||
} | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.