mirror of
https://github.com/chylex/TweetDuck.git
synced 2025-04-30 14:34:09 +02:00
Major refactor to abstract app logic into libraries
This commit is contained in:
parent
68582f6973
commit
bf9a0226be
Application
Browser
Adapters
CefBrowserComponent.csCefContextMenuActionRegistry.csCefContextMenuModel.csCefResourceHandlerFactory.csCefResourceHandlerRegistry.csCefResourceProvider.csCefResourceRequestHandler.csCefSchemeHandlerFactory.csCefScriptExecutor.cs
Bridge
Data
FormBrowser.Designer.csFormBrowser.csHandling
BrowserProcessHandler.csContextMenuBase.csContextMenuBrowser.csContextMenuGuide.csContextMenuNotification.csCustomKeyboardHandler.csCustomLifeSpanHandler.csDragHandlerBrowser.csFileDialogHandler.cs
Filters
JavaScriptDialogHandler.csKeyboardHandlerBrowser.csKeyboardHandlerNotification.csRequestHandlerBase.csResourceRequestHandler.csResourceRequestHandlerBase.csResourceRequestHandlerBrowser.csResponseFilter.csNotification
FormNotificationBase.csFormNotificationExample.csFormNotificationMain.csFormNotificationTweet.cs
TweetDeckBrowser.csTweetDeckInterfaceImpl.csScreenshot
SoundNotification.csConfiguration
Controls
Dialogs
FormAbout.csFormGuide.csFormPlugins.csFormSettings.cs
Settings
DialogSettingsCefArgs.csDialogSettingsManage.csDialogSettingsRestart.csTabSettingsAdvanced.csTabSettingsFeedback.csTabSettingsGeneral.csTabSettingsNotifications.cs
WindowState.csManagement
Plugins
Program.csProperties
Reporter.csResources
Content
images
notification/example
tweetdeck
Guide
ResourceHotSwap.csResourceProvider.csResourceUtils.csResourcesSchemeFactory.csUpdates
Utils
lib/TweetLib.Browser
Base
Contexts
Events
52
Application/DialogHandler.cs
Normal file
52
Application/DialogHandler.cs
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using TweetDuck.Dialogs;
|
||||||
|
using TweetDuck.Management;
|
||||||
|
using TweetLib.Core.Application;
|
||||||
|
using TweetLib.Core.Systems.Dialogs;
|
||||||
|
|
||||||
|
namespace TweetDuck.Application {
|
||||||
|
sealed class DialogHandler : IAppDialogHandler {
|
||||||
|
public void Information(string caption, string text, string buttonAccept, string buttonCancel = null) {
|
||||||
|
FormManager.RunOnUIThreadAsync(() => FormMessage.Information(caption, text, buttonAccept, buttonCancel));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Error(string caption, string text, string buttonAccept, string buttonCancel = null) {
|
||||||
|
FormManager.RunOnUIThreadAsync(() => FormMessage.Error(caption, text, buttonAccept, buttonCancel));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SaveFile(SaveFileDialogSettings settings, Action<string> onAccepted) {
|
||||||
|
static string FormatFilter(FileDialogFilter filter) {
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
builder.Append(filter.Name);
|
||||||
|
|
||||||
|
var extensions = string.Join(";", filter.Extensions.Select(ext => "*" + ext));
|
||||||
|
if (extensions.Length > 0) {
|
||||||
|
builder.Append(" (");
|
||||||
|
builder.Append(extensions);
|
||||||
|
builder.Append(")");
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.Append('|');
|
||||||
|
builder.Append(extensions.Length == 0 ? "*.*" : extensions);
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
FormManager.RunOnUIThreadAsync(() => {
|
||||||
|
using SaveFileDialog dialog = new SaveFileDialog {
|
||||||
|
AutoUpgradeEnabled = true,
|
||||||
|
OverwritePrompt = settings.OverwritePrompt,
|
||||||
|
Title = settings.DialogTitle,
|
||||||
|
FileName = settings.FileName,
|
||||||
|
Filter = settings.Filters == null ? null : string.Join("|", settings.Filters.Select(FormatFilter))
|
||||||
|
};
|
||||||
|
|
||||||
|
if (dialog.ShowDialog() == DialogResult.OK) {
|
||||||
|
onAccepted(dialog.FileName);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
68
Application/Logger.cs
Normal file
68
Application/Logger.cs
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
using TweetLib.Core;
|
||||||
|
using TweetLib.Core.Application;
|
||||||
|
|
||||||
|
namespace TweetDuck.Application {
|
||||||
|
sealed class Logger : IAppLogger {
|
||||||
|
private string LogFilePath => Path.Combine(App.StoragePath, logFileName);
|
||||||
|
|
||||||
|
private readonly string logFileName;
|
||||||
|
|
||||||
|
public Logger(string logFileName) {
|
||||||
|
this.logFileName = logFileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IAppLogger.Debug(string message) {
|
||||||
|
#if DEBUG
|
||||||
|
return Log("DEBUG", message);
|
||||||
|
#else
|
||||||
|
return Arguments.HasFlag(Arguments.ArgLogging) && Log(message);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IAppLogger.Info(string message) {
|
||||||
|
return Log("INFO", message);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IAppLogger.Error(string message) {
|
||||||
|
return Log("ERROR", message);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IAppLogger.OpenLogFile() {
|
||||||
|
try {
|
||||||
|
using (Process.Start(LogFilePath)) {}
|
||||||
|
} catch (Exception) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool Log(string level, string message) {
|
||||||
|
#if DEBUG
|
||||||
|
Debug.WriteLine("[" + level + "] " + message);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
string logFilePath = LogFilePath;
|
||||||
|
|
||||||
|
StringBuilder build = new StringBuilder();
|
||||||
|
|
||||||
|
if (!File.Exists(logFilePath)) {
|
||||||
|
build.Append("Please, report all issues to: https://github.com/chylex/TweetDuck/issues\r\n\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
build.Append("[").Append(DateTime.Now.ToString("G", Lib.Culture)).Append("]\r\n");
|
||||||
|
build.Append(message).Append("\r\n\r\n");
|
||||||
|
|
||||||
|
try {
|
||||||
|
File.AppendAllText(logFilePath, build.ToString(), Encoding.UTF8);
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +1,19 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Drawing;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using TweetDuck.Browser;
|
||||||
|
using TweetDuck.Configuration;
|
||||||
|
using TweetDuck.Dialogs;
|
||||||
|
using TweetDuck.Management;
|
||||||
using TweetLib.Core;
|
using TweetLib.Core;
|
||||||
using TweetLib.Core.Application;
|
using TweetLib.Core.Application;
|
||||||
|
using TweetLib.Core.Features.Twitter;
|
||||||
|
|
||||||
namespace TweetDuck.Application {
|
namespace TweetDuck.Application {
|
||||||
class SystemHandler : IAppSystemHandler {
|
sealed class SystemHandler : IAppSystemHandler {
|
||||||
void IAppSystemHandler.OpenAssociatedProgram(string path) {
|
public void OpenAssociatedProgram(string path) {
|
||||||
try {
|
try {
|
||||||
using (Process.Start(new ProcessStartInfo {
|
using (Process.Start(new ProcessStartInfo {
|
||||||
FileName = path,
|
FileName = path,
|
||||||
@ -17,7 +24,66 @@ void IAppSystemHandler.OpenAssociatedProgram(string path) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void IAppSystemHandler.OpenFileExplorer(string path) {
|
public void OpenBrowser(string url) {
|
||||||
|
if (string.IsNullOrWhiteSpace(url)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
FormManager.RunOnUIThreadAsync(() => {
|
||||||
|
var config = Program.Config.User;
|
||||||
|
|
||||||
|
switch (TwitterUrls.Check(url)) {
|
||||||
|
case TwitterUrls.UrlType.Fine:
|
||||||
|
string browserPath = config.BrowserPath;
|
||||||
|
|
||||||
|
if (browserPath == null || !File.Exists(browserPath)) {
|
||||||
|
OpenAssociatedProgram(url);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
string quotedUrl = '"' + url + '"';
|
||||||
|
string browserArgs = config.BrowserPathArgs == null ? quotedUrl : config.BrowserPathArgs + ' ' + quotedUrl;
|
||||||
|
|
||||||
|
try {
|
||||||
|
using (Process.Start(browserPath, browserArgs)) {}
|
||||||
|
} catch (Exception e) {
|
||||||
|
App.ErrorHandler.HandleException("Error Opening Browser", "Could not open the browser.", true, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TwitterUrls.UrlType.Tracking:
|
||||||
|
if (config.IgnoreTrackingUrlWarning) {
|
||||||
|
goto case TwitterUrls.UrlType.Fine;
|
||||||
|
}
|
||||||
|
|
||||||
|
using (FormMessage form = new FormMessage("Blocked URL", "TweetDuck has blocked a tracking url due to privacy concerns. Do you want to visit it anyway?\n" + url, MessageBoxIcon.Warning)) {
|
||||||
|
form.AddButton(FormMessage.No, DialogResult.No, ControlType.Cancel | ControlType.Focused);
|
||||||
|
form.AddButton(FormMessage.Yes, DialogResult.Yes, ControlType.Accept);
|
||||||
|
form.AddButton("Always Visit", DialogResult.Ignore);
|
||||||
|
|
||||||
|
DialogResult result = form.ShowDialog();
|
||||||
|
|
||||||
|
if (result == DialogResult.Ignore) {
|
||||||
|
config.IgnoreTrackingUrlWarning = true;
|
||||||
|
config.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result == DialogResult.Ignore || result == DialogResult.Yes) {
|
||||||
|
goto case TwitterUrls.UrlType.Fine;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TwitterUrls.UrlType.Invalid:
|
||||||
|
FormMessage.Warning("Blocked URL", "A potentially malicious or invalid URL was blocked from opening:\n" + url, FormMessage.OK);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OpenFileExplorer(string path) {
|
||||||
if (File.Exists(path)) {
|
if (File.Exists(path)) {
|
||||||
using (Process.Start("explorer.exe", "/select,\"" + path.Replace('/', '\\') + "\"")) {}
|
using (Process.Start("explorer.exe", "/select,\"" + path.Replace('/', '\\') + "\"")) {}
|
||||||
}
|
}
|
||||||
@ -25,5 +91,62 @@ void IAppSystemHandler.OpenFileExplorer(string path) {
|
|||||||
using (Process.Start("explorer.exe", '"' + path.Replace('/', '\\') + '"')) {}
|
using (Process.Start("explorer.exe", '"' + path.Replace('/', '\\') + '"')) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void CopyImageFromFile(string path) {
|
||||||
|
FormManager.RunOnUIThreadAsync(() => {
|
||||||
|
Image image;
|
||||||
|
|
||||||
|
try {
|
||||||
|
image = Image.FromFile(path);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
FormMessage.Error("Copy Image", "An error occurred while copying the image: " + ex.Message, FormMessage.OK);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ClipboardManager.SetImage(image);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CopyText(string text) {
|
||||||
|
FormManager.RunOnUIThreadAsync(() => ClipboardManager.SetText(text, TextDataFormat.UnicodeText));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SearchText(string text) {
|
||||||
|
if (string.IsNullOrWhiteSpace(text)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
FormManager.RunOnUIThreadAsync(() => {
|
||||||
|
var config = Program.Config.User;
|
||||||
|
string searchUrl = config.SearchEngineUrl;
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(searchUrl)) {
|
||||||
|
if (FormMessage.Question("Search Options", "You have not configured a default search engine yet, would you like to do it now?", FormMessage.Yes, FormMessage.No)) {
|
||||||
|
bool wereSettingsOpen = FormManager.TryFind<FormSettings>() != null;
|
||||||
|
|
||||||
|
FormManager.TryFind<FormBrowser>()?.OpenSettings();
|
||||||
|
|
||||||
|
if (wereSettingsOpen) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
FormSettings settings = FormManager.TryFind<FormSettings>();
|
||||||
|
|
||||||
|
if (settings == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
settings.FormClosed += (sender, args) => {
|
||||||
|
if (args.CloseReason == CloseReason.UserClosing && config.SearchEngineUrl != searchUrl) {
|
||||||
|
SearchText(text);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
OpenBrowser(searchUrl + Uri.EscapeUriString(text));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
108
Browser/Adapters/CefBrowserComponent.cs
Normal file
108
Browser/Adapters/CefBrowserComponent.cs
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
using System;
|
||||||
|
using CefSharp;
|
||||||
|
using CefSharp.WinForms;
|
||||||
|
using TweetDuck.Browser.Handling;
|
||||||
|
using TweetDuck.Utils;
|
||||||
|
using TweetLib.Browser.Base;
|
||||||
|
using TweetLib.Browser.Events;
|
||||||
|
using TweetLib.Browser.Interfaces;
|
||||||
|
using TweetLib.Utils.Static;
|
||||||
|
using IContextMenuHandler = TweetLib.Browser.Interfaces.IContextMenuHandler;
|
||||||
|
using IResourceRequestHandler = TweetLib.Browser.Interfaces.IResourceRequestHandler;
|
||||||
|
|
||||||
|
namespace TweetDuck.Browser.Adapters {
|
||||||
|
internal abstract class CefBrowserComponent : IBrowserComponent {
|
||||||
|
public bool Ready { get; private set; }
|
||||||
|
|
||||||
|
public string Url => browser.Address;
|
||||||
|
|
||||||
|
public IFileDownloader FileDownloader => TwitterFileDownloader.Instance;
|
||||||
|
|
||||||
|
public event EventHandler<BrowserLoadedEventArgs> BrowserLoaded;
|
||||||
|
public event EventHandler<PageLoadEventArgs> PageLoadStart;
|
||||||
|
public event EventHandler<PageLoadEventArgs> PageLoadEnd;
|
||||||
|
|
||||||
|
private readonly ChromiumWebBrowser browser;
|
||||||
|
|
||||||
|
protected CefBrowserComponent(ChromiumWebBrowser browser) {
|
||||||
|
this.browser = browser;
|
||||||
|
this.browser.JsDialogHandler = new JavaScriptDialogHandler();
|
||||||
|
this.browser.LifeSpanHandler = new CustomLifeSpanHandler();
|
||||||
|
this.browser.LoadingStateChanged += OnLoadingStateChanged;
|
||||||
|
this.browser.LoadError += OnLoadError;
|
||||||
|
this.browser.FrameLoadStart += OnFrameLoadStart;
|
||||||
|
this.browser.FrameLoadEnd += OnFrameLoadEnd;
|
||||||
|
this.browser.SetupZoomEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
void IBrowserComponent.Setup(BrowserSetup setup) {
|
||||||
|
browser.MenuHandler = SetupContextMenu(setup.ContextMenuHandler);
|
||||||
|
browser.ResourceRequestHandlerFactory = SetupResourceHandlerFactory(setup.ResourceRequestHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract ContextMenuBase SetupContextMenu(IContextMenuHandler handler);
|
||||||
|
|
||||||
|
protected abstract CefResourceHandlerFactory SetupResourceHandlerFactory(IResourceRequestHandler handler);
|
||||||
|
|
||||||
|
private void OnLoadingStateChanged(object sender, LoadingStateChangedEventArgs e) {
|
||||||
|
if (!e.IsLoading) {
|
||||||
|
Ready = true;
|
||||||
|
browser.LoadingStateChanged -= OnLoadingStateChanged;
|
||||||
|
BrowserLoaded?.Invoke(this, new BrowserLoadedEventArgsImpl(browser));
|
||||||
|
BrowserLoaded = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class BrowserLoadedEventArgsImpl : BrowserLoadedEventArgs {
|
||||||
|
private readonly IWebBrowser browser;
|
||||||
|
|
||||||
|
public BrowserLoadedEventArgsImpl(IWebBrowser browser) {
|
||||||
|
this.browser = browser;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void AddDictionaryWords(params string[] words) {
|
||||||
|
foreach (string word in words) {
|
||||||
|
browser.AddWordToDictionary(word);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnLoadError(object sender, LoadErrorEventArgs e) {
|
||||||
|
if (e.ErrorCode == CefErrorCode.Aborted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!e.FailedUrl.StartsWithOrdinal("td://resources/error/")) {
|
||||||
|
string errorName = Enum.GetName(typeof(CefErrorCode), e.ErrorCode);
|
||||||
|
string errorTitle = StringUtils.ConvertPascalCaseToScreamingSnakeCase(errorName ?? string.Empty);
|
||||||
|
browser.Load("td://resources/error/error.html#" + Uri.EscapeDataString(errorTitle));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnFrameLoadStart(object sender, FrameLoadStartEventArgs e) {
|
||||||
|
if (e.Frame.IsMain) {
|
||||||
|
PageLoadStart?.Invoke(this, new PageLoadEventArgs(e.Url));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnFrameLoadEnd(object sender, FrameLoadEndEventArgs e) {
|
||||||
|
if (e.Frame.IsMain) {
|
||||||
|
PageLoadEnd?.Invoke(this, new PageLoadEventArgs(e.Url));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AttachBridgeObject(string name, object bridge) {
|
||||||
|
browser.JavascriptObjectRepository.Settings.LegacyBindingEnabled = true;
|
||||||
|
browser.JavascriptObjectRepository.Register(name, bridge, isAsync: true, BindingOptions.DefaultBinder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RunFunction(string name, params object[] args) {
|
||||||
|
browser.BrowserCore.ExecuteScriptAsync(name, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RunScript(string identifier, string script) {
|
||||||
|
using IFrame frame = browser.GetMainFrame();
|
||||||
|
frame.ExecuteJavaScriptAsync(script, identifier, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
28
Browser/Adapters/CefContextMenuActionRegistry.cs
Normal file
28
Browser/Adapters/CefContextMenuActionRegistry.cs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using CefSharp;
|
||||||
|
|
||||||
|
namespace TweetDuck.Browser.Adapters {
|
||||||
|
internal sealed class CefContextMenuActionRegistry {
|
||||||
|
private readonly Dictionary<CefMenuCommand, Action> actions = new Dictionary<CefMenuCommand, Action>();
|
||||||
|
|
||||||
|
public CefMenuCommand AddAction(Action action) {
|
||||||
|
CefMenuCommand id = CefMenuCommand.UserFirst + 500 + actions.Count;
|
||||||
|
actions[id] = action;
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Execute(CefMenuCommand id) {
|
||||||
|
if (actions.TryGetValue(id, out var action)) {
|
||||||
|
action();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear() {
|
||||||
|
actions.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
80
Browser/Adapters/CefContextMenuModel.cs
Normal file
80
Browser/Adapters/CefContextMenuModel.cs
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
using System;
|
||||||
|
using CefSharp;
|
||||||
|
using TweetLib.Browser.Contexts;
|
||||||
|
using TweetLib.Browser.Interfaces;
|
||||||
|
using TweetLib.Core.Features.TweetDeck;
|
||||||
|
using TweetLib.Core.Features.Twitter;
|
||||||
|
|
||||||
|
namespace TweetDuck.Browser.Adapters {
|
||||||
|
internal sealed class CefContextMenuModel : IContextMenuBuilder {
|
||||||
|
private readonly IMenuModel model;
|
||||||
|
private readonly CefContextMenuActionRegistry actionRegistry;
|
||||||
|
|
||||||
|
public CefContextMenuModel(IMenuModel model, CefContextMenuActionRegistry actionRegistry) {
|
||||||
|
this.model = model;
|
||||||
|
this.actionRegistry = actionRegistry;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddAction(string name, Action action) {
|
||||||
|
var id = actionRegistry.AddAction(action);
|
||||||
|
model.AddItem(id, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddActionWithCheck(string name, bool isChecked, Action action) {
|
||||||
|
var id = actionRegistry.AddAction(action);
|
||||||
|
model.AddCheckItem(id, name);
|
||||||
|
model.SetChecked(id, isChecked);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddSeparator() {
|
||||||
|
if (model.Count > 0 && model.GetTypeAt(model.Count - 1) != MenuItemType.Separator) { // do not add separators if there is nothing to separate
|
||||||
|
model.AddSeparator();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Context CreateContext(IContextMenuParams parameters, TweetDeckExtraContext extraContext, ImageQuality imageQuality) {
|
||||||
|
var context = new Context();
|
||||||
|
var flags = parameters.TypeFlags;
|
||||||
|
|
||||||
|
var tweet = extraContext?.Tweet;
|
||||||
|
if (tweet != null && !flags.HasFlag(ContextMenuType.Editable)) {
|
||||||
|
context.Tweet = tweet;
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Link = GetLink(parameters, extraContext);
|
||||||
|
context.Media = GetMedia(parameters, extraContext, imageQuality);
|
||||||
|
|
||||||
|
if (flags.HasFlag(ContextMenuType.Selection)) {
|
||||||
|
context.Selection = new Selection(parameters.SelectionText, flags.HasFlag(ContextMenuType.Editable));
|
||||||
|
}
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Link? GetLink(IContextMenuParams parameters, TweetDeckExtraContext extraContext) {
|
||||||
|
var link = extraContext?.Link;
|
||||||
|
if (link != null) {
|
||||||
|
return link;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parameters.TypeFlags.HasFlag(ContextMenuType.Link) && extraContext?.Media == null) {
|
||||||
|
return new Link(parameters.LinkUrl, parameters.UnfilteredLinkUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Media? GetMedia(IContextMenuParams parameters, TweetDeckExtraContext extraContext, ImageQuality imageQuality) {
|
||||||
|
var media = extraContext?.Media;
|
||||||
|
if (media != null) {
|
||||||
|
return media;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parameters.TypeFlags.HasFlag(ContextMenuType.Media) && parameters.HasImageContents) {
|
||||||
|
return new Media(Media.Type.Image, TwitterUrls.GetMediaLink(parameters.SourceUrl, imageQuality));
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
23
Browser/Adapters/CefResourceHandlerFactory.cs
Normal file
23
Browser/Adapters/CefResourceHandlerFactory.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using CefSharp;
|
||||||
|
using IResourceRequestHandler = TweetLib.Browser.Interfaces.IResourceRequestHandler;
|
||||||
|
|
||||||
|
namespace TweetDuck.Browser.Adapters {
|
||||||
|
sealed class CefResourceHandlerFactory : IResourceRequestHandlerFactory {
|
||||||
|
bool IResourceRequestHandlerFactory.HasHandlers => registry != null;
|
||||||
|
|
||||||
|
private readonly CefResourceRequestHandler resourceRequestHandler;
|
||||||
|
private readonly CefResourceHandlerRegistry registry;
|
||||||
|
|
||||||
|
public CefResourceHandlerFactory(IResourceRequestHandler resourceRequestHandler, CefResourceHandlerRegistry registry) {
|
||||||
|
this.resourceRequestHandler = new CefResourceRequestHandler(registry, resourceRequestHandler);
|
||||||
|
this.registry = registry;
|
||||||
|
}
|
||||||
|
|
||||||
|
[SuppressMessage("ReSharper", "RedundantAssignment")]
|
||||||
|
CefSharp.IResourceRequestHandler IResourceRequestHandlerFactory.GetResourceRequestHandler(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request, bool isNavigation, bool isDownload, string requestInitiator, ref bool disableDefaultHandling) {
|
||||||
|
disableDefaultHandling = registry != null && registry.HasHandler(request.Url);
|
||||||
|
return resourceRequestHandler;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
42
Browser/Adapters/CefResourceHandlerRegistry.cs
Normal file
42
Browser/Adapters/CefResourceHandlerRegistry.cs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Text;
|
||||||
|
using CefSharp;
|
||||||
|
|
||||||
|
namespace TweetDuck.Browser.Adapters {
|
||||||
|
internal sealed class CefResourceHandlerRegistry {
|
||||||
|
private readonly ConcurrentDictionary<string, Func<IResourceHandler>> resourceHandlers = new ConcurrentDictionary<string, Func<IResourceHandler>>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
public bool HasHandler(string url) {
|
||||||
|
return resourceHandlers.ContainsKey(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IResourceHandler GetHandler(string url) {
|
||||||
|
return resourceHandlers.TryGetValue(url, out var handler) ? handler() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Register(string url, Func<IResourceHandler> factory) {
|
||||||
|
if (!Uri.TryCreate(url, UriKind.Absolute, out Uri uri)) {
|
||||||
|
throw new ArgumentException("Resource handler URL must be absolute!");
|
||||||
|
}
|
||||||
|
|
||||||
|
resourceHandlers.AddOrUpdate(uri.AbsoluteUri, factory, (key, prev) => factory);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterStatic(string url, byte[] staticData, string mimeType = ResourceHandler.DefaultMimeType) {
|
||||||
|
Register(url, () => ResourceHandler.FromByteArray(staticData, mimeType));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterStatic(string url, string staticData, string mimeType = ResourceHandler.DefaultMimeType) {
|
||||||
|
Register(url, () => ResourceHandler.FromString(staticData, Encoding.UTF8, mimeType: mimeType));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterDynamic(string url, IResourceHandler handler) {
|
||||||
|
Register(url, () => handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Unregister(string url) {
|
||||||
|
resourceHandlers.TryRemove(url, out _);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
31
Browser/Adapters/CefResourceProvider.cs
Normal file
31
Browser/Adapters/CefResourceProvider.cs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
using System.IO;
|
||||||
|
using System.Net;
|
||||||
|
using System.Text;
|
||||||
|
using CefSharp;
|
||||||
|
using TweetLib.Browser.Interfaces;
|
||||||
|
|
||||||
|
namespace TweetDuck.Browser.Adapters {
|
||||||
|
internal sealed class ResourceProvider : IResourceProvider<IResourceHandler> {
|
||||||
|
public IResourceHandler Status(HttpStatusCode code, string message) {
|
||||||
|
var handler = CreateHandler(Encoding.UTF8.GetBytes(message));
|
||||||
|
handler.StatusCode = (int) code;
|
||||||
|
return handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IResourceHandler File(byte[] contents, string extension) {
|
||||||
|
if (contents.Length == 0) {
|
||||||
|
return Status(HttpStatusCode.NoContent, "File is empty."); // FromByteArray crashes CEF internals with no contents
|
||||||
|
}
|
||||||
|
|
||||||
|
var handler = CreateHandler(contents);
|
||||||
|
handler.MimeType = Cef.GetMimeType(extension);
|
||||||
|
return handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ResourceHandler CreateHandler(byte[] bytes) {
|
||||||
|
var handler = ResourceHandler.FromStream(new MemoryStream(bytes), autoDisposeStream: true);
|
||||||
|
handler.Headers.Set("Access-Control-Allow-Origin", "*");
|
||||||
|
return handler;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
77
Browser/Adapters/CefResourceRequestHandler.cs
Normal file
77
Browser/Adapters/CefResourceRequestHandler.cs
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using CefSharp;
|
||||||
|
using CefSharp.Handler;
|
||||||
|
using TweetDuck.Browser.Handling;
|
||||||
|
using TweetLib.Browser.Interfaces;
|
||||||
|
using TweetLib.Browser.Request;
|
||||||
|
using IResourceRequestHandler = TweetLib.Browser.Interfaces.IResourceRequestHandler;
|
||||||
|
using ResourceType = TweetLib.Browser.Request.ResourceType;
|
||||||
|
|
||||||
|
namespace TweetDuck.Browser.Adapters {
|
||||||
|
sealed class CefResourceRequestHandler : ResourceRequestHandler {
|
||||||
|
private readonly CefResourceHandlerRegistry resourceHandlerRegistry;
|
||||||
|
private readonly IResourceRequestHandler resourceRequestHandler;
|
||||||
|
private readonly Dictionary<ulong, IResponseProcessor> responseProcessors = new Dictionary<ulong, IResponseProcessor>();
|
||||||
|
|
||||||
|
public CefResourceRequestHandler(CefResourceHandlerRegistry resourceHandlerRegistry, IResourceRequestHandler resourceRequestHandler) {
|
||||||
|
this.resourceHandlerRegistry = resourceHandlerRegistry;
|
||||||
|
this.resourceRequestHandler = resourceRequestHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override CefReturnValue OnBeforeResourceLoad(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request, IRequestCallback callback) {
|
||||||
|
if (request.ResourceType == CefSharp.ResourceType.CspReport) {
|
||||||
|
callback.Dispose();
|
||||||
|
return CefReturnValue.Cancel;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resourceRequestHandler != null) {
|
||||||
|
var result = resourceRequestHandler.Handle(request.Url, TranslateResourceType(request.ResourceType));
|
||||||
|
|
||||||
|
switch (result) {
|
||||||
|
case RequestHandleResult.Redirect redirect:
|
||||||
|
request.Url = redirect.Url;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RequestHandleResult.Process process:
|
||||||
|
request.SetHeaderByName("Accept-Encoding", "identity", overwrite: true);
|
||||||
|
responseProcessors[request.Identifier] = process.Processor;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RequestHandleResult.Cancel _:
|
||||||
|
callback.Dispose();
|
||||||
|
return CefReturnValue.Cancel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.OnBeforeResourceLoad(chromiumWebBrowser, browser, frame, request, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override IResourceHandler GetResourceHandler(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request) {
|
||||||
|
return resourceHandlerRegistry?.GetHandler(request.Url);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override IResponseFilter GetResourceResponseFilter(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IResponse response) {
|
||||||
|
if (responseProcessors.TryGetValue(request.Identifier, out var processor) && int.TryParse(response.Headers["Content-Length"], out int totalBytes)) {
|
||||||
|
return new ResponseFilter(processor, totalBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.GetResourceResponseFilter(browserControl, browser, frame, request, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnResourceLoadComplete(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request, IResponse response, UrlRequestStatus status, long receivedContentLength) {
|
||||||
|
responseProcessors.Remove(request.Identifier);
|
||||||
|
base.OnResourceLoadComplete(chromiumWebBrowser, browser, frame, request, response, status, receivedContentLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ResourceType TranslateResourceType(CefSharp.ResourceType resourceType) {
|
||||||
|
return resourceType switch {
|
||||||
|
CefSharp.ResourceType.MainFrame => ResourceType.MainFrame,
|
||||||
|
CefSharp.ResourceType.Script => ResourceType.Script,
|
||||||
|
CefSharp.ResourceType.Stylesheet => ResourceType.Stylesheet,
|
||||||
|
CefSharp.ResourceType.Xhr => ResourceType.Xhr,
|
||||||
|
CefSharp.ResourceType.Image => ResourceType.Image,
|
||||||
|
_ => ResourceType.Unknown
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
29
Browser/Adapters/CefSchemeHandlerFactory.cs
Normal file
29
Browser/Adapters/CefSchemeHandlerFactory.cs
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
using System;
|
||||||
|
using CefSharp;
|
||||||
|
using CefSharp.WinForms;
|
||||||
|
using TweetLib.Browser.Interfaces;
|
||||||
|
|
||||||
|
namespace TweetDuck.Browser.Adapters {
|
||||||
|
internal sealed class CefSchemeHandlerFactory : ISchemeHandlerFactory {
|
||||||
|
public static void Register(CefSettings settings, ICustomSchemeHandler<IResourceHandler> handler) {
|
||||||
|
settings.RegisterScheme(new CefCustomScheme {
|
||||||
|
SchemeName = handler.Protocol,
|
||||||
|
IsStandard = false,
|
||||||
|
IsSecure = true,
|
||||||
|
IsCorsEnabled = true,
|
||||||
|
IsCSPBypassing = true,
|
||||||
|
SchemeHandlerFactory = new CefSchemeHandlerFactory(handler)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly ICustomSchemeHandler<IResourceHandler> handler;
|
||||||
|
|
||||||
|
private CefSchemeHandlerFactory(ICustomSchemeHandler<IResourceHandler> handler) {
|
||||||
|
this.handler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IResourceHandler Create(IBrowser browser, IFrame frame, string schemeName, IRequest request) {
|
||||||
|
return Uri.TryCreate(request.Url, UriKind.Absolute, out var uri) ? handler.Resolve(uri) : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,78 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using CefSharp;
|
|
||||||
using TweetDuck.Resources;
|
|
||||||
using TweetDuck.Utils;
|
|
||||||
using TweetLib.Core.Browser;
|
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Adapters {
|
|
||||||
sealed class CefScriptExecutor : IScriptExecutor {
|
|
||||||
private readonly IWebBrowser browser;
|
|
||||||
|
|
||||||
public CefScriptExecutor(IWebBrowser browser) {
|
|
||||||
this.browser = browser;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RunFunction(string name, params object[] args) {
|
|
||||||
browser.ExecuteJsAsync(name, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RunScript(string identifier, string script) {
|
|
||||||
using IFrame frame = browser.GetMainFrame();
|
|
||||||
RunScript(frame, script, identifier);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RunBootstrap(string moduleNamespace) {
|
|
||||||
using IFrame frame = browser.GetMainFrame();
|
|
||||||
RunBootstrap(frame, moduleNamespace);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helpers
|
|
||||||
|
|
||||||
public static void RunScript(IFrame frame, string script, string identifier) {
|
|
||||||
if (script != null) {
|
|
||||||
frame.ExecuteJavaScriptAsync(script, identifier, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void RunBootstrap(IFrame frame, string moduleNamespace) {
|
|
||||||
string script = GetBootstrapScript(moduleNamespace, includeStylesheets: true);
|
|
||||||
|
|
||||||
if (script != null) {
|
|
||||||
RunScript(frame, script, "bootstrap");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string GetBootstrapScript(string moduleNamespace, bool includeStylesheets) {
|
|
||||||
string script = ResourceUtils.ReadFileOrNull("bootstrap.js");
|
|
||||||
|
|
||||||
if (script == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
string path = Path.Combine(Program.ResourcesPath, moduleNamespace);
|
|
||||||
var files = new DirectoryInfo(path).GetFiles();
|
|
||||||
|
|
||||||
var moduleNames = new List<string>();
|
|
||||||
var stylesheetNames = new List<string>();
|
|
||||||
|
|
||||||
foreach (var file in files) {
|
|
||||||
var ext = Path.GetExtension(file.Name);
|
|
||||||
|
|
||||||
var targetList = ext switch {
|
|
||||||
".js" => moduleNames,
|
|
||||||
".css" => includeStylesheets ? stylesheetNames : null,
|
|
||||||
_ => null
|
|
||||||
};
|
|
||||||
|
|
||||||
targetList?.Add(Path.GetFileNameWithoutExtension(file.Name));
|
|
||||||
}
|
|
||||||
|
|
||||||
script = script.Replace("{{namespace}}", moduleNamespace);
|
|
||||||
script = script.Replace("{{modules}}", string.Join("|", moduleNames));
|
|
||||||
script = script.Replace("{{stylesheets}}", string.Join("|", stylesheetNames));
|
|
||||||
|
|
||||||
return script;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,165 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Windows.Forms;
|
|
||||||
using CefSharp;
|
|
||||||
using TweetDuck.Browser.Handling;
|
|
||||||
using TweetDuck.Browser.Notification;
|
|
||||||
using TweetDuck.Controls;
|
|
||||||
using TweetDuck.Dialogs;
|
|
||||||
using TweetDuck.Management;
|
|
||||||
using TweetDuck.Utils;
|
|
||||||
using TweetLib.Core.Features.Notifications;
|
|
||||||
using TweetLib.Utils.Static;
|
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Bridge {
|
|
||||||
[SuppressMessage("ReSharper", "UnusedMember.Global")]
|
|
||||||
class TweetDeckBridge {
|
|
||||||
public static void ResetStaticProperties() {
|
|
||||||
FormNotificationBase.FontSize = null;
|
|
||||||
FormNotificationBase.HeadLayout = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly FormBrowser form;
|
|
||||||
private readonly FormNotificationMain notification;
|
|
||||||
|
|
||||||
private TweetDeckBridge(FormBrowser form, FormNotificationMain notification) {
|
|
||||||
this.form = form;
|
|
||||||
this.notification = notification;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Browser only
|
|
||||||
|
|
||||||
public sealed class Browser : TweetDeckBridge {
|
|
||||||
public Browser(FormBrowser form, FormNotificationMain notification) : base(form, notification) {}
|
|
||||||
|
|
||||||
public void OnModulesLoaded(string moduleNamespace) {
|
|
||||||
form.InvokeAsyncSafe(() => form.OnModulesLoaded(moduleNamespace));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OpenContextMenu() {
|
|
||||||
form.InvokeAsyncSafe(form.OpenContextMenu);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OpenProfileImport() {
|
|
||||||
form.InvokeAsyncSafe(form.OpenProfileImport);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnIntroductionClosed(bool showGuide) {
|
|
||||||
form.InvokeAsyncSafe(() => form.OnIntroductionClosed(showGuide));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void LoadNotificationLayout(string fontSize, string headLayout) {
|
|
||||||
form.InvokeAsyncSafe(() => {
|
|
||||||
FormNotificationBase.FontSize = fontSize;
|
|
||||||
FormNotificationBase.HeadLayout = headLayout;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetRightClickedLink(string type, string url) {
|
|
||||||
ContextMenuBase.CurrentInfo.SetLink(type, url);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetRightClickedChirp(string columnId, string chirpId, string tweetUrl, string quoteUrl, string chirpAuthors, string chirpImages) {
|
|
||||||
ContextMenuBase.CurrentInfo.SetChirp(columnId, chirpId, tweetUrl, quoteUrl, chirpAuthors, chirpImages);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void DisplayTooltip(string text) {
|
|
||||||
form.InvokeAsyncSafe(() => form.DisplayTooltip(text));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Notification only
|
|
||||||
|
|
||||||
public sealed class Notification : TweetDeckBridge {
|
|
||||||
public Notification(FormBrowser form, FormNotificationMain notification) : base(form, notification) {}
|
|
||||||
|
|
||||||
public void DisplayTooltip(string text) {
|
|
||||||
notification.InvokeAsyncSafe(() => notification.DisplayTooltip(text));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void LoadNextNotification() {
|
|
||||||
notification.InvokeAsyncSafe(notification.FinishCurrentNotification);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ShowTweetDetail() {
|
|
||||||
notification.InvokeAsyncSafe(notification.ShowTweetDetail);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Global
|
|
||||||
|
|
||||||
public void OnTweetPopup(string columnId, string chirpId, string columnName, string tweetHtml, int tweetCharacters, string tweetUrl, string quoteUrl) {
|
|
||||||
notification.InvokeAsyncSafe(() => {
|
|
||||||
form.OnTweetNotification();
|
|
||||||
notification.ShowNotification(new DesktopNotification(columnId, chirpId, columnName, tweetHtml, tweetCharacters, tweetUrl, quoteUrl));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnTweetSound() {
|
|
||||||
form.InvokeAsyncSafe(() => {
|
|
||||||
form.OnTweetNotification();
|
|
||||||
form.OnTweetSound();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ScreenshotTweet(string html, int width) {
|
|
||||||
form.InvokeAsyncSafe(() => form.OnTweetScreenshotReady(html, width));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void PlayVideo(string videoUrl, string tweetUrl, string username, IJavascriptCallback callShowOverlay) {
|
|
||||||
form.InvokeAsyncSafe(() => form.PlayVideo(videoUrl, tweetUrl, username, callShowOverlay));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void StopVideo() {
|
|
||||||
form.InvokeAsyncSafe(form.StopVideo);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void FixClipboard() {
|
|
||||||
form.InvokeAsyncSafe(ClipboardManager.StripHtmlStyles);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OpenBrowser(string url) {
|
|
||||||
form.InvokeAsyncSafe(() => BrowserUtils.OpenExternalBrowser(url));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void MakeGetRequest(string url, IJavascriptCallback onSuccess, IJavascriptCallback onError) {
|
|
||||||
Task.Run(async () => {
|
|
||||||
var client = WebUtils.NewClient(BrowserUtils.UserAgentVanilla);
|
|
||||||
|
|
||||||
try {
|
|
||||||
var result = await client.DownloadStringTaskAsync(url);
|
|
||||||
await onSuccess.ExecuteAsync(result);
|
|
||||||
} catch (Exception e) {
|
|
||||||
await onError.ExecuteAsync(e.Message);
|
|
||||||
} finally {
|
|
||||||
onSuccess.Dispose();
|
|
||||||
onError.Dispose();
|
|
||||||
client.Dispose();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public int GetIdleSeconds() {
|
|
||||||
return NativeMethods.GetIdleSeconds();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Alert(string type, string contents) {
|
|
||||||
MessageBoxIcon icon = type switch {
|
|
||||||
"error" => MessageBoxIcon.Error,
|
|
||||||
"warning" => MessageBoxIcon.Warning,
|
|
||||||
"info" => MessageBoxIcon.Information,
|
|
||||||
_ => MessageBoxIcon.None
|
|
||||||
};
|
|
||||||
|
|
||||||
FormMessage.Show("TweetDuck Browser Message", contents, icon, FormMessage.OK);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void CrashDebug(string message) {
|
|
||||||
#if DEBUG
|
|
||||||
System.Diagnostics.Debug.WriteLine(message);
|
|
||||||
System.Diagnostics.Debugger.Break();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,65 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.Windows.Forms;
|
|
||||||
using TweetDuck.Controls;
|
|
||||||
using TweetLib.Core.Systems.Updates;
|
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Bridge {
|
|
||||||
[SuppressMessage("ReSharper", "UnusedMember.Global")]
|
|
||||||
class UpdateBridge {
|
|
||||||
private readonly UpdateHandler updates;
|
|
||||||
private readonly Control sync;
|
|
||||||
|
|
||||||
private UpdateInfo nextUpdate = null;
|
|
||||||
|
|
||||||
public event EventHandler<UpdateInfo> UpdateAccepted;
|
|
||||||
public event EventHandler<UpdateInfo> UpdateDismissed;
|
|
||||||
|
|
||||||
public UpdateBridge(UpdateHandler updates, Control sync) {
|
|
||||||
this.sync = sync;
|
|
||||||
|
|
||||||
this.updates = updates;
|
|
||||||
this.updates.CheckFinished += updates_CheckFinished;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void Cleanup() {
|
|
||||||
updates.CheckFinished -= updates_CheckFinished;
|
|
||||||
nextUpdate?.DeleteInstaller();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updates_CheckFinished(object sender, UpdateCheckEventArgs e) {
|
|
||||||
UpdateInfo foundUpdate = e.Result.HasValue ? e.Result.Value : null;
|
|
||||||
|
|
||||||
if (nextUpdate != null && !nextUpdate.Equals(foundUpdate)) {
|
|
||||||
nextUpdate.DeleteInstaller();
|
|
||||||
}
|
|
||||||
|
|
||||||
nextUpdate = foundUpdate;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandleInteractionEvent(EventHandler<UpdateInfo> eventHandler) {
|
|
||||||
UpdateInfo tmpInfo = nextUpdate;
|
|
||||||
|
|
||||||
if (tmpInfo != null) {
|
|
||||||
sync.InvokeAsyncSafe(() => eventHandler?.Invoke(this, tmpInfo));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bridge methods
|
|
||||||
|
|
||||||
public void TriggerUpdateCheck() {
|
|
||||||
updates.Check(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnUpdateAccepted() {
|
|
||||||
HandleInteractionEvent(UpdateAccepted);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnUpdateDismissed() {
|
|
||||||
HandleInteractionEvent(UpdateDismissed);
|
|
||||||
|
|
||||||
nextUpdate?.DeleteInstaller();
|
|
||||||
nextUpdate = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,166 +0,0 @@
|
|||||||
using System;
|
|
||||||
using CefSharp;
|
|
||||||
using TweetLib.Utils.Static;
|
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Data {
|
|
||||||
sealed class ContextInfo {
|
|
||||||
private LinkInfo link;
|
|
||||||
private ChirpInfo? chirp;
|
|
||||||
|
|
||||||
public ContextInfo() {
|
|
||||||
Reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetLink(string type, string url) {
|
|
||||||
link = string.IsNullOrEmpty(url) ? null : new LinkInfo(type, url);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetChirp(string columnId, string chirpId, string tweetUrl, string quoteUrl, string chirpAuthors, string chirpImages) {
|
|
||||||
chirp = string.IsNullOrEmpty(tweetUrl) ? (ChirpInfo?) null : new ChirpInfo(columnId, chirpId, tweetUrl, quoteUrl, chirpAuthors, chirpImages);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ContextData Reset() {
|
|
||||||
link = null;
|
|
||||||
chirp = null;
|
|
||||||
return ContextData.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ContextData Create(IContextMenuParams parameters) {
|
|
||||||
ContextData.Builder builder = new ContextData.Builder();
|
|
||||||
builder.AddContext(parameters);
|
|
||||||
|
|
||||||
if (link != null) {
|
|
||||||
builder.AddOverride(link.Type, link.Url);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (chirp.HasValue) {
|
|
||||||
builder.AddChirp(chirp.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder.Build();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Data structures
|
|
||||||
|
|
||||||
private sealed class LinkInfo {
|
|
||||||
public string Type { get; }
|
|
||||||
public string Url { get; }
|
|
||||||
|
|
||||||
public LinkInfo(string type, string url) {
|
|
||||||
this.Type = type;
|
|
||||||
this.Url = url;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public readonly struct ChirpInfo {
|
|
||||||
public string ColumnId { get; }
|
|
||||||
public string ChirpId { get; }
|
|
||||||
|
|
||||||
public string TweetUrl { get; }
|
|
||||||
public string QuoteUrl { get; }
|
|
||||||
|
|
||||||
public string[] Authors => chirpAuthors?.Split(';') ?? StringUtils.EmptyArray;
|
|
||||||
public string[] Images => chirpImages?.Split(';') ?? StringUtils.EmptyArray;
|
|
||||||
|
|
||||||
private readonly string chirpAuthors;
|
|
||||||
private readonly string chirpImages;
|
|
||||||
|
|
||||||
public ChirpInfo(string columnId, string chirpId, string tweetUrl, string quoteUrl, string chirpAuthors, string chirpImages) {
|
|
||||||
this.ColumnId = columnId;
|
|
||||||
this.ChirpId = chirpId;
|
|
||||||
this.TweetUrl = tweetUrl;
|
|
||||||
this.QuoteUrl = quoteUrl;
|
|
||||||
this.chirpAuthors = chirpAuthors;
|
|
||||||
this.chirpImages = chirpImages;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Constructed context
|
|
||||||
|
|
||||||
[Flags]
|
|
||||||
public enum ContextType {
|
|
||||||
Unknown = 0,
|
|
||||||
Link = 0b0001,
|
|
||||||
Image = 0b0010,
|
|
||||||
Video = 0b0100,
|
|
||||||
Chirp = 0b1000
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class ContextData {
|
|
||||||
public static readonly ContextData Empty = new Builder().Build();
|
|
||||||
|
|
||||||
public ContextType Types { get; }
|
|
||||||
|
|
||||||
public string LinkUrl { get; }
|
|
||||||
public string UnsafeLinkUrl { get; }
|
|
||||||
public string MediaUrl { get; }
|
|
||||||
|
|
||||||
public ChirpInfo Chirp { get; }
|
|
||||||
|
|
||||||
private ContextData(ContextType types, string linkUrl, string unsafeLinkUrl, string mediaUrl, ChirpInfo chirp) {
|
|
||||||
Types = types;
|
|
||||||
LinkUrl = linkUrl;
|
|
||||||
UnsafeLinkUrl = unsafeLinkUrl;
|
|
||||||
MediaUrl = mediaUrl;
|
|
||||||
Chirp = chirp;
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class Builder {
|
|
||||||
private ContextType types = ContextType.Unknown;
|
|
||||||
|
|
||||||
private string linkUrl = string.Empty;
|
|
||||||
private string unsafeLinkUrl = string.Empty;
|
|
||||||
private string mediaUrl = string.Empty;
|
|
||||||
|
|
||||||
private ChirpInfo chirp = default;
|
|
||||||
|
|
||||||
public void AddContext(IContextMenuParams parameters) {
|
|
||||||
ContextMenuType flags = parameters.TypeFlags;
|
|
||||||
|
|
||||||
if (flags.HasFlag(ContextMenuType.Media) && parameters.HasImageContents) {
|
|
||||||
types |= ContextType.Image;
|
|
||||||
types &= ~ContextType.Video;
|
|
||||||
mediaUrl = parameters.SourceUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (flags.HasFlag(ContextMenuType.Link)) {
|
|
||||||
types |= ContextType.Link;
|
|
||||||
linkUrl = parameters.LinkUrl;
|
|
||||||
unsafeLinkUrl = parameters.UnfilteredLinkUrl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AddOverride(string type, string url) {
|
|
||||||
switch (type) {
|
|
||||||
case "link":
|
|
||||||
types |= ContextType.Link;
|
|
||||||
linkUrl = url;
|
|
||||||
unsafeLinkUrl = url;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "image":
|
|
||||||
types |= ContextType.Image;
|
|
||||||
types &= ~(ContextType.Video | ContextType.Link);
|
|
||||||
mediaUrl = url;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "video":
|
|
||||||
types |= ContextType.Video;
|
|
||||||
types &= ~(ContextType.Image | ContextType.Link);
|
|
||||||
mediaUrl = url;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AddChirp(ChirpInfo chirp) {
|
|
||||||
this.types |= ContextType.Chirp;
|
|
||||||
this.chirp = chirp;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ContextData Build() {
|
|
||||||
return new ContextData(types, linkUrl, unsafeLinkUrl, mediaUrl, chirp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,50 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using CefSharp;
|
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Data {
|
|
||||||
sealed class ResourceHandlers {
|
|
||||||
private readonly ConcurrentDictionary<string, Func<IResourceHandler>> handlers = new ConcurrentDictionary<string, Func<IResourceHandler>>(StringComparer.OrdinalIgnoreCase);
|
|
||||||
|
|
||||||
public bool HasHandler(IRequest request) {
|
|
||||||
return handlers.ContainsKey(request.Url);
|
|
||||||
}
|
|
||||||
|
|
||||||
public IResourceHandler GetHandler(IRequest request) {
|
|
||||||
return handlers.TryGetValue(request.Url, out var factory) ? factory() : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Register(string url, Func<IResourceHandler> factory) {
|
|
||||||
if (Uri.TryCreate(url, UriKind.Absolute, out Uri uri)) {
|
|
||||||
handlers.AddOrUpdate(uri.AbsoluteUri, factory, (key, prev) => factory);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Register(ResourceLink link) {
|
|
||||||
return Register(link.Url, link.Factory);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Unregister(string url) {
|
|
||||||
return handlers.TryRemove(url, out _);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Unregister(ResourceLink link) {
|
|
||||||
return Unregister(link.Url);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Func<IResourceHandler> ForString(string str) {
|
|
||||||
return () => ResourceHandler.FromString(str);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Func<IResourceHandler> ForString(string str, string mimeType) {
|
|
||||||
return () => ResourceHandler.FromString(str, mimeType: mimeType);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Func<IResourceHandler> ForBytes(byte[] bytes, string mimeType) {
|
|
||||||
return () => ResourceHandler.FromByteArray(bytes, mimeType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
using System;
|
|
||||||
using CefSharp;
|
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Data {
|
|
||||||
sealed class ResourceLink {
|
|
||||||
public string Url { get; }
|
|
||||||
public Func<IResourceHandler> Factory { get; }
|
|
||||||
|
|
||||||
public ResourceLink(string url, Func<IResourceHandler> factory) {
|
|
||||||
this.Url = url;
|
|
||||||
this.Factory = factory;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
2
Browser/FormBrowser.Designer.cs
generated
2
Browser/FormBrowser.Designer.cs
generated
@ -27,7 +27,7 @@ private void InitializeComponent() {
|
|||||||
//
|
//
|
||||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||||
this.BackColor = TweetDuck.Utils.TwitterUtils.BackgroundColor;
|
this.BackColor = TweetDuck.Browser.TweetDeckBrowser.BackgroundColor;
|
||||||
this.ClientSize = new System.Drawing.Size(1008, 730);
|
this.ClientSize = new System.Drawing.Size(1008, 730);
|
||||||
this.Icon = Properties.Resources.icon;
|
this.Icon = Properties.Resources.icon;
|
||||||
this.Location = TweetDuck.Controls.ControlExtensions.InvisibleLocation;
|
this.Location = TweetDuck.Controls.ControlExtensions.InvisibleLocation;
|
||||||
|
@ -5,9 +5,7 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using CefSharp;
|
using CefSharp;
|
||||||
using TweetDuck.Browser.Bridge;
|
|
||||||
using TweetDuck.Browser.Handling;
|
using TweetDuck.Browser.Handling;
|
||||||
using TweetDuck.Browser.Handling.General;
|
|
||||||
using TweetDuck.Browser.Notification;
|
using TweetDuck.Browser.Notification;
|
||||||
using TweetDuck.Browser.Notification.Screenshot;
|
using TweetDuck.Browser.Notification.Screenshot;
|
||||||
using TweetDuck.Configuration;
|
using TweetDuck.Configuration;
|
||||||
@ -15,17 +13,18 @@
|
|||||||
using TweetDuck.Dialogs;
|
using TweetDuck.Dialogs;
|
||||||
using TweetDuck.Dialogs.Settings;
|
using TweetDuck.Dialogs.Settings;
|
||||||
using TweetDuck.Management;
|
using TweetDuck.Management;
|
||||||
using TweetDuck.Plugins;
|
|
||||||
using TweetDuck.Resources;
|
using TweetDuck.Resources;
|
||||||
using TweetDuck.Updates;
|
using TweetDuck.Updates;
|
||||||
using TweetDuck.Utils;
|
using TweetDuck.Utils;
|
||||||
using TweetLib.Core;
|
using TweetLib.Core;
|
||||||
|
using TweetLib.Core.Features.Notifications;
|
||||||
using TweetLib.Core.Features.Plugins;
|
using TweetLib.Core.Features.Plugins;
|
||||||
using TweetLib.Core.Features.Plugins.Events;
|
using TweetLib.Core.Features.TweetDeck;
|
||||||
|
using TweetLib.Core.Resources;
|
||||||
using TweetLib.Core.Systems.Updates;
|
using TweetLib.Core.Systems.Updates;
|
||||||
|
|
||||||
namespace TweetDuck.Browser {
|
namespace TweetDuck.Browser {
|
||||||
sealed partial class FormBrowser : Form {
|
sealed partial class FormBrowser : Form, CustomKeyboardHandler.IBrowserKeyHandler {
|
||||||
private static UserConfig Config => Program.Config.User;
|
private static UserConfig Config => Program.Config.User;
|
||||||
|
|
||||||
public bool IsWaiting {
|
public bool IsWaiting {
|
||||||
@ -46,18 +45,17 @@ public bool IsWaiting {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public UpdateInstaller UpdateInstaller { get; private set; }
|
public UpdateInstaller UpdateInstaller { get; private set; }
|
||||||
private bool ignoreUpdateCheckError;
|
|
||||||
|
|
||||||
#pragma warning disable IDE0069 // Disposable fields should be disposed
|
#pragma warning disable IDE0069 // Disposable fields should be disposed
|
||||||
private readonly TweetDeckBrowser browser;
|
private readonly TweetDeckBrowser browser;
|
||||||
private readonly FormNotificationTweet notification;
|
private readonly FormNotificationTweet notification;
|
||||||
#pragma warning restore IDE0069 // Disposable fields should be disposed
|
#pragma warning restore IDE0069 // Disposable fields should be disposed
|
||||||
|
|
||||||
private readonly ResourceProvider resourceProvider;
|
private readonly CachingResourceProvider<IResourceHandler> resourceProvider;
|
||||||
|
private readonly ITweetDeckInterface tweetDeckInterface;
|
||||||
private readonly PluginManager plugins;
|
private readonly PluginManager plugins;
|
||||||
private readonly UpdateHandler updates;
|
private readonly UpdateChecker updates;
|
||||||
private readonly ContextMenu contextMenu;
|
private readonly ContextMenu contextMenu;
|
||||||
private readonly UpdateBridge updateBridge;
|
|
||||||
|
|
||||||
private bool isLoaded;
|
private bool isLoaded;
|
||||||
private FormWindowState prevState;
|
private FormWindowState prevState;
|
||||||
@ -65,30 +63,25 @@ public bool IsWaiting {
|
|||||||
private TweetScreenshotManager notificationScreenshotManager;
|
private TweetScreenshotManager notificationScreenshotManager;
|
||||||
private VideoPlayer videoPlayer;
|
private VideoPlayer videoPlayer;
|
||||||
|
|
||||||
public FormBrowser(ResourceProvider resourceProvider, PluginSchemeFactory pluginScheme) {
|
public FormBrowser(CachingResourceProvider<IResourceHandler> resourceProvider, PluginManager pluginManager, IUpdateCheckClient updateCheckClient) {
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
Text = Program.BrandName;
|
Text = Program.BrandName;
|
||||||
|
|
||||||
this.resourceProvider = resourceProvider;
|
this.resourceProvider = resourceProvider;
|
||||||
|
|
||||||
this.plugins = new PluginManager(Program.Config.Plugins, Program.PluginPath, Program.PluginDataPath);
|
this.plugins = pluginManager;
|
||||||
this.plugins.Reloaded += plugins_Reloaded;
|
|
||||||
this.plugins.Executed += plugins_Executed;
|
|
||||||
this.plugins.Reload();
|
|
||||||
pluginScheme.Setup(plugins);
|
|
||||||
|
|
||||||
this.notification = new FormNotificationTweet(this, plugins);
|
this.tweetDeckInterface = new TweetDeckInterfaceImpl(this);
|
||||||
|
|
||||||
|
this.notification = new FormNotificationTweet(this, tweetDeckInterface, plugins);
|
||||||
this.notification.Show();
|
this.notification.Show();
|
||||||
|
|
||||||
this.updates = new UpdateHandler(new UpdateCheckClient(Program.InstallerPath), TaskScheduler.FromCurrentSynchronizationContext());
|
this.updates = new UpdateChecker(updateCheckClient, TaskScheduler.FromCurrentSynchronizationContext());
|
||||||
this.updates.CheckFinished += updates_CheckFinished;
|
this.updates.InteractionManager.UpdateAccepted += updateInteractionManager_UpdateAccepted;
|
||||||
|
this.updates.InteractionManager.UpdateDismissed += updateInteractionManager_UpdateDismissed;
|
||||||
|
|
||||||
this.updateBridge = new UpdateBridge(updates, this);
|
this.browser = new TweetDeckBrowser(this, plugins, tweetDeckInterface, updates);
|
||||||
this.updateBridge.UpdateAccepted += updateBridge_UpdateAccepted;
|
|
||||||
this.updateBridge.UpdateDismissed += updateBridge_UpdateDismissed;
|
|
||||||
|
|
||||||
this.browser = new TweetDeckBrowser(this, plugins, new TweetDeckBridge.Browser(this, notification), updateBridge);
|
|
||||||
this.contextMenu = ContextMenuBrowser.CreateMenu(this);
|
this.contextMenu = ContextMenuBrowser.CreateMenu(this);
|
||||||
|
|
||||||
Controls.Add(new MenuStrip { Visible = false }); // fixes Alt freezing the program in Win 10 Anniversary Update
|
Controls.Add(new MenuStrip { Visible = false }); // fixes Alt freezing the program in Win 10 Anniversary Update
|
||||||
@ -233,7 +226,8 @@ private void FormBrowser_FormClosing(object sender, FormClosingEventArgs e) {
|
|||||||
|
|
||||||
private void FormBrowser_FormClosed(object sender, FormClosedEventArgs e) {
|
private void FormBrowser_FormClosed(object sender, FormClosedEventArgs e) {
|
||||||
if (isLoaded && UpdateInstaller == null) {
|
if (isLoaded && UpdateInstaller == null) {
|
||||||
updateBridge.Cleanup();
|
updates.InteractionManager.ClearUpdate();
|
||||||
|
updates.InteractionManager.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -256,94 +250,61 @@ private void trayIcon_ClickClose(object sender, EventArgs e) {
|
|||||||
ForceClose();
|
ForceClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void plugins_Reloaded(object sender, PluginErrorEventArgs e) {
|
private void updateInteractionManager_UpdateAccepted(object sender, UpdateInfo update) {
|
||||||
if (e.HasErrors) {
|
this.InvokeAsyncSafe(() => {
|
||||||
FormMessage.Error("Error Loading Plugins", "The following plugins will not be available until the issues are resolved:\n\n" + string.Join("\n\n", e.Errors), FormMessage.OK);
|
FormManager.CloseAllDialogs();
|
||||||
}
|
|
||||||
|
|
||||||
if (isLoaded) {
|
if (!string.IsNullOrEmpty(Config.DismissedUpdate)) {
|
||||||
browser.ReloadToTweetDeck();
|
Config.DismissedUpdate = null;
|
||||||
}
|
Config.Save();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void plugins_Executed(object sender, PluginErrorEventArgs e) {
|
void OnFinished() {
|
||||||
if (e.HasErrors) {
|
UpdateDownloadStatus status = update.DownloadStatus;
|
||||||
this.InvokeAsyncSafe(() => { FormMessage.Error("Error Executing Plugins", "Failed to execute the following plugins:\n\n" + string.Join("\n\n", e.Errors), FormMessage.OK); });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updates_CheckFinished(object sender, UpdateCheckEventArgs e) {
|
if (status == UpdateDownloadStatus.Done) {
|
||||||
e.Result.Handle(update => {
|
UpdateInstaller = new UpdateInstaller(update.InstallerPath);
|
||||||
string tag = update.VersionTag;
|
ForceClose();
|
||||||
|
}
|
||||||
|
else if (status != UpdateDownloadStatus.Canceled && FormMessage.Error("Update Has Failed", "Could not automatically download the update: " + (update.DownloadError?.Message ?? "unknown error") + "\n\nWould you like to open the website and try downloading the update manually?", FormMessage.Yes, FormMessage.No)) {
|
||||||
|
App.SystemHandler.OpenBrowser(Program.Website);
|
||||||
|
ForceClose();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (tag != Program.VersionTag && tag != Config.DismissedUpdate) {
|
if (update.DownloadStatus.IsFinished(true)) {
|
||||||
update.BeginSilentDownload();
|
OnFinished();
|
||||||
browser.ShowUpdateNotification(tag, update.ReleaseNotes);
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
updates.StartTimer();
|
FormUpdateDownload downloadForm = new FormUpdateDownload(update);
|
||||||
}
|
|
||||||
}, ex => {
|
downloadForm.VisibleChanged += (sender2, args2) => {
|
||||||
if (!ignoreUpdateCheckError) {
|
downloadForm.MoveToCenter(this);
|
||||||
App.ErrorHandler.HandleException("Update Check Error", "An error occurred while checking for updates.", true, ex);
|
Hide();
|
||||||
updates.StartTimer();
|
};
|
||||||
|
|
||||||
|
downloadForm.FormClosed += (sender2, args2) => {
|
||||||
|
if (downloadForm.DialogResult != DialogResult.OK) {
|
||||||
|
update.CancelDownload();
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadForm.Dispose();
|
||||||
|
OnFinished();
|
||||||
|
};
|
||||||
|
|
||||||
|
downloadForm.Show();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ignoreUpdateCheckError = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateBridge_UpdateAccepted(object sender, UpdateInfo update) {
|
private void updateInteractionManager_UpdateDismissed(object sender, UpdateInfo update) {
|
||||||
FormManager.CloseAllDialogs();
|
this.InvokeAsyncSafe(() => {
|
||||||
|
Config.DismissedUpdate = update.VersionTag;
|
||||||
if (!string.IsNullOrEmpty(Config.DismissedUpdate)) {
|
|
||||||
Config.DismissedUpdate = null;
|
|
||||||
Config.Save();
|
Config.Save();
|
||||||
}
|
});
|
||||||
|
|
||||||
void OnFinished() {
|
|
||||||
UpdateDownloadStatus status = update.DownloadStatus;
|
|
||||||
|
|
||||||
if (status == UpdateDownloadStatus.Done) {
|
|
||||||
UpdateInstaller = new UpdateInstaller(update.InstallerPath);
|
|
||||||
ForceClose();
|
|
||||||
}
|
|
||||||
else if (status != UpdateDownloadStatus.Canceled && FormMessage.Error("Update Has Failed", "Could not automatically download the update: " + (update.DownloadError?.Message ?? "unknown error") + "\n\nWould you like to open the website and try downloading the update manually?", FormMessage.Yes, FormMessage.No)) {
|
|
||||||
BrowserUtils.OpenExternalBrowser(Program.Website);
|
|
||||||
ForceClose();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (update.DownloadStatus.IsFinished(true)) {
|
|
||||||
OnFinished();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
FormUpdateDownload downloadForm = new FormUpdateDownload(update);
|
|
||||||
|
|
||||||
downloadForm.VisibleChanged += (sender2, args2) => {
|
|
||||||
downloadForm.MoveToCenter(this);
|
|
||||||
Hide();
|
|
||||||
};
|
|
||||||
|
|
||||||
downloadForm.FormClosed += (sender2, args2) => {
|
|
||||||
if (downloadForm.DialogResult != DialogResult.OK) {
|
|
||||||
update.CancelDownload();
|
|
||||||
}
|
|
||||||
|
|
||||||
downloadForm.Dispose();
|
|
||||||
OnFinished();
|
|
||||||
};
|
|
||||||
|
|
||||||
downloadForm.Show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateBridge_UpdateDismissed(object sender, UpdateInfo update) {
|
|
||||||
Config.DismissedUpdate = update.VersionTag;
|
|
||||||
Config.Save();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void WndProc(ref Message m) {
|
protected override void WndProc(ref Message m) {
|
||||||
@ -358,11 +319,11 @@ protected override void WndProc(ref Message m) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (browser.Ready && m.Msg == NativeMethods.WM_PARENTNOTIFY && (m.WParam.ToInt32() & 0xFFFF) == NativeMethods.WM_XBUTTONDOWN) {
|
if (browser.Ready && m.Msg == NativeMethods.WM_PARENTNOTIFY && (m.WParam.ToInt32() & 0xFFFF) == NativeMethods.WM_XBUTTONDOWN) {
|
||||||
if (videoPlayer != null && videoPlayer.Running) {
|
if (videoPlayer is { Running: true }) {
|
||||||
videoPlayer.Close();
|
videoPlayer.Close();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
browser.OnMouseClickExtra(m.WParam);
|
browser.Functions.OnMouseClickExtra((m.WParam.ToInt32() >> 16) & 0xFFFF);
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@ -373,10 +334,6 @@ protected override void WndProc(ref Message m) {
|
|||||||
|
|
||||||
// bridge methods
|
// bridge methods
|
||||||
|
|
||||||
public void OnModulesLoaded(string moduleNamespace) {
|
|
||||||
browser.OnModulesLoaded(moduleNamespace);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void PauseNotification() {
|
public void PauseNotification() {
|
||||||
notification.PauseNotification();
|
notification.PauseNotification();
|
||||||
}
|
}
|
||||||
@ -385,10 +342,6 @@ public void ResumeNotification() {
|
|||||||
notification.ResumeNotification();
|
notification.ResumeNotification();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ReinjectCustomCSS(string css) {
|
|
||||||
browser.ReinjectCustomCSS(css);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ReloadToTweetDeck() {
|
public void ReloadToTweetDeck() {
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
ResourceHotSwap.Run();
|
ResourceHotSwap.Run();
|
||||||
@ -399,30 +352,9 @@ public void ReloadToTweetDeck() {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
ignoreUpdateCheckError = false;
|
|
||||||
browser.ReloadToTweetDeck();
|
browser.ReloadToTweetDeck();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddSearchColumn(string query) {
|
|
||||||
browser.AddSearchColumn(query);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void TriggerTweetScreenshot(string columnId, string chirpId) {
|
|
||||||
browser.TriggerTweetScreenshot(columnId, chirpId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ReloadColumns() {
|
|
||||||
browser.ReloadColumns();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void PlaySoundNotification() {
|
|
||||||
browser.PlaySoundNotification();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ApplyROT13() {
|
|
||||||
browser.ApplyROT13();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OpenDevTools() {
|
public void OpenDevTools() {
|
||||||
browser.OpenDevTools();
|
browser.OpenDevTools();
|
||||||
}
|
}
|
||||||
@ -452,7 +384,7 @@ public void OpenSettings(Type startTab) {
|
|||||||
if (!FormManager.TryBringToFront<FormSettings>()) {
|
if (!FormManager.TryBringToFront<FormSettings>()) {
|
||||||
bool prevEnableUpdateCheck = Config.EnableUpdateCheck;
|
bool prevEnableUpdateCheck = Config.EnableUpdateCheck;
|
||||||
|
|
||||||
FormSettings form = new FormSettings(this, plugins, updates, startTab);
|
FormSettings form = new FormSettings(this, plugins, updates, browser.Functions, startTab);
|
||||||
|
|
||||||
form.FormClosed += (sender, args) => {
|
form.FormClosed += (sender, args) => {
|
||||||
if (!prevEnableUpdateCheck && Config.EnableUpdateCheck) {
|
if (!prevEnableUpdateCheck && Config.EnableUpdateCheck) {
|
||||||
@ -473,7 +405,7 @@ public void OpenSettings(Type startTab) {
|
|||||||
plugins.Reload(); // also reloads the browser
|
plugins.Reload(); // also reloads the browser
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
browser.UpdateProperties();
|
Program.Config.User.TriggerOptionsDialogClosed();
|
||||||
}
|
}
|
||||||
|
|
||||||
notification.RequiresResize = true;
|
notification.RequiresResize = true;
|
||||||
@ -508,13 +440,19 @@ public void OpenProfileImport() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ShowDesktopNotification(DesktopNotification notification) {
|
||||||
|
this.notification.ShowNotification(notification);
|
||||||
|
}
|
||||||
|
|
||||||
public void OnTweetNotification() { // may be called multiple times, once for each type of notification
|
public void OnTweetNotification() { // may be called multiple times, once for each type of notification
|
||||||
if (Config.EnableTrayHighlight && !ContainsFocus) {
|
if (Config.EnableTrayHighlight && !ContainsFocus) {
|
||||||
trayIcon.HasNotifications = true;
|
trayIcon.HasNotifications = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnTweetSound() {}
|
public void SaveVideo(string url, string username) {
|
||||||
|
browser.SaveVideo(url, username);
|
||||||
|
}
|
||||||
|
|
||||||
public void PlayVideo(string videoUrl, string tweetUrl, string username, IJavascriptCallback callShowOverlay) {
|
public void PlayVideo(string videoUrl, string tweetUrl, string username, IJavascriptCallback callShowOverlay) {
|
||||||
string playerPath = Config.VideoPlayerPath;
|
string playerPath = Config.VideoPlayerPath;
|
||||||
@ -548,25 +486,16 @@ public void StopVideo() {
|
|||||||
videoPlayer?.Close();
|
videoPlayer?.Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool ProcessBrowserKey(Keys key) {
|
public bool ShowTweetDetail(string columnId, string chirpId, string fallbackUrl) {
|
||||||
if (videoPlayer != null && videoPlayer.Running) {
|
|
||||||
videoPlayer.SendKeyEvent(key);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ShowTweetDetail(string columnId, string chirpId, string fallbackUrl) {
|
|
||||||
Activate();
|
Activate();
|
||||||
|
|
||||||
if (!browser.IsTweetDeckWebsite) {
|
if (!browser.IsTweetDeckWebsite) {
|
||||||
FormMessage.Error("View Tweet Detail", "TweetDeck is not currently loaded.", FormMessage.OK);
|
FormMessage.Error("View Tweet Detail", "TweetDeck is not currently loaded.", FormMessage.OK);
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
notification.FinishCurrentNotification();
|
browser.Functions.ShowTweetDetail(columnId, chirpId, fallbackUrl);
|
||||||
browser.ShowTweetDetail(columnId, chirpId, fallbackUrl);
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnTweetScreenshotReady(string html, int width) {
|
public void OnTweetScreenshotReady(string html, int width) {
|
||||||
@ -584,5 +513,18 @@ public void DisplayTooltip(string text) {
|
|||||||
toolTip.Show(text, this, position);
|
toolTip.Show(text, this, position);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public FormNotificationExample CreateExampleNotification() {
|
||||||
|
return new FormNotificationExample(this, tweetDeckInterface, plugins);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CustomKeyboardHandler.IBrowserKeyHandler.HandleBrowserKey(Keys key) {
|
||||||
|
if (videoPlayer is { Running: true }) {
|
||||||
|
videoPlayer.SendKeyEvent(key);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
using CefSharp;
|
using CefSharp;
|
||||||
using TweetDuck.Configuration;
|
using TweetDuck.Configuration;
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Handling.General {
|
namespace TweetDuck.Browser.Handling {
|
||||||
sealed class BrowserProcessHandler : IBrowserProcessHandler {
|
sealed class BrowserProcessHandler : IBrowserProcessHandler {
|
||||||
public static Task UpdatePrefs() {
|
public static Task UpdatePrefs() {
|
||||||
return Cef.UIThreadTaskFactory.StartNew(UpdatePrefsInternal);
|
return Cef.UIThreadTaskFactory.StartNew(UpdatePrefsInternal);
|
@ -1,213 +1,92 @@
|
|||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Linq;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using System.Windows.Forms;
|
|
||||||
using CefSharp;
|
using CefSharp;
|
||||||
using TweetDuck.Browser.Adapters;
|
using TweetDuck.Browser.Adapters;
|
||||||
using TweetDuck.Browser.Data;
|
|
||||||
using TweetDuck.Browser.Notification;
|
|
||||||
using TweetDuck.Configuration;
|
using TweetDuck.Configuration;
|
||||||
using TweetDuck.Controls;
|
|
||||||
using TweetDuck.Dialogs;
|
|
||||||
using TweetDuck.Management;
|
|
||||||
using TweetDuck.Utils;
|
using TweetDuck.Utils;
|
||||||
using TweetLib.Core.Features.Twitter;
|
using TweetLib.Browser.Contexts;
|
||||||
using TweetLib.Utils.Static;
|
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Handling {
|
namespace TweetDuck.Browser.Handling {
|
||||||
abstract class ContextMenuBase : IContextMenuHandler {
|
abstract class ContextMenuBase : IContextMenuHandler {
|
||||||
public static ContextInfo CurrentInfo { get; } = new ContextInfo();
|
private const CefMenuCommand MenuOpenDevTools = (CefMenuCommand) 26500;
|
||||||
|
|
||||||
|
private static readonly HashSet<CefMenuCommand> AllowedCefCommands = new HashSet<CefMenuCommand> {
|
||||||
|
CefMenuCommand.NotFound,
|
||||||
|
CefMenuCommand.Undo,
|
||||||
|
CefMenuCommand.Redo,
|
||||||
|
CefMenuCommand.Cut,
|
||||||
|
CefMenuCommand.Copy,
|
||||||
|
CefMenuCommand.Paste,
|
||||||
|
CefMenuCommand.Delete,
|
||||||
|
CefMenuCommand.SelectAll,
|
||||||
|
CefMenuCommand.SpellCheckSuggestion0,
|
||||||
|
CefMenuCommand.SpellCheckSuggestion1,
|
||||||
|
CefMenuCommand.SpellCheckSuggestion2,
|
||||||
|
CefMenuCommand.SpellCheckSuggestion3,
|
||||||
|
CefMenuCommand.SpellCheckSuggestion4,
|
||||||
|
CefMenuCommand.SpellCheckNoSuggestions,
|
||||||
|
CefMenuCommand.AddToDictionary
|
||||||
|
};
|
||||||
|
|
||||||
protected static UserConfig Config => Program.Config.User;
|
protected static UserConfig Config => Program.Config.User;
|
||||||
private static ImageQuality ImageQuality => Config.TwitterImageQuality;
|
|
||||||
|
|
||||||
private const CefMenuCommand MenuOpenLinkUrl = (CefMenuCommand) 26500;
|
private readonly TweetLib.Browser.Interfaces.IContextMenuHandler handler;
|
||||||
private const CefMenuCommand MenuCopyLinkUrl = (CefMenuCommand) 26501;
|
private readonly CefContextMenuActionRegistry actionRegistry;
|
||||||
private const CefMenuCommand MenuCopyUsername = (CefMenuCommand) 26502;
|
|
||||||
private const CefMenuCommand MenuViewImage = (CefMenuCommand) 26503;
|
|
||||||
private const CefMenuCommand MenuOpenMediaUrl = (CefMenuCommand) 26504;
|
|
||||||
private const CefMenuCommand MenuCopyMediaUrl = (CefMenuCommand) 26505;
|
|
||||||
private const CefMenuCommand MenuCopyImage = (CefMenuCommand) 26506;
|
|
||||||
private const CefMenuCommand MenuSaveMedia = (CefMenuCommand) 26507;
|
|
||||||
private const CefMenuCommand MenuSaveTweetImages = (CefMenuCommand) 26508;
|
|
||||||
private const CefMenuCommand MenuSearchInBrowser = (CefMenuCommand) 26509;
|
|
||||||
private const CefMenuCommand MenuReadApplyROT13 = (CefMenuCommand) 26510;
|
|
||||||
private const CefMenuCommand MenuOpenDevTools = (CefMenuCommand) 26599;
|
|
||||||
|
|
||||||
protected ContextInfo.ContextData Context { get; private set; }
|
protected ContextMenuBase(TweetLib.Browser.Interfaces.IContextMenuHandler handler) {
|
||||||
|
this.handler = handler;
|
||||||
|
this.actionRegistry = new CefContextMenuActionRegistry();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual Context CreateContext(IContextMenuParams parameters) {
|
||||||
|
return CefContextMenuModel.CreateContext(parameters, null, Config.TwitterImageQuality);
|
||||||
|
}
|
||||||
|
|
||||||
public virtual void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model) {
|
public virtual void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model) {
|
||||||
if (!TwitterUrls.IsTweetDeck(frame.Url) || browser.IsLoading) {
|
for (int i = model.Count - 1; i >= 0; i--) {
|
||||||
Context = CurrentInfo.Reset();
|
CefMenuCommand command = model.GetCommandIdAt(i);
|
||||||
}
|
|
||||||
else {
|
|
||||||
Context = CurrentInfo.Create(parameters);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parameters.TypeFlags.HasFlag(ContextMenuType.Selection) && !parameters.TypeFlags.HasFlag(ContextMenuType.Editable)) {
|
if (!AllowedCefCommands.Contains(command) && !(command >= CefMenuCommand.CustomFirst && command <= CefMenuCommand.CustomLast)) {
|
||||||
model.AddItem(MenuSearchInBrowser, "Search in browser");
|
model.RemoveAt(i);
|
||||||
model.AddSeparator();
|
|
||||||
model.AddItem(MenuReadApplyROT13, "Apply ROT13");
|
|
||||||
model.AddSeparator();
|
|
||||||
}
|
|
||||||
|
|
||||||
static string TextOpen(string name) => "Open " + name + " in browser";
|
|
||||||
static string TextCopy(string name) => "Copy " + name + " address";
|
|
||||||
static string TextSave(string name) => "Save " + name + " as...";
|
|
||||||
|
|
||||||
if (Context.Types.HasFlag(ContextInfo.ContextType.Link) && !Context.UnsafeLinkUrl.EndsWith("tweetdeck.twitter.com/#", StringComparison.Ordinal)) {
|
|
||||||
if (TwitterUrls.RegexAccount.IsMatch(Context.UnsafeLinkUrl)) {
|
|
||||||
model.AddItem(MenuOpenLinkUrl, TextOpen("account"));
|
|
||||||
model.AddItem(MenuCopyLinkUrl, TextCopy("account"));
|
|
||||||
model.AddItem(MenuCopyUsername, "Copy account username");
|
|
||||||
}
|
}
|
||||||
else {
|
}
|
||||||
model.AddItem(MenuOpenLinkUrl, TextOpen("link"));
|
|
||||||
model.AddItem(MenuCopyLinkUrl, TextCopy("link"));
|
for (int i = model.Count - 2; i >= 0; i--) {
|
||||||
|
if (model.GetTypeAt(i) == MenuItemType.Separator && model.GetTypeAt(i + 1) == MenuItemType.Separator) {
|
||||||
|
model.RemoveAt(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
model.AddSeparator();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Context.Types.HasFlag(ContextInfo.ContextType.Video)) {
|
if (model.Count > 0 && model.GetTypeAt(0) == MenuItemType.Separator) {
|
||||||
model.AddItem(MenuOpenMediaUrl, TextOpen("video"));
|
model.RemoveAt(0);
|
||||||
model.AddItem(MenuCopyMediaUrl, TextCopy("video"));
|
|
||||||
model.AddItem(MenuSaveMedia, TextSave("video"));
|
|
||||||
model.AddSeparator();
|
|
||||||
}
|
}
|
||||||
else if (Context.Types.HasFlag(ContextInfo.ContextType.Image) && Context.MediaUrl != FormNotificationBase.AppLogo.Url) {
|
|
||||||
model.AddItem(MenuViewImage, "View image in photo viewer");
|
|
||||||
model.AddItem(MenuOpenMediaUrl, TextOpen("image"));
|
|
||||||
model.AddItem(MenuCopyMediaUrl, TextCopy("image"));
|
|
||||||
model.AddItem(MenuCopyImage, "Copy image");
|
|
||||||
model.AddItem(MenuSaveMedia, TextSave("image"));
|
|
||||||
|
|
||||||
if (Context.Chirp.Images.Length > 1) {
|
AddSeparator(model);
|
||||||
model.AddItem(MenuSaveTweetImages, TextSave("all images"));
|
handler.Show(new CefContextMenuModel(model, actionRegistry), CreateContext(parameters));
|
||||||
}
|
RemoveSeparatorIfLast(model);
|
||||||
|
|
||||||
model.AddSeparator();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags) {
|
public virtual bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags) {
|
||||||
Control control = browserControl.AsControl();
|
if (actionRegistry.Execute(commandId)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
switch (commandId) {
|
if (commandId == MenuOpenDevTools) {
|
||||||
case MenuOpenLinkUrl:
|
browserControl.OpenDevToolsCustom(new Point(parameters.XCoord, parameters.YCoord));
|
||||||
OpenBrowser(control, Context.LinkUrl);
|
return true;
|
||||||
break;
|
|
||||||
|
|
||||||
case MenuCopyLinkUrl:
|
|
||||||
SetClipboardText(control, Context.UnsafeLinkUrl);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case MenuCopyUsername: {
|
|
||||||
string url = Context.UnsafeLinkUrl;
|
|
||||||
Match match = TwitterUrls.RegexAccount.Match(url);
|
|
||||||
|
|
||||||
SetClipboardText(control, match.Success ? match.Groups[1].Value : url);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case MenuOpenMediaUrl:
|
|
||||||
OpenBrowser(control, TwitterUrls.GetMediaLink(Context.MediaUrl, ImageQuality));
|
|
||||||
break;
|
|
||||||
|
|
||||||
case MenuCopyMediaUrl:
|
|
||||||
SetClipboardText(control, TwitterUrls.GetMediaLink(Context.MediaUrl, ImageQuality));
|
|
||||||
break;
|
|
||||||
|
|
||||||
case MenuCopyImage: {
|
|
||||||
string url = Context.MediaUrl;
|
|
||||||
|
|
||||||
control.InvokeAsyncSafe(() => { TwitterUtils.CopyImage(url, ImageQuality); });
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case MenuViewImage: {
|
|
||||||
string url = Context.MediaUrl;
|
|
||||||
|
|
||||||
control.InvokeAsyncSafe(() => {
|
|
||||||
TwitterUtils.ViewImage(url, ImageQuality);
|
|
||||||
});
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case MenuSaveMedia: {
|
|
||||||
bool isVideo = Context.Types.HasFlag(ContextInfo.ContextType.Video);
|
|
||||||
string url = Context.MediaUrl;
|
|
||||||
string username = Context.Chirp.Authors.LastOrDefault();
|
|
||||||
|
|
||||||
control.InvokeAsyncSafe(() => {
|
|
||||||
if (isVideo) {
|
|
||||||
TwitterUtils.DownloadVideo(url, username);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
TwitterUtils.DownloadImage(url, username, ImageQuality);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case MenuSaveTweetImages: {
|
|
||||||
string[] urls = Context.Chirp.Images;
|
|
||||||
string username = Context.Chirp.Authors.LastOrDefault();
|
|
||||||
|
|
||||||
control.InvokeAsyncSafe(() => {
|
|
||||||
TwitterUtils.DownloadImages(urls, username, ImageQuality);
|
|
||||||
});
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case MenuReadApplyROT13:
|
|
||||||
string selection = parameters.SelectionText;
|
|
||||||
control.InvokeAsyncSafe(() => FormMessage.Information("ROT13", StringUtils.ConvertRot13(selection), FormMessage.OK));
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case MenuSearchInBrowser:
|
|
||||||
string query = parameters.SelectionText;
|
|
||||||
control.InvokeAsyncSafe(() => BrowserUtils.OpenExternalSearch(query));
|
|
||||||
DeselectAll(frame);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case MenuOpenDevTools:
|
|
||||||
browserControl.OpenDevToolsCustom(new Point(parameters.XCoord, parameters.YCoord));
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame) {
|
public virtual void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame) {
|
||||||
Context = CurrentInfo.Reset();
|
actionRegistry.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual bool RunContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model, IRunContextMenuCallback callback) {
|
public virtual bool RunContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model, IRunContextMenuCallback callback) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static void DeselectAll(IFrame frame) {
|
|
||||||
CefScriptExecutor.RunScript(frame, "window.getSelection().removeAllRanges()", "gen:deselect");
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static void OpenBrowser(Control control, string url) {
|
|
||||||
control.InvokeAsyncSafe(() => BrowserUtils.OpenExternalBrowser(url));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static void SetClipboardText(Control control, string text) {
|
|
||||||
control.InvokeAsyncSafe(() => ClipboardManager.SetText(text, TextDataFormat.UnicodeText));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static void InsertSelectionSearchItem(IMenuModel model, CefMenuCommand insertCommand, string insertLabel) {
|
|
||||||
model.InsertItemAt(model.GetIndexOf(MenuSearchInBrowser) + 1, insertCommand, insertLabel);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static void AddDebugMenuItems(IMenuModel model) {
|
protected static void AddDebugMenuItems(IMenuModel model) {
|
||||||
if (Config.DevToolsInContextMenu) {
|
if (Config.DevToolsInContextMenu) {
|
||||||
AddSeparator(model);
|
AddSeparator(model);
|
||||||
|
@ -1,8 +1,12 @@
|
|||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using CefSharp;
|
using CefSharp;
|
||||||
using TweetDuck.Browser.Data;
|
using TweetDuck.Browser.Adapters;
|
||||||
|
using TweetDuck.Configuration;
|
||||||
using TweetDuck.Controls;
|
using TweetDuck.Controls;
|
||||||
|
using TweetLib.Browser.Contexts;
|
||||||
|
using TweetLib.Core.Features.TweetDeck;
|
||||||
using TweetLib.Core.Features.Twitter;
|
using TweetLib.Core.Features.Twitter;
|
||||||
|
using IContextMenuHandler = TweetLib.Browser.Interfaces.IContextMenuHandler;
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Handling {
|
namespace TweetDuck.Browser.Handling {
|
||||||
sealed class ContextMenuBrowser : ContextMenuBase {
|
sealed class ContextMenuBrowser : ContextMenuBase {
|
||||||
@ -12,14 +16,6 @@ sealed class ContextMenuBrowser : ContextMenuBase {
|
|||||||
private const CefMenuCommand MenuPlugins = (CefMenuCommand) 26003;
|
private const CefMenuCommand MenuPlugins = (CefMenuCommand) 26003;
|
||||||
private const CefMenuCommand MenuAbout = (CefMenuCommand) 26604;
|
private const CefMenuCommand MenuAbout = (CefMenuCommand) 26604;
|
||||||
|
|
||||||
private const CefMenuCommand MenuOpenTweetUrl = (CefMenuCommand) 26610;
|
|
||||||
private const CefMenuCommand MenuCopyTweetUrl = (CefMenuCommand) 26611;
|
|
||||||
private const CefMenuCommand MenuOpenQuotedTweetUrl = (CefMenuCommand) 26612;
|
|
||||||
private const CefMenuCommand MenuCopyQuotedTweetUrl = (CefMenuCommand) 26613;
|
|
||||||
private const CefMenuCommand MenuScreenshotTweet = (CefMenuCommand) 26614;
|
|
||||||
private const CefMenuCommand MenuWriteApplyROT13 = (CefMenuCommand) 26615;
|
|
||||||
private const CefMenuCommand MenuSearchInColumn = (CefMenuCommand) 26616;
|
|
||||||
|
|
||||||
private const string TitleReloadBrowser = "Reload browser";
|
private const string TitleReloadBrowser = "Reload browser";
|
||||||
private const string TitleMuteNotifications = "Mute notifications";
|
private const string TitleMuteNotifications = "Mute notifications";
|
||||||
private const string TitleSettings = "Options";
|
private const string TitleSettings = "Options";
|
||||||
@ -27,49 +23,26 @@ sealed class ContextMenuBrowser : ContextMenuBase {
|
|||||||
private const string TitleAboutProgram = "About " + Program.BrandName;
|
private const string TitleAboutProgram = "About " + Program.BrandName;
|
||||||
|
|
||||||
private readonly FormBrowser form;
|
private readonly FormBrowser form;
|
||||||
|
private readonly TweetDeckExtraContext extraContext;
|
||||||
|
|
||||||
public ContextMenuBrowser(FormBrowser form) {
|
public ContextMenuBrowser(FormBrowser form, IContextMenuHandler handler, TweetDeckExtraContext extraContext) : base(handler) {
|
||||||
this.form = form;
|
this.form = form;
|
||||||
|
this.extraContext = extraContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Context CreateContext(IContextMenuParams parameters) {
|
||||||
|
return CefContextMenuModel.CreateContext(parameters, extraContext, Config.TwitterImageQuality);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model) {
|
public override void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model) {
|
||||||
bool isSelecting = parameters.TypeFlags.HasFlag(ContextMenuType.Selection);
|
if (!TwitterUrls.IsTweetDeck(frame.Url) || browser.IsLoading) {
|
||||||
bool isEditing = parameters.TypeFlags.HasFlag(ContextMenuType.Editable);
|
extraContext.Reset();
|
||||||
|
|
||||||
model.Remove(CefMenuCommand.Back);
|
|
||||||
model.Remove(CefMenuCommand.Forward);
|
|
||||||
model.Remove(CefMenuCommand.Print);
|
|
||||||
model.Remove(CefMenuCommand.ViewSource);
|
|
||||||
RemoveSeparatorIfLast(model);
|
|
||||||
|
|
||||||
if (isSelecting) {
|
|
||||||
if (isEditing) {
|
|
||||||
model.AddSeparator();
|
|
||||||
model.AddItem(MenuWriteApplyROT13, "Apply ROT13");
|
|
||||||
}
|
|
||||||
|
|
||||||
model.AddSeparator();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
|
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
|
||||||
|
|
||||||
if (isSelecting && !isEditing && TwitterUrls.IsTweetDeck(frame.Url)) {
|
bool isSelecting = parameters.TypeFlags.HasFlag(ContextMenuType.Selection);
|
||||||
InsertSelectionSearchItem(model, MenuSearchInColumn, "Search in a column");
|
bool isEditing = parameters.TypeFlags.HasFlag(ContextMenuType.Editable);
|
||||||
}
|
|
||||||
|
|
||||||
if (Context.Types.HasFlag(ContextInfo.ContextType.Chirp) && !isSelecting && !isEditing) {
|
|
||||||
model.AddItem(MenuOpenTweetUrl, "Open tweet in browser");
|
|
||||||
model.AddItem(MenuCopyTweetUrl, "Copy tweet address");
|
|
||||||
model.AddItem(MenuScreenshotTweet, "Screenshot tweet to clipboard");
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(Context.Chirp.QuoteUrl)) {
|
|
||||||
model.AddSeparator();
|
|
||||||
model.AddItem(MenuOpenQuotedTweetUrl, "Open quoted tweet in browser");
|
|
||||||
model.AddItem(MenuCopyQuotedTweetUrl, "Copy quoted tweet address");
|
|
||||||
}
|
|
||||||
|
|
||||||
model.AddSeparator();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isSelecting && !isEditing) {
|
if (!isSelecting && !isEditing) {
|
||||||
AddSeparator(model);
|
AddSeparator(model);
|
||||||
@ -87,8 +60,6 @@ public override void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser br
|
|||||||
|
|
||||||
AddDebugMenuItems(globalMenu);
|
AddDebugMenuItems(globalMenu);
|
||||||
}
|
}
|
||||||
|
|
||||||
RemoveSeparatorIfLast(model);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags) {
|
public override bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags) {
|
||||||
@ -117,41 +88,14 @@ public override bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser b
|
|||||||
form.InvokeAsyncSafe(ToggleMuteNotifications);
|
form.InvokeAsyncSafe(ToggleMuteNotifications);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case MenuOpenTweetUrl:
|
default:
|
||||||
OpenBrowser(form, Context.Chirp.TweetUrl);
|
return false;
|
||||||
return true;
|
|
||||||
|
|
||||||
case MenuCopyTweetUrl:
|
|
||||||
SetClipboardText(form, Context.Chirp.TweetUrl);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case MenuScreenshotTweet:
|
|
||||||
var chirp = Context.Chirp;
|
|
||||||
|
|
||||||
form.InvokeAsyncSafe(() => form.TriggerTweetScreenshot(chirp.ColumnId, chirp.ChirpId));
|
|
||||||
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case MenuOpenQuotedTweetUrl:
|
|
||||||
OpenBrowser(form, Context.Chirp.QuoteUrl);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case MenuCopyQuotedTweetUrl:
|
|
||||||
SetClipboardText(form, Context.Chirp.QuoteUrl);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case MenuWriteApplyROT13:
|
|
||||||
form.InvokeAsyncSafe(form.ApplyROT13);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case MenuSearchInColumn:
|
|
||||||
string query = parameters.SelectionText;
|
|
||||||
form.InvokeAsyncSafe(() => form.AddSearchColumn(query));
|
|
||||||
DeselectAll(frame);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
public override void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame) {
|
||||||
|
base.OnContextMenuDismissed(browserControl, browser, frame);
|
||||||
|
extraContext.Reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ContextMenu CreateMenu(FormBrowser form) {
|
public static ContextMenu CreateMenu(FormBrowser form) {
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
using CefSharp;
|
using CefSharp;
|
||||||
|
using IContextMenuHandler = TweetLib.Browser.Interfaces.IContextMenuHandler;
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Handling {
|
namespace TweetDuck.Browser.Handling {
|
||||||
sealed class ContextMenuGuide : ContextMenuBase {
|
sealed class ContextMenuGuide : ContextMenuBase {
|
||||||
|
public ContextMenuGuide(IContextMenuHandler handler) : base(handler) {}
|
||||||
|
|
||||||
public override void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model) {
|
public override void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model) {
|
||||||
model.Clear();
|
|
||||||
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
|
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
|
||||||
AddDebugMenuItems(model);
|
AddDebugMenuItems(model);
|
||||||
}
|
}
|
||||||
|
@ -1,88 +1,27 @@
|
|||||||
using CefSharp;
|
using CefSharp;
|
||||||
using TweetDuck.Browser.Notification;
|
using TweetDuck.Browser.Notification;
|
||||||
using TweetDuck.Controls;
|
using TweetDuck.Controls;
|
||||||
|
using TweetLib.Browser.Contexts;
|
||||||
|
using IContextMenuHandler = TweetLib.Browser.Interfaces.IContextMenuHandler;
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Handling {
|
namespace TweetDuck.Browser.Handling {
|
||||||
sealed class ContextMenuNotification : ContextMenuBase {
|
sealed class ContextMenuNotification : ContextMenuBase {
|
||||||
private const CefMenuCommand MenuViewDetail = (CefMenuCommand) 26600;
|
|
||||||
private const CefMenuCommand MenuSkipTweet = (CefMenuCommand) 26601;
|
|
||||||
private const CefMenuCommand MenuFreeze = (CefMenuCommand) 26602;
|
|
||||||
private const CefMenuCommand MenuCopyTweetUrl = (CefMenuCommand) 26603;
|
|
||||||
private const CefMenuCommand MenuCopyQuotedTweetUrl = (CefMenuCommand) 26604;
|
|
||||||
|
|
||||||
private readonly FormNotificationBase form;
|
private readonly FormNotificationBase form;
|
||||||
private readonly bool enableCustomMenu;
|
|
||||||
|
|
||||||
public ContextMenuNotification(FormNotificationBase form, bool enableCustomMenu) {
|
public ContextMenuNotification(FormNotificationBase form, IContextMenuHandler handler) : base(handler) {
|
||||||
this.form = form;
|
this.form = form;
|
||||||
this.enableCustomMenu = enableCustomMenu;
|
}
|
||||||
|
|
||||||
|
protected override Context CreateContext(IContextMenuParams parameters) {
|
||||||
|
Context context = base.CreateContext(parameters);
|
||||||
|
context.Notification = new TweetLib.Browser.Contexts.Notification(form.CurrentTweetUrl, form.CurrentQuoteUrl);
|
||||||
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model) {
|
public override void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model) {
|
||||||
model.Clear();
|
|
||||||
|
|
||||||
if (parameters.TypeFlags.HasFlag(ContextMenuType.Selection)) {
|
|
||||||
model.AddItem(CefMenuCommand.Copy, "Copy");
|
|
||||||
model.AddSeparator();
|
|
||||||
}
|
|
||||||
|
|
||||||
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
|
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
|
||||||
|
|
||||||
if (enableCustomMenu) {
|
|
||||||
if (form.CanViewDetail) {
|
|
||||||
model.AddItem(MenuViewDetail, "View detail");
|
|
||||||
}
|
|
||||||
|
|
||||||
model.AddItem(MenuSkipTweet, "Skip tweet");
|
|
||||||
model.AddCheckItem(MenuFreeze, "Freeze");
|
|
||||||
model.SetChecked(MenuFreeze, form.FreezeTimer);
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(form.CurrentTweetUrl)) {
|
|
||||||
model.AddSeparator();
|
|
||||||
model.AddItem(MenuCopyTweetUrl, "Copy tweet address");
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(form.CurrentQuoteUrl)) {
|
|
||||||
model.AddItem(MenuCopyQuotedTweetUrl, "Copy quoted tweet address");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AddDebugMenuItems(model);
|
AddDebugMenuItems(model);
|
||||||
RemoveSeparatorIfLast(model);
|
form.InvokeAsyncSafe(() => form.ContextMenuOpen = true);
|
||||||
|
|
||||||
form.InvokeAsyncSafe(() => {
|
|
||||||
form.ContextMenuOpen = true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags) {
|
|
||||||
if (base.OnContextMenuCommand(browserControl, browser, frame, parameters, commandId, eventFlags)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (commandId) {
|
|
||||||
case MenuSkipTweet:
|
|
||||||
form.InvokeAsyncSafe(form.FinishCurrentNotification);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case MenuFreeze:
|
|
||||||
form.InvokeAsyncSafe(() => form.FreezeTimer = !form.FreezeTimer);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case MenuViewDetail:
|
|
||||||
form.InvokeSafe(form.ShowTweetDetail);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case MenuCopyTweetUrl:
|
|
||||||
SetClipboardText(form, form.CurrentTweetUrl);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case MenuCopyQuotedTweetUrl:
|
|
||||||
SetClipboardText(form, form.CurrentQuoteUrl);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame) {
|
public override void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame) {
|
||||||
|
@ -1,32 +1,43 @@
|
|||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using CefSharp;
|
using CefSharp;
|
||||||
using TweetDuck.Utils;
|
using TweetDuck.Utils;
|
||||||
|
using TweetLib.Utils.Static;
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Handling {
|
namespace TweetDuck.Browser.Handling {
|
||||||
class KeyboardHandlerBase : IKeyboardHandler {
|
sealed class CustomKeyboardHandler : IKeyboardHandler {
|
||||||
protected virtual bool HandleRawKey(IWebBrowser browserControl, Keys key, CefEventFlags modifiers) {
|
private readonly IBrowserKeyHandler handler;
|
||||||
|
|
||||||
|
public CustomKeyboardHandler(IBrowserKeyHandler handler) {
|
||||||
|
this.handler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IKeyboardHandler.OnPreKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey, ref bool isKeyboardShortcut) {
|
||||||
|
if (type != KeyType.RawKeyDown) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var frame = browser.FocusedFrame) {
|
||||||
|
if (frame.Url.StartsWithOrdinal("devtools://")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Keys key = (Keys) windowsKeyCode;
|
||||||
|
|
||||||
if (modifiers == (CefEventFlags.ControlDown | CefEventFlags.ShiftDown) && key == Keys.I) {
|
if (modifiers == (CefEventFlags.ControlDown | CefEventFlags.ShiftDown) && key == Keys.I) {
|
||||||
browserControl.OpenDevToolsCustom();
|
browserControl.OpenDevToolsCustom();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return handler != null && handler.HandleBrowserKey(key);
|
||||||
}
|
|
||||||
|
|
||||||
bool IKeyboardHandler.OnPreKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey, ref bool isKeyboardShortcut) {
|
|
||||||
if (type == KeyType.RawKeyDown) {
|
|
||||||
using var frame = browser.FocusedFrame;
|
|
||||||
|
|
||||||
if (!frame.Url.StartsWith("devtools://")) {
|
|
||||||
return HandleRawKey(browserControl, (Keys) windowsKeyCode, modifiers);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IKeyboardHandler.OnKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey) {
|
bool IKeyboardHandler.OnKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface IBrowserKeyHandler {
|
||||||
|
bool HandleBrowserKey(Keys key);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,24 +1,23 @@
|
|||||||
using System;
|
using CefSharp;
|
||||||
using CefSharp;
|
|
||||||
using CefSharp.Handler;
|
using CefSharp.Handler;
|
||||||
using TweetDuck.Controls;
|
using TweetLib.Core;
|
||||||
using TweetDuck.Utils;
|
using TweetLib.Utils.Static;
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Handling.General {
|
namespace TweetDuck.Browser.Handling {
|
||||||
sealed class CustomLifeSpanHandler : LifeSpanHandler {
|
sealed class CustomLifeSpanHandler : LifeSpanHandler {
|
||||||
private static bool IsPopupAllowed(string url) {
|
private static bool IsPopupAllowed(string url) {
|
||||||
return url.StartsWith("https://twitter.com/teams/authorize?", StringComparison.Ordinal) ||
|
return url.StartsWithOrdinal("https://twitter.com/teams/authorize?") ||
|
||||||
url.StartsWith("https://accounts.google.com/", StringComparison.Ordinal) ||
|
url.StartsWithOrdinal("https://accounts.google.com/") ||
|
||||||
url.StartsWith("https://appleid.apple.com/", StringComparison.Ordinal);
|
url.StartsWithOrdinal("https://appleid.apple.com/");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool HandleLinkClick(IWebBrowser browserControl, WindowOpenDisposition targetDisposition, string targetUrl) {
|
public static bool HandleLinkClick(WindowOpenDisposition targetDisposition, string targetUrl) {
|
||||||
switch (targetDisposition) {
|
switch (targetDisposition) {
|
||||||
case WindowOpenDisposition.NewBackgroundTab:
|
case WindowOpenDisposition.NewBackgroundTab:
|
||||||
case WindowOpenDisposition.NewForegroundTab:
|
case WindowOpenDisposition.NewForegroundTab:
|
||||||
case WindowOpenDisposition.NewPopup when !IsPopupAllowed(targetUrl):
|
case WindowOpenDisposition.NewPopup when !IsPopupAllowed(targetUrl):
|
||||||
case WindowOpenDisposition.NewWindow:
|
case WindowOpenDisposition.NewWindow:
|
||||||
browserControl.AsControl().InvokeAsyncSafe(() => BrowserUtils.OpenExternalBrowser(targetUrl));
|
App.SystemHandler.OpenBrowser(targetUrl);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@ -28,7 +27,7 @@ public static bool HandleLinkClick(IWebBrowser browserControl, WindowOpenDisposi
|
|||||||
|
|
||||||
protected override bool OnBeforePopup(IWebBrowser browserControl, IBrowser browser, IFrame frame, string targetUrl, string targetFrameName, WindowOpenDisposition targetDisposition, bool userGesture, IPopupFeatures popupFeatures, IWindowInfo windowInfo, IBrowserSettings browserSettings, ref bool noJavascriptAccess, out IWebBrowser newBrowser) {
|
protected override bool OnBeforePopup(IWebBrowser browserControl, IBrowser browser, IFrame frame, string targetUrl, string targetFrameName, WindowOpenDisposition targetDisposition, bool userGesture, IPopupFeatures popupFeatures, IWindowInfo windowInfo, IBrowserSettings browserSettings, ref bool noJavascriptAccess, out IWebBrowser newBrowser) {
|
||||||
newBrowser = null;
|
newBrowser = null;
|
||||||
return HandleLinkClick(browserControl, targetDisposition, targetUrl);
|
return HandleLinkClick(targetDisposition, targetUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool DoClose(IWebBrowser browserControl, IBrowser browser) {
|
protected override bool DoClose(IWebBrowser browserControl, IBrowser browser) {
|
@ -1,7 +1,6 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using CefSharp;
|
using CefSharp;
|
||||||
using CefSharp.Enums;
|
using CefSharp.Enums;
|
||||||
using TweetDuck.Utils;
|
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Handling {
|
namespace TweetDuck.Browser.Handling {
|
||||||
sealed class DragHandlerBrowser : IDragHandler {
|
sealed class DragHandlerBrowser : IDragHandler {
|
||||||
@ -13,7 +12,7 @@ public DragHandlerBrowser(RequestHandlerBrowser requestHandler) {
|
|||||||
|
|
||||||
public bool OnDragEnter(IWebBrowser browserControl, IBrowser browser, IDragData dragData, DragOperationsMask mask) {
|
public bool OnDragEnter(IWebBrowser browserControl, IBrowser browser, IDragData dragData, DragOperationsMask mask) {
|
||||||
void TriggerDragStart(string type, string data = null) {
|
void TriggerDragStart(string type, string data = null) {
|
||||||
browserControl.ExecuteJsAsync("window.TDGF_onGlobalDragStart", type, data);
|
browserControl.BrowserCore.ExecuteScriptAsync("window.TDGF_onGlobalDragStart", type, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
requestHandler.BlockNextUserNavUrl = dragData.LinkUrl; // empty if not a link
|
requestHandler.BlockNextUserNavUrl = dragData.LinkUrl; // empty if not a link
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using CefSharp;
|
using CefSharp;
|
||||||
using TweetLib.Utils.Static;
|
using TweetLib.Utils.Static;
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Handling.General {
|
namespace TweetDuck.Browser.Handling {
|
||||||
sealed class FileDialogHandler : IDialogHandler {
|
sealed class FileDialogHandler : IDialogHandler {
|
||||||
public bool OnFileDialog(IWebBrowser browserControl, IBrowser browser, CefFileDialogMode mode, CefFileDialogFlags flags, string title, string defaultFilePath, List<string> acceptFilters, int selectedAcceptFilter, IFileDialogCallback callback) {
|
public bool OnFileDialog(IWebBrowser browserControl, IBrowser browser, CefFileDialogMode mode, CefFileDialogFlags flags, string title, string defaultFilePath, List<string> acceptFilters, int selectedAcceptFilter, IFileDialogCallback callback) {
|
||||||
if (mode == CefFileDialogMode.Open || mode == CefFileDialogMode.OpenMultiple) {
|
if (mode == CefFileDialogMode.Open || mode == CefFileDialogMode.OpenMultiple) {
|
||||||
@ -54,7 +55,7 @@ private static IEnumerable<string> ParseFileType(string type) {
|
|||||||
case "video/quicktime": return new string[] { ".mov", ".qt" };
|
case "video/quicktime": return new string[] { ".mov", ".qt" };
|
||||||
}
|
}
|
||||||
|
|
||||||
System.Diagnostics.Debugger.Break();
|
Debugger.Break();
|
||||||
return StringUtils.EmptyArray;
|
return StringUtils.EmptyArray;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,20 +0,0 @@
|
|||||||
using System.Text;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Handling.Filters {
|
|
||||||
sealed class ResponseFilterVendor : ResponseFilterBase {
|
|
||||||
private static readonly Regex RegexRestoreJQuery = new Regex(@"(\w+)\.fn=\1\.prototype", RegexOptions.Compiled);
|
|
||||||
|
|
||||||
public ResponseFilterVendor(int totalBytes) : base(totalBytes, Encoding.UTF8) {}
|
|
||||||
|
|
||||||
public override bool InitFilter() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override string ProcessResponse(string text) {
|
|
||||||
return RegexRestoreJQuery.Replace(text, "window.$$=$1;$&", 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Dispose() {}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,11 +1,12 @@
|
|||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using CefSharp;
|
using CefSharp;
|
||||||
|
using CefSharp.WinForms;
|
||||||
using TweetDuck.Controls;
|
using TweetDuck.Controls;
|
||||||
using TweetDuck.Dialogs;
|
using TweetDuck.Dialogs;
|
||||||
using TweetDuck.Utils;
|
using TweetDuck.Utils;
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Handling.General {
|
namespace TweetDuck.Browser.Handling {
|
||||||
sealed class JavaScriptDialogHandler : IJsDialogHandler {
|
sealed class JavaScriptDialogHandler : IJsDialogHandler {
|
||||||
private static FormMessage CreateMessageForm(string caption, string text) {
|
private static FormMessage CreateMessageForm(string caption, string text) {
|
||||||
MessageBoxIcon icon = MessageBoxIcon.None;
|
MessageBoxIcon icon = MessageBoxIcon.None;
|
||||||
@ -29,7 +30,9 @@ private static FormMessage CreateMessageForm(string caption, string text) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool IJsDialogHandler.OnJSDialog(IWebBrowser browserControl, IBrowser browser, string originUrl, CefJsDialogType dialogType, string messageText, string defaultPromptText, IJsDialogCallback callback, ref bool suppressMessage) {
|
bool IJsDialogHandler.OnJSDialog(IWebBrowser browserControl, IBrowser browser, string originUrl, CefJsDialogType dialogType, string messageText, string defaultPromptText, IJsDialogCallback callback, ref bool suppressMessage) {
|
||||||
browserControl.AsControl().InvokeSafe(() => {
|
var control = (ChromiumWebBrowser) browserControl;
|
||||||
|
|
||||||
|
control.InvokeSafe(() => {
|
||||||
FormMessage form;
|
FormMessage form;
|
||||||
TextBox input = null;
|
TextBox input = null;
|
||||||
|
|
@ -1,16 +0,0 @@
|
|||||||
using System.Windows.Forms;
|
|
||||||
using CefSharp;
|
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Handling {
|
|
||||||
sealed class KeyboardHandlerBrowser : KeyboardHandlerBase {
|
|
||||||
private readonly FormBrowser form;
|
|
||||||
|
|
||||||
public KeyboardHandlerBrowser(FormBrowser form) {
|
|
||||||
this.form = form;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool HandleRawKey(IWebBrowser browserControl, Keys key, CefEventFlags modifiers) {
|
|
||||||
return base.HandleRawKey(browserControl, key, modifiers) || form.ProcessBrowserKey(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,37 +0,0 @@
|
|||||||
using System.Windows.Forms;
|
|
||||||
using CefSharp;
|
|
||||||
using TweetDuck.Browser.Notification;
|
|
||||||
using TweetDuck.Controls;
|
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Handling {
|
|
||||||
sealed class KeyboardHandlerNotification : KeyboardHandlerBase {
|
|
||||||
private readonly FormNotificationBase notification;
|
|
||||||
|
|
||||||
public KeyboardHandlerNotification(FormNotificationBase notification) {
|
|
||||||
this.notification = notification;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool HandleRawKey(IWebBrowser browserControl, Keys key, CefEventFlags modifiers) {
|
|
||||||
if (base.HandleRawKey(browserControl, key, modifiers)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (key) {
|
|
||||||
case Keys.Enter:
|
|
||||||
notification.InvokeAsyncSafe(notification.FinishCurrentNotification);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case Keys.Escape:
|
|
||||||
notification.InvokeAsyncSafe(notification.HideNotification);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case Keys.Space:
|
|
||||||
notification.InvokeAsyncSafe(() => notification.FreezeTimer = !notification.FreezeTimer);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +1,5 @@
|
|||||||
using CefSharp;
|
using CefSharp;
|
||||||
using CefSharp.Handler;
|
using CefSharp.Handler;
|
||||||
using TweetDuck.Browser.Handling.General;
|
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Handling {
|
namespace TweetDuck.Browser.Handling {
|
||||||
class RequestHandlerBase : RequestHandler {
|
class RequestHandlerBase : RequestHandler {
|
||||||
@ -11,7 +10,7 @@ public RequestHandlerBase(bool autoReload) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnOpenUrlFromTab(IWebBrowser browserControl, IBrowser browser, IFrame frame, string targetUrl, WindowOpenDisposition targetDisposition, bool userGesture) {
|
protected override bool OnOpenUrlFromTab(IWebBrowser browserControl, IBrowser browser, IFrame frame, string targetUrl, WindowOpenDisposition targetDisposition, bool userGesture) {
|
||||||
return CustomLifeSpanHandler.HandleLinkClick(browserControl, targetDisposition, targetUrl);
|
return CustomLifeSpanHandler.HandleLinkClick(targetDisposition, targetUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnRenderProcessTerminated(IWebBrowser browserControl, IBrowser browser, CefTerminationStatus status) {
|
protected override void OnRenderProcessTerminated(IWebBrowser browserControl, IBrowser browser, CefTerminationStatus status) {
|
||||||
|
@ -1,35 +0,0 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using CefSharp;
|
|
||||||
using TweetDuck.Browser.Data;
|
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Handling {
|
|
||||||
abstract class ResourceRequestHandler : CefSharp.Handler.ResourceRequestHandler {
|
|
||||||
private class SelfFactoryImpl : IResourceRequestHandlerFactory {
|
|
||||||
private readonly ResourceRequestHandler me;
|
|
||||||
|
|
||||||
public SelfFactoryImpl(ResourceRequestHandler me) {
|
|
||||||
this.me = me;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IResourceRequestHandlerFactory.HasHandlers => true;
|
|
||||||
|
|
||||||
[SuppressMessage("ReSharper", "RedundantAssignment")]
|
|
||||||
IResourceRequestHandler IResourceRequestHandlerFactory.GetResourceRequestHandler(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, bool isNavigation, bool isDownload, string requestInitiator, ref bool disableDefaultHandling) {
|
|
||||||
disableDefaultHandling = me.ResourceHandlers.HasHandler(request);
|
|
||||||
return me;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public IResourceRequestHandlerFactory SelfFactory { get; }
|
|
||||||
public ResourceHandlers ResourceHandlers { get; }
|
|
||||||
|
|
||||||
protected ResourceRequestHandler() {
|
|
||||||
this.SelfFactory = new SelfFactoryImpl(this);
|
|
||||||
this.ResourceHandlers = new ResourceHandlers();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override IResourceHandler GetResourceHandler(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request) {
|
|
||||||
return ResourceHandlers.GetHandler(request);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,66 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.Specialized;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using CefSharp;
|
|
||||||
using TweetLib.Utils.Static;
|
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Handling {
|
|
||||||
class ResourceRequestHandlerBase : ResourceRequestHandler {
|
|
||||||
private static readonly Regex TweetDeckResourceUrl = new Regex(@"/dist/(.*?)\.(.*?)\.(css|js)$");
|
|
||||||
private static readonly SortedList<string, string> TweetDeckHashes = new SortedList<string, string>(4);
|
|
||||||
|
|
||||||
public static void LoadResourceRewriteRules(string rules) {
|
|
||||||
if (string.IsNullOrEmpty(rules)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
TweetDeckHashes.Clear();
|
|
||||||
|
|
||||||
foreach (string rule in rules.Replace(" ", "").ToLower().Split(',')) {
|
|
||||||
var (key, hash) = StringUtils.SplitInTwo(rule, '=') ?? throw new ArgumentException("A rule must have one '=' character: " + rule);
|
|
||||||
|
|
||||||
if (hash.All(chr => char.IsDigit(chr) || (chr >= 'a' && chr <= 'f'))) {
|
|
||||||
TweetDeckHashes.Add(key, hash);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
throw new ArgumentException("Invalid hash characters: " + rule);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override CefReturnValue OnBeforeResourceLoad(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IRequestCallback callback) {
|
|
||||||
if (request.ResourceType == ResourceType.CspReport) {
|
|
||||||
callback.Dispose();
|
|
||||||
return CefReturnValue.Cancel;
|
|
||||||
}
|
|
||||||
|
|
||||||
NameValueCollection headers = request.Headers;
|
|
||||||
headers.Remove("x-devtools-emulate-network-conditions-client-id");
|
|
||||||
request.Headers = headers;
|
|
||||||
|
|
||||||
return base.OnBeforeResourceLoad(browserControl, browser, frame, request, callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool OnResourceResponse(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IResponse response) {
|
|
||||||
if ((request.ResourceType == ResourceType.Script || request.ResourceType == ResourceType.Stylesheet) && TweetDeckHashes.Count > 0) {
|
|
||||||
string url = request.Url;
|
|
||||||
Match match = TweetDeckResourceUrl.Match(url);
|
|
||||||
|
|
||||||
if (match.Success && TweetDeckHashes.TryGetValue($"{match.Groups[1]}.{match.Groups[3]}", out string hash)) {
|
|
||||||
if (match.Groups[2].Value == hash) {
|
|
||||||
Program.Reporter.LogVerbose("[RequestHandlerBase] Accepting " + url);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Program.Reporter.LogVerbose("[RequestHandlerBase] Replacing " + url + " hash with " + hash);
|
|
||||||
request.Url = TweetDeckResourceUrl.Replace(url, $"/dist/$1.{hash}.$3");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return base.OnResourceResponse(browserControl, browser, frame, request, response);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,52 +0,0 @@
|
|||||||
using CefSharp;
|
|
||||||
using TweetDuck.Browser.Handling.Filters;
|
|
||||||
using TweetDuck.Utils;
|
|
||||||
using TweetLib.Core.Features.Twitter;
|
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Handling {
|
|
||||||
class ResourceRequestHandlerBrowser : ResourceRequestHandlerBase {
|
|
||||||
private const string UrlVendorResource = "/dist/vendor";
|
|
||||||
private const string UrlLoadingSpinner = "/backgrounds/spinner_blue";
|
|
||||||
private const string UrlVersionCheck = "/web/dist/version.json";
|
|
||||||
|
|
||||||
protected override CefReturnValue OnBeforeResourceLoad(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IRequestCallback callback) {
|
|
||||||
if (request.ResourceType == ResourceType.MainFrame) {
|
|
||||||
if (request.Url.EndsWith("//twitter.com/")) {
|
|
||||||
request.Url = TwitterUrls.TweetDeck; // redirect plain twitter.com requests, fixes bugs with login 2FA
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (request.ResourceType == ResourceType.Image) {
|
|
||||||
if (request.Url.Contains(UrlLoadingSpinner)) {
|
|
||||||
request.Url = TwitterUtils.LoadingSpinner.Url;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (request.ResourceType == ResourceType.Script) {
|
|
||||||
string url = request.Url;
|
|
||||||
|
|
||||||
if (url.Contains("analytics.")) {
|
|
||||||
callback.Dispose();
|
|
||||||
return CefReturnValue.Cancel;
|
|
||||||
}
|
|
||||||
else if (url.Contains(UrlVendorResource)) {
|
|
||||||
request.SetHeaderByName("Accept-Encoding", "identity", overwrite: true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (request.ResourceType == ResourceType.Xhr) {
|
|
||||||
if (request.Url.Contains(UrlVersionCheck)) {
|
|
||||||
callback.Dispose();
|
|
||||||
return CefReturnValue.Cancel;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return base.OnBeforeResourceLoad(browserControl, browser, frame, request, callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override IResponseFilter GetResourceResponseFilter(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IResponse response) {
|
|
||||||
if (request.ResourceType == ResourceType.Script && request.Url.Contains(UrlVendorResource) && int.TryParse(response.Headers["Content-Length"], out int totalBytes)) {
|
|
||||||
return new ResponseFilterVendor(totalBytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
return base.GetResourceResponseFilter(browserControl, browser, frame, request, response);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,28 +1,32 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
|
||||||
using CefSharp;
|
using CefSharp;
|
||||||
|
using TweetLib.Browser.Interfaces;
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Handling.Filters {
|
namespace TweetDuck.Browser.Handling {
|
||||||
abstract class ResponseFilterBase : IResponseFilter {
|
sealed class ResponseFilter : IResponseFilter {
|
||||||
private enum State {
|
private enum State {
|
||||||
Reading,
|
Reading,
|
||||||
Writing,
|
Writing,
|
||||||
Done
|
Done
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly Encoding encoding;
|
private readonly IResponseProcessor processor;
|
||||||
private byte[] responseData;
|
private byte[] responseData;
|
||||||
|
|
||||||
private State state;
|
private State state;
|
||||||
private int offset;
|
private int offset;
|
||||||
|
|
||||||
protected ResponseFilterBase(int totalBytes, Encoding encoding) {
|
public ResponseFilter(IResponseProcessor processor, int totalBytes) {
|
||||||
|
this.processor = processor;
|
||||||
this.responseData = new byte[totalBytes];
|
this.responseData = new byte[totalBytes];
|
||||||
this.encoding = encoding;
|
|
||||||
this.state = State.Reading;
|
this.state = State.Reading;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool InitFilter() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
FilterStatus IResponseFilter.Filter(Stream dataIn, out long dataInRead, Stream dataOut, out long dataOutWritten) {
|
FilterStatus IResponseFilter.Filter(Stream dataIn, out long dataInRead, Stream dataOut, out long dataOutWritten) {
|
||||||
int responseLength = responseData.Length;
|
int responseLength = responseData.Length;
|
||||||
|
|
||||||
@ -36,7 +40,7 @@ FilterStatus IResponseFilter.Filter(Stream dataIn, out long dataInRead, Stream d
|
|||||||
dataOutWritten = 0;
|
dataOutWritten = 0;
|
||||||
|
|
||||||
if (offset >= responseLength) {
|
if (offset >= responseLength) {
|
||||||
responseData = encoding.GetBytes(ProcessResponse(encoding.GetString(responseData)));
|
responseData = processor.Process(responseData);
|
||||||
state = State.Writing;
|
state = State.Writing;
|
||||||
offset = 0;
|
offset = 0;
|
||||||
}
|
}
|
||||||
@ -67,8 +71,6 @@ FilterStatus IResponseFilter.Filter(Stream dataIn, out long dataInRead, Stream d
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract bool InitFilter();
|
public void Dispose() {}
|
||||||
protected abstract string ProcessResponse(string text);
|
|
||||||
public abstract void Dispose();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,35 +1,20 @@
|
|||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using CefSharp.WinForms;
|
using CefSharp.WinForms;
|
||||||
using TweetDuck.Browser.Data;
|
using TweetDuck.Browser.Adapters;
|
||||||
using TweetDuck.Browser.Handling;
|
using TweetDuck.Browser.Handling;
|
||||||
using TweetDuck.Browser.Handling.General;
|
|
||||||
using TweetDuck.Configuration;
|
using TweetDuck.Configuration;
|
||||||
using TweetDuck.Controls;
|
using TweetDuck.Controls;
|
||||||
using TweetDuck.Utils;
|
using TweetDuck.Utils;
|
||||||
|
using TweetLib.Browser.Interfaces;
|
||||||
using TweetLib.Core.Features.Notifications;
|
using TweetLib.Core.Features.Notifications;
|
||||||
using TweetLib.Core.Features.Twitter;
|
using TweetLib.Core.Features.Twitter;
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Notification {
|
namespace TweetDuck.Browser.Notification {
|
||||||
abstract partial class FormNotificationBase : Form {
|
abstract partial class FormNotificationBase : Form {
|
||||||
public static readonly ResourceLink AppLogo = new ResourceLink("https://ton.twimg.com/tduck/avatar", ResourceHandlers.ForBytes(Properties.Resources.avatar, "image/png"));
|
|
||||||
|
|
||||||
protected const string BlankURL = TwitterUrls.TweetDeck + "/?blank";
|
|
||||||
|
|
||||||
public static string FontSize = null;
|
|
||||||
public static string HeadLayout = null;
|
|
||||||
|
|
||||||
protected static UserConfig Config => Program.Config.User;
|
protected static UserConfig Config => Program.Config.User;
|
||||||
|
|
||||||
protected static int FontSizeLevel {
|
protected delegate NotificationBrowser CreateBrowserImplFunc(FormNotificationBase form, IBrowserComponent browserComponent);
|
||||||
get => FontSize switch {
|
|
||||||
"largest" => 4,
|
|
||||||
"large" => 3,
|
|
||||||
"small" => 1,
|
|
||||||
"smallest" => 0,
|
|
||||||
_ => 2
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual Point PrimaryLocation {
|
protected virtual Point PrimaryLocation {
|
||||||
get {
|
get {
|
||||||
@ -100,6 +85,9 @@ protected virtual FormBorderStyle NotificationBorderStyle {
|
|||||||
|
|
||||||
private readonly FormBrowser owner;
|
private readonly FormBrowser owner;
|
||||||
|
|
||||||
|
protected readonly IBrowserComponent browserComponent;
|
||||||
|
private readonly NotificationBrowser browserImpl;
|
||||||
|
|
||||||
#pragma warning disable IDE0069 // Disposable fields should be disposed
|
#pragma warning disable IDE0069 // Disposable fields should be disposed
|
||||||
protected readonly ChromiumWebBrowser browser;
|
protected readonly ChromiumWebBrowser browser;
|
||||||
#pragma warning restore IDE0069 // Disposable fields should be disposed
|
#pragma warning restore IDE0069 // Disposable fields should be disposed
|
||||||
@ -112,43 +100,33 @@ protected virtual FormBorderStyle NotificationBorderStyle {
|
|||||||
public string CurrentTweetUrl => currentNotification?.TweetUrl;
|
public string CurrentTweetUrl => currentNotification?.TweetUrl;
|
||||||
public string CurrentQuoteUrl => currentNotification?.QuoteUrl;
|
public string CurrentQuoteUrl => currentNotification?.QuoteUrl;
|
||||||
|
|
||||||
public bool CanViewDetail => currentNotification != null && !string.IsNullOrEmpty(currentNotification.ColumnId) && !string.IsNullOrEmpty(currentNotification.ChirpId);
|
|
||||||
|
|
||||||
protected bool IsPaused => pauseCounter > 0;
|
protected bool IsPaused => pauseCounter > 0;
|
||||||
protected bool IsCursorOverBrowser => browser.Bounds.Contains(PointToClient(Cursor.Position));
|
protected internal bool IsCursorOverBrowser => browser.Bounds.Contains(PointToClient(Cursor.Position));
|
||||||
|
|
||||||
public bool FreezeTimer { get; set; }
|
public bool FreezeTimer { get; set; }
|
||||||
public bool ContextMenuOpen { get; set; }
|
public bool ContextMenuOpen { get; set; }
|
||||||
|
|
||||||
protected FormNotificationBase(FormBrowser owner, bool enableContextMenu) {
|
protected FormNotificationBase(FormBrowser owner, CreateBrowserImplFunc createBrowserImpl) {
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
this.owner = owner;
|
this.owner = owner;
|
||||||
this.owner.FormClosed += owner_FormClosed;
|
this.owner.FormClosed += owner_FormClosed;
|
||||||
|
|
||||||
var resourceRequestHandler = new ResourceRequestHandlerBase();
|
this.browser = new ChromiumWebBrowser(NotificationBrowser.BlankURL) {
|
||||||
var resourceHandlers = resourceRequestHandler.ResourceHandlers;
|
RequestHandler = new RequestHandlerBase(false)
|
||||||
|
|
||||||
resourceHandlers.Register(BlankURL, ResourceHandlers.ForString(string.Empty));
|
|
||||||
resourceHandlers.Register(TwitterUrls.TweetDeck, () => this.resourceHandler);
|
|
||||||
resourceHandlers.Register(AppLogo);
|
|
||||||
|
|
||||||
this.browser = new ChromiumWebBrowser(BlankURL) {
|
|
||||||
MenuHandler = new ContextMenuNotification(this, enableContextMenu),
|
|
||||||
JsDialogHandler = new JavaScriptDialogHandler(),
|
|
||||||
LifeSpanHandler = new CustomLifeSpanHandler(),
|
|
||||||
RequestHandler = new RequestHandlerBase(false),
|
|
||||||
ResourceRequestHandlerFactory = resourceRequestHandler.SelfFactory
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.browserComponent = new ComponentImpl(browser, this);
|
||||||
|
this.browserImpl = createBrowserImpl(this, browserComponent);
|
||||||
|
|
||||||
this.browser.Dock = DockStyle.None;
|
this.browser.Dock = DockStyle.None;
|
||||||
this.browser.ClientSize = ClientSize;
|
this.browser.ClientSize = ClientSize;
|
||||||
this.browser.SetupZoomEvents();
|
|
||||||
|
|
||||||
Controls.Add(browser);
|
Controls.Add(browser);
|
||||||
|
|
||||||
Disposed += (sender, args) => {
|
Disposed += (sender, args) => {
|
||||||
this.owner.FormClosed -= owner_FormClosed;
|
this.owner.FormClosed -= owner_FormClosed;
|
||||||
|
this.browserImpl.Dispose();
|
||||||
this.browser.Dispose();
|
this.browser.Dispose();
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -158,6 +136,25 @@ protected FormNotificationBase(FormBrowser owner, bool enableContextMenu) {
|
|||||||
UpdateTitle();
|
UpdateTitle();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected sealed class ComponentImpl : CefBrowserComponent {
|
||||||
|
private readonly FormNotificationBase owner;
|
||||||
|
|
||||||
|
public ComponentImpl(ChromiumWebBrowser browser, FormNotificationBase owner) : base(browser) {
|
||||||
|
this.owner = owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override ContextMenuBase SetupContextMenu(IContextMenuHandler handler) {
|
||||||
|
return new ContextMenuNotification(owner, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override CefResourceHandlerFactory SetupResourceHandlerFactory(IResourceRequestHandler handler) {
|
||||||
|
var registry = new CefResourceHandlerRegistry();
|
||||||
|
registry.RegisterStatic(NotificationBrowser.BlankURL, string.Empty);
|
||||||
|
registry.RegisterDynamic(TwitterUrls.TweetDeck, owner.resourceHandler);
|
||||||
|
return new CefResourceHandlerFactory(handler, registry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool disposing) {
|
protected override void Dispose(bool disposing) {
|
||||||
if (disposing) {
|
if (disposing) {
|
||||||
components?.Dispose();
|
components?.Dispose();
|
||||||
@ -184,7 +181,7 @@ private void owner_FormClosed(object sender, FormClosedEventArgs e) {
|
|||||||
// notification methods
|
// notification methods
|
||||||
|
|
||||||
public virtual void HideNotification() {
|
public virtual void HideNotification() {
|
||||||
browser.Load(BlankURL);
|
browser.Load(NotificationBrowser.BlankURL);
|
||||||
DisplayTooltip(null);
|
DisplayTooltip(null);
|
||||||
|
|
||||||
Location = ControlExtensions.InvisibleLocation;
|
Location = ControlExtensions.InvisibleLocation;
|
||||||
@ -205,11 +202,9 @@ public virtual void ResumeNotification() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract string GetTweetHTML(DesktopNotification tweet);
|
|
||||||
|
|
||||||
protected virtual void LoadTweet(DesktopNotification tweet) {
|
protected virtual void LoadTweet(DesktopNotification tweet) {
|
||||||
currentNotification = tweet;
|
currentNotification = tweet;
|
||||||
resourceHandler.SetHTML(GetTweetHTML(tweet));
|
resourceHandler.SetHTML(browserImpl.GetTweetHTML(tweet));
|
||||||
|
|
||||||
browser.Load(TwitterUrls.TweetDeck);
|
browser.Load(TwitterUrls.TweetDeck);
|
||||||
DisplayTooltip(null);
|
DisplayTooltip(null);
|
||||||
@ -225,8 +220,8 @@ protected virtual void UpdateTitle() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void ShowTweetDetail() {
|
public void ShowTweetDetail() {
|
||||||
if (currentNotification != null) {
|
if (currentNotification != null && owner.ShowTweetDetail(currentNotification.ColumnId, currentNotification.ChirpId, currentNotification.TweetUrl)) {
|
||||||
owner.ShowTweetDetail(currentNotification.ColumnId, currentNotification.ChirpId, currentNotification.TweetUrl);
|
FinishCurrentNotification();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,13 +1,18 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using CefSharp;
|
|
||||||
using TweetDuck.Controls;
|
using TweetDuck.Controls;
|
||||||
using TweetDuck.Resources;
|
using TweetLib.Browser.Interfaces;
|
||||||
using TweetLib.Core.Features.Notifications;
|
using TweetLib.Core.Features.Notifications;
|
||||||
using TweetLib.Core.Features.Plugins;
|
using TweetLib.Core.Features.Plugins;
|
||||||
|
using TweetLib.Core.Features.TweetDeck;
|
||||||
|
using TweetLib.Core.Resources;
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Notification.Example {
|
namespace TweetDuck.Browser.Notification {
|
||||||
sealed class FormNotificationExample : FormNotificationMain {
|
sealed class FormNotificationExample : FormNotificationMain {
|
||||||
|
private static NotificationBrowser CreateBrowserImpl(IBrowserComponent browserComponent, INotificationInterface notificationInterface, ITweetDeckInterface tweetDeckInterface, PluginManager pluginManager) {
|
||||||
|
return new NotificationBrowser.Example(browserComponent, notificationInterface, tweetDeckInterface, pluginManager);
|
||||||
|
}
|
||||||
|
|
||||||
public override bool RequiresResize => true;
|
public override bool RequiresResize => true;
|
||||||
protected override bool CanDragWindow => Config.NotificationPosition == DesktopNotification.Position.Custom;
|
protected override bool CanDragWindow => Config.NotificationPosition == DesktopNotification.Position.Custom;
|
||||||
|
|
||||||
@ -24,16 +29,16 @@ protected override FormBorderStyle NotificationBorderStyle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string BodyClasses => base.BodyClasses + " td-example";
|
|
||||||
|
|
||||||
public event EventHandler Ready;
|
public event EventHandler Ready;
|
||||||
|
|
||||||
private readonly DesktopNotification exampleNotification;
|
private readonly DesktopNotification exampleNotification;
|
||||||
|
|
||||||
public FormNotificationExample(FormBrowser owner, PluginManager pluginManager) : base(owner, pluginManager, false) {
|
public FormNotificationExample(FormBrowser owner, ITweetDeckInterface tweetDeckInterface, PluginManager pluginManager) : base(owner, (form, browserComponent) => CreateBrowserImpl(browserComponent, new NotificationInterfaceImpl(form), tweetDeckInterface, pluginManager)) {
|
||||||
browser.LoadingStateChanged += browser_LoadingStateChanged;
|
browserComponent.BrowserLoaded += (sender, args) => {
|
||||||
|
Ready?.Invoke(this, EventArgs.Empty);
|
||||||
|
};
|
||||||
|
|
||||||
string exampleTweetHTML = ResourceUtils.ReadFileOrNull("notification/example/example.html")?.Replace("{avatar}", AppLogo.Url) ?? string.Empty;
|
string exampleTweetHTML = ResourceUtils.ReadFileOrNull("notification/example/example.html") ?? string.Empty;
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
exampleTweetHTML = exampleTweetHTML.Replace("</p>", @"</p><div style='margin-top:256px'>Scrollbar test padding...</div>");
|
exampleTweetHTML = exampleTweetHTML.Replace("</p>", @"</p><div style='margin-top:256px'>Scrollbar test padding...</div>");
|
||||||
@ -42,13 +47,6 @@ public FormNotificationExample(FormBrowser owner, PluginManager pluginManager) :
|
|||||||
exampleNotification = new DesktopNotification(string.Empty, string.Empty, "Home", exampleTweetHTML, 176, string.Empty, string.Empty);
|
exampleNotification = new DesktopNotification(string.Empty, string.Empty, "Home", exampleTweetHTML, 176, string.Empty, string.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e) {
|
|
||||||
if (!e.IsLoading) {
|
|
||||||
Ready?.Invoke(this, EventArgs.Empty);
|
|
||||||
browser.LoadingStateChanged -= browser_LoadingStateChanged;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void HideNotification() {
|
public override void HideNotification() {
|
||||||
Location = ControlExtensions.InvisibleLocation;
|
Location = ControlExtensions.InvisibleLocation;
|
||||||
}
|
}
|
@ -2,20 +2,50 @@
|
|||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using CefSharp;
|
using CefSharp;
|
||||||
using TweetDuck.Browser.Adapters;
|
|
||||||
using TweetDuck.Browser.Bridge;
|
|
||||||
using TweetDuck.Browser.Handling;
|
using TweetDuck.Browser.Handling;
|
||||||
using TweetDuck.Controls;
|
using TweetDuck.Controls;
|
||||||
using TweetDuck.Plugins;
|
|
||||||
using TweetDuck.Utils;
|
using TweetDuck.Utils;
|
||||||
using TweetLib.Core.Features.Notifications;
|
using TweetLib.Core.Features.Notifications;
|
||||||
using TweetLib.Core.Features.Plugins;
|
|
||||||
using TweetLib.Core.Features.Plugins.Enums;
|
|
||||||
using TweetLib.Core.Features.Twitter;
|
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Notification {
|
namespace TweetDuck.Browser.Notification {
|
||||||
abstract partial class FormNotificationMain : FormNotificationBase {
|
abstract partial class FormNotificationMain : FormNotificationBase, CustomKeyboardHandler.IBrowserKeyHandler {
|
||||||
private readonly PluginManager plugins;
|
protected sealed class NotificationInterfaceImpl : INotificationInterface {
|
||||||
|
public bool FreezeTimer {
|
||||||
|
get => notification.FreezeTimer;
|
||||||
|
set => notification.FreezeTimer = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsHovered => notification.IsCursorOverBrowser;
|
||||||
|
|
||||||
|
private readonly FormNotificationBase notification;
|
||||||
|
|
||||||
|
public NotificationInterfaceImpl(FormNotificationBase notification) {
|
||||||
|
this.notification = notification;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DisplayTooltip(string text) {
|
||||||
|
notification.InvokeAsyncSafe(() => notification.DisplayTooltip(text));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void FinishCurrentNotification() {
|
||||||
|
notification.InvokeAsyncSafe(notification.FinishCurrentNotification);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ShowTweetDetail() {
|
||||||
|
notification.InvokeAsyncSafe(notification.ShowTweetDetail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int FontSizeLevel {
|
||||||
|
get => NotificationBrowser.FontSize switch {
|
||||||
|
"largest" => 4,
|
||||||
|
"large" => 3,
|
||||||
|
"small" => 1,
|
||||||
|
"smallest" => 0,
|
||||||
|
_ => 2
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private readonly int timerBarHeight;
|
private readonly int timerBarHeight;
|
||||||
|
|
||||||
protected int timeLeft, totalTime;
|
protected int timeLeft, totalTime;
|
||||||
@ -59,29 +89,21 @@ private int BaseClientHeight {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual string BodyClasses => IsCursorOverBrowser ? "td-notification td-hover" : "td-notification";
|
|
||||||
|
|
||||||
public Size BrowserSize => Config.DisplayNotificationTimer ? new Size(ClientSize.Width, ClientSize.Height - timerBarHeight) : ClientSize;
|
public Size BrowserSize => Config.DisplayNotificationTimer ? new Size(ClientSize.Width, ClientSize.Height - timerBarHeight) : ClientSize;
|
||||||
|
|
||||||
protected FormNotificationMain(FormBrowser owner, PluginManager pluginManager, bool enableContextMenu) : base(owner, enableContextMenu) {
|
protected FormNotificationMain(FormBrowser owner, CreateBrowserImplFunc createBrowserImpl) : base(owner, createBrowserImpl) {
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
this.plugins = pluginManager;
|
|
||||||
this.timerBarHeight = BrowserUtils.Scale(4, DpiScale);
|
this.timerBarHeight = BrowserUtils.Scale(4, DpiScale);
|
||||||
|
|
||||||
browser.KeyboardHandler = new KeyboardHandlerNotification(this);
|
browser.KeyboardHandler = new CustomKeyboardHandler(this);
|
||||||
browser.RegisterJsBridge("$TD", new TweetDeckBridge.Notification(owner, this));
|
|
||||||
|
|
||||||
browser.LoadingStateChanged += Browser_LoadingStateChanged;
|
browser.LoadingStateChanged += Browser_LoadingStateChanged;
|
||||||
|
|
||||||
plugins.Register(PluginEnvironment.Notification, new PluginDispatcher(browser, url => TwitterUrls.IsTweetDeck(url) && url != BlankURL));
|
|
||||||
|
|
||||||
mouseHookDelegate = MouseHookProc;
|
mouseHookDelegate = MouseHookProc;
|
||||||
Disposed += (sender, args) => StopMouseHook(true);
|
Disposed += (sender, args) => StopMouseHook(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// helpers
|
|
||||||
|
|
||||||
private void SetOpacity(int opacity) {
|
private void SetOpacity(int opacity) {
|
||||||
if (currentOpacity != opacity) {
|
if (currentOpacity != opacity) {
|
||||||
currentOpacity = opacity;
|
currentOpacity = opacity;
|
||||||
@ -113,7 +135,7 @@ private IntPtr MouseHookProc(int nCode, IntPtr wParam, IntPtr lParam) {
|
|||||||
int delta = BrowserUtils.Scale(NativeMethods.GetMouseHookData(lParam), Config.NotificationScrollSpeed * 0.01);
|
int delta = BrowserUtils.Scale(NativeMethods.GetMouseHookData(lParam), Config.NotificationScrollSpeed * 0.01);
|
||||||
|
|
||||||
if (Config.EnableSmoothScrolling) {
|
if (Config.EnableSmoothScrolling) {
|
||||||
browser.ExecuteJsAsync("window.TDGF_scrollSmoothly", (int) Math.Round(-delta / 0.6));
|
browser.BrowserCore.ExecuteScriptAsync("window.TDGF_scrollSmoothly", (int) Math.Round(-delta / 0.6));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
browser.SendMouseWheelEvent(0, 0, 0, delta, CefEventFlags.None);
|
browser.SendMouseWheelEvent(0, 0, 0, delta, CefEventFlags.None);
|
||||||
@ -158,7 +180,7 @@ private void FormNotification_FormClosing(object sender, FormClosingEventArgs e)
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void Browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e) {
|
private void Browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e) {
|
||||||
if (!e.IsLoading && browser.Address != BlankURL) {
|
if (!e.IsLoading && browser.Address != NotificationBrowser.BlankURL) {
|
||||||
this.InvokeSafe(() => {
|
this.InvokeSafe(() => {
|
||||||
Visible = true; // ensures repaint before moving the window to a visible location
|
Visible = true; // ensures repaint before moving the window to a visible location
|
||||||
timerDisplayDelay.Start();
|
timerDisplayDelay.Start();
|
||||||
@ -236,13 +258,6 @@ public override void ResumeNotification() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string GetTweetHTML(DesktopNotification tweet) {
|
|
||||||
return tweet.GenerateHtml(BodyClasses, HeadLayout, Config.CustomNotificationCSS, plugins.NotificationInjections, new string[] {
|
|
||||||
PropertyBridge.GenerateScript(PropertyBridge.Environment.Notification),
|
|
||||||
CefScriptExecutor.GetBootstrapScript("notification", includeStylesheets: false)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void LoadTweet(DesktopNotification tweet) {
|
protected override void LoadTweet(DesktopNotification tweet) {
|
||||||
timerProgress.Stop();
|
timerProgress.Stop();
|
||||||
totalTime = timeLeft = tweet.GetDisplayDuration(Config.NotificationDurationValue);
|
totalTime = timeLeft = tweet.GetDisplayDuration(Config.NotificationDurationValue);
|
||||||
@ -278,5 +293,24 @@ protected virtual void OnNotificationReady() {
|
|||||||
PrepareAndDisplayWindow();
|
PrepareAndDisplayWindow();
|
||||||
timerProgress.Start();
|
timerProgress.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CustomKeyboardHandler.IBrowserKeyHandler.HandleBrowserKey(Keys key) {
|
||||||
|
switch (key) {
|
||||||
|
case Keys.Enter:
|
||||||
|
this.InvokeAsyncSafe(FinishCurrentNotification);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case Keys.Escape:
|
||||||
|
this.InvokeAsyncSafe(HideNotification);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case Keys.Space:
|
||||||
|
this.InvokeAsyncSafe(() => FreezeTimer = !FreezeTimer);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,11 +3,17 @@
|
|||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using TweetDuck.Utils;
|
using TweetDuck.Utils;
|
||||||
|
using TweetLib.Browser.Interfaces;
|
||||||
using TweetLib.Core.Features.Notifications;
|
using TweetLib.Core.Features.Notifications;
|
||||||
using TweetLib.Core.Features.Plugins;
|
using TweetLib.Core.Features.Plugins;
|
||||||
|
using TweetLib.Core.Features.TweetDeck;
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Notification {
|
namespace TweetDuck.Browser.Notification {
|
||||||
sealed partial class FormNotificationTweet : FormNotificationMain {
|
sealed partial class FormNotificationTweet : FormNotificationMain {
|
||||||
|
private static NotificationBrowser CreateBrowserImpl(IBrowserComponent browserComponent, INotificationInterface notificationInterface, ITweetDeckInterface tweetDeckInterface, PluginManager pluginManager) {
|
||||||
|
return new NotificationBrowser.Tweet(browserComponent, notificationInterface, tweetDeckInterface, pluginManager);
|
||||||
|
}
|
||||||
|
|
||||||
private const int NonIntrusiveIdleLimit = 30;
|
private const int NonIntrusiveIdleLimit = 30;
|
||||||
private const int TrimMinimum = 32;
|
private const int TrimMinimum = 32;
|
||||||
|
|
||||||
@ -30,7 +36,7 @@ protected override bool CanDragWindow {
|
|||||||
private bool needsTrim;
|
private bool needsTrim;
|
||||||
private bool hasTemporarilyMoved;
|
private bool hasTemporarilyMoved;
|
||||||
|
|
||||||
public FormNotificationTweet(FormBrowser owner, PluginManager pluginManager) : base(owner, pluginManager, true) {
|
public FormNotificationTweet(FormBrowser owner, ITweetDeckInterface tweetDeckInterface, PluginManager pluginManager) : base(owner, (form, browserComponent) => CreateBrowserImpl(browserComponent, new NotificationInterfaceImpl(form), tweetDeckInterface, pluginManager)) {
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
Config.MuteToggled += Config_MuteToggled;
|
Config.MuteToggled += Config_MuteToggled;
|
||||||
|
@ -4,33 +4,30 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using CefSharp;
|
using CefSharp;
|
||||||
using CefSharp.DevTools.Page;
|
using CefSharp.DevTools.Page;
|
||||||
using TweetDuck.Browser.Adapters;
|
|
||||||
using TweetDuck.Controls;
|
using TweetDuck.Controls;
|
||||||
using TweetDuck.Dialogs;
|
using TweetDuck.Dialogs;
|
||||||
using TweetDuck.Resources;
|
|
||||||
using TweetDuck.Utils;
|
using TweetDuck.Utils;
|
||||||
|
using TweetLib.Browser.Interfaces;
|
||||||
using TweetLib.Core.Features.Notifications;
|
using TweetLib.Core.Features.Notifications;
|
||||||
using TweetLib.Core.Features.Plugins;
|
using TweetLib.Core.Features.Plugins;
|
||||||
|
using TweetLib.Core.Resources;
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Notification.Screenshot {
|
namespace TweetDuck.Browser.Notification.Screenshot {
|
||||||
sealed class FormNotificationScreenshotable : FormNotificationBase {
|
sealed class FormNotificationScreenshotable : FormNotificationBase {
|
||||||
|
private static NotificationBrowser CreateBrowserImpl( IBrowserComponent browserComponent, PluginManager pluginManager) {
|
||||||
|
return new NotificationBrowser.Screenshot(browserComponent, pluginManager.NotificationInjections);
|
||||||
|
}
|
||||||
|
|
||||||
protected override bool CanDragWindow => false;
|
protected override bool CanDragWindow => false;
|
||||||
|
|
||||||
private readonly PluginManager plugins;
|
|
||||||
private int height;
|
private int height;
|
||||||
|
|
||||||
public FormNotificationScreenshotable(Action callback, FormBrowser owner, PluginManager pluginManager, string html, int width) : base(owner, false) {
|
public FormNotificationScreenshotable(Action callback, FormBrowser owner, PluginManager pluginManager, string html, int width) : base(owner, (_, browserComponent) => CreateBrowserImpl(browserComponent, pluginManager)) {
|
||||||
this.plugins = pluginManager;
|
|
||||||
|
|
||||||
int realWidth = BrowserUtils.Scale(width, DpiScale);
|
int realWidth = BrowserUtils.Scale(width, DpiScale);
|
||||||
|
|
||||||
browser.RegisterJsBridge("$TD_NotificationScreenshot", new ScreenshotBridge(this, SetScreenshotHeight, callback));
|
browserComponent.AttachBridgeObject("$TD_NotificationScreenshot", new ScreenshotBridge(this, SetScreenshotHeight, callback));
|
||||||
|
|
||||||
browser.LoadingStateChanged += (sender, args) => {
|
|
||||||
if (args.IsLoading) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
browserComponent.BrowserLoaded += (sender, args) => {
|
||||||
string script = ResourceUtils.ReadFileOrNull("notification/screenshot/screenshot.js");
|
string script = ResourceUtils.ReadFileOrNull("notification/screenshot/screenshot.js");
|
||||||
|
|
||||||
if (script == null) {
|
if (script == null) {
|
||||||
@ -38,18 +35,14 @@ public FormNotificationScreenshotable(Action callback, FormBrowser owner, Plugin
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
using IFrame frame = args.Browser.MainFrame;
|
string substituted = script.Replace("{width}", realWidth.ToString()).Replace("1/*FRAMES*/", TweetScreenshotManager.WaitFrames.ToString());
|
||||||
CefScriptExecutor.RunScript(frame, script.Replace("{width}", realWidth.ToString()).Replace("1/*FRAMES*/", TweetScreenshotManager.WaitFrames.ToString()), "gen:screenshot");
|
browserComponent.RunScript("gen:screenshot", substituted);
|
||||||
};
|
};
|
||||||
|
|
||||||
SetNotificationSize(realWidth, 1024);
|
SetNotificationSize(realWidth, 1024);
|
||||||
LoadTweet(new DesktopNotification(string.Empty, string.Empty, string.Empty, html, 0, string.Empty, string.Empty));
|
LoadTweet(new DesktopNotification(string.Empty, string.Empty, string.Empty, html, 0, string.Empty, string.Empty));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string GetTweetHTML(DesktopNotification tweet) {
|
|
||||||
return tweet.GenerateHtml("td-screenshot", HeadLayout, Config.CustomNotificationCSS, plugins.NotificationInjections, Array.Empty<string>());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SetScreenshotHeight(int browserHeight) {
|
private void SetScreenshotHeight(int browserHeight) {
|
||||||
this.height = BrowserUtils.Scale(browserHeight, SizeScale);
|
this.height = BrowserUtils.Scale(browserHeight, SizeScale);
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,36 @@
|
|||||||
using System;
|
using System.Drawing;
|
||||||
using System.Drawing;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using CefSharp;
|
using TweetDuck.Browser.Adapters;
|
||||||
using TweetDuck.Browser.Data;
|
|
||||||
using TweetDuck.Controls;
|
using TweetDuck.Controls;
|
||||||
using TweetDuck.Dialogs;
|
using TweetDuck.Dialogs;
|
||||||
using TweetDuck.Dialogs.Settings;
|
using TweetDuck.Dialogs.Settings;
|
||||||
using TweetDuck.Management;
|
using TweetDuck.Management;
|
||||||
|
using TweetLib.Core.Features.TweetDeck;
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Notification {
|
namespace TweetDuck.Browser.Notification {
|
||||||
static class SoundNotification {
|
sealed class SoundNotification : ISoundNotificationHandler {
|
||||||
public const string SupportedFormats = "*.wav;*.ogg;*.mp3;*.flac;*.opus;*.weba;*.webm";
|
public const string SupportedFormats = "*.wav;*.ogg;*.mp3;*.flac;*.opus;*.weba;*.webm";
|
||||||
|
|
||||||
public static Func<IResourceHandler> CreateFileHandler(string path) {
|
private readonly CefResourceHandlerRegistry registry;
|
||||||
|
|
||||||
|
public SoundNotification(CefResourceHandlerRegistry registry) {
|
||||||
|
this.registry = registry;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Unregister(string url) {
|
||||||
|
registry.Unregister(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Register(string url, string path) {
|
||||||
|
var fileHandler = CreateFileHandler(path);
|
||||||
|
if (fileHandler.HasValue) {
|
||||||
|
var (data, mimeType) = fileHandler.Value;
|
||||||
|
registry.RegisterStatic(url, data, mimeType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static (byte[] data, string mimeType)? CreateFileHandler(string path) {
|
||||||
string mimeType = Path.GetExtension(path) switch {
|
string mimeType = Path.GetExtension(path) switch {
|
||||||
".weba" => "audio/webm",
|
".weba" => "audio/webm",
|
||||||
".webm" => "audio/webm",
|
".webm" => "audio/webm",
|
||||||
@ -26,7 +43,7 @@ public static Func<IResourceHandler> CreateFileHandler(string path) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return ResourceHandlers.ForBytes(File.ReadAllBytes(path), mimeType);
|
return (File.ReadAllBytes(path), mimeType);
|
||||||
} catch {
|
} catch {
|
||||||
FormBrowser browser = FormManager.TryFind<FormBrowser>();
|
FormBrowser browser = FormManager.TryFind<FormBrowser>();
|
||||||
|
|
||||||
|
@ -1,31 +1,25 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Text;
|
|
||||||
using System.Windows.Forms;
|
|
||||||
using CefSharp;
|
using CefSharp;
|
||||||
using CefSharp.WinForms;
|
using CefSharp.WinForms;
|
||||||
using TweetDuck.Browser.Adapters;
|
using TweetDuck.Browser.Adapters;
|
||||||
using TweetDuck.Browser.Bridge;
|
|
||||||
using TweetDuck.Browser.Data;
|
|
||||||
using TweetDuck.Browser.Handling;
|
using TweetDuck.Browser.Handling;
|
||||||
using TweetDuck.Browser.Handling.General;
|
|
||||||
using TweetDuck.Browser.Notification;
|
using TweetDuck.Browser.Notification;
|
||||||
using TweetDuck.Configuration;
|
using TweetDuck.Configuration;
|
||||||
using TweetDuck.Controls;
|
|
||||||
using TweetDuck.Plugins;
|
|
||||||
using TweetDuck.Utils;
|
using TweetDuck.Utils;
|
||||||
using TweetLib.Core.Features.Plugins;
|
using TweetLib.Core.Features.Plugins;
|
||||||
using TweetLib.Core.Features.Plugins.Enums;
|
using TweetLib.Core.Features.TweetDeck;
|
||||||
using TweetLib.Core.Features.Twitter;
|
using TweetLib.Core.Features.Twitter;
|
||||||
using TweetLib.Utils.Static;
|
using TweetLib.Core.Systems.Updates;
|
||||||
|
using IContextMenuHandler = TweetLib.Browser.Interfaces.IContextMenuHandler;
|
||||||
|
using IResourceRequestHandler = TweetLib.Browser.Interfaces.IResourceRequestHandler;
|
||||||
|
using TweetDeckBrowserImpl = TweetLib.Core.Features.TweetDeck.TweetDeckBrowser;
|
||||||
|
|
||||||
namespace TweetDuck.Browser {
|
namespace TweetDuck.Browser {
|
||||||
sealed class TweetDeckBrowser : IDisposable {
|
sealed class TweetDeckBrowser : IDisposable {
|
||||||
private static UserConfig Config => Program.Config.User;
|
public static readonly Color BackgroundColor = Color.FromArgb(28, 99, 153);
|
||||||
|
|
||||||
private const string NamespaceTweetDeck = "tweetdeck";
|
public bool Ready => browserComponent.Ready;
|
||||||
|
|
||||||
public bool Ready { get; private set; }
|
|
||||||
|
|
||||||
public bool Enabled {
|
public bool Enabled {
|
||||||
get => browser.Enabled;
|
get => browser.Enabled;
|
||||||
@ -43,53 +37,62 @@ public bool IsTweetDeckWebsite {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public TweetDeckFunctions Functions => browserImpl.Functions;
|
||||||
|
|
||||||
|
private readonly CefBrowserComponent browserComponent;
|
||||||
|
private readonly TweetDeckBrowserImpl browserImpl;
|
||||||
private readonly ChromiumWebBrowser browser;
|
private readonly ChromiumWebBrowser browser;
|
||||||
private readonly ResourceHandlers resourceHandlers;
|
|
||||||
|
|
||||||
private string prevSoundNotificationPath = null;
|
|
||||||
|
|
||||||
public TweetDeckBrowser(FormBrowser owner, PluginManager plugins, TweetDeckBridge tdBridge, UpdateBridge updateBridge) {
|
|
||||||
var resourceRequestHandler = new ResourceRequestHandlerBrowser();
|
|
||||||
resourceHandlers = resourceRequestHandler.ResourceHandlers;
|
|
||||||
|
|
||||||
resourceHandlers.Register(FormNotificationBase.AppLogo);
|
|
||||||
resourceHandlers.Register(TwitterUtils.LoadingSpinner);
|
|
||||||
|
|
||||||
|
public TweetDeckBrowser(FormBrowser owner, PluginManager pluginManager, ITweetDeckInterface tweetDeckInterface, UpdateChecker updateChecker) {
|
||||||
RequestHandlerBrowser requestHandler = new RequestHandlerBrowser();
|
RequestHandlerBrowser requestHandler = new RequestHandlerBrowser();
|
||||||
|
|
||||||
this.browser = new ChromiumWebBrowser(TwitterUrls.TweetDeck) {
|
this.browser = new ChromiumWebBrowser(TwitterUrls.TweetDeck) {
|
||||||
DialogHandler = new FileDialogHandler(),
|
DialogHandler = new FileDialogHandler(),
|
||||||
DragHandler = new DragHandlerBrowser(requestHandler),
|
DragHandler = new DragHandlerBrowser(requestHandler),
|
||||||
MenuHandler = new ContextMenuBrowser(owner),
|
KeyboardHandler = new CustomKeyboardHandler(owner),
|
||||||
JsDialogHandler = new JavaScriptDialogHandler(),
|
RequestHandler = requestHandler
|
||||||
KeyboardHandler = new KeyboardHandlerBrowser(owner),
|
|
||||||
LifeSpanHandler = new CustomLifeSpanHandler(),
|
|
||||||
RequestHandler = requestHandler,
|
|
||||||
ResourceRequestHandlerFactory = resourceRequestHandler.SelfFactory
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.browser.LoadingStateChanged += browser_LoadingStateChanged;
|
|
||||||
this.browser.FrameLoadStart += browser_FrameLoadStart;
|
|
||||||
this.browser.FrameLoadEnd += browser_FrameLoadEnd;
|
|
||||||
this.browser.LoadError += browser_LoadError;
|
|
||||||
|
|
||||||
this.browser.RegisterJsBridge("$TD", tdBridge);
|
|
||||||
this.browser.RegisterJsBridge("$TDU", updateBridge);
|
|
||||||
|
|
||||||
// ReSharper disable once PossiblyImpureMethodCallOnReadonlyVariable
|
// ReSharper disable once PossiblyImpureMethodCallOnReadonlyVariable
|
||||||
this.browser.BrowserSettings.BackgroundColor = (uint) TwitterUtils.BackgroundColor.ToArgb();
|
this.browser.BrowserSettings.BackgroundColor = (uint) BackgroundColor.ToArgb();
|
||||||
this.browser.Dock = DockStyle.None;
|
|
||||||
this.browser.Location = ControlExtensions.InvisibleLocation;
|
var extraContext = new TweetDeckExtraContext();
|
||||||
this.browser.SetupZoomEvents();
|
var resourceHandlerRegistry = new CefResourceHandlerRegistry();
|
||||||
|
var soundNotificationHandler = new SoundNotification(resourceHandlerRegistry);
|
||||||
|
|
||||||
|
this.browserComponent = new ComponentImpl(browser, owner, extraContext, resourceHandlerRegistry);
|
||||||
|
this.browserImpl = new TweetDeckBrowserImpl(browserComponent, tweetDeckInterface, extraContext, soundNotificationHandler, pluginManager, updateChecker);
|
||||||
|
|
||||||
|
if (Arguments.HasFlag(Arguments.ArgIgnoreGDPR)) {
|
||||||
|
browserComponent.PageLoadEnd += (sender, args) => {
|
||||||
|
if (TwitterUrls.IsTweetDeck(args.Url)) {
|
||||||
|
browserImpl.ScriptExecutor.RunScript("gen:gdpr", "TD.storage.Account.prototype.requiresConsent = function() { return false; }");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
owner.Controls.Add(browser);
|
owner.Controls.Add(browser);
|
||||||
plugins.Register(PluginEnvironment.Browser, new PluginDispatcher(browser, TwitterUrls.IsTweetDeck));
|
|
||||||
|
|
||||||
Config.MuteToggled += Config_MuteToggled;
|
|
||||||
Config.SoundNotificationChanged += Config_SoundNotificationInfoChanged;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// setup and management
|
private sealed class ComponentImpl : CefBrowserComponent {
|
||||||
|
private readonly FormBrowser owner;
|
||||||
|
private readonly TweetDeckExtraContext extraContext;
|
||||||
|
private readonly CefResourceHandlerRegistry registry;
|
||||||
|
|
||||||
|
public ComponentImpl(ChromiumWebBrowser browser, FormBrowser owner, TweetDeckExtraContext extraContext, CefResourceHandlerRegistry registry) : base(browser) {
|
||||||
|
this.owner = owner;
|
||||||
|
this.extraContext = extraContext;
|
||||||
|
this.registry = registry;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override ContextMenuBase SetupContextMenu(IContextMenuHandler handler) {
|
||||||
|
return new ContextMenuBrowser(owner, handler, extraContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override CefResourceHandlerFactory SetupResourceHandlerFactory(IResourceRequestHandler handler) {
|
||||||
|
return new CefResourceHandlerFactory(handler, registry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void PrepareSize(Size size) {
|
public void PrepareSize(Size size) {
|
||||||
if (!Ready) {
|
if (!Ready) {
|
||||||
@ -97,178 +100,33 @@ public void PrepareSize(Size size) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnBrowserReady() {
|
public void Dispose() {
|
||||||
if (!Ready) {
|
browserImpl.Dispose();
|
||||||
browser.Location = Point.Empty;
|
browser.Dispose();
|
||||||
browser.Dock = DockStyle.Fill;
|
|
||||||
Ready = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Focus() {
|
public void Focus() {
|
||||||
browser.Focus();
|
browser.Focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose() {
|
public void OpenDevTools() {
|
||||||
Config.MuteToggled -= Config_MuteToggled;
|
browser.OpenDevToolsCustom();
|
||||||
Config.SoundNotificationChanged -= Config_SoundNotificationInfoChanged;
|
|
||||||
|
|
||||||
browser.Dispose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// event handlers
|
public void ReloadToTweetDeck() {
|
||||||
|
browserImpl.ReloadToTweetDeck();
|
||||||
private void browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e) {
|
|
||||||
if (!e.IsLoading) {
|
|
||||||
foreach (string word in TwitterUtils.DictionaryWords) {
|
|
||||||
browser.AddWordToDictionary(word);
|
|
||||||
}
|
|
||||||
|
|
||||||
browser.BeginInvoke(new Action(OnBrowserReady));
|
|
||||||
browser.LoadingStateChanged -= browser_LoadingStateChanged;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void browser_FrameLoadStart(object sender, FrameLoadStartEventArgs e) {
|
public void SaveVideo(string url, string username) {
|
||||||
IFrame frame = e.Frame;
|
browserImpl.FileDownloadManager.SaveVideo(url, username);
|
||||||
|
|
||||||
if (frame.IsMain) {
|
|
||||||
string url = frame.Url;
|
|
||||||
|
|
||||||
if (TwitterUrls.IsTweetDeck(url) || (TwitterUrls.IsTwitter(url) && !TwitterUrls.IsTwitterLogin2Factor(url))) {
|
|
||||||
frame.ExecuteJavaScriptAsync(TwitterUtils.BackgroundColorOverride);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e) {
|
|
||||||
IFrame frame = e.Frame;
|
|
||||||
string url = frame.Url;
|
|
||||||
|
|
||||||
if (frame.IsMain) {
|
|
||||||
if (TwitterUrls.IsTweetDeck(url)) {
|
|
||||||
UpdateProperties();
|
|
||||||
CefScriptExecutor.RunBootstrap(frame, NamespaceTweetDeck);
|
|
||||||
|
|
||||||
TweetDeckBridge.ResetStaticProperties();
|
|
||||||
|
|
||||||
if (Arguments.HasFlag(Arguments.ArgIgnoreGDPR)) {
|
|
||||||
CefScriptExecutor.RunScript(frame, "TD.storage.Account.prototype.requiresConsent = function(){ return false; }", "gen:gdpr");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Config.FirstRun) {
|
|
||||||
CefScriptExecutor.RunBootstrap(frame, "introduction");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (TwitterUrls.IsTwitter(url)) {
|
|
||||||
CefScriptExecutor.RunBootstrap(frame, "login");
|
|
||||||
}
|
|
||||||
|
|
||||||
CefScriptExecutor.RunBootstrap(frame, "update");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void browser_LoadError(object sender, LoadErrorEventArgs e) {
|
|
||||||
if (e.ErrorCode == CefErrorCode.Aborted) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!e.FailedUrl.StartsWith("td://resources/error/", StringComparison.Ordinal)) {
|
|
||||||
string errorName = Enum.GetName(typeof(CefErrorCode), e.ErrorCode);
|
|
||||||
string errorTitle = StringUtils.ConvertPascalCaseToScreamingSnakeCase(errorName ?? string.Empty);
|
|
||||||
browser.Load("td://resources/error/error.html#" + Uri.EscapeDataString(errorTitle));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Config_MuteToggled(object sender, EventArgs e) {
|
|
||||||
UpdateProperties();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Config_SoundNotificationInfoChanged(object sender, EventArgs e) {
|
|
||||||
const string soundUrl = "https://ton.twimg.com/tduck/updatesnd";
|
|
||||||
|
|
||||||
bool hasCustomSound = Config.IsCustomSoundNotificationSet;
|
|
||||||
string newNotificationPath = Config.NotificationSoundPath;
|
|
||||||
|
|
||||||
if (prevSoundNotificationPath != newNotificationPath) {
|
|
||||||
prevSoundNotificationPath = newNotificationPath;
|
|
||||||
|
|
||||||
if (hasCustomSound) {
|
|
||||||
resourceHandlers.Register(soundUrl, SoundNotification.CreateFileHandler(newNotificationPath));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
resourceHandlers.Unregister(soundUrl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
browser.ExecuteJsAsync("TDGF_setSoundNotificationData", hasCustomSound, Config.NotificationSoundVolume);
|
|
||||||
}
|
|
||||||
|
|
||||||
// external handling
|
|
||||||
|
|
||||||
public void HideVideoOverlay(bool focus) {
|
public void HideVideoOverlay(bool focus) {
|
||||||
if (focus) {
|
if (focus) {
|
||||||
browser.GetBrowser().GetHost().SendFocusEvent(true);
|
browser.GetBrowser().GetHost().SendFocusEvent(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
browser.ExecuteJsAsync("$('#td-video-player-overlay').remove()");
|
browserImpl.ScriptExecutor.RunScript("gen:hidevideo", "$('#td-video-player-overlay').remove()");
|
||||||
}
|
|
||||||
|
|
||||||
// javascript calls
|
|
||||||
|
|
||||||
public void ReloadToTweetDeck() {
|
|
||||||
browser.ExecuteJsAsync($"if(window.TDGF_reload)window.TDGF_reload();else window.location.href='{TwitterUrls.TweetDeck}'");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnModulesLoaded(string moduleNamespace) {
|
|
||||||
if (moduleNamespace == NamespaceTweetDeck) {
|
|
||||||
ReinjectCustomCSS(Config.CustomBrowserCSS);
|
|
||||||
Config_SoundNotificationInfoChanged(null, EventArgs.Empty);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void UpdateProperties() {
|
|
||||||
browser.ExecuteJsAsync(PropertyBridge.GenerateScript(PropertyBridge.Environment.Browser));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ReinjectCustomCSS(string css) {
|
|
||||||
browser.ExecuteJsAsync("TDGF_reinjectCustomCSS", css?.Replace(Environment.NewLine, " ") ?? string.Empty);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnMouseClickExtra(IntPtr param) {
|
|
||||||
browser.ExecuteJsAsync("TDGF_onMouseClickExtra", (param.ToInt32() >> 16) & 0xFFFF);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ShowTweetDetail(string columnId, string chirpId, string fallbackUrl) {
|
|
||||||
browser.ExecuteJsAsync("TDGF_showTweetDetail", columnId, chirpId, fallbackUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AddSearchColumn(string query) {
|
|
||||||
browser.ExecuteJsAsync("TDGF_performSearch", query);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void TriggerTweetScreenshot(string columnId, string chirpId) {
|
|
||||||
browser.ExecuteJsAsync("TDGF_triggerScreenshot", columnId, chirpId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ReloadColumns() {
|
|
||||||
browser.ExecuteJsAsync("TDGF_reloadColumns()");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void PlaySoundNotification() {
|
|
||||||
browser.ExecuteJsAsync("TDGF_playSoundNotification()");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ApplyROT13() {
|
|
||||||
browser.ExecuteJsAsync("TDGF_applyROT13()");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ShowUpdateNotification(string versionTag, string releaseNotes) {
|
|
||||||
browser.ExecuteJsAsync("TDUF_displayNotification", versionTag, Convert.ToBase64String(Encoding.GetEncoding("iso-8859-1").GetBytes(releaseNotes)));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OpenDevTools() {
|
|
||||||
browser.OpenDevToolsCustom();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
81
Browser/TweetDeckInterfaceImpl.cs
Normal file
81
Browser/TweetDeckInterfaceImpl.cs
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using CefSharp;
|
||||||
|
using TweetDuck.Controls;
|
||||||
|
using TweetDuck.Dialogs;
|
||||||
|
using TweetDuck.Management;
|
||||||
|
using TweetDuck.Utils;
|
||||||
|
using TweetLib.Core.Features.Notifications;
|
||||||
|
using TweetLib.Core.Features.TweetDeck;
|
||||||
|
|
||||||
|
namespace TweetDuck.Browser {
|
||||||
|
sealed class TweetDeckInterfaceImpl : ITweetDeckInterface {
|
||||||
|
private readonly FormBrowser form;
|
||||||
|
|
||||||
|
public TweetDeckInterfaceImpl(FormBrowser form) {
|
||||||
|
this.form = form;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Alert(string type, string contents) {
|
||||||
|
MessageBoxIcon icon = type switch {
|
||||||
|
"error" => MessageBoxIcon.Error,
|
||||||
|
"warning" => MessageBoxIcon.Warning,
|
||||||
|
"info" => MessageBoxIcon.Information,
|
||||||
|
_ => MessageBoxIcon.None
|
||||||
|
};
|
||||||
|
|
||||||
|
FormMessage.Show("TweetDuck Browser Message", contents, icon, FormMessage.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DisplayTooltip(string text) {
|
||||||
|
form.InvokeAsyncSafe(() => form.DisplayTooltip(text));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void FixClipboard() {
|
||||||
|
form.InvokeAsyncSafe(ClipboardManager.StripHtmlStyles);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetIdleSeconds() {
|
||||||
|
return NativeMethods.GetIdleSeconds();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnIntroductionClosed(bool showGuide) {
|
||||||
|
form.InvokeAsyncSafe(() => form.OnIntroductionClosed(showGuide));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnSoundNotification() {
|
||||||
|
form.InvokeAsyncSafe(form.OnTweetNotification);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OpenContextMenu() {
|
||||||
|
form.InvokeAsyncSafe(form.OpenContextMenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OpenProfileImport() {
|
||||||
|
form.InvokeAsyncSafe(form.OpenProfileImport);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PlayVideo(string videoUrl, string tweetUrl, string username, object callShowOverlay) {
|
||||||
|
form.InvokeAsyncSafe(() => form.PlayVideo(videoUrl, tweetUrl, username, (IJavascriptCallback) callShowOverlay));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ScreenshotTweet(string html, int width) {
|
||||||
|
form.InvokeAsyncSafe(() => form.OnTweetScreenshotReady(html, width));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ShowDesktopNotification(DesktopNotification notification) {
|
||||||
|
form.InvokeAsyncSafe(() => {
|
||||||
|
form.OnTweetNotification();
|
||||||
|
form.ShowDesktopNotification(notification);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StopVideo() {
|
||||||
|
form.InvokeAsyncSafe(form.StopVideo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task ExecuteCallback(object callback, params object[] parameters) {
|
||||||
|
return ((IJavascriptCallback) callback).ExecuteAsync(parameters);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using TweetDuck.Browser.Data;
|
using TweetDuck.Dialogs;
|
||||||
using TweetLib.Core.Features.Plugins.Config;
|
using TweetLib.Core.Features.Plugins.Config;
|
||||||
using TweetLib.Core.Systems.Configuration;
|
using TweetLib.Core.Systems.Configuration;
|
||||||
using TweetLib.Utils.Serialization.Converters;
|
using TweetLib.Utils.Serialization.Converters;
|
||||||
@ -8,6 +8,14 @@
|
|||||||
|
|
||||||
namespace TweetDuck.Configuration {
|
namespace TweetDuck.Configuration {
|
||||||
sealed class ConfigManager : IConfigManager {
|
sealed class ConfigManager : IConfigManager {
|
||||||
|
internal sealed class Paths {
|
||||||
|
public string UserConfig { get; set; }
|
||||||
|
public string SystemConfig { get; set; }
|
||||||
|
public string PluginConfig { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public Paths FilePaths { get; }
|
||||||
|
|
||||||
public UserConfig User { get; }
|
public UserConfig User { get; }
|
||||||
public SystemConfig System { get; }
|
public SystemConfig System { get; }
|
||||||
public PluginConfig Plugins { get; }
|
public PluginConfig Plugins { get; }
|
||||||
@ -20,15 +28,17 @@ sealed class ConfigManager : IConfigManager {
|
|||||||
|
|
||||||
private readonly IConfigInstance<BaseConfig>[] infoList;
|
private readonly IConfigInstance<BaseConfig>[] infoList;
|
||||||
|
|
||||||
public ConfigManager() {
|
public ConfigManager(UserConfig userConfig, Paths paths) {
|
||||||
User = new UserConfig(this);
|
FilePaths = paths;
|
||||||
System = new SystemConfig(this);
|
|
||||||
Plugins = new PluginConfig(this);
|
User = userConfig;
|
||||||
|
System = new SystemConfig();
|
||||||
|
Plugins = new PluginConfig();
|
||||||
|
|
||||||
infoList = new IConfigInstance<BaseConfig>[] {
|
infoList = new IConfigInstance<BaseConfig>[] {
|
||||||
infoUser = new FileConfigInstance<UserConfig>(Program.UserConfigFilePath, User, "program options"),
|
infoUser = new FileConfigInstance<UserConfig>(paths.UserConfig, User, "program options"),
|
||||||
infoSystem = new FileConfigInstance<SystemConfig>(Program.SystemConfigFilePath, System, "system options"),
|
infoSystem = new FileConfigInstance<SystemConfig>(paths.SystemConfig, System, "system options"),
|
||||||
infoPlugins = new PluginConfigInstance<PluginConfig>(Program.PluginConfigFilePath, Plugins)
|
infoPlugins = new PluginConfigInstance<PluginConfig>(paths.PluginConfig, Plugins)
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO refactor further
|
// TODO refactor further
|
||||||
@ -70,7 +80,15 @@ public void ReloadAll() {
|
|||||||
infoPlugins.Reload();
|
infoPlugins.Reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
void IConfigManager.TriggerProgramRestartRequested() {
|
public void Save(BaseConfig instance) {
|
||||||
|
((IConfigManager) this).GetInstanceInfo(instance).Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reset(BaseConfig instance) {
|
||||||
|
((IConfigManager) this).GetInstanceInfo(instance).Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void TriggerProgramRestartRequested() {
|
||||||
ProgramRestartRequested?.Invoke(this, EventArgs.Empty);
|
ProgramRestartRequested?.Invoke(this, EventArgs.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,4 +97,14 @@ IConfigInstance<BaseConfig> IConfigManager.GetInstanceInfo(BaseConfig instance)
|
|||||||
return Array.Find(infoList, info => info.Instance.GetType() == instanceType); // TODO handle null
|
return Array.Find(infoList, info => info.Instance.GetType() == instanceType); // TODO handle null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static class ConfigManagerExtensions {
|
||||||
|
public static void Save(this BaseConfig instance) {
|
||||||
|
Program.Config.Save(instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Reset(this BaseConfig instance) {
|
||||||
|
Program.Config.Reset(instance);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,10 +22,8 @@ sealed class PluginConfig : BaseConfig, IPluginConfig {
|
|||||||
|
|
||||||
// END OF CONFIG
|
// END OF CONFIG
|
||||||
|
|
||||||
public PluginConfig(IConfigManager configManager) : base(configManager) {}
|
protected override BaseConfig ConstructWithDefaults() {
|
||||||
|
return new PluginConfig();
|
||||||
protected override BaseConfig ConstructWithDefaults(IConfigManager configManager) {
|
|
||||||
return new PluginConfig(configManager);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// INTERFACE IMPLEMENTATION
|
// INTERFACE IMPLEMENTATION
|
||||||
@ -40,7 +38,7 @@ void IPluginConfig.Reset(IEnumerable<string> newDisabledPlugins) {
|
|||||||
public void SetEnabled(Plugin plugin, bool enabled) {
|
public void SetEnabled(Plugin plugin, bool enabled) {
|
||||||
if ((enabled && disabled.Remove(plugin.Identifier)) || (!enabled && disabled.Add(plugin.Identifier))) {
|
if ((enabled && disabled.Remove(plugin.Identifier)) || (!enabled && disabled.Add(plugin.Identifier))) {
|
||||||
PluginChangedState?.Invoke(this, new PluginChangedStateEventArgs(plugin, enabled));
|
PluginChangedState?.Invoke(this, new PluginChangedStateEventArgs(plugin, enabled));
|
||||||
Save();
|
this.Save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,8 +2,6 @@
|
|||||||
|
|
||||||
namespace TweetDuck.Configuration {
|
namespace TweetDuck.Configuration {
|
||||||
sealed class SystemConfig : BaseConfig {
|
sealed class SystemConfig : BaseConfig {
|
||||||
// CONFIGURATION DATA
|
|
||||||
|
|
||||||
private bool _hardwareAcceleration = true;
|
private bool _hardwareAcceleration = true;
|
||||||
|
|
||||||
public bool ClearCacheAutomatically { get; set; } = true;
|
public bool ClearCacheAutomatically { get; set; } = true;
|
||||||
@ -13,15 +11,13 @@ sealed class SystemConfig : BaseConfig {
|
|||||||
|
|
||||||
public bool HardwareAcceleration {
|
public bool HardwareAcceleration {
|
||||||
get => _hardwareAcceleration;
|
get => _hardwareAcceleration;
|
||||||
set => UpdatePropertyWithRestartRequest(ref _hardwareAcceleration, value);
|
set => UpdatePropertyWithCallback(ref _hardwareAcceleration, value, Program.Config.TriggerProgramRestartRequested);
|
||||||
}
|
}
|
||||||
|
|
||||||
// END OF CONFIG
|
// END OF CONFIG
|
||||||
|
|
||||||
public SystemConfig(IConfigManager configManager) : base(configManager) {}
|
protected override BaseConfig ConstructWithDefaults() {
|
||||||
|
return new SystemConfig();
|
||||||
protected override BaseConfig ConstructWithDefaults(IConfigManager configManager) {
|
|
||||||
return new SystemConfig(configManager);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,17 +2,16 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using TweetDuck.Browser;
|
using TweetDuck.Browser;
|
||||||
using TweetDuck.Browser.Data;
|
|
||||||
using TweetDuck.Controls;
|
using TweetDuck.Controls;
|
||||||
|
using TweetDuck.Dialogs;
|
||||||
|
using TweetLib.Core.Application;
|
||||||
using TweetLib.Core.Features.Notifications;
|
using TweetLib.Core.Features.Notifications;
|
||||||
using TweetLib.Core.Features.Twitter;
|
using TweetLib.Core.Features.Twitter;
|
||||||
using TweetLib.Core.Systems.Configuration;
|
using TweetLib.Core.Systems.Configuration;
|
||||||
|
|
||||||
namespace TweetDuck.Configuration {
|
namespace TweetDuck.Configuration {
|
||||||
sealed class UserConfig : BaseConfig {
|
sealed class UserConfig : BaseConfig, IAppUserConfiguration {
|
||||||
// CONFIGURATION DATA
|
public bool FirstRun { get; set; } = true;
|
||||||
|
|
||||||
public bool FirstRun { get; set; } = true;
|
|
||||||
|
|
||||||
[SuppressMessage("ReSharper", "UnusedMember.Global")]
|
[SuppressMessage("ReSharper", "UnusedMember.Global")]
|
||||||
public bool AllowDataCollection { get; set; } = false;
|
public bool AllowDataCollection { get; set; } = false;
|
||||||
@ -122,32 +121,32 @@ public TrayIcon.Behavior TrayBehavior {
|
|||||||
|
|
||||||
public bool EnableSmoothScrolling {
|
public bool EnableSmoothScrolling {
|
||||||
get => _enableSmoothScrolling;
|
get => _enableSmoothScrolling;
|
||||||
set => UpdatePropertyWithRestartRequest(ref _enableSmoothScrolling, value);
|
set => UpdatePropertyWithCallback(ref _enableSmoothScrolling, value, Program.Config.TriggerProgramRestartRequested);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool EnableTouchAdjustment {
|
public bool EnableTouchAdjustment {
|
||||||
get => _enableTouchAdjustment;
|
get => _enableTouchAdjustment;
|
||||||
set => UpdatePropertyWithRestartRequest(ref _enableTouchAdjustment, value);
|
set => UpdatePropertyWithCallback(ref _enableTouchAdjustment, value, Program.Config.TriggerProgramRestartRequested);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool EnableColorProfileDetection {
|
public bool EnableColorProfileDetection {
|
||||||
get => _enableColorProfileDetection;
|
get => _enableColorProfileDetection;
|
||||||
set => UpdatePropertyWithRestartRequest(ref _enableColorProfileDetection, value);
|
set => UpdatePropertyWithCallback(ref _enableColorProfileDetection, value, Program.Config.TriggerProgramRestartRequested);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool UseSystemProxyForAllConnections {
|
public bool UseSystemProxyForAllConnections {
|
||||||
get => _useSystemProxyForAllConnections;
|
get => _useSystemProxyForAllConnections;
|
||||||
set => UpdatePropertyWithRestartRequest(ref _useSystemProxyForAllConnections, value);
|
set => UpdatePropertyWithCallback(ref _useSystemProxyForAllConnections, value, Program.Config.TriggerProgramRestartRequested);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string CustomCefArgs {
|
public string CustomCefArgs {
|
||||||
get => _customCefArgs;
|
get => _customCefArgs;
|
||||||
set => UpdatePropertyWithRestartRequest(ref _customCefArgs, value);
|
set => UpdatePropertyWithCallback(ref _customCefArgs, value, Program.Config.TriggerProgramRestartRequested);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string SpellCheckLanguage {
|
public string SpellCheckLanguage {
|
||||||
get => _spellCheckLanguage;
|
get => _spellCheckLanguage;
|
||||||
set => UpdatePropertyWithRestartRequest(ref _spellCheckLanguage, value);
|
set => UpdatePropertyWithCallback(ref _spellCheckLanguage, value, Program.Config.TriggerProgramRestartRequested);
|
||||||
}
|
}
|
||||||
|
|
||||||
// EVENTS
|
// EVENTS
|
||||||
@ -156,13 +155,16 @@ public string SpellCheckLanguage {
|
|||||||
public event EventHandler ZoomLevelChanged;
|
public event EventHandler ZoomLevelChanged;
|
||||||
public event EventHandler TrayBehaviorChanged;
|
public event EventHandler TrayBehaviorChanged;
|
||||||
public event EventHandler SoundNotificationChanged;
|
public event EventHandler SoundNotificationChanged;
|
||||||
|
public event EventHandler OptionsDialogClosed;
|
||||||
|
|
||||||
|
public void TriggerOptionsDialogClosed() {
|
||||||
|
OptionsDialogClosed?.Invoke(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
// END OF CONFIG
|
// END OF CONFIG
|
||||||
|
|
||||||
public UserConfig(IConfigManager configManager) : base(configManager) {}
|
protected override BaseConfig ConstructWithDefaults() {
|
||||||
|
return new UserConfig();
|
||||||
protected override BaseConfig ConstructWithDefaults(IConfigManager configManager) {
|
|
||||||
return new UserConfig(configManager);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,12 @@ public static void InvokeSafe(this Control control, Action func) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void InvokeAsyncSafe(this Control control, Action func) {
|
public static void InvokeAsyncSafe(this Control control, Action func) {
|
||||||
control.BeginInvoke(func);
|
if (control.InvokeRequired) {
|
||||||
|
control.BeginInvoke(func);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
func();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static float GetDPIScale(this Control control) {
|
public static float GetDPIScale(this Control control) {
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
using System.ComponentModel;
|
using System;
|
||||||
|
using System.ComponentModel;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using TweetDuck.Management;
|
using TweetDuck.Management;
|
||||||
using TweetDuck.Utils;
|
using TweetLib.Core;
|
||||||
|
|
||||||
namespace TweetDuck.Dialogs {
|
namespace TweetDuck.Dialogs {
|
||||||
sealed partial class FormAbout : Form, FormManager.IAppDialog {
|
sealed partial class FormAbout : Form, FormManager.IAppDialog {
|
||||||
@ -21,13 +22,15 @@ public FormAbout() {
|
|||||||
labelTips.Links.Add(new LinkLabel.Link(0, labelTips.Text.Length, TipsLink));
|
labelTips.Links.Add(new LinkLabel.Link(0, labelTips.Text.Length, TipsLink));
|
||||||
labelIssues.Links.Add(new LinkLabel.Link(0, labelIssues.Text.Length, IssuesLink));
|
labelIssues.Links.Add(new LinkLabel.Link(0, labelIssues.Text.Length, IssuesLink));
|
||||||
|
|
||||||
MemoryStream logoStream = new MemoryStream(Properties.Resources.avatar);
|
try {
|
||||||
pictureLogo.Image = Image.FromStream(logoStream);
|
pictureLogo.Image = Image.FromFile(Path.Combine(App.ResourcesPath, "images/logo.png"));
|
||||||
Disposed += (sender, args) => logoStream.Dispose();
|
} catch (Exception) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnLinkClicked(object sender, LinkLabelLinkClickedEventArgs e) {
|
private void OnLinkClicked(object sender, LinkLabelLinkClickedEventArgs e) {
|
||||||
BrowserUtils.OpenExternalBrowser(e.Link.LinkData as string);
|
App.SystemHandler.OpenBrowser(e.Link.LinkData as string);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void FormAbout_HelpRequested(object sender, HelpEventArgs hlpevent) {
|
private void FormAbout_HelpRequested(object sender, HelpEventArgs hlpevent) {
|
||||||
|
@ -1,20 +1,18 @@
|
|||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using CefSharp;
|
|
||||||
using CefSharp.WinForms;
|
using CefSharp.WinForms;
|
||||||
using TweetDuck.Browser;
|
using TweetDuck.Browser;
|
||||||
using TweetDuck.Browser.Data;
|
using TweetDuck.Browser.Adapters;
|
||||||
using TweetDuck.Browser.Handling;
|
using TweetDuck.Browser.Handling;
|
||||||
using TweetDuck.Browser.Handling.General;
|
|
||||||
using TweetDuck.Controls;
|
using TweetDuck.Controls;
|
||||||
using TweetDuck.Management;
|
using TweetDuck.Management;
|
||||||
using TweetDuck.Utils;
|
using TweetDuck.Utils;
|
||||||
|
using TweetLib.Browser.Interfaces;
|
||||||
|
using TweetLib.Core.Features;
|
||||||
|
|
||||||
namespace TweetDuck.Dialogs {
|
namespace TweetDuck.Dialogs {
|
||||||
sealed partial class FormGuide : Form, FormManager.IAppDialog {
|
sealed partial class FormGuide : Form, FormManager.IAppDialog {
|
||||||
private const string GuideUrl = @"td://guide/index.html";
|
private const string GuideUrl = "td://guide/index.html";
|
||||||
|
|
||||||
private static readonly ResourceLink DummyPage = new ResourceLink("http://td/dummy", ResourceHandlers.ForString(string.Empty));
|
|
||||||
|
|
||||||
public static void Show(string hash = null) {
|
public static void Show(string hash = null) {
|
||||||
string url = GuideUrl + (string.IsNullOrEmpty(hash) ? string.Empty : "#" + hash);
|
string url = GuideUrl + (string.IsNullOrEmpty(hash) ? string.Empty : "#" + hash);
|
||||||
@ -37,36 +35,43 @@ public static void Show(string hash = null) {
|
|||||||
private readonly ChromiumWebBrowser browser;
|
private readonly ChromiumWebBrowser browser;
|
||||||
#pragma warning restore IDE0069 // Disposable fields should be disposed
|
#pragma warning restore IDE0069 // Disposable fields should be disposed
|
||||||
|
|
||||||
private string nextUrl;
|
private FormGuide(string url, Form owner) {
|
||||||
|
|
||||||
private FormGuide(string url, FormBrowser owner) {
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
Text = Program.BrandName + " Guide";
|
Text = Program.BrandName + " Guide";
|
||||||
Size = new Size(owner.Size.Width * 3 / 4, owner.Size.Height * 3 / 4);
|
Size = new Size(owner.Size.Width * 3 / 4, owner.Size.Height * 3 / 4);
|
||||||
VisibleChanged += (sender, args) => this.MoveToCenter(owner);
|
VisibleChanged += (sender, args) => this.MoveToCenter(owner);
|
||||||
|
|
||||||
var resourceRequestHandler = new ResourceRequestHandlerBase();
|
browser = new ChromiumWebBrowser(url) {
|
||||||
resourceRequestHandler.ResourceHandlers.Register(DummyPage);
|
KeyboardHandler = new CustomKeyboardHandler(null),
|
||||||
|
RequestHandler = new RequestHandlerBase(true)
|
||||||
this.browser = new ChromiumWebBrowser(url) {
|
|
||||||
MenuHandler = new ContextMenuGuide(),
|
|
||||||
JsDialogHandler = new JavaScriptDialogHandler(),
|
|
||||||
KeyboardHandler = new KeyboardHandlerBase(),
|
|
||||||
LifeSpanHandler = new CustomLifeSpanHandler(),
|
|
||||||
RequestHandler = new RequestHandlerBase(true),
|
|
||||||
ResourceRequestHandlerFactory = resourceRequestHandler.SelfFactory
|
|
||||||
};
|
};
|
||||||
|
|
||||||
browser.LoadingStateChanged += browser_LoadingStateChanged;
|
|
||||||
|
|
||||||
browser.BrowserSettings.BackgroundColor = (uint) BackColor.ToArgb();
|
browser.BrowserSettings.BackgroundColor = (uint) BackColor.ToArgb();
|
||||||
browser.Dock = DockStyle.None;
|
|
||||||
browser.Location = ControlExtensions.InvisibleLocation;
|
var browserComponent = new ComponentImpl(browser);
|
||||||
browser.SetupZoomEvents();
|
var browserImpl = new BaseBrowser(browserComponent);
|
||||||
|
|
||||||
|
BrowserUtils.SetupDockOnLoad(browserComponent, browser);
|
||||||
|
|
||||||
Controls.Add(browser);
|
Controls.Add(browser);
|
||||||
Disposed += (sender, args) => browser.Dispose();
|
|
||||||
|
Disposed += (sender, args) => {
|
||||||
|
browserImpl.Dispose();
|
||||||
|
browser.Dispose();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class ComponentImpl : CefBrowserComponent {
|
||||||
|
public ComponentImpl(ChromiumWebBrowser browser) : base(browser) {}
|
||||||
|
|
||||||
|
protected override ContextMenuBase SetupContextMenu(IContextMenuHandler handler) {
|
||||||
|
return new ContextMenuGuide(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override CefResourceHandlerFactory SetupResourceHandlerFactory(IResourceRequestHandler handler) {
|
||||||
|
return new CefResourceHandlerFactory(handler, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool disposing) {
|
protected override void Dispose(bool disposing) {
|
||||||
@ -78,27 +83,7 @@ protected override void Dispose(bool disposing) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void Reload(string url) {
|
private void Reload(string url) {
|
||||||
nextUrl = url;
|
browser.Load(url);
|
||||||
browser.LoadingStateChanged += browser_LoadingStateChanged;
|
|
||||||
browser.Dock = DockStyle.None;
|
|
||||||
browser.Location = ControlExtensions.InvisibleLocation;
|
|
||||||
browser.Load(DummyPage.Url);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e) {
|
|
||||||
if (!e.IsLoading) {
|
|
||||||
if (browser.Address == DummyPage.Url) {
|
|
||||||
browser.Load(nextUrl);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.InvokeAsyncSafe(() => {
|
|
||||||
browser.Location = Point.Empty;
|
|
||||||
browser.Dock = DockStyle.Fill;
|
|
||||||
});
|
|
||||||
|
|
||||||
browser.LoadingStateChanged -= browser_LoadingStateChanged;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -92,7 +92,7 @@ private void flowLayoutPlugins_Resize(object sender, EventArgs e) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void btnOpenFolder_Click(object sender, EventArgs e) {
|
private void btnOpenFolder_Click(object sender, EventArgs e) {
|
||||||
App.SystemHandler.OpenFileExplorer(pluginManager.PathCustomPlugins);
|
App.SystemHandler.OpenFileExplorer(pluginManager.CustomPluginFolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void btnReload_Click(object sender, EventArgs e) {
|
private void btnReload_Click(object sender, EventArgs e) {
|
||||||
|
@ -3,14 +3,14 @@
|
|||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using TweetDuck.Browser;
|
using TweetDuck.Browser;
|
||||||
using TweetDuck.Browser.Handling.General;
|
using TweetDuck.Browser.Handling;
|
||||||
using TweetDuck.Browser.Notification.Example;
|
|
||||||
using TweetDuck.Configuration;
|
using TweetDuck.Configuration;
|
||||||
using TweetDuck.Controls;
|
using TweetDuck.Controls;
|
||||||
using TweetDuck.Dialogs.Settings;
|
using TweetDuck.Dialogs.Settings;
|
||||||
using TweetDuck.Management;
|
using TweetDuck.Management;
|
||||||
using TweetDuck.Utils;
|
using TweetDuck.Utils;
|
||||||
using TweetLib.Core.Features.Plugins;
|
using TweetLib.Core.Features.Plugins;
|
||||||
|
using TweetLib.Core.Features.TweetDeck;
|
||||||
using TweetLib.Core.Systems.Updates;
|
using TweetLib.Core.Systems.Updates;
|
||||||
|
|
||||||
namespace TweetDuck.Dialogs {
|
namespace TweetDuck.Dialogs {
|
||||||
@ -25,7 +25,7 @@ sealed partial class FormSettings : Form, FormManager.IAppDialog {
|
|||||||
private readonly Dictionary<Type, SettingsTab> tabs = new Dictionary<Type, SettingsTab>(8);
|
private readonly Dictionary<Type, SettingsTab> tabs = new Dictionary<Type, SettingsTab>(8);
|
||||||
private SettingsTab currentTab;
|
private SettingsTab currentTab;
|
||||||
|
|
||||||
public FormSettings(FormBrowser browser, PluginManager plugins, UpdateHandler updates, Type startTab) {
|
public FormSettings(FormBrowser browser, PluginManager plugins, UpdateChecker updates, TweetDeckFunctions tweetDeckFunctions, Type startTab) {
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
Text = Program.BrandName + " Options";
|
Text = Program.BrandName + " Options";
|
||||||
@ -39,12 +39,12 @@ public FormSettings(FormBrowser browser, PluginManager plugins, UpdateHandler up
|
|||||||
|
|
||||||
PrepareLoad();
|
PrepareLoad();
|
||||||
|
|
||||||
AddButton("General", () => new TabSettingsGeneral(this.browser.ReloadColumns, updates));
|
AddButton("General", () => new TabSettingsGeneral(tweetDeckFunctions.ReloadColumns, updates));
|
||||||
AddButton("Notifications", () => new TabSettingsNotifications(new FormNotificationExample(this.browser, this.plugins)));
|
AddButton("Notifications", () => new TabSettingsNotifications(this.browser.CreateExampleNotification()));
|
||||||
AddButton("Sounds", () => new TabSettingsSounds(this.browser.PlaySoundNotification));
|
AddButton("Sounds", () => new TabSettingsSounds(tweetDeckFunctions.PlaySoundNotification));
|
||||||
AddButton("Tray", () => new TabSettingsTray());
|
AddButton("Tray", () => new TabSettingsTray());
|
||||||
AddButton("Feedback", () => new TabSettingsFeedback());
|
AddButton("Feedback", () => new TabSettingsFeedback());
|
||||||
AddButton("Advanced", () => new TabSettingsAdvanced(this.browser.ReinjectCustomCSS, this.browser.OpenDevTools));
|
AddButton("Advanced", () => new TabSettingsAdvanced(tweetDeckFunctions.ReinjectCustomCSS, this.browser.OpenDevTools));
|
||||||
|
|
||||||
SelectTab(tabs[startTab ?? typeof(TabSettingsGeneral)]);
|
SelectTab(tabs[startTab ?? typeof(TabSettingsGeneral)]);
|
||||||
}
|
}
|
||||||
@ -181,7 +181,7 @@ private void SelectTab(SettingsTab tab) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void control_MouseLeave(object sender, EventArgs e) {
|
private void control_MouseLeave(object sender, EventArgs e) {
|
||||||
if (sender is ComboBox cb && cb.DroppedDown) {
|
if (sender is ComboBox { DroppedDown: true } ) {
|
||||||
return; // prevents comboboxes from closing when MouseLeave event triggers during opening animation
|
return; // prevents comboboxes from closing when MouseLeave event triggers during opening animation
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using TweetDuck.Controls;
|
using TweetDuck.Controls;
|
||||||
using TweetDuck.Utils;
|
using TweetLib.Core;
|
||||||
using TweetLib.Core.Features.Chromium;
|
using TweetLib.Core.Features.Chromium;
|
||||||
|
|
||||||
namespace TweetDuck.Dialogs.Settings {
|
namespace TweetDuck.Dialogs.Settings {
|
||||||
@ -21,7 +21,7 @@ public DialogSettingsCefArgs(string args) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void btnHelp_Click(object sender, EventArgs e) {
|
private void btnHelp_Click(object sender, EventArgs e) {
|
||||||
BrowserUtils.OpenExternalBrowser("http://peter.sh/experiments/chromium-command-line-switches/");
|
App.SystemHandler.OpenBrowser("http://peter.sh/experiments/chromium-command-line-switches/");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void btnApply_Click(object sender, EventArgs e) {
|
private void btnApply_Click(object sender, EventArgs e) {
|
||||||
|
@ -149,7 +149,7 @@ private void btnContinue_Click(object sender, EventArgs e) {
|
|||||||
Program.Config.Plugins.Reset();
|
Program.Config.Plugins.Reset();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Directory.Delete(Program.PluginDataPath, true);
|
Directory.Delete(plugins.PluginDataFolder, true);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
App.ErrorHandler.HandleException("Profile Reset", "Could not delete plugin data.", true, ex);
|
App.ErrorHandler.HandleException("Profile Reset", "Could not delete plugin data.", true, ex);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using TweetDuck.Configuration;
|
using TweetDuck.Configuration;
|
||||||
|
using TweetLib.Core;
|
||||||
using TweetLib.Utils.Collections;
|
using TweetLib.Utils.Collections;
|
||||||
|
|
||||||
namespace TweetDuck.Dialogs.Settings {
|
namespace TweetDuck.Dialogs.Settings {
|
||||||
@ -13,7 +14,7 @@ public DialogSettingsRestart(CommandLineArgs currentArgs) {
|
|||||||
cbLogging.Checked = currentArgs.HasFlag(Arguments.ArgLogging);
|
cbLogging.Checked = currentArgs.HasFlag(Arguments.ArgLogging);
|
||||||
cbLogging.CheckedChanged += control_Change;
|
cbLogging.CheckedChanged += control_Change;
|
||||||
|
|
||||||
if (Program.IsPortable) {
|
if (App.IsPortable) {
|
||||||
tbDataFolder.Text = "Not available in portable version";
|
tbDataFolder.Text = "Not available in portable version";
|
||||||
tbDataFolder.Enabled = false;
|
tbDataFolder.Enabled = false;
|
||||||
}
|
}
|
||||||
|
@ -85,11 +85,11 @@ public override void OnClosing() {
|
|||||||
#region Application
|
#region Application
|
||||||
|
|
||||||
private void btnOpenAppFolder_Click(object sender, EventArgs e) {
|
private void btnOpenAppFolder_Click(object sender, EventArgs e) {
|
||||||
App.SystemHandler.OpenFileExplorer(Program.ProgramPath);
|
App.SystemHandler.OpenFileExplorer(App.ProgramPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void btnOpenDataFolder_Click(object sender, EventArgs e) {
|
private void btnOpenDataFolder_Click(object sender, EventArgs e) {
|
||||||
App.SystemHandler.OpenFileExplorer(Program.StoragePath);
|
App.SystemHandler.OpenFileExplorer(App.StoragePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void btnRestart_Click(object sender, EventArgs e) {
|
private void btnRestart_Click(object sender, EventArgs e) {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using TweetDuck.Utils;
|
using TweetLib.Core;
|
||||||
|
|
||||||
namespace TweetDuck.Dialogs.Settings {
|
namespace TweetDuck.Dialogs.Settings {
|
||||||
sealed partial class TabSettingsFeedback : FormSettings.BaseTab {
|
sealed partial class TabSettingsFeedback : FormSettings.BaseTab {
|
||||||
@ -14,7 +14,7 @@ public override void OnReady() {
|
|||||||
#region Feedback
|
#region Feedback
|
||||||
|
|
||||||
private void btnSendFeedback_Click(object sender, EventArgs e) {
|
private void btnSendFeedback_Click(object sender, EventArgs e) {
|
||||||
BrowserUtils.OpenExternalBrowser("https://github.com/chylex/TweetDuck/issues/new");
|
App.SystemHandler.OpenBrowser("https://github.com/chylex/TweetDuck/issues/new");
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using TweetDuck.Browser.Handling.General;
|
using TweetDuck.Browser.Handling;
|
||||||
using TweetDuck.Controls;
|
using TweetDuck.Controls;
|
||||||
using TweetDuck.Utils;
|
using TweetDuck.Utils;
|
||||||
using TweetLib.Core;
|
using TweetLib.Core;
|
||||||
@ -15,7 +15,7 @@ namespace TweetDuck.Dialogs.Settings {
|
|||||||
sealed partial class TabSettingsGeneral : FormSettings.BaseTab {
|
sealed partial class TabSettingsGeneral : FormSettings.BaseTab {
|
||||||
private readonly Action reloadColumns;
|
private readonly Action reloadColumns;
|
||||||
|
|
||||||
private readonly UpdateHandler updates;
|
private readonly UpdateChecker updates;
|
||||||
private int updateCheckEventId = -1;
|
private int updateCheckEventId = -1;
|
||||||
|
|
||||||
private readonly int browserListIndexDefault;
|
private readonly int browserListIndexDefault;
|
||||||
@ -27,7 +27,7 @@ sealed partial class TabSettingsGeneral : FormSettings.BaseTab {
|
|||||||
private readonly int searchEngineIndexDefault;
|
private readonly int searchEngineIndexDefault;
|
||||||
private readonly int searchEngineIndexCustom;
|
private readonly int searchEngineIndexCustom;
|
||||||
|
|
||||||
public TabSettingsGeneral(Action reloadColumns, UpdateHandler updates) {
|
public TabSettingsGeneral(Action reloadColumns, UpdateChecker updates) {
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
this.reloadColumns = reloadColumns;
|
this.reloadColumns = reloadColumns;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using TweetDuck.Browser.Notification.Example;
|
using TweetDuck.Browser.Notification;
|
||||||
using TweetDuck.Controls;
|
using TweetDuck.Controls;
|
||||||
using TweetLib.Core.Features.Notifications;
|
using TweetLib.Core.Features.Notifications;
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
using TweetLib.Utils.Serialization.Converters;
|
using TweetLib.Utils.Serialization.Converters;
|
||||||
using TweetLib.Utils.Static;
|
using TweetLib.Utils.Static;
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Data {
|
namespace TweetDuck.Dialogs {
|
||||||
sealed class WindowState {
|
sealed class WindowState {
|
||||||
private Rectangle rect;
|
private Rectangle rect;
|
||||||
private bool isMaximized;
|
private bool isMaximized;
|
@ -3,10 +3,11 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using TweetLib.Core;
|
||||||
|
|
||||||
namespace TweetDuck.Management {
|
namespace TweetDuck.Management {
|
||||||
static class BrowserCache {
|
static class BrowserCache {
|
||||||
public static string CacheFolder => Path.Combine(Program.StoragePath, "Cache");
|
public static string CacheFolder => Path.Combine(App.StoragePath, "Cache");
|
||||||
|
|
||||||
private static bool clearOnExit;
|
private static bool clearOnExit;
|
||||||
private static Timer autoClearTimer;
|
private static Timer autoClearTimer;
|
||||||
@ -22,7 +23,7 @@ private static long CalculateCacheSize() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void GetCacheSize(Action<Task<long>> callbackBytes) {
|
public static void GetCacheSize(Action<Task<long>> callbackBytes) {
|
||||||
Task<long> task = new Task<long>(CalculateCacheSize);
|
var task = new Task<long>(CalculateCacheSize);
|
||||||
task.ContinueWith(callbackBytes);
|
task.ContinueWith(callbackBytes);
|
||||||
task.Start();
|
task.Start();
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,21 @@
|
|||||||
using System.Linq;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
|
using TweetDuck.Browser;
|
||||||
|
using TweetDuck.Controls;
|
||||||
|
|
||||||
namespace TweetDuck.Management {
|
namespace TweetDuck.Management {
|
||||||
static class FormManager {
|
static class FormManager {
|
||||||
private static FormCollection OpenForms => System.Windows.Forms.Application.OpenForms;
|
private static FormCollection OpenForms => System.Windows.Forms.Application.OpenForms;
|
||||||
|
|
||||||
|
public static void RunOnUIThread(Action action) {
|
||||||
|
TryFind<FormBrowser>()?.InvokeSafe(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RunOnUIThreadAsync(Action action) {
|
||||||
|
TryFind<FormBrowser>()?.InvokeAsyncSafe(action);
|
||||||
|
}
|
||||||
|
|
||||||
public static T TryFind<T>() where T : Form {
|
public static T TryFind<T>() where T : Form {
|
||||||
return OpenForms.OfType<T>().FirstOrDefault();
|
return OpenForms.OfType<T>().FirstOrDefault();
|
||||||
}
|
}
|
||||||
|
@ -10,13 +10,13 @@
|
|||||||
|
|
||||||
namespace TweetDuck.Management {
|
namespace TweetDuck.Management {
|
||||||
sealed class ProfileManager {
|
sealed class ProfileManager {
|
||||||
private static readonly string CookiesPath = Path.Combine(Program.StoragePath, "Cookies");
|
private static readonly string CookiesPath = Path.Combine(App.StoragePath, "Cookies");
|
||||||
private static readonly string LocalPrefsPath = Path.Combine(Program.StoragePath, "LocalPrefs.json");
|
private static readonly string LocalPrefsPath = Path.Combine(App.StoragePath, "LocalPrefs.json");
|
||||||
|
|
||||||
private static readonly string TempCookiesPath = Path.Combine(Program.StoragePath, "CookiesTmp");
|
private static readonly string TempCookiesPath = Path.Combine(App.StoragePath, "CookiesTmp");
|
||||||
private static readonly string TempLocalPrefsPath = Path.Combine(Program.StoragePath, "LocalPrefsTmp.json");
|
private static readonly string TempLocalPrefsPath = Path.Combine(App.StoragePath, "LocalPrefsTmp.json");
|
||||||
|
|
||||||
private static readonly int SessionFileCount = 2;
|
private const int SessionFileCount = 2;
|
||||||
|
|
||||||
[Flags]
|
[Flags]
|
||||||
public enum Items {
|
public enum Items {
|
||||||
@ -41,15 +41,15 @@ public bool Export(Items items) {
|
|||||||
using CombinedFileStream stream = new CombinedFileStream(new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None));
|
using CombinedFileStream stream = new CombinedFileStream(new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None));
|
||||||
|
|
||||||
if (items.HasFlag(Items.UserConfig)) {
|
if (items.HasFlag(Items.UserConfig)) {
|
||||||
stream.WriteFile("config", Program.UserConfigFilePath);
|
stream.WriteFile("config", Program.Config.FilePaths.UserConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (items.HasFlag(Items.SystemConfig)) {
|
if (items.HasFlag(Items.SystemConfig)) {
|
||||||
stream.WriteFile("system", Program.SystemConfigFilePath);
|
stream.WriteFile("system", Program.Config.FilePaths.SystemConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (items.HasFlag(Items.PluginData)) {
|
if (items.HasFlag(Items.PluginData)) {
|
||||||
stream.WriteFile("plugin.config", Program.PluginConfigFilePath);
|
stream.WriteFile("plugin.config", Program.Config.FilePaths.PluginConfig);
|
||||||
|
|
||||||
foreach (Plugin plugin in plugins.Plugins) {
|
foreach (Plugin plugin in plugins.Plugins) {
|
||||||
foreach (PathInfo path in EnumerateFilesRelative(plugin.GetPluginFolder(PluginFolder.Data))) {
|
foreach (PathInfo path in EnumerateFilesRelative(plugin.GetPluginFolder(PluginFolder.Data))) {
|
||||||
@ -122,21 +122,21 @@ public bool Import(Items items) {
|
|||||||
switch (entry.KeyName) {
|
switch (entry.KeyName) {
|
||||||
case "config":
|
case "config":
|
||||||
if (items.HasFlag(Items.UserConfig)) {
|
if (items.HasFlag(Items.UserConfig)) {
|
||||||
entry.WriteToFile(Program.UserConfigFilePath);
|
entry.WriteToFile(Program.Config.FilePaths.UserConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "system":
|
case "system":
|
||||||
if (items.HasFlag(Items.SystemConfig)) {
|
if (items.HasFlag(Items.SystemConfig)) {
|
||||||
entry.WriteToFile(Program.SystemConfigFilePath);
|
entry.WriteToFile(Program.Config.FilePaths.SystemConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "plugin.config":
|
case "plugin.config":
|
||||||
if (items.HasFlag(Items.PluginData)) {
|
if (items.HasFlag(Items.PluginData)) {
|
||||||
entry.WriteToFile(Program.PluginConfigFilePath);
|
entry.WriteToFile(Program.Config.FilePaths.PluginConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@ -145,7 +145,7 @@ public bool Import(Items items) {
|
|||||||
if (items.HasFlag(Items.PluginData)) {
|
if (items.HasFlag(Items.PluginData)) {
|
||||||
string[] value = entry.KeyValue;
|
string[] value = entry.KeyValue;
|
||||||
|
|
||||||
entry.WriteToFile(Path.Combine(Program.PluginDataPath, value[0], value[1]), true);
|
entry.WriteToFile(Path.Combine(plugins.PluginDataFolder, value[0], value[1]), true);
|
||||||
|
|
||||||
if (!plugins.Plugins.Any(plugin => plugin.Identifier.Equals(value[0]))) {
|
if (!plugins.Plugins.Any(plugin => plugin.Identifier.Equals(value[0]))) {
|
||||||
missingPlugins.Add(value[0]);
|
missingPlugins.Add(value[0]);
|
||||||
|
@ -6,7 +6,6 @@
|
|||||||
using TweetDuck.Configuration;
|
using TweetDuck.Configuration;
|
||||||
using TweetDuck.Controls;
|
using TweetDuck.Controls;
|
||||||
using TweetDuck.Dialogs;
|
using TweetDuck.Dialogs;
|
||||||
using TweetDuck.Utils;
|
|
||||||
using TweetLib.Communication.Pipe;
|
using TweetLib.Communication.Pipe;
|
||||||
using TweetLib.Core;
|
using TweetLib.Core;
|
||||||
|
|
||||||
@ -14,7 +13,7 @@ namespace TweetDuck.Management {
|
|||||||
sealed class VideoPlayer : IDisposable {
|
sealed class VideoPlayer : IDisposable {
|
||||||
private static UserConfig Config => Program.Config.User;
|
private static UserConfig Config => Program.Config.User;
|
||||||
|
|
||||||
public bool Running => currentInstance != null && currentInstance.Running;
|
public bool Running => currentInstance is { Running: true };
|
||||||
|
|
||||||
public event EventHandler ProcessExited;
|
public event EventHandler ProcessExited;
|
||||||
|
|
||||||
@ -39,7 +38,7 @@ public void Launch(string videoUrl, string tweetUrl, string username) {
|
|||||||
pipe.DataIn += pipe_DataIn;
|
pipe.DataIn += pipe_DataIn;
|
||||||
|
|
||||||
ProcessStartInfo startInfo = new ProcessStartInfo {
|
ProcessStartInfo startInfo = new ProcessStartInfo {
|
||||||
FileName = Path.Combine(Program.ProgramPath, "TweetDuck.Video.exe"),
|
FileName = Path.Combine(App.ProgramPath, "TweetDuck.Video.exe"),
|
||||||
Arguments = $"{owner.Handle} {(int) Math.Floor(100F * owner.GetDPIScale())} {Config.VideoPlayerVolume} \"{videoUrl}\" \"{pipe.GenerateToken()}\"",
|
Arguments = $"{owner.Handle} {(int) Math.Floor(100F * owner.GetDPIScale())} {Config.VideoPlayerVolume} \"{videoUrl}\" \"{pipe.GenerateToken()}\"",
|
||||||
UseShellExecute = false,
|
UseShellExecute = false,
|
||||||
RedirectStandardOutput = true
|
RedirectStandardOutput = true
|
||||||
@ -83,7 +82,7 @@ private void pipe_DataIn(object sender, DuplexPipe.PipeReadEventArgs e) {
|
|||||||
|
|
||||||
case "download":
|
case "download":
|
||||||
if (currentInstance != null) {
|
if (currentInstance != null) {
|
||||||
TwitterUtils.DownloadVideo(currentInstance.VideoUrl, currentInstance.Username);
|
owner.SaveVideo(currentInstance.VideoUrl, currentInstance.Username);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@ -137,7 +136,7 @@ private void owner_FormClosing(object sender, FormClosingEventArgs e) {
|
|||||||
|
|
||||||
private void process_OutputDataReceived(object sender, DataReceivedEventArgs e) {
|
private void process_OutputDataReceived(object sender, DataReceivedEventArgs e) {
|
||||||
if (!string.IsNullOrEmpty(e.Data)) {
|
if (!string.IsNullOrEmpty(e.Data)) {
|
||||||
Program.Reporter.LogVerbose("[VideoPlayer] " + e.Data);
|
App.Logger.Debug("[VideoPlayer] " + e.Data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,14 +154,14 @@ private void process_Exited(object sender, EventArgs e) {
|
|||||||
switch (exitCode) {
|
switch (exitCode) {
|
||||||
case 3: // CODE_LAUNCH_FAIL
|
case 3: // CODE_LAUNCH_FAIL
|
||||||
if (FormMessage.Error("Video Playback Error", "Error launching video player, this may be caused by missing Windows Media Player. Do you want to open the video in your browser?", FormMessage.Yes, FormMessage.No)) {
|
if (FormMessage.Error("Video Playback Error", "Error launching video player, this may be caused by missing Windows Media Player. Do you want to open the video in your browser?", FormMessage.Yes, FormMessage.No)) {
|
||||||
BrowserUtils.OpenExternalBrowser(tweetUrl);
|
App.SystemHandler.OpenBrowser(tweetUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 4: // CODE_MEDIA_ERROR
|
case 4: // CODE_MEDIA_ERROR
|
||||||
if (FormMessage.Error("Video Playback Error", "The video could not be loaded, most likely due to unknown format. Do you want to open the video in your browser?", FormMessage.Yes, FormMessage.No)) {
|
if (FormMessage.Error("Video Playback Error", "The video could not be loaded, most likely due to unknown format. Do you want to open the video in your browser?", FormMessage.Yes, FormMessage.No)) {
|
||||||
BrowserUtils.OpenExternalBrowser(tweetUrl);
|
App.SystemHandler.OpenBrowser(tweetUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using TweetDuck.Controls;
|
using TweetDuck.Controls;
|
||||||
using TweetDuck.Utils;
|
using TweetDuck.Utils;
|
||||||
|
using TweetLib.Core;
|
||||||
using TweetLib.Core.Features.Plugins;
|
using TweetLib.Core.Features.Plugins;
|
||||||
using TweetLib.Core.Features.Plugins.Enums;
|
using TweetLib.Core.Features.Plugins.Enums;
|
||||||
|
|
||||||
@ -80,7 +81,7 @@ private void panelDescription_Resize(object sender, EventArgs e) {
|
|||||||
|
|
||||||
private void labelWebsite_Click(object sender, EventArgs e) {
|
private void labelWebsite_Click(object sender, EventArgs e) {
|
||||||
if (labelWebsite.Text.Length > 0) {
|
if (labelWebsite.Text.Length > 0) {
|
||||||
BrowserUtils.OpenExternalBrowser(labelWebsite.Text);
|
App.SystemHandler.OpenBrowser(labelWebsite.Text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,36 +0,0 @@
|
|||||||
using System;
|
|
||||||
using CefSharp;
|
|
||||||
using TweetDuck.Browser.Adapters;
|
|
||||||
using TweetDuck.Utils;
|
|
||||||
using TweetLib.Core.Browser;
|
|
||||||
using TweetLib.Core.Features.Plugins;
|
|
||||||
using TweetLib.Core.Features.Plugins.Events;
|
|
||||||
|
|
||||||
namespace TweetDuck.Plugins {
|
|
||||||
sealed class PluginDispatcher : IPluginDispatcher {
|
|
||||||
public event EventHandler<PluginDispatchEventArgs> Ready;
|
|
||||||
|
|
||||||
private readonly IWebBrowser browser;
|
|
||||||
private readonly IScriptExecutor executor;
|
|
||||||
private readonly Func<string, bool> executeOnUrl;
|
|
||||||
|
|
||||||
public PluginDispatcher(IWebBrowser browser, Func<string, bool> executeOnUrl) {
|
|
||||||
this.executeOnUrl = executeOnUrl;
|
|
||||||
this.browser = browser;
|
|
||||||
this.browser.FrameLoadEnd += browser_FrameLoadEnd;
|
|
||||||
this.executor = new CefScriptExecutor(browser);
|
|
||||||
}
|
|
||||||
|
|
||||||
void IPluginDispatcher.AttachBridge(string name, object bridge) {
|
|
||||||
browser.RegisterJsBridge(name, bridge);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e) {
|
|
||||||
IFrame frame = e.Frame;
|
|
||||||
|
|
||||||
if (frame.IsMain && executeOnUrl(frame.Url)) {
|
|
||||||
Ready?.Invoke(this, new PluginDispatchEventArgs(executor));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
using CefSharp;
|
|
||||||
using TweetDuck.Resources;
|
|
||||||
using TweetLib.Core.Features.Plugins;
|
|
||||||
|
|
||||||
namespace TweetDuck.Plugins {
|
|
||||||
sealed class PluginSchemeFactory : ISchemeHandlerFactory {
|
|
||||||
public const string Name = PluginSchemeHandler<IResourceHandler>.Name;
|
|
||||||
|
|
||||||
private readonly PluginSchemeHandler<IResourceHandler> handler;
|
|
||||||
|
|
||||||
public PluginSchemeFactory(ResourceProvider resourceProvider) {
|
|
||||||
handler = new PluginSchemeHandler<IResourceHandler>(resourceProvider);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void Setup(PluginManager plugins) {
|
|
||||||
handler.Setup(plugins);
|
|
||||||
}
|
|
||||||
|
|
||||||
public IResourceHandler Create(IBrowser browser, IFrame frame, string schemeName, IRequest request) {
|
|
||||||
return handler.Process(request.Url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
149
Program.cs
149
Program.cs
@ -1,21 +1,24 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using CefSharp;
|
using CefSharp;
|
||||||
using CefSharp.WinForms;
|
using CefSharp.WinForms;
|
||||||
using TweetDuck.Application;
|
using TweetDuck.Application;
|
||||||
using TweetDuck.Browser;
|
using TweetDuck.Browser;
|
||||||
|
using TweetDuck.Browser.Adapters;
|
||||||
using TweetDuck.Browser.Handling;
|
using TweetDuck.Browser.Handling;
|
||||||
using TweetDuck.Browser.Handling.General;
|
|
||||||
using TweetDuck.Configuration;
|
using TweetDuck.Configuration;
|
||||||
using TweetDuck.Dialogs;
|
using TweetDuck.Dialogs;
|
||||||
using TweetDuck.Management;
|
using TweetDuck.Management;
|
||||||
using TweetDuck.Plugins;
|
using TweetDuck.Updates;
|
||||||
using TweetDuck.Resources;
|
|
||||||
using TweetDuck.Utils;
|
using TweetDuck.Utils;
|
||||||
using TweetLib.Core;
|
using TweetLib.Core;
|
||||||
|
using TweetLib.Core.Application;
|
||||||
|
using TweetLib.Core.Features;
|
||||||
using TweetLib.Core.Features.Chromium;
|
using TweetLib.Core.Features.Chromium;
|
||||||
|
using TweetLib.Core.Features.Plugins;
|
||||||
|
using TweetLib.Core.Features.TweetDeck;
|
||||||
|
using TweetLib.Core.Resources;
|
||||||
using TweetLib.Utils.Collections;
|
using TweetLib.Utils.Collections;
|
||||||
using TweetLib.Utils.Static;
|
using TweetLib.Utils.Static;
|
||||||
using Win = System.Windows.Forms;
|
using Win = System.Windows.Forms;
|
||||||
@ -27,47 +30,22 @@ static class Program {
|
|||||||
|
|
||||||
public const string Website = "https://tweetduck.chylex.com";
|
public const string Website = "https://tweetduck.chylex.com";
|
||||||
|
|
||||||
public static readonly string ProgramPath = AppDomain.CurrentDomain.BaseDirectory;
|
private const string PluginDataFolder = "TD_Plugins";
|
||||||
public static readonly string ExecutablePath = Win.Application.ExecutablePath;
|
private const string InstallerFolder = "TD_Updates";
|
||||||
|
private const string CefDataFolder = "TD_Chromium";
|
||||||
|
|
||||||
public static readonly bool IsPortable = File.Exists(Path.Combine(ProgramPath, "makeportable"));
|
private const string ProgramLogFile = "TD_Log.txt";
|
||||||
|
private const string ConsoleLogFile = "TD_Console.txt";
|
||||||
|
|
||||||
public static readonly string ResourcesPath = Path.Combine(ProgramPath, "resources");
|
public static string ExecutablePath => Win.Application.ExecutablePath;
|
||||||
public static readonly string PluginPath = Path.Combine(ProgramPath, "plugins");
|
|
||||||
public static readonly string GuidePath = Path.Combine(ProgramPath, "guide");
|
|
||||||
|
|
||||||
public static readonly string StoragePath = IsPortable ? Path.Combine(ProgramPath, "portable", "storage") : GetDataStoragePath();
|
|
||||||
|
|
||||||
public static readonly string PluginDataPath = Path.Combine(StoragePath, "TD_Plugins");
|
|
||||||
public static readonly string InstallerPath = Path.Combine(StoragePath, "TD_Updates");
|
|
||||||
private static readonly string CefDataPath = Path.Combine(StoragePath, "TD_Chromium");
|
|
||||||
|
|
||||||
public static string UserConfigFilePath => Path.Combine(StoragePath, "TD_UserConfig.cfg");
|
|
||||||
public static string SystemConfigFilePath => Path.Combine(StoragePath, "TD_SystemConfig.cfg");
|
|
||||||
public static string PluginConfigFilePath => Path.Combine(StoragePath, "TD_PluginConfig.cfg");
|
|
||||||
|
|
||||||
private static string ErrorLogFilePath => Path.Combine(StoragePath, "TD_Log.txt");
|
|
||||||
private static string ConsoleLogFilePath => Path.Combine(StoragePath, "TD_Console.txt");
|
|
||||||
|
|
||||||
public static uint WindowRestoreMessage;
|
public static uint WindowRestoreMessage;
|
||||||
|
|
||||||
private static readonly LockManager LockManager = new LockManager(Path.Combine(StoragePath, ".lock"));
|
private static LockManager lockManager;
|
||||||
|
private static Reporter errorReporter;
|
||||||
private static bool hasCleanedUp;
|
private static bool hasCleanedUp;
|
||||||
|
|
||||||
public static Reporter Reporter { get; }
|
public static ConfigManager Config { get; private set; }
|
||||||
public static ConfigManager Config { get; }
|
|
||||||
|
|
||||||
static Program() {
|
|
||||||
Reporter = new Reporter(ErrorLogFilePath);
|
|
||||||
Reporter.SetupUnhandledExceptionHandler("TweetDuck Has Failed :(");
|
|
||||||
|
|
||||||
Config = new ConfigManager();
|
|
||||||
|
|
||||||
Lib.Initialize(new App.Builder {
|
|
||||||
ErrorHandler = Reporter,
|
|
||||||
SystemHandler = new SystemHandler(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static void SetupWinForms() {
|
internal static void SetupWinForms() {
|
||||||
Win.Application.EnableVisualStyles();
|
Win.Application.EnableVisualStyles();
|
||||||
@ -76,17 +54,46 @@ internal static void SetupWinForms() {
|
|||||||
|
|
||||||
[STAThread]
|
[STAThread]
|
||||||
private static void Main() {
|
private static void Main() {
|
||||||
|
AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
|
||||||
|
|
||||||
SetupWinForms();
|
SetupWinForms();
|
||||||
Cef.EnableHighDPISupport();
|
Cef.EnableHighDPISupport();
|
||||||
|
|
||||||
|
var startup = new AppStartup {
|
||||||
|
CustomDataFolder = Arguments.GetValue(Arguments.ArgDataFolder)
|
||||||
|
};
|
||||||
|
|
||||||
|
var reporter = new Reporter();
|
||||||
|
var userConfig = new UserConfig();
|
||||||
|
|
||||||
|
Lib.Initialize(new AppBuilder {
|
||||||
|
Startup = startup,
|
||||||
|
Logger = new Logger(ProgramLogFile),
|
||||||
|
ErrorHandler = reporter,
|
||||||
|
SystemHandler = new SystemHandler(),
|
||||||
|
DialogHandler = new DialogHandler(),
|
||||||
|
UserConfiguration = userConfig
|
||||||
|
});
|
||||||
|
|
||||||
|
LaunchApp(reporter, userConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void LaunchApp(Reporter reporter, UserConfig userConfig) {
|
||||||
|
App.Launch();
|
||||||
|
|
||||||
|
errorReporter = reporter;
|
||||||
|
string storagePath = App.StoragePath;
|
||||||
|
|
||||||
|
Config = new ConfigManager(userConfig, new ConfigManager.Paths {
|
||||||
|
UserConfig = Path.Combine(storagePath, "TD_UserConfig.cfg"),
|
||||||
|
SystemConfig = Path.Combine(storagePath, "TD_SystemConfig.cfg"),
|
||||||
|
PluginConfig = Path.Combine(storagePath, "TD_PluginConfig.cfg")
|
||||||
|
});
|
||||||
|
|
||||||
|
lockManager = new LockManager(Path.Combine(storagePath, ".lock"));
|
||||||
WindowRestoreMessage = NativeMethods.RegisterWindowMessage("TweetDuckRestore");
|
WindowRestoreMessage = NativeMethods.RegisterWindowMessage("TweetDuckRestore");
|
||||||
|
|
||||||
if (!FileUtils.CheckFolderWritePermission(StoragePath)) {
|
if (!lockManager.Lock(Arguments.HasFlag(Arguments.ArgRestart))) {
|
||||||
FormMessage.Warning("Permission Error", "TweetDuck does not have write permissions to the storage folder: " + StoragePath, FormMessage.OK);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!LockManager.Lock(Arguments.HasFlag(Arguments.ArgRestart))) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,19 +106,23 @@ private static void Main() {
|
|||||||
ProfileManager.DeleteCookies();
|
ProfileManager.DeleteCookies();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var installerFolderPath = Path.Combine(storagePath, InstallerFolder);
|
||||||
|
|
||||||
if (Arguments.HasFlag(Arguments.ArgUpdated)) {
|
if (Arguments.HasFlag(Arguments.ArgUpdated)) {
|
||||||
WindowsUtils.TryDeleteFolderWhenAble(InstallerPath, 8000);
|
WindowsUtils.TryDeleteFolderWhenAble(installerFolderPath, 8000);
|
||||||
WindowsUtils.TryDeleteFolderWhenAble(Path.Combine(StoragePath, "Service Worker"), 4000);
|
WindowsUtils.TryDeleteFolderWhenAble(Path.Combine(storagePath, "Service Worker"), 4000);
|
||||||
BrowserCache.TryClearNow();
|
BrowserCache.TryClearNow();
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
ResourceRequestHandlerBase.LoadResourceRewriteRules(Arguments.GetValue(Arguments.ArgFreeze));
|
BaseResourceRequestHandler.LoadResourceRewriteRules(Arguments.GetValue(Arguments.ArgFreeze));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
FormMessage.Error("Resource Freeze", "Error parsing resource rewrite rules: " + e.Message, FormMessage.OK);
|
FormMessage.Error("Resource Freeze", "Error parsing resource rewrite rules: " + e.Message, FormMessage.OK);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WebUtils.DefaultUserAgent = BrowserUtils.UserAgentVanilla;
|
||||||
|
|
||||||
if (Config.User.UseSystemProxyForAllConnections) {
|
if (Config.User.UseSystemProxyForAllConnections) {
|
||||||
WebUtils.EnableSystemProxy();
|
WebUtils.EnableSystemProxy();
|
||||||
}
|
}
|
||||||
@ -122,21 +133,20 @@ private static void Main() {
|
|||||||
|
|
||||||
CefSettings settings = new CefSettings {
|
CefSettings settings = new CefSettings {
|
||||||
UserAgent = BrowserUtils.UserAgentChrome,
|
UserAgent = BrowserUtils.UserAgentChrome,
|
||||||
BrowserSubprocessPath = Path.Combine(ProgramPath, BrandName + ".Browser.exe"),
|
BrowserSubprocessPath = Path.Combine(App.ProgramPath, BrandName + ".Browser.exe"),
|
||||||
CachePath = StoragePath,
|
CachePath = storagePath,
|
||||||
UserDataPath = CefDataPath,
|
UserDataPath = Path.Combine(storagePath, CefDataFolder),
|
||||||
LogFile = ConsoleLogFilePath,
|
LogFile = Path.Combine(storagePath, ConsoleLogFile),
|
||||||
#if !DEBUG
|
#if !DEBUG
|
||||||
LogSeverity = Arguments.HasFlag(Arguments.ArgLogging) ? LogSeverity.Info : LogSeverity.Disable
|
LogSeverity = Arguments.HasFlag(Arguments.ArgLogging) ? LogSeverity.Info : LogSeverity.Disable
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
var resourceProvider = new ResourceProvider();
|
var resourceProvider = new CachingResourceProvider<IResourceHandler>(new ResourceProvider());
|
||||||
var resourceScheme = new ResourceSchemeFactory(resourceProvider);
|
var pluginManager = new PluginManager(Config.Plugins, Path.Combine(storagePath, PluginDataFolder));
|
||||||
var pluginScheme = new PluginSchemeFactory(resourceProvider);
|
|
||||||
|
|
||||||
settings.SetupCustomScheme(ResourceSchemeFactory.Name, resourceScheme);
|
CefSchemeHandlerFactory.Register(settings, new TweetDuckSchemeHandler<IResourceHandler>(resourceProvider));
|
||||||
settings.SetupCustomScheme(PluginSchemeFactory.Name, pluginScheme);
|
CefSchemeHandlerFactory.Register(settings, new PluginSchemeHandler<IResourceHandler>(resourceProvider, pluginManager));
|
||||||
|
|
||||||
CefUtils.ParseCommandLineArguments(Config.User.CustomCefArgs).ToDictionary(settings.CefCommandLineArgs);
|
CefUtils.ParseCommandLineArguments(Config.User.CustomCefArgs).ToDictionary(settings.CefCommandLineArgs);
|
||||||
BrowserUtils.SetupCefArgs(settings.CefCommandLineArgs);
|
BrowserUtils.SetupCefArgs(settings.CefCommandLineArgs);
|
||||||
@ -144,8 +154,7 @@ private static void Main() {
|
|||||||
Cef.Initialize(settings, false, new BrowserProcessHandler());
|
Cef.Initialize(settings, false, new BrowserProcessHandler());
|
||||||
|
|
||||||
Win.Application.ApplicationExit += (sender, args) => ExitCleanup();
|
Win.Application.ApplicationExit += (sender, args) => ExitCleanup();
|
||||||
|
FormBrowser mainForm = new FormBrowser(resourceProvider, pluginManager, new UpdateCheckClient(installerFolderPath));
|
||||||
FormBrowser mainForm = new FormBrowser(resourceProvider, pluginScheme);
|
|
||||||
Win.Application.Run(mainForm);
|
Win.Application.Run(mainForm);
|
||||||
|
|
||||||
if (mainForm.UpdateInstaller != null) {
|
if (mainForm.UpdateInstaller != null) {
|
||||||
@ -160,21 +169,19 @@ private static void Main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GetDataStoragePath() {
|
private static void OnUnhandledException(object sender, UnhandledExceptionEventArgs e) {
|
||||||
string custom = Arguments.GetValue(Arguments.ArgDataFolder);
|
if (e.ExceptionObject is Exception ex) {
|
||||||
|
AppException appEx = ex.GetBaseException() as AppException;
|
||||||
|
string title = appEx?.Title ?? "TweetDuck Has Failed :(";
|
||||||
|
string message = appEx?.Message ?? "An unhandled exception has occurred: " + ex.Message;
|
||||||
|
|
||||||
if (custom != null && (custom.Contains(Path.DirectorySeparatorChar) || custom.Contains(Path.AltDirectorySeparatorChar))) {
|
if (errorReporter == null) {
|
||||||
if (Path.GetInvalidPathChars().Any(custom.Contains)) {
|
Debug.WriteLine(ex);
|
||||||
Reporter.HandleEarlyFailure("Data Folder Invalid", "The data folder contains invalid characters:\n" + custom);
|
Reporter.HandleEarlyFailure(title, message);
|
||||||
}
|
}
|
||||||
else if (!Path.IsPathRooted(custom)) {
|
else {
|
||||||
Reporter.HandleEarlyFailure("Data Folder Invalid", "The data folder has to be either a simple folder name, or a full path:\n" + custom);
|
errorReporter.HandleException(title, message, false, ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Environment.ExpandEnvironmentVariables(custom);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), custom ?? BrandName);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -211,7 +218,7 @@ private static void ExitCleanup() {
|
|||||||
Cef.Shutdown();
|
Cef.Shutdown();
|
||||||
BrowserCache.Exit();
|
BrowserCache.Exit();
|
||||||
|
|
||||||
LockManager.Unlock();
|
lockManager.Unlock();
|
||||||
hasCleanedUp = true;
|
hasCleanedUp = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
20
Properties/Resources.Designer.cs
generated
20
Properties/Resources.Designer.cs
generated
@ -60,16 +60,6 @@ internal Resources() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Looks up a localized resource of type System.Byte[].
|
|
||||||
/// </summary>
|
|
||||||
internal static byte[] avatar {
|
|
||||||
get {
|
|
||||||
object obj = ResourceManager.GetObject("avatar", resourceCulture);
|
|
||||||
return ((byte[])(obj));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized resource of type System.Drawing.Icon similar to (Icon).
|
/// Looks up a localized resource of type System.Drawing.Icon similar to (Icon).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -119,15 +109,5 @@ internal static System.Drawing.Icon icon_tray_new {
|
|||||||
return ((System.Drawing.Icon)(obj));
|
return ((System.Drawing.Icon)(obj));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Looks up a localized resource of type System.Byte[].
|
|
||||||
/// </summary>
|
|
||||||
internal static byte[] spinner {
|
|
||||||
get {
|
|
||||||
object obj = ResourceManager.GetObject("spinner", resourceCulture);
|
|
||||||
return ((byte[])(obj));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -118,9 +118,6 @@
|
|||||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
</resheader>
|
</resheader>
|
||||||
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
|
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
|
||||||
<data name="avatar" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
|
||||||
<value>..\Resources\Images\avatar.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
|
||||||
</data>
|
|
||||||
<data name="icon" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
<data name="icon" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||||
<value>..\Resources\Images\icon.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
|
<value>..\Resources\Images\icon.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
|
||||||
</data>
|
</data>
|
||||||
@ -136,7 +133,4 @@
|
|||||||
<data name="icon_tray_new" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
<data name="icon_tray_new" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||||
<value>..\Resources\Images\icon-tray-new.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
|
<value>..\Resources\Images\icon-tray-new.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="spinner" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
|
||||||
<value>..\Resources\Images\spinner.apng;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
|
||||||
</data>
|
|
||||||
</root>
|
</root>
|
127
Reporter.cs
127
Reporter.cs
@ -1,109 +1,64 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.IO;
|
|
||||||
using System.Text;
|
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using TweetDuck.Configuration;
|
|
||||||
using TweetDuck.Dialogs;
|
using TweetDuck.Dialogs;
|
||||||
|
using TweetDuck.Management;
|
||||||
using TweetLib.Core;
|
using TweetLib.Core;
|
||||||
using TweetLib.Core.Application;
|
using TweetLib.Core.Application;
|
||||||
|
|
||||||
namespace TweetDuck {
|
namespace TweetDuck {
|
||||||
sealed class Reporter : IAppErrorHandler {
|
sealed class Reporter : IAppErrorHandler {
|
||||||
private readonly string logFile;
|
private static void Exit(string message, Exception ex = null) {
|
||||||
|
|
||||||
public Reporter(string logFile) {
|
|
||||||
this.logFile = logFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetupUnhandledExceptionHandler(string caption) {
|
|
||||||
AppDomain.CurrentDomain.UnhandledException += (sender, args) => {
|
|
||||||
if (args.ExceptionObject is Exception ex) {
|
|
||||||
HandleException(caption, "An unhandled exception has occurred.", false, ex);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool LogVerbose(string data) {
|
|
||||||
return Arguments.HasFlag(Arguments.ArgLogging) && LogImportant(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool LogImportant(string data) {
|
|
||||||
return ((IAppErrorHandler) this).Log(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IAppErrorHandler.Log(string text) {
|
|
||||||
#if DEBUG
|
|
||||||
Debug.WriteLine(text);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
StringBuilder build = new StringBuilder();
|
|
||||||
|
|
||||||
if (!File.Exists(logFile)) {
|
|
||||||
build.Append("Please, report all issues to: https://github.com/chylex/TweetDuck/issues\r\n\r\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
build.Append("[").Append(DateTime.Now.ToString("G", Lib.Culture)).Append("]\r\n");
|
|
||||||
build.Append(text).Append("\r\n\r\n");
|
|
||||||
|
|
||||||
try {
|
|
||||||
File.AppendAllText(logFile, build.ToString(), Encoding.UTF8);
|
|
||||||
return true;
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void HandleException(string caption, string message, bool canIgnore, Exception e) {
|
|
||||||
bool loggedSuccessfully = LogImportant(e.ToString());
|
|
||||||
|
|
||||||
string exceptionText = e is ExpandedLogException ? e.Message + "\n\nDetails with potentially sensitive information are in the Error Log." : e.Message;
|
|
||||||
FormMessage form = new FormMessage(caption, message + "\nError: " + exceptionText, canIgnore ? MessageBoxIcon.Warning : MessageBoxIcon.Error);
|
|
||||||
|
|
||||||
Button btnExit = form.AddButton(FormMessage.Exit);
|
|
||||||
Button btnIgnore = form.AddButton(FormMessage.Ignore, DialogResult.Ignore, ControlType.Cancel);
|
|
||||||
|
|
||||||
btnIgnore.Enabled = canIgnore;
|
|
||||||
form.ActiveControl = canIgnore ? btnIgnore : btnExit;
|
|
||||||
|
|
||||||
Button btnOpenLog = new Button {
|
|
||||||
Anchor = AnchorStyles.Bottom | AnchorStyles.Left,
|
|
||||||
Enabled = loggedSuccessfully,
|
|
||||||
Font = SystemFonts.MessageBoxFont,
|
|
||||||
Location = new Point(9, 12),
|
|
||||||
Margin = new Padding(0, 0, 48, 0),
|
|
||||||
Size = new Size(106, 26),
|
|
||||||
Text = "Show Error Log",
|
|
||||||
UseVisualStyleBackColor = true
|
|
||||||
};
|
|
||||||
|
|
||||||
btnOpenLog.Click += (sender, args) => {
|
|
||||||
using (Process.Start(logFile)) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
form.AddActionControl(btnOpenLog);
|
|
||||||
|
|
||||||
if (form.ShowDialog() == DialogResult.Ignore) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Process.GetCurrentProcess().Kill();
|
Process.GetCurrentProcess().Kill();
|
||||||
} catch {
|
} catch {
|
||||||
Environment.FailFast(message, e);
|
Environment.FailFast(message, ex ?? new Exception(message));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void HandleEarlyFailure(string caption, string message) {
|
public static void HandleEarlyFailure(string caption, string message) {
|
||||||
Program.SetupWinForms();
|
Program.SetupWinForms();
|
||||||
FormMessage.Error(caption, message, "Exit");
|
FormMessage.Error(caption, message, "Exit");
|
||||||
|
Exit(message);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
public void HandleException(string caption, string message, bool canIgnore, Exception e) {
|
||||||
Process.GetCurrentProcess().Kill();
|
bool loggedSuccessfully = App.Logger.Error(e.ToString());
|
||||||
} catch {
|
|
||||||
Environment.FailFast(message, new Exception(message));
|
FormManager.RunOnUIThread(() => {
|
||||||
}
|
string exceptionText = e is ExpandedLogException ? e.Message + "\n\nDetails with potentially sensitive information are in the Error Log." : e.Message;
|
||||||
|
FormMessage form = new FormMessage(caption, message + "\nError: " + exceptionText, canIgnore ? MessageBoxIcon.Warning : MessageBoxIcon.Error);
|
||||||
|
|
||||||
|
Button btnExit = form.AddButton(FormMessage.Exit);
|
||||||
|
Button btnIgnore = form.AddButton(FormMessage.Ignore, DialogResult.Ignore, ControlType.Cancel);
|
||||||
|
|
||||||
|
btnIgnore.Enabled = canIgnore;
|
||||||
|
form.ActiveControl = canIgnore ? btnIgnore : btnExit;
|
||||||
|
|
||||||
|
Button btnOpenLog = new Button {
|
||||||
|
Anchor = AnchorStyles.Bottom | AnchorStyles.Left,
|
||||||
|
Enabled = loggedSuccessfully,
|
||||||
|
Font = SystemFonts.MessageBoxFont,
|
||||||
|
Location = new Point(9, 12),
|
||||||
|
Margin = new Padding(0, 0, 48, 0),
|
||||||
|
Size = new Size(106, 26),
|
||||||
|
Text = "Show Error Log",
|
||||||
|
UseVisualStyleBackColor = true
|
||||||
|
};
|
||||||
|
|
||||||
|
btnOpenLog.Click += (sender, args) => {
|
||||||
|
if (!App.Logger.OpenLogFile()) {
|
||||||
|
FormMessage.Error("Error Log", "Cannot open error log.", FormMessage.OK);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
form.AddActionControl(btnOpenLog);
|
||||||
|
|
||||||
|
if (form.ShowDialog() != DialogResult.Ignore) {
|
||||||
|
Exit(message, e);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class ExpandedLogException : Exception {
|
public sealed class ExpandedLogException : Exception {
|
||||||
|
Before ![]() (image error) Size: 4.4 KiB After ![]() (image error) Size: 4.4 KiB ![]() ![]() |
Before (image error) Size: 12 KiB After (image error) Size: 12 KiB |
@ -7,7 +7,7 @@
|
|||||||
</time>
|
</time>
|
||||||
<a target="_blank" rel="user" href="https://twitter.com/TryMyAwesomeApp" class="account-link link-complex block">
|
<a target="_blank" rel="user" href="https://twitter.com/TryMyAwesomeApp" class="account-link link-complex block">
|
||||||
<div class="obj-left item-img tweet-img">
|
<div class="obj-left item-img tweet-img">
|
||||||
<img width="48" height="48" alt="TryMyAwesomeApp's avatar" src="{avatar}" class="tweet-avatar avatar pull-right">
|
<img width="48" height="48" alt="TryMyAwesomeApp's avatar" src="td://resources/images/logo.png" class="tweet-avatar avatar pull-right">
|
||||||
</div>
|
</div>
|
||||||
<div class="nbfc">
|
<div class="nbfc">
|
||||||
<span class="account-inline txt-ellipsis">
|
<span class="account-inline txt-ellipsis">
|
||||||
|
@ -7,7 +7,7 @@ import { checkPropertyExists } from "../api/utils.js";
|
|||||||
*/
|
*/
|
||||||
export default function() {
|
export default function() {
|
||||||
const realDisplayName = "TweetDuck";
|
const realDisplayName = "TweetDuck";
|
||||||
const realAvatar = "https://ton.twimg.com/tduck/avatar";
|
const realAvatar = "td://resources/images/logo.png";
|
||||||
const accountId = "957608948189880320";
|
const accountId = "957608948189880320";
|
||||||
|
|
||||||
if (checkPropertyExists(TD, "services", "TwitterUser", "prototype")) {
|
if (checkPropertyExists(TD, "services", "TwitterUser", "prototype")) {
|
||||||
|
@ -34,3 +34,7 @@ if (location.hash.length > 1) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.addEventListener("hashchange", function() {
|
||||||
|
location.reload();
|
||||||
|
});
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
#if DEBUG
|
#if DEBUG
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using TweetLib.Core;
|
||||||
|
|
||||||
namespace TweetDuck.Resources {
|
namespace TweetDuck.Resources {
|
||||||
static class ResourceHotSwap {
|
static class ResourceHotSwap {
|
||||||
private static readonly string HotSwapProjectRoot = FixPathSlash(Path.GetFullPath(Path.Combine(Program.ProgramPath, "../../../")));
|
private static readonly string HotSwapProjectRoot = FixPathSlash(Path.GetFullPath(Path.Combine(App.ProgramPath, "../../../")));
|
||||||
private static readonly string HotSwapTargetDir = FixPathSlash(Path.Combine(HotSwapProjectRoot, "bin", "tmp"));
|
private static readonly string HotSwapTargetDir = FixPathSlash(Path.Combine(HotSwapProjectRoot, "bin", "tmp"));
|
||||||
private static readonly string HotSwapRebuildScript = Path.Combine(HotSwapProjectRoot, "bld", "post_build.exe");
|
private static readonly string HotSwapRebuildScript = Path.Combine(HotSwapProjectRoot, "bld", "post_build.exe");
|
||||||
|
|
||||||
@ -44,11 +45,11 @@ public static void Run() {
|
|||||||
sw.Stop();
|
sw.Stop();
|
||||||
Debug.WriteLine($"Finished rebuild script in {sw.ElapsedMilliseconds} ms");
|
Debug.WriteLine($"Finished rebuild script in {sw.ElapsedMilliseconds} ms");
|
||||||
|
|
||||||
Directory.Delete(Program.ResourcesPath, true);
|
Directory.Delete(App.ResourcesPath, true);
|
||||||
Directory.Delete(Program.PluginPath, true);
|
Directory.Delete(App.PluginPath, true);
|
||||||
|
|
||||||
Directory.Move(Path.Combine(HotSwapTargetDir, "resources"), Program.ResourcesPath);
|
Directory.Move(Path.Combine(HotSwapTargetDir, "resources"), App.ResourcesPath);
|
||||||
Directory.Move(Path.Combine(HotSwapTargetDir, "plugins"), Program.PluginPath);
|
Directory.Move(Path.Combine(HotSwapTargetDir, "plugins"), App.PluginPath);
|
||||||
|
|
||||||
DeleteHotSwapFolder();
|
DeleteHotSwapFolder();
|
||||||
}
|
}
|
||||||
|
@ -1,100 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Net;
|
|
||||||
using System.Text;
|
|
||||||
using CefSharp;
|
|
||||||
using TweetLib.Core.Browser;
|
|
||||||
|
|
||||||
namespace TweetDuck.Resources {
|
|
||||||
internal sealed class ResourceProvider : IResourceProvider<IResourceHandler> {
|
|
||||||
private readonly Dictionary<string, ICachedResource> cache = new Dictionary<string, ICachedResource>();
|
|
||||||
|
|
||||||
public IResourceHandler Status(HttpStatusCode code, string message) {
|
|
||||||
return CreateStatusHandler(code, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public IResourceHandler File(string path) {
|
|
||||||
string key = new Uri(path).LocalPath;
|
|
||||||
|
|
||||||
if (cache.TryGetValue(key, out var cachedResource)) {
|
|
||||||
return cachedResource.GetResource();
|
|
||||||
}
|
|
||||||
|
|
||||||
cachedResource = FileWithCaching(path);
|
|
||||||
cache[key] = cachedResource;
|
|
||||||
return cachedResource.GetResource();
|
|
||||||
}
|
|
||||||
|
|
||||||
private ICachedResource FileWithCaching(string path) {
|
|
||||||
try {
|
|
||||||
return new CachedFile(System.IO.File.ReadAllBytes(path), Path.GetExtension(path));
|
|
||||||
} catch (FileNotFoundException) {
|
|
||||||
return new CachedStatus(HttpStatusCode.NotFound, "File not found.");
|
|
||||||
} catch (DirectoryNotFoundException) {
|
|
||||||
return new CachedStatus(HttpStatusCode.NotFound, "Directory not found.");
|
|
||||||
} catch (Exception e) {
|
|
||||||
return new CachedStatus(HttpStatusCode.InternalServerError, e.Message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ClearCache() {
|
|
||||||
cache.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ResourceHandler CreateHandler(byte[] bytes) {
|
|
||||||
var handler = ResourceHandler.FromStream(new MemoryStream(bytes), autoDisposeStream: true);
|
|
||||||
handler.Headers.Set("Access-Control-Allow-Origin", "*");
|
|
||||||
return handler;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IResourceHandler CreateFileContentsHandler(byte[] bytes, string extension) {
|
|
||||||
if (bytes.Length == 0) {
|
|
||||||
return CreateStatusHandler(HttpStatusCode.NoContent, "File is empty."); // FromByteArray crashes CEF internals with no contents
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
var handler = CreateHandler(bytes);
|
|
||||||
handler.MimeType = Cef.GetMimeType(extension);
|
|
||||||
return handler;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IResourceHandler CreateStatusHandler(HttpStatusCode code, string message) {
|
|
||||||
var handler = CreateHandler(Encoding.UTF8.GetBytes(message));
|
|
||||||
handler.StatusCode = (int) code;
|
|
||||||
return handler;
|
|
||||||
}
|
|
||||||
|
|
||||||
private interface ICachedResource {
|
|
||||||
IResourceHandler GetResource();
|
|
||||||
}
|
|
||||||
|
|
||||||
private sealed class CachedFile : ICachedResource {
|
|
||||||
private readonly byte[] bytes;
|
|
||||||
private readonly string extension;
|
|
||||||
|
|
||||||
public CachedFile(byte[] bytes, string extension) {
|
|
||||||
this.bytes = bytes;
|
|
||||||
this.extension = extension;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IResourceHandler GetResource() {
|
|
||||||
return CreateFileContentsHandler(bytes, extension);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private sealed class CachedStatus : ICachedResource {
|
|
||||||
private readonly HttpStatusCode code;
|
|
||||||
private readonly string message;
|
|
||||||
|
|
||||||
public CachedStatus(HttpStatusCode code, string message) {
|
|
||||||
this.code = code;
|
|
||||||
this.message = message;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IResourceHandler GetResource() {
|
|
||||||
return CreateStatusHandler(code, message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using System.Text;
|
|
||||||
using TweetLib.Core;
|
|
||||||
|
|
||||||
namespace TweetDuck.Resources {
|
|
||||||
public static class ResourceUtils {
|
|
||||||
public static string ReadFileOrNull(string relativePath) {
|
|
||||||
string path = Path.Combine(Program.ResourcesPath, relativePath);
|
|
||||||
|
|
||||||
try {
|
|
||||||
return File.ReadAllText(path, Encoding.UTF8);
|
|
||||||
} catch (Exception e) {
|
|
||||||
App.ErrorHandler.Log("Error reading file: " + path);
|
|
||||||
App.ErrorHandler.Log(e.ToString());
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,36 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Net;
|
|
||||||
using CefSharp;
|
|
||||||
using TweetLib.Core.Browser;
|
|
||||||
using TweetLib.Utils.Static;
|
|
||||||
|
|
||||||
namespace TweetDuck.Resources {
|
|
||||||
public class ResourceSchemeFactory : ISchemeHandlerFactory {
|
|
||||||
public const string Name = "td";
|
|
||||||
|
|
||||||
private readonly IResourceProvider<IResourceHandler> resourceProvider;
|
|
||||||
|
|
||||||
public ResourceSchemeFactory(IResourceProvider<IResourceHandler> resourceProvider) {
|
|
||||||
this.resourceProvider = resourceProvider;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IResourceHandler Create(IBrowser browser, IFrame frame, string schemeName, IRequest request) {
|
|
||||||
if (!Uri.TryCreate(request.Url, UriKind.Absolute, out var uri) || uri.Scheme != Name) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
string rootPath = uri.Authority switch {
|
|
||||||
"resources" => Program.ResourcesPath,
|
|
||||||
"guide" => Program.GuidePath,
|
|
||||||
_ => null
|
|
||||||
};
|
|
||||||
|
|
||||||
if (rootPath == null) {
|
|
||||||
return resourceProvider.Status(HttpStatusCode.NotFound, "Invalid URL.");
|
|
||||||
}
|
|
||||||
|
|
||||||
string filePath = FileUtils.ResolveRelativePathSafely(rootPath, uri.AbsolutePath.TrimStart('/'));
|
|
||||||
return filePath.Length == 0 ? resourceProvider.Status(HttpStatusCode.Forbidden, "File path has to be relative to the root folder.") : resourceProvider.File(filePath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -62,36 +62,37 @@
|
|||||||
<Reference Include="System.Windows.Forms" />
|
<Reference Include="System.Windows.Forms" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Compile Include="Application\DialogHandler.cs" />
|
||||||
|
<Compile Include="Application\Logger.cs" />
|
||||||
|
<Compile Include="Browser\Adapters\CefBrowserComponent.cs" />
|
||||||
|
<Compile Include="Browser\Adapters\CefContextMenuActionRegistry.cs" />
|
||||||
|
<Compile Include="Browser\Adapters\CefContextMenuModel.cs" />
|
||||||
|
<Compile Include="Browser\Adapters\CefResourceHandlerFactory.cs" />
|
||||||
|
<Compile Include="Browser\Adapters\CefResourceHandlerRegistry.cs" />
|
||||||
|
<Compile Include="Browser\Adapters\CefResourceProvider.cs" />
|
||||||
|
<Compile Include="Browser\Adapters\CefResourceRequestHandler.cs" />
|
||||||
|
<Compile Include="Browser\Adapters\CefSchemeHandlerFactory.cs" />
|
||||||
|
<Compile Include="Browser\Handling\BrowserProcessHandler.cs" />
|
||||||
|
<Compile Include="Browser\Handling\CustomLifeSpanHandler.cs" />
|
||||||
|
<Compile Include="Browser\Handling\FileDialogHandler.cs" />
|
||||||
|
<Compile Include="Browser\Handling\JavaScriptDialogHandler.cs" />
|
||||||
|
<Compile Include="Browser\Handling\ResponseFilter.cs" />
|
||||||
|
<Compile Include="Browser\Notification\FormNotificationExample.cs">
|
||||||
|
<SubType>Form</SubType>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="Browser\TweetDeckInterfaceImpl.cs" />
|
||||||
|
<Compile Include="Dialogs\WindowState.cs" />
|
||||||
<Compile Include="Management\LockManager.cs" />
|
<Compile Include="Management\LockManager.cs" />
|
||||||
<Compile Include="Application\SystemHandler.cs" />
|
<Compile Include="Application\SystemHandler.cs" />
|
||||||
<Compile Include="Browser\Adapters\CefScriptExecutor.cs" />
|
|
||||||
<Compile Include="Browser\Bridge\PropertyBridge.cs" />
|
|
||||||
<Compile Include="Browser\Bridge\TweetDeckBridge.cs" />
|
|
||||||
<Compile Include="Browser\Bridge\UpdateBridge.cs" />
|
|
||||||
<Compile Include="Browser\Data\ContextInfo.cs" />
|
|
||||||
<Compile Include="Browser\Data\ResourceHandlers.cs" />
|
|
||||||
<Compile Include="Browser\Data\ResourceLink.cs" />
|
|
||||||
<Compile Include="Browser\Data\WindowState.cs" />
|
|
||||||
<Compile Include="Browser\Handling\ContextMenuBase.cs" />
|
<Compile Include="Browser\Handling\ContextMenuBase.cs" />
|
||||||
<Compile Include="Browser\Handling\ContextMenuBrowser.cs" />
|
<Compile Include="Browser\Handling\ContextMenuBrowser.cs" />
|
||||||
<Compile Include="Browser\Handling\ContextMenuGuide.cs" />
|
<Compile Include="Browser\Handling\ContextMenuGuide.cs" />
|
||||||
<Compile Include="Browser\Handling\ContextMenuNotification.cs" />
|
<Compile Include="Browser\Handling\ContextMenuNotification.cs" />
|
||||||
<Compile Include="Browser\Handling\DragHandlerBrowser.cs" />
|
<Compile Include="Browser\Handling\DragHandlerBrowser.cs" />
|
||||||
<Compile Include="Browser\Handling\Filters\ResponseFilterBase.cs" />
|
<Compile Include="Browser\Handling\CustomKeyboardHandler.cs" />
|
||||||
<Compile Include="Browser\Handling\Filters\ResponseFilterVendor.cs" />
|
|
||||||
<Compile Include="Browser\Handling\General\BrowserProcessHandler.cs" />
|
|
||||||
<Compile Include="Browser\Handling\General\FileDialogHandler.cs" />
|
|
||||||
<Compile Include="Browser\Handling\General\JavaScriptDialogHandler.cs" />
|
|
||||||
<Compile Include="Browser\Handling\General\CustomLifeSpanHandler.cs" />
|
|
||||||
<Compile Include="Browser\Handling\KeyboardHandlerBase.cs" />
|
|
||||||
<Compile Include="Browser\Handling\KeyboardHandlerBrowser.cs" />
|
|
||||||
<Compile Include="Browser\Handling\KeyboardHandlerNotification.cs" />
|
|
||||||
<Compile Include="Browser\Handling\RequestHandlerBase.cs" />
|
<Compile Include="Browser\Handling\RequestHandlerBase.cs" />
|
||||||
<Compile Include="Browser\Handling\RequestHandlerBrowser.cs" />
|
<Compile Include="Browser\Handling\RequestHandlerBrowser.cs" />
|
||||||
<Compile Include="Browser\Handling\ResourceHandlerNotification.cs" />
|
<Compile Include="Browser\Handling\ResourceHandlerNotification.cs" />
|
||||||
<Compile Include="Browser\Handling\ResourceRequestHandler.cs" />
|
|
||||||
<Compile Include="Browser\Handling\ResourceRequestHandlerBase.cs" />
|
|
||||||
<Compile Include="Browser\Handling\ResourceRequestHandlerBrowser.cs" />
|
|
||||||
<Compile Include="Browser\Notification\Screenshot\ScreenshotBridge.cs" />
|
<Compile Include="Browser\Notification\Screenshot\ScreenshotBridge.cs" />
|
||||||
<Compile Include="Browser\Notification\Screenshot\TweetScreenshotManager.cs" />
|
<Compile Include="Browser\Notification\Screenshot\TweetScreenshotManager.cs" />
|
||||||
<Compile Include="Browser\Notification\SoundNotification.cs" />
|
<Compile Include="Browser\Notification\SoundNotification.cs" />
|
||||||
@ -107,20 +108,15 @@
|
|||||||
<Compile Include="Management\FormManager.cs" />
|
<Compile Include="Management\FormManager.cs" />
|
||||||
<Compile Include="Management\ProfileManager.cs" />
|
<Compile Include="Management\ProfileManager.cs" />
|
||||||
<Compile Include="Management\VideoPlayer.cs" />
|
<Compile Include="Management\VideoPlayer.cs" />
|
||||||
<Compile Include="Plugins\PluginDispatcher.cs" />
|
|
||||||
<Compile Include="Plugins\PluginSchemeFactory.cs" />
|
|
||||||
<Compile Include="Program.cs" />
|
<Compile Include="Program.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
<Compile Include="Reporter.cs" />
|
<Compile Include="Reporter.cs" />
|
||||||
<Compile Include="Resources\ResourcesSchemeFactory.cs" />
|
|
||||||
<Compile Include="Resources\ResourceHotSwap.cs" />
|
<Compile Include="Resources\ResourceHotSwap.cs" />
|
||||||
<Compile Include="Resources\ResourceProvider.cs" />
|
|
||||||
<Compile Include="Resources\ResourceUtils.cs" />
|
|
||||||
<Compile Include="Updates\UpdateCheckClient.cs" />
|
<Compile Include="Updates\UpdateCheckClient.cs" />
|
||||||
<Compile Include="Updates\UpdateInstaller.cs" />
|
<Compile Include="Updates\UpdateInstaller.cs" />
|
||||||
<Compile Include="Utils\BrowserUtils.cs" />
|
<Compile Include="Utils\BrowserUtils.cs" />
|
||||||
<Compile Include="Utils\NativeMethods.cs" />
|
<Compile Include="Utils\NativeMethods.cs" />
|
||||||
<Compile Include="Utils\TwitterUtils.cs" />
|
<Compile Include="Utils\TwitterFileDownloader.cs" />
|
||||||
<Compile Include="Utils\WindowsUtils.cs" />
|
<Compile Include="Utils\WindowsUtils.cs" />
|
||||||
<Compile Include="Version.cs" />
|
<Compile Include="Version.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
@ -143,9 +139,6 @@
|
|||||||
<Compile Include="Browser\FormBrowser.Designer.cs">
|
<Compile Include="Browser\FormBrowser.Designer.cs">
|
||||||
<DependentUpon>FormBrowser.cs</DependentUpon>
|
<DependentUpon>FormBrowser.cs</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="Browser\Notification\Example\FormNotificationExample.cs">
|
|
||||||
<SubType>Form</SubType>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="Browser\Notification\FormNotificationMain.cs">
|
<Compile Include="Browser\Notification\FormNotificationMain.cs">
|
||||||
<SubType>Form</SubType>
|
<SubType>Form</SubType>
|
||||||
</Compile>
|
</Compile>
|
||||||
@ -323,19 +316,19 @@
|
|||||||
<None Include="app.config" />
|
<None Include="app.config" />
|
||||||
<None Include="packages.config" />
|
<None Include="packages.config" />
|
||||||
<None Include="Resources\Content\error\error.html" />
|
<None Include="Resources\Content\error\error.html" />
|
||||||
|
<None Include="Resources\Content\images\logo.png" />
|
||||||
|
<None Include="Resources\Content\images\spinner.apng" />
|
||||||
<None Include="Resources\Content\notification\example\example.html" />
|
<None Include="Resources\Content\notification\example\example.html" />
|
||||||
<None Include="Resources\Content\notification\notification.css" />
|
<None Include="Resources\Content\notification\notification.css" />
|
||||||
<None Include="Resources\Content\notification\screenshot\screenshot.js" />
|
<None Include="Resources\Content\notification\screenshot\screenshot.js" />
|
||||||
<None Include="Resources\Content\plugins\notification\plugins.js" />
|
<None Include="Resources\Content\plugins\notification\plugins.js" />
|
||||||
<None Include="Resources\Content\plugins\tweetdeck\plugins.js" />
|
<None Include="Resources\Content\plugins\tweetdeck\plugins.js" />
|
||||||
<None Include="Resources\Images\avatar.png" />
|
|
||||||
<None Include="Resources\Images\icon-muted.ico" />
|
<None Include="Resources\Images\icon-muted.ico" />
|
||||||
<None Include="Resources\Images\icon-small.ico" />
|
<None Include="Resources\Images\icon-small.ico" />
|
||||||
<None Include="Resources\Images\icon-tray-muted.ico" />
|
<None Include="Resources\Images\icon-tray-muted.ico" />
|
||||||
<None Include="Resources\Images\icon-tray-new.ico" />
|
<None Include="Resources\Images\icon-tray-new.ico" />
|
||||||
<None Include="Resources\Images\icon-tray.ico" />
|
<None Include="Resources\Images\icon-tray.ico" />
|
||||||
<None Include="Resources\Images\icon.ico" />
|
<None Include="Resources\Images\icon.ico" />
|
||||||
<None Include="Resources\Images\spinner.apng" />
|
|
||||||
<None Include="Resources\Plugins\.debug\.meta" />
|
<None Include="Resources\Plugins\.debug\.meta" />
|
||||||
<None Include="Resources\Plugins\.debug\browser.js" />
|
<None Include="Resources\Plugins\.debug\browser.js" />
|
||||||
<None Include="Resources\Plugins\.debug\notification.js" />
|
<None Include="Resources\Plugins\.debug\notification.js" />
|
||||||
@ -364,6 +357,10 @@
|
|||||||
<Redist Include="$(ProjectDir)bld\Redist\*.*" Visible="false" />
|
<Redist Include="$(ProjectDir)bld\Redist\*.*" Visible="false" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="lib\TweetLib.Browser\TweetLib.Browser.csproj">
|
||||||
|
<Project>{eefb1f37-7cad-46bd-8042-66e7b502ab02}</Project>
|
||||||
|
<Name>TweetLib.Browser</Name>
|
||||||
|
</ProjectReference>
|
||||||
<ProjectReference Include="lib\TweetLib.Core\TweetLib.Core.csproj">
|
<ProjectReference Include="lib\TweetLib.Core\TweetLib.Core.csproj">
|
||||||
<Project>{93ba3cb4-a812-4949-b07d-8d393fb38937}</Project>
|
<Project>{93ba3cb4-a812-4949-b07d-8d393fb38937}</Project>
|
||||||
<Name>TweetLib.Core</Name>
|
<Name>TweetLib.Core</Name>
|
||||||
|
@ -10,7 +10,9 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetDuck.Video", "video\Tw
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetLib.Communication", "lib\TweetLib.Communication\TweetLib.Communication.csproj", "{72473763-4B9D-4FB6-A923-9364B2680F06}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetLib.Communication", "lib\TweetLib.Communication\TweetLib.Communication.csproj", "{72473763-4B9D-4FB6-A923-9364B2680F06}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetDuck.Core", "lib\TweetLib.Core\TweetLib.Core.csproj", "{93BA3CB4-A812-4949-B07D-8D393FB38937}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetLib.Core", "lib\TweetLib.Core\TweetLib.Core.csproj", "{93BA3CB4-A812-4949-B07D-8D393FB38937}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetLib.Browser", "lib\TweetLib.Browser\TweetLib.Browser.csproj", "{EEFB1F37-7CAD-46BD-8042-66E7B502AB02}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetLib.Utils", "lib\TweetLib.Utils\TweetLib.Utils.csproj", "{476B1007-B12C-447F-B855-9886048201D6}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetLib.Utils", "lib\TweetLib.Utils\TweetLib.Utils.csproj", "{476B1007-B12C-447F-B855-9886048201D6}"
|
||||||
EndProject
|
EndProject
|
||||||
@ -44,6 +46,10 @@ Global
|
|||||||
{93BA3CB4-A812-4949-B07D-8D393FB38937}.Debug|x86.Build.0 = Debug|x86
|
{93BA3CB4-A812-4949-B07D-8D393FB38937}.Debug|x86.Build.0 = Debug|x86
|
||||||
{93BA3CB4-A812-4949-B07D-8D393FB38937}.Release|x86.ActiveCfg = Release|x86
|
{93BA3CB4-A812-4949-B07D-8D393FB38937}.Release|x86.ActiveCfg = Release|x86
|
||||||
{93BA3CB4-A812-4949-B07D-8D393FB38937}.Release|x86.Build.0 = Release|x86
|
{93BA3CB4-A812-4949-B07D-8D393FB38937}.Release|x86.Build.0 = Release|x86
|
||||||
|
{EEFB1F37-7CAD-46BD-8042-66E7B502AB02}.Debug|x86.ActiveCfg = Debug|x86
|
||||||
|
{EEFB1F37-7CAD-46BD-8042-66E7B502AB02}.Debug|x86.Build.0 = Debug|x86
|
||||||
|
{EEFB1F37-7CAD-46BD-8042-66E7B502AB02}.Release|x86.ActiveCfg = Release|x86
|
||||||
|
{EEFB1F37-7CAD-46BD-8042-66E7B502AB02}.Release|x86.Build.0 = Release|x86
|
||||||
{476B1007-B12C-447F-B855-9886048201D6}.Debug|x86.ActiveCfg = Debug|x86
|
{476B1007-B12C-447F-B855-9886048201D6}.Debug|x86.ActiveCfg = Debug|x86
|
||||||
{476B1007-B12C-447F-B855-9886048201D6}.Debug|x86.Build.0 = Debug|x86
|
{476B1007-B12C-447F-B855-9886048201D6}.Debug|x86.Build.0 = Debug|x86
|
||||||
{476B1007-B12C-447F-B855-9886048201D6}.Release|x86.ActiveCfg = Release|x86
|
{476B1007-B12C-447F-B855-9886048201D6}.Release|x86.ActiveCfg = Release|x86
|
||||||
|
@ -5,7 +5,6 @@
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Web.Script.Serialization;
|
using System.Web.Script.Serialization;
|
||||||
using TweetDuck.Utils;
|
|
||||||
using TweetLib.Core.Systems.Updates;
|
using TweetLib.Core.Systems.Updates;
|
||||||
using TweetLib.Utils.Static;
|
using TweetLib.Utils.Static;
|
||||||
using JsonObject = System.Collections.Generic.IDictionary<string, object>;
|
using JsonObject = System.Collections.Generic.IDictionary<string, object>;
|
||||||
@ -24,9 +23,9 @@ public UpdateCheckClient(string installerFolder) {
|
|||||||
bool IUpdateCheckClient.CanCheck => Program.Config.User.EnableUpdateCheck;
|
bool IUpdateCheckClient.CanCheck => Program.Config.User.EnableUpdateCheck;
|
||||||
|
|
||||||
Task<UpdateInfo> IUpdateCheckClient.Check() {
|
Task<UpdateInfo> IUpdateCheckClient.Check() {
|
||||||
TaskCompletionSource<UpdateInfo> result = new TaskCompletionSource<UpdateInfo>();
|
var result = new TaskCompletionSource<UpdateInfo>();
|
||||||
|
|
||||||
WebClient client = WebUtils.NewClient(BrowserUtils.UserAgentVanilla);
|
WebClient client = WebUtils.NewClient();
|
||||||
client.Headers[HttpRequestHeader.Accept] = "application/vnd.github.v3+json";
|
client.Headers[HttpRequestHeader.Accept] = "application/vnd.github.v3+json";
|
||||||
|
|
||||||
client.DownloadStringTaskAsync(ApiLatestRelease).ContinueWith(task => {
|
client.DownloadStringTaskAsync(ApiLatestRelease).ContinueWith(task => {
|
||||||
@ -57,7 +56,7 @@ static string AssetDownloadUrl(JsonObject obj) {
|
|||||||
return (string) obj["browser_download_url"];
|
return (string) obj["browser_download_url"];
|
||||||
}
|
}
|
||||||
|
|
||||||
JsonObject root = (JsonObject) new JavaScriptSerializer().DeserializeObject(json);
|
var root = (JsonObject) new JavaScriptSerializer().DeserializeObject(json);
|
||||||
|
|
||||||
string versionTag = (string) root["tag_name"];
|
string versionTag = (string) root["tag_name"];
|
||||||
string releaseNotes = (string) root["body"];
|
string releaseNotes = (string) root["body"];
|
||||||
@ -67,7 +66,7 @@ static string AssetDownloadUrl(JsonObject obj) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static Exception ExpandWebException(Exception e) {
|
private static Exception ExpandWebException(Exception e) {
|
||||||
if (e is WebException we && we.Response is HttpWebResponse response) {
|
if (e is WebException { Response: HttpWebResponse response } ) {
|
||||||
try {
|
try {
|
||||||
using var stream = response.GetResponseStream();
|
using var stream = response.GetResponseStream();
|
||||||
using var reader = new StreamReader(stream, Encoding.GetEncoding(response.CharacterSet ?? "utf-8"));
|
using var reader = new StreamReader(stream, Encoding.GetEncoding(response.CharacterSet ?? "utf-8"));
|
||||||
|
@ -15,8 +15,8 @@ public UpdateInstaller(string path) {
|
|||||||
|
|
||||||
public bool Launch() {
|
public bool Launch() {
|
||||||
// ProgramPath has a trailing backslash
|
// ProgramPath has a trailing backslash
|
||||||
string arguments = "/SP- /SILENT /FORCECLOSEAPPLICATIONS /UPDATEPATH=\"" + Program.ProgramPath + "\" /RUNARGS=\"" + Arguments.GetCurrentForInstallerCmd() + "\"" + (Program.IsPortable ? " /PORTABLE=1" : "");
|
string arguments = "/SP- /SILENT /FORCECLOSEAPPLICATIONS /UPDATEPATH=\"" + App.ProgramPath + "\" /RUNARGS=\"" + Arguments.GetCurrentForInstallerCmd() + "\"" + (App.IsPortable ? " /PORTABLE=1" : "");
|
||||||
bool runElevated = !Program.IsPortable || !FileUtils.CheckFolderWritePermission(Program.ProgramPath);
|
bool runElevated = !App.IsPortable || !FileUtils.CheckFolderWritePermission(App.ProgramPath);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
using (Process.Start(new ProcessStartInfo {
|
using (Process.Start(new ProcessStartInfo {
|
||||||
|
@ -1,17 +1,12 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.IO;
|
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using CefSharp;
|
using CefSharp;
|
||||||
using CefSharp.WinForms;
|
using CefSharp.WinForms;
|
||||||
using TweetDuck.Browser;
|
|
||||||
using TweetDuck.Configuration;
|
using TweetDuck.Configuration;
|
||||||
using TweetDuck.Dialogs;
|
using TweetDuck.Controls;
|
||||||
using TweetDuck.Management;
|
using TweetLib.Browser.Interfaces;
|
||||||
using TweetLib.Core;
|
|
||||||
using TweetLib.Core.Features.Twitter;
|
|
||||||
|
|
||||||
namespace TweetDuck.Utils {
|
namespace TweetDuck.Utils {
|
||||||
static class BrowserUtils {
|
static class BrowserUtils {
|
||||||
@ -52,19 +47,16 @@ public static void SetupCefArgs(IDictionary<string, string> args) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void SetupCustomScheme(this CefSettings settings, string name, ISchemeHandlerFactory factory) {
|
public static void SetupDockOnLoad(IBrowserComponent browserComponent, ChromiumWebBrowser browser) {
|
||||||
settings.RegisterScheme(new CefCustomScheme {
|
browser.Dock = DockStyle.None;
|
||||||
SchemeName = name,
|
browser.Location = ControlExtensions.InvisibleLocation;
|
||||||
IsStandard = false,
|
|
||||||
IsSecure = true,
|
|
||||||
IsCorsEnabled = true,
|
|
||||||
IsCSPBypassing = true,
|
|
||||||
SchemeHandlerFactory = factory
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ChromiumWebBrowser AsControl(this IWebBrowser browserControl) {
|
browserComponent.BrowserLoaded += (sender, args) => {
|
||||||
return (ChromiumWebBrowser) browserControl;
|
browser.InvokeAsyncSafe(() => {
|
||||||
|
browser.Location = Point.Empty;
|
||||||
|
browser.Dock = DockStyle.Fill;
|
||||||
|
});
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void SetupZoomEvents(this ChromiumWebBrowser browser) {
|
public static void SetupZoomEvents(this ChromiumWebBrowser browser) {
|
||||||
@ -86,20 +78,6 @@ void UpdateZoomLevel(object sender, EventArgs args) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void RegisterJsBridge(this IWebBrowser browserControl, string name, object bridge) {
|
|
||||||
browserControl.JavascriptObjectRepository.Settings.LegacyBindingEnabled = true;
|
|
||||||
browserControl.JavascriptObjectRepository.Register(name, bridge, isAsync: true, BindingOptions.DefaultBinder);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void ExecuteJsAsync(this IWebBrowser browserControl, string scriptOrMethodName, params object[] args) {
|
|
||||||
if (args.Length == 0) {
|
|
||||||
browserControl.BrowserCore.ExecuteScriptAsync(scriptOrMethodName);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
browserControl.BrowserCore.ExecuteScriptAsync(scriptOrMethodName, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void OpenDevToolsCustom(this IWebBrowser browser, Point? inspectPoint = null) {
|
public static void OpenDevToolsCustom(this IWebBrowser browser, Point? inspectPoint = null) {
|
||||||
var info = new WindowInfo();
|
var info = new WindowInfo();
|
||||||
info.SetAsPopup(IntPtr.Zero, "Dev Tools");
|
info.SetAsPopup(IntPtr.Zero, "Dev Tools");
|
||||||
@ -112,96 +90,6 @@ public static void OpenDevToolsCustom(this IWebBrowser browser, Point? inspectPo
|
|||||||
browser.GetBrowserHost().ShowDevTools(info, p.X, p.Y);
|
browser.GetBrowserHost().ShowDevTools(info, p.X, p.Y);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void OpenExternalBrowser(string url) {
|
|
||||||
if (string.IsNullOrWhiteSpace(url)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (TwitterUrls.Check(url)) {
|
|
||||||
case TwitterUrls.UrlType.Fine:
|
|
||||||
string browserPath = Config.BrowserPath;
|
|
||||||
|
|
||||||
if (browserPath == null || !File.Exists(browserPath)) {
|
|
||||||
App.SystemHandler.OpenAssociatedProgram(url);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
string quotedUrl = '"' + url + '"';
|
|
||||||
string browserArgs = Config.BrowserPathArgs == null ? quotedUrl : Config.BrowserPathArgs + ' ' + quotedUrl;
|
|
||||||
|
|
||||||
try {
|
|
||||||
using (Process.Start(browserPath, browserArgs)) {}
|
|
||||||
} catch (Exception e) {
|
|
||||||
App.ErrorHandler.HandleException("Error Opening Browser", "Could not open the browser.", true, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case TwitterUrls.UrlType.Tracking:
|
|
||||||
if (Config.IgnoreTrackingUrlWarning) {
|
|
||||||
goto case TwitterUrls.UrlType.Fine;
|
|
||||||
}
|
|
||||||
|
|
||||||
using (FormMessage form = new FormMessage("Blocked URL", "TweetDuck has blocked a tracking url due to privacy concerns. Do you want to visit it anyway?\n" + url, MessageBoxIcon.Warning)) {
|
|
||||||
form.AddButton(FormMessage.No, DialogResult.No, ControlType.Cancel | ControlType.Focused);
|
|
||||||
form.AddButton(FormMessage.Yes, DialogResult.Yes, ControlType.Accept);
|
|
||||||
form.AddButton("Always Visit", DialogResult.Ignore);
|
|
||||||
|
|
||||||
DialogResult result = form.ShowDialog();
|
|
||||||
|
|
||||||
if (result == DialogResult.Ignore) {
|
|
||||||
Config.IgnoreTrackingUrlWarning = true;
|
|
||||||
Config.Save();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result == DialogResult.Ignore || result == DialogResult.Yes) {
|
|
||||||
goto case TwitterUrls.UrlType.Fine;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case TwitterUrls.UrlType.Invalid:
|
|
||||||
FormMessage.Warning("Blocked URL", "A potentially malicious or invalid URL was blocked from opening:\n" + url, FormMessage.OK);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void OpenExternalSearch(string query) {
|
|
||||||
if (string.IsNullOrWhiteSpace(query)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string searchUrl = Config.SearchEngineUrl;
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(searchUrl)) {
|
|
||||||
if (FormMessage.Question("Search Options", "You have not configured a default search engine yet, would you like to do it now?", FormMessage.Yes, FormMessage.No)) {
|
|
||||||
bool wereSettingsOpen = FormManager.TryFind<FormSettings>() != null;
|
|
||||||
|
|
||||||
FormManager.TryFind<FormBrowser>()?.OpenSettings();
|
|
||||||
|
|
||||||
if (wereSettingsOpen) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
FormSettings settings = FormManager.TryFind<FormSettings>();
|
|
||||||
|
|
||||||
if (settings == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.FormClosed += (sender, args) => {
|
|
||||||
if (args.CloseReason == CloseReason.UserClosing && Config.SearchEngineUrl != searchUrl) {
|
|
||||||
OpenExternalSearch(query);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
OpenExternalBrowser(searchUrl + Uri.EscapeUriString(query));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int Scale(int baseValue, double scaleFactor) {
|
public static int Scale(int baseValue, double scaleFactor) {
|
||||||
return (int) Math.Round(baseValue * scaleFactor);
|
return (int) Math.Round(baseValue * scaleFactor);
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
namespace TweetDuck.Utils {
|
namespace TweetDuck.Utils {
|
||||||
[SuppressMessage("ReSharper", "FieldCanBeMadeReadOnly.Local")]
|
[SuppressMessage("ReSharper", "FieldCanBeMadeReadOnly.Local")]
|
||||||
[SuppressMessage("ReSharper", "MemberCanBePrivate.Local")]
|
[SuppressMessage("ReSharper", "MemberCanBePrivate.Local")]
|
||||||
|
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||||
static class NativeMethods {
|
static class NativeMethods {
|
||||||
public static readonly IntPtr HWND_BROADCAST = new IntPtr(0xFFFF);
|
public static readonly IntPtr HWND_BROADCAST = new IntPtr(0xFFFF);
|
||||||
public static readonly IntPtr HOOK_HANDLED = new IntPtr(-1);
|
public static readonly IntPtr HOOK_HANDLED = new IntPtr(-1);
|
||||||
|
41
Utils/TwitterFileDownloader.cs
Normal file
41
Utils/TwitterFileDownloader.cs
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
using System;
|
||||||
|
using System.Net;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using CefSharp;
|
||||||
|
using TweetDuck.Management;
|
||||||
|
using TweetLib.Browser.Interfaces;
|
||||||
|
using TweetLib.Utils.Static;
|
||||||
|
using Cookie = CefSharp.Cookie;
|
||||||
|
|
||||||
|
namespace TweetDuck.Utils {
|
||||||
|
sealed class TwitterFileDownloader : IFileDownloader {
|
||||||
|
public static TwitterFileDownloader Instance { get; } = new TwitterFileDownloader();
|
||||||
|
|
||||||
|
private TwitterFileDownloader() {}
|
||||||
|
|
||||||
|
public string CacheFolder => BrowserCache.CacheFolder;
|
||||||
|
|
||||||
|
public void DownloadFile(string url, string path, Action onSuccess, Action<Exception> onFailure) {
|
||||||
|
const string authCookieName = "auth_token";
|
||||||
|
|
||||||
|
using ICookieManager cookies = Cef.GetGlobalCookieManager();
|
||||||
|
|
||||||
|
cookies.VisitUrlCookiesAsync(url, true).ContinueWith(task => {
|
||||||
|
string cookieStr = null;
|
||||||
|
|
||||||
|
if (task.Status == TaskStatus.RanToCompletion) {
|
||||||
|
Cookie found = task.Result?.Find(cookie => cookie.Name == authCookieName); // the list may be null
|
||||||
|
|
||||||
|
if (found != null) {
|
||||||
|
cookieStr = $"{found.Name}={found.Value}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WebClient client = WebUtils.NewClient(BrowserUtils.UserAgentChrome);
|
||||||
|
client.Headers[HttpRequestHeader.Cookie] = cookieStr;
|
||||||
|
client.DownloadFileCompleted += WebUtils.FileDownloadCallback(path, onSuccess, onFailure);
|
||||||
|
client.DownloadFileAsync(new Uri(url), path);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,156 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Drawing;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Net;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Windows.Forms;
|
|
||||||
using CefSharp;
|
|
||||||
using TweetDuck.Browser.Data;
|
|
||||||
using TweetDuck.Dialogs;
|
|
||||||
using TweetDuck.Management;
|
|
||||||
using TweetLib.Core;
|
|
||||||
using TweetLib.Core.Features.Twitter;
|
|
||||||
using TweetLib.Utils.Static;
|
|
||||||
using Cookie = CefSharp.Cookie;
|
|
||||||
|
|
||||||
namespace TweetDuck.Utils {
|
|
||||||
static class TwitterUtils {
|
|
||||||
public static readonly Color BackgroundColor = Color.FromArgb(28, 99, 153);
|
|
||||||
public const string BackgroundColorOverride = "setTimeout(function f(){let h=document.head;if(!h){setTimeout(f,5);return;}let e=document.createElement('style');e.innerHTML='body,body::before{background:#1c6399!important;margin:0}';h.appendChild(e);},1)";
|
|
||||||
|
|
||||||
public static readonly ResourceLink LoadingSpinner = new ResourceLink("https://ton.twimg.com/tduck/spinner", ResourceHandlers.ForBytes(Properties.Resources.spinner, "image/apng"));
|
|
||||||
|
|
||||||
public static readonly string[] DictionaryWords = {
|
|
||||||
"tweetdeck", "TweetDeck", "tweetduck", "TweetDuck", "TD"
|
|
||||||
};
|
|
||||||
|
|
||||||
private static void DownloadTempImage(string url, ImageQuality quality, Action<string> process) {
|
|
||||||
string file = Path.Combine(BrowserCache.CacheFolder, TwitterUrls.GetImageFileName(url) ?? Path.GetRandomFileName());
|
|
||||||
|
|
||||||
if (FileUtils.FileExistsAndNotEmpty(file)) {
|
|
||||||
process(file);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
DownloadFileAuth(TwitterUrls.GetMediaLink(url, quality), file, () => {
|
|
||||||
process(file);
|
|
||||||
}, ex => {
|
|
||||||
FormMessage.Error("Image Download", "An error occurred while downloading the image: " + ex.Message, FormMessage.OK);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void ViewImage(string url, ImageQuality quality) {
|
|
||||||
DownloadTempImage(url, quality, path => {
|
|
||||||
string ext = Path.GetExtension(path);
|
|
||||||
|
|
||||||
if (ImageUrl.ValidExtensions.Contains(ext)) {
|
|
||||||
App.SystemHandler.OpenAssociatedProgram(path);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
FormMessage.Error("Image Download", "Invalid file extension " + ext, FormMessage.OK);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void CopyImage(string url, ImageQuality quality) {
|
|
||||||
DownloadTempImage(url, quality, path => {
|
|
||||||
Image image;
|
|
||||||
|
|
||||||
try {
|
|
||||||
image = Image.FromFile(path);
|
|
||||||
} catch (Exception ex) {
|
|
||||||
FormMessage.Error("Copy Image", "An error occurred while copying the image: " + ex.Message, FormMessage.OK);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ClipboardManager.SetImage(image);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void DownloadImage(string url, string username, ImageQuality quality) {
|
|
||||||
DownloadImages(new string[] { url }, username, quality);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void DownloadImages(string[] urls, string username, ImageQuality quality) {
|
|
||||||
if (urls.Length == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string firstImageLink = TwitterUrls.GetMediaLink(urls[0], quality);
|
|
||||||
int qualityIndex = firstImageLink.IndexOf(':', firstImageLink.LastIndexOf('/'));
|
|
||||||
|
|
||||||
string filename = TwitterUrls.GetImageFileName(firstImageLink);
|
|
||||||
string ext = Path.GetExtension(filename); // includes dot
|
|
||||||
|
|
||||||
using SaveFileDialog dialog = new SaveFileDialog {
|
|
||||||
AutoUpgradeEnabled = true,
|
|
||||||
OverwritePrompt = urls.Length == 1,
|
|
||||||
Title = "Save Image",
|
|
||||||
FileName = qualityIndex == -1 ? filename : $"{username} {Path.ChangeExtension(filename, null)} {firstImageLink.Substring(qualityIndex + 1)}".Trim() + ext,
|
|
||||||
Filter = (urls.Length == 1 ? "Image" : "Images") + (string.IsNullOrEmpty(ext) ? " (unknown)|*.*" : $" (*{ext})|*{ext}")
|
|
||||||
};
|
|
||||||
|
|
||||||
if (dialog.ShowDialog() == DialogResult.OK) {
|
|
||||||
static void OnFailure(Exception ex) {
|
|
||||||
FormMessage.Error("Image Download", "An error occurred while downloading the image: " + ex.Message, FormMessage.OK);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (urls.Length == 1) {
|
|
||||||
DownloadFileAuth(firstImageLink, dialog.FileName, null, OnFailure);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
string pathBase = Path.ChangeExtension(dialog.FileName, null);
|
|
||||||
string pathExt = Path.GetExtension(dialog.FileName);
|
|
||||||
|
|
||||||
for (int index = 0; index < urls.Length; index++) {
|
|
||||||
DownloadFileAuth(TwitterUrls.GetMediaLink(urls[index], quality), $"{pathBase} {index + 1}{pathExt}", null, OnFailure);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void DownloadVideo(string url, string username) {
|
|
||||||
string filename = TwitterUrls.GetFileNameFromUrl(url);
|
|
||||||
string ext = Path.GetExtension(filename);
|
|
||||||
|
|
||||||
using SaveFileDialog dialog = new SaveFileDialog {
|
|
||||||
AutoUpgradeEnabled = true,
|
|
||||||
OverwritePrompt = true,
|
|
||||||
Title = "Save Video",
|
|
||||||
FileName = string.IsNullOrEmpty(username) ? filename : $"{username} {filename}".TrimStart(),
|
|
||||||
Filter = "Video" + (string.IsNullOrEmpty(ext) ? " (unknown)|*.*" : $" (*{ext})|*{ext}")
|
|
||||||
};
|
|
||||||
|
|
||||||
if (dialog.ShowDialog() == DialogResult.OK) {
|
|
||||||
DownloadFileAuth(url, dialog.FileName, null, ex => {
|
|
||||||
FormMessage.Error("Video Download", "An error occurred while downloading the video: " + ex.Message, FormMessage.OK);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void DownloadFileAuth(string url, string target, Action onSuccess, Action<Exception> onFailure) {
|
|
||||||
const string authCookieName = "auth_token";
|
|
||||||
|
|
||||||
TaskScheduler scheduler = TaskScheduler.FromCurrentSynchronizationContext();
|
|
||||||
using ICookieManager cookies = Cef.GetGlobalCookieManager();
|
|
||||||
|
|
||||||
cookies.VisitUrlCookiesAsync(url, true).ContinueWith(task => {
|
|
||||||
string cookieStr = null;
|
|
||||||
|
|
||||||
if (task.Status == TaskStatus.RanToCompletion) {
|
|
||||||
Cookie found = task.Result?.Find(cookie => cookie.Name == authCookieName); // the list may be null
|
|
||||||
|
|
||||||
if (found != null) {
|
|
||||||
cookieStr = $"{found.Name}={found.Value}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
WebClient client = WebUtils.NewClient(BrowserUtils.UserAgentChrome);
|
|
||||||
client.Headers[HttpRequestHeader.Cookie] = cookieStr;
|
|
||||||
client.DownloadFileCompleted += WebUtils.FileDownloadCallback(target, onSuccess, onFailure);
|
|
||||||
client.DownloadFileAsync(new Uri(url), target);
|
|
||||||
}, scheduler);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -72,7 +72,7 @@ static IEnumerable<Browser> ReadBrowsersFromKey(RegistryHive hive) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
HashSet<Browser> browsers = new HashSet<Browser>();
|
var browsers = new HashSet<Browser>();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
browsers.UnionWith(ReadBrowsersFromKey(RegistryHive.CurrentUser));
|
browsers.UnionWith(ReadBrowsersFromKey(RegistryHive.CurrentUser));
|
||||||
|
19
lib/TweetLib.Browser/Base/BaseBrowser.cs
Normal file
19
lib/TweetLib.Browser/Base/BaseBrowser.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
using System;
|
||||||
|
using TweetLib.Browser.Interfaces;
|
||||||
|
|
||||||
|
namespace TweetLib.Browser.Base {
|
||||||
|
public class BaseBrowser<T> : IDisposable where T : BaseBrowser<T> {
|
||||||
|
public IScriptExecutor ScriptExecutor { get; }
|
||||||
|
|
||||||
|
protected readonly IBrowserComponent browserComponent;
|
||||||
|
|
||||||
|
protected BaseBrowser(IBrowserComponent browserComponent, Func<T, BrowserSetup> setup) {
|
||||||
|
this.browserComponent = browserComponent;
|
||||||
|
this.browserComponent.Setup(setup((T) this));
|
||||||
|
|
||||||
|
this.ScriptExecutor = browserComponent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void Dispose() {}
|
||||||
|
}
|
||||||
|
}
|
8
lib/TweetLib.Browser/Base/BrowserSetup.cs
Normal file
8
lib/TweetLib.Browser/Base/BrowserSetup.cs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
using TweetLib.Browser.Interfaces;
|
||||||
|
|
||||||
|
namespace TweetLib.Browser.Base {
|
||||||
|
public sealed class BrowserSetup {
|
||||||
|
public IContextMenuHandler? ContextMenuHandler { get; set; }
|
||||||
|
public IResourceRequestHandler? ResourceRequestHandler { get; set; }
|
||||||
|
}
|
||||||
|
}
|
9
lib/TweetLib.Browser/Contexts/Context.cs
Normal file
9
lib/TweetLib.Browser/Contexts/Context.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
namespace TweetLib.Browser.Contexts {
|
||||||
|
public sealed class Context {
|
||||||
|
public Tweet? Tweet { get; set; }
|
||||||
|
public Link? Link { get; set; }
|
||||||
|
public Media? Media { get; set; }
|
||||||
|
public Selection? Selection { get; set; }
|
||||||
|
public Notification? Notification { get; set; }
|
||||||
|
}
|
||||||
|
}
|
11
lib/TweetLib.Browser/Contexts/Link.cs
Normal file
11
lib/TweetLib.Browser/Contexts/Link.cs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
namespace TweetLib.Browser.Contexts {
|
||||||
|
public readonly struct Link {
|
||||||
|
public string Url { get; }
|
||||||
|
public string CopyUrl { get; }
|
||||||
|
|
||||||
|
public Link(string url, string copyUrl) {
|
||||||
|
Url = url;
|
||||||
|
CopyUrl = copyUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
16
lib/TweetLib.Browser/Contexts/Media.cs
Normal file
16
lib/TweetLib.Browser/Contexts/Media.cs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
namespace TweetLib.Browser.Contexts {
|
||||||
|
public readonly struct Media {
|
||||||
|
public Type MediaType { get; }
|
||||||
|
public string Url { get; }
|
||||||
|
|
||||||
|
public Media(Type mediaType, string url) {
|
||||||
|
MediaType = mediaType;
|
||||||
|
Url = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Type {
|
||||||
|
Image,
|
||||||
|
Video
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
lib/TweetLib.Browser/Contexts/Notification.cs
Normal file
11
lib/TweetLib.Browser/Contexts/Notification.cs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
namespace TweetLib.Browser.Contexts {
|
||||||
|
public struct Notification {
|
||||||
|
public string TweetUrl { get; }
|
||||||
|
public string? QuoteUrl { get; }
|
||||||
|
|
||||||
|
public Notification(string tweetUrl, string? quoteUrl) {
|
||||||
|
TweetUrl = tweetUrl;
|
||||||
|
QuoteUrl = quoteUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
lib/TweetLib.Browser/Contexts/Selection.cs
Normal file
11
lib/TweetLib.Browser/Contexts/Selection.cs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
namespace TweetLib.Browser.Contexts {
|
||||||
|
public readonly struct Selection {
|
||||||
|
public string Text { get; }
|
||||||
|
public bool Editable { get; }
|
||||||
|
|
||||||
|
public Selection(string text, bool editable) {
|
||||||
|
Text = text;
|
||||||
|
Editable = editable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
24
lib/TweetLib.Browser/Contexts/Tweet.cs
Normal file
24
lib/TweetLib.Browser/Contexts/Tweet.cs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
namespace TweetLib.Browser.Contexts {
|
||||||
|
public readonly struct Tweet {
|
||||||
|
public string Url { get; }
|
||||||
|
public string? QuoteUrl { get; }
|
||||||
|
public string? Author { get; }
|
||||||
|
public string? QuoteAuthor { get; }
|
||||||
|
public string[] ImageUrls { get; }
|
||||||
|
|
||||||
|
public string ColumnId { get; }
|
||||||
|
public string ChirpId { get; }
|
||||||
|
|
||||||
|
public string? MediaAuthor => QuoteAuthor ?? Author;
|
||||||
|
|
||||||
|
public Tweet(string url, string? quoteUrl, string? author, string? quoteAuthor, string[] imageUrls, string columnId, string chirpId) {
|
||||||
|
Url = url;
|
||||||
|
QuoteUrl = quoteUrl;
|
||||||
|
Author = author;
|
||||||
|
QuoteAuthor = quoteAuthor;
|
||||||
|
ImageUrls = imageUrls;
|
||||||
|
ColumnId = columnId;
|
||||||
|
ChirpId = chirpId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
7
lib/TweetLib.Browser/Events/BrowserLoadedEventArgs.cs
Normal file
7
lib/TweetLib.Browser/Events/BrowserLoadedEventArgs.cs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace TweetLib.Browser.Events {
|
||||||
|
public abstract class BrowserLoadedEventArgs : EventArgs {
|
||||||
|
public abstract void AddDictionaryWords(params string[] word);
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user