1
0
mirror of https://github.com/chylex/TweetDuck.git synced 2025-09-14 10:32:10 +02:00

Compare commits

..

33 Commits

Author SHA1 Message Date
6040337bb4 Release 1.21.2 2022-01-22 14:38:27 +01:00
1ced72388b Add option to hide tweets by users with NFT avatars 2022-01-22 14:27:15 +01:00
4751a948e7 Fix JSDoc issues 2022-01-22 02:00:25 +01:00
3939c2263a Move some options from the General tab to the Advanced tab 2022-01-21 13:22:14 +01:00
b0ba4595ae Remove unnecessary 'internal' keyword on classes 2022-01-21 10:55:50 +01:00
38b1057a4c Fix downloading images from DMs 2022-01-21 10:55:50 +01:00
af5d785ff2 Move IScriptExecutor.RunFunction into an extension method 2022-01-18 15:07:55 +01:00
655d334714 Fix login session export not working across different computers after a Chromium update 2022-01-18 14:35:37 +01:00
eee72959e6 Release 1.21.1 2022-01-18 11:15:35 +01:00
89b8977f7d Fix not setting custom scheme response status text correctly 2022-01-18 11:15:35 +01:00
9ede2e1ccc Move some settings from user config to system config (touch adjustment, color profile detection, system proxy)
Closes #327
2022-01-17 23:59:43 +01:00
03d1bc0f4c Remove option for 6 columns on screen in 'Edit layout & design' to reduce drop-down height, since custom values are now possible 2022-01-17 23:05:10 +01:00
cde9f66111 Add option for a custom number of visible columns in 'Edit layout & design' plugin
Closes #322
2022-01-17 22:58:53 +01:00
8149ed50e1 Reformat plugin code 2022-01-17 22:32:01 +01:00
24f5075116 Fix clicking 'Play' in 'Options - Sounds' not playing notification if notifications are muted
Closes #326
2022-01-17 21:03:46 +01:00
2a636245b4 Remove directives to expose assembly internals to now removed test projects 2022-01-17 20:51:28 +01:00
3f4844f6f6 Add numbered lines to example notification to make adjusting scroll speed easier 2022-01-17 20:46:33 +01:00
29308de3ee Fix .csproj issues 2022-01-17 07:22:44 +01:00
0d3d744d94 Move custom build script from F# to MSBuild directives and Powershell 2022-01-17 02:20:19 +01:00
d38e525fed Fix exceptions during app launch not showing error message dialogs 2022-01-16 18:28:48 +01:00
e2ac38ed0b Disable CefSharp's default parent process monitor 2022-01-16 17:49:55 +01:00
fa534f9eb3 Work on abstracting app logic and making some implementation optional 2022-01-16 17:49:55 +01:00
ec7827df24 Fix compile errors in Release configuration 2022-01-08 14:07:17 +01:00
b915488651 Add command line argument to use http:// for video playback in case WMP has issues with https:// 2022-01-08 13:54:34 +01:00
bf9a0226be Major refactor to abstract app logic into libraries 2022-01-08 13:50:21 +01:00
68582f6973 Fix not disposing frame object when handling key events 2022-01-08 05:44:24 +01:00
03f3d4d450 Fix compile error in FormBrowser for Release builds 2022-01-01 19:53:55 +01:00
115428ec50 Fix popups for Google & Apple sign-in 2022-01-01 19:53:36 +01:00
5f60852fbb Fix broken forced redirect from plain twitter.com to TweetDeck 2022-01-01 19:44:01 +01:00
a7a5723c4b Revert removal of default browser background color 2021-12-30 10:40:30 +01:00
17e42df42d Minor tweak to load error handling 2021-12-30 10:25:47 +01:00
7e692460d8 Hide VC++ redist files from project 2021-12-30 10:24:24 +01:00
f41a5946e4 Reorganize libraries and unit tests 2021-12-28 15:40:45 +01:00
292 changed files with 9043 additions and 7688 deletions

View File

@@ -0,0 +1,43 @@
using System;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using TweetDuck.Management;
using TweetLib.Core.Application;
using TweetLib.Core.Systems.Dialogs;
namespace TweetDuck.Application {
sealed class FileDialogs : IAppFileDialogs {
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);
}
});
}
}
}

View File

@@ -0,0 +1,15 @@
using TweetDuck.Dialogs;
using TweetDuck.Management;
using TweetLib.Core.Application;
namespace TweetDuck.Application {
sealed class MessageDialogs : IAppMessageDialogs {
public void Information(string caption, string text, string buttonAccept) {
FormManager.RunOnUIThreadAsync(() => FormMessage.Information(caption, text, buttonAccept));
}
public void Error(string caption, string text, string buttonAccept) {
FormManager.RunOnUIThreadAsync(() => FormMessage.Error(caption, text, buttonAccept));
}
}
}

View File

@@ -1,22 +1,78 @@
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.Dialogs;
using TweetDuck.Management;
using TweetLib.Core;
using TweetLib.Core.Application; using TweetLib.Core.Application;
using TweetLib.Core.Features.Twitter;
using TweetLib.Core.Systems.Configuration;
namespace TweetDuck.Application { namespace TweetDuck.Application {
class SystemHandler : IAppSystemHandler { sealed class SystemHandler : IAppSystemHandler {
void IAppSystemHandler.OpenAssociatedProgram(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 { try {
using (Process.Start(new ProcessStartInfo { using (Process.Start(browserPath, browserArgs)) {}
FileName = path,
ErrorDialog = true
})) {}
} catch (Exception e) { } catch (Exception e) {
Program.Reporter.HandleException("Error Opening Program", "Could not open the associated program for " + path, true, e); App.ErrorHandler.HandleException("Error Opening Browser", "Could not open the browser.", true, e);
} }
} }
void IAppSystemHandler.OpenFileExplorer(string path) { 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('/', '\\') + "\"")) {}
} }
@@ -24,5 +80,76 @@ namespace TweetDuck.Application {
using (Process.Start("explorer.exe", '"' + path.Replace('/', '\\') + '"')) {} using (Process.Start("explorer.exe", '"' + path.Replace('/', '\\') + '"')) {}
} }
} }
public IAppSystemHandler.OpenAssociatedProgramFunc OpenAssociatedProgram { get; } = path => {
try {
using (Process.Start(new ProcessStartInfo {
FileName = path,
ErrorDialog = true
})) {}
} catch (Exception e) {
App.ErrorHandler.HandleException("Error Opening Program", "Could not open the associated program for " + path, true, e);
}
};
public IAppSystemHandler.CopyImageFromFileFunc CopyImageFromFile { get; } = 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 IAppSystemHandler.CopyTextFunc CopyText { get; } = text => {
FormManager.RunOnUIThreadAsync(() => ClipboardManager.SetText(text, TextDataFormat.UnicodeText));
};
public IAppSystemHandler.SearchTextFunc SearchText { get; } = text => {
if (string.IsNullOrWhiteSpace(text)) {
return;
}
void PerformSearch() {
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) {
PerformSearch();
}
};
}
}
else {
App.SystemHandler.OpenBrowser(searchUrl + Uri.EscapeUriString(text));
}
}
FormManager.RunOnUIThreadAsync(PerformSearch);
};
} }
} }

View File

@@ -0,0 +1,125 @@
using System;
using System.IO;
using CefSharp;
using CefSharp.WinForms;
using TweetDuck.Browser.Handling;
using TweetDuck.Management;
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 {
abstract class CefBrowserComponent : IBrowserComponent {
public bool Ready { get; private set; }
public string Url => browser.Address;
public string CacheFolder => BrowserCache.CacheFolder;
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 RunScript(string identifier, string script) {
using IFrame frame = browser.GetMainFrame();
frame.ExecuteJavaScriptAsync(script, identifier, 1);
}
public void DownloadFile(string url, string path, Action onSuccess, Action<Exception> onError) {
Cef.UIThreadTaskFactory.StartNew(() => {
try {
using IFrame frame = browser.GetMainFrame();
var request = frame.CreateRequest(false);
request.Method = "GET";
request.Url = url;
request.Flags = UrlRequestFlags.AllowStoredCredentials;
request.SetReferrer(Url, ReferrerPolicy.Default);
var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read);
var client = new DownloadRequestClient(fileStream, onSuccess, onError);
frame.CreateUrlRequest(request, client);
} catch (Exception e) {
onError?.Invoke(e);
}
});
}
}
}

View File

@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using CefSharp;
namespace TweetDuck.Browser.Adapters {
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();
}
}
}

View 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 {
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;
}
}
}

View 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;
}
}
}

View File

@@ -0,0 +1,42 @@
using System;
using System.Collections.Concurrent;
using System.Text;
using CefSharp;
namespace TweetDuck.Browser.Adapters {
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 _);
}
}
}

View 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
};
}
}
}

View File

@@ -0,0 +1,29 @@
using System;
using CefSharp;
using CefSharp.WinForms;
using TweetLib.Browser.Interfaces;
namespace TweetDuck.Browser.Adapters {
sealed class CefSchemeHandlerFactory : ISchemeHandlerFactory {
public static void Register(CefSettings settings, ICustomSchemeHandler handler) {
settings.RegisterScheme(new CefCustomScheme {
SchemeName = handler.Protocol,
IsStandard = false,
IsSecure = true,
IsCorsEnabled = true,
IsCSPBypassing = true,
SchemeHandlerFactory = new CefSchemeHandlerFactory(handler)
});
}
private readonly ICustomSchemeHandler handler;
private CefSchemeHandlerFactory(ICustomSchemeHandler 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)?.Visit(CefSchemeResourceVisitor.Instance) : null;
}
}
}

View File

@@ -0,0 +1,38 @@
using System;
using System.IO;
using System.Net;
using CefSharp;
using TweetLib.Browser.Interfaces;
using TweetLib.Browser.Request;
namespace TweetDuck.Browser.Adapters {
sealed class CefSchemeResourceVisitor : ISchemeResourceVisitor<IResourceHandler> {
public static CefSchemeResourceVisitor Instance { get; } = new CefSchemeResourceVisitor();
private static readonly SchemeResource.Status FileIsEmpty = new SchemeResource.Status(HttpStatusCode.NoContent, "File is empty.");
private CefSchemeResourceVisitor() {}
public IResourceHandler Status(SchemeResource.Status status) {
var handler = CreateHandler(Array.Empty<byte>());
handler.StatusCode = (int) status.Code;
handler.StatusText = status.Message;
return handler;
}
public IResourceHandler File(SchemeResource.File file) {
byte[] contents = file.Contents;
if (contents.Length == 0) {
return Status(FileIsEmpty); // FromByteArray crashes CEF internals with no contents
}
var handler = CreateHandler(contents);
handler.MimeType = Cef.GetMimeType(file.Extension);
return handler;
}
private static ResourceHandler CreateHandler(byte[] bytes) {
return ResourceHandler.FromStream(new MemoryStream(bytes), autoDisposeStream: true);
}
}
}

View File

@@ -1,78 +0,0 @@
using System.Collections.Generic;
using System.IO;
using CefSharp;
using TweetDuck.Utils;
using TweetLib.Core.Browser;
using TweetLib.Core.Utils;
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 = FileUtils.ReadFileOrNull(Path.Combine(Program.ResourcesPath, "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;
}
}
}

View File

@@ -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.Core.Utils;
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
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -1,166 +0,0 @@
using System;
using CefSharp;
using TweetLib.Core.Utils;
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);
}
}
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -1,42 +0,0 @@
using System.Drawing;
using System.Windows.Forms;
using TweetDuck.Controls;
using TweetLib.Core.Serialization.Converters;
using TweetLib.Core.Utils;
namespace TweetDuck.Browser.Data {
sealed class WindowState {
private Rectangle rect;
private bool isMaximized;
public void Save(Form form) {
rect = form.WindowState == FormWindowState.Normal ? form.DesktopBounds : form.RestoreBounds;
isMaximized = form.WindowState == FormWindowState.Maximized;
}
public void Restore(Form form, bool firstTimeFullscreen) {
if (rect != Rectangle.Empty) {
form.DesktopBounds = rect;
form.WindowState = isMaximized ? FormWindowState.Maximized : FormWindowState.Normal;
}
if ((rect == Rectangle.Empty && firstTimeFullscreen) || form.IsFullyOutsideView()) {
form.DesktopBounds = Screen.PrimaryScreen.WorkingArea;
form.WindowState = FormWindowState.Maximized;
Save(form);
}
}
public static readonly SingleTypeConverter<WindowState> Converter = new SingleTypeConverter<WindowState> {
ConvertToString = value => $"{(value.isMaximized ? 'M' : '_')}{value.rect.X} {value.rect.Y} {value.rect.Width} {value.rect.Height}",
ConvertToObject = value => {
int[] elements = StringUtils.ParseInts(value.Substring(1), ' ');
return new WindowState {
rect = new Rectangle(elements[0], elements[1], elements[2], elements[3]),
isMaximized = value[0] == 'M'
};
}
};
}
}

View File

@@ -27,7 +27,7 @@
// //
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;

View File

@@ -2,12 +2,11 @@
using System.Diagnostics; using System.Diagnostics;
using System.Drawing; using System.Drawing;
using System.IO; using System.IO;
using System.Text.RegularExpressions;
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,18 +14,18 @@ using TweetDuck.Controls;
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.Updates; using TweetDuck.Updates;
using TweetDuck.Utils; using TweetDuck.Utils;
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.Configuration;
using TweetLib.Core.Systems.Updates; using TweetLib.Core.Systems.Updates;
#if DEBUG
using TweetDuck.Resources;
#endif
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 {
@@ -47,18 +46,18 @@ namespace TweetDuck.Browser {
} }
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 ResourceCache resourceCache;
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 readonly uint windowRestoreMessage;
private bool isLoaded; private bool isLoaded;
private FormWindowState prevState; private FormWindowState prevState;
@@ -66,45 +65,42 @@ namespace TweetDuck.Browser {
private TweetScreenshotManager notificationScreenshotManager; private TweetScreenshotManager notificationScreenshotManager;
private VideoPlayer videoPlayer; private VideoPlayer videoPlayer;
public FormBrowser(ResourceProvider resourceProvider, PluginSchemeFactory pluginScheme) { public FormBrowser(ResourceCache resourceCache, PluginManager pluginManager, IUpdateCheckClient updateCheckClient, uint windowRestoreMessage) {
InitializeComponent(); InitializeComponent();
Text = Program.BrandName; Text = Program.BrandName;
this.resourceProvider = resourceProvider; this.resourceCache = resourceCache;
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);
this.windowRestoreMessage = windowRestoreMessage;
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
Config.MuteToggled += Config_MuteToggled;
Config.TrayBehaviorChanged += Config_TrayBehaviorChanged;
Disposed += (sender, args) => { Disposed += (sender, args) => {
Config.MuteToggled -= Config_MuteToggled; Config.MuteToggled -= Config_MuteToggled;
Config.TrayBehaviorChanged -= Config_TrayBehaviorChanged; Config.TrayBehaviorChanged -= Config_TrayBehaviorChanged;
browser.Dispose(); browser.Dispose();
}; };
Config.MuteToggled += Config_MuteToggled;
this.trayIcon.ClickRestore += trayIcon_ClickRestore; this.trayIcon.ClickRestore += trayIcon_ClickRestore;
this.trayIcon.ClickClose += trayIcon_ClickClose; this.trayIcon.ClickClose += trayIcon_ClickClose;
Config.TrayBehaviorChanged += Config_TrayBehaviorChanged;
UpdateTray(); UpdateTray();
@@ -234,7 +230,8 @@ namespace TweetDuck.Browser {
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();
} }
} }
@@ -257,44 +254,8 @@ namespace TweetDuck.Browser {
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);
}
if (isLoaded) {
browser.ReloadToTweetDeck();
}
}
private void plugins_Executed(object sender, PluginErrorEventArgs e) {
if (e.HasErrors) {
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) {
e.Result.Handle(update => {
string tag = update.VersionTag;
if (tag != Program.VersionTag && tag != Config.DismissedUpdate) {
update.BeginSilentDownload();
browser.ShowUpdateNotification(tag, update.ReleaseNotes);
}
else {
updates.StartTimer();
}
}, ex => {
if (!ignoreUpdateCheckError) {
Program.Reporter.HandleException("Update Check Error", "An error occurred while checking for updates.", true, ex);
updates.StartTimer();
}
});
ignoreUpdateCheckError = true;
}
private void updateBridge_UpdateAccepted(object sender, UpdateInfo update) {
FormManager.CloseAllDialogs(); FormManager.CloseAllDialogs();
if (!string.IsNullOrEmpty(Config.DismissedUpdate)) { if (!string.IsNullOrEmpty(Config.DismissedUpdate)) {
@@ -310,7 +271,7 @@ namespace TweetDuck.Browser {
ForceClose(); 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)) { 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); App.SystemHandler.OpenBrowser(Program.Website);
ForceClose(); ForceClose();
} }
else { else {
@@ -340,15 +301,18 @@ namespace TweetDuck.Browser {
downloadForm.Show(); downloadForm.Show();
} }
});
} }
private void updateBridge_UpdateDismissed(object sender, UpdateInfo update) { private void updateInteractionManager_UpdateDismissed(object sender, UpdateInfo update) {
this.InvokeAsyncSafe(() => {
Config.DismissedUpdate = update.VersionTag; Config.DismissedUpdate = update.VersionTag;
Config.Save(); Config.Save();
});
} }
protected override void WndProc(ref Message m) { protected override void WndProc(ref Message m) {
if (isLoaded && m.Msg == Program.WindowRestoreMessage) { if (isLoaded && m.Msg == windowRestoreMessage) {
using Process me = Process.GetCurrentProcess(); using Process me = Process.GetCurrentProcess();
if (me.Id == m.WParam.ToInt32()) { if (me.Id == m.WParam.ToInt32()) {
@@ -359,11 +323,11 @@ namespace TweetDuck.Browser {
} }
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;
@@ -374,10 +338,6 @@ namespace TweetDuck.Browser {
// bridge methods // bridge methods
public void OnModulesLoaded(string moduleNamespace) {
browser.OnModulesLoaded(moduleNamespace);
}
public void PauseNotification() { public void PauseNotification() {
notification.PauseNotification(); notification.PauseNotification();
} }
@@ -386,51 +346,26 @@ namespace TweetDuck.Browser {
notification.ResumeNotification(); notification.ResumeNotification();
} }
public void ReinjectCustomCSS(string css) {
browser.ReinjectCustomCSS(css);
}
public void ReloadToTweetDeck() { public void ReloadToTweetDeck() {
#if DEBUG #if DEBUG
ResourceHotSwap.Run(); Resources.ResourceHotSwap.Run();
resourceProvider.ClearCache(); resourceCache.ClearCache();
#else #else
if (ModifierKeys.HasFlag(Keys.Shift)) { if (ModifierKeys.HasFlag(Keys.Shift)) {
resourceProvider.ClearCache(); resourceCache.ClearCache();
} }
#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();
} }
// callback handlers // callback handlers
public void OnIntroductionClosed(bool showGuide) { private void OnIntroductionClosed(bool showGuide) {
if (Config.FirstRun) { if (Config.FirstRun) {
Config.FirstRun = false; Config.FirstRun = false;
Config.Save(); Config.Save();
@@ -441,7 +376,7 @@ namespace TweetDuck.Browser {
} }
} }
public void OpenContextMenu() { private void OpenContextMenu() {
contextMenu.Show(this, PointToClient(Cursor.Position)); contextMenu.Show(this, PointToClient(Cursor.Position));
} }
@@ -453,7 +388,7 @@ namespace TweetDuck.Browser {
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) {
@@ -474,7 +409,7 @@ namespace TweetDuck.Browser {
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;
@@ -497,7 +432,7 @@ namespace TweetDuck.Browser {
} }
} }
public void OpenProfileImport() { private void OpenProfileImport() {
FormManager.TryFind<FormSettings>()?.Close(); FormManager.TryFind<FormSettings>()?.Close();
using DialogSettingsManage dialog = new DialogSettingsManage(plugins, true); using DialogSettingsManage dialog = new DialogSettingsManage(plugins, true);
@@ -509,15 +444,25 @@ namespace TweetDuck.Browser {
} }
} }
public void OnTweetNotification() { // may be called multiple times, once for each type of notification private void ShowDesktopNotification(DesktopNotification notification) {
this.notification.ShowNotification(notification);
}
private 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);
}
private void PlayVideo(string videoUrl, string tweetUrl, string username, IJavascriptCallback callShowOverlay) {
if (Arguments.HasFlag(Arguments.ArgHttpVideo)) {
videoUrl = Regex.Replace(videoUrl, "^https://", "http://");
}
public void PlayVideo(string videoUrl, string tweetUrl, string username, IJavascriptCallback callShowOverlay) {
string playerPath = Config.VideoPlayerPath; string playerPath = Config.VideoPlayerPath;
if (playerPath == null || !File.Exists(playerPath)) { if (playerPath == null || !File.Exists(playerPath)) {
@@ -540,42 +485,33 @@ namespace TweetDuck.Browser {
try { try {
using (Process.Start(playerPath, playerArgs)) {} using (Process.Start(playerPath, playerArgs)) {}
} catch (Exception e) { } catch (Exception e) {
Program.Reporter.HandleException("Error Opening Video Player", "Could not open the video player.", true, e); App.ErrorHandler.HandleException("Error Opening Video Player", "Could not open the video player.", true, e);
} }
} }
} }
public void StopVideo() { private 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) { private void OnTweetScreenshotReady(string html, int width) {
notificationScreenshotManager ??= new TweetScreenshotManager(this, plugins); notificationScreenshotManager ??= new TweetScreenshotManager(this, plugins);
notificationScreenshotManager.Trigger(html, width); notificationScreenshotManager.Trigger(html, width);
} }
public void DisplayTooltip(string text) { private void DisplayTooltip(string text) {
if (string.IsNullOrEmpty(text)) { if (string.IsNullOrEmpty(text)) {
toolTip.Hide(this); toolTip.Hide(this);
} }
@@ -585,5 +521,88 @@ namespace TweetDuck.Browser {
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;
}
private 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);
}
}
} }
} }

View File

@@ -3,7 +3,7 @@ using System.Threading.Tasks;
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);

View File

@@ -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.Core.Utils;
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);
if (!AllowedCefCommands.Contains(command) && !(command >= CefMenuCommand.CustomFirst && command <= CefMenuCommand.CustomLast)) {
model.RemoveAt(i);
} }
else {
Context = CurrentInfo.Create(parameters);
} }
if (parameters.TypeFlags.HasFlag(ContextMenuType.Selection) && !parameters.TypeFlags.HasFlag(ContextMenuType.Editable)) { for (int i = model.Count - 2; i >= 0; i--) {
model.AddItem(MenuSearchInBrowser, "Search in browser"); if (model.GetTypeAt(i) == MenuItemType.Separator && model.GetTypeAt(i + 1) == MenuItemType.Separator) {
model.AddSeparator(); model.RemoveAt(i);
model.AddItem(MenuReadApplyROT13, "Apply ROT13"); }
model.AddSeparator();
} }
static string TextOpen(string name) => "Open " + name + " in browser"; if (model.Count > 0 && model.GetTypeAt(0) == MenuItemType.Separator) {
static string TextCopy(string name) => "Copy " + name + " address"; model.RemoveAt(0);
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"));
} }
model.AddSeparator(); AddSeparator(model);
} handler.Show(new CefContextMenuModel(model, actionRegistry), CreateContext(parameters));
RemoveSeparatorIfLast(model);
if (Context.Types.HasFlag(ContextInfo.ContextType.Video)) {
model.AddItem(MenuOpenMediaUrl, TextOpen("video"));
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) {
model.AddItem(MenuSaveTweetImages, TextSave("all images"));
}
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)) {
switch (commandId) {
case MenuOpenLinkUrl:
OpenBrowser(control, Context.LinkUrl);
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; return true;
}
case MenuSearchInBrowser: if (commandId == MenuOpenDevTools) {
string query = parameters.SelectionText;
control.InvokeAsyncSafe(() => BrowserUtils.OpenExternalSearch(query));
DeselectAll(frame);
break;
case MenuOpenDevTools:
browserControl.OpenDevToolsCustom(new Point(parameters.XCoord, parameters.YCoord)); browserControl.OpenDevToolsCustom(new Point(parameters.XCoord, parameters.YCoord));
break; return true;
} }
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);

View File

@@ -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.Controls; using TweetDuck.Controls;
using TweetLib.Browser.Contexts;
using TweetLib.Core.Features.TweetDeck;
using TweetLib.Core.Features.Twitter; using TweetLib.Core.Features.Twitter;
using TweetLib.Core.Systems.Configuration;
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 @@ namespace TweetDuck.Browser.Handling {
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 @@ namespace TweetDuck.Browser.Handling {
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 @@ namespace TweetDuck.Browser.Handling {
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 @@ namespace TweetDuck.Browser.Handling {
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) {

View File

@@ -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);
} }

View File

@@ -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) {

View File

@@ -1,28 +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 && !browser.FocusedFrame.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);
}
} }
} }

View File

@@ -1,21 +1,23 @@
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?"); return url.StartsWithOrdinal("https://twitter.com/teams/authorize?") ||
url.StartsWithOrdinal("https://accounts.google.com/") ||
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:
@@ -25,7 +27,7 @@ namespace TweetDuck.Browser.Handling.General {
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) {

View File

@@ -0,0 +1,62 @@
using System;
using System.IO;
using CefSharp;
namespace TweetDuck.Browser.Handling {
sealed class DownloadRequestClient : UrlRequestClient {
private readonly FileStream fileStream;
private readonly Action onSuccess;
private readonly Action<Exception> onError;
private bool hasFailed;
public DownloadRequestClient(FileStream fileStream, Action onSuccess, Action<Exception> onError) {
this.fileStream = fileStream;
this.onSuccess = onSuccess;
this.onError = onError;
}
protected override bool GetAuthCredentials(bool isProxy, string host, int port, string realm, string scheme, IAuthCallback callback) {
onError?.Invoke(new Exception("This URL requires authentication."));
fileStream.Dispose();
hasFailed = true;
return false;
}
protected override void OnDownloadData(IUrlRequest request, Stream data) {
if (hasFailed) {
return;
}
try {
data.CopyTo(fileStream);
} catch (Exception e) {
fileStream.Dispose();
onError?.Invoke(e);
hasFailed = true;
}
}
protected override void OnRequestComplete(IUrlRequest request) {
if (hasFailed) {
return;
}
bool isEmpty = fileStream.Position == 0;
fileStream.Dispose();
var status = request.RequestStatus;
if (status == UrlRequestStatus.Failed) {
onError?.Invoke(new Exception("Unknown error."));
}
else if (status == UrlRequestStatus.Success) {
if (isEmpty) {
onError?.Invoke(new Exception("File is empty."));
return;
}
onSuccess?.Invoke();
}
}
}
}

View File

@@ -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 @@ namespace TweetDuck.Browser.Handling {
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

View File

@@ -1,11 +1,13 @@
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.Core.Utils; using TweetLib.Core;
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) {
@@ -45,17 +47,22 @@ namespace TweetDuck.Browser.Handling.General {
return new string[] { type }; return new string[] { type };
} }
switch (type) { string[] extensions = type switch {
case "image/jpeg": return new string[] { ".jpg", ".jpeg" }; "image/jpeg" => new string[] { ".jpg", ".jpeg" },
case "image/png": return new string[] { ".png" }; "image/png" => new string[] { ".png" },
case "image/gif": return new string[] { ".gif" }; "image/gif" => new string[] { ".gif" },
case "image/webp": return new string[] { ".webp" }; "image/webp" => new string[] { ".webp" },
case "video/mp4": return new string[] { ".mp4" }; "video/mp4" => new string[] { ".mp4" },
case "video/quicktime": return new string[] { ".mov", ".qt" }; "video/quicktime" => new string[] { ".mov", ".qt" },
_ => StringUtils.EmptyArray
};
if (extensions.Length == 0) {
App.Logger.Warn("Unknown file type: " + type);
Debugger.Break();
} }
System.Diagnostics.Debugger.Break(); return extensions;
return StringUtils.EmptyArray;
} }
} }
} }

View File

@@ -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() {}
}
}

View File

@@ -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 @@ namespace TweetDuck.Browser.Handling.General {
} }
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;

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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 @@ namespace TweetDuck.Browser.Handling {
} }
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) {

View File

@@ -1,101 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
using CefSharp;
using TweetLib.Core.Browser;
using IOFile = System.IO.File;
namespace TweetDuck.Browser.Handling {
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);
}
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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.Core.Utils;
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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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 @@ namespace TweetDuck.Browser.Handling.Filters {
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 @@ namespace TweetDuck.Browser.Handling.Filters {
} }
} }
public abstract bool InitFilter(); public void Dispose() {}
protected abstract string ProcessResponse(string text);
public abstract void Dispose();
} }
} }

View File

@@ -1,35 +1,21 @@
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;
using TweetLib.Core.Systems.Configuration;
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 {
@@ -74,7 +60,9 @@ namespace TweetDuck.Browser.Notification {
protected virtual bool CanDragWindow => true; protected virtual bool CanDragWindow => true;
public new Point Location { public new Point Location {
get { return base.Location; } get {
return base.Location;
}
set { set {
Visible = (base.Location = value) != ControlExtensions.InvisibleLocation; Visible = (base.Location = value) != ControlExtensions.InvisibleLocation;
@@ -100,6 +88,9 @@ namespace TweetDuck.Browser.Notification {
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 +103,33 @@ namespace TweetDuck.Browser.Notification {
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 +139,25 @@ namespace TweetDuck.Browser.Notification {
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 +184,7 @@ namespace TweetDuck.Browser.Notification {
// 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 +205,9 @@ namespace TweetDuck.Browser.Notification {
} }
} }
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 +223,8 @@ namespace TweetDuck.Browser.Notification {
} }
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();
} }
} }

View File

@@ -1,14 +1,18 @@
using System; using System;
using System.IO;
using System.Windows.Forms; using System.Windows.Forms;
using CefSharp;
using TweetDuck.Controls; using TweetDuck.Controls;
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.Utils; 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;
@@ -25,29 +29,17 @@ namespace TweetDuck.Browser.Notification.Example {
} }
} }
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) => {
string exampleTweetHTML = FileUtils.ReadFileOrNull(Path.Combine(Program.ResourcesPath, "notification/example/example.html"))?.Replace("{avatar}", AppLogo.Url) ?? string.Empty;
#if DEBUG
exampleTweetHTML = exampleTweetHTML.Replace("</p>", @"</p><div style='margin-top:256px'>Scrollbar test padding...</div>");
#endif
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); Ready?.Invoke(this, EventArgs.Empty);
browser.LoadingStateChanged -= browser_LoadingStateChanged; };
}
string exampleTweetHTML = ResourceUtils.ReadFileOrNull("notification/example/example.html") ?? string.Empty;
exampleNotification = new DesktopNotification(string.Empty, string.Empty, "Home", exampleTweetHTML, 176, string.Empty, string.Empty);
} }
public override void HideNotification() { public override void HideNotification() {

View File

@@ -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;
@@ -31,7 +61,9 @@ namespace TweetDuck.Browser.Notification {
private int? prevFontSize; private int? prevFontSize;
public virtual bool RequiresResize { public virtual bool RequiresResize {
get { return !prevDisplayTimer.HasValue || !prevFontSize.HasValue || prevDisplayTimer != Config.DisplayNotificationTimer || prevFontSize != FontSizeLevel; } get {
return !prevDisplayTimer.HasValue || !prevFontSize.HasValue || prevDisplayTimer != Config.DisplayNotificationTimer || prevFontSize != FontSizeLevel;
}
set { set {
if (value) { if (value) {
@@ -59,29 +91,21 @@ namespace TweetDuck.Browser.Notification {
}; };
} }
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 +137,7 @@ namespace TweetDuck.Browser.Notification {
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 +182,7 @@ namespace TweetDuck.Browser.Notification {
} }
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 +260,6 @@ namespace TweetDuck.Browser.Notification {
} }
} }
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 +295,24 @@ namespace TweetDuck.Browser.Notification {
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;
}
}
} }
} }

View File

@@ -3,11 +3,17 @@ using System.Collections.Generic;
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 @@ namespace TweetDuck.Browser.Notification {
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;

View File

@@ -4,52 +4,45 @@ using System.IO;
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.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.Utils; using TweetLib.Core.Resources;
namespace TweetDuck.Browser.Notification.Screenshot { namespace TweetDuck.Browser.Notification.Screenshot {
sealed class FormNotificationScreenshotable : FormNotificationBase { sealed class FormNotificationScreenshotable : FormNotificationBase {
protected override bool CanDragWindow => false; private static NotificationBrowser CreateBrowserImpl( IBrowserComponent browserComponent, PluginManager pluginManager) {
return new NotificationBrowser.Screenshot(browserComponent, pluginManager.NotificationInjections);
private readonly PluginManager plugins;
private int height;
public FormNotificationScreenshotable(Action callback, FormBrowser owner, PluginManager pluginManager, string html, int width) : base(owner, false) {
this.plugins = pluginManager;
int realWidth = BrowserUtils.Scale(width, DpiScale);
browser.RegisterJsBridge("$TD_NotificationScreenshot", new ScreenshotBridge(this, SetScreenshotHeight, callback));
browser.LoadingStateChanged += (sender, args) => {
if (args.IsLoading) {
return;
} }
string script = FileUtils.ReadFileOrNull(Path.Combine(Program.ResourcesPath, "notification/screenshot/screenshot.js")); protected override bool CanDragWindow => false;
private int height;
public FormNotificationScreenshotable(Action callback, FormBrowser owner, PluginManager pluginManager, string html, int width) : base(owner, (_, browserComponent) => CreateBrowserImpl(browserComponent, pluginManager)) {
int realWidth = BrowserUtils.Scale(width, DpiScale);
browserComponent.AttachBridgeObject("$TD_NotificationScreenshot", new ScreenshotBridge(this, SetScreenshotHeight, callback));
browserComponent.BrowserLoaded += (sender, args) => {
string script = ResourceUtils.ReadFileOrNull("notification/screenshot/screenshot.js");
if (script == null) { if (script == null) {
this.InvokeAsyncSafe(callback); this.InvokeAsyncSafe(callback);
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);
} }

View File

@@ -11,6 +11,7 @@ using System.Drawing;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Controls; using TweetDuck.Controls;
using TweetLib.Core;
using TweetLib.Core.Features.Plugins; using TweetLib.Core.Features.Plugins;
#if GEN_SCREENSHOT_FRAMES #if GEN_SCREENSHOT_FRAMES
using System.Drawing.Imaging; using System.Drawing.Imaging;
@@ -92,7 +93,7 @@ namespace TweetDuck.Browser.Notification.Screenshot {
private void HandleResult(Task<Image> task) { private void HandleResult(Task<Image> task) {
if (task.IsFaulted) { if (task.IsFaulted) {
Program.Reporter.HandleException("Screenshot Failed", "An error occurred while taking a screenshot.", true, task.Exception!.InnerException); App.ErrorHandler.HandleException("Screenshot Failed", "An error occurred while taking a screenshot.", true, task.Exception!.InnerException);
} }
else if (task.IsCompleted) { else if (task.IsCompleted) {
Clipboard.SetImage(task.Result); Clipboard.SetImage(task.Result);

View File

@@ -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 @@ namespace TweetDuck.Browser.Notification {
}; };
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>();

View File

@@ -2,6 +2,7 @@
using System.ComponentModel; using System.ComponentModel;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Configuration; using TweetDuck.Configuration;
using TweetLib.Core.Systems.Configuration;
using Res = TweetDuck.Properties.Resources; using Res = TweetDuck.Properties.Resources;
namespace TweetDuck.Browser { namespace TweetDuck.Browser {
@@ -20,7 +21,9 @@ namespace TweetDuck.Browser {
public event EventHandler ClickClose; public event EventHandler ClickClose;
public bool Visible { public bool Visible {
get { return notifyIcon.Visible; } get {
return notifyIcon.Visible;
}
set { set {
notifyIcon.Visible = value; notifyIcon.Visible = value;
@@ -30,7 +33,9 @@ namespace TweetDuck.Browser {
} }
public bool HasNotifications { public bool HasNotifications {
get { return hasNotifications; } get {
return hasNotifications;
}
set { set {
if (hasNotifications != value) { if (hasNotifications != value) {
@@ -74,7 +79,7 @@ namespace TweetDuck.Browser {
private void UpdateIcon() { private void UpdateIcon() {
if (Visible) { if (Visible) {
notifyIcon.Icon = hasNotifications ? Res.icon_tray_new : Config.MuteNotifications ? Res.icon_tray_muted : Res.icon_tray; notifyIcon.Icon = HasNotifications ? Res.icon_tray_new : Config.MuteNotifications ? Res.icon_tray_muted : Res.icon_tray;
} }
} }

View File

@@ -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.Core.Utils; 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,51 +37,62 @@ namespace TweetDuck.Browser {
} }
} }
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; // ReSharper disable once PossiblyImpureMethodCallOnReadonlyVariable
this.browser.FrameLoadStart += browser_FrameLoadStart; this.browser.BrowserSettings.BackgroundColor = (uint) BackgroundColor.ToArgb();
this.browser.FrameLoadEnd += browser_FrameLoadEnd;
this.browser.LoadError += browser_LoadError;
this.browser.RegisterJsBridge("$TD", tdBridge); var extraContext = new TweetDeckExtraContext();
this.browser.RegisterJsBridge("$TDU", updateBridge); var resourceHandlerRegistry = new CefResourceHandlerRegistry();
var soundNotificationHandler = new SoundNotification(resourceHandlerRegistry);
this.browser.Dock = DockStyle.None; this.browserComponent = new ComponentImpl(browser, owner, extraContext, resourceHandlerRegistry);
this.browser.Location = ControlExtensions.InvisibleLocation; this.browserImpl = new TweetDeckBrowserImpl(browserComponent, tweetDeckInterface, extraContext, soundNotificationHandler, pluginManager, updateChecker);
this.browser.SetupZoomEvents();
owner.Controls.Add(browser); if (Arguments.HasFlag(Arguments.ArgIgnoreGDPR)) {
plugins.Register(PluginEnvironment.Browser, new PluginDispatcher(browser, TwitterUrls.IsTweetDeck)); browserComponent.PageLoadEnd += (sender, args) => {
if (TwitterUrls.IsTweetDeck(args.Url)) {
Config.MuteToggled += Config_MuteToggled; browserComponent.RunScript("gen:gdpr", "TD.storage.Account.prototype.requiresConsent = function() { return false; }");
Config.SoundNotificationChanged += Config_SoundNotificationInfoChanged; }
};
} }
// setup and management owner.Controls.Add(browser);
}
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) {
@@ -95,178 +100,33 @@ namespace TweetDuck.Browser {
} }
} }
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)); public void SaveVideo(string url, string username) {
browser.LoadingStateChanged -= browser_LoadingStateChanged; browserImpl.FileDownloadManager.SaveVideo(url, username);
} }
}
private void browser_FrameLoadStart(object sender, FrameLoadStartEventArgs e) {
IFrame frame = e.Frame;
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://", 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()"); browserComponent.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();
} }
} }
} }

View File

@@ -1,5 +1,5 @@
using System; using System;
using TweetLib.Core.Collections; using TweetLib.Utils.Collections;
namespace TweetDuck.Configuration { namespace TweetDuck.Configuration {
static class Arguments { static class Arguments {
@@ -7,12 +7,11 @@ namespace TweetDuck.Configuration {
public const string ArgDataFolder = "-datafolder"; public const string ArgDataFolder = "-datafolder";
public const string ArgLogging = "-log"; public const string ArgLogging = "-log";
public const string ArgIgnoreGDPR = "-nogdpr"; public const string ArgIgnoreGDPR = "-nogdpr";
public const string ArgHttpVideo = "-httpvideo";
public const string ArgFreeze = "-freeze"; public const string ArgFreeze = "-freeze";
// internal args // internal args
public const string ArgRestart = "-restart"; public const string ArgRestart = "-restart";
public const string ArgImportCookies = "-importcookies";
public const string ArgDeleteCookies = "-deletecookies";
public const string ArgUpdated = "-updated"; public const string ArgUpdated = "-updated";
// class data and methods // class data and methods
@@ -29,8 +28,6 @@ namespace TweetDuck.Configuration {
public static CommandLineArgs GetCurrentClean() { public static CommandLineArgs GetCurrentClean() {
CommandLineArgs args = Current.Clone(); CommandLineArgs args = Current.Clone();
args.RemoveFlag(ArgRestart); args.RemoveFlag(ArgRestart);
args.RemoveFlag(ArgImportCookies);
args.RemoveFlag(ArgDeleteCookies);
args.RemoveFlag(ArgUpdated); args.RemoveFlag(ArgUpdated);
return args; return args;
} }

View File

@@ -1,82 +0,0 @@
using System;
using System.Drawing;
using TweetDuck.Browser.Data;
using TweetLib.Core.Features.Plugins.Config;
using TweetLib.Core.Serialization.Converters;
using TweetLib.Core.Systems.Configuration;
using TweetLib.Core.Utils;
namespace TweetDuck.Configuration {
sealed class ConfigManager : IConfigManager {
public UserConfig User { get; }
public SystemConfig System { get; }
public PluginConfig Plugins { get; }
public event EventHandler ProgramRestartRequested;
private readonly FileConfigInstance<UserConfig> infoUser;
private readonly FileConfigInstance<SystemConfig> infoSystem;
private readonly PluginConfigInstance<PluginConfig> infoPlugins;
private readonly IConfigInstance<BaseConfig>[] infoList;
public ConfigManager() {
User = new UserConfig(this);
System = new SystemConfig(this);
Plugins = new PluginConfig(this);
infoList = new IConfigInstance<BaseConfig>[] {
infoUser = new FileConfigInstance<UserConfig>(Program.UserConfigFilePath, User, "program options"),
infoSystem = new FileConfigInstance<SystemConfig>(Program.SystemConfigFilePath, System, "system options"),
infoPlugins = new PluginConfigInstance<PluginConfig>(Program.PluginConfigFilePath, Plugins)
};
// TODO refactor further
infoUser.Serializer.RegisterTypeConverter(typeof(WindowState), WindowState.Converter);
infoUser.Serializer.RegisterTypeConverter(typeof(Point), new SingleTypeConverter<Point> {
ConvertToString = value => $"{value.X} {value.Y}",
ConvertToObject = value => {
int[] elements = StringUtils.ParseInts(value, ' ');
return new Point(elements[0], elements[1]);
}
});
infoUser.Serializer.RegisterTypeConverter(typeof(Size), new SingleTypeConverter<Size> {
ConvertToString = value => $"{value.Width} {value.Height}",
ConvertToObject = value => {
int[] elements = StringUtils.ParseInts(value, ' ');
return new Size(elements[0], elements[1]);
}
});
}
public void LoadAll() {
infoUser.Load();
infoSystem.Load();
infoPlugins.Load();
}
public void SaveAll() {
infoUser.Save();
infoSystem.Save();
infoPlugins.Save();
}
public void ReloadAll() {
infoUser.Reload();
infoSystem.Reload();
infoPlugins.Reload();
}
void IConfigManager.TriggerProgramRestartRequested() {
ProgramRestartRequested?.Invoke(this, EventArgs.Empty);
}
IConfigInstance<BaseConfig> IConfigManager.GetInstanceInfo(BaseConfig instance) {
Type instanceType = instance.GetType();
return Array.Find(infoList, info => info.Instance.GetType() == instanceType); // TODO handle null
}
}
}

View File

@@ -1,51 +0,0 @@
using System;
using System.Collections.Generic;
using TweetLib.Core.Features.Plugins;
using TweetLib.Core.Features.Plugins.Config;
using TweetLib.Core.Features.Plugins.Events;
using TweetLib.Core.Systems.Configuration;
namespace TweetDuck.Configuration {
sealed class PluginConfig : BaseConfig, IPluginConfig {
private static readonly string[] DefaultDisabled = {
"official/clear-columns",
"official/reply-account"
};
// CONFIGURATION DATA
private readonly HashSet<string> disabled = new HashSet<string>(DefaultDisabled);
// EVENTS
public event EventHandler<PluginChangedStateEventArgs> PluginChangedState;
// END OF CONFIG
public PluginConfig(IConfigManager configManager) : base(configManager) {}
protected override BaseConfig ConstructWithDefaults(IConfigManager configManager) {
return new PluginConfig(configManager);
}
// INTERFACE IMPLEMENTATION
IEnumerable<string> IPluginConfig.DisabledPlugins => disabled;
void IPluginConfig.Reset(IEnumerable<string> newDisabledPlugins) {
disabled.Clear();
disabled.UnionWith(newDisabledPlugins);
}
public void SetEnabled(Plugin plugin, bool enabled) {
if ((enabled && disabled.Remove(plugin.Identifier)) || (!enabled && disabled.Add(plugin.Identifier))) {
PluginChangedState?.Invoke(this, new PluginChangedStateEventArgs(plugin, enabled));
Save();
}
}
public bool IsEnabled(Plugin plugin) {
return !disabled.Contains(plugin.Identifier);
}
}
}

View File

@@ -1,10 +1,17 @@
using TweetLib.Core.Systems.Configuration; using System.Diagnostics.CodeAnalysis;
using TweetLib.Core;
using TweetLib.Core.Application;
using TweetLib.Core.Systems.Configuration;
namespace TweetDuck.Configuration { namespace TweetDuck.Configuration {
sealed class SystemConfig : BaseConfig { sealed class SystemConfig : BaseConfig<SystemConfig>, IAppSystemConfiguration {
// CONFIGURATION DATA [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
public int MigrationVersion { get; set; } = 0;
private bool _hardwareAcceleration = true; private bool _hardwareAcceleration = true;
private bool _enableTouchAdjustment = false;
private bool _enableColorProfileDetection = false;
private bool _useSystemProxyForAllConnections = false;
public bool ClearCacheAutomatically { get; set; } = true; public bool ClearCacheAutomatically { get; set; } = true;
public int ClearCacheThreshold { get; set; } = 250; public int ClearCacheThreshold { get; set; } = 250;
@@ -13,15 +20,50 @@ namespace TweetDuck.Configuration {
public bool HardwareAcceleration { public bool HardwareAcceleration {
get => _hardwareAcceleration; get => _hardwareAcceleration;
set => UpdatePropertyWithRestartRequest(ref _hardwareAcceleration, value); set => UpdatePropertyWithCallback(ref _hardwareAcceleration, value, App.ConfigManager.TriggerProgramRestartRequested);
}
public bool EnableTouchAdjustment {
get => _enableTouchAdjustment;
set => UpdatePropertyWithCallback(ref _enableTouchAdjustment, value, App.ConfigManager.TriggerProgramRestartRequested);
}
public bool EnableColorProfileDetection {
get => _enableColorProfileDetection;
set => UpdatePropertyWithCallback(ref _enableColorProfileDetection, value, App.ConfigManager.TriggerProgramRestartRequested);
}
public bool UseSystemProxyForAllConnections {
get => _useSystemProxyForAllConnections;
set => UpdatePropertyWithCallback(ref _useSystemProxyForAllConnections, value, App.ConfigManager.TriggerProgramRestartRequested);
} }
// END OF CONFIG // END OF CONFIG
public SystemConfig(IConfigManager configManager) : base(configManager) {} #pragma warning disable CS0618
protected override BaseConfig ConstructWithDefaults(IConfigManager configManager) { public bool Migrate() {
return new SystemConfig(configManager); bool hasChanged = false;
if (MigrationVersion < 1) {
MigrationVersion = 1;
hasChanged = true;
var userConfig = Program.Config.User;
_enableTouchAdjustment = userConfig.EnableTouchAdjustment;
_enableColorProfileDetection = userConfig.EnableColorProfileDetection;
_useSystemProxyForAllConnections = userConfig.UseSystemProxyForAllConnections;
}
return hasChanged;
}
#pragma warning restore CS0618
public override SystemConfig ConstructWithDefaults() {
return new SystemConfig {
MigrationVersion = 1
};
} }
} }
} }

View File

@@ -2,16 +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 TweetLib.Core;
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;
using TweetLib.Utils.Data;
namespace TweetDuck.Configuration { namespace TweetDuck.Configuration {
sealed class UserConfig : BaseConfig { sealed class UserConfig : BaseConfig<UserConfig>, IAppUserConfiguration {
// CONFIGURATION DATA
public bool FirstRun { get; set; } = true; public bool FirstRun { get; set; } = true;
[SuppressMessage("ReSharper", "UnusedMember.Global")] [SuppressMessage("ReSharper", "UnusedMember.Global")]
@@ -26,10 +26,9 @@ namespace TweetDuck.Configuration {
public bool KeepLikeFollowDialogsOpen { get; set; } = true; public bool KeepLikeFollowDialogsOpen { get; set; } = true;
public bool BestImageQuality { get; set; } = true; public bool BestImageQuality { get; set; } = true;
public bool EnableAnimatedImages { get; set; } = true; public bool EnableAnimatedImages { get; set; } = true;
public bool HideTweetsByNftUsers { get; set; } = false;
private bool _enableSmoothScrolling = true; private bool _enableSmoothScrolling = true;
private bool _enableTouchAdjustment = false;
private bool _enableColorProfileDetection = false;
private string _customCefArgs = null; private string _customCefArgs = null;
public string BrowserPath { get; set; } = null; public string BrowserPath { get; set; } = null;
@@ -82,8 +81,6 @@ namespace TweetDuck.Configuration {
public string CustomBrowserCSS { get; set; } = null; public string CustomBrowserCSS { get; set; } = null;
public string CustomNotificationCSS { get; set; } = null; public string CustomNotificationCSS { get; set; } = null;
private bool _useSystemProxyForAllConnections;
public bool DevToolsInContextMenu { get; set; } = false; public bool DevToolsInContextMenu { get; set; } = false;
public bool DevToolsWindowOnTop { get; set; } = true; public bool DevToolsWindowOnTop { get; set; } = true;
@@ -122,47 +119,49 @@ namespace TweetDuck.Configuration {
public bool EnableSmoothScrolling { public bool EnableSmoothScrolling {
get => _enableSmoothScrolling; get => _enableSmoothScrolling;
set => UpdatePropertyWithRestartRequest(ref _enableSmoothScrolling, value); set => UpdatePropertyWithCallback(ref _enableSmoothScrolling, value, App.ConfigManager.TriggerProgramRestartRequested);
}
public bool EnableTouchAdjustment {
get => _enableTouchAdjustment;
set => UpdatePropertyWithRestartRequest(ref _enableTouchAdjustment, value);
}
public bool EnableColorProfileDetection {
get => _enableColorProfileDetection;
set => UpdatePropertyWithRestartRequest(ref _enableColorProfileDetection, value);
}
public bool UseSystemProxyForAllConnections {
get => _useSystemProxyForAllConnections;
set => UpdatePropertyWithRestartRequest(ref _useSystemProxyForAllConnections, value);
} }
public string CustomCefArgs { public string CustomCefArgs {
get => _customCefArgs; get => _customCefArgs;
set => UpdatePropertyWithRestartRequest(ref _customCefArgs, value); set => UpdatePropertyWithCallback(ref _customCefArgs, value, App.ConfigManager.TriggerProgramRestartRequested);
} }
public string SpellCheckLanguage { public string SpellCheckLanguage {
get => _spellCheckLanguage; get => _spellCheckLanguage;
set => UpdatePropertyWithRestartRequest(ref _spellCheckLanguage, value); set => UpdatePropertyWithCallback(ref _spellCheckLanguage, value, App.ConfigManager.TriggerProgramRestartRequested);
} }
// DEPRECATED
[Obsolete("Moved to SystemConfig")]
[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")]
public bool EnableTouchAdjustment { get; set; }
[Obsolete("Moved to SystemConfig")]
[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")]
public bool EnableColorProfileDetection { get; set; }
[Obsolete("Moved to SystemConfig")]
[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")]
public bool UseSystemProxyForAllConnections { get; set; }
// EVENTS // EVENTS
public event EventHandler MuteToggled; public event EventHandler MuteToggled;
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) {} public override UserConfig ConstructWithDefaults() {
return new UserConfig();
protected override BaseConfig ConstructWithDefaults(IConfigManager configManager) {
return new UserConfig(configManager);
} }
} }
} }

View File

@@ -2,6 +2,7 @@ using System;
using System.Drawing; using System.Drawing;
using System.Linq; using System.Linq;
using System.Windows.Forms; using System.Windows.Forms;
using TweetLib.Utils.Data;
namespace TweetDuck.Controls { namespace TweetDuck.Controls {
static class ControlExtensions { static class ControlExtensions {
@@ -17,8 +18,13 @@ namespace TweetDuck.Controls {
} }
public static void InvokeAsyncSafe(this Control control, Action func) { public static void InvokeAsyncSafe(this Control control, Action func) {
if (control.InvokeRequired) {
control.BeginInvoke(func); control.BeginInvoke(func);
} }
else {
func();
}
}
public static float GetDPIScale(this Control control) { public static float GetDPIScale(this Control control) {
using Graphics graphics = control.CreateGraphics(); using Graphics graphics = control.CreateGraphics();
@@ -75,5 +81,23 @@ namespace TweetDuck.Controls {
} }
}; };
} }
public static void Save(this WindowState state, Form form) {
state.Bounds = form.WindowState == FormWindowState.Normal ? form.DesktopBounds : form.RestoreBounds;
state.IsMaximized = form.WindowState == FormWindowState.Maximized;
}
public static void Restore(this WindowState state, Form form, bool firstTimeFullscreen) {
if (state.Bounds != Rectangle.Empty) {
form.DesktopBounds = state.Bounds;
form.WindowState = state.IsMaximized ? FormWindowState.Maximized : FormWindowState.Normal;
}
if ((state.Bounds == Rectangle.Empty && firstTimeFullscreen) || form.IsFullyOutsideView()) {
form.DesktopBounds = Screen.PrimaryScreen.WorkingArea;
form.WindowState = FormWindowState.Maximized;
state.Save(form);
}
}
} }
} }

View File

@@ -1,14 +1,14 @@
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 {
private const string TipsLink = "https://github.com/chylex/TweetDuck/wiki"; private const string TipsLink = "https://github.com/chylex/TweetDuck/wiki";
private const string IssuesLink = "https://github.com/chylex/TweetDuck/issues";
public FormAbout() { public FormAbout() {
InitializeComponent(); InitializeComponent();
@@ -19,15 +19,17 @@ namespace TweetDuck.Dialogs {
labelWebsite.Links.Add(new LinkLabel.Link(0, labelWebsite.Text.Length, Program.Website)); labelWebsite.Links.Add(new LinkLabel.Link(0, labelWebsite.Text.Length, Program.Website));
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, Lib.IssueTrackerUrl));
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) {

View File

@@ -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 @@ namespace TweetDuck.Dialogs {
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 @@ namespace TweetDuck.Dialogs {
} }
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;
}
}
} }
} }
} }

View File

@@ -38,11 +38,7 @@ namespace TweetDuck.Dialogs {
return Show(caption, text, MessageBoxIcon.Question, buttonAccept, buttonCancel); return Show(caption, text, MessageBoxIcon.Question, buttonAccept, buttonCancel);
} }
public static bool Show(string caption, string text, MessageBoxIcon icon, string button) { public static bool Show(string caption, string text, MessageBoxIcon icon, string buttonAccept, string buttonCancel = null) {
return Show(caption, text, icon, button, null);
}
public static bool Show(string caption, string text, MessageBoxIcon icon, string buttonAccept, string buttonCancel) {
using FormMessage message = new FormMessage(caption, text, icon); using FormMessage message = new FormMessage(caption, text, icon);
if (buttonCancel == null) { if (buttonCancel == null) {

View File

@@ -7,6 +7,8 @@ using TweetDuck.Management;
using TweetDuck.Plugins; using TweetDuck.Plugins;
using TweetLib.Core; using TweetLib.Core;
using TweetLib.Core.Features.Plugins; using TweetLib.Core.Features.Plugins;
using TweetLib.Core.Features.Plugins.Enums;
using TweetLib.Core.Systems.Configuration;
namespace TweetDuck.Dialogs { namespace TweetDuck.Dialogs {
sealed partial class FormPlugins : Form, FormManager.IAppDialog { sealed partial class FormPlugins : Form, FormManager.IAppDialog {
@@ -28,14 +30,18 @@ namespace TweetDuck.Dialogs {
Size = new Size(Math.Max(MinimumSize.Width, targetSize.Width), Math.Max(MinimumSize.Height, targetSize.Height)); Size = new Size(Math.Max(MinimumSize.Width, targetSize.Width), Math.Max(MinimumSize.Height, targetSize.Height));
} }
Shown += (sender, args) => { ReloadPluginList(); }; Shown += (sender, args) => {
ReloadPluginList();
};
FormClosed += (sender, args) => { FormClosed += (sender, args) => {
Config.PluginsWindowSize = Size; Config.PluginsWindowSize = Size;
Config.Save(); Config.Save();
}; };
ResizeEnd += (sender, args) => { timerLayout.Start(); }; ResizeEnd += (sender, args) => {
timerLayout.Start();
};
} }
private int GetPluginOrderIndex(Plugin plugin) { private int GetPluginOrderIndex(Plugin plugin) {
@@ -92,7 +98,7 @@ namespace TweetDuck.Dialogs {
} }
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.GetPluginFolder(PluginGroup.Custom));
} }
private void btnReload_Click(object sender, EventArgs e) { private void btnReload_Click(object sender, EventArgs e) {

View File

@@ -3,14 +3,15 @@ using System.Collections.Generic;
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;
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 +26,7 @@ namespace TweetDuck.Dialogs {
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,25 +40,25 @@ namespace TweetDuck.Dialogs {
PrepareLoad(); PrepareLoad();
AddButton("General", () => new TabSettingsGeneral(this.browser.ReloadColumns, updates)); AddButton("General", () => new TabSettingsGeneral(this.browser.ReloadToTweetDeck, 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(true)));
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)]);
} }
private void PrepareLoad() { private void PrepareLoad() {
Program.Config.ProgramRestartRequested += Config_ProgramRestartRequested; App.ConfigManager.ProgramRestartRequested += Config_ProgramRestartRequested;
} }
private void PrepareUnload() { // TODO refactor this further later private void PrepareUnload() { // TODO refactor this further later
currentTab.Control.OnClosing(); currentTab.Control.OnClosing();
Program.Config.ProgramRestartRequested -= Config_ProgramRestartRequested; App.ConfigManager.ProgramRestartRequested -= Config_ProgramRestartRequested;
Program.Config.SaveAll(); App.ConfigManager.SaveAll();
} }
private void Config_ProgramRestartRequested(object sender, EventArgs e) { private void Config_ProgramRestartRequested(object sender, EventArgs e) {
@@ -181,7 +182,7 @@ namespace TweetDuck.Dialogs {
} }
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
} }

View File

@@ -1,8 +1,8 @@
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.Collections; using TweetLib.Core.Features.Chromium;
namespace TweetDuck.Dialogs.Settings { namespace TweetDuck.Dialogs.Settings {
sealed partial class DialogSettingsCefArgs : Form { sealed partial class DialogSettingsCefArgs : Form {
@@ -21,7 +21,7 @@ namespace TweetDuck.Dialogs.Settings {
} }
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) {
@@ -31,7 +31,7 @@ namespace TweetDuck.Dialogs.Settings {
return; return;
} }
int count = CommandLineArgs.ReadCefArguments(CefArgs).Count; int count = CefUtils.ParseCommandLineArguments(CefArgs).Count;
string prompt = count == 0 && !string.IsNullOrWhiteSpace(initialArgs) ? "All current arguments will be removed. Continue?" : count + (count == 1 ? " argument was" : " arguments were") + " detected. Continue?"; string prompt = count == 0 && !string.IsNullOrWhiteSpace(initialArgs) ? "All current arguments will be removed. Continue?" : count + (count == 1 ? " argument was" : " arguments were") + " detected. Continue?";
if (FormMessage.Question("Confirm CEF Arguments", prompt, FormMessage.OK, FormMessage.Cancel)) { if (FormMessage.Question("Confirm CEF Arguments", prompt, FormMessage.OK, FormMessage.Cancel)) {

View File

@@ -1,6 +1,6 @@
using System; using System;
using System.Windows.Forms; using System.Windows.Forms;
using TweetLib.Core.Utils; using TweetLib.Utils.Static;
using IOPath = System.IO.Path; using IOPath = System.IO.Path;
namespace TweetDuck.Dialogs.Settings { namespace TweetDuck.Dialogs.Settings {

View File

@@ -121,7 +121,7 @@
this.cbSystemConfig.Size = new System.Drawing.Size(109, 19); this.cbSystemConfig.Size = new System.Drawing.Size(109, 19);
this.cbSystemConfig.TabIndex = 1; this.cbSystemConfig.TabIndex = 1;
this.cbSystemConfig.Text = "System Options"; this.cbSystemConfig.Text = "System Options";
this.toolTip.SetToolTip(this.cbSystemConfig, "Hardware acceleration and cache options."); this.toolTip.SetToolTip(this.cbSystemConfig, "Options related to the current system and hardware,\r\nsuch as hardware acceleration, proxy, and cache settings.");
this.cbSystemConfig.UseVisualStyleBackColor = true; this.cbSystemConfig.UseVisualStyleBackColor = true;
this.cbSystemConfig.CheckedChanged += new System.EventHandler(this.checkBoxSelection_CheckedChanged); this.cbSystemConfig.CheckedChanged += new System.EventHandler(this.checkBoxSelection_CheckedChanged);
// //

View File

@@ -2,10 +2,10 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Configuration;
using TweetDuck.Management; using TweetDuck.Management;
using TweetLib.Core;
using TweetLib.Core.Features.Plugins; using TweetLib.Core.Features.Plugins;
using TweetLib.Core.Utils; using TweetLib.Core.Systems.Configuration;
namespace TweetDuck.Dialogs.Settings { namespace TweetDuck.Dialogs.Settings {
sealed partial class DialogSettingsManage : Form { sealed partial class DialogSettingsManage : Form {
@@ -27,10 +27,6 @@ namespace TweetDuck.Dialogs.Settings {
} }
} }
private bool SelectedItemsForceRestart {
get => _selectedItems.HasFlag(ProfileManager.Items.Session);
}
public bool IsRestarting { get; private set; } public bool IsRestarting { get; private set; }
public bool ShouldReloadBrowser { get; private set; } public bool ShouldReloadBrowser { get; private set; }
@@ -132,7 +128,7 @@ namespace TweetDuck.Dialogs.Settings {
case State.Reset: case State.Reset:
if (FormMessage.Warning("Reset TweetDuck Options", "This will reset the selected items. Are you sure you want to proceed?", FormMessage.Yes, FormMessage.No)) { if (FormMessage.Warning("Reset TweetDuck Options", "This will reset the selected items. Are you sure you want to proceed?", FormMessage.Yes, FormMessage.No)) {
Program.Config.ProgramRestartRequested += Config_ProgramRestartRequested; App.ConfigManager.ProgramRestartRequested += Config_ProgramRestartRequested;
if (SelectedItems.HasFlag(ProfileManager.Items.UserConfig)) { if (SelectedItems.HasFlag(ProfileManager.Items.UserConfig)) {
Program.Config.User.Reset(); Program.Config.User.Reset();
@@ -142,26 +138,25 @@ namespace TweetDuck.Dialogs.Settings {
Program.Config.System.Reset(); Program.Config.System.Reset();
} }
Program.Config.ProgramRestartRequested -= Config_ProgramRestartRequested; App.ConfigManager.ProgramRestartRequested -= Config_ProgramRestartRequested;
if (SelectedItems.HasFlag(ProfileManager.Items.Session)) {
ProfileManager.DeleteAuthCookie();
}
if (SelectedItems.HasFlag(ProfileManager.Items.PluginData)) { if (SelectedItems.HasFlag(ProfileManager.Items.PluginData)) {
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) {
Program.Reporter.HandleException("Profile Reset", "Could not delete plugin data.", true, ex); App.ErrorHandler.HandleException("Profile Reset", "Could not delete plugin data.", true, ex);
} }
} }
if (SelectedItemsForceRestart) { if (requestedRestartFromConfig && FormMessage.Information("Profile Reset", "The application must restart for some of the restored options to take place. Do you want to restart now?", FormMessage.Yes, FormMessage.No)) {
RestartProgram(SelectedItems.HasFlag(ProfileManager.Items.Session) ? new string[] { Arguments.ArgDeleteCookies } : StringUtils.EmptyArray);
}
else if (requestedRestartFromConfig) {
if (FormMessage.Information("Profile Reset", "The application must restart for some of the restored options to take place. Do you want to restart now?", FormMessage.Yes, FormMessage.No)) {
RestartProgram(); RestartProgram();
} }
}
ShouldReloadBrowser = true; ShouldReloadBrowser = true;
@@ -173,19 +168,15 @@ namespace TweetDuck.Dialogs.Settings {
case State.Import: case State.Import:
if (importManager.Import(SelectedItems)) { if (importManager.Import(SelectedItems)) {
Program.Config.ProgramRestartRequested += Config_ProgramRestartRequested; App.ConfigManager.ProgramRestartRequested += Config_ProgramRestartRequested;
Program.Config.ReloadAll(); App.ConfigManager.ReloadAll();
Program.Config.ProgramRestartRequested -= Config_ProgramRestartRequested; App.ConfigManager.SaveAll();
App.ConfigManager.ProgramRestartRequested -= Config_ProgramRestartRequested;
if (SelectedItemsForceRestart) { if (requestedRestartFromConfig && FormMessage.Information("Profile Import", "The application must restart for some of the imported options to take place. Do you want to restart now?", FormMessage.Yes, FormMessage.No)) {
RestartProgram(SelectedItems.HasFlag(ProfileManager.Items.Session) ? new string[] { Arguments.ArgImportCookies } : StringUtils.EmptyArray);
}
else if (requestedRestartFromConfig) {
if (FormMessage.Information("Profile Import", "The application must restart for some of the imported options to take place. Do you want to restart now?", FormMessage.Yes, FormMessage.No)) {
RestartProgram(); RestartProgram();
} }
} }
}
ShouldReloadBrowser = true; ShouldReloadBrowser = true;
@@ -232,16 +223,16 @@ namespace TweetDuck.Dialogs.Settings {
btnContinue.Enabled = _selectedItems != ProfileManager.Items.None; btnContinue.Enabled = _selectedItems != ProfileManager.Items.None;
if (currentState == State.Import) { if (currentState == State.Import) {
btnContinue.Text = SelectedItemsForceRestart ? "Import && Restart" : "Import Profile"; btnContinue.Text = "Import Profile";
} }
else if (currentState == State.Reset) { else if (currentState == State.Reset) {
btnContinue.Text = SelectedItemsForceRestart ? "Restore && Restart" : "Restore Defaults"; btnContinue.Text = "Restore Defaults";
} }
} }
private void RestartProgram(params string[] extraArgs) { private void RestartProgram() {
IsRestarting = true; IsRestarting = true;
Program.Restart(extraArgs); Program.Restart();
} }
} }
} }

View File

@@ -1,7 +1,8 @@
using System; using System;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Configuration; using TweetDuck.Configuration;
using TweetLib.Core.Collections; using TweetLib.Core;
using TweetLib.Utils.Collections;
namespace TweetDuck.Dialogs.Settings { namespace TweetDuck.Dialogs.Settings {
sealed partial class DialogSettingsRestart : Form { sealed partial class DialogSettingsRestart : Form {
@@ -13,7 +14,7 @@ namespace TweetDuck.Dialogs.Settings {
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;
} }

View File

@@ -40,27 +40,34 @@
this.panelClearCacheAuto = new System.Windows.Forms.Panel(); this.panelClearCacheAuto = new System.Windows.Forms.Panel();
this.panelConfiguration = new System.Windows.Forms.Panel(); this.panelConfiguration = new System.Windows.Forms.Panel();
this.labelConfiguration = new System.Windows.Forms.Label(); this.labelConfiguration = new System.Windows.Forms.Label();
this.flowPanel = new System.Windows.Forms.FlowLayoutPanel(); this.flowPanelLeft = new System.Windows.Forms.FlowLayoutPanel();
this.labelBrowserSettings = new System.Windows.Forms.Label();
this.checkHardwareAcceleration = new System.Windows.Forms.CheckBox();
this.checkAutomaticallyDetectColorProfile = new System.Windows.Forms.CheckBox();
this.checkTouchAdjustment = new System.Windows.Forms.CheckBox();
this.labelProxy = new System.Windows.Forms.Label(); this.labelProxy = new System.Windows.Forms.Label();
this.checkUseSystemProxyForAllConnections = new System.Windows.Forms.CheckBox(); this.checkUseSystemProxyForAllConnections = new System.Windows.Forms.CheckBox();
this.labelDevTools = new System.Windows.Forms.Label(); this.labelDevTools = new System.Windows.Forms.Label();
this.checkDevToolsInContextMenu = new System.Windows.Forms.CheckBox(); this.checkDevToolsInContextMenu = new System.Windows.Forms.CheckBox();
this.checkDevToolsWindowOnTop = new System.Windows.Forms.CheckBox(); this.checkDevToolsWindowOnTop = new System.Windows.Forms.CheckBox();
this.flowPanelRight = new System.Windows.Forms.FlowLayoutPanel();
this.panelSeparator = new System.Windows.Forms.Panel();
((System.ComponentModel.ISupportInitialize)(this.numClearCacheThreshold)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.numClearCacheThreshold)).BeginInit();
this.panelAppButtons.SuspendLayout(); this.panelAppButtons.SuspendLayout();
this.panelClearCacheAuto.SuspendLayout(); this.panelClearCacheAuto.SuspendLayout();
this.panelConfiguration.SuspendLayout(); this.panelConfiguration.SuspendLayout();
this.flowPanel.SuspendLayout(); this.flowPanelLeft.SuspendLayout();
this.flowPanelRight.SuspendLayout();
this.SuspendLayout(); this.SuspendLayout();
// //
// btnClearCache // btnClearCache
// //
this.btnClearCache.Font = new System.Drawing.Font("Segoe UI", 9F); this.btnClearCache.Font = new System.Drawing.Font("Segoe UI", 9F);
this.btnClearCache.Location = new System.Drawing.Point(5, 135); this.btnClearCache.Location = new System.Drawing.Point(5, 145);
this.btnClearCache.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3); this.btnClearCache.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3);
this.btnClearCache.Name = "btnClearCache"; this.btnClearCache.Name = "btnClearCache";
this.btnClearCache.Size = new System.Drawing.Size(143, 25); this.btnClearCache.Size = new System.Drawing.Size(143, 25);
this.btnClearCache.TabIndex = 3; this.btnClearCache.TabIndex = 5;
this.btnClearCache.Text = "Clear Cache (...)"; this.btnClearCache.Text = "Clear Cache (...)";
this.btnClearCache.UseVisualStyleBackColor = true; this.btnClearCache.UseVisualStyleBackColor = true;
// //
@@ -147,7 +154,7 @@
0, 0,
0}); 0});
this.numClearCacheThreshold.Name = "numClearCacheThreshold"; this.numClearCacheThreshold.Name = "numClearCacheThreshold";
this.numClearCacheThreshold.Size = new System.Drawing.Size(89, 23); this.numClearCacheThreshold.Size = new System.Drawing.Size(80, 23);
this.numClearCacheThreshold.TabIndex = 1; this.numClearCacheThreshold.TabIndex = 1;
this.numClearCacheThreshold.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; this.numClearCacheThreshold.TextAlign = System.Windows.Forms.HorizontalAlignment.Right;
this.numClearCacheThreshold.TextSuffix = " MB"; this.numClearCacheThreshold.TextSuffix = " MB";
@@ -196,72 +203,119 @@
// //
this.labelCache.AutoSize = true; this.labelCache.AutoSize = true;
this.labelCache.Font = new System.Drawing.Font("Segoe UI Semibold", 10.5F, System.Drawing.FontStyle.Bold); this.labelCache.Font = new System.Drawing.Font("Segoe UI Semibold", 10.5F, System.Drawing.FontStyle.Bold);
this.labelCache.Location = new System.Drawing.Point(0, 112); this.labelCache.Location = new System.Drawing.Point(0, 122);
this.labelCache.Margin = new System.Windows.Forms.Padding(0, 30, 0, 1); this.labelCache.Margin = new System.Windows.Forms.Padding(0, 30, 0, 1);
this.labelCache.Name = "labelCache"; this.labelCache.Name = "labelCache";
this.labelCache.Size = new System.Drawing.Size(123, 19); this.labelCache.Size = new System.Drawing.Size(123, 19);
this.labelCache.TabIndex = 2; this.labelCache.TabIndex = 4;
this.labelCache.Text = "BROWSER CACHE"; this.labelCache.Text = "BROWSER CACHE";
// //
// panelClearCacheAuto // panelClearCacheAuto
// //
this.panelClearCacheAuto.Controls.Add(this.checkClearCacheAuto); this.panelClearCacheAuto.Controls.Add(this.checkClearCacheAuto);
this.panelClearCacheAuto.Controls.Add(this.numClearCacheThreshold); this.panelClearCacheAuto.Controls.Add(this.numClearCacheThreshold);
this.panelClearCacheAuto.Location = new System.Drawing.Point(0, 163); this.panelClearCacheAuto.Location = new System.Drawing.Point(0, 173);
this.panelClearCacheAuto.Margin = new System.Windows.Forms.Padding(0); this.panelClearCacheAuto.Margin = new System.Windows.Forms.Padding(0);
this.panelClearCacheAuto.Name = "panelClearCacheAuto"; this.panelClearCacheAuto.Name = "panelClearCacheAuto";
this.panelClearCacheAuto.Size = new System.Drawing.Size(300, 28); this.panelClearCacheAuto.Size = new System.Drawing.Size(300, 28);
this.panelClearCacheAuto.TabIndex = 4; this.panelClearCacheAuto.TabIndex = 6;
// //
// panelConfiguration // panelConfiguration
// //
this.panelConfiguration.Controls.Add(this.btnEditCSS); this.panelConfiguration.Controls.Add(this.btnEditCSS);
this.panelConfiguration.Controls.Add(this.btnEditCefArgs); this.panelConfiguration.Controls.Add(this.btnEditCefArgs);
this.panelConfiguration.Location = new System.Drawing.Point(0, 241); this.panelConfiguration.Location = new System.Drawing.Point(0, 132);
this.panelConfiguration.Margin = new System.Windows.Forms.Padding(0); this.panelConfiguration.Margin = new System.Windows.Forms.Padding(0);
this.panelConfiguration.Name = "panelConfiguration"; this.panelConfiguration.Name = "panelConfiguration";
this.panelConfiguration.Size = new System.Drawing.Size(300, 31); this.panelConfiguration.Size = new System.Drawing.Size(300, 31);
this.panelConfiguration.TabIndex = 6; this.panelConfiguration.TabIndex = 3;
// //
// labelConfiguration // labelConfiguration
// //
this.labelConfiguration.AutoSize = true; this.labelConfiguration.AutoSize = true;
this.labelConfiguration.Font = new System.Drawing.Font("Segoe UI Semibold", 10.5F, System.Drawing.FontStyle.Bold); this.labelConfiguration.Font = new System.Drawing.Font("Segoe UI Semibold", 10.5F, System.Drawing.FontStyle.Bold);
this.labelConfiguration.Location = new System.Drawing.Point(0, 221); this.labelConfiguration.Location = new System.Drawing.Point(0, 112);
this.labelConfiguration.Margin = new System.Windows.Forms.Padding(0, 30, 0, 1); this.labelConfiguration.Margin = new System.Windows.Forms.Padding(0, 30, 0, 1);
this.labelConfiguration.Name = "labelConfiguration"; this.labelConfiguration.Name = "labelConfiguration";
this.labelConfiguration.Size = new System.Drawing.Size(123, 19); this.labelConfiguration.Size = new System.Drawing.Size(123, 19);
this.labelConfiguration.TabIndex = 5; this.labelConfiguration.TabIndex = 2;
this.labelConfiguration.Text = "CONFIGURATION"; this.labelConfiguration.Text = "CONFIGURATION";
// //
// flowPanel // flowPanelLeft
// //
this.flowPanel.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) this.flowPanelLeft.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left))); | System.Windows.Forms.AnchorStyles.Left)));
this.flowPanel.Controls.Add(this.labelApp); this.flowPanelLeft.Controls.Add(this.labelBrowserSettings);
this.flowPanel.Controls.Add(this.panelAppButtons); this.flowPanelLeft.Controls.Add(this.checkHardwareAcceleration);
this.flowPanel.Controls.Add(this.labelCache); this.flowPanelLeft.Controls.Add(this.checkAutomaticallyDetectColorProfile);
this.flowPanel.Controls.Add(this.btnClearCache); this.flowPanelLeft.Controls.Add(this.checkTouchAdjustment);
this.flowPanel.Controls.Add(this.panelClearCacheAuto); this.flowPanelLeft.Controls.Add(this.labelCache);
this.flowPanel.Controls.Add(this.labelConfiguration); this.flowPanelLeft.Controls.Add(this.btnClearCache);
this.flowPanel.Controls.Add(this.panelConfiguration); this.flowPanelLeft.Controls.Add(this.panelClearCacheAuto);
this.flowPanel.Controls.Add(this.labelProxy); this.flowPanelLeft.Controls.Add(this.labelProxy);
this.flowPanel.Controls.Add(this.checkUseSystemProxyForAllConnections); this.flowPanelLeft.Controls.Add(this.checkUseSystemProxyForAllConnections);
this.flowPanel.Controls.Add(this.labelDevTools); this.flowPanelLeft.Controls.Add(this.labelDevTools);
this.flowPanel.Controls.Add(this.checkDevToolsInContextMenu); this.flowPanelLeft.Controls.Add(this.checkDevToolsInContextMenu);
this.flowPanel.Controls.Add(this.checkDevToolsWindowOnTop); this.flowPanelLeft.Controls.Add(this.checkDevToolsWindowOnTop);
this.flowPanel.FlowDirection = System.Windows.Forms.FlowDirection.TopDown; this.flowPanelLeft.FlowDirection = System.Windows.Forms.FlowDirection.TopDown;
this.flowPanel.Location = new System.Drawing.Point(9, 9); this.flowPanelLeft.Location = new System.Drawing.Point(9, 9);
this.flowPanel.Name = "flowPanel"; this.flowPanelLeft.Name = "flowPanelLeft";
this.flowPanel.Size = new System.Drawing.Size(300, 462); this.flowPanelLeft.Size = new System.Drawing.Size(300, 462);
this.flowPanel.TabIndex = 0; this.flowPanelLeft.TabIndex = 0;
this.flowPanel.WrapContents = false; this.flowPanelLeft.WrapContents = false;
//
// labelBrowserSettings
//
this.labelBrowserSettings.AutoSize = true;
this.labelBrowserSettings.Font = new System.Drawing.Font("Segoe UI Semibold", 10.5F, System.Drawing.FontStyle.Bold);
this.labelBrowserSettings.Location = new System.Drawing.Point(0, 0);
this.labelBrowserSettings.Margin = new System.Windows.Forms.Padding(0, 0, 0, 1);
this.labelBrowserSettings.Name = "labelBrowserSettings";
this.labelBrowserSettings.Size = new System.Drawing.Size(143, 19);
this.labelBrowserSettings.TabIndex = 0;
this.labelBrowserSettings.Text = "BROWSER SETTINGS";
//
// checkHardwareAcceleration
//
this.checkHardwareAcceleration.AutoSize = true;
this.checkHardwareAcceleration.Font = new System.Drawing.Font("Segoe UI", 9F);
this.checkHardwareAcceleration.Location = new System.Drawing.Point(6, 23);
this.checkHardwareAcceleration.Margin = new System.Windows.Forms.Padding(6, 3, 3, 2);
this.checkHardwareAcceleration.Name = "checkHardwareAcceleration";
this.checkHardwareAcceleration.Size = new System.Drawing.Size(146, 19);
this.checkHardwareAcceleration.TabIndex = 1;
this.checkHardwareAcceleration.Text = "Hardware Acceleration";
this.checkHardwareAcceleration.UseVisualStyleBackColor = true;
//
// checkAutomaticallyDetectColorProfile
//
this.checkAutomaticallyDetectColorProfile.AutoSize = true;
this.checkAutomaticallyDetectColorProfile.Font = new System.Drawing.Font("Segoe UI", 9F);
this.checkAutomaticallyDetectColorProfile.Location = new System.Drawing.Point(6, 47);
this.checkAutomaticallyDetectColorProfile.Margin = new System.Windows.Forms.Padding(6, 3, 3, 2);
this.checkAutomaticallyDetectColorProfile.Name = "checkAutomaticallyDetectColorProfile";
this.checkAutomaticallyDetectColorProfile.Size = new System.Drawing.Size(206, 19);
this.checkAutomaticallyDetectColorProfile.TabIndex = 2;
this.checkAutomaticallyDetectColorProfile.Text = "Automatically Detect Color Profile";
this.checkAutomaticallyDetectColorProfile.UseVisualStyleBackColor = true;
//
// checkTouchAdjustment
//
this.checkTouchAdjustment.AutoSize = true;
this.checkTouchAdjustment.Font = new System.Drawing.Font("Segoe UI", 9F);
this.checkTouchAdjustment.Location = new System.Drawing.Point(6, 71);
this.checkTouchAdjustment.Margin = new System.Windows.Forms.Padding(6, 3, 3, 2);
this.checkTouchAdjustment.Name = "checkTouchAdjustment";
this.checkTouchAdjustment.Size = new System.Drawing.Size(163, 19);
this.checkTouchAdjustment.TabIndex = 3;
this.checkTouchAdjustment.Text = "Touch Screen Adjustment";
this.checkTouchAdjustment.UseVisualStyleBackColor = true;
// //
// labelProxy // labelProxy
// //
this.labelProxy.AutoSize = true; this.labelProxy.AutoSize = true;
this.labelProxy.Font = new System.Drawing.Font("Segoe UI Semibold", 10.5F, System.Drawing.FontStyle.Bold); this.labelProxy.Font = new System.Drawing.Font("Segoe UI Semibold", 10.5F, System.Drawing.FontStyle.Bold);
this.labelProxy.Location = new System.Drawing.Point(0, 302); this.labelProxy.Location = new System.Drawing.Point(0, 231);
this.labelProxy.Margin = new System.Windows.Forms.Padding(0, 30, 0, 1); this.labelProxy.Margin = new System.Windows.Forms.Padding(0, 30, 0, 1);
this.labelProxy.Name = "labelProxy"; this.labelProxy.Name = "labelProxy";
this.labelProxy.Size = new System.Drawing.Size(54, 19); this.labelProxy.Size = new System.Drawing.Size(54, 19);
@@ -272,7 +326,7 @@
// //
this.checkUseSystemProxyForAllConnections.AutoSize = true; this.checkUseSystemProxyForAllConnections.AutoSize = true;
this.checkUseSystemProxyForAllConnections.Font = new System.Drawing.Font("Segoe UI", 9F); this.checkUseSystemProxyForAllConnections.Font = new System.Drawing.Font("Segoe UI", 9F);
this.checkUseSystemProxyForAllConnections.Location = new System.Drawing.Point(6, 328); this.checkUseSystemProxyForAllConnections.Location = new System.Drawing.Point(6, 257);
this.checkUseSystemProxyForAllConnections.Margin = new System.Windows.Forms.Padding(6, 6, 3, 2); this.checkUseSystemProxyForAllConnections.Margin = new System.Windows.Forms.Padding(6, 6, 3, 2);
this.checkUseSystemProxyForAllConnections.Name = "checkUseSystemProxyForAllConnections"; this.checkUseSystemProxyForAllConnections.Name = "checkUseSystemProxyForAllConnections";
this.checkUseSystemProxyForAllConnections.Size = new System.Drawing.Size(223, 19); this.checkUseSystemProxyForAllConnections.Size = new System.Drawing.Size(223, 19);
@@ -284,7 +338,7 @@
// //
this.labelDevTools.AutoSize = true; this.labelDevTools.AutoSize = true;
this.labelDevTools.Font = new System.Drawing.Font("Segoe UI Semibold", 10.5F, System.Drawing.FontStyle.Bold); this.labelDevTools.Font = new System.Drawing.Font("Segoe UI Semibold", 10.5F, System.Drawing.FontStyle.Bold);
this.labelDevTools.Location = new System.Drawing.Point(0, 379); this.labelDevTools.Location = new System.Drawing.Point(0, 308);
this.labelDevTools.Margin = new System.Windows.Forms.Padding(0, 30, 0, 1); this.labelDevTools.Margin = new System.Windows.Forms.Padding(0, 30, 0, 1);
this.labelDevTools.Name = "labelDevTools"; this.labelDevTools.Name = "labelDevTools";
this.labelDevTools.Size = new System.Drawing.Size(156, 19); this.labelDevTools.Size = new System.Drawing.Size(156, 19);
@@ -295,7 +349,7 @@
// //
this.checkDevToolsInContextMenu.AutoSize = true; this.checkDevToolsInContextMenu.AutoSize = true;
this.checkDevToolsInContextMenu.Font = new System.Drawing.Font("Segoe UI", 9F); this.checkDevToolsInContextMenu.Font = new System.Drawing.Font("Segoe UI", 9F);
this.checkDevToolsInContextMenu.Location = new System.Drawing.Point(6, 405); this.checkDevToolsInContextMenu.Location = new System.Drawing.Point(6, 334);
this.checkDevToolsInContextMenu.Margin = new System.Windows.Forms.Padding(6, 6, 3, 2); this.checkDevToolsInContextMenu.Margin = new System.Windows.Forms.Padding(6, 6, 3, 2);
this.checkDevToolsInContextMenu.Name = "checkDevToolsInContextMenu"; this.checkDevToolsInContextMenu.Name = "checkDevToolsInContextMenu";
this.checkDevToolsInContextMenu.Size = new System.Drawing.Size(201, 19); this.checkDevToolsInContextMenu.Size = new System.Drawing.Size(201, 19);
@@ -307,7 +361,7 @@
// //
this.checkDevToolsWindowOnTop.AutoSize = true; this.checkDevToolsWindowOnTop.AutoSize = true;
this.checkDevToolsWindowOnTop.Font = new System.Drawing.Font("Segoe UI", 9F); this.checkDevToolsWindowOnTop.Font = new System.Drawing.Font("Segoe UI", 9F);
this.checkDevToolsWindowOnTop.Location = new System.Drawing.Point(6, 429); this.checkDevToolsWindowOnTop.Location = new System.Drawing.Point(6, 358);
this.checkDevToolsWindowOnTop.Margin = new System.Windows.Forms.Padding(6, 3, 3, 2); this.checkDevToolsWindowOnTop.Margin = new System.Windows.Forms.Padding(6, 3, 3, 2);
this.checkDevToolsWindowOnTop.Name = "checkDevToolsWindowOnTop"; this.checkDevToolsWindowOnTop.Name = "checkDevToolsWindowOnTop";
this.checkDevToolsWindowOnTop.Size = new System.Drawing.Size(168, 19); this.checkDevToolsWindowOnTop.Size = new System.Drawing.Size(168, 19);
@@ -315,11 +369,39 @@
this.checkDevToolsWindowOnTop.Text = "Dev Tools Window On Top"; this.checkDevToolsWindowOnTop.Text = "Dev Tools Window On Top";
this.checkDevToolsWindowOnTop.UseVisualStyleBackColor = true; this.checkDevToolsWindowOnTop.UseVisualStyleBackColor = true;
// //
// flowPanelRight
//
this.flowPanelRight.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)));
this.flowPanelRight.Controls.Add(this.labelApp);
this.flowPanelRight.Controls.Add(this.panelAppButtons);
this.flowPanelRight.Controls.Add(this.labelConfiguration);
this.flowPanelRight.Controls.Add(this.panelConfiguration);
this.flowPanelRight.FlowDirection = System.Windows.Forms.FlowDirection.TopDown;
this.flowPanelRight.Location = new System.Drawing.Point(322, 9);
this.flowPanelRight.Name = "flowPanelRight";
this.flowPanelRight.Size = new System.Drawing.Size(300, 462);
this.flowPanelRight.TabIndex = 1;
this.flowPanelRight.WrapContents = false;
//
// panelSeparator
//
this.panelSeparator.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)));
this.panelSeparator.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(200)))), ((int)(((byte)(200)))), ((int)(((byte)(200)))));
this.panelSeparator.Location = new System.Drawing.Point(312, 0);
this.panelSeparator.Margin = new System.Windows.Forms.Padding(0, 0, 6, 0);
this.panelSeparator.Name = "panelSeparator";
this.panelSeparator.Size = new System.Drawing.Size(1, 480);
this.panelSeparator.TabIndex = 3;
//
// TabSettingsAdvanced // TabSettingsAdvanced
// //
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.Controls.Add(this.flowPanel); this.Controls.Add(this.panelSeparator);
this.Controls.Add(this.flowPanelRight);
this.Controls.Add(this.flowPanelLeft);
this.Name = "TabSettingsAdvanced"; this.Name = "TabSettingsAdvanced";
this.Size = new System.Drawing.Size(631, 480); this.Size = new System.Drawing.Size(631, 480);
((System.ComponentModel.ISupportInitialize)(this.numClearCacheThreshold)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.numClearCacheThreshold)).EndInit();
@@ -327,8 +409,10 @@
this.panelClearCacheAuto.ResumeLayout(false); this.panelClearCacheAuto.ResumeLayout(false);
this.panelClearCacheAuto.PerformLayout(); this.panelClearCacheAuto.PerformLayout();
this.panelConfiguration.ResumeLayout(false); this.panelConfiguration.ResumeLayout(false);
this.flowPanel.ResumeLayout(false); this.flowPanelLeft.ResumeLayout(false);
this.flowPanel.PerformLayout(); this.flowPanelLeft.PerformLayout();
this.flowPanelRight.ResumeLayout(false);
this.flowPanelRight.PerformLayout();
this.ResumeLayout(false); this.ResumeLayout(false);
} }
@@ -351,11 +435,17 @@
private System.Windows.Forms.Label labelConfiguration; private System.Windows.Forms.Label labelConfiguration;
private Controls.NumericUpDownEx numClearCacheThreshold; private Controls.NumericUpDownEx numClearCacheThreshold;
private System.Windows.Forms.CheckBox checkClearCacheAuto; private System.Windows.Forms.CheckBox checkClearCacheAuto;
private System.Windows.Forms.FlowLayoutPanel flowPanel; private System.Windows.Forms.FlowLayoutPanel flowPanelLeft;
private System.Windows.Forms.Label labelDevTools; private System.Windows.Forms.Label labelDevTools;
private System.Windows.Forms.CheckBox checkDevToolsInContextMenu; private System.Windows.Forms.CheckBox checkDevToolsInContextMenu;
private System.Windows.Forms.CheckBox checkDevToolsWindowOnTop; private System.Windows.Forms.CheckBox checkDevToolsWindowOnTop;
private System.Windows.Forms.Label labelProxy; private System.Windows.Forms.Label labelProxy;
private System.Windows.Forms.CheckBox checkUseSystemProxyForAllConnections; private System.Windows.Forms.CheckBox checkUseSystemProxyForAllConnections;
private System.Windows.Forms.FlowLayoutPanel flowPanelRight;
private System.Windows.Forms.Label labelBrowserSettings;
private System.Windows.Forms.CheckBox checkTouchAdjustment;
private System.Windows.Forms.CheckBox checkAutomaticallyDetectColorProfile;
private System.Windows.Forms.CheckBox checkHardwareAcceleration;
private System.Windows.Forms.Panel panelSeparator;
} }
} }

View File

@@ -25,6 +25,16 @@ namespace TweetDuck.Dialogs.Settings {
toolTip.SetToolTip(btnRestart, "Restarts the program using the same command\r\nline arguments that were used at launch."); toolTip.SetToolTip(btnRestart, "Restarts the program using the same command\r\nline arguments that were used at launch.");
toolTip.SetToolTip(btnRestartArgs, "Restarts the program with customizable\r\ncommand line arguments."); toolTip.SetToolTip(btnRestartArgs, "Restarts the program with customizable\r\ncommand line arguments.");
// browser settings
toolTip.SetToolTip(checkTouchAdjustment, "Toggles Chromium touch screen adjustment.\r\nDisabled by default, because it is very imprecise with TweetDeck.");
toolTip.SetToolTip(checkAutomaticallyDetectColorProfile, "Automatically detects the color profile of your system.\r\nUses the sRGB profile if disabled.");
toolTip.SetToolTip(checkHardwareAcceleration, "Uses graphics card to improve performance.\r\nDisable if you experience visual glitches, or to save a small amount of RAM.");
checkTouchAdjustment.Checked = SysConfig.EnableTouchAdjustment;
checkAutomaticallyDetectColorProfile.Checked = SysConfig.EnableColorProfileDetection;
checkHardwareAcceleration.Checked = SysConfig.HardwareAcceleration;
// browser cache // browser cache
toolTip.SetToolTip(btnClearCache, "Clearing cache will free up space taken by downloaded images and other resources."); toolTip.SetToolTip(btnClearCache, "Clearing cache will free up space taken by downloaded images and other resources.");
@@ -48,7 +58,7 @@ namespace TweetDuck.Dialogs.Settings {
toolTip.SetToolTip(checkUseSystemProxyForAllConnections, "Sets whether all connections should automatically detect and use the system proxy.\r\nBy default, only the browser component uses the system proxy, while other parts (such as update checks) ignore it.\r\nDisabled by default because Windows' proxy detection can be really slow."); toolTip.SetToolTip(checkUseSystemProxyForAllConnections, "Sets whether all connections should automatically detect and use the system proxy.\r\nBy default, only the browser component uses the system proxy, while other parts (such as update checks) ignore it.\r\nDisabled by default because Windows' proxy detection can be really slow.");
checkUseSystemProxyForAllConnections.Checked = Config.UseSystemProxyForAllConnections; checkUseSystemProxyForAllConnections.Checked = SysConfig.UseSystemProxyForAllConnections;
// development tools // development tools
@@ -65,6 +75,10 @@ namespace TweetDuck.Dialogs.Settings {
btnRestart.Click += btnRestart_Click; btnRestart.Click += btnRestart_Click;
btnRestartArgs.Click += btnRestartArgs_Click; btnRestartArgs.Click += btnRestartArgs_Click;
checkTouchAdjustment.CheckedChanged += checkTouchAdjustment_CheckedChanged;
checkAutomaticallyDetectColorProfile.CheckedChanged += checkAutomaticallyDetectColorProfile_CheckedChanged;
checkHardwareAcceleration.CheckedChanged += checkHardwareAcceleration_CheckedChanged;
btnClearCache.Click += btnClearCache_Click; btnClearCache.Click += btnClearCache_Click;
checkClearCacheAuto.CheckedChanged += checkClearCacheAuto_CheckedChanged; checkClearCacheAuto.CheckedChanged += checkClearCacheAuto_CheckedChanged;
@@ -85,11 +99,11 @@ namespace TweetDuck.Dialogs.Settings {
#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) {
@@ -106,6 +120,22 @@ namespace TweetDuck.Dialogs.Settings {
#endregion #endregion
#region Browser Settings
private void checkTouchAdjustment_CheckedChanged(object sender, EventArgs e) {
SysConfig.EnableTouchAdjustment = checkTouchAdjustment.Checked;
}
private void checkAutomaticallyDetectColorProfile_CheckedChanged(object sender, EventArgs e) {
SysConfig.EnableColorProfileDetection = checkAutomaticallyDetectColorProfile.Checked;
}
private void checkHardwareAcceleration_CheckedChanged(object sender, EventArgs e) {
SysConfig.HardwareAcceleration = checkHardwareAcceleration.Checked;
}
#endregion
#region Browser Cache #region Browser Cache
private void btnClearCache_Click(object sender, EventArgs e) { private void btnClearCache_Click(object sender, EventArgs e) {
@@ -125,7 +155,9 @@ namespace TweetDuck.Dialogs.Settings {
private void btnEditCefArgs_Click(object sender, EventArgs e) { private void btnEditCefArgs_Click(object sender, EventArgs e) {
DialogSettingsCefArgs form = new DialogSettingsCefArgs(Config.CustomCefArgs); DialogSettingsCefArgs form = new DialogSettingsCefArgs(Config.CustomCefArgs);
form.VisibleChanged += (sender2, args2) => { form.MoveToCenter(ParentForm); }; form.VisibleChanged += (sender2, args2) => {
form.MoveToCenter(ParentForm);
};
form.FormClosed += (sender2, args2) => { form.FormClosed += (sender2, args2) => {
RestoreParentForm(); RestoreParentForm();
@@ -144,7 +176,9 @@ namespace TweetDuck.Dialogs.Settings {
private void btnEditCSS_Click(object sender, EventArgs e) { private void btnEditCSS_Click(object sender, EventArgs e) {
DialogSettingsCSS form = new DialogSettingsCSS(Config.CustomBrowserCSS, Config.CustomNotificationCSS, reinjectBrowserCSS, openDevTools); DialogSettingsCSS form = new DialogSettingsCSS(Config.CustomBrowserCSS, Config.CustomNotificationCSS, reinjectBrowserCSS, openDevTools);
form.VisibleChanged += (sender2, args2) => { form.MoveToCenter(ParentForm); }; form.VisibleChanged += (sender2, args2) => {
form.MoveToCenter(ParentForm);
};
form.FormClosed += (sender2, args2) => { form.FormClosed += (sender2, args2) => {
RestoreParentForm(); RestoreParentForm();
@@ -173,7 +207,7 @@ namespace TweetDuck.Dialogs.Settings {
#region Proxy #region Proxy
private void checkUseSystemProxyForAllConnections_CheckedChanged(object sender, EventArgs e) { private void checkUseSystemProxyForAllConnections_CheckedChanged(object sender, EventArgs e) {
Config.UseSystemProxyForAllConnections = checkUseSystemProxyForAllConnections.Checked; SysConfig.UseSystemProxyForAllConnections = checkUseSystemProxyForAllConnections.Checked;
} }
#endregion #endregion

View File

@@ -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 @@ namespace TweetDuck.Dialogs.Settings {
#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(Lib.IssueTrackerUrl + "/new");
} }
#endregion #endregion

View File

@@ -34,18 +34,16 @@
this.trackBarZoom = new System.Windows.Forms.TrackBar(); this.trackBarZoom = new System.Windows.Forms.TrackBar();
this.labelZoom = new System.Windows.Forms.Label(); this.labelZoom = new System.Windows.Forms.Label();
this.zoomUpdateTimer = new System.Windows.Forms.Timer(this.components); this.zoomUpdateTimer = new System.Windows.Forms.Timer(this.components);
this.labelUI = new System.Windows.Forms.Label();
this.panelZoom = new System.Windows.Forms.Panel(); this.panelZoom = new System.Windows.Forms.Panel();
this.checkAnimatedAvatars = new System.Windows.Forms.CheckBox(); this.checkAnimatedAvatars = new System.Windows.Forms.CheckBox();
this.labelUpdates = new System.Windows.Forms.Label(); this.labelUpdates = new System.Windows.Forms.Label();
this.flowPanelLeft = new System.Windows.Forms.FlowLayoutPanel(); this.flowPanelLeft = new System.Windows.Forms.FlowLayoutPanel();
this.labelUI = new System.Windows.Forms.Label();
this.checkFocusDmInput = new System.Windows.Forms.CheckBox(); this.checkFocusDmInput = new System.Windows.Forms.CheckBox();
this.checkKeepLikeFollowDialogsOpen = new System.Windows.Forms.CheckBox(); this.checkKeepLikeFollowDialogsOpen = new System.Windows.Forms.CheckBox();
this.labelBrowserSettings = new System.Windows.Forms.Label();
this.checkSmoothScrolling = new System.Windows.Forms.CheckBox(); this.checkSmoothScrolling = new System.Windows.Forms.CheckBox();
this.checkTouchAdjustment = new System.Windows.Forms.CheckBox(); this.labelTweetDeckSettings = new System.Windows.Forms.Label();
this.checkAutomaticallyDetectColorProfile = new System.Windows.Forms.CheckBox(); this.checkHideTweetsByNftUsers = new System.Windows.Forms.CheckBox();
this.checkHardwareAcceleration = new System.Windows.Forms.CheckBox();
this.labelBrowserPath = new System.Windows.Forms.Label(); this.labelBrowserPath = new System.Windows.Forms.Label();
this.comboBoxCustomBrowser = new System.Windows.Forms.ComboBox(); this.comboBoxCustomBrowser = new System.Windows.Forms.ComboBox();
this.labelSearchEngine = new System.Windows.Forms.Label(); this.labelSearchEngine = new System.Windows.Forms.Label();
@@ -91,11 +89,11 @@
// //
this.checkUpdateNotifications.AutoSize = true; this.checkUpdateNotifications.AutoSize = true;
this.checkUpdateNotifications.Font = new System.Drawing.Font("Segoe UI", 9F); this.checkUpdateNotifications.Font = new System.Drawing.Font("Segoe UI", 9F);
this.checkUpdateNotifications.Location = new System.Drawing.Point(6, 409); this.checkUpdateNotifications.Location = new System.Drawing.Point(6, 381);
this.checkUpdateNotifications.Margin = new System.Windows.Forms.Padding(6, 6, 3, 2); this.checkUpdateNotifications.Margin = new System.Windows.Forms.Padding(6, 6, 3, 2);
this.checkUpdateNotifications.Name = "checkUpdateNotifications"; this.checkUpdateNotifications.Name = "checkUpdateNotifications";
this.checkUpdateNotifications.Size = new System.Drawing.Size(182, 19); this.checkUpdateNotifications.Size = new System.Drawing.Size(182, 19);
this.checkUpdateNotifications.TabIndex = 15; this.checkUpdateNotifications.TabIndex = 13;
this.checkUpdateNotifications.Text = "Check Updates Automatically"; this.checkUpdateNotifications.Text = "Check Updates Automatically";
this.checkUpdateNotifications.UseVisualStyleBackColor = true; this.checkUpdateNotifications.UseVisualStyleBackColor = true;
// //
@@ -103,12 +101,12 @@
// //
this.btnCheckUpdates.AutoSize = true; this.btnCheckUpdates.AutoSize = true;
this.btnCheckUpdates.Font = new System.Drawing.Font("Segoe UI", 9F); this.btnCheckUpdates.Font = new System.Drawing.Font("Segoe UI", 9F);
this.btnCheckUpdates.Location = new System.Drawing.Point(5, 433); this.btnCheckUpdates.Location = new System.Drawing.Point(5, 405);
this.btnCheckUpdates.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3); this.btnCheckUpdates.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3);
this.btnCheckUpdates.Name = "btnCheckUpdates"; this.btnCheckUpdates.Name = "btnCheckUpdates";
this.btnCheckUpdates.Padding = new System.Windows.Forms.Padding(2, 0, 2, 0); this.btnCheckUpdates.Padding = new System.Windows.Forms.Padding(2, 0, 2, 0);
this.btnCheckUpdates.Size = new System.Drawing.Size(128, 25); this.btnCheckUpdates.Size = new System.Drawing.Size(128, 25);
this.btnCheckUpdates.TabIndex = 16; this.btnCheckUpdates.TabIndex = 14;
this.btnCheckUpdates.Text = "Check Updates Now"; this.btnCheckUpdates.Text = "Check Updates Now";
this.btnCheckUpdates.UseVisualStyleBackColor = true; this.btnCheckUpdates.UseVisualStyleBackColor = true;
// //
@@ -128,12 +126,12 @@
// //
this.checkBestImageQuality.AutoSize = true; this.checkBestImageQuality.AutoSize = true;
this.checkBestImageQuality.Font = new System.Drawing.Font("Segoe UI", 9F); this.checkBestImageQuality.Font = new System.Drawing.Font("Segoe UI", 9F);
this.checkBestImageQuality.Location = new System.Drawing.Point(6, 122); this.checkBestImageQuality.Location = new System.Drawing.Point(6, 259);
this.checkBestImageQuality.Margin = new System.Windows.Forms.Padding(6, 3, 3, 2); this.checkBestImageQuality.Margin = new System.Windows.Forms.Padding(6, 6, 3, 2);
this.checkBestImageQuality.Name = "checkBestImageQuality"; this.checkBestImageQuality.Name = "checkBestImageQuality";
this.checkBestImageQuality.Size = new System.Drawing.Size(125, 19); this.checkBestImageQuality.Size = new System.Drawing.Size(182, 19);
this.checkBestImageQuality.TabIndex = 5; this.checkBestImageQuality.TabIndex = 9;
this.checkBestImageQuality.Text = "Best Image Quality"; this.checkBestImageQuality.Text = "Download Best Image Quality";
this.checkBestImageQuality.UseVisualStyleBackColor = true; this.checkBestImageQuality.UseVisualStyleBackColor = true;
// //
// checkOpenSearchInFirstColumn // checkOpenSearchInFirstColumn
@@ -167,11 +165,11 @@
// //
this.labelZoom.AutoSize = true; this.labelZoom.AutoSize = true;
this.labelZoom.Font = new System.Drawing.Font("Segoe UI Semibold", 9F, System.Drawing.FontStyle.Bold); this.labelZoom.Font = new System.Drawing.Font("Segoe UI Semibold", 9F, System.Drawing.FontStyle.Bold);
this.labelZoom.Location = new System.Drawing.Point(3, 323); this.labelZoom.Location = new System.Drawing.Point(3, 155);
this.labelZoom.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0); this.labelZoom.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
this.labelZoom.Name = "labelZoom"; this.labelZoom.Name = "labelZoom";
this.labelZoom.Size = new System.Drawing.Size(39, 15); this.labelZoom.Size = new System.Drawing.Size(39, 15);
this.labelZoom.TabIndex = 12; this.labelZoom.TabIndex = 6;
this.labelZoom.Text = "Zoom"; this.labelZoom.Text = "Zoom";
// //
// zoomUpdateTimer // zoomUpdateTimer
@@ -179,36 +177,25 @@
this.zoomUpdateTimer.Interval = 250; this.zoomUpdateTimer.Interval = 250;
this.zoomUpdateTimer.Tick += new System.EventHandler(this.zoomUpdateTimer_Tick); this.zoomUpdateTimer.Tick += new System.EventHandler(this.zoomUpdateTimer_Tick);
// //
// labelUI
//
this.labelUI.AutoSize = true;
this.labelUI.Font = new System.Drawing.Font("Segoe UI Semibold", 10.5F, System.Drawing.FontStyle.Bold);
this.labelUI.Location = new System.Drawing.Point(0, 0);
this.labelUI.Margin = new System.Windows.Forms.Padding(0, 0, 0, 1);
this.labelUI.Name = "labelUI";
this.labelUI.Size = new System.Drawing.Size(118, 19);
this.labelUI.TabIndex = 0;
this.labelUI.Text = "USER INTERFACE";
//
// panelZoom // panelZoom
// //
this.panelZoom.Controls.Add(this.trackBarZoom); this.panelZoom.Controls.Add(this.trackBarZoom);
this.panelZoom.Controls.Add(this.labelZoomValue); this.panelZoom.Controls.Add(this.labelZoomValue);
this.panelZoom.Location = new System.Drawing.Point(0, 339); this.panelZoom.Location = new System.Drawing.Point(0, 171);
this.panelZoom.Margin = new System.Windows.Forms.Padding(0, 1, 0, 0); this.panelZoom.Margin = new System.Windows.Forms.Padding(0, 1, 0, 0);
this.panelZoom.Name = "panelZoom"; this.panelZoom.Name = "panelZoom";
this.panelZoom.Size = new System.Drawing.Size(300, 35); this.panelZoom.Size = new System.Drawing.Size(300, 35);
this.panelZoom.TabIndex = 13; this.panelZoom.TabIndex = 7;
// //
// checkAnimatedAvatars // checkAnimatedAvatars
// //
this.checkAnimatedAvatars.AutoSize = true; this.checkAnimatedAvatars.AutoSize = true;
this.checkAnimatedAvatars.Font = new System.Drawing.Font("Segoe UI", 9F); this.checkAnimatedAvatars.Font = new System.Drawing.Font("Segoe UI", 9F);
this.checkAnimatedAvatars.Location = new System.Drawing.Point(6, 146); this.checkAnimatedAvatars.Location = new System.Drawing.Point(6, 307);
this.checkAnimatedAvatars.Margin = new System.Windows.Forms.Padding(6, 3, 3, 2); this.checkAnimatedAvatars.Margin = new System.Windows.Forms.Padding(6, 3, 3, 2);
this.checkAnimatedAvatars.Name = "checkAnimatedAvatars"; this.checkAnimatedAvatars.Name = "checkAnimatedAvatars";
this.checkAnimatedAvatars.Size = new System.Drawing.Size(158, 19); this.checkAnimatedAvatars.Size = new System.Drawing.Size(158, 19);
this.checkAnimatedAvatars.TabIndex = 6; this.checkAnimatedAvatars.TabIndex = 11;
this.checkAnimatedAvatars.Text = "Enable Animated Avatars"; this.checkAnimatedAvatars.Text = "Enable Animated Avatars";
this.checkAnimatedAvatars.UseVisualStyleBackColor = true; this.checkAnimatedAvatars.UseVisualStyleBackColor = true;
// //
@@ -216,11 +203,11 @@
// //
this.labelUpdates.AutoSize = true; this.labelUpdates.AutoSize = true;
this.labelUpdates.Font = new System.Drawing.Font("Segoe UI Semibold", 10.5F, System.Drawing.FontStyle.Bold); this.labelUpdates.Font = new System.Drawing.Font("Segoe UI Semibold", 10.5F, System.Drawing.FontStyle.Bold);
this.labelUpdates.Location = new System.Drawing.Point(0, 383); this.labelUpdates.Location = new System.Drawing.Point(0, 355);
this.labelUpdates.Margin = new System.Windows.Forms.Padding(0, 7, 0, 1); this.labelUpdates.Margin = new System.Windows.Forms.Padding(0, 27, 0, 1);
this.labelUpdates.Name = "labelUpdates"; this.labelUpdates.Name = "labelUpdates";
this.labelUpdates.Size = new System.Drawing.Size(69, 19); this.labelUpdates.Size = new System.Drawing.Size(69, 19);
this.labelUpdates.TabIndex = 14; this.labelUpdates.TabIndex = 12;
this.labelUpdates.Text = "UPDATES"; this.labelUpdates.Text = "UPDATES";
// //
// flowPanelLeft // flowPanelLeft
@@ -232,15 +219,13 @@
this.flowPanelLeft.Controls.Add(this.checkFocusDmInput); this.flowPanelLeft.Controls.Add(this.checkFocusDmInput);
this.flowPanelLeft.Controls.Add(this.checkOpenSearchInFirstColumn); this.flowPanelLeft.Controls.Add(this.checkOpenSearchInFirstColumn);
this.flowPanelLeft.Controls.Add(this.checkKeepLikeFollowDialogsOpen); this.flowPanelLeft.Controls.Add(this.checkKeepLikeFollowDialogsOpen);
this.flowPanelLeft.Controls.Add(this.checkBestImageQuality);
this.flowPanelLeft.Controls.Add(this.checkAnimatedAvatars);
this.flowPanelLeft.Controls.Add(this.labelBrowserSettings);
this.flowPanelLeft.Controls.Add(this.checkSmoothScrolling); this.flowPanelLeft.Controls.Add(this.checkSmoothScrolling);
this.flowPanelLeft.Controls.Add(this.checkTouchAdjustment);
this.flowPanelLeft.Controls.Add(this.checkAutomaticallyDetectColorProfile);
this.flowPanelLeft.Controls.Add(this.checkHardwareAcceleration);
this.flowPanelLeft.Controls.Add(this.labelZoom); this.flowPanelLeft.Controls.Add(this.labelZoom);
this.flowPanelLeft.Controls.Add(this.panelZoom); this.flowPanelLeft.Controls.Add(this.panelZoom);
this.flowPanelLeft.Controls.Add(this.labelTweetDeckSettings);
this.flowPanelLeft.Controls.Add(this.checkBestImageQuality);
this.flowPanelLeft.Controls.Add(this.checkHideTweetsByNftUsers);
this.flowPanelLeft.Controls.Add(this.checkAnimatedAvatars);
this.flowPanelLeft.Controls.Add(this.labelUpdates); this.flowPanelLeft.Controls.Add(this.labelUpdates);
this.flowPanelLeft.Controls.Add(this.checkUpdateNotifications); this.flowPanelLeft.Controls.Add(this.checkUpdateNotifications);
this.flowPanelLeft.Controls.Add(this.btnCheckUpdates); this.flowPanelLeft.Controls.Add(this.btnCheckUpdates);
@@ -251,6 +236,17 @@
this.flowPanelLeft.TabIndex = 0; this.flowPanelLeft.TabIndex = 0;
this.flowPanelLeft.WrapContents = false; this.flowPanelLeft.WrapContents = false;
// //
// labelUI
//
this.labelUI.AutoSize = true;
this.labelUI.Font = new System.Drawing.Font("Segoe UI Semibold", 10.5F, System.Drawing.FontStyle.Bold);
this.labelUI.Location = new System.Drawing.Point(0, 0);
this.labelUI.Margin = new System.Windows.Forms.Padding(0, 0, 0, 1);
this.labelUI.Name = "labelUI";
this.labelUI.Size = new System.Drawing.Size(118, 19);
this.labelUI.TabIndex = 0;
this.labelUI.Text = "USER INTERFACE";
//
// checkFocusDmInput // checkFocusDmInput
// //
this.checkFocusDmInput.AutoSize = true; this.checkFocusDmInput.AutoSize = true;
@@ -275,64 +271,40 @@
this.checkKeepLikeFollowDialogsOpen.Text = "Keep Like/Follow Dialogs Open"; this.checkKeepLikeFollowDialogsOpen.Text = "Keep Like/Follow Dialogs Open";
this.checkKeepLikeFollowDialogsOpen.UseVisualStyleBackColor = true; this.checkKeepLikeFollowDialogsOpen.UseVisualStyleBackColor = true;
// //
// labelBrowserSettings
//
this.labelBrowserSettings.AutoSize = true;
this.labelBrowserSettings.Font = new System.Drawing.Font("Segoe UI Semibold", 10.5F, System.Drawing.FontStyle.Bold);
this.labelBrowserSettings.Location = new System.Drawing.Point(0, 192);
this.labelBrowserSettings.Margin = new System.Windows.Forms.Padding(0, 25, 0, 1);
this.labelBrowserSettings.Name = "labelBrowserSettings";
this.labelBrowserSettings.Size = new System.Drawing.Size(143, 19);
this.labelBrowserSettings.TabIndex = 7;
this.labelBrowserSettings.Text = "BROWSER SETTINGS";
//
// checkSmoothScrolling // checkSmoothScrolling
// //
this.checkSmoothScrolling.AutoSize = true; this.checkSmoothScrolling.AutoSize = true;
this.checkSmoothScrolling.Font = new System.Drawing.Font("Segoe UI", 9F); this.checkSmoothScrolling.Font = new System.Drawing.Font("Segoe UI", 9F);
this.checkSmoothScrolling.Location = new System.Drawing.Point(6, 218); this.checkSmoothScrolling.Location = new System.Drawing.Point(6, 122);
this.checkSmoothScrolling.Margin = new System.Windows.Forms.Padding(6, 6, 3, 2); this.checkSmoothScrolling.Margin = new System.Windows.Forms.Padding(6, 3, 3, 2);
this.checkSmoothScrolling.Name = "checkSmoothScrolling"; this.checkSmoothScrolling.Name = "checkSmoothScrolling";
this.checkSmoothScrolling.Size = new System.Drawing.Size(117, 19); this.checkSmoothScrolling.Size = new System.Drawing.Size(117, 19);
this.checkSmoothScrolling.TabIndex = 8; this.checkSmoothScrolling.TabIndex = 5;
this.checkSmoothScrolling.Text = "Smooth Scrolling"; this.checkSmoothScrolling.Text = "Smooth Scrolling";
this.checkSmoothScrolling.UseVisualStyleBackColor = true; this.checkSmoothScrolling.UseVisualStyleBackColor = true;
// //
// checkTouchAdjustment // labelTweetDeckSettings
// //
this.checkTouchAdjustment.AutoSize = true; this.labelTweetDeckSettings.AutoSize = true;
this.checkTouchAdjustment.Font = new System.Drawing.Font("Segoe UI", 9F); this.labelTweetDeckSettings.Font = new System.Drawing.Font("Segoe UI Semibold", 10.5F, System.Drawing.FontStyle.Bold);
this.checkTouchAdjustment.Location = new System.Drawing.Point(6, 242); this.labelTweetDeckSettings.Location = new System.Drawing.Point(0, 233);
this.checkTouchAdjustment.Margin = new System.Windows.Forms.Padding(6, 3, 3, 2); this.labelTweetDeckSettings.Margin = new System.Windows.Forms.Padding(0, 27, 0, 1);
this.checkTouchAdjustment.Name = "checkTouchAdjustment"; this.labelTweetDeckSettings.Name = "labelTweetDeckSettings";
this.checkTouchAdjustment.Size = new System.Drawing.Size(163, 19); this.labelTweetDeckSettings.Size = new System.Drawing.Size(67, 19);
this.checkTouchAdjustment.TabIndex = 9; this.labelTweetDeckSettings.TabIndex = 8;
this.checkTouchAdjustment.Text = "Touch Screen Adjustment"; this.labelTweetDeckSettings.Text = "TWITTER";
this.checkTouchAdjustment.UseVisualStyleBackColor = true;
// //
// checkAutomaticallyDetectColorProfile // checkHideTweetsByNftUsers
// //
this.checkAutomaticallyDetectColorProfile.AutoSize = true; this.checkHideTweetsByNftUsers.AutoSize = true;
this.checkAutomaticallyDetectColorProfile.Font = new System.Drawing.Font("Segoe UI", 9F); this.checkHideTweetsByNftUsers.Font = new System.Drawing.Font("Segoe UI", 9F);
this.checkAutomaticallyDetectColorProfile.Location = new System.Drawing.Point(6, 266); this.checkHideTweetsByNftUsers.Location = new System.Drawing.Point(6, 283);
this.checkAutomaticallyDetectColorProfile.Margin = new System.Windows.Forms.Padding(6, 3, 3, 2); this.checkHideTweetsByNftUsers.Margin = new System.Windows.Forms.Padding(6, 3, 3, 2);
this.checkAutomaticallyDetectColorProfile.Name = "checkAutomaticallyDetectColorProfile"; this.checkHideTweetsByNftUsers.Name = "checkHideTweetsByNftUsers";
this.checkAutomaticallyDetectColorProfile.Size = new System.Drawing.Size(206, 19); this.checkHideTweetsByNftUsers.Size = new System.Drawing.Size(231, 19);
this.checkAutomaticallyDetectColorProfile.TabIndex = 10; this.checkHideTweetsByNftUsers.TabIndex = 10;
this.checkAutomaticallyDetectColorProfile.Text = "Automatically Detect Color Profile"; this.checkHideTweetsByNftUsers.Text = "Hide Tweets by Users with NFT Avatars";
this.checkAutomaticallyDetectColorProfile.UseVisualStyleBackColor = true; this.checkHideTweetsByNftUsers.UseVisualStyleBackColor = true;
//
// checkHardwareAcceleration
//
this.checkHardwareAcceleration.AutoSize = true;
this.checkHardwareAcceleration.Font = new System.Drawing.Font("Segoe UI", 9F);
this.checkHardwareAcceleration.Location = new System.Drawing.Point(6, 290);
this.checkHardwareAcceleration.Margin = new System.Windows.Forms.Padding(6, 3, 3, 2);
this.checkHardwareAcceleration.Name = "checkHardwareAcceleration";
this.checkHardwareAcceleration.Size = new System.Drawing.Size(146, 19);
this.checkHardwareAcceleration.TabIndex = 11;
this.checkHardwareAcceleration.Text = "Hardware Acceleration";
this.checkHardwareAcceleration.UseVisualStyleBackColor = true;
// //
// labelBrowserPath // labelBrowserPath
// //
@@ -401,7 +373,7 @@
this.flowPanelRight.Location = new System.Drawing.Point(322, 9); this.flowPanelRight.Location = new System.Drawing.Point(322, 9);
this.flowPanelRight.Name = "flowPanelRight"; this.flowPanelRight.Name = "flowPanelRight";
this.flowPanelRight.Size = new System.Drawing.Size(300, 462); this.flowPanelRight.Size = new System.Drawing.Size(300, 462);
this.flowPanelRight.TabIndex = 1; this.flowPanelRight.TabIndex = 2;
this.flowPanelRight.WrapContents = false; this.flowPanelRight.WrapContents = false;
// //
// labelLocales // labelLocales
@@ -583,7 +555,7 @@
this.panelSeparator.Margin = new System.Windows.Forms.Padding(0, 0, 6, 0); this.panelSeparator.Margin = new System.Windows.Forms.Padding(0, 0, 6, 0);
this.panelSeparator.Name = "panelSeparator"; this.panelSeparator.Name = "panelSeparator";
this.panelSeparator.Size = new System.Drawing.Size(1, 480); this.panelSeparator.Size = new System.Drawing.Size(1, 480);
this.panelSeparator.TabIndex = 2; this.panelSeparator.TabIndex = 1;
// //
// TabSettingsGeneral // TabSettingsGeneral
// //
@@ -618,7 +590,6 @@
private System.Windows.Forms.Label labelZoomValue; private System.Windows.Forms.Label labelZoomValue;
private System.Windows.Forms.TrackBar trackBarZoom; private System.Windows.Forms.TrackBar trackBarZoom;
private System.Windows.Forms.Timer zoomUpdateTimer; private System.Windows.Forms.Timer zoomUpdateTimer;
private System.Windows.Forms.Label labelUI;
private System.Windows.Forms.Panel panelZoom; private System.Windows.Forms.Panel panelZoom;
private System.Windows.Forms.Label labelUpdates; private System.Windows.Forms.Label labelUpdates;
private System.Windows.Forms.CheckBox checkBestImageQuality; private System.Windows.Forms.CheckBox checkBestImageQuality;
@@ -628,12 +599,9 @@
private System.Windows.Forms.CheckBox checkKeepLikeFollowDialogsOpen; private System.Windows.Forms.CheckBox checkKeepLikeFollowDialogsOpen;
private System.Windows.Forms.Label labelBrowserPath; private System.Windows.Forms.Label labelBrowserPath;
private System.Windows.Forms.ComboBox comboBoxCustomBrowser; private System.Windows.Forms.ComboBox comboBoxCustomBrowser;
private System.Windows.Forms.Label labelBrowserSettings;
private System.Windows.Forms.CheckBox checkSmoothScrolling; private System.Windows.Forms.CheckBox checkSmoothScrolling;
private System.Windows.Forms.Label labelSearchEngine; private System.Windows.Forms.Label labelSearchEngine;
private System.Windows.Forms.ComboBox comboBoxSearchEngine; private System.Windows.Forms.ComboBox comboBoxSearchEngine;
private System.Windows.Forms.CheckBox checkTouchAdjustment;
private System.Windows.Forms.CheckBox checkAutomaticallyDetectColorProfile;
private System.Windows.Forms.FlowLayoutPanel flowPanelRight; private System.Windows.Forms.FlowLayoutPanel flowPanelRight;
private System.Windows.Forms.Panel panelSeparator; private System.Windows.Forms.Panel panelSeparator;
private System.Windows.Forms.Label labelLocales; private System.Windows.Forms.Label labelLocales;
@@ -642,7 +610,6 @@
private System.Windows.Forms.ComboBox comboBoxSpellCheckLanguage; private System.Windows.Forms.ComboBox comboBoxSpellCheckLanguage;
private System.Windows.Forms.Label labelTranslationTarget; private System.Windows.Forms.Label labelTranslationTarget;
private System.Windows.Forms.ComboBox comboBoxTranslationTarget; private System.Windows.Forms.ComboBox comboBoxTranslationTarget;
private System.Windows.Forms.CheckBox checkHardwareAcceleration;
private System.Windows.Forms.CheckBox checkFocusDmInput; private System.Windows.Forms.CheckBox checkFocusDmInput;
private System.Windows.Forms.Panel panelCustomBrowser; private System.Windows.Forms.Panel panelCustomBrowser;
private System.Windows.Forms.Button btnCustomBrowserChange; private System.Windows.Forms.Button btnCustomBrowserChange;
@@ -653,5 +620,8 @@
private System.Windows.Forms.Label labelExternalApplications; private System.Windows.Forms.Label labelExternalApplications;
private System.Windows.Forms.Label labelFirstDayOfWeek; private System.Windows.Forms.Label labelFirstDayOfWeek;
private System.Windows.Forms.ComboBox comboBoxFirstDayOfWeek; private System.Windows.Forms.ComboBox comboBoxFirstDayOfWeek;
private System.Windows.Forms.Label labelUI;
private System.Windows.Forms.CheckBox checkHideTweetsByNftUsers;
private System.Windows.Forms.Label labelTweetDeckSettings;
} }
} }

View File

@@ -2,17 +2,21 @@
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.Features.Chromium;
using TweetLib.Core.Features.Twitter;
using TweetLib.Core.Systems.Updates; using TweetLib.Core.Systems.Updates;
using TweetLib.Core.Utils; using TweetLib.Utils.Globalization;
namespace TweetDuck.Dialogs.Settings { namespace TweetDuck.Dialogs.Settings {
sealed partial class TabSettingsGeneral : FormSettings.BaseTab { sealed partial class TabSettingsGeneral : FormSettings.BaseTab {
private readonly Action reloadTweetDeck;
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;
@@ -24,9 +28,10 @@ namespace TweetDuck.Dialogs.Settings {
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 reloadTweetDeck, Action reloadColumns, UpdateChecker updates) {
InitializeComponent(); InitializeComponent();
this.reloadTweetDeck = reloadTweetDeck;
this.reloadColumns = reloadColumns; this.reloadColumns = reloadColumns;
this.updates = updates; this.updates = updates;
@@ -40,8 +45,7 @@ namespace TweetDuck.Dialogs.Settings {
toolTip.SetToolTip(checkFocusDmInput, "Places cursor into Direct Message input\r\nfield when opening a conversation."); toolTip.SetToolTip(checkFocusDmInput, "Places cursor into Direct Message input\r\nfield when opening a conversation.");
toolTip.SetToolTip(checkOpenSearchInFirstColumn, "By default, TweetDeck adds Search columns at the end.\r\nThis option makes them appear before the first column instead."); toolTip.SetToolTip(checkOpenSearchInFirstColumn, "By default, TweetDeck adds Search columns at the end.\r\nThis option makes them appear before the first column instead.");
toolTip.SetToolTip(checkKeepLikeFollowDialogsOpen, "Allows liking and following from multiple accounts at once,\r\ninstead of automatically closing the dialog after taking an action."); toolTip.SetToolTip(checkKeepLikeFollowDialogsOpen, "Allows liking and following from multiple accounts at once,\r\ninstead of automatically closing the dialog after taking an action.");
toolTip.SetToolTip(checkBestImageQuality, "When right-clicking a tweet image, the context menu options\r\nwill use links to the original image size (:orig in the URL)."); toolTip.SetToolTip(checkSmoothScrolling, "Toggles smooth mouse wheel scrolling.");
toolTip.SetToolTip(checkAnimatedAvatars, "Some old Twitter avatars could be uploaded as animated GIFs.");
toolTip.SetToolTip(labelZoomValue, "Changes the zoom level.\r\nAlso affects notifications and screenshots."); toolTip.SetToolTip(labelZoomValue, "Changes the zoom level.\r\nAlso affects notifications and screenshots.");
toolTip.SetToolTip(trackBarZoom, toolTip.GetToolTip(labelZoomValue)); toolTip.SetToolTip(trackBarZoom, toolTip.GetToolTip(labelZoomValue));
@@ -49,12 +53,21 @@ namespace TweetDuck.Dialogs.Settings {
checkFocusDmInput.Checked = Config.FocusDmInput; checkFocusDmInput.Checked = Config.FocusDmInput;
checkOpenSearchInFirstColumn.Checked = Config.OpenSearchInFirstColumn; checkOpenSearchInFirstColumn.Checked = Config.OpenSearchInFirstColumn;
checkKeepLikeFollowDialogsOpen.Checked = Config.KeepLikeFollowDialogsOpen; checkKeepLikeFollowDialogsOpen.Checked = Config.KeepLikeFollowDialogsOpen;
checkBestImageQuality.Checked = Config.BestImageQuality; checkSmoothScrolling.Checked = Config.EnableSmoothScrolling;
checkAnimatedAvatars.Checked = Config.EnableAnimatedImages;
trackBarZoom.SetValueSafe(Config.ZoomLevel); trackBarZoom.SetValueSafe(Config.ZoomLevel);
labelZoomValue.Text = trackBarZoom.Value + "%"; labelZoomValue.Text = trackBarZoom.Value + "%";
// twitter
toolTip.SetToolTip(checkBestImageQuality, "When right-clicking a tweet image, the context menu options\r\nwill use links to the original image size (:orig in the URL).");
toolTip.SetToolTip(checkHideTweetsByNftUsers, "Hides tweets created by users who use Twitter's NFT avatar integration.\r\nThis feature is somewhat experimental, some accounts might not be detected immediately.");
toolTip.SetToolTip(checkAnimatedAvatars, "Some old Twitter avatars could be uploaded as animated GIFs.");
checkBestImageQuality.Checked = Config.BestImageQuality;
checkHideTweetsByNftUsers.Checked = Config.HideTweetsByNftUsers;
checkAnimatedAvatars.Checked = Config.EnableAnimatedImages;
// updates // updates
toolTip.SetToolTip(checkUpdateNotifications, "Checks for updates every hour.\r\nIf an update is dismissed, it will not appear again."); toolTip.SetToolTip(checkUpdateNotifications, "Checks for updates every hour.\r\nIf an update is dismissed, it will not appear again.");
@@ -62,21 +75,12 @@ namespace TweetDuck.Dialogs.Settings {
checkUpdateNotifications.Checked = Config.EnableUpdateCheck; checkUpdateNotifications.Checked = Config.EnableUpdateCheck;
// browser settings // external applications
toolTip.SetToolTip(checkSmoothScrolling, "Toggles smooth mouse wheel scrolling.");
toolTip.SetToolTip(checkTouchAdjustment, "Toggles Chromium touch screen adjustment.\r\nDisabled by default, because it is very imprecise with TweetDeck.");
toolTip.SetToolTip(checkAutomaticallyDetectColorProfile, "Automatically detects the color profile of your system.\r\nUses the sRGB profile if disabled.");
toolTip.SetToolTip(checkHardwareAcceleration, "Uses graphics card to improve performance.\r\nDisable if you experience visual glitches, or to save a small amount of RAM.");
toolTip.SetToolTip(comboBoxCustomBrowser, "Sets the default browser for opening links."); toolTip.SetToolTip(comboBoxCustomBrowser, "Sets the default browser for opening links.");
toolTip.SetToolTip(comboBoxCustomVideoPlayer, "Sets the default application for playing videos."); toolTip.SetToolTip(comboBoxCustomVideoPlayer, "Sets the default application for playing videos.");
toolTip.SetToolTip(comboBoxSearchEngine, "Sets the default website for opening searches."); toolTip.SetToolTip(comboBoxSearchEngine, "Sets the default website for opening searches.");
checkSmoothScrolling.Checked = Config.EnableSmoothScrolling;
checkTouchAdjustment.Checked = Config.EnableTouchAdjustment;
checkAutomaticallyDetectColorProfile.Checked = Config.EnableColorProfileDetection;
checkHardwareAcceleration.Checked = SysConfig.HardwareAcceleration;
foreach (WindowsUtils.Browser browserInfo in WindowsUtils.FindInstalledBrowsers()) { foreach (WindowsUtils.Browser browserInfo in WindowsUtils.FindInstalledBrowsers()) {
comboBoxCustomBrowser.Items.Add(browserInfo); comboBoxCustomBrowser.Items.Add(browserInfo);
} }
@@ -107,20 +111,20 @@ namespace TweetDuck.Dialogs.Settings {
checkSpellCheck.Checked = Config.EnableSpellCheck; checkSpellCheck.Checked = Config.EnableSpellCheck;
try { try {
foreach (LocaleUtils.Item item in LocaleUtils.SpellCheckLanguages) { foreach (Language item in SpellCheck.SupportedLanguages) {
comboBoxSpellCheckLanguage.Items.Add(item); comboBoxSpellCheckLanguage.Items.Add(item);
} }
} catch { } catch {
comboBoxSpellCheckLanguage.Items.Add(new LocaleUtils.Item("en-US")); comboBoxSpellCheckLanguage.Items.Add(new Language("en-US"));
} }
comboBoxSpellCheckLanguage.SelectedItem = new LocaleUtils.Item(Config.SpellCheckLanguage); comboBoxSpellCheckLanguage.SelectedItem = new Language(Config.SpellCheckLanguage);
foreach (LocaleUtils.Item item in LocaleUtils.TweetDeckTranslationLocales) { foreach (Language item in TweetDeckTranslations.SupportedLanguages) {
comboBoxTranslationTarget.Items.Add(item); comboBoxTranslationTarget.Items.Add(item);
} }
comboBoxTranslationTarget.SelectedItem = new LocaleUtils.Item(Config.TranslationTarget); comboBoxTranslationTarget.SelectedItem = new Language(Config.TranslationTarget);
var daysOfWeek = comboBoxFirstDayOfWeek.Items; var daysOfWeek = comboBoxFirstDayOfWeek.Items;
daysOfWeek.Add("(based on system locale)"); daysOfWeek.Add("(based on system locale)");
@@ -139,17 +143,16 @@ namespace TweetDuck.Dialogs.Settings {
checkFocusDmInput.CheckedChanged += checkFocusDmInput_CheckedChanged; checkFocusDmInput.CheckedChanged += checkFocusDmInput_CheckedChanged;
checkOpenSearchInFirstColumn.CheckedChanged += checkOpenSearchInFirstColumn_CheckedChanged; checkOpenSearchInFirstColumn.CheckedChanged += checkOpenSearchInFirstColumn_CheckedChanged;
checkKeepLikeFollowDialogsOpen.CheckedChanged += checkKeepLikeFollowDialogsOpen_CheckedChanged; checkKeepLikeFollowDialogsOpen.CheckedChanged += checkKeepLikeFollowDialogsOpen_CheckedChanged;
checkBestImageQuality.CheckedChanged += checkBestImageQuality_CheckedChanged; checkSmoothScrolling.CheckedChanged += checkSmoothScrolling_CheckedChanged;
checkAnimatedAvatars.CheckedChanged += checkAnimatedAvatars_CheckedChanged;
trackBarZoom.ValueChanged += trackBarZoom_ValueChanged; trackBarZoom.ValueChanged += trackBarZoom_ValueChanged;
checkBestImageQuality.CheckedChanged += checkBestImageQuality_CheckedChanged;
checkHideTweetsByNftUsers.CheckedChanged += checkHideTweetsByNftUsers_CheckedChanged;
checkAnimatedAvatars.CheckedChanged += checkAnimatedAvatars_CheckedChanged;
checkUpdateNotifications.CheckedChanged += checkUpdateNotifications_CheckedChanged; checkUpdateNotifications.CheckedChanged += checkUpdateNotifications_CheckedChanged;
btnCheckUpdates.Click += btnCheckUpdates_Click; btnCheckUpdates.Click += btnCheckUpdates_Click;
checkSmoothScrolling.CheckedChanged += checkSmoothScrolling_CheckedChanged;
checkTouchAdjustment.CheckedChanged += checkTouchAdjustment_CheckedChanged;
checkAutomaticallyDetectColorProfile.CheckedChanged += checkAutomaticallyDetectColorProfile_CheckedChanged;
checkHardwareAcceleration.CheckedChanged += checkHardwareAcceleration_CheckedChanged;
comboBoxCustomBrowser.SelectedIndexChanged += comboBoxCustomBrowser_SelectedIndexChanged; comboBoxCustomBrowser.SelectedIndexChanged += comboBoxCustomBrowser_SelectedIndexChanged;
btnCustomBrowserChange.Click += btnCustomBrowserChange_Click; btnCustomBrowserChange.Click += btnCustomBrowserChange_Click;
comboBoxCustomVideoPlayer.SelectedIndexChanged += comboBoxCustomVideoPlayer_SelectedIndexChanged; comboBoxCustomVideoPlayer.SelectedIndexChanged += comboBoxCustomVideoPlayer_SelectedIndexChanged;
@@ -184,13 +187,8 @@ namespace TweetDuck.Dialogs.Settings {
Config.KeepLikeFollowDialogsOpen = checkKeepLikeFollowDialogsOpen.Checked; Config.KeepLikeFollowDialogsOpen = checkKeepLikeFollowDialogsOpen.Checked;
} }
private void checkBestImageQuality_CheckedChanged(object sender, EventArgs e) { private void checkSmoothScrolling_CheckedChanged(object sender, EventArgs e) {
Config.BestImageQuality = checkBestImageQuality.Checked; Config.EnableSmoothScrolling = checkSmoothScrolling.Checked;
}
private void checkAnimatedAvatars_CheckedChanged(object sender, EventArgs e) {
Config.EnableAnimatedImages = checkAnimatedAvatars.Checked;
BrowserProcessHandler.UpdatePrefs().ContinueWith(task => reloadColumns());
} }
private void trackBarZoom_ValueChanged(object sender, EventArgs e) { private void trackBarZoom_ValueChanged(object sender, EventArgs e) {
@@ -208,6 +206,24 @@ namespace TweetDuck.Dialogs.Settings {
#endregion #endregion
#region Twitter
private void checkBestImageQuality_CheckedChanged(object sender, EventArgs e) {
Config.BestImageQuality = checkBestImageQuality.Checked;
}
private void checkHideTweetsByNftUsers_CheckedChanged(object sender, EventArgs e) {
Config.HideTweetsByNftUsers = checkHideTweetsByNftUsers.Checked;
BeginInvoke(reloadTweetDeck);
}
private void checkAnimatedAvatars_CheckedChanged(object sender, EventArgs e) {
Config.EnableAnimatedImages = checkAnimatedAvatars.Checked;
BrowserProcessHandler.UpdatePrefs().ContinueWith(task => reloadColumns());
}
#endregion
#region Updates #region Updates
private void checkUpdateNotifications_CheckedChanged(object sender, EventArgs e) { private void checkUpdateNotifications_CheckedChanged(object sender, EventArgs e) {
@@ -230,30 +246,14 @@ namespace TweetDuck.Dialogs.Settings {
FormMessage.Information("No Updates Available", "Your version of TweetDuck is up to date.", FormMessage.OK); FormMessage.Information("No Updates Available", "Your version of TweetDuck is up to date.", FormMessage.OK);
} }
}, ex => { }, ex => {
Program.Reporter.HandleException("Update Check Error", "An error occurred while checking for updates.", true, ex); App.ErrorHandler.HandleException("Update Check Error", "An error occurred while checking for updates.", true, ex);
}); });
} }
} }
#endregion #endregion
#region Browser Settings #region External Applications
private void checkSmoothScrolling_CheckedChanged(object sender, EventArgs e) {
Config.EnableSmoothScrolling = checkSmoothScrolling.Checked;
}
private void checkTouchAdjustment_CheckedChanged(object sender, EventArgs e) {
Config.EnableTouchAdjustment = checkTouchAdjustment.Checked;
}
private void checkAutomaticallyDetectColorProfile_CheckedChanged(object sender, EventArgs e) {
Config.EnableColorProfileDetection = checkAutomaticallyDetectColorProfile.Checked;
}
private void checkHardwareAcceleration_CheckedChanged(object sender, EventArgs e) {
SysConfig.HardwareAcceleration = checkHardwareAcceleration.Checked;
}
private void UpdateBrowserChangeButton() { private void UpdateBrowserChangeButton() {
btnCustomBrowserChange.Visible = comboBoxCustomBrowser.SelectedIndex == browserListIndexCustom; btnCustomBrowserChange.Visible = comboBoxCustomBrowser.SelectedIndex == browserListIndexCustom;
@@ -403,11 +403,11 @@ namespace TweetDuck.Dialogs.Settings {
} }
private void comboBoxSpellCheckLanguage_SelectedValueChanged(object sender, EventArgs e) { private void comboBoxSpellCheckLanguage_SelectedValueChanged(object sender, EventArgs e) {
Config.SpellCheckLanguage = (comboBoxSpellCheckLanguage.SelectedItem as LocaleUtils.Item)?.Code ?? "en-US"; Config.SpellCheckLanguage = (comboBoxSpellCheckLanguage.SelectedItem as Language)?.Code ?? "en-US";
} }
private void comboBoxTranslationTarget_SelectedValueChanged(object sender, EventArgs e) { private void comboBoxTranslationTarget_SelectedValueChanged(object sender, EventArgs e) {
Config.TranslationTarget = (comboBoxTranslationTarget.SelectedItem as LocaleUtils.Item)?.Code ?? "en"; Config.TranslationTarget = (comboBoxTranslationTarget.SelectedItem as Language)?.Code ?? "en";
} }
private void comboBoxFirstDayOfWeek_SelectedValueChanged(object sender, EventArgs e) { private void comboBoxFirstDayOfWeek_SelectedValueChanged(object sender, EventArgs e) {
@@ -420,7 +420,7 @@ namespace TweetDuck.Dialogs.Settings {
public DayOfWeekItem(string name, DayOfWeek dow) { public DayOfWeekItem(string name, DayOfWeek dow) {
Name = name; Name = name;
Id = LocaleUtils.GetJQueryDayOfWeek(dow); Id = JQuery.GetDatePickerDayOfWeek(dow);
} }
public override int GetHashCode() => Name.GetHashCode(); public override int GetHashCode() => Name.GetHashCode();

View File

@@ -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;

View File

@@ -3,10 +3,11 @@ using System.IO;
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 @@ namespace TweetDuck.Management {
} }
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();
} }

View File

@@ -3,6 +3,7 @@ using System.Drawing;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Windows.Forms; using System.Windows.Forms;
using TweetLib.Core;
namespace TweetDuck.Management { namespace TweetDuck.Management {
static class ClipboardManager { static class ClipboardManager {
@@ -29,7 +30,7 @@ namespace TweetDuck.Management {
try { try {
Clipboard.SetDataObject(obj); Clipboard.SetDataObject(obj);
} catch (ExternalException e) { } catch (ExternalException e) {
Program.Reporter.HandleException("Clipboard Error", "TweetDuck could not access the clipboard as it is currently used by another process.", true, e); App.ErrorHandler.HandleException("Clipboard Error", "TweetDuck could not access the clipboard as it is currently used by another process.", true, e);
} }
} }

View File

@@ -1,10 +1,33 @@
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) {
var form = TryFind<FormBrowser>();
if (form == null) {
action();
}
else {
form.InvokeSafe(action);
}
}
public static void RunOnUIThreadAsync(Action action) {
var form = TryFind<FormBrowser>();
if (form == null) {
action();
}
else {
form.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();
} }

View File

@@ -3,6 +3,7 @@ using System.ComponentModel;
using System.Diagnostics; using System.Diagnostics;
using TweetDuck.Dialogs; using TweetDuck.Dialogs;
using TweetDuck.Utils; using TweetDuck.Utils;
using TweetLib.Core;
using TweetLib.Core.Systems.Startup; using TweetLib.Core.Systems.Startup;
namespace TweetDuck.Management { namespace TweetDuck.Management {
@@ -12,6 +13,8 @@ namespace TweetDuck.Management {
private const int CloseNaturallyTimeout = 10000; private const int CloseNaturallyTimeout = 10000;
private const int CloseKillTimeout = 5000; private const int CloseKillTimeout = 5000;
public uint WindowRestoreMessage { get; } = NativeMethods.RegisterWindowMessage("TweetDuckRestore");
private readonly LockFile lockFile; private readonly LockFile lockFile;
public LockManager(string path) { public LockManager(string path) {
@@ -32,7 +35,7 @@ namespace TweetDuck.Management {
LockResult lockResult = lockFile.Lock(); LockResult lockResult = lockFile.Lock();
if (lockResult is LockResult.HasProcess info) { if (lockResult is LockResult.HasProcess info) {
if (!RestoreProcess(info.Process) && FormMessage.Error("TweetDuck is Already Running", "Another instance of TweetDuck is already running.\nDo you want to close it?", FormMessage.Yes, FormMessage.No)) { if (!RestoreProcess(info.Process, WindowRestoreMessage) && FormMessage.Error("TweetDuck is Already Running", "Another instance of TweetDuck is already running.\nDo you want to close it?", FormMessage.Yes, FormMessage.No)) {
if (!CloseProcess(info.Process)) { if (!CloseProcess(info.Process)) {
FormMessage.Error("TweetDuck Has Failed :(", "Could not close the other process.", FormMessage.OK); FormMessage.Error("TweetDuck Has Failed :(", "Could not close the other process.", FormMessage.OK);
return false; return false;
@@ -79,12 +82,12 @@ namespace TweetDuck.Management {
// Helpers // Helpers
private static void ShowGenericException(LockResult.Fail fail) { private static void ShowGenericException(LockResult.Fail fail) {
Program.Reporter.HandleException("TweetDuck Has Failed :(", "An unknown error occurred accessing the data folder. Please, make sure TweetDuck is not already running. If the problem persists, try restarting your system.", false, fail.Exception); App.ErrorHandler.HandleException("TweetDuck Has Failed :(", "An unknown error occurred accessing the data folder. Please, make sure TweetDuck is not already running. If the problem persists, try restarting your system.", false, fail.Exception);
} }
private static bool RestoreProcess(Process process) { private static bool RestoreProcess(Process process, uint windowRestoreMessage) {
if (process.MainWindowHandle == IntPtr.Zero) { // restore if the original process is in tray if (process.MainWindowHandle == IntPtr.Zero) { // restore if the original process is in tray
NativeMethods.BroadcastMessage(Program.WindowRestoreMessage, (uint) process.Id, 0); NativeMethods.BroadcastMessage(windowRestoreMessage, (uint) process.Id, 0);
if (WindowsUtils.TrySleepUntil(() => CheckProcessExited(process) || (process.MainWindowHandle != IntPtr.Zero && process.Responding), RestoreFailTimeout, WaitRetryDelay)) { if (WindowsUtils.TrySleepUntil(() => CheckProcessExited(process) || (process.MainWindowHandle != IntPtr.Zero && process.Responding), RestoreFailTimeout, WaitRetryDelay)) {
return true; return true;

View File

@@ -2,20 +2,20 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text;
using CefSharp;
using TweetDuck.Dialogs; using TweetDuck.Dialogs;
using TweetLib.Core.Data; 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;
using TweetLib.Utils.IO;
namespace TweetDuck.Management { namespace TweetDuck.Management {
sealed class ProfileManager { sealed class ProfileManager {
private static readonly string CookiesPath = Path.Combine(Program.StoragePath, "Cookies"); private const string AuthCookieUrl = "https://twitter.com";
private static readonly string LocalPrefsPath = Path.Combine(Program.StoragePath, "LocalPrefs.json"); private const string AuthCookieName = "auth_token";
private const string AuthCookieDomain = ".twitter.com";
private static readonly string TempCookiesPath = Path.Combine(Program.StoragePath, "CookiesTmp"); private const string AuthCookiePath = "/";
private static readonly string TempLocalPrefsPath = Path.Combine(Program.StoragePath, "LocalPrefsTmp.json");
private static readonly int SessionFileCount = 2;
[Flags] [Flags]
public enum Items { public enum Items {
@@ -23,8 +23,7 @@ namespace TweetDuck.Management {
UserConfig = 1, UserConfig = 1,
SystemConfig = 2, SystemConfig = 2,
Session = 4, Session = 4,
PluginData = 8, PluginData = 8
All = UserConfig | SystemConfig | Session | PluginData
} }
private readonly string file; private readonly string file;
@@ -40,15 +39,15 @@ namespace TweetDuck.Management {
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", App.ConfigManager.UserPath);
} }
if (items.HasFlag(Items.SystemConfig)) { if (items.HasFlag(Items.SystemConfig)) {
stream.WriteFile("system", Program.SystemConfigFilePath); stream.WriteFile("system", App.ConfigManager.SystemPath);
} }
if (items.HasFlag(Items.PluginData)) { if (items.HasFlag(Items.PluginData)) {
stream.WriteFile("plugin.config", Program.PluginConfigFilePath); stream.WriteFile("plugin.config", App.ConfigManager.PluginsPath);
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))) {
@@ -62,14 +61,20 @@ namespace TweetDuck.Management {
} }
if (items.HasFlag(Items.Session)) { if (items.HasFlag(Items.Session)) {
stream.WriteFile("cookies", CookiesPath); string authToken = ReadAuthCookie();
stream.WriteFile("localprefs", LocalPrefsPath);
if (authToken != null) {
stream.WriteString("cookie.auth", authToken);
}
else {
FormMessage.Warning("Export Profile", "Could not find any login session.", FormMessage.OK);
}
} }
stream.Flush(); stream.Flush();
return true; return true;
} catch (Exception e) { } catch (Exception e) {
Program.Reporter.HandleException("Profile Export Error", "An exception happened while exporting TweetDuck profile.", true, e); App.ErrorHandler.HandleException("Profile Export Error", "An exception happened while exporting TweetDuck profile.", true, e);
return false; return false;
} }
} }
@@ -98,6 +103,7 @@ namespace TweetDuck.Management {
case "cookies": case "cookies":
case "localprefs": case "localprefs":
case "cookie.auth":
items |= Items.Session; items |= Items.Session;
break; break;
} }
@@ -112,7 +118,7 @@ namespace TweetDuck.Management {
public bool Import(Items items) { public bool Import(Items items) {
try { try {
var missingPlugins = new HashSet<string>(); var missingPlugins = new HashSet<string>();
var sessionFiles = new HashSet<string>(); bool oldCookies = false;
using (CombinedFileStream stream = new CombinedFileStream(new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.None))) { using (CombinedFileStream stream = new CombinedFileStream(new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.None))) {
CombinedFileStream.Entry entry; CombinedFileStream.Entry entry;
@@ -121,21 +127,21 @@ namespace TweetDuck.Management {
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(App.ConfigManager.UserPath);
} }
break; break;
case "system": case "system":
if (items.HasFlag(Items.SystemConfig)) { if (items.HasFlag(Items.SystemConfig)) {
entry.WriteToFile(Program.SystemConfigFilePath); entry.WriteToFile(App.ConfigManager.SystemPath);
} }
break; break;
case "plugin.config": case "plugin.config":
if (items.HasFlag(Items.PluginData)) { if (items.HasFlag(Items.PluginData)) {
entry.WriteToFile(Program.PluginConfigFilePath); entry.WriteToFile(App.ConfigManager.PluginsPath);
} }
break; break;
@@ -144,7 +150,7 @@ namespace TweetDuck.Management {
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]);
@@ -154,17 +160,30 @@ namespace TweetDuck.Management {
break; break;
case "cookies": case "cookies":
if (items.HasFlag(Items.Session)) {
entry.WriteToFile(TempCookiesPath);
sessionFiles.Add(entry.KeyName);
}
break;
case "localprefs": case "localprefs":
if (items.HasFlag(Items.Session)) { if (items.HasFlag(Items.Session)) {
entry.WriteToFile(TempLocalPrefsPath); oldCookies = true;
sessionFiles.Add(entry.KeyName); }
break;
case "cookie.auth":
if (items.HasFlag(Items.Session)) {
using ICookieManager cookies = Cef.GetGlobalCookieManager();
var _ = cookies.SetCookieAsync(AuthCookieUrl, new Cookie {
Name = AuthCookieName,
Domain = AuthCookieDomain,
Path = AuthCookiePath,
Value = Encoding.UTF8.GetString(entry.Contents),
Expires = DateTime.Now.Add(TimeSpan.FromDays(365 * 5)),
HttpOnly = true,
Secure = true
}).ContinueWith(t => {
// ReSharper disable once AccessToDisposedClosure
// ReSharper disable once ConvertToLambdaExpression
return cookies.FlushStoreAsync();
}).Result;
} }
break; break;
@@ -172,10 +191,8 @@ namespace TweetDuck.Management {
} }
} }
if (items.HasFlag(Items.Session) && sessionFiles.Count != SessionFileCount) { if (items.HasFlag(Items.Session) && oldCookies) {
FormMessage.Error("Profile Import Error", "Cannot import login session from an older version of TweetDuck.", FormMessage.OK); FormMessage.Error("Profile Import Error", "Cannot import login session from an older version of TweetDuck.", FormMessage.OK);
File.Delete(TempCookiesPath);
File.Delete(TempLocalPrefsPath);
return false; return false;
} }
@@ -185,40 +202,11 @@ namespace TweetDuck.Management {
return true; return true;
} catch (Exception e) { } catch (Exception e) {
Program.Reporter.HandleException("Profile Import", "An exception happened while importing TweetDuck profile.", true, e); App.ErrorHandler.HandleException("Profile Import", "An exception happened while importing TweetDuck profile.", true, e);
return false; return false;
} }
} }
public static void ImportCookies() {
if (File.Exists(TempCookiesPath) && File.Exists(TempLocalPrefsPath)) {
try {
if (File.Exists(CookiesPath)) {
File.Delete(CookiesPath);
}
if (File.Exists(LocalPrefsPath)) {
File.Delete(LocalPrefsPath);
}
File.Move(TempCookiesPath, CookiesPath);
File.Move(TempLocalPrefsPath, LocalPrefsPath);
} catch (Exception e) {
Program.Reporter.HandleException("Profile Import Error", "Could not import the cookie file to restore login session.", true, e);
}
}
}
public static void DeleteCookies() {
try {
if (File.Exists(CookiesPath)) {
File.Delete(CookiesPath);
}
} catch (Exception e) {
Program.Reporter.HandleException("Session Reset Error", "Could not remove the cookie file to reset the login session.", true, e);
}
}
private static IEnumerable<PathInfo> EnumerateFilesRelative(string root) { private static IEnumerable<PathInfo> EnumerateFilesRelative(string root) {
if (Directory.Exists(root)) { if (Directory.Exists(root)) {
int rootLength = root.Length; int rootLength = root.Length;
@@ -238,5 +226,22 @@ namespace TweetDuck.Management {
this.Relative = fullPath.Substring(rootLength).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); // strip leading separator character this.Relative = fullPath.Substring(rootLength).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); // strip leading separator character
} }
} }
private static string ReadAuthCookie() {
using var cookieManager = Cef.GetGlobalCookieManager();
foreach (var cookie in cookieManager.VisitUrlCookiesAsync(AuthCookieUrl, true).Result) {
if (cookie.Name == AuthCookieName && cookie.Domain == AuthCookieDomain && cookie.Path == AuthCookiePath && cookie.HttpOnly && cookie.Secure) {
return cookie.Value;
}
}
return null;
}
public static void DeleteAuthCookie() {
using var cookieManager = Cef.GetGlobalCookieManager();
var _ = cookieManager.DeleteCookiesAsync(AuthCookieUrl, "auth_token").Result;
}
} }
} }

View File

@@ -6,14 +6,15 @@ using TweetDuck.Browser;
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.Systems.Configuration;
namespace TweetDuck.Management { 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;
@@ -38,7 +39,7 @@ namespace TweetDuck.Management {
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
@@ -61,7 +62,7 @@ namespace TweetDuck.Management {
pipe.Dispose(); pipe.Dispose();
} }
} catch (Exception e) { } catch (Exception e) {
Program.Reporter.HandleException("Video Playback Error", "Error launching video player.", true, e); App.ErrorHandler.HandleException("Video Playback Error", "Error launching video player.", true, e);
} }
} }
@@ -82,7 +83,7 @@ namespace TweetDuck.Management {
case "download": case "download":
if (currentInstance != null) { if (currentInstance != null) {
TwitterUtils.DownloadVideo(currentInstance.VideoUrl, currentInstance.Username); owner.SaveVideo(currentInstance.VideoUrl, currentInstance.Username);
} }
break; break;
@@ -136,7 +137,7 @@ namespace TweetDuck.Management {
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);
} }
} }
@@ -154,14 +155,14 @@ namespace TweetDuck.Management {
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;

View File

@@ -3,8 +3,10 @@ using System.Drawing;
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;
using TweetLib.Core.Systems.Configuration;
namespace TweetDuck.Plugins { namespace TweetDuck.Plugins {
sealed partial class PluginControl : UserControl { sealed partial class PluginControl : UserControl {
@@ -80,7 +82,7 @@ namespace TweetDuck.Plugins {
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);
} }
} }
@@ -91,6 +93,7 @@ namespace TweetDuck.Plugins {
private void btnToggleState_Click(object sender, EventArgs e) { private void btnToggleState_Click(object sender, EventArgs e) {
pluginManager.Config.SetEnabled(plugin, !pluginManager.Config.IsEnabled(plugin)); pluginManager.Config.SetEnabled(plugin, !pluginManager.Config.IsEnabled(plugin));
pluginManager.Config.Save();
UpdatePluginState(); UpdatePluginState();
} }

View File

@@ -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));
}
}
}
}

View File

@@ -1,23 +0,0 @@
using CefSharp;
using TweetDuck.Browser.Handling;
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);
}
}
}

View File

@@ -1,22 +1,26 @@
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.Collections; using TweetLib.Core.Application;
using TweetLib.Core.Utils; using TweetLib.Core.Features.Chromium;
using TweetLib.Core.Features.Plugins;
using TweetLib.Core.Features.Plugins.Config;
using TweetLib.Core.Features.TweetDeck;
using TweetLib.Core.Resources;
using TweetLib.Core.Systems.Configuration;
using TweetLib.Utils.Collections;
using Win = System.Windows.Forms; using Win = System.Windows.Forms;
namespace TweetDuck { namespace TweetDuck {
@@ -26,47 +30,17 @@ namespace TweetDuck {
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 InstallerFolder = "TD_Updates";
public static readonly string ExecutablePath = Win.Application.ExecutablePath; private const string CefDataFolder = "TD_Chromium";
private const string ConsoleLogFile = "TD_Console.txt";
public static readonly bool IsPortable = File.Exists(Path.Combine(ProgramPath, "makeportable")); public static string ExecutablePath => Win.Application.ExecutablePath;
public static readonly string ResourcesPath = Path.Combine(ProgramPath, "resources"); private static Reporter errorReporter;
public static readonly string PluginPath = Path.Combine(ProgramPath, "plugins"); private static LockManager lockManager;
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;
private static readonly LockManager LockManager = new LockManager(Path.Combine(StoragePath, ".lock"));
private static bool hasCleanedUp; private static bool hasCleanedUp;
public static Reporter Reporter { get; } public static ConfigObjects<UserConfig, SystemConfig> 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();
@@ -75,76 +49,95 @@ namespace TweetDuck {
[STAThread] [STAThread]
private static void Main() { private static void Main() {
AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
SetupWinForms(); SetupWinForms();
Cef.EnableHighDPISupport(); Cef.EnableHighDPISupport();
WindowRestoreMessage = NativeMethods.RegisterWindowMessage("TweetDuckRestore"); var reporter = new Reporter();
if (!FileUtils.CheckFolderWritePermission(StoragePath)) { Config = new ConfigObjects<UserConfig, SystemConfig>(
FormMessage.Warning("Permission Error", "TweetDuck does not have write permissions to the storage folder: " + StoragePath, FormMessage.OK); new UserConfig(),
return; new SystemConfig(),
new PluginConfig(new string[] {
"official/clear-columns",
"official/reply-account"
})
);
try {
Lib.AppLauncher launch = Lib.Initialize(new AppBuilder {
Setup = new Setup(),
ErrorHandler = reporter,
SystemHandler = new SystemHandler(),
MessageDialogs = new MessageDialogs(),
FileDialogs = new FileDialogs(),
});
errorReporter = reporter;
launch();
} catch (AppException e) {
FormMessage.Error(e.Title, e.Message, FormMessage.OK);
}
} }
if (!LockManager.Lock(Arguments.HasFlag(Arguments.ArgRestart))) { private sealed class Setup : IAppSetup {
return; public bool IsPortable => File.Exists(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "makeportable"));
public bool IsDebugLogging => Arguments.HasFlag(Arguments.ArgLogging);
public string CustomDataFolder => Arguments.GetValue(Arguments.ArgDataFolder);
public string ResourceRewriteRules => Arguments.GetValue(Arguments.ArgFreeze);
public ConfigManager CreateConfigManager(string storagePath) {
return new ConfigManager<UserConfig, SystemConfig>(storagePath, Config);
} }
Config.LoadAll(); public bool TryLockDataFolder(string lockFile) {
lockManager = new LockManager(lockFile);
if (Arguments.HasFlag(Arguments.ArgImportCookies)) { return lockManager.Lock(Arguments.HasFlag(Arguments.ArgRestart));
ProfileManager.ImportCookies();
}
else if (Arguments.HasFlag(Arguments.ArgDeleteCookies)) {
ProfileManager.DeleteCookies();
} }
public void BeforeLaunch() {
if (Arguments.HasFlag(Arguments.ArgUpdated)) { if (Arguments.HasFlag(Arguments.ArgUpdated)) {
WindowsUtils.TryDeleteFolderWhenAble(InstallerPath, 8000); WindowsUtils.TryDeleteFolderWhenAble(Path.Combine(App.StoragePath, InstallerFolder), 8000);
WindowsUtils.TryDeleteFolderWhenAble(Path.Combine(StoragePath, "Service Worker"), 4000); WindowsUtils.TryDeleteFolderWhenAble(Path.Combine(App.StoragePath, "Service Worker"), 4000);
BrowserCache.TryClearNow(); BrowserCache.TryClearNow();
} }
try { if (Config.System.Migrate()) {
ResourceRequestHandlerBase.LoadResourceRewriteRules(Arguments.GetValue(Arguments.ArgFreeze)); Config.System.Save();
} catch (Exception e) { }
FormMessage.Error("Resource Freeze", "Error parsing resource rewrite rules: " + e.Message, FormMessage.OK);
return;
} }
if (Config.User.UseSystemProxyForAllConnections) { public void Launch(ResourceCache resourceCache, PluginManager pluginManager) {
WebUtils.EnableSystemProxy(); string storagePath = App.StoragePath;
}
BrowserCache.RefreshTimer(); BrowserCache.RefreshTimer();
CefSharpSettings.WcfEnabled = false; CefSharpSettings.WcfEnabled = false;
CefSharpSettings.SubprocessExitIfParentProcessClosed = false;
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(); CefSchemeHandlerFactory.Register(settings, new TweetDuckSchemeHandler(resourceCache));
var resourceScheme = new ResourceSchemeFactory(resourceProvider); CefSchemeHandlerFactory.Register(settings, new PluginSchemeHandler(resourceCache, pluginManager));
var pluginScheme = new PluginSchemeFactory(resourceProvider);
settings.SetupCustomScheme(ResourceSchemeFactory.Name, resourceScheme); CefUtils.ParseCommandLineArguments(Config.User.CustomCefArgs).ToDictionary(settings.CefCommandLineArgs);
settings.SetupCustomScheme(PluginSchemeFactory.Name, pluginScheme);
CommandLineArgs.ReadCefArguments(Config.User.CustomCefArgs).ToDictionary(settings.CefCommandLineArgs);
BrowserUtils.SetupCefArgs(settings.CefCommandLineArgs); BrowserUtils.SetupCefArgs(settings.CefCommandLineArgs);
Cef.Initialize(settings, false, new BrowserProcessHandler()); Cef.Initialize(settings, false, new BrowserProcessHandler());
Win.Application.ApplicationExit += (sender, args) => ExitCleanup(); Win.Application.ApplicationExit += (sender, args) => ExitCleanup();
var updateCheckClient = new UpdateCheckClient(Path.Combine(storagePath, InstallerFolder));
FormBrowser mainForm = new FormBrowser(resourceProvider, pluginScheme); var mainForm = new FormBrowser(resourceCache, pluginManager, updateCheckClient, lockManager.WindowRestoreMessage);
Win.Application.Run(mainForm); Win.Application.Run(mainForm);
if (mainForm.UpdateInstaller != null) { if (mainForm.UpdateInstaller != null) {
@@ -158,29 +151,25 @@ namespace TweetDuck {
} }
} }
} }
private static string GetDataStoragePath() {
string custom = Arguments.GetValue(Arguments.ArgDataFolder);
if (custom != null && (custom.Contains(Path.DirectorySeparatorChar) || custom.Contains(Path.AltDirectorySeparatorChar))) {
if (Path.GetInvalidPathChars().Any(custom.Contains)) {
Reporter.HandleEarlyFailure("Data Folder Invalid", "The data folder contains invalid characters:\n" + custom);
}
else if (!Path.IsPathRooted(custom)) {
Reporter.HandleEarlyFailure("Data Folder Invalid", "The data folder has to be either a simple folder name, or a full path:\n" + custom);
} }
return Environment.ExpandEnvironmentVariables(custom); private static void OnUnhandledException(object sender, UnhandledExceptionEventArgs e) {
if (e.ExceptionObject is Exception ex) {
const string title = "TweetDuck Has Failed :(";
string message = "An unhandled exception has occurred: " + ex.Message;
if (errorReporter == null) {
Debug.WriteLine(ex);
Reporter.HandleEarlyFailure(title, message);
} }
else { else {
return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), custom ?? BrandName); errorReporter.HandleException(title, message, false, ex);
}
} }
} }
public static void Restart(params string[] extraArgs) { public static void Restart() {
CommandLineArgs args = Arguments.GetCurrentClean(); RestartWithArgs(Arguments.GetCurrentClean());
CommandLineArgs.ReadStringArray('-', extraArgs, args);
RestartWithArgs(args);
} }
public static void RestartWithArgs(CommandLineArgs args) { public static void RestartWithArgs(CommandLineArgs args) {
@@ -205,12 +194,12 @@ namespace TweetDuck {
return; return;
} }
Config.SaveAll(); App.Close();
Cef.Shutdown(); Cef.Shutdown();
BrowserCache.Exit(); BrowserCache.Exit();
LockManager.Unlock(); lockManager.Unlock();
hasCleanedUp = true; hasCleanedUp = true;
} }
} }

View File

@@ -27,8 +27,3 @@ using TweetDuck;
[assembly: NeutralResourcesLanguage("en")] [assembly: NeutralResourcesLanguage("en")]
[assembly: CLSCompliant(true)] [assembly: CLSCompliant(true)]
#if DEBUG
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("TweetTest.System")]
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("TweetTest.Unit")]
#endif

View File

@@ -60,16 +60,6 @@ namespace TweetDuck.Properties {
} }
} }
/// <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 @@ namespace TweetDuck.Properties {
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));
}
}
} }
} }

View File

@@ -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>

View File

@@ -1,63 +1,32 @@
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 { try {
File.AppendAllText(logFile, build.ToString(), Encoding.UTF8); Process.GetCurrentProcess().Kill();
return true;
} catch { } catch {
return false; Environment.FailFast(message, ex ?? new Exception(message));
} }
} }
public static void HandleEarlyFailure(string caption, string message) {
Program.SetupWinForms();
FormMessage.Error(caption, message, "Exit");
Exit(message);
}
public void HandleException(string caption, string message, bool canIgnore, Exception e) { public void HandleException(string caption, string message, bool canIgnore, Exception e) {
bool loggedSuccessfully = LogImportant(e.ToString()); bool loggedSuccessfully = App.Logger.Error(e.ToString());
FormManager.RunOnUIThread(() => {
string exceptionText = e is ExpandedLogException ? e.Message + "\n\nDetails with potentially sensitive information are in the Error Log." : e.Message; 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); FormMessage form = new FormMessage(caption, message + "\nError: " + exceptionText, canIgnore ? MessageBoxIcon.Warning : MessageBoxIcon.Error);
@@ -79,31 +48,27 @@ namespace TweetDuck {
}; };
btnOpenLog.Click += (sender, args) => { btnOpenLog.Click += (sender, args) => {
using (Process.Start(logFile)) {} if (!OpenLogFile()) {
FormMessage.Error("Error Log", "Cannot open error log.", FormMessage.OK);
}
}; };
form.AddActionControl(btnOpenLog); form.AddActionControl(btnOpenLog);
if (form.ShowDialog() == DialogResult.Ignore) { if (form.ShowDialog() != DialogResult.Ignore) {
return; Exit(message, e);
}
});
} }
private static bool OpenLogFile() {
try { try {
Process.GetCurrentProcess().Kill(); using (Process.Start(App.Logger.LogFilePath)) {}
} catch { } catch (Exception) {
Environment.FailFast(message, e); return false;
}
} }
public static void HandleEarlyFailure(string caption, string message) { return true;
Program.SetupWinForms();
FormMessage.Error(caption, message, "Exit");
try {
Process.GetCurrentProcess().Kill();
} catch {
Environment.FailFast(message, new Exception(message));
}
} }
public sealed class ExpandedLogException : Exception { public sealed class ExpandedLogException : Exception {

View File

@@ -6,6 +6,7 @@
import introduction from "./introduction/introduction.js"; import introduction from "./introduction/introduction.js";
import hide_cookie_bar from "./login/hide_cookie_bar.js"; import hide_cookie_bar from "./login/hide_cookie_bar.js";
import redirect_plain_twitter_com from "./login/redirect_plain_twitter_com";
import setup_document_attributes from "./login/setup_document_attributes.js"; import setup_document_attributes from "./login/setup_document_attributes.js";
import add_skip_button from "./notification/add_skip_button.js"; import add_skip_button from "./notification/add_skip_button.js";
import disable_clipboard_formatting_notification from "./notification/disable_clipboard_formatting.js"; import disable_clipboard_formatting_notification from "./notification/disable_clipboard_formatting.js";
@@ -44,6 +45,7 @@ import limit_loaded_dm_count from "./tweetdeck/limit_loaded_dm_count.js";
import make_retweets_lowercase from "./tweetdeck/make_retweets_lowercase.js"; import make_retweets_lowercase from "./tweetdeck/make_retweets_lowercase.js";
import middle_click_tweet_icon_actions from "./tweetdeck/middle_click_tweet_icon_actions.js"; import middle_click_tweet_icon_actions from "./tweetdeck/middle_click_tweet_icon_actions.js";
import move_accounts_above_hashtags_in_search from "./tweetdeck/move_accounts_above_hashtags_in_search.js"; import move_accounts_above_hashtags_in_search from "./tweetdeck/move_accounts_above_hashtags_in_search.js";
import mute_accounts_with_nft_avatars from "./tweetdeck/mute_accounts_with_nft_avatars.js";
import offline_notification from "./tweetdeck/offline_notification.js"; import offline_notification from "./tweetdeck/offline_notification.js";
import open_search_externally from "./tweetdeck/open_search_externally.js"; import open_search_externally from "./tweetdeck/open_search_externally.js";
import open_search_in_first_column from "./tweetdeck/open_search_in_first_column.js"; import open_search_in_first_column from "./tweetdeck/open_search_in_first_column.js";

View File

@@ -37,6 +37,7 @@ if (!("$TDX" in window)) {
* @property {boolean} [expandLinksOnHover] * @property {boolean} [expandLinksOnHover]
* @property {number} [firstDayOfWeek] * @property {number} [firstDayOfWeek]
* @property {boolean} [focusDmInput] * @property {boolean} [focusDmInput]
* @property {boolean} [hideTweetsByNftUsers]
* @property {boolean} [keepLikeFollowDialogsOpen] * @property {boolean} [keepLikeFollowDialogsOpen]
* @property {boolean} [muteNotifications] * @property {boolean} [muteNotifications]
* @property {boolean} [notificationMediaPreviews] * @property {boolean} [notificationMediaPreviews]

View File

@@ -1,4 +1,4 @@
import { crashDebug } from "../../api/utils.js"; import { crashDebug } from "./utils.js";
/** /**
* @callback FunctionReplacementCallback * @callback FunctionReplacementCallback

View File

@@ -40,6 +40,7 @@ if (!("TD" in window)) {
* @property {TD_Column_Model} model * @property {TD_Column_Model} model
* @property {boolean} notificationsDisabled * @property {boolean} notificationsDisabled
* @property {function} reloadTweets * @property {function} reloadTweets
* @property {ChirpBase[]} updateArray
* @property {{ columnWidth: number }} visibility * @property {{ columnWidth: number }} visibility
*/ */
@@ -136,7 +137,7 @@ if (!("TD" in window)) {
* @property {Class} TwitterActionRetweetedInteraction * @property {Class} TwitterActionRetweetedInteraction
* @property {Class<TwitterClient>} TwitterClient * @property {Class<TwitterClient>} TwitterClient
* @property {Class<TwitterConversation>} TwitterConversation * @property {Class<TwitterConversation>} TwitterConversation
* @property {Class} TwitterConversationMessageEvent * @property {Class<TwitterConversationMessageEvent>} TwitterConversationMessageEvent
* @property {TwitterMedia_Class} TwitterMedia * @property {TwitterMedia_Class} TwitterMedia
* @property {Class<TwitterStatus>} TwitterStatus * @property {Class<TwitterStatus>} TwitterStatus
* @property {Class<TwitterUser>} TwitterUser * @property {Class<TwitterUser>} TwitterUser
@@ -225,13 +226,20 @@ if (!("TD" in window)) {
* @typedef ChirpRenderSettings * @typedef ChirpRenderSettings
* @type {Object} * @type {Object}
* *
* @property {boolean} withFooter * @property {boolean} [isFavorite}
* @property {boolean} withTweetActions * @property {boolean} [isInConvo}
* @property {boolean} isInConvo * @property {boolean} [isMediaPreviewCompact}
* @property {boolean} isFavorite * @property {boolean} [isMediaPreviewInQuoted}
* @property {boolean} isRetweeted * @property {boolean} [isMediaPreviewLarge}
* @property {boolean} isPossiblySensitive * @property {boolean} [isMediaPreviewOff}
* @property {string} mediaPreviewSize * @property {boolean} [isMediaPreviewSmall}
* @property {boolean} [isPossiblySensitive}
* @property {boolean} [isRetweeted}
* @property {string} [mediaPreviewSize}
* @property {string} [thumbSizeClass}
* @property {boolean} [withFooter}
* @property {boolean} [withMediaPreview}
* @property {boolean} [withTweetActions}
*/ */
/** /**
@@ -265,14 +273,19 @@ if (!("TD" in window)) {
* @typedef TwitterClient * @typedef TwitterClient
* @type {Object} * @type {Object}
* *
* @property {string} API_BASE_URL
* @property {function(id: string)} addIdToMuteList
* @property {function(chirp: ChirpBase)} callback * @property {function(chirp: ChirpBase)} callback
* @property {string} chirpId * @property {string} chirpId
* @property {TwitterConversations} conversations * @property {TwitterConversations} conversations
* @property {function(ids: string[], onSuccess: function(users: TwitterUser[]), onError: function)} getUsersByIds * @property {function(ids: string[], onSuccess: function(users: TwitterUser[]), onError: function)} getUsersByIds
* @property {function(url: string, data: object, method: "GET"|"POST", responseProcessor: function, onSuccess: function, onError: function)} makeTwitterCall
* @property {function(json: string[]): TwitterUser[]} processUsers
*/ */
/** /**
* @typedef TwitterConversation * @typedef TwitterConversation
* @extends ChirpBase
* @type {Object} * @type {Object}
* *
* @property {function} markAsRead * @property {function} markAsRead
@@ -352,6 +365,7 @@ if (!("TD" in window)) {
* @typedef TwitterUserJSON * @typedef TwitterUserJSON
* @type {Object} * @type {Object}
* *
* @property {boolean} [ext_has_nft_avatar]
* @property {string} id * @property {string} id
* @property {string} id_str * @property {string} id_str
* @property {string} name * @property {string} name

View File

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -0,0 +1,23 @@
import { replaceFunction } from "../api/patch.js";
function redirectToTweetDeck() {
location.href = "https://tweetdeck.twitter.com";
}
function hookHistoryStateFunction(func, args) {
debugger;
if (args[2] === "/") {
redirectToTweetDeck();
}
else {
func.apply(this, args);
}
}
/**
* Redirects plain twitter.com to TweetDeck, so that users cannot accidentally land on twitter.com login.
*/
export default function() {
replaceFunction(window.history, "pushState", hookHistoryStateFunction);
replaceFunction(window.history, "replaceState", hookHistoryStateFunction);
};

View File

@@ -1,3 +1,6 @@
/**
* Sets up attributes on the <html> element for styling login/logout pages.
*/
export default function() { export default function() {
if (location.pathname === "/login") { if (location.pathname === "/login") {
document.documentElement.setAttribute("login", ""); document.documentElement.setAttribute("login", "");

View File

@@ -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">
@@ -18,7 +18,36 @@
</a> </a>
</header> </header>
<div class="tweet-body"> <div class="tweet-body">
<p class="js-tweet-text tweet-text with-linebreaks">Here you can see the position and appearance of desktop notifications.<br><br>For location and size, you can pick a preset, or select <strong>Custom</strong> and then freely move or resize the window.</p> <p class="js-tweet-text tweet-text">
Here you can see the position and appearance of desktop notifications.<br><br>
For location and size, you can pick a preset, or select <strong>Custom</strong> and then freely move or resize the window.<br><br>
You can use the numbered lines below to test and adjust scroll speed:<br><br>
01<br>
02<br>
03<br>
04<br>
05<br>
06<br>
07<br>
08<br>
09<br>
10<br>
11<br>
12<br>
13<br>
14<br>
15<br>
16<br>
17<br>
18<br>
19<br>
20<br>
21<br>
22<br>
23<br>
24<br>
25
</p>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -34,4 +34,4 @@
onNextFrame(); onNextFrame();
}); });
})(/** @type TD_Screenshot_Bridge */ $TD_NotificationScreenshot); })(/** @type {TD_Screenshot_Bridge} */ $TD_NotificationScreenshot);

View File

@@ -49,7 +49,7 @@ export function loadConfigurationFile(pluginObject, fileNameUser, fileNameDefaul
const token = pluginObject.$token; const token = pluginObject.$token;
$TDP.checkFileExists(token, fileNameUser).then(exists => { $TDP.checkFileExists(token, fileNameUser).then(exists => {
/** @type string|null */ /** @type {string|null} */
const fileName = exists ? fileNameUser : fileNameDefault; const fileName = exists ? fileNameUser : fileNameDefault;
if (fileName === null) { if (fileName === null) {

View File

@@ -1,7 +1,7 @@
import { $TD } from "../api/bridge.js"; import { $TD } from "../api/bridge.js";
import { replaceFunction } from "../api/patch.js";
import { TD } from "../api/td.js"; import { TD } from "../api/td.js";
import { checkPropertyExists } from "../api/utils.js"; import { checkPropertyExists } from "../api/utils.js";
import { replaceFunction } from "./globals/patch_functions.js";
/** /**
* @property {string[]} eventNames * @property {string[]} eventNames

View File

@@ -19,10 +19,16 @@ export default function() {
ensurePropertyExists(TD, "controller", "columnManager", "_columnOrder"); ensurePropertyExists(TD, "controller", "columnManager", "_columnOrder");
ensurePropertyExists(TD, "controller", "columnManager", "move"); ensurePropertyExists(TD, "controller", "columnManager", "move");
$(document).on("uiSearchNoTemporaryColumn", function(e, /** @type SearchEventData */ data) { /**
* @param e
* @param {SearchEventData} data
*/
const onSearch = function(e, data) {
if (data.query && data.searchScope !== "users" && !data.columnKey && !("tduckResetInput" in data)) { if (data.query && data.searchScope !== "users" && !data.columnKey && !("tduckResetInput" in data)) {
$(".js-app-search-input").val(""); $(".js-app-search-input").val("");
$(".js-perform-search").blur(); $(".js-perform-search").blur();
} }
}); };
$(document).on("uiSearchNoTemporaryColumn", onSearch);
}; };

View File

@@ -23,7 +23,7 @@ import { ensurePropertyExists } from "../api/utils.js";
export default function() { export default function() {
ensurePropertyExists($, "tools", "dateinput", "conf", "firstDay"); ensurePropertyExists($, "tools", "dateinput", "conf", "firstDay");
/** @type DateInput */ /** @type {DateInput} */
const dateinput = $["tools"]["dateinput"]; const dateinput = $["tools"]["dateinput"];
onAppReady(function setupDatePickerFirstDayCallback() { onAppReady(function setupDatePickerFirstDayCallback() {

View File

@@ -1,7 +1,7 @@
import { $TDX } from "../api/bridge.js"; import { $TDX } from "../api/bridge.js";
import { replaceFunction } from "../api/patch.js";
import { TD } from "../api/td.js"; import { TD } from "../api/td.js";
import { ensurePropertyExists } from "../api/utils.js"; import { ensurePropertyExists } from "../api/utils.js";
import { replaceFunction } from "./globals/patch_functions.js";
/** /**
* Sets language for automatic translations. * Sets language for automatic translations.

Some files were not shown because too many files have changed in this diff Show More