mirror of
https://github.com/chylex/TweetDuck.git
synced 2025-09-14 10:32:10 +02:00
Compare commits
142 Commits
Author | SHA1 | Date | |
---|---|---|---|
a369c65451 | |||
318f65f187 | |||
1cd60e831c | |||
b988959eaa | |||
1eb1f9848a | |||
7f6cc0da01 | |||
19fcb69525 | |||
22cef0a44c | |||
2459d31bff | |||
19f104239a | |||
bd0be65038 | |||
bbb7907e54 | |||
a6963a18d4 | |||
92716ea3e0 | |||
aec4c1feea | |||
d505b3305b | |||
a34a02e14d | |||
26d2d7a51e | |||
c2f7e52d13 | |||
de68d8934d | |||
4fdf7fc958 | |||
42a5e72f19 | |||
f7359ebc8a | |||
f395ac53dc | |||
1113e0b559 | |||
5e3bd31862 | |||
11d978dad1 | |||
f7961024d7 | |||
72973a8707 | |||
68254f48d5 | |||
eac4f30c50 | |||
25680fa980 | |||
ff5e1da14d | |||
95afff7879 | |||
50bd526025 | |||
108a0fefc3 | |||
dd8c5d27be | |||
b2937bc776 | |||
4d8e764211 | |||
544b8664fd | |||
d0610865bd | |||
ebc0b51590 | |||
4487f1169e | |||
85559b6083 | |||
1056273c57 | |||
61af2ebc8b | |||
9121c86656 | |||
1ccefe853a | |||
aca438b837 | |||
7210c29cd8 | |||
26d90c0c9b | |||
a03b222a95 | |||
7944a24d3c | |||
cc8459c759 | |||
10074ff92c | |||
173f25bebc | |||
31680fc4ae | |||
e937d43614 | |||
20e29a7975 | |||
ef815dabce | |||
1fb133e6b8 | |||
50b58cd6a6 | |||
01485d7ef9 | |||
b17c6a5ac7 | |||
d2ed2b4a00 | |||
710a7524a1 | |||
2be46464d6 | |||
8d536a6734 | |||
250d502238 | |||
e8de7266d0 | |||
9414f372d7 | |||
b0f9de67cf | |||
9b082e114e | |||
816a5334ac | |||
15a4e10da9 | |||
01b9302b0c | |||
442126a11a | |||
a9c140c0fc | |||
97ad7a3e68 | |||
7d737eefb6 | |||
4ac05b38d3 | |||
651bbbb672 | |||
52da4d8687 | |||
36063ae76a | |||
2fcec2d2cd | |||
762a7fdfb7 | |||
cd7aeaeed2 | |||
6f414d312c | |||
1b5304efb7 | |||
d59375308f | |||
8c9509a906 | |||
fb86d8f3a8 | |||
50e909cb3d | |||
2f54edf7e7 | |||
c251603e1e | |||
4476edb6c3 | |||
28fc67660f | |||
6e8b5a5ce5 | |||
e53681416f | |||
acb5e184e8 | |||
bdbafb3e5c | |||
ac70cf87c6 | |||
93de835ab4 | |||
2ea38b88ce | |||
2c4f2be57d | |||
fa4beea425 | |||
7a976edc82 | |||
bb22c35221 | |||
ff3dc59016 | |||
2e4dd3df3e | |||
b82e5d33f9 | |||
65d56b336b | |||
7836d61173 | |||
898437720b | |||
d9a80d1085 | |||
ab3d8b0ae2 | |||
b865074c32 | |||
2b8ca77c0d | |||
fa8b4e1e7f | |||
a310c5bcc1 | |||
b5dccd6b91 | |||
c2bcb39b38 | |||
4515add0a0 | |||
5b6aaec48c | |||
810e56ca31 | |||
5bcc8ac2e0 | |||
f5bfb35867 | |||
9088b8cd07 | |||
a7d90dc708 | |||
5968b57a01 | |||
94946a9ed6 | |||
561aec5ef0 | |||
e31696d843 | |||
8b33fd2002 | |||
064e43750f | |||
29d2f4f681 | |||
c11b40b94a | |||
e874e1d798 | |||
01244ec632 | |||
671657e2b0 | |||
dff7278e2e | |||
0881328636 |
4
.github/FUNDING.yml
vendored
Normal file
4
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# These are supported funding model platforms
|
||||||
|
|
||||||
|
patreon: chylex
|
||||||
|
ko_fi: chylex
|
@@ -1,5 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using TweetDuck.Data;
|
using TweetLib.Core.Collections;
|
||||||
|
|
||||||
namespace TweetDuck.Configuration{
|
namespace TweetDuck.Configuration{
|
||||||
static class Arguments{
|
static class Arguments{
|
||||||
@@ -7,6 +7,7 @@ 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 ArgFreeze = "-freeze";
|
||||||
|
|
||||||
// internal args
|
// internal args
|
||||||
public const string ArgRestart = "-restart";
|
public const string ArgRestart = "-restart";
|
||||||
@@ -21,8 +22,8 @@ namespace TweetDuck.Configuration{
|
|||||||
return Current.HasFlag(flag);
|
return Current.HasFlag(flag);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GetValue(string key, string defaultValue){
|
public static string GetValue(string key){
|
||||||
return Current.GetValue(key, defaultValue);
|
return Current.GetValue(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static CommandLineArgs GetCurrentClean(){
|
public static CommandLineArgs GetCurrentClean(){
|
||||||
|
@@ -1,30 +1,34 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using TweetDuck.Configuration.Instance;
|
|
||||||
using TweetDuck.Core.Utils;
|
|
||||||
using TweetDuck.Data;
|
using TweetDuck.Data;
|
||||||
using TweetDuck.Data.Serialization;
|
using TweetLib.Core.Features.Configuration;
|
||||||
|
using TweetLib.Core.Features.Plugins.Config;
|
||||||
|
using TweetLib.Core.Serialization.Converters;
|
||||||
|
using TweetLib.Core.Utils;
|
||||||
|
|
||||||
namespace TweetDuck.Configuration{
|
namespace TweetDuck.Configuration{
|
||||||
sealed class ConfigManager{
|
sealed class ConfigManager : IConfigManager{
|
||||||
public UserConfig User { get; }
|
public UserConfig User { get; }
|
||||||
public SystemConfig System { get; }
|
public SystemConfig System { get; }
|
||||||
|
public PluginConfig Plugins { get; }
|
||||||
|
|
||||||
public event EventHandler ProgramRestartRequested;
|
public event EventHandler ProgramRestartRequested;
|
||||||
|
|
||||||
private readonly FileConfigInstance<UserConfig> infoUser;
|
private readonly FileConfigInstance<UserConfig> infoUser;
|
||||||
private readonly FileConfigInstance<SystemConfig> infoSystem;
|
private readonly FileConfigInstance<SystemConfig> infoSystem;
|
||||||
|
private readonly PluginConfigInstance<PluginConfig> infoPlugins;
|
||||||
|
|
||||||
private readonly IConfigInstance<BaseConfig>[] infoList;
|
private readonly IConfigInstance<BaseConfig>[] infoList;
|
||||||
|
|
||||||
public ConfigManager(){
|
public ConfigManager(){
|
||||||
User = new UserConfig(this);
|
User = new UserConfig(this);
|
||||||
System = new SystemConfig(this);
|
System = new SystemConfig(this);
|
||||||
|
Plugins = new PluginConfig(this);
|
||||||
|
|
||||||
infoList = new IConfigInstance<BaseConfig>[]{
|
infoList = new IConfigInstance<BaseConfig>[]{
|
||||||
infoUser = new FileConfigInstance<UserConfig>(Program.UserConfigFilePath, User, "program options"),
|
infoUser = new FileConfigInstance<UserConfig>(Program.UserConfigFilePath, User, "program options"),
|
||||||
infoSystem = new FileConfigInstance<SystemConfig>(Program.SystemConfigFilePath, System, "system options")
|
infoSystem = new FileConfigInstance<SystemConfig>(Program.SystemConfigFilePath, System, "system options"),
|
||||||
|
infoPlugins = new PluginConfigInstance<PluginConfig>(Program.PluginConfigFilePath, Plugins)
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO refactor further
|
// TODO refactor further
|
||||||
@@ -51,71 +55,28 @@ namespace TweetDuck.Configuration{
|
|||||||
public void LoadAll(){
|
public void LoadAll(){
|
||||||
infoUser.Load();
|
infoUser.Load();
|
||||||
infoSystem.Load();
|
infoSystem.Load();
|
||||||
|
infoPlugins.Load();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SaveAll(){
|
public void SaveAll(){
|
||||||
infoUser.Save();
|
infoUser.Save();
|
||||||
infoSystem.Save();
|
infoSystem.Save();
|
||||||
|
infoPlugins.Save();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ReloadAll(){
|
public void ReloadAll(){
|
||||||
infoUser.Reload();
|
infoUser.Reload();
|
||||||
infoSystem.Reload();
|
infoSystem.Reload();
|
||||||
|
infoPlugins.Reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TriggerProgramRestartRequested(){
|
void IConfigManager.TriggerProgramRestartRequested(){
|
||||||
ProgramRestartRequested?.Invoke(this, EventArgs.Empty);
|
ProgramRestartRequested?.Invoke(this, EventArgs.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
private IConfigInstance<BaseConfig> GetInstanceInfo(BaseConfig instance){
|
IConfigInstance<BaseConfig> IConfigManager.GetInstanceInfo(BaseConfig instance){
|
||||||
Type instanceType = instance.GetType();
|
Type instanceType = instance.GetType();
|
||||||
return Array.Find(infoList, info => info.Instance.GetType() == instanceType); // TODO handle null
|
return Array.Find(infoList, info => info.Instance.GetType() == instanceType); // TODO handle null
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract class BaseConfig{
|
|
||||||
private readonly ConfigManager configManager;
|
|
||||||
|
|
||||||
protected BaseConfig(ConfigManager configManager){
|
|
||||||
this.configManager = configManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Management
|
|
||||||
|
|
||||||
public void Save(){
|
|
||||||
configManager.GetInstanceInfo(this).Save();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Reload(){
|
|
||||||
configManager.GetInstanceInfo(this).Reload();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Reset(){
|
|
||||||
configManager.GetInstanceInfo(this).Reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Construction methods
|
|
||||||
|
|
||||||
public T ConstructWithDefaults<T>() where T : BaseConfig{
|
|
||||||
return ConstructWithDefaults(configManager) as T;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract BaseConfig ConstructWithDefaults(ConfigManager configManager);
|
|
||||||
|
|
||||||
// Utility methods
|
|
||||||
|
|
||||||
protected void UpdatePropertyWithEvent<T>(ref T field, T value, EventHandler eventHandler){
|
|
||||||
if (!EqualityComparer<T>.Default.Equals(field, value)){
|
|
||||||
field = value;
|
|
||||||
eventHandler?.Invoke(this, EventArgs.Empty);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void UpdatePropertyWithRestartRequest<T>(ref T field, T value){
|
|
||||||
if (!EqualityComparer<T>.Default.Equals(field, value)){
|
|
||||||
field = value;
|
|
||||||
configManager.TriggerProgramRestartRequested();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
51
Configuration/PluginConfig.cs
Normal file
51
Configuration/PluginConfig.cs
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using TweetLib.Core.Features.Configuration;
|
||||||
|
using TweetLib.Core.Features.Plugins;
|
||||||
|
using TweetLib.Core.Features.Plugins.Config;
|
||||||
|
using TweetLib.Core.Features.Plugins.Events;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,5 +1,7 @@
|
|||||||
namespace TweetDuck.Configuration{
|
using TweetLib.Core.Features.Configuration;
|
||||||
sealed class SystemConfig : ConfigManager.BaseConfig{
|
|
||||||
|
namespace TweetDuck.Configuration{
|
||||||
|
sealed class SystemConfig : BaseConfig{
|
||||||
|
|
||||||
// CONFIGURATION DATA
|
// CONFIGURATION DATA
|
||||||
|
|
||||||
@@ -17,9 +19,9 @@
|
|||||||
|
|
||||||
// END OF CONFIG
|
// END OF CONFIG
|
||||||
|
|
||||||
public SystemConfig(ConfigManager configManager) : base(configManager){}
|
public SystemConfig(IConfigManager configManager) : base(configManager){}
|
||||||
|
|
||||||
protected override ConfigManager.BaseConfig ConstructWithDefaults(ConfigManager configManager){
|
protected override BaseConfig ConstructWithDefaults(IConfigManager configManager){
|
||||||
return new SystemConfig(configManager);
|
return new SystemConfig(configManager);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,13 +1,14 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using TweetDuck.Core.Controls;
|
using TweetDuck.Core.Controls;
|
||||||
using TweetDuck.Core.Notification;
|
|
||||||
using TweetDuck.Core.Other;
|
using TweetDuck.Core.Other;
|
||||||
using TweetDuck.Core.Utils;
|
|
||||||
using TweetDuck.Data;
|
using TweetDuck.Data;
|
||||||
|
using TweetLib.Core.Features.Configuration;
|
||||||
|
using TweetLib.Core.Features.Notifications;
|
||||||
|
using TweetLib.Core.Features.Twitter;
|
||||||
|
|
||||||
namespace TweetDuck.Configuration{
|
namespace TweetDuck.Configuration{
|
||||||
sealed class UserConfig : ConfigManager.BaseConfig{
|
sealed class UserConfig : BaseConfig{
|
||||||
|
|
||||||
// CONFIGURATION DATA
|
// CONFIGURATION DATA
|
||||||
|
|
||||||
@@ -18,14 +19,15 @@ namespace TweetDuck.Configuration{
|
|||||||
public Size PluginsWindowSize { get; set; } = Size.Empty;
|
public Size PluginsWindowSize { get; set; } = Size.Empty;
|
||||||
|
|
||||||
public bool ExpandLinksOnHover { get; set; } = true;
|
public bool ExpandLinksOnHover { get; set; } = true;
|
||||||
|
public bool FocusDmInput { get; set; } = true;
|
||||||
public bool OpenSearchInFirstColumn { get; set; } = true;
|
public bool OpenSearchInFirstColumn { get; set; } = true;
|
||||||
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 _enableSmoothScrolling = true;
|
private bool _enableSmoothScrolling = true;
|
||||||
public bool _enableTouchAdjustment = false;
|
private bool _enableTouchAdjustment = false;
|
||||||
public string _customCefArgs = null;
|
private string _customCefArgs = null;
|
||||||
|
|
||||||
public string BrowserPath { get; set; } = null;
|
public string BrowserPath { get; set; } = null;
|
||||||
public bool IgnoreTrackingUrlWarning { get; set; } = false;
|
public bool IgnoreTrackingUrlWarning { get; set; } = false;
|
||||||
@@ -55,14 +57,14 @@ namespace TweetDuck.Configuration{
|
|||||||
public bool NotificationTimerCountDown { get; set; } = false;
|
public bool NotificationTimerCountDown { get; set; } = false;
|
||||||
public int NotificationDurationValue { get; set; } = 25;
|
public int NotificationDurationValue { get; set; } = 25;
|
||||||
|
|
||||||
public TweetNotification.Position NotificationPosition { get; set; } = TweetNotification.Position.TopRight;
|
public DesktopNotification.Position NotificationPosition { get; set; } = DesktopNotification.Position.TopRight;
|
||||||
public Point CustomNotificationPosition { get; set; } = ControlExtensions.InvisibleLocation;
|
public Point CustomNotificationPosition { get; set; } = ControlExtensions.InvisibleLocation;
|
||||||
public int NotificationDisplay { get; set; } = 0;
|
public int NotificationDisplay { get; set; } = 0;
|
||||||
public int NotificationEdgeDistance { get; set; } = 8;
|
public int NotificationEdgeDistance { get; set; } = 8;
|
||||||
|
|
||||||
public TweetNotification.Size NotificationSize { get; set; } = TweetNotification.Size.Auto;
|
public DesktopNotification.Size NotificationSize { get; set; } = DesktopNotification.Size.Auto;
|
||||||
public Size CustomNotificationSize { get; set; } = Size.Empty;
|
public Size CustomNotificationSize { get; set; } = Size.Empty;
|
||||||
public int NotificationScrollSpeed { get; set; } = 100;
|
public int NotificationScrollSpeed { get; set; } = 100;
|
||||||
|
|
||||||
private string _notificationSoundPath;
|
private string _notificationSoundPath;
|
||||||
private int _notificationSoundVolume = 100;
|
private int _notificationSoundVolume = 100;
|
||||||
@@ -78,7 +80,7 @@ namespace TweetDuck.Configuration{
|
|||||||
public bool IsCustomNotificationSizeSet => CustomNotificationSize != Size.Empty;
|
public bool IsCustomNotificationSizeSet => CustomNotificationSize != Size.Empty;
|
||||||
public bool IsCustomSoundNotificationSet => NotificationSoundPath != string.Empty;
|
public bool IsCustomSoundNotificationSet => NotificationSoundPath != string.Empty;
|
||||||
|
|
||||||
public TwitterUtils.ImageQuality TwitterImageQuality => BestImageQuality ? TwitterUtils.ImageQuality.Orig : TwitterUtils.ImageQuality.Default;
|
public ImageQuality TwitterImageQuality => BestImageQuality ? ImageQuality.Best : ImageQuality.Default;
|
||||||
|
|
||||||
public string NotificationSoundPath{
|
public string NotificationSoundPath{
|
||||||
get => _notificationSoundPath ?? string.Empty;
|
get => _notificationSoundPath ?? string.Empty;
|
||||||
@@ -134,9 +136,9 @@ namespace TweetDuck.Configuration{
|
|||||||
|
|
||||||
// END OF CONFIG
|
// END OF CONFIG
|
||||||
|
|
||||||
public UserConfig(ConfigManager configManager) : base(configManager){}
|
public UserConfig(IConfigManager configManager) : base(configManager){}
|
||||||
|
|
||||||
protected override ConfigManager.BaseConfig ConstructWithDefaults(ConfigManager configManager){
|
protected override BaseConfig ConstructWithDefaults(IConfigManager configManager){
|
||||||
return new UserConfig(configManager);
|
return new UserConfig(configManager);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
41
Core/Adapters/CefScriptExecutor.cs
Normal file
41
Core/Adapters/CefScriptExecutor.cs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
using System.IO;
|
||||||
|
using CefSharp;
|
||||||
|
using TweetLib.Core.Browser;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.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.ExecuteScriptAsync(name, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RunScript(string identifier, string script){
|
||||||
|
using IFrame frame = browser.GetMainFrame();
|
||||||
|
RunScript(frame, script, identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool RunFile(string file){
|
||||||
|
using IFrame frame = browser.GetMainFrame();
|
||||||
|
return RunFile(frame, file);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helpers
|
||||||
|
|
||||||
|
public static void RunScript(IFrame frame, string script, string identifier){
|
||||||
|
if (script != null){
|
||||||
|
frame.ExecuteJavaScriptAsync(script, identifier, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool RunFile(IFrame frame, string file){
|
||||||
|
string script = Program.Resources.Load(file);
|
||||||
|
RunScript(frame, script, "root:" + Path.GetFileNameWithoutExtension(file));
|
||||||
|
return script != null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,4 +1,5 @@
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
|
using TweetDuck.Configuration;
|
||||||
|
|
||||||
namespace TweetDuck.Core.Bridge{
|
namespace TweetDuck.Core.Bridge{
|
||||||
static class PropertyBridge{
|
static class PropertyBridge{
|
||||||
@@ -7,23 +8,25 @@ namespace TweetDuck.Core.Bridge{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static string GenerateScript(Environment environment){
|
public static string GenerateScript(Environment environment){
|
||||||
string Bool(bool value) => value ? "true;" : "false;";
|
static string Bool(bool value) => value ? "true;" : "false;";
|
||||||
string Str(string value) => '"'+value+"\";";
|
static string Str(string value) => $"\"{value}\";";
|
||||||
|
|
||||||
StringBuilder build = new StringBuilder().Append("(function(x){");
|
UserConfig config = Program.Config.User;
|
||||||
|
StringBuilder build = new StringBuilder(128).Append("(function(x){");
|
||||||
|
|
||||||
build.Append("x.expandLinksOnHover=").Append(Bool(Program.UserConfig.ExpandLinksOnHover));
|
build.Append("x.expandLinksOnHover=").Append(Bool(config.ExpandLinksOnHover));
|
||||||
|
|
||||||
if (environment == Environment.Browser){
|
if (environment == Environment.Browser){
|
||||||
build.Append("x.openSearchInFirstColumn=").Append(Bool(Program.UserConfig.OpenSearchInFirstColumn));
|
build.Append("x.focusDmInput=").Append(Bool(config.FocusDmInput));
|
||||||
build.Append("x.keepLikeFollowDialogsOpen=").Append(Bool(Program.UserConfig.KeepLikeFollowDialogsOpen));
|
build.Append("x.openSearchInFirstColumn=").Append(Bool(config.OpenSearchInFirstColumn));
|
||||||
build.Append("x.muteNotifications=").Append(Bool(Program.UserConfig.MuteNotifications));
|
build.Append("x.keepLikeFollowDialogsOpen=").Append(Bool(config.KeepLikeFollowDialogsOpen));
|
||||||
build.Append("x.notificationMediaPreviews=").Append(Bool(Program.UserConfig.NotificationMediaPreviews));
|
build.Append("x.muteNotifications=").Append(Bool(config.MuteNotifications));
|
||||||
build.Append("x.translationTarget=").Append(Str(Program.UserConfig.TranslationTarget));
|
build.Append("x.notificationMediaPreviews=").Append(Bool(config.NotificationMediaPreviews));
|
||||||
|
build.Append("x.translationTarget=").Append(Str(config.TranslationTarget));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (environment == Environment.Notification){
|
if (environment == Environment.Notification){
|
||||||
build.Append("x.skipOnLinkClick=").Append(Bool(Program.UserConfig.NotificationSkipOnLinkClick));
|
build.Append("x.skipOnLinkClick=").Append(Bool(config.NotificationSkipOnLinkClick));
|
||||||
}
|
}
|
||||||
|
|
||||||
return build.Append("})(window.$TDX=window.$TDX||{})").ToString();
|
return build.Append("})(window.$TDX=window.$TDX||{})").ToString();
|
||||||
|
@@ -1,37 +1,18 @@
|
|||||||
using System.Collections.Generic;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Text;
|
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using CefSharp;
|
|
||||||
using TweetDuck.Core.Controls;
|
using TweetDuck.Core.Controls;
|
||||||
using TweetDuck.Core.Management;
|
using TweetDuck.Core.Handling;
|
||||||
using TweetDuck.Core.Notification;
|
using TweetDuck.Core.Notification;
|
||||||
using TweetDuck.Core.Other;
|
using TweetDuck.Core.Other;
|
||||||
using TweetDuck.Core.Utils;
|
using TweetDuck.Core.Utils;
|
||||||
using TweetDuck.Resources;
|
using TweetLib.Core.Features.Notifications;
|
||||||
|
|
||||||
namespace TweetDuck.Core.Bridge{
|
namespace TweetDuck.Core.Bridge{
|
||||||
|
[SuppressMessage("ReSharper", "UnusedMember.Global")]
|
||||||
class TweetDeckBridge{
|
class TweetDeckBridge{
|
||||||
public static string FontSize { get; private set; }
|
|
||||||
public static string NotificationHeadLayout { get; private set; }
|
|
||||||
public static readonly ContextInfo ContextInfo = new ContextInfo();
|
|
||||||
|
|
||||||
private static readonly Dictionary<string, string> SessionData = new Dictionary<string, string>(2);
|
|
||||||
|
|
||||||
public static void ResetStaticProperties(){
|
public static void ResetStaticProperties(){
|
||||||
FontSize = NotificationHeadLayout = null;
|
FormNotificationBase.FontSize = null;
|
||||||
}
|
FormNotificationBase.HeadLayout = null;
|
||||||
|
|
||||||
public static void RestoreSessionData(IFrame frame){
|
|
||||||
if (SessionData.Count > 0){
|
|
||||||
StringBuilder build = new StringBuilder().Append("window.TD_SESSION={");
|
|
||||||
|
|
||||||
foreach(KeyValuePair<string, string> kvp in SessionData){
|
|
||||||
build.Append(kvp.Key).Append(":'").Append(kvp.Value.Replace("'", "\\'")).Append("',");
|
|
||||||
}
|
|
||||||
|
|
||||||
ScriptLoader.ExecuteScript(frame, build.Append("}").ToString(), "gen:session");
|
|
||||||
SessionData.Clear();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly FormBrowser form;
|
private readonly FormBrowser form;
|
||||||
@@ -63,32 +44,26 @@ namespace TweetDuck.Core.Bridge{
|
|||||||
|
|
||||||
public void LoadNotificationLayout(string fontSize, string headLayout){
|
public void LoadNotificationLayout(string fontSize, string headLayout){
|
||||||
form.InvokeAsyncSafe(() => {
|
form.InvokeAsyncSafe(() => {
|
||||||
FontSize = fontSize;
|
FormNotificationBase.FontSize = fontSize;
|
||||||
NotificationHeadLayout = headLayout;
|
FormNotificationBase.HeadLayout = headLayout;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetRightClickedLink(string type, string url){
|
public void SetRightClickedLink(string type, string url){
|
||||||
ContextInfo.SetLink(type, url);
|
ContextMenuBase.CurrentInfo.SetLink(type, url);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetRightClickedChirp(string tweetUrl, string quoteUrl, string chirpAuthors, string chirpImages){
|
public void SetRightClickedChirp(string tweetUrl, string quoteUrl, string chirpAuthors, string chirpImages){
|
||||||
ContextInfo.SetChirp(tweetUrl, quoteUrl, chirpAuthors, chirpImages);
|
ContextMenuBase.CurrentInfo.SetChirp(tweetUrl, quoteUrl, chirpAuthors, chirpImages);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DisplayTooltip(string text){
|
public void DisplayTooltip(string text){
|
||||||
form.InvokeAsyncSafe(() => form.DisplayTooltip(text));
|
form.InvokeAsyncSafe(() => form.DisplayTooltip(text));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetSessionData(string key, string value){
|
|
||||||
form.InvokeSafe(() => { // do not use InvokeAsyncSafe, return only after invocation
|
|
||||||
SessionData.Add(key, value);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notification only
|
// Notification only
|
||||||
|
|
||||||
public sealed class Notification : TweetDeckBridge{
|
public sealed class Notification : TweetDeckBridge{
|
||||||
public Notification(FormBrowser form, FormNotificationMain notification) : base(form, notification){}
|
public Notification(FormBrowser form, FormNotificationMain notification) : base(form, notification){}
|
||||||
|
|
||||||
@@ -110,7 +85,7 @@ namespace TweetDuck.Core.Bridge{
|
|||||||
public void OnTweetPopup(string columnId, string chirpId, string columnName, string tweetHtml, int tweetCharacters, string tweetUrl, string quoteUrl){
|
public void OnTweetPopup(string columnId, string chirpId, string columnName, string tweetHtml, int tweetCharacters, string tweetUrl, string quoteUrl){
|
||||||
notification.InvokeAsyncSafe(() => {
|
notification.InvokeAsyncSafe(() => {
|
||||||
form.OnTweetNotification();
|
form.OnTweetNotification();
|
||||||
notification.ShowNotification(new TweetNotification(columnId, chirpId, columnName, tweetHtml, tweetCharacters, tweetUrl, quoteUrl));
|
notification.ShowNotification(new DesktopNotification(columnId, chirpId, columnName, tweetHtml, tweetCharacters, tweetUrl, quoteUrl));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,14 +117,12 @@ namespace TweetDuck.Core.Bridge{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void Alert(string type, string contents){
|
public void Alert(string type, string contents){
|
||||||
MessageBoxIcon icon;
|
MessageBoxIcon icon = type switch{
|
||||||
|
"error" => MessageBoxIcon.Error,
|
||||||
switch(type){
|
"warning" => MessageBoxIcon.Warning,
|
||||||
case "error": icon = MessageBoxIcon.Error; break;
|
"info" => MessageBoxIcon.Information,
|
||||||
case "warning": icon = MessageBoxIcon.Warning; break;
|
_ => MessageBoxIcon.None
|
||||||
case "info": icon = MessageBoxIcon.Information; break;
|
};
|
||||||
default: icon = MessageBoxIcon.None; break;
|
|
||||||
}
|
|
||||||
|
|
||||||
FormMessage.Show("TweetDuck Browser Message", contents, icon, FormMessage.OK);
|
FormMessage.Show("TweetDuck Browser Message", contents, icon, FormMessage.OK);
|
||||||
}
|
}
|
||||||
|
@@ -1,9 +1,11 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using TweetDuck.Core.Controls;
|
using TweetDuck.Core.Controls;
|
||||||
using TweetDuck.Updates;
|
using TweetLib.Core.Features.Updates;
|
||||||
|
|
||||||
namespace TweetDuck.Core.Bridge{
|
namespace TweetDuck.Core.Bridge{
|
||||||
|
[SuppressMessage("ReSharper", "UnusedMember.Global")]
|
||||||
class UpdateBridge{
|
class UpdateBridge{
|
||||||
private readonly UpdateHandler updates;
|
private readonly UpdateHandler updates;
|
||||||
private readonly Control sync;
|
private readonly Control sync;
|
||||||
@@ -11,7 +13,6 @@ namespace TweetDuck.Core.Bridge{
|
|||||||
private UpdateInfo nextUpdate = null;
|
private UpdateInfo nextUpdate = null;
|
||||||
|
|
||||||
public event EventHandler<UpdateInfo> UpdateAccepted;
|
public event EventHandler<UpdateInfo> UpdateAccepted;
|
||||||
public event EventHandler<UpdateInfo> UpdateDelayed;
|
|
||||||
public event EventHandler<UpdateInfo> UpdateDismissed;
|
public event EventHandler<UpdateInfo> UpdateDismissed;
|
||||||
|
|
||||||
public UpdateBridge(UpdateHandler updates, Control sync){
|
public UpdateBridge(UpdateHandler updates, Control sync){
|
||||||
@@ -54,10 +55,6 @@ namespace TweetDuck.Core.Bridge{
|
|||||||
HandleInteractionEvent(UpdateAccepted);
|
HandleInteractionEvent(UpdateAccepted);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnUpdateDelayed(){
|
|
||||||
HandleInteractionEvent(UpdateDelayed);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnUpdateDismissed(){
|
public void OnUpdateDismissed(){
|
||||||
HandleInteractionEvent(UpdateDismissed);
|
HandleInteractionEvent(UpdateDismissed);
|
||||||
|
|
||||||
|
@@ -21,9 +21,8 @@ namespace TweetDuck.Core.Controls{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static float GetDPIScale(this Control control){
|
public static float GetDPIScale(this Control control){
|
||||||
using(Graphics graphics = control.CreateGraphics()){
|
using Graphics graphics = control.CreateGraphics();
|
||||||
return graphics.DpiY/96F;
|
return graphics.DpiY / 96F;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool IsFullyOutsideView(this Form form){
|
public static bool IsFullyOutsideView(this Form form){
|
||||||
@@ -31,17 +30,17 @@ namespace TweetDuck.Core.Controls{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void MoveToCenter(this Form targetForm, Form parentForm){
|
public static void MoveToCenter(this Form targetForm, Form parentForm){
|
||||||
targetForm.Location = new Point(parentForm.Location.X+parentForm.Width/2-targetForm.Width/2, parentForm.Location.Y+parentForm.Height/2-targetForm.Height/2);
|
targetForm.Location = new Point(parentForm.Location.X + (parentForm.Width / 2) - (targetForm.Width / 2), parentForm.Location.Y + (parentForm.Height / 2) - (targetForm.Height / 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void SetValueInstant(this ProgressBar bar, int value){
|
public static void SetValueInstant(this ProgressBar bar, int value){
|
||||||
if (value == bar.Maximum){
|
if (value == bar.Maximum){
|
||||||
bar.Value = value;
|
bar.Value = value;
|
||||||
bar.Value = value-1;
|
bar.Value = value - 1;
|
||||||
bar.Value = value;
|
bar.Value = value;
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
bar.Value = value+1;
|
bar.Value = value + 1;
|
||||||
bar.Value = value;
|
bar.Value = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -60,10 +59,11 @@ namespace TweetDuck.Core.Controls{
|
|||||||
|
|
||||||
public static bool AlignValueToTick(this TrackBar trackBar){
|
public static bool AlignValueToTick(this TrackBar trackBar){
|
||||||
if (trackBar.Value % trackBar.SmallChange != 0){
|
if (trackBar.Value % trackBar.SmallChange != 0){
|
||||||
trackBar.Value = trackBar.SmallChange*(int)Math.Floor(((double)trackBar.Value/trackBar.SmallChange)+0.5);
|
trackBar.Value = trackBar.SmallChange * (int)Math.Floor(((double)trackBar.Value / trackBar.SmallChange) + 0.5);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else return true;
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void EnableMultilineShortcuts(this TextBox textBox){
|
public static void EnableMultilineShortcuts(this TextBox textBox){
|
||||||
|
@@ -23,7 +23,7 @@ namespace TweetDuck.Core.Controls{
|
|||||||
}
|
}
|
||||||
|
|
||||||
Rectangle rect = e.ClipRectangle;
|
Rectangle rect = e.ClipRectangle;
|
||||||
rect.Width = (int)(rect.Width*((double)Value/Maximum));
|
rect.Width = (int)(rect.Width * ((double)Value / Maximum));
|
||||||
e.Graphics.FillRectangle(brush, rect);
|
e.Graphics.FillRectangle(brush, rect);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,13 +1,8 @@
|
|||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using TweetDuck.Core.Utils;
|
using TweetDuck.Core.Utils;
|
||||||
|
|
||||||
namespace TweetDuck.Plugins.Controls{
|
namespace TweetDuck.Core.Controls{
|
||||||
sealed class PluginListFlowLayout : FlowLayoutPanel{
|
sealed class FlowLayoutPanelNoHScroll : FlowLayoutPanel{
|
||||||
public PluginListFlowLayout(){
|
|
||||||
FlowDirection = FlowDirection.TopDown;
|
|
||||||
WrapContents = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void WndProc(ref Message m){
|
protected override void WndProc(ref Message m){
|
||||||
if (m.Msg == 0x85){ // WM_NCPAINT
|
if (m.Msg == 0x85){ // WM_NCPAINT
|
||||||
NativeMethods.ShowScrollBar(Handle, NativeMethods.SB_HORZ, false); // basically fuck the horizontal scrollbar very much
|
NativeMethods.ShowScrollBar(Handle, NativeMethods.SB_HORZ, false); // basically fuck the horizontal scrollbar very much
|
@@ -7,16 +7,15 @@ namespace TweetDuck.Core.Controls{
|
|||||||
public int LineHeight { get; set; }
|
public int LineHeight { get; set; }
|
||||||
|
|
||||||
protected override void OnPaint(PaintEventArgs e){
|
protected override void OnPaint(PaintEventArgs e){
|
||||||
int y = (int)Math.Floor((ClientRectangle.Height-Text.Length*LineHeight)/2F)-1;
|
int y = (int)Math.Floor((ClientRectangle.Height - Text.Length * LineHeight) / 2F) - 1;
|
||||||
|
using Brush brush = new SolidBrush(ForeColor);
|
||||||
|
|
||||||
using(Brush brush = new SolidBrush(ForeColor)){
|
foreach(char chr in Text){
|
||||||
foreach(char chr in Text){
|
string str = chr.ToString();
|
||||||
string str = chr.ToString();
|
float x = (ClientRectangle.Width - e.Graphics.MeasureString(str, Font).Width) / 2F;
|
||||||
float x = (ClientRectangle.Width-e.Graphics.MeasureString(str, Font).Width)/2F;
|
|
||||||
|
|
||||||
e.Graphics.DrawString(str, Font, brush, x, y);
|
e.Graphics.DrawString(str, Font, brush, x, y);
|
||||||
y += LineHeight;
|
y += LineHeight;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using TweetDuck.Configuration;
|
using TweetDuck.Configuration;
|
||||||
using TweetDuck.Core.Bridge;
|
using TweetDuck.Core.Bridge;
|
||||||
@@ -13,14 +15,14 @@ using TweetDuck.Core.Other;
|
|||||||
using TweetDuck.Core.Other.Analytics;
|
using TweetDuck.Core.Other.Analytics;
|
||||||
using TweetDuck.Core.Other.Settings.Dialogs;
|
using TweetDuck.Core.Other.Settings.Dialogs;
|
||||||
using TweetDuck.Core.Utils;
|
using TweetDuck.Core.Utils;
|
||||||
using TweetDuck.Plugins;
|
|
||||||
using TweetDuck.Plugins.Enums;
|
|
||||||
using TweetDuck.Plugins.Events;
|
|
||||||
using TweetDuck.Updates;
|
using TweetDuck.Updates;
|
||||||
|
using TweetLib.Core.Features.Plugins;
|
||||||
|
using TweetLib.Core.Features.Plugins.Events;
|
||||||
|
using TweetLib.Core.Features.Updates;
|
||||||
|
|
||||||
namespace TweetDuck.Core{
|
namespace TweetDuck.Core{
|
||||||
sealed partial class FormBrowser : Form, AnalyticsFile.IProvider{
|
sealed partial class FormBrowser : Form, AnalyticsFile.IProvider{
|
||||||
private static UserConfig Config => Program.UserConfig;
|
private static UserConfig Config => Program.Config.User;
|
||||||
|
|
||||||
public bool IsWaiting{
|
public bool IsWaiting{
|
||||||
set{
|
set{
|
||||||
@@ -63,7 +65,7 @@ namespace TweetDuck.Core{
|
|||||||
|
|
||||||
Text = Program.BrandName;
|
Text = Program.BrandName;
|
||||||
|
|
||||||
this.plugins = new PluginManager(Program.PluginPath, Program.PluginConfigFilePath);
|
this.plugins = new PluginManager(Program.Config.Plugins, Program.PluginPath, Program.PluginDataPath);
|
||||||
this.plugins.Reloaded += plugins_Reloaded;
|
this.plugins.Reloaded += plugins_Reloaded;
|
||||||
this.plugins.Executed += plugins_Executed;
|
this.plugins.Executed += plugins_Executed;
|
||||||
this.plugins.Reload();
|
this.plugins.Reload();
|
||||||
@@ -71,19 +73,16 @@ namespace TweetDuck.Core{
|
|||||||
this.notification = new FormNotificationTweet(this, plugins);
|
this.notification = new FormNotificationTweet(this, plugins);
|
||||||
this.notification.Show();
|
this.notification.Show();
|
||||||
|
|
||||||
this.updates = new UpdateHandler(Program.InstallerPath);
|
this.updates = new UpdateHandler(new UpdateCheckClient(Program.InstallerPath), TaskScheduler.FromCurrentSynchronizationContext());
|
||||||
this.updates.CheckFinished += updates_CheckFinished;
|
this.updates.CheckFinished += updates_CheckFinished;
|
||||||
|
|
||||||
this.updateBridge = new UpdateBridge(updates, this);
|
this.updateBridge = new UpdateBridge(updates, this);
|
||||||
this.updateBridge.UpdateAccepted += updateBridge_UpdateAccepted;
|
this.updateBridge.UpdateAccepted += updateBridge_UpdateAccepted;
|
||||||
this.updateBridge.UpdateDelayed += updateBridge_UpdateDelayed;
|
|
||||||
this.updateBridge.UpdateDismissed += updateBridge_UpdateDismissed;
|
this.updateBridge.UpdateDismissed += updateBridge_UpdateDismissed;
|
||||||
|
|
||||||
this.browser = new TweetDeckBrowser(this, new TweetDeckBridge.Browser(this, notification), updateBridge);
|
this.browser = new TweetDeckBrowser(this, plugins, new TweetDeckBridge.Browser(this, notification), updateBridge);
|
||||||
this.contextMenu = ContextMenuBrowser.CreateMenu(this);
|
this.contextMenu = ContextMenuBrowser.CreateMenu(this);
|
||||||
|
|
||||||
this.plugins.Register(browser, PluginEnvironment.Browser, this, true);
|
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
Disposed += (sender, args) => {
|
Disposed += (sender, args) => {
|
||||||
@@ -236,7 +235,9 @@ namespace TweetDuck.Core{
|
|||||||
|
|
||||||
private void plugins_Reloaded(object sender, PluginErrorEventArgs e){
|
private void plugins_Reloaded(object sender, PluginErrorEventArgs e){
|
||||||
if (e.HasErrors){
|
if (e.HasErrors){
|
||||||
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);
|
this.InvokeAsyncSafe(() => { // TODO not needed but makes code consistent...
|
||||||
|
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){
|
if (isLoaded){
|
||||||
@@ -244,9 +245,11 @@ namespace TweetDuck.Core{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void plugins_Executed(object sender, PluginErrorEventArgs e){
|
private void plugins_Executed(object sender, PluginErrorEventArgs e){
|
||||||
if (e.HasErrors){
|
if (e.HasErrors){
|
||||||
FormMessage.Error("Error Executing Plugins", "Failed to execute the following plugins:\n\n"+string.Join("\n\n", e.Errors), FormMessage.OK);
|
this.InvokeAsyncSafe(() => {
|
||||||
|
FormMessage.Error("Error Executing Plugins", "Failed to execute the following plugins:\n\n" + string.Join("\n\n", e.Errors), FormMessage.OK);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -286,7 +289,7 @@ namespace TweetDuck.Core{
|
|||||||
UpdateInstallerPath = update.InstallerPath;
|
UpdateInstallerPath = update.InstallerPath;
|
||||||
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);
|
BrowserUtils.OpenExternalBrowser(Program.Website);
|
||||||
ForceClose();
|
ForceClose();
|
||||||
}
|
}
|
||||||
@@ -319,10 +322,6 @@ namespace TweetDuck.Core{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateBridge_UpdateDelayed(object sender, UpdateInfo update){
|
|
||||||
// stops the timer
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateBridge_UpdateDismissed(object sender, UpdateInfo update){
|
private void updateBridge_UpdateDismissed(object sender, UpdateInfo update){
|
||||||
Config.DismissedUpdate = update.VersionTag;
|
Config.DismissedUpdate = update.VersionTag;
|
||||||
Config.Save();
|
Config.Save();
|
||||||
@@ -330,7 +329,9 @@ namespace TweetDuck.Core{
|
|||||||
|
|
||||||
protected override void WndProc(ref Message m){
|
protected override void WndProc(ref Message m){
|
||||||
if (isLoaded && m.Msg == Program.WindowRestoreMessage){
|
if (isLoaded && m.Msg == Program.WindowRestoreMessage){
|
||||||
if (WindowsUtils.CurrentProcessID == m.WParam.ToInt32()){
|
using Process me = Process.GetCurrentProcess();
|
||||||
|
|
||||||
|
if (me.Id == m.WParam.ToInt32()){
|
||||||
trayIcon_ClickRestore(trayIcon, EventArgs.Empty);
|
trayIcon_ClickRestore(trayIcon, EventArgs.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -367,10 +368,7 @@ namespace TweetDuck.Core{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void ReloadToTweetDeck(){
|
public void ReloadToTweetDeck(){
|
||||||
#if DEBUG
|
Program.Resources.OnReloadTriggered();
|
||||||
Resources.ScriptLoader.HotSwap();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
ignoreUpdateCheckError = false;
|
ignoreUpdateCheckError = false;
|
||||||
browser.ReloadToTweetDeck();
|
browser.ReloadToTweetDeck();
|
||||||
AnalyticsFile.BrowserReloads.Trigger();
|
AnalyticsFile.BrowserReloads.Trigger();
|
||||||
@@ -492,7 +490,7 @@ namespace TweetDuck.Core{
|
|||||||
FormManager.TryFind<FormSettings>()?.Close();
|
FormManager.TryFind<FormSettings>()?.Close();
|
||||||
|
|
||||||
using(DialogSettingsManage dialog = new DialogSettingsManage(plugins, true)){
|
using(DialogSettingsManage dialog = new DialogSettingsManage(plugins, true)){
|
||||||
if (dialog.ShowDialog() == DialogResult.OK && !dialog.IsRestarting){
|
if (!dialog.IsDisposed && dialog.ShowDialog() == DialogResult.OK && !dialog.IsRestarting){ // needs disposal check because the dialog may be closed in constructor
|
||||||
BrowserProcessHandler.UpdatePrefs();
|
BrowserProcessHandler.UpdatePrefs();
|
||||||
FormManager.TryFind<FormPlugins>()?.Close();
|
FormManager.TryFind<FormPlugins>()?.Close();
|
||||||
plugins.Reload(); // also reloads the browser
|
plugins.Reload(); // also reloads the browser
|
||||||
|
@@ -17,8 +17,6 @@ namespace TweetDuck.Core{
|
|||||||
else return false;
|
else return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool HasAnyDialogs => Application.OpenForms.OfType<IAppDialog>().Any();
|
|
||||||
|
|
||||||
public static void CloseAllDialogs(){
|
public static void CloseAllDialogs(){
|
||||||
foreach(IAppDialog dialog in Application.OpenForms.OfType<IAppDialog>().Reverse()){
|
foreach(IAppDialog dialog in Application.OpenForms.OfType<IAppDialog>().Reverse()){
|
||||||
((Form)dialog).Close();
|
((Form)dialog).Close();
|
||||||
|
@@ -1,21 +1,25 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using CefSharp;
|
using CefSharp;
|
||||||
using TweetDuck.Core.Controls;
|
using TweetDuck.Core.Controls;
|
||||||
using TweetDuck.Core.Utils;
|
using TweetDuck.Core.Utils;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using TweetDuck.Core.Bridge;
|
using TweetDuck.Configuration;
|
||||||
|
using TweetDuck.Core.Adapters;
|
||||||
using TweetDuck.Core.Management;
|
using TweetDuck.Core.Management;
|
||||||
using TweetDuck.Core.Notification;
|
using TweetDuck.Core.Notification;
|
||||||
using TweetDuck.Core.Other;
|
using TweetDuck.Core.Other;
|
||||||
using TweetDuck.Core.Other.Analytics;
|
using TweetDuck.Core.Other.Analytics;
|
||||||
using TweetDuck.Resources;
|
using TweetLib.Core.Features.Twitter;
|
||||||
|
using TweetLib.Core.Utils;
|
||||||
|
|
||||||
namespace TweetDuck.Core.Handling{
|
namespace TweetDuck.Core.Handling{
|
||||||
abstract class ContextMenuBase : IContextMenuHandler{
|
abstract class ContextMenuBase : IContextMenuHandler{
|
||||||
private static TwitterUtils.ImageQuality ImageQuality => Program.UserConfig.TwitterImageQuality;
|
public static ContextInfo CurrentInfo { get; } = new ContextInfo();
|
||||||
|
|
||||||
|
protected static UserConfig Config => Program.Config.User;
|
||||||
|
private static ImageQuality ImageQuality => Config.TwitterImageQuality;
|
||||||
|
|
||||||
private const CefMenuCommand MenuOpenLinkUrl = (CefMenuCommand)26500;
|
private const CefMenuCommand MenuOpenLinkUrl = (CefMenuCommand)26500;
|
||||||
private const CefMenuCommand MenuCopyLinkUrl = (CefMenuCommand)26501;
|
private const CefMenuCommand MenuCopyLinkUrl = (CefMenuCommand)26501;
|
||||||
@@ -29,8 +33,7 @@ namespace TweetDuck.Core.Handling{
|
|||||||
private const CefMenuCommand MenuReadApplyROT13 = (CefMenuCommand)26509;
|
private const CefMenuCommand MenuReadApplyROT13 = (CefMenuCommand)26509;
|
||||||
private const CefMenuCommand MenuOpenDevTools = (CefMenuCommand)26599;
|
private const CefMenuCommand MenuOpenDevTools = (CefMenuCommand)26599;
|
||||||
|
|
||||||
protected ContextInfo.LinkInfo LastLink { get; private set; }
|
protected ContextInfo.ContextData Context { get; private set; }
|
||||||
protected ContextInfo.ChirpInfo LastChirp { get; private set; }
|
|
||||||
|
|
||||||
private readonly AnalyticsFile.IProvider analytics;
|
private readonly AnalyticsFile.IProvider analytics;
|
||||||
|
|
||||||
@@ -38,21 +41,14 @@ namespace TweetDuck.Core.Handling{
|
|||||||
this.analytics = analytics;
|
this.analytics = analytics;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ResetContextInfo(){
|
|
||||||
LastLink = default(ContextInfo.LinkInfo);
|
|
||||||
LastChirp = default(ContextInfo.ChirpInfo);
|
|
||||||
TweetDeckBridge.ContextInfo.Reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (!TwitterUtils.IsTweetDeckWebsite(frame) || browser.IsLoading){
|
if (!TwitterUrls.IsTweetDeck(frame.Url) || browser.IsLoading){
|
||||||
ResetContextInfo();
|
Context = CurrentInfo.Reset();
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
LastLink = TweetDeckBridge.ContextInfo.Link;
|
Context = CurrentInfo.Create(parameters);
|
||||||
LastChirp = TweetDeckBridge.ContextInfo.Chirp;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parameters.TypeFlags.HasFlag(ContextMenuType.Selection) && !parameters.TypeFlags.HasFlag(ContextMenuType.Editable)){
|
if (parameters.TypeFlags.HasFlag(ContextMenuType.Selection) && !parameters.TypeFlags.HasFlag(ContextMenuType.Editable)){
|
||||||
model.AddItem(MenuSearchInBrowser, "Search in browser");
|
model.AddItem(MenuSearchInBrowser, "Search in browser");
|
||||||
model.AddSeparator();
|
model.AddSeparator();
|
||||||
@@ -60,15 +56,12 @@ namespace TweetDuck.Core.Handling{
|
|||||||
model.AddSeparator();
|
model.AddSeparator();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool hasTweetImage = LastLink.IsImage;
|
static string TextOpen(string name) => "Open " + name + " in browser";
|
||||||
bool hasTweetVideo = LastLink.IsVideo;
|
static string TextCopy(string name) => "Copy " + name + " address";
|
||||||
|
static string TextSave(string name) => "Save " + name + " as...";
|
||||||
string TextOpen(string name) => "Open "+name+" in browser";
|
|
||||||
string TextCopy(string name) => "Copy "+name+" address";
|
|
||||||
string TextSave(string name) => "Save "+name+" as...";
|
|
||||||
|
|
||||||
if (parameters.TypeFlags.HasFlag(ContextMenuType.Link) && !parameters.UnfilteredLinkUrl.EndsWith("tweetdeck.twitter.com/#", StringComparison.Ordinal) && !hasTweetImage && !hasTweetVideo){
|
if (Context.Types.HasFlag(ContextInfo.ContextType.Link) && !Context.UnsafeLinkUrl.EndsWith("tweetdeck.twitter.com/#", StringComparison.Ordinal)){
|
||||||
if (TwitterUtils.RegexAccount.IsMatch(parameters.UnfilteredLinkUrl)){
|
if (TwitterUrls.RegexAccount.IsMatch(Context.UnsafeLinkUrl)){
|
||||||
model.AddItem(MenuOpenLinkUrl, TextOpen("account"));
|
model.AddItem(MenuOpenLinkUrl, TextOpen("account"));
|
||||||
model.AddItem(MenuCopyLinkUrl, TextCopy("account"));
|
model.AddItem(MenuCopyLinkUrl, TextCopy("account"));
|
||||||
model.AddItem(MenuCopyUsername, "Copy account username");
|
model.AddItem(MenuCopyUsername, "Copy account username");
|
||||||
@@ -81,19 +74,19 @@ namespace TweetDuck.Core.Handling{
|
|||||||
model.AddSeparator();
|
model.AddSeparator();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasTweetVideo){
|
if (Context.Types.HasFlag(ContextInfo.ContextType.Video)){
|
||||||
model.AddItem(MenuOpenMediaUrl, TextOpen("video"));
|
model.AddItem(MenuOpenMediaUrl, TextOpen("video"));
|
||||||
model.AddItem(MenuCopyMediaUrl, TextCopy("video"));
|
model.AddItem(MenuCopyMediaUrl, TextCopy("video"));
|
||||||
model.AddItem(MenuSaveMedia, TextSave("video"));
|
model.AddItem(MenuSaveMedia, TextSave("video"));
|
||||||
model.AddSeparator();
|
model.AddSeparator();
|
||||||
}
|
}
|
||||||
else if (((parameters.TypeFlags.HasFlag(ContextMenuType.Media) && parameters.HasImageContents) || hasTweetImage) && parameters.SourceUrl != TweetNotification.AppLogo.Url){
|
else if (Context.Types.HasFlag(ContextInfo.ContextType.Image) && Context.MediaUrl != FormNotificationBase.AppLogo.Url){
|
||||||
model.AddItem(MenuViewImage, "View image in photo viewer");
|
model.AddItem(MenuViewImage, "View image in photo viewer");
|
||||||
model.AddItem(MenuOpenMediaUrl, TextOpen("image"));
|
model.AddItem(MenuOpenMediaUrl, TextOpen("image"));
|
||||||
model.AddItem(MenuCopyMediaUrl, TextCopy("image"));
|
model.AddItem(MenuCopyMediaUrl, TextCopy("image"));
|
||||||
model.AddItem(MenuSaveMedia, TextSave("image"));
|
model.AddItem(MenuSaveMedia, TextSave("image"));
|
||||||
|
|
||||||
if (LastChirp.Images.Length > 1){
|
if (Context.Chirp.Images.Length > 1){
|
||||||
model.AddItem(MenuSaveTweetImages, TextSave("all images"));
|
model.AddItem(MenuSaveTweetImages, TextSave("all images"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,64 +99,45 @@ namespace TweetDuck.Core.Handling{
|
|||||||
|
|
||||||
switch(commandId){
|
switch(commandId){
|
||||||
case MenuOpenLinkUrl:
|
case MenuOpenLinkUrl:
|
||||||
OpenBrowser(control, LastLink.GetUrl(parameters, true));
|
OpenBrowser(control, Context.LinkUrl);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MenuCopyLinkUrl:
|
case MenuCopyLinkUrl:
|
||||||
SetClipboardText(control, LastLink.GetUrl(parameters, false));
|
SetClipboardText(control, Context.UnsafeLinkUrl);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MenuCopyUsername:
|
case MenuCopyUsername: {
|
||||||
Match match = TwitterUtils.RegexAccount.Match(parameters.UnfilteredLinkUrl);
|
string url = Context.UnsafeLinkUrl;
|
||||||
SetClipboardText(control, match.Success ? match.Groups[1].Value : parameters.UnfilteredLinkUrl);
|
Match match = TwitterUrls.RegexAccount.Match(url);
|
||||||
|
|
||||||
|
SetClipboardText(control, match.Success ? match.Groups[1].Value : url);
|
||||||
control.InvokeAsyncSafe(analytics.AnalyticsFile.CopiedUsernames.Trigger);
|
control.InvokeAsyncSafe(analytics.AnalyticsFile.CopiedUsernames.Trigger);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case MenuOpenMediaUrl:
|
case MenuOpenMediaUrl:
|
||||||
OpenBrowser(control, TwitterUtils.GetMediaLink(LastLink.GetMediaSource(parameters), ImageQuality));
|
OpenBrowser(control, TwitterUrls.GetMediaLink(Context.MediaUrl, ImageQuality));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MenuCopyMediaUrl:
|
case MenuCopyMediaUrl:
|
||||||
SetClipboardText(control, TwitterUtils.GetMediaLink(LastLink.GetMediaSource(parameters), ImageQuality));
|
SetClipboardText(control, TwitterUrls.GetMediaLink(Context.MediaUrl, ImageQuality));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MenuViewImage: {
|
case MenuViewImage: {
|
||||||
void ViewImage(string path){
|
string url = Context.MediaUrl;
|
||||||
string ext = Path.GetExtension(path);
|
|
||||||
|
|
||||||
if (TwitterUtils.ValidImageExtensions.Contains(ext)){
|
|
||||||
WindowsUtils.OpenAssociatedProgram(path);
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
FormMessage.Error("Image Download", "Invalid file extension "+ext, FormMessage.OK);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
string url = LastLink.GetMediaSource(parameters);
|
|
||||||
string file = Path.Combine(BrowserCache.CacheFolder, TwitterUtils.GetImageFileName(url) ?? Path.GetRandomFileName());
|
|
||||||
|
|
||||||
control.InvokeAsyncSafe(() => {
|
control.InvokeAsyncSafe(() => {
|
||||||
if (File.Exists(file)){
|
TwitterUtils.ViewImage(url, ImageQuality);
|
||||||
ViewImage(file);
|
analytics.AnalyticsFile.ViewedImages.Trigger();
|
||||||
}
|
|
||||||
else{
|
|
||||||
analytics.AnalyticsFile.ViewedImages.Trigger();
|
|
||||||
|
|
||||||
BrowserUtils.DownloadFileAsync(TwitterUtils.GetMediaLink(url, ImageQuality), file, () => {
|
|
||||||
ViewImage(file);
|
|
||||||
}, ex => {
|
|
||||||
FormMessage.Error("Image Download", "An error occurred while downloading the image: "+ex.Message, FormMessage.OK);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case MenuSaveMedia: {
|
case MenuSaveMedia: {
|
||||||
bool isVideo = LastLink.IsVideo;
|
bool isVideo = Context.Types.HasFlag(ContextInfo.ContextType.Video);
|
||||||
string url = LastLink.GetMediaSource(parameters);
|
string url = Context.MediaUrl;
|
||||||
string username = LastChirp.Authors.LastOrDefault();
|
string username = Context.Chirp.Authors.LastOrDefault();
|
||||||
|
|
||||||
control.InvokeAsyncSafe(() => {
|
control.InvokeAsyncSafe(() => {
|
||||||
if (isVideo){
|
if (isVideo){
|
||||||
@@ -180,8 +154,8 @@ namespace TweetDuck.Core.Handling{
|
|||||||
}
|
}
|
||||||
|
|
||||||
case MenuSaveTweetImages: {
|
case MenuSaveTweetImages: {
|
||||||
string[] urls = LastChirp.Images;
|
string[] urls = Context.Chirp.Images;
|
||||||
string username = LastChirp.Authors.LastOrDefault();
|
string username = Context.Chirp.Authors.LastOrDefault();
|
||||||
|
|
||||||
control.InvokeAsyncSafe(() => {
|
control.InvokeAsyncSafe(() => {
|
||||||
TwitterUtils.DownloadImages(urls, username, ImageQuality);
|
TwitterUtils.DownloadImages(urls, username, ImageQuality);
|
||||||
@@ -212,7 +186,7 @@ namespace TweetDuck.Core.Handling{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public virtual void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame){
|
public virtual void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame){
|
||||||
ResetContextInfo();
|
Context = CurrentInfo.Reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
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){
|
||||||
@@ -220,7 +194,7 @@ namespace TweetDuck.Core.Handling{
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected static void DeselectAll(IFrame frame){
|
protected static void DeselectAll(IFrame frame){
|
||||||
ScriptLoader.ExecuteScript(frame, "window.getSelection().removeAllRanges()", "gen:deselect");
|
CefScriptExecutor.RunScript(frame, "window.getSelection().removeAllRanges()", "gen:deselect");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static void OpenBrowser(Control control, string url){
|
protected static void OpenBrowser(Control control, string url){
|
||||||
@@ -232,7 +206,7 @@ namespace TweetDuck.Core.Handling{
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected static void InsertSelectionSearchItem(IMenuModel model, CefMenuCommand insertCommand, string insertLabel){
|
protected static void InsertSelectionSearchItem(IMenuModel model, CefMenuCommand insertCommand, string insertLabel){
|
||||||
model.InsertItemAt(model.GetIndexOf(MenuSearchInBrowser)+1, insertCommand, insertLabel);
|
model.InsertItemAt(model.GetIndexOf(MenuSearchInBrowser) + 1, insertCommand, insertLabel);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static void AddDebugMenuItems(IMenuModel model){
|
protected static void AddDebugMenuItems(IMenuModel model){
|
||||||
@@ -243,13 +217,13 @@ namespace TweetDuck.Core.Handling{
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected static void RemoveSeparatorIfLast(IMenuModel model){
|
protected static void RemoveSeparatorIfLast(IMenuModel model){
|
||||||
if (model.Count > 0 && model.GetTypeAt(model.Count-1) == MenuItemType.Separator){
|
if (model.Count > 0 && model.GetTypeAt(model.Count - 1) == MenuItemType.Separator){
|
||||||
model.RemoveAt(model.Count-1);
|
model.RemoveAt(model.Count - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static void AddSeparator(IMenuModel model){
|
protected static void AddSeparator(IMenuModel model){
|
||||||
if (model.Count > 0 && model.GetTypeAt(model.Count-1) != MenuItemType.Separator){ // do not add separators if there is nothing to separate
|
if (model.Count > 0 && model.GetTypeAt(model.Count - 1) != MenuItemType.Separator){ // do not add separators if there is nothing to separate
|
||||||
model.AddSeparator();
|
model.AddSeparator();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,8 @@
|
|||||||
using CefSharp;
|
using CefSharp;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using TweetDuck.Core.Controls;
|
using TweetDuck.Core.Controls;
|
||||||
using TweetDuck.Core.Utils;
|
using TweetDuck.Core.Management;
|
||||||
|
using TweetLib.Core.Features.Twitter;
|
||||||
|
|
||||||
namespace TweetDuck.Core.Handling{
|
namespace TweetDuck.Core.Handling{
|
||||||
sealed class ContextMenuBrowser : ContextMenuBase{
|
sealed class ContextMenuBrowser : ContextMenuBase{
|
||||||
@@ -23,7 +24,7 @@ namespace TweetDuck.Core.Handling{
|
|||||||
private const string TitleMuteNotifications = "Mute notifications";
|
private const string TitleMuteNotifications = "Mute notifications";
|
||||||
private const string TitleSettings = "Options";
|
private const string TitleSettings = "Options";
|
||||||
private const string TitlePlugins = "Plugins";
|
private const string TitlePlugins = "Plugins";
|
||||||
private const string TitleAboutProgram = "About "+Program.BrandName;
|
private const string TitleAboutProgram = "About " + Program.BrandName;
|
||||||
|
|
||||||
private readonly FormBrowser form;
|
private readonly FormBrowser form;
|
||||||
|
|
||||||
@@ -52,16 +53,16 @@ namespace TweetDuck.Core.Handling{
|
|||||||
|
|
||||||
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
|
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
|
||||||
|
|
||||||
if (isSelecting && !isEditing && TwitterUtils.IsTweetDeckWebsite(frame)){
|
if (isSelecting && !isEditing && TwitterUrls.IsTweetDeck(frame.Url)){
|
||||||
InsertSelectionSearchItem(model, MenuSearchInColumn, "Search in a column");
|
InsertSelectionSearchItem(model, MenuSearchInColumn, "Search in a column");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(LastChirp.TweetUrl) && !isSelecting && !isEditing){
|
if (Context.Types.HasFlag(ContextInfo.ContextType.Chirp) && !isSelecting && !isEditing){
|
||||||
model.AddItem(MenuOpenTweetUrl, "Open tweet in browser");
|
model.AddItem(MenuOpenTweetUrl, "Open tweet in browser");
|
||||||
model.AddItem(MenuCopyTweetUrl, "Copy tweet address");
|
model.AddItem(MenuCopyTweetUrl, "Copy tweet address");
|
||||||
model.AddItem(MenuScreenshotTweet, "Screenshot tweet to clipboard");
|
model.AddItem(MenuScreenshotTweet, "Screenshot tweet to clipboard");
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(LastChirp.QuoteUrl)){
|
if (!string.IsNullOrEmpty(Context.Chirp.QuoteUrl)){
|
||||||
model.AddSeparator();
|
model.AddSeparator();
|
||||||
model.AddItem(MenuOpenQuotedTweetUrl, "Open quoted tweet in browser");
|
model.AddItem(MenuOpenQuotedTweetUrl, "Open quoted tweet in browser");
|
||||||
model.AddItem(MenuCopyQuotedTweetUrl, "Copy quoted tweet address");
|
model.AddItem(MenuCopyQuotedTweetUrl, "Copy quoted tweet address");
|
||||||
@@ -77,7 +78,7 @@ namespace TweetDuck.Core.Handling{
|
|||||||
|
|
||||||
globalMenu.AddItem(CefMenuCommand.Reload, TitleReloadBrowser);
|
globalMenu.AddItem(CefMenuCommand.Reload, TitleReloadBrowser);
|
||||||
globalMenu.AddCheckItem(MenuMute, TitleMuteNotifications);
|
globalMenu.AddCheckItem(MenuMute, TitleMuteNotifications);
|
||||||
globalMenu.SetChecked(MenuMute, Program.UserConfig.MuteNotifications);
|
globalMenu.SetChecked(MenuMute, Config.MuteNotifications);
|
||||||
globalMenu.AddSeparator();
|
globalMenu.AddSeparator();
|
||||||
|
|
||||||
globalMenu.AddItem(MenuSettings, TitleSettings);
|
globalMenu.AddItem(MenuSettings, TitleSettings);
|
||||||
@@ -119,11 +120,11 @@ namespace TweetDuck.Core.Handling{
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
case MenuOpenTweetUrl:
|
case MenuOpenTweetUrl:
|
||||||
OpenBrowser(form, LastChirp.TweetUrl);
|
OpenBrowser(form, Context.Chirp.TweetUrl);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case MenuCopyTweetUrl:
|
case MenuCopyTweetUrl:
|
||||||
SetClipboardText(form, LastChirp.TweetUrl);
|
SetClipboardText(form, Context.Chirp.TweetUrl);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case MenuScreenshotTweet:
|
case MenuScreenshotTweet:
|
||||||
@@ -131,11 +132,11 @@ namespace TweetDuck.Core.Handling{
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
case MenuOpenQuotedTweetUrl:
|
case MenuOpenQuotedTweetUrl:
|
||||||
OpenBrowser(form, LastChirp.QuoteUrl);
|
OpenBrowser(form, Context.Chirp.QuoteUrl);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case MenuCopyQuotedTweetUrl:
|
case MenuCopyQuotedTweetUrl:
|
||||||
SetClipboardText(form, LastChirp.QuoteUrl);
|
SetClipboardText(form, Context.Chirp.QuoteUrl);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case MenuWriteApplyROT13:
|
case MenuWriteApplyROT13:
|
||||||
@@ -163,7 +164,7 @@ namespace TweetDuck.Core.Handling{
|
|||||||
menu.MenuItems.Add(TitleAboutProgram, (sender, args) => form.OpenAbout());
|
menu.MenuItems.Add(TitleAboutProgram, (sender, args) => form.OpenAbout());
|
||||||
|
|
||||||
menu.Popup += (sender, args) => {
|
menu.Popup += (sender, args) => {
|
||||||
menu.MenuItems[1].Checked = Program.UserConfig.MuteNotifications;
|
menu.MenuItems[1].Checked = Config.MuteNotifications;
|
||||||
form.AnalyticsFile.BrowserContextMenus.Trigger();
|
form.AnalyticsFile.BrowserContextMenus.Trigger();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -171,8 +172,8 @@ namespace TweetDuck.Core.Handling{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void ToggleMuteNotifications(){
|
private static void ToggleMuteNotifications(){
|
||||||
Program.UserConfig.MuteNotifications = !Program.UserConfig.MuteNotifications;
|
Config.MuteNotifications = !Config.MuteNotifications;
|
||||||
Program.UserConfig.Save();
|
Config.Save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -25,7 +25,7 @@ namespace TweetDuck.Core.Handling.Filters{
|
|||||||
int responseLength = responseData.Length;
|
int responseLength = responseData.Length;
|
||||||
|
|
||||||
if (state == State.Reading){
|
if (state == State.Reading){
|
||||||
int bytesToRead = Math.Min(responseLength-offset, (int)Math.Min(dataIn?.Length ?? 0, int.MaxValue));
|
int bytesToRead = Math.Min(responseLength - offset, (int)Math.Min(dataIn?.Length ?? 0, int.MaxValue));
|
||||||
|
|
||||||
dataIn?.Read(responseData, offset, bytesToRead);
|
dataIn?.Read(responseData, offset, bytesToRead);
|
||||||
offset += bytesToRead;
|
offset += bytesToRead;
|
||||||
@@ -42,7 +42,7 @@ namespace TweetDuck.Core.Handling.Filters{
|
|||||||
return FilterStatus.NeedMoreData;
|
return FilterStatus.NeedMoreData;
|
||||||
}
|
}
|
||||||
else if (state == State.Writing){
|
else if (state == State.Writing){
|
||||||
int bytesToWrite = Math.Min(responseLength-offset, (int)Math.Min(dataOut.Length, int.MaxValue));
|
int bytesToWrite = Math.Min(responseLength - offset, (int)Math.Min(dataOut.Length, int.MaxValue));
|
||||||
|
|
||||||
if (bytesToWrite > 0){
|
if (bytesToWrite > 0){
|
||||||
dataOut.Write(responseData, offset, bytesToWrite);
|
dataOut.Write(responseData, offset, bytesToWrite);
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using CefSharp;
|
using CefSharp;
|
||||||
|
using TweetDuck.Configuration;
|
||||||
|
|
||||||
namespace TweetDuck.Core.Handling.General{
|
namespace TweetDuck.Core.Handling.General{
|
||||||
sealed class BrowserProcessHandler : IBrowserProcessHandler{
|
sealed class BrowserProcessHandler : IBrowserProcessHandler{
|
||||||
@@ -9,11 +10,12 @@ namespace TweetDuck.Core.Handling.General{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void UpdatePrefsInternal(){
|
private static void UpdatePrefsInternal(){
|
||||||
using(IRequestContext ctx = Cef.GetGlobalRequestContext()){
|
UserConfig config = Program.Config.User;
|
||||||
ctx.SetPreference("browser.enable_spellchecking", Program.UserConfig.EnableSpellCheck, out string _);
|
using IRequestContext ctx = Cef.GetGlobalRequestContext();
|
||||||
ctx.SetPreference("spellcheck.dictionary", Program.UserConfig.SpellCheckLanguage, out string _);
|
|
||||||
ctx.SetPreference("settings.a11y.animation_policy", Program.UserConfig.EnableAnimatedImages ? "allowed" : "none", out string _);
|
ctx.SetPreference("browser.enable_spellchecking", config.EnableSpellCheck, out string _);
|
||||||
}
|
ctx.SetPreference("spellcheck.dictionary", config.SpellCheckLanguage, out string _);
|
||||||
|
ctx.SetPreference("settings.a11y.animation_policy", config.EnableAnimatedImages ? "allowed" : "none", out string _);
|
||||||
}
|
}
|
||||||
|
|
||||||
void IBrowserProcessHandler.OnContextInitialized(){
|
void IBrowserProcessHandler.OnContextInitialized(){
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
@@ -9,7 +8,7 @@ namespace TweetDuck.Core.Handling.General{
|
|||||||
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){
|
||||||
string allFilters = string.Join(";", acceptFilters.Select(filter => "*"+filter));
|
string allFilters = string.Join(";", acceptFilters.SelectMany(ParseFileType).Where(filter => !string.IsNullOrEmpty(filter)).Select(filter => "*" + filter));
|
||||||
|
|
||||||
using(OpenFileDialog dialog = new OpenFileDialog{
|
using(OpenFileDialog dialog = new OpenFileDialog{
|
||||||
AutoUpgradeEnabled = true,
|
AutoUpgradeEnabled = true,
|
||||||
@@ -19,8 +18,8 @@ namespace TweetDuck.Core.Handling.General{
|
|||||||
Filter = $"All Supported Formats ({allFilters})|{allFilters}|All Files (*.*)|*.*"
|
Filter = $"All Supported Formats ({allFilters})|{allFilters}|All Files (*.*)|*.*"
|
||||||
}){
|
}){
|
||||||
if (dialog.ShowDialog() == DialogResult.OK){
|
if (dialog.ShowDialog() == DialogResult.OK){
|
||||||
string ext = Path.GetExtension(dialog.FileName);
|
string ext = Path.GetExtension(dialog.FileName)?.ToLower();
|
||||||
callback.Continue(acceptFilters.FindIndex(filter => filter.Equals(ext, StringComparison.OrdinalIgnoreCase)), dialog.FileNames.ToList());
|
callback.Continue(acceptFilters.FindIndex(filter => ParseFileType(filter).Contains(ext)), dialog.FileNames.ToList());
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
callback.Cancel();
|
callback.Cancel();
|
||||||
@@ -36,5 +35,27 @@ namespace TweetDuck.Core.Handling.General{
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<string> ParseFileType(string type){
|
||||||
|
if (string.IsNullOrEmpty(type)){
|
||||||
|
return new string[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type[0] == '.'){
|
||||||
|
return new string[]{ type };
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(type){
|
||||||
|
case "image/jpeg": return new string[]{ ".jpg", ".jpeg" };
|
||||||
|
case "image/png": return new string[]{ ".png" };
|
||||||
|
case "image/gif": return new string[]{ ".gif" };
|
||||||
|
case "image/webp": return new string[]{ ".webp" };
|
||||||
|
case "video/mp4": return new string[]{ ".mp4" };
|
||||||
|
case "video/quicktime": return new string[]{ ".mov", ".qt" };
|
||||||
|
}
|
||||||
|
|
||||||
|
System.Diagnostics.Debugger.Break();
|
||||||
|
return new string[0];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -12,15 +12,17 @@ namespace TweetDuck.Core.Handling.General{
|
|||||||
int pipe = text.IndexOf('|');
|
int pipe = text.IndexOf('|');
|
||||||
|
|
||||||
if (pipe != -1){
|
if (pipe != -1){
|
||||||
switch(text.Substring(0, pipe)){
|
icon = text.Substring(0, pipe) switch{
|
||||||
case "error": icon = MessageBoxIcon.Error; break;
|
"error" => MessageBoxIcon.Error,
|
||||||
case "warning": icon = MessageBoxIcon.Warning; break;
|
"warning" => MessageBoxIcon.Warning,
|
||||||
case "info": icon = MessageBoxIcon.Information; break;
|
"info" => MessageBoxIcon.Information,
|
||||||
case "question": icon = MessageBoxIcon.Question; break;
|
"question" => MessageBoxIcon.Question,
|
||||||
default: return new FormMessage(caption, text, icon);
|
_ => MessageBoxIcon.None
|
||||||
}
|
};
|
||||||
|
|
||||||
text = text.Substring(pipe+1);
|
if (icon != MessageBoxIcon.None){
|
||||||
|
text = text.Substring(pipe + 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new FormMessage(caption, text, icon);
|
return new FormMessage(caption, text, icon);
|
||||||
@@ -51,13 +53,13 @@ namespace TweetDuck.Core.Handling.General{
|
|||||||
input = new TextBox{
|
input = new TextBox{
|
||||||
Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Bottom,
|
Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Bottom,
|
||||||
Font = SystemFonts.MessageBoxFont,
|
Font = SystemFonts.MessageBoxFont,
|
||||||
Location = new Point(BrowserUtils.Scale(22+inputPad, dpiScale), form.ActionPanelY-BrowserUtils.Scale(46, dpiScale)),
|
Location = new Point(BrowserUtils.Scale(22 + inputPad, dpiScale), form.ActionPanelY - BrowserUtils.Scale(46, dpiScale)),
|
||||||
Size = new Size(form.ClientSize.Width-BrowserUtils.Scale(44+inputPad, dpiScale), BrowserUtils.Scale(23, dpiScale))
|
Size = new Size(form.ClientSize.Width - BrowserUtils.Scale(44 + inputPad, dpiScale), BrowserUtils.Scale(23, dpiScale))
|
||||||
};
|
};
|
||||||
|
|
||||||
form.Controls.Add(input);
|
form.Controls.Add(input);
|
||||||
form.ActiveControl = input;
|
form.ActiveControl = input;
|
||||||
form.Height += input.Size.Height+input.Margin.Vertical;
|
form.Height += input.Size.Height + input.Margin.Vertical;
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
callback.Continue(false);
|
callback.Continue(false);
|
||||||
|
@@ -4,11 +4,15 @@ using TweetDuck.Core.Utils;
|
|||||||
|
|
||||||
namespace TweetDuck.Core.Handling.General{
|
namespace TweetDuck.Core.Handling.General{
|
||||||
sealed class LifeSpanHandler : ILifeSpanHandler{
|
sealed class LifeSpanHandler : ILifeSpanHandler{
|
||||||
|
private static bool IsPopupAllowed(string url){
|
||||||
|
return url.StartsWith("https://twitter.com/teams/authorize?");
|
||||||
|
}
|
||||||
|
|
||||||
public static bool HandleLinkClick(IWebBrowser browserControl, WindowOpenDisposition targetDisposition, string targetUrl){
|
public static bool HandleLinkClick(IWebBrowser browserControl, WindowOpenDisposition targetDisposition, string targetUrl){
|
||||||
switch(targetDisposition){
|
switch(targetDisposition){
|
||||||
case WindowOpenDisposition.NewBackgroundTab:
|
case WindowOpenDisposition.NewBackgroundTab:
|
||||||
case WindowOpenDisposition.NewForegroundTab:
|
case WindowOpenDisposition.NewForegroundTab:
|
||||||
case WindowOpenDisposition.NewPopup:
|
case WindowOpenDisposition.NewPopup when !IsPopupAllowed(targetUrl):
|
||||||
case WindowOpenDisposition.NewWindow:
|
case WindowOpenDisposition.NewWindow:
|
||||||
browserControl.AsControl().InvokeAsyncSafe(() => BrowserUtils.OpenExternalBrowser(targetUrl));
|
browserControl.AsControl().InvokeAsyncSafe(() => BrowserUtils.OpenExternalBrowser(targetUrl));
|
||||||
return true;
|
return true;
|
||||||
|
47
Core/Handling/KeyboardHandlerBase.cs
Normal file
47
Core/Handling/KeyboardHandlerBase.cs
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
using System.Windows.Forms;
|
||||||
|
using CefSharp;
|
||||||
|
using TweetDuck.Core.Controls;
|
||||||
|
using TweetDuck.Core.Other;
|
||||||
|
using TweetDuck.Core.Utils;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.Handling{
|
||||||
|
class KeyboardHandlerBase : IKeyboardHandler{
|
||||||
|
protected virtual bool HandleRawKey(IWebBrowser browserControl, IBrowser browser, Keys key, CefEventFlags modifiers){
|
||||||
|
if (modifiers == (CefEventFlags.ControlDown | CefEventFlags.ShiftDown) && key == Keys.I){
|
||||||
|
if (BrowserUtils.HasDevTools){
|
||||||
|
browser.ShowDevTools();
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
browserControl.AsControl().InvokeSafe(() => {
|
||||||
|
string extraMessage;
|
||||||
|
|
||||||
|
if (Program.IsPortable){
|
||||||
|
extraMessage = "Please download the portable installer, select the folder with your current installation of TweetDuck Portable, and tick 'Install dev tools' during the installation process.";
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
extraMessage = "Please download the installer, and tick 'Install dev tools' during the installation process. The installer will automatically find and update your current installation of TweetDuck.";
|
||||||
|
}
|
||||||
|
|
||||||
|
FormMessage.Information("Dev Tools", "You do not have dev tools installed. " + extraMessage, FormMessage.OK);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
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("chrome-devtools://")){
|
||||||
|
return HandleRawKey(browserControl, browser, (Keys)windowsKeyCode, modifiers);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IKeyboardHandler.OnKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -2,19 +2,19 @@
|
|||||||
using CefSharp;
|
using CefSharp;
|
||||||
|
|
||||||
namespace TweetDuck.Core.Handling{
|
namespace TweetDuck.Core.Handling{
|
||||||
sealed class KeyboardHandlerBrowser : IKeyboardHandler{
|
sealed class KeyboardHandlerBrowser : KeyboardHandlerBase{
|
||||||
private readonly FormBrowser form;
|
private readonly FormBrowser form;
|
||||||
|
|
||||||
public KeyboardHandlerBrowser(FormBrowser form){
|
public KeyboardHandlerBrowser(FormBrowser form){
|
||||||
this.form = form;
|
this.form = form;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IKeyboardHandler.OnPreKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey, ref bool isKeyboardShortcut){
|
protected override bool HandleRawKey(IWebBrowser browserControl, IBrowser browser, Keys key, CefEventFlags modifiers){
|
||||||
return type == KeyType.RawKeyDown && form.ProcessBrowserKey((Keys)windowsKeyCode);
|
if (base.HandleRawKey(browserControl, browser, key, modifiers)){
|
||||||
}
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool IKeyboardHandler.OnKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey){
|
return form.ProcessBrowserKey(key);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,7 +4,7 @@ using TweetDuck.Core.Controls;
|
|||||||
using TweetDuck.Core.Notification;
|
using TweetDuck.Core.Notification;
|
||||||
|
|
||||||
namespace TweetDuck.Core.Handling {
|
namespace TweetDuck.Core.Handling {
|
||||||
sealed class KeyboardHandlerNotification : IKeyboardHandler{
|
sealed class KeyboardHandlerNotification : KeyboardHandlerBase{
|
||||||
private readonly FormNotificationBase notification;
|
private readonly FormNotificationBase notification;
|
||||||
|
|
||||||
public KeyboardHandlerNotification(FormNotificationBase notification){
|
public KeyboardHandlerNotification(FormNotificationBase notification){
|
||||||
@@ -15,31 +15,30 @@ namespace TweetDuck.Core.Handling {
|
|||||||
notification.InvokeAsyncSafe(notification.AnalyticsFile.NotificationKeyboardShortcuts.Trigger);
|
notification.InvokeAsyncSafe(notification.AnalyticsFile.NotificationKeyboardShortcuts.Trigger);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IKeyboardHandler.OnPreKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey, ref bool isKeyboardShortcut){
|
protected override bool HandleRawKey(IWebBrowser browserControl, IBrowser browser, Keys key, CefEventFlags modifiers){
|
||||||
if (type == KeyType.RawKeyDown && !browser.FocusedFrame.Url.StartsWith("chrome-devtools://")){
|
if (base.HandleRawKey(browserControl, browser, key, modifiers)){
|
||||||
switch((Keys)windowsKeyCode){
|
return true;
|
||||||
case Keys.Enter:
|
|
||||||
notification.InvokeAsyncSafe(notification.FinishCurrentNotification);
|
|
||||||
TriggerKeyboardShortcutAnalytics();
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case Keys.Escape:
|
|
||||||
notification.InvokeAsyncSafe(notification.HideNotification);
|
|
||||||
TriggerKeyboardShortcutAnalytics();
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case Keys.Space:
|
|
||||||
notification.InvokeAsyncSafe(() => notification.FreezeTimer = !notification.FreezeTimer);
|
|
||||||
TriggerKeyboardShortcutAnalytics();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
switch(key){
|
||||||
}
|
case Keys.Enter:
|
||||||
|
notification.InvokeAsyncSafe(notification.FinishCurrentNotification);
|
||||||
|
TriggerKeyboardShortcutAnalytics();
|
||||||
|
return true;
|
||||||
|
|
||||||
bool IKeyboardHandler.OnKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey){
|
case Keys.Escape:
|
||||||
return false;
|
notification.InvokeAsyncSafe(notification.HideNotification);
|
||||||
|
TriggerKeyboardShortcutAnalytics();
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case Keys.Space:
|
||||||
|
notification.InvokeAsyncSafe(() => notification.FreezeTimer = !notification.FreezeTimer);
|
||||||
|
TriggerKeyboardShortcutAnalytics();
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,11 +1,38 @@
|
|||||||
using System.Collections.Specialized;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using CefSharp;
|
using CefSharp;
|
||||||
using CefSharp.Handler;
|
using CefSharp.Handler;
|
||||||
using TweetDuck.Core.Handling.General;
|
using TweetDuck.Core.Handling.General;
|
||||||
using TweetDuck.Core.Utils;
|
using TweetDuck.Core.Utils;
|
||||||
|
using TweetLib.Core.Utils;
|
||||||
|
|
||||||
namespace TweetDuck.Core.Handling{
|
namespace TweetDuck.Core.Handling{
|
||||||
class RequestHandlerBase : DefaultRequestHandler{
|
class RequestHandlerBase : DefaultRequestHandler{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private readonly bool autoReload;
|
private readonly bool autoReload;
|
||||||
|
|
||||||
public RequestHandlerBase(bool autoReload){
|
public RequestHandlerBase(bool autoReload){
|
||||||
@@ -25,6 +52,26 @@ namespace TweetDuck.Core.Handling{
|
|||||||
|
|
||||||
return base.OnBeforeResourceLoad(browserControl, browser, frame, request, callback);
|
return base.OnBeforeResourceLoad(browserControl, browser, frame, request, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public 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);
|
||||||
|
}
|
||||||
|
|
||||||
public override void OnRenderProcessTerminated(IWebBrowser browserControl, IBrowser browser, CefTerminationStatus status){
|
public override void OnRenderProcessTerminated(IWebBrowser browserControl, IBrowser browser, CefTerminationStatus status){
|
||||||
if (autoReload){
|
if (autoReload){
|
||||||
|
@@ -1,26 +1,36 @@
|
|||||||
// Uncomment to force TweetDeck to load a predefined version of the vendor/bundle scripts
|
using System.Collections.Specialized;
|
||||||
// #define FREEZE_TWEETDECK_SCRIPTS
|
|
||||||
|
|
||||||
using System.Collections.Specialized;
|
|
||||||
using CefSharp;
|
using CefSharp;
|
||||||
using TweetDuck.Core.Handling.Filters;
|
using TweetDuck.Core.Handling.Filters;
|
||||||
using TweetDuck.Core.Utils;
|
using TweetDuck.Core.Utils;
|
||||||
|
using TweetLib.Core.Features.Twitter;
|
||||||
#if FREEZE_TWEETDECK_SCRIPTS
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace TweetDuck.Core.Handling{
|
namespace TweetDuck.Core.Handling{
|
||||||
sealed class RequestHandlerBrowser : RequestHandlerBase{
|
sealed class RequestHandlerBrowser : RequestHandlerBase{
|
||||||
|
private const string UrlVendorResource = "/dist/vendor";
|
||||||
|
private const string UrlLoadingSpinner = "/backgrounds/spinner_blue";
|
||||||
|
|
||||||
public string BlockNextUserNavUrl { get; set; }
|
public string BlockNextUserNavUrl { get; set; }
|
||||||
|
|
||||||
public RequestHandlerBrowser() : base(true){}
|
public RequestHandlerBrowser() : base(true){}
|
||||||
|
|
||||||
public override CefReturnValue OnBeforeResourceLoad(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IRequestCallback callback){
|
public override CefReturnValue OnBeforeResourceLoad(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IRequestCallback callback){
|
||||||
if (request.ResourceType == ResourceType.Script && request.Url.Contains("analytics.")){
|
if (request.ResourceType == ResourceType.MainFrame){
|
||||||
callback.Dispose();
|
if (request.Url.EndsWith("//twitter.com/")){
|
||||||
return CefReturnValue.Cancel;
|
request.Url = TwitterUrls.TweetDeck; // redirect plain twitter.com requests, fixes bugs with login 2FA
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (request.ResourceType == ResourceType.Script){
|
||||||
|
string url = request.Url;
|
||||||
|
|
||||||
|
if (url.Contains("analytics.")){
|
||||||
|
callback.Dispose();
|
||||||
|
return CefReturnValue.Cancel;
|
||||||
|
}
|
||||||
|
else if (url.Contains(UrlVendorResource)){
|
||||||
|
NameValueCollection headers = request.Headers;
|
||||||
|
headers["Accept-Encoding"] = "identity";
|
||||||
|
request.Headers = headers;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return base.OnBeforeResourceLoad(browserControl, browser, frame, request, callback);
|
return base.OnBeforeResourceLoad(browserControl, browser, frame, request, callback);
|
||||||
@@ -32,62 +42,25 @@ namespace TweetDuck.Core.Handling{
|
|||||||
BlockNextUserNavUrl = string.Empty;
|
BlockNextUserNavUrl = string.Empty;
|
||||||
return block;
|
return block;
|
||||||
}
|
}
|
||||||
|
else if (request.TransitionType.HasFlag(TransitionType.ForwardBack) && TwitterUrls.IsTweetDeck(frame.Url)){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return base.OnBeforeBrowse(browserControl, browser, frame, request, userGesture, isRedirect);
|
return base.OnBeforeBrowse(browserControl, browser, frame, request, userGesture, isRedirect);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if FREEZE_TWEETDECK_SCRIPTS
|
|
||||||
private static readonly Regex TweetDeckScriptUrl = new Regex(@"/dist/(.*?)\.(.*?)\.js$", RegexOptions.Compiled);
|
|
||||||
|
|
||||||
private static readonly SortedList<string, string> TweetDeckHashes = new SortedList<string, string>(2){
|
|
||||||
{ "vendor", "942c0a20e8" },
|
|
||||||
{ "bundle", "1bd75b5854" }
|
|
||||||
};
|
|
||||||
#endif
|
|
||||||
|
|
||||||
public override bool OnResourceResponse(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IResponse response){
|
public override bool OnResourceResponse(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IResponse response){
|
||||||
if (request.ResourceType == ResourceType.Image && request.Url.Contains("/backgrounds/spinner_blue")){
|
if (request.ResourceType == ResourceType.Image && request.Url.Contains(UrlLoadingSpinner)){
|
||||||
request.Url = TwitterUtils.LoadingSpinner.Url;
|
request.Url = TwitterUtils.LoadingSpinner.Url;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
#if FREEZE_TWEETDECK_SCRIPTS
|
|
||||||
else if (request.ResourceType == ResourceType.Script){
|
|
||||||
Match match = TweetDeckScriptUrl.Match(request.Url);
|
|
||||||
|
|
||||||
if (match.Success && TweetDeckHashes.TryGetValue(match.Groups[1].Value, out string hash)){
|
|
||||||
if (match.Groups[2].Value == hash){
|
|
||||||
System.Diagnostics.Debug.WriteLine($"accepting {request.Url}");
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
System.Diagnostics.Debug.WriteLine($"rewriting {request.Url} to {hash}");
|
|
||||||
request.Url = TweetDeckScriptUrl.Replace(request.Url, "/dist/$1."+hash+".js");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return base.OnResourceResponse(browserControl, browser, frame, request, response);
|
return base.OnResourceResponse(browserControl, browser, frame, request, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override IResponseFilter GetResourceResponseFilter(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IResponse response){
|
public override IResponseFilter GetResourceResponseFilter(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IResponse response){
|
||||||
if (request.ResourceType == ResourceType.Script && request.Url.Contains("/dist/vendor")){
|
if (request.ResourceType == ResourceType.Script && request.Url.Contains(UrlVendorResource) && int.TryParse(response.ResponseHeaders["Content-Length"], out int totalBytes)){
|
||||||
NameValueCollection headers = response.ResponseHeaders;
|
return new ResponseFilterVendor(totalBytes);
|
||||||
|
|
||||||
if (int.TryParse(headers["x-ton-expected-size"], out int totalBytes)){
|
|
||||||
return new ResponseFilterVendor(totalBytes);
|
|
||||||
}
|
|
||||||
#if DEBUG
|
|
||||||
else{
|
|
||||||
System.Diagnostics.Debug.WriteLine($"Missing uncompressed size header in {request.Url}");
|
|
||||||
|
|
||||||
foreach(string key in headers){
|
|
||||||
System.Diagnostics.Debug.WriteLine($" {key}: {headers[key]}");
|
|
||||||
}
|
|
||||||
|
|
||||||
System.Diagnostics.Debugger.Break();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return base.GetResourceResponseFilter(browserControl, browser, frame, request, response);
|
return base.GetResourceResponseFilter(browserControl, browser, frame, request, response);
|
||||||
|
43
Core/Handling/ResourceHandlerFactory.cs
Normal file
43
Core/Handling/ResourceHandlerFactory.cs
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using CefSharp;
|
||||||
|
using TweetDuck.Data;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.Handling{
|
||||||
|
sealed class ResourceHandlerFactory : IResourceHandlerFactory{
|
||||||
|
public bool HasHandlers => !handlers.IsEmpty;
|
||||||
|
|
||||||
|
private readonly ConcurrentDictionary<string, IResourceHandler> handlers = new ConcurrentDictionary<string, IResourceHandler>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
public IResourceHandler GetResourceHandler(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request){
|
||||||
|
try{
|
||||||
|
return handlers.TryGetValue(request.Url, out IResourceHandler handler) ? handler : null;
|
||||||
|
}finally{
|
||||||
|
request.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// registration
|
||||||
|
|
||||||
|
public bool RegisterHandler(string url, IResourceHandler handler){
|
||||||
|
if (Uri.TryCreate(url, UriKind.Absolute, out Uri uri)){
|
||||||
|
handlers.AddOrUpdate(uri.AbsoluteUri, handler, (key, prev) => handler);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool RegisterHandler(ResourceLink link){
|
||||||
|
return RegisterHandler(link.Url, link.Handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool UnregisterHandler(string url){
|
||||||
|
return handlers.TryRemove(url, out IResourceHandler _);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool UnregisterHandler(ResourceLink link){
|
||||||
|
return UnregisterHandler(link.Url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -28,7 +28,7 @@ namespace TweetDuck.Core.Management{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void RefreshTimer(){
|
public static void RefreshTimer(){
|
||||||
bool shouldRun = Program.SystemConfig.ClearCacheAutomatically && !ClearOnExit;
|
bool shouldRun = Program.Config.System.ClearCacheAutomatically && !ClearOnExit;
|
||||||
|
|
||||||
if (!shouldRun && AutoClearTimer != null){
|
if (!shouldRun && AutoClearTimer != null){
|
||||||
AutoClearTimer.Dispose();
|
AutoClearTimer.Dispose();
|
||||||
@@ -38,7 +38,7 @@ namespace TweetDuck.Core.Management{
|
|||||||
AutoClearTimer = new Timer(state => {
|
AutoClearTimer = new Timer(state => {
|
||||||
if (AutoClearTimer != null){
|
if (AutoClearTimer != null){
|
||||||
try{
|
try{
|
||||||
if (CalculateCacheSize() >= Program.SystemConfig.ClearCacheThreshold*1024L*1024L){
|
if (CalculateCacheSize() >= Program.Config.System.ClearCacheThreshold * 1024L * 1024L){
|
||||||
SetClearOnExit();
|
SetClearOnExit();
|
||||||
}
|
}
|
||||||
}catch(Exception){
|
}catch(Exception){
|
||||||
@@ -54,6 +54,14 @@ namespace TweetDuck.Core.Management{
|
|||||||
RefreshTimer();
|
RefreshTimer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void TryClearNow(){
|
||||||
|
try{
|
||||||
|
Directory.Delete(CacheFolder, true);
|
||||||
|
}catch{
|
||||||
|
// welp, too bad
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static void Exit(){
|
public static void Exit(){
|
||||||
if (AutoClearTimer != null){
|
if (AutoClearTimer != null){
|
||||||
AutoClearTimer.Dispose();
|
AutoClearTimer.Dispose();
|
||||||
@@ -61,11 +69,7 @@ namespace TweetDuck.Core.Management{
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (ClearOnExit){
|
if (ClearOnExit){
|
||||||
try{
|
TryClearNow();
|
||||||
Directory.Delete(CacheFolder, true);
|
|
||||||
}catch{
|
|
||||||
// welp, too bad
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,49 +1,54 @@
|
|||||||
using CefSharp;
|
using System;
|
||||||
using TweetDuck.Core.Utils;
|
using CefSharp;
|
||||||
|
using TweetLib.Core.Utils;
|
||||||
|
|
||||||
namespace TweetDuck.Core.Management{
|
namespace TweetDuck.Core.Management{
|
||||||
sealed class ContextInfo{
|
sealed class ContextInfo{
|
||||||
public LinkInfo Link { get; private set; }
|
private LinkInfo link;
|
||||||
public ChirpInfo Chirp { get; private set; }
|
private ChirpInfo? chirp;
|
||||||
|
|
||||||
public ContextInfo(){
|
public ContextInfo(){
|
||||||
Reset();
|
Reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetLink(string type, string url){
|
public void SetLink(string type, string url){
|
||||||
Link = string.IsNullOrEmpty(url) ? new LinkInfo() : new LinkInfo(type, url);
|
link = string.IsNullOrEmpty(url) ? null : new LinkInfo(type, url);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetChirp(string tweetUrl, string quoteUrl, string chirpAuthors, string chirpImages){
|
public void SetChirp(string tweetUrl, string quoteUrl, string chirpAuthors, string chirpImages){
|
||||||
Chirp = new ChirpInfo(tweetUrl, quoteUrl, chirpAuthors, chirpImages);
|
chirp = string.IsNullOrEmpty(tweetUrl) ? (ChirpInfo?)null : new ChirpInfo(tweetUrl, quoteUrl, chirpAuthors, chirpImages);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Reset(){
|
public ContextData Reset(){
|
||||||
Link = new LinkInfo();
|
link = null;
|
||||||
Chirp = new ChirpInfo();
|
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
|
// Data structures
|
||||||
|
|
||||||
public struct LinkInfo{
|
private sealed class LinkInfo{
|
||||||
public bool IsLink => type == "link";
|
public string Type { get; }
|
||||||
public bool IsImage => type == "image";
|
public string Url { get; }
|
||||||
public bool IsVideo => type == "video";
|
|
||||||
|
|
||||||
public string GetUrl(IContextMenuParams parameters, bool safe){
|
|
||||||
return IsLink ? url : (safe ? parameters.LinkUrl : parameters.UnfilteredLinkUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
public string GetMediaSource(IContextMenuParams parameters){
|
|
||||||
return IsImage || IsVideo ? url : parameters.SourceUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly string type;
|
|
||||||
private readonly string url;
|
|
||||||
|
|
||||||
public LinkInfo(string type, string url){
|
public LinkInfo(string type, string url){
|
||||||
this.type = type;
|
this.Type = type;
|
||||||
this.url = url;
|
this.Url = url;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,5 +69,93 @@ namespace TweetDuck.Core.Management{
|
|||||||
this.chirpImages = chirpImages;
|
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; }
|
||||||
|
|
||||||
|
public 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -3,9 +3,9 @@ using System.Collections.Generic;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using TweetDuck.Core.Other;
|
using TweetDuck.Core.Other;
|
||||||
using TweetDuck.Data;
|
using TweetLib.Core.Data;
|
||||||
using TweetDuck.Plugins;
|
using TweetLib.Core.Features.Plugins;
|
||||||
using TweetDuck.Plugins.Enums;
|
using TweetLib.Core.Features.Plugins.Enums;
|
||||||
|
|
||||||
namespace TweetDuck.Core.Management{
|
namespace TweetDuck.Core.Management{
|
||||||
sealed class ProfileManager{
|
sealed class ProfileManager{
|
||||||
@@ -49,7 +49,7 @@ namespace TweetDuck.Core.Management{
|
|||||||
try{
|
try{
|
||||||
stream.WriteFile(new string[]{ "plugin.data", plugin.Identifier, path.Relative }, path.Full);
|
stream.WriteFile(new string[]{ "plugin.data", plugin.Identifier, path.Relative }, path.Full);
|
||||||
}catch(ArgumentOutOfRangeException e){
|
}catch(ArgumentOutOfRangeException e){
|
||||||
FormMessage.Warning("Export Profile", "Could not include a plugin file in the export. "+e.Message, FormMessage.OK);
|
FormMessage.Warning("Export Profile", "Could not include a plugin file in the export. " + e.Message, FormMessage.OK);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -73,28 +73,27 @@ namespace TweetDuck.Core.Management{
|
|||||||
Items items = Items.None;
|
Items items = Items.None;
|
||||||
|
|
||||||
try{
|
try{
|
||||||
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));
|
||||||
string key;
|
string key;
|
||||||
|
|
||||||
while((key = stream.SkipFile()) != null){
|
while((key = stream.SkipFile()) != null){
|
||||||
switch(key){
|
switch(key){
|
||||||
case "config":
|
case "config":
|
||||||
items |= Items.UserConfig;
|
items |= Items.UserConfig;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "system":
|
case "system":
|
||||||
items |= Items.SystemConfig;
|
items |= Items.SystemConfig;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "plugin.config":
|
case "plugin.config":
|
||||||
case "plugin.data":
|
case "plugin.data":
|
||||||
items |= Items.PluginData;
|
items |= Items.PluginData;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "cookies":
|
case "cookies":
|
||||||
items |= Items.Session;
|
items |= Items.Session;
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}catch(Exception){
|
}catch(Exception){
|
||||||
@@ -140,7 +139,7 @@ namespace TweetDuck.Core.Management{
|
|||||||
|
|
||||||
entry.WriteToFile(Path.Combine(Program.PluginDataPath, value[0], value[1]), true);
|
entry.WriteToFile(Path.Combine(Program.PluginDataPath, value[0], value[1]), true);
|
||||||
|
|
||||||
if (!plugins.IsPluginInstalled(value[0])){
|
if (!plugins.Plugins.Any(plugin => plugin.Identifier.Equals(value[0]))){
|
||||||
missingPlugins.Add(value[0]);
|
missingPlugins.Add(value[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -158,7 +157,7 @@ namespace TweetDuck.Core.Management{
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (missingPlugins.Count > 0){
|
if (missingPlugins.Count > 0){
|
||||||
FormMessage.Information("Profile Import", "Detected missing plugins when importing plugin data:\n"+string.Join("\n", missingPlugins), FormMessage.OK);
|
FormMessage.Information("Profile Import", "Detected missing plugins when importing plugin data:\n" + string.Join("\n", missingPlugins), FormMessage.OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
|
using TweetDuck.Configuration;
|
||||||
using TweetDuck.Core.Controls;
|
using TweetDuck.Core.Controls;
|
||||||
using TweetDuck.Core.Other;
|
using TweetDuck.Core.Other;
|
||||||
using TweetDuck.Core.Utils;
|
using TweetDuck.Core.Utils;
|
||||||
@@ -9,6 +10,8 @@ using TweetLib.Communication;
|
|||||||
|
|
||||||
namespace TweetDuck.Core.Management{
|
namespace TweetDuck.Core.Management{
|
||||||
sealed class VideoPlayer : IDisposable{
|
sealed class VideoPlayer : IDisposable{
|
||||||
|
private static UserConfig Config => Program.Config.User;
|
||||||
|
|
||||||
public bool Running => currentInstance != null && currentInstance.Running;
|
public bool Running => currentInstance != null && currentInstance.Running;
|
||||||
|
|
||||||
public event EventHandler ProcessExited;
|
public event EventHandler ProcessExited;
|
||||||
@@ -37,7 +40,7 @@ namespace TweetDuck.Core.Management{
|
|||||||
|
|
||||||
if ((process = Process.Start(new ProcessStartInfo{
|
if ((process = Process.Start(new ProcessStartInfo{
|
||||||
FileName = Path.Combine(Program.ProgramPath, "TweetDuck.Video.exe"),
|
FileName = Path.Combine(Program.ProgramPath, "TweetDuck.Video.exe"),
|
||||||
Arguments = $"{owner.Handle} {Program.UserConfig.VideoPlayerVolume} \"{url}\" \"{pipe.GenerateToken()}\"",
|
Arguments = $"{owner.Handle} {(int)Math.Floor(100F * owner.GetDPIScale())} {Config.VideoPlayerVolume} \"{url}\" \"{pipe.GenerateToken()}\"",
|
||||||
UseShellExecute = false,
|
UseShellExecute = false,
|
||||||
RedirectStandardOutput = true
|
RedirectStandardOutput = true
|
||||||
})) != null){
|
})) != null){
|
||||||
@@ -68,9 +71,9 @@ namespace TweetDuck.Core.Management{
|
|||||||
owner.InvokeSafe(() => {
|
owner.InvokeSafe(() => {
|
||||||
switch(e.Key){
|
switch(e.Key){
|
||||||
case "vol":
|
case "vol":
|
||||||
if (int.TryParse(e.Data, out int volume) && volume != Program.UserConfig.VideoPlayerVolume){
|
if (int.TryParse(e.Data, out int volume) && volume != Config.VideoPlayerVolume){
|
||||||
Program.UserConfig.VideoPlayerVolume = volume;
|
Config.VideoPlayerVolume = volume;
|
||||||
Program.UserConfig.Save();
|
Config.Save();
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@@ -132,7 +135,7 @@ namespace TweetDuck.Core.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.Log("[VideoPlayer] "+e.Data);
|
Program.Reporter.LogVerbose("[VideoPlayer] " + e.Data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -2,17 +2,17 @@
|
|||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using CefSharp;
|
using CefSharp;
|
||||||
using TweetDuck.Core.Controls;
|
using TweetDuck.Core.Controls;
|
||||||
using TweetDuck.Plugins;
|
using TweetLib.Core.Features.Notifications;
|
||||||
using TweetDuck.Resources;
|
using TweetLib.Core.Features.Plugins;
|
||||||
|
|
||||||
namespace TweetDuck.Core.Notification.Example{
|
namespace TweetDuck.Core.Notification.Example{
|
||||||
sealed class FormNotificationExample : FormNotificationMain{
|
sealed class FormNotificationExample : FormNotificationMain{
|
||||||
public override bool RequiresResize => true;
|
public override bool RequiresResize => true;
|
||||||
protected override bool CanDragWindow => Program.UserConfig.NotificationPosition == TweetNotification.Position.Custom;
|
protected override bool CanDragWindow => Config.NotificationPosition == DesktopNotification.Position.Custom;
|
||||||
|
|
||||||
protected override FormBorderStyle NotificationBorderStyle{
|
protected override FormBorderStyle NotificationBorderStyle{
|
||||||
get{
|
get{
|
||||||
if (Program.UserConfig.NotificationSize == TweetNotification.Size.Custom){
|
if (Config.NotificationSize == DesktopNotification.Size.Custom){
|
||||||
switch(base.NotificationBorderStyle){
|
switch(base.NotificationBorderStyle){
|
||||||
case FormBorderStyle.FixedSingle: return FormBorderStyle.Sizable;
|
case FormBorderStyle.FixedSingle: return FormBorderStyle.Sizable;
|
||||||
case FormBorderStyle.FixedToolWindow: return FormBorderStyle.SizableToolWindow;
|
case FormBorderStyle.FixedToolWindow: return FormBorderStyle.SizableToolWindow;
|
||||||
@@ -23,20 +23,22 @@ namespace TweetDuck.Core.Notification.Example{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override string BodyClasses => base.BodyClasses + " td-example";
|
||||||
|
|
||||||
public event EventHandler Ready;
|
public event EventHandler Ready;
|
||||||
|
|
||||||
private readonly TweetNotification exampleNotification;
|
private readonly DesktopNotification exampleNotification;
|
||||||
|
|
||||||
public FormNotificationExample(FormBrowser owner, PluginManager pluginManager) : base(owner, pluginManager, false){
|
public FormNotificationExample(FormBrowser owner, PluginManager pluginManager) : base(owner, pluginManager, false){
|
||||||
browser.LoadingStateChanged += browser_LoadingStateChanged;
|
browser.LoadingStateChanged += browser_LoadingStateChanged;
|
||||||
|
|
||||||
string exampleTweetHTML = ScriptLoader.LoadResourceSilent("pages/example.html")?.Replace("{avatar}", TweetNotification.AppLogo.Url) ?? string.Empty;
|
string exampleTweetHTML = Program.Resources.LoadSilent("pages/example.html")?.Replace("{avatar}", AppLogo.Url) ?? string.Empty;
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
exampleTweetHTML = exampleTweetHTML.Replace("</p>", @"</p><div style='margin-top:256px'>Scrollbar test padding...</div>");
|
exampleTweetHTML = exampleTweetHTML.Replace("</p>", @"</p><div style='margin-top:256px'>Scrollbar test padding...</div>");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
exampleNotification = TweetNotification.Example(exampleTweetHTML, 176);
|
exampleNotification = new DesktopNotification(string.Empty, string.Empty, "Home", exampleTweetHTML, 176, string.Empty, string.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e){
|
private void browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e){
|
||||||
|
@@ -1,62 +1,69 @@
|
|||||||
using CefSharp.WinForms;
|
using CefSharp.WinForms;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
|
using CefSharp;
|
||||||
using TweetDuck.Configuration;
|
using TweetDuck.Configuration;
|
||||||
using TweetDuck.Core.Bridge;
|
|
||||||
using TweetDuck.Core.Controls;
|
using TweetDuck.Core.Controls;
|
||||||
using TweetDuck.Core.Handling;
|
using TweetDuck.Core.Handling;
|
||||||
using TweetDuck.Core.Handling.General;
|
using TweetDuck.Core.Handling.General;
|
||||||
using TweetDuck.Core.Other.Analytics;
|
using TweetDuck.Core.Other.Analytics;
|
||||||
using TweetDuck.Core.Utils;
|
using TweetDuck.Core.Utils;
|
||||||
|
using TweetDuck.Data;
|
||||||
|
using TweetLib.Core.Features.Notifications;
|
||||||
|
using TweetLib.Core.Features.Twitter;
|
||||||
|
|
||||||
namespace TweetDuck.Core.Notification{
|
namespace TweetDuck.Core.Notification{
|
||||||
partial class FormNotificationBase : Form, AnalyticsFile.IProvider{
|
abstract partial class FormNotificationBase : Form, AnalyticsFile.IProvider{
|
||||||
|
public static readonly ResourceLink AppLogo = new ResourceLink("https://ton.twimg.com/tduck/avatar", ResourceHandler.FromByteArray(Properties.Resources.avatar, "image/png"));
|
||||||
|
|
||||||
|
public static string FontSize = null;
|
||||||
|
public static string HeadLayout = null;
|
||||||
|
|
||||||
|
protected static UserConfig Config => Program.Config.User;
|
||||||
|
|
||||||
protected static int FontSizeLevel{
|
protected static int FontSizeLevel{
|
||||||
get{
|
get => FontSize switch{
|
||||||
switch(TweetDeckBridge.FontSize){
|
"largest" => 4,
|
||||||
case "largest": return 4;
|
"large" => 3,
|
||||||
case "large": return 3;
|
"small" => 1,
|
||||||
case "small": return 1;
|
"smallest" => 0,
|
||||||
case "smallest": return 0;
|
_ => 2
|
||||||
default: return 2;
|
};
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual Point PrimaryLocation{
|
protected virtual Point PrimaryLocation{
|
||||||
get{
|
get{
|
||||||
UserConfig config = Program.UserConfig;
|
|
||||||
Screen screen;
|
Screen screen;
|
||||||
|
|
||||||
if (config.NotificationDisplay > 0 && config.NotificationDisplay <= Screen.AllScreens.Length){
|
if (Config.NotificationDisplay > 0 && Config.NotificationDisplay <= Screen.AllScreens.Length){
|
||||||
screen = Screen.AllScreens[config.NotificationDisplay-1];
|
screen = Screen.AllScreens[Config.NotificationDisplay - 1];
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
screen = Screen.FromControl(owner);
|
screen = Screen.FromControl(owner);
|
||||||
}
|
}
|
||||||
|
|
||||||
int edgeDist = config.NotificationEdgeDistance;
|
int edgeDist = Config.NotificationEdgeDistance;
|
||||||
|
|
||||||
switch(config.NotificationPosition){
|
switch(Config.NotificationPosition){
|
||||||
case TweetNotification.Position.TopLeft:
|
case DesktopNotification.Position.TopLeft:
|
||||||
return new Point(screen.WorkingArea.X+edgeDist, screen.WorkingArea.Y+edgeDist);
|
return new Point(screen.WorkingArea.X + edgeDist, screen.WorkingArea.Y + edgeDist);
|
||||||
|
|
||||||
case TweetNotification.Position.TopRight:
|
case DesktopNotification.Position.TopRight:
|
||||||
return new Point(screen.WorkingArea.X+screen.WorkingArea.Width-edgeDist-Width, screen.WorkingArea.Y+edgeDist);
|
return new Point(screen.WorkingArea.X + screen.WorkingArea.Width - edgeDist - Width, screen.WorkingArea.Y + edgeDist);
|
||||||
|
|
||||||
case TweetNotification.Position.BottomLeft:
|
case DesktopNotification.Position.BottomLeft:
|
||||||
return new Point(screen.WorkingArea.X+edgeDist, screen.WorkingArea.Y+screen.WorkingArea.Height-edgeDist-Height);
|
return new Point(screen.WorkingArea.X + edgeDist, screen.WorkingArea.Y + screen.WorkingArea.Height - edgeDist - Height);
|
||||||
|
|
||||||
case TweetNotification.Position.BottomRight:
|
case DesktopNotification.Position.BottomRight:
|
||||||
return new Point(screen.WorkingArea.X+screen.WorkingArea.Width-edgeDist-Width, screen.WorkingArea.Y+screen.WorkingArea.Height-edgeDist-Height);
|
return new Point(screen.WorkingArea.X + screen.WorkingArea.Width - edgeDist - Width, screen.WorkingArea.Y + screen.WorkingArea.Height - edgeDist - Height);
|
||||||
|
|
||||||
case TweetNotification.Position.Custom:
|
case DesktopNotification.Position.Custom:
|
||||||
if (!config.IsCustomNotificationPositionSet){
|
if (!Config.IsCustomNotificationPositionSet){
|
||||||
config.CustomNotificationPosition = new Point(screen.WorkingArea.X+screen.WorkingArea.Width-edgeDist-Width, screen.WorkingArea.Y+edgeDist);
|
Config.CustomNotificationPosition = new Point(screen.WorkingArea.X + screen.WorkingArea.Width - edgeDist - Width, screen.WorkingArea.Y + edgeDist);
|
||||||
config.Save();
|
Config.Save();
|
||||||
}
|
}
|
||||||
|
|
||||||
return config.CustomNotificationPosition;
|
return Config.CustomNotificationPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Location;
|
return Location;
|
||||||
@@ -93,14 +100,14 @@ namespace TweetDuck.Core.Notification{
|
|||||||
protected override bool ShowWithoutActivation => true;
|
protected override bool ShowWithoutActivation => true;
|
||||||
|
|
||||||
protected float DpiScale { get; }
|
protected float DpiScale { get; }
|
||||||
protected double SizeScale => DpiScale*Program.UserConfig.ZoomLevel/100.0;
|
protected double SizeScale => DpiScale * Config.ZoomLevel / 100.0;
|
||||||
|
|
||||||
protected readonly FormBrowser owner;
|
protected readonly FormBrowser owner;
|
||||||
protected readonly ChromiumWebBrowser browser;
|
protected readonly ChromiumWebBrowser browser;
|
||||||
|
|
||||||
private readonly ResourceHandlerNotification resourceHandler = new ResourceHandlerNotification();
|
private readonly ResourceHandlerNotification resourceHandler = new ResourceHandlerNotification();
|
||||||
|
|
||||||
private TweetNotification currentNotification;
|
private DesktopNotification currentNotification;
|
||||||
private int pauseCounter;
|
private int pauseCounter;
|
||||||
|
|
||||||
public string CurrentTweetUrl => currentNotification?.TweetUrl;
|
public string CurrentTweetUrl => currentNotification?.TweetUrl;
|
||||||
@@ -120,18 +127,21 @@ namespace TweetDuck.Core.Notification{
|
|||||||
this.owner = owner;
|
this.owner = owner;
|
||||||
this.owner.FormClosed += owner_FormClosed;
|
this.owner.FormClosed += owner_FormClosed;
|
||||||
|
|
||||||
|
ResourceHandlerFactory resourceHandlerFactory = new ResourceHandlerFactory();
|
||||||
|
resourceHandlerFactory.RegisterHandler(TwitterUrls.TweetDeck, this.resourceHandler);
|
||||||
|
resourceHandlerFactory.RegisterHandler(AppLogo);
|
||||||
|
|
||||||
this.browser = new ChromiumWebBrowser("about:blank"){
|
this.browser = new ChromiumWebBrowser("about:blank"){
|
||||||
MenuHandler = new ContextMenuNotification(this, enableContextMenu),
|
MenuHandler = new ContextMenuNotification(this, enableContextMenu),
|
||||||
JsDialogHandler = new JavaScriptDialogHandler(),
|
JsDialogHandler = new JavaScriptDialogHandler(),
|
||||||
LifeSpanHandler = new LifeSpanHandler(),
|
LifeSpanHandler = new LifeSpanHandler(),
|
||||||
RequestHandler = new RequestHandlerBase(false)
|
RequestHandler = new RequestHandlerBase(false),
|
||||||
|
ResourceHandlerFactory = resourceHandlerFactory
|
||||||
};
|
};
|
||||||
|
|
||||||
this.browser.Dock = DockStyle.None;
|
this.browser.Dock = DockStyle.None;
|
||||||
this.browser.ClientSize = ClientSize;
|
this.browser.ClientSize = ClientSize;
|
||||||
|
this.browser.SetupZoomEvents();
|
||||||
browser.SetupResourceHandler(TwitterUtils.TweetDeckURL, this.resourceHandler);
|
|
||||||
browser.SetupResourceHandler(TweetNotification.AppLogo);
|
|
||||||
|
|
||||||
Controls.Add(browser);
|
Controls.Add(browser);
|
||||||
|
|
||||||
@@ -184,15 +194,13 @@ namespace TweetDuck.Core.Notification{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual string GetTweetHTML(TweetNotification tweet){
|
protected abstract string GetTweetHTML(DesktopNotification tweet);
|
||||||
return tweet.GenerateHtml(IsCursorOverBrowser ? "td-notification td-hover" : "td-notification", this);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void LoadTweet(TweetNotification tweet){
|
protected virtual void LoadTweet(DesktopNotification tweet){
|
||||||
currentNotification = tweet;
|
currentNotification = tweet;
|
||||||
resourceHandler.SetHTML(GetTweetHTML(tweet));
|
resourceHandler.SetHTML(GetTweetHTML(tweet));
|
||||||
|
|
||||||
browser.Load(TwitterUtils.TweetDeckURL);
|
browser.Load(TwitterUrls.TweetDeck);
|
||||||
DisplayTooltip(null);
|
DisplayTooltip(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,7 +210,7 @@ namespace TweetDuck.Core.Notification{
|
|||||||
|
|
||||||
protected virtual void UpdateTitle(){
|
protected virtual void UpdateTitle(){
|
||||||
string title = currentNotification?.ColumnTitle;
|
string title = currentNotification?.ColumnTitle;
|
||||||
Text = string.IsNullOrEmpty(title) || !Program.UserConfig.DisplayNotificationColumn ? Program.BrandName : Program.BrandName+" - "+title;
|
Text = string.IsNullOrEmpty(title) || !Config.DisplayNotificationColumn ? Program.BrandName : $"{Program.BrandName} - {title}";
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ShowTweetDetail(){
|
public void ShowTweetDetail(){
|
||||||
|
@@ -2,18 +2,19 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
|
using TweetDuck.Core.Adapters;
|
||||||
using TweetDuck.Core.Bridge;
|
using TweetDuck.Core.Bridge;
|
||||||
using TweetDuck.Core.Controls;
|
using TweetDuck.Core.Controls;
|
||||||
using TweetDuck.Core.Handling;
|
using TweetDuck.Core.Handling;
|
||||||
using TweetDuck.Core.Other.Interfaces;
|
|
||||||
using TweetDuck.Core.Utils;
|
using TweetDuck.Core.Utils;
|
||||||
using TweetDuck.Data;
|
|
||||||
using TweetDuck.Plugins;
|
using TweetDuck.Plugins;
|
||||||
using TweetDuck.Plugins.Enums;
|
using TweetLib.Core.Data;
|
||||||
using TweetDuck.Resources;
|
using TweetLib.Core.Features.Notifications;
|
||||||
|
using TweetLib.Core.Features.Plugins;
|
||||||
|
using TweetLib.Core.Features.Plugins.Enums;
|
||||||
|
|
||||||
namespace TweetDuck.Core.Notification{
|
namespace TweetDuck.Core.Notification{
|
||||||
abstract partial class FormNotificationMain : FormNotificationBase, ITweetDeckBrowser{
|
abstract partial class FormNotificationMain : FormNotificationBase{
|
||||||
private readonly PluginManager plugins;
|
private readonly PluginManager plugins;
|
||||||
private readonly int timerBarHeight;
|
private readonly int timerBarHeight;
|
||||||
|
|
||||||
@@ -29,7 +30,7 @@ namespace TweetDuck.Core.Notification{
|
|||||||
|
|
||||||
public virtual bool RequiresResize{
|
public virtual bool RequiresResize{
|
||||||
get{
|
get{
|
||||||
return !prevDisplayTimer.HasValue || !prevFontSize.HasValue || prevDisplayTimer != Program.UserConfig.DisplayNotificationTimer || prevFontSize != FontSizeLevel;
|
return !prevDisplayTimer.HasValue || !prevFontSize.HasValue || prevDisplayTimer != Config.DisplayNotificationTimer || prevFontSize != FontSizeLevel;
|
||||||
}
|
}
|
||||||
|
|
||||||
set{
|
set{
|
||||||
@@ -38,37 +39,29 @@ namespace TweetDuck.Core.Notification{
|
|||||||
prevFontSize = null;
|
prevFontSize = null;
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
prevDisplayTimer = Program.UserConfig.DisplayNotificationTimer;
|
prevDisplayTimer = Config.DisplayNotificationTimer;
|
||||||
prevFontSize = FontSizeLevel;
|
prevFontSize = FontSizeLevel;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private int BaseClientWidth{
|
private int BaseClientWidth{
|
||||||
get{
|
get => Config.NotificationSize switch{
|
||||||
switch(Program.UserConfig.NotificationSize){
|
DesktopNotification.Size.Custom => Config.CustomNotificationSize.Width,
|
||||||
default:
|
_ => BrowserUtils.Scale(284, SizeScale * (1.0 + 0.05 * FontSizeLevel))
|
||||||
return BrowserUtils.Scale(284, SizeScale*(1.0+0.05*FontSizeLevel));
|
};
|
||||||
|
|
||||||
case TweetNotification.Size.Custom:
|
|
||||||
return Program.UserConfig.CustomNotificationSize.Width;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private int BaseClientHeight{
|
private int BaseClientHeight{
|
||||||
get{
|
get => Config.NotificationSize switch{
|
||||||
switch(Program.UserConfig.NotificationSize){
|
DesktopNotification.Size.Custom => Config.CustomNotificationSize.Height,
|
||||||
default:
|
_ => BrowserUtils.Scale(122, SizeScale * (1.0 + 0.08 * FontSizeLevel))
|
||||||
return BrowserUtils.Scale(122, SizeScale*(1.0+0.08*FontSizeLevel));
|
};
|
||||||
|
|
||||||
case TweetNotification.Size.Custom:
|
|
||||||
return Program.UserConfig.CustomNotificationSize.Height;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected virtual string BodyClasses => IsCursorOverBrowser ? "td-notification td-hover" : "td-notification";
|
||||||
|
|
||||||
public Size BrowserSize => Program.UserConfig.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, PluginManager pluginManager, bool enableContextMenu) : base(owner, enableContextMenu){
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
@@ -82,34 +75,12 @@ namespace TweetDuck.Core.Notification{
|
|||||||
browser.LoadingStateChanged += Browser_LoadingStateChanged;
|
browser.LoadingStateChanged += Browser_LoadingStateChanged;
|
||||||
browser.FrameLoadEnd += Browser_FrameLoadEnd;
|
browser.FrameLoadEnd += Browser_FrameLoadEnd;
|
||||||
|
|
||||||
plugins.Register(this, PluginEnvironment.Notification, this);
|
plugins.Register(PluginEnvironment.Notification, new PluginDispatcher(browser));
|
||||||
|
|
||||||
mouseHookDelegate = MouseHookProc;
|
mouseHookDelegate = MouseHookProc;
|
||||||
Disposed += (sender, args) => StopMouseHook(true);
|
Disposed += (sender, args) => StopMouseHook(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// implementation of ITweetDeckBrowser
|
|
||||||
|
|
||||||
bool ITweetDeckBrowser.IsTweetDeckWebsite => IsNotificationVisible;
|
|
||||||
|
|
||||||
void ITweetDeckBrowser.RegisterBridge(string name, object obj){
|
|
||||||
browser.RegisterAsyncJsObject(name, obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ITweetDeckBrowser.OnFrameLoaded(Action<IFrame> callback){
|
|
||||||
browser.FrameLoadEnd += (sender, args) => {
|
|
||||||
IFrame frame = args.Frame;
|
|
||||||
|
|
||||||
if (frame.IsMain && browser.Address != "about:blank"){
|
|
||||||
callback(frame);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
void ITweetDeckBrowser.ExecuteFunction(string name, params object[] args){
|
|
||||||
browser.ExecuteScriptAsync(name, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
// mouse wheel hook
|
// mouse wheel hook
|
||||||
|
|
||||||
private void StartMouseHook(){
|
private void StartMouseHook(){
|
||||||
@@ -131,7 +102,7 @@ namespace TweetDuck.Core.Notification{
|
|||||||
int eventType = wParam.ToInt32();
|
int eventType = wParam.ToInt32();
|
||||||
|
|
||||||
if (eventType == NativeMethods.WM_MOUSEWHEEL && IsCursorOverBrowser){
|
if (eventType == NativeMethods.WM_MOUSEWHEEL && IsCursorOverBrowser){
|
||||||
browser.SendMouseWheelEvent(0, 0, 0, BrowserUtils.Scale(NativeMethods.GetMouseHookData(lParam), Program.UserConfig.NotificationScrollSpeed*0.01), CefEventFlags.None);
|
browser.SendMouseWheelEvent(0, 0, 0, BrowserUtils.Scale(NativeMethods.GetMouseHookData(lParam), Config.NotificationScrollSpeed * 0.01), CefEventFlags.None);
|
||||||
return NativeMethods.HOOK_HANDLED;
|
return NativeMethods.HOOK_HANDLED;
|
||||||
}
|
}
|
||||||
else if (eventType == NativeMethods.WM_XBUTTONDOWN && DesktopBounds.Contains(Cursor.Position)){
|
else if (eventType == NativeMethods.WM_XBUTTONDOWN && DesktopBounds.Contains(Cursor.Position)){
|
||||||
@@ -181,9 +152,11 @@ namespace TweetDuck.Core.Notification{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void Browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
|
private void Browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
|
||||||
if (e.Frame.IsMain && browser.Address != "about:blank"){
|
IFrame frame = e.Frame;
|
||||||
e.Frame.ExecuteJavaScriptAsync(PropertyBridge.GenerateScript(PropertyBridge.Environment.Notification));
|
|
||||||
ScriptLoader.ExecuteScript(e.Frame, ScriptLoader.LoadResource("notification.js", this), "root:notification");
|
if (frame.IsMain && browser.Address != "about:blank"){
|
||||||
|
frame.ExecuteJavaScriptAsync(PropertyBridge.GenerateScript(PropertyBridge.Environment.Notification));
|
||||||
|
CefScriptExecutor.RunFile(frame, "notification.js");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,14 +166,23 @@ namespace TweetDuck.Core.Notification{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void timerHideProgress_Tick(object sender, EventArgs e){
|
private void timerHideProgress_Tick(object sender, EventArgs e){
|
||||||
if (Bounds.Contains(Cursor.Position) || FreezeTimer || ContextMenuOpen){
|
bool isCursorInside = Bounds.Contains(Cursor.Position);
|
||||||
|
|
||||||
|
if (isCursorInside){
|
||||||
|
StartMouseHook();
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
StopMouseHook(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isCursorInside || FreezeTimer || ContextMenuOpen){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
timeLeft -= timerProgress.Interval;
|
timeLeft -= timerProgress.Interval;
|
||||||
|
|
||||||
int value = BrowserUtils.Scale(progressBarTimer.Maximum+25, (totalTime-timeLeft)/(double)totalTime);
|
int value = BrowserUtils.Scale(progressBarTimer.Maximum + 25, (totalTime - timeLeft) / (double)totalTime);
|
||||||
progressBarTimer.SetValueInstant(Program.UserConfig.NotificationTimerCountDown ? progressBarTimer.Maximum-value : value);
|
progressBarTimer.SetValueInstant(Config.NotificationTimerCountDown ? progressBarTimer.Maximum - value : value);
|
||||||
|
|
||||||
if (timeLeft <= 0){
|
if (timeLeft <= 0){
|
||||||
FinishCurrentNotification();
|
FinishCurrentNotification();
|
||||||
@@ -209,14 +191,14 @@ namespace TweetDuck.Core.Notification{
|
|||||||
|
|
||||||
// notification methods
|
// notification methods
|
||||||
|
|
||||||
public virtual void ShowNotification(TweetNotification notification){
|
public virtual void ShowNotification(DesktopNotification notification){
|
||||||
LoadTweet(notification);
|
LoadTweet(notification);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void HideNotification(){
|
public override void HideNotification(){
|
||||||
base.HideNotification();
|
base.HideNotification();
|
||||||
|
|
||||||
progressBarTimer.Value = Program.UserConfig.NotificationTimerCountDown ? progressBarTimer.Maximum : progressBarTimer.Minimum;
|
progressBarTimer.Value = Config.NotificationTimerCountDown ? progressBarTimer.Maximum : progressBarTimer.Minimum;
|
||||||
timerProgress.Stop();
|
timerProgress.Stop();
|
||||||
totalTime = 0;
|
totalTime = 0;
|
||||||
|
|
||||||
@@ -246,8 +228,8 @@ namespace TweetDuck.Core.Notification{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string GetTweetHTML(TweetNotification tweet){
|
protected override string GetTweetHTML(DesktopNotification tweet){
|
||||||
string html = base.GetTweetHTML(tweet);
|
string html = tweet.GenerateHtml(BodyClasses, HeadLayout, Config.CustomNotificationCSS);
|
||||||
|
|
||||||
foreach(InjectedHTML injection in plugins.NotificationInjections){
|
foreach(InjectedHTML injection in plugins.NotificationInjections){
|
||||||
html = injection.InjectInto(html);
|
html = injection.InjectInto(html);
|
||||||
@@ -256,17 +238,17 @@ namespace TweetDuck.Core.Notification{
|
|||||||
return html;
|
return html;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadTweet(TweetNotification tweet){
|
protected override void LoadTweet(DesktopNotification tweet){
|
||||||
timerProgress.Stop();
|
timerProgress.Stop();
|
||||||
totalTime = timeLeft = tweet.GetDisplayDuration(Program.UserConfig.NotificationDurationValue);
|
totalTime = timeLeft = tweet.GetDisplayDuration(Config.NotificationDurationValue);
|
||||||
progressBarTimer.Value = Program.UserConfig.NotificationTimerCountDown ? progressBarTimer.Maximum : progressBarTimer.Minimum;
|
progressBarTimer.Value = Config.NotificationTimerCountDown ? progressBarTimer.Maximum : progressBarTimer.Minimum;
|
||||||
|
|
||||||
base.LoadTweet(tweet);
|
base.LoadTweet(tweet);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void SetNotificationSize(int width, int height){
|
protected override void SetNotificationSize(int width, int height){
|
||||||
if (Program.UserConfig.DisplayNotificationTimer){
|
if (Config.DisplayNotificationTimer){
|
||||||
ClientSize = new Size(width, height+timerBarHeight);
|
ClientSize = new Size(width, height + timerBarHeight);
|
||||||
progressBarTimer.Visible = true;
|
progressBarTimer.Visible = true;
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
@@ -284,7 +266,6 @@ namespace TweetDuck.Core.Notification{
|
|||||||
}
|
}
|
||||||
|
|
||||||
MoveToVisibleLocation();
|
MoveToVisibleLocation();
|
||||||
StartMouseHook();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void OnNotificationReady(){
|
protected virtual void OnNotificationReady(){
|
||||||
|
@@ -1,9 +1,10 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using TweetDuck.Plugins;
|
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using TweetDuck.Core.Utils;
|
using TweetDuck.Core.Utils;
|
||||||
|
using TweetLib.Core.Features.Notifications;
|
||||||
|
using TweetLib.Core.Features.Plugins;
|
||||||
|
|
||||||
namespace TweetDuck.Core.Notification{
|
namespace TweetDuck.Core.Notification{
|
||||||
sealed partial class FormNotificationTweet : FormNotificationMain{
|
sealed partial class FormNotificationTweet : FormNotificationMain{
|
||||||
@@ -25,17 +26,17 @@ namespace TweetDuck.Core.Notification{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly Queue<TweetNotification> tweetQueue = new Queue<TweetNotification>(4);
|
private readonly Queue<DesktopNotification> tweetQueue = new Queue<DesktopNotification>(4);
|
||||||
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, PluginManager pluginManager) : base(owner, pluginManager, true){
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
Program.UserConfig.MuteToggled += Config_MuteToggled;
|
Config.MuteToggled += Config_MuteToggled;
|
||||||
Disposed += (sender, args) => Program.UserConfig.MuteToggled -= Config_MuteToggled;
|
Disposed += (sender, args) => Config.MuteToggled -= Config_MuteToggled;
|
||||||
|
|
||||||
if (Program.UserConfig.MuteNotifications){
|
if (Config.MuteNotifications){
|
||||||
PauseNotification();
|
PauseNotification();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -57,7 +58,7 @@ namespace TweetDuck.Core.Notification{
|
|||||||
// event handlers
|
// event handlers
|
||||||
|
|
||||||
private void Config_MuteToggled(object sender, EventArgs e){
|
private void Config_MuteToggled(object sender, EventArgs e){
|
||||||
if (Program.UserConfig.MuteNotifications){
|
if (Config.MuteNotifications){
|
||||||
PauseNotification();
|
PauseNotification();
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
@@ -73,7 +74,7 @@ namespace TweetDuck.Core.Notification{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void timerIdlePauseCheck_Tick(object sender, EventArgs e){
|
private void timerIdlePauseCheck_Tick(object sender, EventArgs e){
|
||||||
if (NativeMethods.GetIdleSeconds() < Program.UserConfig.NotificationIdlePauseSeconds){
|
if (NativeMethods.GetIdleSeconds() < Config.NotificationIdlePauseSeconds){
|
||||||
ResumeNotification();
|
ResumeNotification();
|
||||||
timerIdlePauseCheck.Stop();
|
timerIdlePauseCheck.Stop();
|
||||||
}
|
}
|
||||||
@@ -81,7 +82,7 @@ namespace TweetDuck.Core.Notification{
|
|||||||
|
|
||||||
// notification methods
|
// notification methods
|
||||||
|
|
||||||
public override void ShowNotification(TweetNotification notification){
|
public override void ShowNotification(DesktopNotification notification){
|
||||||
tweetQueue.Enqueue(notification);
|
tweetQueue.Enqueue(notification);
|
||||||
|
|
||||||
if (!IsPaused){
|
if (!IsPaused){
|
||||||
@@ -128,7 +129,7 @@ namespace TweetDuck.Core.Notification{
|
|||||||
|
|
||||||
private void LoadNextNotification(){
|
private void LoadNextNotification(){
|
||||||
if (!IsNotificationVisible){
|
if (!IsNotificationVisible){
|
||||||
if (Program.UserConfig.NotificationNonIntrusiveMode && IsCursorOverNotificationArea && NativeMethods.GetIdleSeconds() < NonIntrusiveIdleLimit){
|
if (Config.NotificationNonIntrusiveMode && IsCursorOverNotificationArea && NativeMethods.GetIdleSeconds() < NonIntrusiveIdleLimit){
|
||||||
if (!timerCursorCheck.Enabled){
|
if (!timerCursorCheck.Enabled){
|
||||||
PauseNotification();
|
PauseNotification();
|
||||||
timerCursorCheck.Start();
|
timerCursorCheck.Start();
|
||||||
@@ -136,7 +137,7 @@ namespace TweetDuck.Core.Notification{
|
|||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else if (Program.UserConfig.NotificationIdlePauseSeconds > 0 && NativeMethods.GetIdleSeconds() >= Program.UserConfig.NotificationIdlePauseSeconds){
|
else if (Config.NotificationIdlePauseSeconds > 0 && NativeMethods.GetIdleSeconds() >= Config.NotificationIdlePauseSeconds){
|
||||||
if (!timerIdlePauseCheck.Enabled){
|
if (!timerIdlePauseCheck.Enabled){
|
||||||
PauseNotification();
|
PauseNotification();
|
||||||
timerIdlePauseCheck.Start();
|
timerIdlePauseCheck.Start();
|
||||||
@@ -153,7 +154,7 @@ namespace TweetDuck.Core.Notification{
|
|||||||
base.UpdateTitle();
|
base.UpdateTitle();
|
||||||
|
|
||||||
if (tweetQueue.Count > 0){
|
if (tweetQueue.Count > 0){
|
||||||
Text = Text+" ("+tweetQueue.Count+" more left)";
|
Text = Text + " (" + tweetQueue.Count + " more left)";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -3,12 +3,13 @@ using System.Drawing;
|
|||||||
using System.Drawing.Imaging;
|
using System.Drawing.Imaging;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using CefSharp;
|
using CefSharp;
|
||||||
|
using TweetDuck.Core.Adapters;
|
||||||
using TweetDuck.Core.Controls;
|
using TweetDuck.Core.Controls;
|
||||||
using TweetDuck.Core.Other;
|
using TweetDuck.Core.Other;
|
||||||
using TweetDuck.Core.Utils;
|
using TweetDuck.Core.Utils;
|
||||||
using TweetDuck.Data;
|
using TweetLib.Core.Data;
|
||||||
using TweetDuck.Plugins;
|
using TweetLib.Core.Features.Notifications;
|
||||||
using TweetDuck.Resources;
|
using TweetLib.Core.Features.Plugins;
|
||||||
|
|
||||||
namespace TweetDuck.Core.Notification.Screenshot{
|
namespace TweetDuck.Core.Notification.Screenshot{
|
||||||
sealed class FormNotificationScreenshotable : FormNotificationBase{
|
sealed class FormNotificationScreenshotable : FormNotificationBase{
|
||||||
@@ -29,24 +30,23 @@ namespace TweetDuck.Core.Notification.Screenshot{
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
string script = ScriptLoader.LoadResourceSilent("screenshot.js");
|
string script = Program.Resources.LoadSilent("screenshot.js");
|
||||||
|
|
||||||
if (script == null){
|
if (script == null){
|
||||||
this.InvokeAsyncSafe(callback);
|
this.InvokeAsyncSafe(callback);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
using(IFrame frame = args.Browser.MainFrame){
|
using IFrame frame = args.Browser.MainFrame;
|
||||||
ScriptLoader.ExecuteScript(frame, script.Replace("{width}", realWidth.ToString()).Replace("{frames}", TweetScreenshotManager.WaitFrames.ToString()), "gen:screenshot");
|
CefScriptExecutor.RunScript(frame, script.Replace("{width}", realWidth.ToString()).Replace("{frames}", TweetScreenshotManager.WaitFrames.ToString()), "gen:screenshot");
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
SetNotificationSize(realWidth, 1024);
|
SetNotificationSize(realWidth, 1024);
|
||||||
LoadTweet(new TweetNotification(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(TweetNotification tweet){
|
protected override string GetTweetHTML(DesktopNotification tweet){
|
||||||
string html = tweet.GenerateHtml("td-screenshot", this);
|
string html = tweet.GenerateHtml("td-screenshot", HeadLayout, Config.CustomNotificationCSS);
|
||||||
|
|
||||||
foreach(InjectedHTML injection in plugins.NotificationInjections){
|
foreach(InjectedHTML injection in plugins.NotificationInjections){
|
||||||
html = injection.InjectInto(html);
|
html = injection.InjectInto(html);
|
||||||
|
@@ -1,8 +1,10 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using TweetDuck.Core.Controls;
|
using TweetDuck.Core.Controls;
|
||||||
|
|
||||||
namespace TweetDuck.Core.Notification.Screenshot{
|
namespace TweetDuck.Core.Notification.Screenshot{
|
||||||
|
[SuppressMessage("ReSharper", "UnusedMember.Global")]
|
||||||
sealed class ScreenshotBridge{
|
sealed class ScreenshotBridge{
|
||||||
private readonly Control owner;
|
private readonly Control owner;
|
||||||
|
|
||||||
|
@@ -9,7 +9,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using TweetDuck.Core.Controls;
|
using TweetDuck.Core.Controls;
|
||||||
using TweetDuck.Plugins;
|
using TweetLib.Core.Features.Plugins;
|
||||||
|
|
||||||
#if GEN_SCREENSHOT_FRAMES
|
#if GEN_SCREENSHOT_FRAMES
|
||||||
using System.Drawing.Imaging;
|
using System.Drawing.Imaging;
|
||||||
@@ -134,9 +134,9 @@ namespace TweetDuck.Core.Notification.Screenshot{
|
|||||||
private void debugger_Tick(object sender, EventArgs e){
|
private void debugger_Tick(object sender, EventArgs e){
|
||||||
if (frameCounter < 63 && screenshot.TakeScreenshot(true)){
|
if (frameCounter < 63 && screenshot.TakeScreenshot(true)){
|
||||||
try{
|
try{
|
||||||
Clipboard.GetImage()?.Save(Path.Combine(DebugScreenshotPath, "frame_"+(++frameCounter)+".png"), ImageFormat.Png);
|
Clipboard.GetImage()?.Save(Path.Combine(DebugScreenshotPath, "frame_" + (++frameCounter) + ".png"), ImageFormat.Png);
|
||||||
}catch{
|
}catch{
|
||||||
System.Diagnostics.Debug.WriteLine("Failed generating frame "+frameCounter);
|
System.Diagnostics.Debug.WriteLine("Failed generating frame " + frameCounter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
|
@@ -11,18 +11,16 @@ namespace TweetDuck.Core.Notification{
|
|||||||
public const string SupportedFormats = "*.wav;*.ogg;*.mp3;*.flac;*.opus;*.weba;*.webm";
|
public const string SupportedFormats = "*.wav;*.ogg;*.mp3;*.flac;*.opus;*.weba;*.webm";
|
||||||
|
|
||||||
public static IResourceHandler CreateFileHandler(string path){
|
public static IResourceHandler CreateFileHandler(string path){
|
||||||
string mimeType;
|
string mimeType = Path.GetExtension(path) switch{
|
||||||
|
".weba" => "audio/webm",
|
||||||
switch(Path.GetExtension(path)){
|
".webm" => "audio/webm",
|
||||||
case ".weba":
|
".wav" => "audio/wav",
|
||||||
case ".webm": mimeType = "audio/webm"; break;
|
".ogg" => "audio/ogg",
|
||||||
case ".wav": mimeType = "audio/wav"; break;
|
".mp3" => "audio/mp3",
|
||||||
case ".ogg": mimeType = "audio/ogg"; break;
|
".flac" => "audio/flac",
|
||||||
case ".mp3": mimeType = "audio/mp3"; break;
|
".opus" => "audio/ogg; codecs=opus",
|
||||||
case ".flac": mimeType = "audio/flac"; break;
|
_ => null
|
||||||
case ".opus": mimeType = "audio/ogg; codecs=opus"; break;
|
};
|
||||||
default: mimeType = null; break;
|
|
||||||
}
|
|
||||||
|
|
||||||
try{
|
try{
|
||||||
return ResourceHandler.FromFilePath(path, mimeType);
|
return ResourceHandler.FromFilePath(path, mimeType);
|
||||||
@@ -30,12 +28,12 @@ namespace TweetDuck.Core.Notification{
|
|||||||
FormBrowser browser = FormManager.TryFind<FormBrowser>();
|
FormBrowser browser = FormManager.TryFind<FormBrowser>();
|
||||||
|
|
||||||
browser?.InvokeAsyncSafe(() => {
|
browser?.InvokeAsyncSafe(() => {
|
||||||
using(FormMessage form = new FormMessage("Sound Notification Error", "Could not find custom notification sound file:\n"+path, MessageBoxIcon.Error)){
|
using(FormMessage form = new FormMessage("Sound Notification Error", "Could not find custom notification sound file:\n" + path, MessageBoxIcon.Error)){
|
||||||
form.AddButton(FormMessage.Ignore, ControlType.Cancel | ControlType.Focused);
|
form.AddButton(FormMessage.Ignore, ControlType.Cancel | ControlType.Focused);
|
||||||
|
|
||||||
Button btnViewOptions = form.AddButton("View Options");
|
Button btnViewOptions = form.AddButton("View Options");
|
||||||
btnViewOptions.Width += 16;
|
btnViewOptions.Width += 16;
|
||||||
btnViewOptions.Location = new Point(btnViewOptions.Location.X-16, btnViewOptions.Location.Y);
|
btnViewOptions.Location = new Point(btnViewOptions.Location.X - 16, btnViewOptions.Location.Y);
|
||||||
|
|
||||||
if (form.ShowDialog() == DialogResult.OK && form.ClickedButton == btnViewOptions){
|
if (form.ShowDialog() == DialogResult.OK && form.ClickedButton == btnViewOptions){
|
||||||
browser.OpenSettings(typeof(TabSettingsSounds));
|
browser.OpenSettings(typeof(TabSettingsSounds));
|
||||||
|
@@ -1,80 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Text;
|
|
||||||
using System.Windows.Forms;
|
|
||||||
using CefSharp;
|
|
||||||
using TweetDuck.Core.Bridge;
|
|
||||||
using TweetDuck.Data;
|
|
||||||
using TweetDuck.Resources;
|
|
||||||
|
|
||||||
namespace TweetDuck.Core.Notification{
|
|
||||||
sealed class TweetNotification{
|
|
||||||
private const string DefaultHeadLayout = @"<html class=""scroll-v os-windows dark txt-size--14"" lang=""en-US"" id=""tduck"" data-td-font=""medium"" data-td-theme=""dark""><head><meta charset=""utf-8""><link href=""https://ton.twimg.com/tweetdeck-web/web/dist/bundle.4b1f87e09d.css"" rel=""stylesheet""><style type='text/css'>body { background: rgb(34, 36, 38) !important }</style>";
|
|
||||||
public static readonly ResourceLink AppLogo = new ResourceLink("https://ton.twimg.com/tduck/avatar", ResourceHandler.FromByteArray(Properties.Resources.avatar, "image/png"));
|
|
||||||
|
|
||||||
public static TweetNotification Example(string html, int characters){
|
|
||||||
return new TweetNotification(string.Empty, string.Empty, "Home", html, characters, string.Empty, string.Empty, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum Position{
|
|
||||||
TopLeft, TopRight, BottomLeft, BottomRight, Custom
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum Size{
|
|
||||||
Auto, Custom
|
|
||||||
}
|
|
||||||
|
|
||||||
public string ColumnId { get; }
|
|
||||||
public string ChirpId { get; }
|
|
||||||
|
|
||||||
public string ColumnTitle { get; }
|
|
||||||
public string TweetUrl { get; }
|
|
||||||
public string QuoteUrl { get; }
|
|
||||||
|
|
||||||
private readonly string html;
|
|
||||||
private readonly int characters;
|
|
||||||
private readonly bool isExample;
|
|
||||||
|
|
||||||
public TweetNotification(string columnId, string chirpId, string title, string html, int characters, string tweetUrl, string quoteUrl) : this(columnId, chirpId, title, html, characters, tweetUrl, quoteUrl, false){}
|
|
||||||
|
|
||||||
private TweetNotification(string columnId, string chirpId, string title, string html, int characters, string tweetUrl, string quoteUrl, bool isExample){
|
|
||||||
this.ColumnId = columnId;
|
|
||||||
this.ChirpId = chirpId;
|
|
||||||
|
|
||||||
this.ColumnTitle = title;
|
|
||||||
this.TweetUrl = tweetUrl;
|
|
||||||
this.QuoteUrl = quoteUrl;
|
|
||||||
|
|
||||||
this.html = html;
|
|
||||||
this.characters = characters;
|
|
||||||
this.isExample = isExample;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int GetDisplayDuration(int value){
|
|
||||||
return 2000+Math.Max(1000, value*characters);
|
|
||||||
}
|
|
||||||
|
|
||||||
public string GenerateHtml(string bodyClasses, Control sync){
|
|
||||||
StringBuilder build = new StringBuilder();
|
|
||||||
build.Append("<!DOCTYPE html>");
|
|
||||||
build.Append(TweetDeckBridge.NotificationHeadLayout ?? DefaultHeadLayout);
|
|
||||||
build.Append("<style type='text/css'>").Append(ScriptLoader.LoadResource("styles/notification.css", sync) ?? string.Empty).Append("</style>");
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(Program.UserConfig.CustomNotificationCSS)){
|
|
||||||
build.Append("<style type='text/css'>").Append(Program.UserConfig.CustomNotificationCSS).Append("</style>");
|
|
||||||
}
|
|
||||||
|
|
||||||
build.Append("</head>");
|
|
||||||
build.Append("<body class='scroll-styled-v");
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(bodyClasses)){
|
|
||||||
build.Append(' ').Append(bodyClasses);
|
|
||||||
}
|
|
||||||
|
|
||||||
build.Append('\'').Append(isExample ? " td-example-notification" : "").Append("><div class='column' style='width:100%!important;min-height:100vh!important;height:auto!important;overflow:initial!important;'>");
|
|
||||||
build.Append(html);
|
|
||||||
build.Append("</div></body>");
|
|
||||||
build.Append("</html>");
|
|
||||||
return build.ToString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -2,7 +2,8 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using TweetDuck.Data.Serialization;
|
using TweetLib.Core.Serialization;
|
||||||
|
using TweetLib.Core.Serialization.Converters;
|
||||||
|
|
||||||
namespace TweetDuck.Core.Other.Analytics{
|
namespace TweetDuck.Core.Other.Analytics{
|
||||||
[SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Local")]
|
[SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Local")]
|
||||||
|
@@ -8,7 +8,9 @@ using System.Threading.Tasks;
|
|||||||
using System.Timers;
|
using System.Timers;
|
||||||
using TweetDuck.Core.Controls;
|
using TweetDuck.Core.Controls;
|
||||||
using TweetDuck.Core.Utils;
|
using TweetDuck.Core.Utils;
|
||||||
using TweetDuck.Plugins;
|
using TweetLib.Core;
|
||||||
|
using TweetLib.Core.Features.Plugins;
|
||||||
|
using TweetLib.Core.Utils;
|
||||||
|
|
||||||
namespace TweetDuck.Core.Other.Analytics{
|
namespace TweetDuck.Core.Other.Analytics{
|
||||||
sealed class AnalyticsManager : IDisposable{
|
sealed class AnalyticsManager : IDisposable{
|
||||||
@@ -20,7 +22,7 @@ namespace TweetDuck.Core.Other.Analytics{
|
|||||||
#else
|
#else
|
||||||
"https://tweetduck.chylex.com/breadcrumb/report"
|
"https://tweetduck.chylex.com/breadcrumb/report"
|
||||||
#endif
|
#endif
|
||||||
);
|
);
|
||||||
|
|
||||||
public AnalyticsFile File { get; }
|
public AnalyticsFile File { get; }
|
||||||
|
|
||||||
@@ -80,7 +82,7 @@ namespace TweetDuck.Core.Other.Analytics{
|
|||||||
private void SetLastDataCollectionTime(DateTime dt, string message = null){
|
private void SetLastDataCollectionTime(DateTime dt, string message = null){
|
||||||
File.LastDataCollection = new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, 0, dt.Kind);
|
File.LastDataCollection = new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, 0, dt.Kind);
|
||||||
File.LastCollectionVersion = Program.VersionTag;
|
File.LastCollectionVersion = Program.VersionTag;
|
||||||
File.LastCollectionMessage = message ?? dt.ToString("g", Program.Culture);
|
File.LastCollectionMessage = message ?? dt.ToString("g", Lib.Culture);
|
||||||
|
|
||||||
File.Save();
|
File.Save();
|
||||||
RestartTimer();
|
RestartTimer();
|
||||||
@@ -88,9 +90,9 @@ namespace TweetDuck.Core.Other.Analytics{
|
|||||||
|
|
||||||
private void RestartTimer(){
|
private void RestartTimer(){
|
||||||
TimeSpan diff = DateTime.Now.Subtract(File.LastDataCollection);
|
TimeSpan diff = DateTime.Now.Subtract(File.LastDataCollection);
|
||||||
int minutesTillNext = (int)(CollectionInterval.TotalMinutes-Math.Floor(diff.TotalMinutes));
|
int minutesTillNext = (int)(CollectionInterval.TotalMinutes - Math.Floor(diff.TotalMinutes));
|
||||||
|
|
||||||
currentTimer.Interval = Math.Max(minutesTillNext, 2)*60000;
|
currentTimer.Interval = Math.Max(minutesTillNext, 2) * 60000;
|
||||||
currentTimer.Start();
|
currentTimer.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,7 +119,7 @@ namespace TweetDuck.Core.Other.Analytics{
|
|||||||
System.Diagnostics.Debugger.Break();
|
System.Diagnostics.Debugger.Break();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
BrowserUtils.CreateWebClient().UploadValues(CollectionUrl, "POST", report.ToNameValueCollection());
|
WebUtils.NewClient(BrowserUtils.UserAgentVanilla).UploadValues(CollectionUrl, "POST", report.ToNameValueCollection());
|
||||||
}).ContinueWith(task => browser.InvokeAsyncSafe(() => {
|
}).ContinueWith(task => browser.InvokeAsyncSafe(() => {
|
||||||
if (task.Status == TaskStatus.RanToCompletion){
|
if (task.Status == TaskStatus.RanToCompletion){
|
||||||
SetLastDataCollectionTime(DateTime.Now);
|
SetLastDataCollectionTime(DateTime.Now);
|
||||||
@@ -137,7 +139,7 @@ namespace TweetDuck.Core.Other.Analytics{
|
|||||||
|
|
||||||
case WebExceptionStatus.ProtocolError:
|
case WebExceptionStatus.ProtocolError:
|
||||||
HttpWebResponse response = e.Response as HttpWebResponse;
|
HttpWebResponse response = e.Response as HttpWebResponse;
|
||||||
message = "HTTP Error "+(response != null ? $"{(int)response.StatusCode} ({response.StatusDescription})" : "(unknown code)");
|
message = "HTTP Error " + (response != null ? $"{(int)response.StatusCode} ({response.StatusDescription})" : "(unknown code)");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,7 +152,7 @@ namespace TweetDuck.Core.Other.Analytics{
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
ScheduleReportIn(TimeSpan.FromHours(4), message ?? "Error: "+(task.Exception.InnerException?.Message ?? task.Exception.Message));
|
ScheduleReportIn(TimeSpan.FromHours(4), message ?? "Error: " + (task.Exception.InnerException?.Message ?? task.Exception.Message));
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
@@ -40,14 +40,14 @@ namespace TweetDuck.Core.Other.Analytics{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString(){
|
public override string ToString(){
|
||||||
StringBuilder build = new StringBuilder();
|
StringBuilder build = new StringBuilder(625);
|
||||||
|
|
||||||
foreach(DictionaryEntry entry in data){
|
foreach(DictionaryEntry entry in data){
|
||||||
if (entry.Value == null){
|
if (entry.Value == null){
|
||||||
build.AppendLine();
|
build.AppendLine();
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
build.AppendLine(entry.Key+": "+entry.Value);
|
build.AppendLine(entry.Key + ": " + entry.Value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -8,10 +8,12 @@ using TweetDuck.Configuration;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Management;
|
using System.Management;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using TweetDuck.Core.Notification;
|
|
||||||
using TweetDuck.Core.Utils;
|
using TweetDuck.Core.Utils;
|
||||||
using TweetDuck.Plugins;
|
using TweetLib.Core;
|
||||||
using TweetDuck.Plugins.Enums;
|
using TweetLib.Core.Features.Notifications;
|
||||||
|
using TweetLib.Core.Features.Plugins;
|
||||||
|
using TweetLib.Core.Features.Plugins.Enums;
|
||||||
|
using TweetLib.Core.Utils;
|
||||||
|
|
||||||
namespace TweetDuck.Core.Other.Analytics{
|
namespace TweetDuck.Core.Other.Analytics{
|
||||||
static class AnalyticsReportGenerator{
|
static class AnalyticsReportGenerator{
|
||||||
@@ -27,7 +29,7 @@ namespace TweetDuck.Core.Other.Analytics{
|
|||||||
{ "System Edition" , SystemEdition },
|
{ "System Edition" , SystemEdition },
|
||||||
{ "System Environment" , Environment.Is64BitOperatingSystem ? "64-bit" : "32-bit" },
|
{ "System Environment" , Environment.Is64BitOperatingSystem ? "64-bit" : "32-bit" },
|
||||||
{ "System Build" , SystemBuild },
|
{ "System Build" , SystemBuild },
|
||||||
{ "System Locale" , Program.Culture.Name.ToLower() },
|
{ "System Locale" , Lib.Culture.Name.ToLower() },
|
||||||
0,
|
0,
|
||||||
{ "RAM" , Exact(RamSize) },
|
{ "RAM" , Exact(RamSize) },
|
||||||
{ "GPU" , GpuVendor },
|
{ "GPU" , GpuVendor },
|
||||||
@@ -79,7 +81,7 @@ namespace TweetDuck.Core.Other.Analytics{
|
|||||||
{ "Custom Notification CSS" , RoundUp((UserConfig.CustomNotificationCSS ?? string.Empty).Length, 50) },
|
{ "Custom Notification CSS" , RoundUp((UserConfig.CustomNotificationCSS ?? string.Empty).Length, 50) },
|
||||||
0,
|
0,
|
||||||
{ "Plugins All" , List(plugins.Plugins.Select(Plugin)) },
|
{ "Plugins All" , List(plugins.Plugins.Select(Plugin)) },
|
||||||
{ "Plugins Enabled" , List(plugins.Plugins.Where(plugin => plugins.Config.IsEnabled(plugin)).Select(Plugin)) },
|
{ "Plugins Enabled" , List(plugins.Plugins.Where(plugins.Config.IsEnabled).Select(Plugin)) },
|
||||||
0,
|
0,
|
||||||
{ "Theme" , Dict(editLayoutDesign, "_theme", "light/def") },
|
{ "Theme" , Dict(editLayoutDesign, "_theme", "light/def") },
|
||||||
{ "Column Width" , Dict(editLayoutDesign, "columnWidth", "310px/def") },
|
{ "Column Width" , Dict(editLayoutDesign, "columnWidth", "310px/def") },
|
||||||
@@ -119,14 +121,14 @@ namespace TweetDuck.Core.Other.Analytics{
|
|||||||
}.FinalizeReport();
|
}.FinalizeReport();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static UserConfig UserConfig => Program.UserConfig;
|
private static UserConfig UserConfig => Program.Config.User;
|
||||||
private static SystemConfig SysConfig => Program.SystemConfig;
|
private static SystemConfig SysConfig => Program.Config.System;
|
||||||
|
|
||||||
private static string Bool(bool value) => value ? "on" : "off";
|
private static string Bool(bool value) => value ? "on" : "off";
|
||||||
private static string Exact(int value) => value.ToString();
|
private static string Exact(int value) => value.ToString();
|
||||||
private static string RoundUp(int value, int multiple) => (multiple*(int)Math.Ceiling((double)value/multiple)).ToString();
|
private static string RoundUp(int value, int multiple) => (multiple * (int)Math.Ceiling((double)value / multiple)).ToString();
|
||||||
private static string LogRound(int value, int logBase) => (value <= 0 ? 0 : (int)Math.Pow(logBase, Math.Floor(Math.Log(value, logBase)))).ToString();
|
private static string LogRound(int value, int logBase) => (value <= 0 ? 0 : (int)Math.Pow(logBase, Math.Floor(Math.Log(value, logBase)))).ToString();
|
||||||
private static string Plugin(Plugin plugin) => plugin.Group.GetIdentifierPrefixShort()+plugin.Identifier.Substring(plugin.Group.GetIdentifierPrefix().Length);
|
private static string Plugin(Plugin plugin) => plugin.Group.GetIdentifierPrefixShort() + plugin.Identifier.Substring(plugin.Group.GetIdentifierPrefix().Length);
|
||||||
private static string Dict(Dictionary<string, string> dict, string key, string def = "(unknown)") => dict.TryGetValue(key, out string value) ? value : def;
|
private static string Dict(Dictionary<string, string> dict, string key, string def = "(unknown)") => dict.TryGetValue(key, out string value) ? value : def;
|
||||||
private static string List(IEnumerable<string> list) => string.Join("|", list.DefaultIfEmpty("(none)"));
|
private static string List(IEnumerable<string> list) => string.Join("|", list.DefaultIfEmpty("(none)"));
|
||||||
|
|
||||||
@@ -141,19 +143,19 @@ namespace TweetDuck.Core.Other.Analytics{
|
|||||||
string osName, osEdition, osBuild;
|
string osName, osEdition, osBuild;
|
||||||
|
|
||||||
try{
|
try{
|
||||||
using(RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion", false)){
|
using RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion", false);
|
||||||
// ReSharper disable once PossibleNullReferenceException
|
|
||||||
osName = key.GetValue("ProductName") as string;
|
|
||||||
osBuild = key.GetValue("CurrentBuild") as string;
|
|
||||||
osEdition = null;
|
|
||||||
|
|
||||||
if (osName != null){
|
|
||||||
Match match = Regex.Match(osName, @"^(.*?\d+(?:\.\d+)?) (.*)$");
|
|
||||||
|
|
||||||
if (match.Success){
|
// ReSharper disable once PossibleNullReferenceException
|
||||||
osName = match.Groups[1].Value;
|
osName = key.GetValue("ProductName") as string;
|
||||||
osEdition = match.Groups[2].Value;
|
osBuild = key.GetValue("CurrentBuild") as string;
|
||||||
}
|
osEdition = null;
|
||||||
|
|
||||||
|
if (osName != null){
|
||||||
|
Match match = Regex.Match(osName, @"^(.*?\d+(?:\.\d+)?) (.*)$");
|
||||||
|
|
||||||
|
if (match.Success){
|
||||||
|
osName = match.Groups[1].Value;
|
||||||
|
osEdition = match.Groups[2].Value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}catch{
|
}catch{
|
||||||
@@ -165,10 +167,10 @@ namespace TweetDuck.Core.Other.Analytics{
|
|||||||
SystemBuild = osBuild ?? "(unknown)";
|
SystemBuild = osBuild ?? "(unknown)";
|
||||||
|
|
||||||
try{
|
try{
|
||||||
using(ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT Capacity FROM Win32_PhysicalMemory")){
|
using ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT Capacity FROM Win32_PhysicalMemory");
|
||||||
foreach(ManagementBaseObject obj in searcher.Get()){
|
|
||||||
RamSize += (int)((ulong)obj["Capacity"]/(1024L*1024L));
|
foreach(ManagementBaseObject obj in searcher.Get()){
|
||||||
}
|
RamSize += (int)((ulong)obj["Capacity"] / (1024L * 1024L));
|
||||||
}
|
}
|
||||||
}catch{
|
}catch{
|
||||||
RamSize = 0;
|
RamSize = 0;
|
||||||
@@ -177,13 +179,13 @@ namespace TweetDuck.Core.Other.Analytics{
|
|||||||
string gpu = null;
|
string gpu = null;
|
||||||
|
|
||||||
try{
|
try{
|
||||||
using(ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT Caption FROM Win32_VideoController")){
|
using ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT Caption FROM Win32_VideoController");
|
||||||
foreach(ManagementBaseObject obj in searcher.Get()){
|
|
||||||
string vendor = obj["Caption"] as string;
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(vendor)){
|
foreach(ManagementBaseObject obj in searcher.Get()){
|
||||||
gpu = vendor;
|
string vendor = obj["Caption"] as string;
|
||||||
}
|
|
||||||
|
if (!string.IsNullOrEmpty(vendor)){
|
||||||
|
gpu = vendor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}catch{
|
}catch{
|
||||||
@@ -204,36 +206,30 @@ namespace TweetDuck.Core.Other.Analytics{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static string TrayMode{
|
private static string TrayMode{
|
||||||
get{
|
get => UserConfig.TrayBehavior switch{
|
||||||
switch(UserConfig.TrayBehavior){
|
TrayIcon.Behavior.DisplayOnly => "icon",
|
||||||
case TrayIcon.Behavior.DisplayOnly: return "icon";
|
TrayIcon.Behavior.MinimizeToTray => "minimize",
|
||||||
case TrayIcon.Behavior.MinimizeToTray: return "minimize";
|
TrayIcon.Behavior.CloseToTray => "close",
|
||||||
case TrayIcon.Behavior.CloseToTray: return "close";
|
TrayIcon.Behavior.Combined => "combined",
|
||||||
case TrayIcon.Behavior.Combined: return "combined";
|
_ => "off"
|
||||||
default: return "off";
|
};
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string NotificationPosition{
|
private static string NotificationPosition{
|
||||||
get{
|
get => UserConfig.NotificationPosition switch{
|
||||||
switch(UserConfig.NotificationPosition){
|
DesktopNotification.Position.TopLeft => "top left",
|
||||||
case TweetNotification.Position.TopLeft: return "top left";
|
DesktopNotification.Position.TopRight => "top right",
|
||||||
case TweetNotification.Position.TopRight: return "top right";
|
DesktopNotification.Position.BottomLeft => "bottom left",
|
||||||
case TweetNotification.Position.BottomLeft: return "bottom left";
|
DesktopNotification.Position.BottomRight => "bottom right",
|
||||||
case TweetNotification.Position.BottomRight: return "bottom right";
|
_ => "custom"
|
||||||
default: return "custom";
|
};
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string NotificationSize{
|
private static string NotificationSize{
|
||||||
get{
|
get => UserConfig.NotificationSize switch{
|
||||||
switch(UserConfig.NotificationSize){
|
DesktopNotification.Size.Auto => "auto",
|
||||||
case TweetNotification.Size.Auto: return "auto";
|
_ => RoundUp(UserConfig.CustomNotificationSize.Width, 20) + "x" + RoundUp(UserConfig.CustomNotificationSize.Height, 20)
|
||||||
default: return RoundUp(UserConfig.CustomNotificationSize.Width, 20)+"x"+RoundUp(UserConfig.CustomNotificationSize.Height, 20);
|
};
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string NotificationTimer{
|
private static string NotificationTimer{
|
||||||
@@ -289,7 +285,7 @@ namespace TweetDuck.Core.Other.Analytics{
|
|||||||
}
|
}
|
||||||
|
|
||||||
string accType = matchType.Groups[1].Value == "#" ? matchType.Groups[2].Value : "account";
|
string accType = matchType.Groups[1].Value == "#" ? matchType.Groups[2].Value : "account";
|
||||||
return matchAdvanced.Success && !matchAdvanced.Value.Contains("false") ? "advanced/"+accType : accType;
|
return matchAdvanced.Success && !matchAdvanced.Value.Contains("false") ? "advanced/" + accType : accType;
|
||||||
}catch{
|
}catch{
|
||||||
return "(unknown)";
|
return "(unknown)";
|
||||||
}
|
}
|
||||||
@@ -310,7 +306,7 @@ namespace TweetDuck.Core.Other.Analytics{
|
|||||||
}
|
}
|
||||||
|
|
||||||
return new ExternalInfo{
|
return new ExternalInfo{
|
||||||
Resolution = screen.Bounds.Width+"x"+screen.Bounds.Height,
|
Resolution = screen.Bounds.Width + "x" + screen.Bounds.Height,
|
||||||
DPI = dpi
|
DPI = dpi
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@@ -12,7 +12,7 @@ namespace TweetDuck.Core.Other{
|
|||||||
public FormAbout(){
|
public FormAbout(){
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
Text = "About "+Program.BrandName+" "+Program.VersionTag;
|
Text = "About " + Program.BrandName + " " + Program.VersionTag;
|
||||||
|
|
||||||
labelDescription.Text = "TweetDuck was created by chylex as a replacement to the discontinued official TweetDeck client for Windows.\n\nThe program is available for free under the open source MIT license.";
|
labelDescription.Text = "TweetDuck was created by chylex as a replacement to the discontinued official TweetDeck client for Windows.\n\nThe program is available for free under the open source MIT license.";
|
||||||
|
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System.Drawing;
|
||||||
using System.Drawing;
|
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using CefSharp;
|
using CefSharp;
|
||||||
using CefSharp.WinForms;
|
using CefSharp.WinForms;
|
||||||
@@ -8,14 +7,14 @@ using TweetDuck.Core.Handling;
|
|||||||
using TweetDuck.Core.Handling.General;
|
using TweetDuck.Core.Handling.General;
|
||||||
using TweetDuck.Core.Utils;
|
using TweetDuck.Core.Utils;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
using TweetDuck.Core.Adapters;
|
||||||
using TweetDuck.Data;
|
using TweetDuck.Data;
|
||||||
using TweetDuck.Resources;
|
|
||||||
|
|
||||||
namespace TweetDuck.Core.Other{
|
namespace TweetDuck.Core.Other{
|
||||||
sealed partial class FormGuide : Form, FormManager.IAppDialog{
|
sealed partial class FormGuide : Form, FormManager.IAppDialog{
|
||||||
private const string GuideUrl = "https://tweetduck.chylex.com/guide/v2/";
|
private const string GuideUrl = "https://tweetduck.chylex.com/guide/v2/";
|
||||||
private const string GuidePathRegex = @"^guide(?:/v\d+)?(?:/(#.*))?";
|
private const string GuidePathRegex = @"^guide(?:/v\d+)?(?:/(#.*))?";
|
||||||
|
|
||||||
private static readonly ResourceLink DummyPage = new ResourceLink("http://td/dummy", ResourceHandler.FromString(""));
|
private static readonly ResourceLink DummyPage = new ResourceLink("http://td/dummy", ResourceHandler.FromString(""));
|
||||||
|
|
||||||
public static bool CheckGuideUrl(string url, out string hash){
|
public static bool CheckGuideUrl(string url, out string hash){
|
||||||
@@ -38,7 +37,7 @@ namespace TweetDuck.Core.Other{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void Show(string hash = null){
|
public static void Show(string hash = null){
|
||||||
string url = GuideUrl+(hash ?? string.Empty);
|
string url = GuideUrl + (hash ?? string.Empty);
|
||||||
FormGuide guide = FormManager.TryFind<FormGuide>();
|
FormGuide guide = FormManager.TryFind<FormGuide>();
|
||||||
|
|
||||||
if (guide == null){
|
if (guide == null){
|
||||||
@@ -61,35 +60,35 @@ namespace TweetDuck.Core.Other{
|
|||||||
private FormGuide(string url, FormBrowser 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);
|
||||||
|
|
||||||
|
ResourceHandlerFactory resourceHandlerFactory = new ResourceHandlerFactory();
|
||||||
|
resourceHandlerFactory.RegisterHandler(DummyPage);
|
||||||
|
|
||||||
this.browser = new ChromiumWebBrowser(url){
|
this.browser = new ChromiumWebBrowser(url){
|
||||||
MenuHandler = new ContextMenuGuide(owner),
|
MenuHandler = new ContextMenuGuide(owner),
|
||||||
JsDialogHandler = new JavaScriptDialogHandler(),
|
JsDialogHandler = new JavaScriptDialogHandler(),
|
||||||
|
KeyboardHandler = new KeyboardHandlerBase(),
|
||||||
LifeSpanHandler = new LifeSpanHandler(),
|
LifeSpanHandler = new LifeSpanHandler(),
|
||||||
RequestHandler = new RequestHandlerBase(true)
|
RequestHandler = new RequestHandlerBase(true),
|
||||||
|
ResourceHandlerFactory = resourceHandlerFactory
|
||||||
};
|
};
|
||||||
|
|
||||||
browser.LoadingStateChanged += browser_LoadingStateChanged;
|
browser.LoadingStateChanged += browser_LoadingStateChanged;
|
||||||
browser.FrameLoadStart += browser_FrameLoadStart;
|
|
||||||
browser.FrameLoadEnd += browser_FrameLoadEnd;
|
browser.FrameLoadEnd += browser_FrameLoadEnd;
|
||||||
|
|
||||||
browser.BrowserSettings.BackgroundColor = (uint)BackColor.ToArgb();
|
browser.BrowserSettings.BackgroundColor = (uint)BackColor.ToArgb();
|
||||||
browser.Dock = DockStyle.None;
|
browser.Dock = DockStyle.None;
|
||||||
browser.Location = ControlExtensions.InvisibleLocation;
|
browser.Location = ControlExtensions.InvisibleLocation;
|
||||||
|
browser.SetupZoomEvents();
|
||||||
browser.SetupResourceHandler(DummyPage);
|
|
||||||
|
|
||||||
Controls.Add(browser);
|
Controls.Add(browser);
|
||||||
|
|
||||||
Disposed += (sender, args) => {
|
Disposed += (sender, args) => {
|
||||||
Program.UserConfig.ZoomLevelChanged -= Config_ZoomLevelChanged;
|
|
||||||
browser.Dispose();
|
browser.Dispose();
|
||||||
};
|
};
|
||||||
|
|
||||||
Program.UserConfig.ZoomLevelChanged += Config_ZoomLevelChanged;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Reload(string url){
|
private void Reload(string url){
|
||||||
@@ -116,16 +115,8 @@ namespace TweetDuck.Core.Other{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void browser_FrameLoadStart(object sender, FrameLoadStartEventArgs e){
|
|
||||||
BrowserUtils.SetZoomLevel(browser.GetBrowser(), Program.UserConfig.ZoomLevel);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
|
private void browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
|
||||||
ScriptLoader.ExecuteScript(e.Frame, "Array.prototype.forEach.call(document.getElementsByTagName('A'), ele => ele.addEventListener('click', e => { e.preventDefault(); window.open(ele.getAttribute('href')); }))", "gen:links");
|
CefScriptExecutor.RunScript(e.Frame, "Array.prototype.forEach.call(document.getElementsByTagName('A'), ele => ele.addEventListener('click', e => { e.preventDefault(); window.open(ele.getAttribute('href')); }))", "gen:links");
|
||||||
}
|
|
||||||
|
|
||||||
private void Config_ZoomLevelChanged(object sender, EventArgs e){
|
|
||||||
BrowserUtils.SetZoomLevel(browser.GetBrowser(), Program.UserConfig.ZoomLevel);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -133,7 +133,7 @@ namespace TweetDuck.Core.Other{
|
|||||||
Font = SystemFonts.MessageBoxFont,
|
Font = SystemFonts.MessageBoxFont,
|
||||||
Location = new Point(0, 12),
|
Location = new Point(0, 12),
|
||||||
Size = new Size(BrowserUtils.Scale(88, dpiScale), BrowserUtils.Scale(26, dpiScale)),
|
Size = new Size(BrowserUtils.Scale(88, dpiScale), BrowserUtils.Scale(26, dpiScale)),
|
||||||
TabIndex = 256-buttonCount,
|
TabIndex = 256 - buttonCount,
|
||||||
Text = title,
|
Text = title,
|
||||||
UseVisualStyleBackColor = true
|
UseVisualStyleBackColor = true
|
||||||
};
|
};
|
||||||
@@ -171,17 +171,17 @@ namespace TweetDuck.Core.Other{
|
|||||||
|
|
||||||
control.Size = new Size(BrowserUtils.Scale(control.Width, dpiScale), BrowserUtils.Scale(control.Height, dpiScale));
|
control.Size = new Size(BrowserUtils.Scale(control.Width, dpiScale), BrowserUtils.Scale(control.Height, dpiScale));
|
||||||
|
|
||||||
minFormWidth += control.Width+control.Margin.Horizontal;
|
minFormWidth += control.Width + control.Margin.Horizontal;
|
||||||
ClientWidth = Math.Max(realFormWidth, minFormWidth);
|
ClientWidth = Math.Max(realFormWidth, minFormWidth);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RecalculateButtonLocation(){
|
private void RecalculateButtonLocation(){
|
||||||
int dist = ButtonDistance;
|
int dist = ButtonDistance;
|
||||||
int start = ClientWidth-dist;
|
int start = ClientWidth - dist;
|
||||||
|
|
||||||
for(int index = 0; index < buttonCount; index++){
|
for(int index = 0; index < buttonCount; index++){
|
||||||
Control control = panelActions.Controls[index];
|
Control control = panelActions.Controls[index];
|
||||||
control.Location = new Point(start-index*dist, control.Location.Y);
|
control.Location = new Point(start - index * dist, control.Location.Y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,17 +194,17 @@ namespace TweetDuck.Core.Other{
|
|||||||
int labelOffset = BrowserUtils.Scale(8, dpiScale);
|
int labelOffset = BrowserUtils.Scale(8, dpiScale);
|
||||||
|
|
||||||
if (isMultiline && !wasLabelMultiline){
|
if (isMultiline && !wasLabelMultiline){
|
||||||
labelMessage.Location = new Point(labelMessage.Location.X, labelMessage.Location.Y-labelOffset);
|
labelMessage.Location = new Point(labelMessage.Location.X, labelMessage.Location.Y - labelOffset);
|
||||||
prevLabelHeight += labelOffset;
|
prevLabelHeight += labelOffset;
|
||||||
}
|
}
|
||||||
else if (!isMultiline && wasLabelMultiline){
|
else if (!isMultiline && wasLabelMultiline){
|
||||||
labelMessage.Location = new Point(labelMessage.Location.X, labelMessage.Location.Y+labelOffset);
|
labelMessage.Location = new Point(labelMessage.Location.X, labelMessage.Location.Y + labelOffset);
|
||||||
prevLabelHeight -= labelOffset;
|
prevLabelHeight -= labelOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
realFormWidth = ClientWidth-(icon == null ? BrowserUtils.Scale(50, dpiScale) : 0)+labelMessage.Width-prevLabelWidth;
|
realFormWidth = ClientWidth - (icon == null ? BrowserUtils.Scale(50, dpiScale) : 0) + labelMessage.Width - prevLabelWidth;
|
||||||
ClientWidth = Math.Max(realFormWidth, minFormWidth);
|
ClientWidth = Math.Max(realFormWidth, minFormWidth);
|
||||||
Height += labelMessage.Height-prevLabelHeight;
|
Height += labelMessage.Height - prevLabelHeight;
|
||||||
|
|
||||||
prevLabelWidth = labelMessage.Width;
|
prevLabelWidth = labelMessage.Width;
|
||||||
prevLabelHeight = labelMessage.Height;
|
prevLabelHeight = labelMessage.Height;
|
||||||
@@ -213,7 +213,7 @@ namespace TweetDuck.Core.Other{
|
|||||||
|
|
||||||
protected override void OnPaint(PaintEventArgs e){
|
protected override void OnPaint(PaintEventArgs e){
|
||||||
if (icon != null){
|
if (icon != null){
|
||||||
e.Graphics.DrawIcon(icon, BrowserUtils.Scale(25, dpiScale), 1+BrowserUtils.Scale(25, dpiScale));
|
e.Graphics.DrawIcon(icon, BrowserUtils.Scale(25, dpiScale), 1 + BrowserUtils.Scale(25, dpiScale));
|
||||||
}
|
}
|
||||||
|
|
||||||
base.OnPaint(e);
|
base.OnPaint(e);
|
||||||
|
8
Core/Other/FormPlugins.Designer.cs
generated
8
Core/Other/FormPlugins.Designer.cs
generated
@@ -1,4 +1,6 @@
|
|||||||
namespace TweetDuck.Core.Other {
|
using TweetDuck.Core.Controls;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.Other {
|
||||||
partial class FormPlugins {
|
partial class FormPlugins {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Required designer variable.
|
/// Required designer variable.
|
||||||
@@ -27,7 +29,7 @@
|
|||||||
this.btnClose = new System.Windows.Forms.Button();
|
this.btnClose = new System.Windows.Forms.Button();
|
||||||
this.btnReload = new System.Windows.Forms.Button();
|
this.btnReload = new System.Windows.Forms.Button();
|
||||||
this.btnOpenFolder = new System.Windows.Forms.Button();
|
this.btnOpenFolder = new System.Windows.Forms.Button();
|
||||||
this.flowLayoutPlugins = new TweetDuck.Plugins.Controls.PluginListFlowLayout();
|
this.flowLayoutPlugins = new FlowLayoutPanelNoHScroll();
|
||||||
this.timerLayout = new System.Windows.Forms.Timer(this.components);
|
this.timerLayout = new System.Windows.Forms.Timer(this.components);
|
||||||
this.SuspendLayout();
|
this.SuspendLayout();
|
||||||
//
|
//
|
||||||
@@ -117,7 +119,7 @@
|
|||||||
private System.Windows.Forms.Button btnClose;
|
private System.Windows.Forms.Button btnClose;
|
||||||
private System.Windows.Forms.Button btnReload;
|
private System.Windows.Forms.Button btnReload;
|
||||||
private System.Windows.Forms.Button btnOpenFolder;
|
private System.Windows.Forms.Button btnOpenFolder;
|
||||||
private Plugins.Controls.PluginListFlowLayout flowLayoutPlugins;
|
private FlowLayoutPanelNoHScroll flowLayoutPlugins;
|
||||||
private System.Windows.Forms.Timer timerLayout;
|
private System.Windows.Forms.Timer timerLayout;
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,26 +1,29 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
|
using TweetDuck.Configuration;
|
||||||
using TweetDuck.Plugins;
|
using TweetDuck.Plugins;
|
||||||
using TweetDuck.Plugins.Controls;
|
using TweetLib.Core;
|
||||||
|
using TweetLib.Core.Features.Plugins;
|
||||||
|
|
||||||
namespace TweetDuck.Core.Other{
|
namespace TweetDuck.Core.Other{
|
||||||
sealed partial class FormPlugins : Form, FormManager.IAppDialog{
|
sealed partial class FormPlugins : Form, FormManager.IAppDialog{
|
||||||
|
private static UserConfig Config => Program.Config.User;
|
||||||
|
|
||||||
private readonly PluginManager pluginManager;
|
private readonly PluginManager pluginManager;
|
||||||
|
|
||||||
public FormPlugins(){
|
public FormPlugins(){
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
Text = Program.BrandName+" Plugins";
|
Text = Program.BrandName + " Plugins";
|
||||||
}
|
}
|
||||||
|
|
||||||
public FormPlugins(PluginManager pluginManager) : this(){
|
public FormPlugins(PluginManager pluginManager) : this(){
|
||||||
this.pluginManager = pluginManager;
|
this.pluginManager = pluginManager;
|
||||||
|
|
||||||
if (!Program.UserConfig.PluginsWindowSize.IsEmpty){
|
if (!Config.PluginsWindowSize.IsEmpty){
|
||||||
Size targetSize = Program.UserConfig.PluginsWindowSize;
|
Size targetSize = Config.PluginsWindowSize;
|
||||||
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));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,8 +32,8 @@ namespace TweetDuck.Core.Other{
|
|||||||
};
|
};
|
||||||
|
|
||||||
FormClosed += (sender, args) => {
|
FormClosed += (sender, args) => {
|
||||||
Program.UserConfig.PluginsWindowSize = Size;
|
Config.PluginsWindowSize = Size;
|
||||||
Program.UserConfig.Save();
|
Config.Save();
|
||||||
};
|
};
|
||||||
|
|
||||||
ResizeEnd += (sender, args) => {
|
ResizeEnd += (sender, args) => {
|
||||||
@@ -66,8 +69,8 @@ namespace TweetDuck.Core.Other{
|
|||||||
timerLayout.Stop();
|
timerLayout.Stop();
|
||||||
|
|
||||||
// stupid WinForms scrollbars and panels
|
// stupid WinForms scrollbars and panels
|
||||||
Padding = new Padding(Padding.Left, Padding.Top, Padding.Right+1, Padding.Bottom+1);
|
Padding = new Padding(Padding.Left, Padding.Top, Padding.Right + 1, Padding.Bottom + 1);
|
||||||
Padding = new Padding(Padding.Left, Padding.Top, Padding.Right-1, Padding.Bottom-1);
|
Padding = new Padding(Padding.Left, Padding.Top, Padding.Right - 1, Padding.Bottom - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void flowLayoutPlugins_Resize(object sender, EventArgs e){
|
public void flowLayoutPlugins_Resize(object sender, EventArgs e){
|
||||||
@@ -77,22 +80,22 @@ namespace TweetDuck.Core.Other{
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool showScrollBar = lastPlugin.Location.Y+lastPlugin.Height+1 >= flowLayoutPlugins.Height;
|
bool showScrollBar = lastPlugin.Location.Y + lastPlugin.Height + 1 >= flowLayoutPlugins.Height;
|
||||||
int horizontalOffset = showScrollBar ? SystemInformation.VerticalScrollBarWidth : 0;
|
int horizontalOffset = showScrollBar ? SystemInformation.VerticalScrollBarWidth : 0;
|
||||||
|
|
||||||
flowLayoutPlugins.AutoScroll = showScrollBar;
|
flowLayoutPlugins.AutoScroll = showScrollBar;
|
||||||
flowLayoutPlugins.VerticalScroll.Visible = showScrollBar;
|
flowLayoutPlugins.VerticalScroll.Visible = showScrollBar;
|
||||||
|
|
||||||
foreach(Control control in flowLayoutPlugins.Controls){
|
foreach(Control control in flowLayoutPlugins.Controls){
|
||||||
control.Width = flowLayoutPlugins.Width-control.Margin.Horizontal-horizontalOffset;
|
control.Width = flowLayoutPlugins.Width - control.Margin.Horizontal - horizontalOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
flowLayoutPlugins.Controls[flowLayoutPlugins.Controls.Count-1].Visible = !showScrollBar;
|
flowLayoutPlugins.Controls[flowLayoutPlugins.Controls.Count - 1].Visible = !showScrollBar;
|
||||||
flowLayoutPlugins.Focus();
|
flowLayoutPlugins.Focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void btnOpenFolder_Click(object sender, EventArgs e){
|
private void btnOpenFolder_Click(object sender, EventArgs e){
|
||||||
using(Process.Start("explorer.exe", '"'+pluginManager.PathCustomPlugins+'"')){}
|
App.SystemHandler.OpenFileExplorer(pluginManager.PathCustomPlugins);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void btnReload_Click(object sender, EventArgs e){
|
private void btnReload_Click(object sender, EventArgs e){
|
||||||
|
@@ -9,8 +9,8 @@ using TweetDuck.Core.Other.Analytics;
|
|||||||
using TweetDuck.Core.Other.Settings;
|
using TweetDuck.Core.Other.Settings;
|
||||||
using TweetDuck.Core.Other.Settings.Dialogs;
|
using TweetDuck.Core.Other.Settings.Dialogs;
|
||||||
using TweetDuck.Core.Utils;
|
using TweetDuck.Core.Utils;
|
||||||
using TweetDuck.Plugins;
|
using TweetLib.Core.Features.Plugins;
|
||||||
using TweetDuck.Updates;
|
using TweetLib.Core.Features.Updates;
|
||||||
|
|
||||||
namespace TweetDuck.Core.Other{
|
namespace TweetDuck.Core.Other{
|
||||||
sealed partial class FormSettings : Form, FormManager.IAppDialog{
|
sealed partial class FormSettings : Form, FormManager.IAppDialog{
|
||||||
@@ -27,7 +27,7 @@ namespace TweetDuck.Core.Other{
|
|||||||
public FormSettings(FormBrowser browser, PluginManager plugins, UpdateHandler updates, AnalyticsManager analytics, Type startTab){
|
public FormSettings(FormBrowser browser, PluginManager plugins, UpdateHandler updates, AnalyticsManager analytics, Type startTab){
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
Text = Program.BrandName+" Options";
|
Text = Program.BrandName + " Options";
|
||||||
|
|
||||||
this.browser = browser;
|
this.browser = browser;
|
||||||
this.browser.PauseNotification();
|
this.browser.PauseNotification();
|
||||||
@@ -110,7 +110,7 @@ namespace TweetDuck.Core.Other{
|
|||||||
BackColor = SystemColors.Control,
|
BackColor = SystemColors.Control,
|
||||||
FlatStyle = FlatStyle.Flat,
|
FlatStyle = FlatStyle.Flat,
|
||||||
Font = SystemFonts.MessageBoxFont,
|
Font = SystemFonts.MessageBoxFont,
|
||||||
Location = new Point(0, (buttonHeight+1)*(panelButtons.Controls.Count/2)),
|
Location = new Point(0, (buttonHeight + 1) * (panelButtons.Controls.Count / 2)),
|
||||||
Margin = new Padding(0),
|
Margin = new Padding(0),
|
||||||
Size = new Size(panelButtons.Width, buttonHeight),
|
Size = new Size(panelButtons.Width, buttonHeight),
|
||||||
Text = title,
|
Text = title,
|
||||||
@@ -125,7 +125,7 @@ namespace TweetDuck.Core.Other{
|
|||||||
|
|
||||||
panelButtons.Controls.Add(new Panel{
|
panelButtons.Controls.Add(new Panel{
|
||||||
BackColor = Color.DimGray,
|
BackColor = Color.DimGray,
|
||||||
Location = new Point(0, panelButtons.Controls[panelButtons.Controls.Count-1].Location.Y+buttonHeight),
|
Location = new Point(0, panelButtons.Controls[panelButtons.Controls.Count - 1].Location.Y + buttonHeight),
|
||||||
Margin = new Padding(0),
|
Margin = new Padding(0),
|
||||||
Size = new Size(panelButtons.Width, 1)
|
Size = new Size(panelButtons.Width, 1)
|
||||||
});
|
});
|
||||||
@@ -157,8 +157,8 @@ namespace TweetDuck.Core.Other{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tab.Control.Height < panelContents.Height-2){
|
if (tab.Control.Height < panelContents.Height - 2){
|
||||||
tab.Control.Height = panelContents.Height-2; // fixes off-by-pixel error on high DPI
|
tab.Control.Height = panelContents.Height - 2; // fixes off-by-pixel error on high DPI
|
||||||
}
|
}
|
||||||
|
|
||||||
tab.Control.OnReady();
|
tab.Control.OnReady();
|
||||||
@@ -195,7 +195,7 @@ namespace TweetDuck.Core.Other{
|
|||||||
private sealed class SettingsTab{
|
private sealed class SettingsTab{
|
||||||
public Button Button { get; }
|
public Button Button { get; }
|
||||||
|
|
||||||
public BaseTabSettings Control => control ?? (control = constructor());
|
public BaseTabSettings Control => control ??= constructor();
|
||||||
public bool IsInitialized => control != null;
|
public bool IsInitialized => control != null;
|
||||||
|
|
||||||
private readonly Func<BaseTabSettings> constructor;
|
private readonly Func<BaseTabSettings> constructor;
|
||||||
|
@@ -1,12 +0,0 @@
|
|||||||
using System;
|
|
||||||
using CefSharp;
|
|
||||||
|
|
||||||
namespace TweetDuck.Core.Other.Interfaces{
|
|
||||||
interface ITweetDeckBrowser{
|
|
||||||
bool IsTweetDeckWebsite { get; }
|
|
||||||
|
|
||||||
void RegisterBridge(string name, object obj);
|
|
||||||
void OnFrameLoaded(Action<IFrame> callback);
|
|
||||||
void ExecuteFunction(string name, params object[] args);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -4,12 +4,12 @@ using TweetDuck.Configuration;
|
|||||||
|
|
||||||
namespace TweetDuck.Core.Other.Settings{
|
namespace TweetDuck.Core.Other.Settings{
|
||||||
class BaseTabSettings : UserControl{
|
class BaseTabSettings : UserControl{
|
||||||
protected static UserConfig Config => Program.UserConfig;
|
protected static UserConfig Config => Program.Config.User;
|
||||||
protected static SystemConfig SysConfig => Program.SystemConfig;
|
protected static SystemConfig SysConfig => Program.Config.System;
|
||||||
|
|
||||||
public IEnumerable<Control> InteractiveControls{
|
public IEnumerable<Control> InteractiveControls{
|
||||||
get{
|
get{
|
||||||
IEnumerable<Control> FindInteractiveControls(Control parent){
|
static IEnumerable<Control> FindInteractiveControls(Control parent){
|
||||||
foreach(Control control in parent.Controls){
|
foreach(Control control in parent.Controls){
|
||||||
if (control is Panel subPanel){
|
if (control is Panel subPanel){
|
||||||
foreach(Control subControl in FindInteractiveControls(subPanel)){
|
foreach(Control subControl in FindInteractiveControls(subPanel)){
|
||||||
|
@@ -5,12 +5,10 @@ using TweetDuck.Core.Other.Analytics;
|
|||||||
|
|
||||||
namespace TweetDuck.Core.Other.Settings.Dialogs{
|
namespace TweetDuck.Core.Other.Settings.Dialogs{
|
||||||
sealed partial class DialogSettingsAnalytics : Form{
|
sealed partial class DialogSettingsAnalytics : Form{
|
||||||
public string CefArgs => textBoxReport.Text;
|
|
||||||
|
|
||||||
public DialogSettingsAnalytics(AnalyticsReport report){
|
public DialogSettingsAnalytics(AnalyticsReport report){
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
Text = Program.BrandName+" Options - Analytics Report";
|
Text = Program.BrandName + " Options - Analytics Report";
|
||||||
|
|
||||||
textBoxReport.EnableMultilineShortcuts();
|
textBoxReport.EnableMultilineShortcuts();
|
||||||
textBoxReport.Text = report.ToString().TrimEnd();
|
textBoxReport.Text = report.ToString().TrimEnd();
|
||||||
|
@@ -13,19 +13,19 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
|
|||||||
private readonly Action<string> reinjectBrowserCSS;
|
private readonly Action<string> reinjectBrowserCSS;
|
||||||
private readonly Action openDevTools;
|
private readonly Action openDevTools;
|
||||||
|
|
||||||
public DialogSettingsCSS(Action<string> reinjectBrowserCSS, Action openDevTools){
|
public DialogSettingsCSS(string browserCSS, string notificationCSS, Action<string> reinjectBrowserCSS, Action openDevTools){
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
Text = Program.BrandName+" Options - CSS";
|
Text = Program.BrandName + " Options - CSS";
|
||||||
|
|
||||||
this.reinjectBrowserCSS = reinjectBrowserCSS;
|
this.reinjectBrowserCSS = reinjectBrowserCSS;
|
||||||
this.openDevTools = openDevTools;
|
this.openDevTools = openDevTools;
|
||||||
|
|
||||||
textBoxBrowserCSS.EnableMultilineShortcuts();
|
textBoxBrowserCSS.EnableMultilineShortcuts();
|
||||||
textBoxBrowserCSS.Text = Program.UserConfig.CustomBrowserCSS ?? "";
|
textBoxBrowserCSS.Text = browserCSS ?? "";
|
||||||
|
|
||||||
textBoxNotificationCSS.EnableMultilineShortcuts();
|
textBoxNotificationCSS.EnableMultilineShortcuts();
|
||||||
textBoxNotificationCSS.Text = Program.UserConfig.CustomNotificationCSS ?? "";
|
textBoxNotificationCSS.Text = notificationCSS ?? "";
|
||||||
|
|
||||||
if (!BrowserUtils.HasDevTools){
|
if (!BrowserUtils.HasDevTools){
|
||||||
btnOpenDevTools.Enabled = false;
|
btnOpenDevTools.Enabled = false;
|
||||||
@@ -64,21 +64,21 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(deleteTo < text.Length-1 && text[deleteTo] == '\r' && text[deleteTo+1] == '\n')){
|
if (!(deleteTo < text.Length - 1 && text[deleteTo] == '\r' && text[deleteTo + 1] == '\n')){
|
||||||
++deleteTo;
|
++deleteTo;
|
||||||
}
|
}
|
||||||
|
|
||||||
tb.Select(deleteTo, tb.SelectionLength+tb.SelectionStart-deleteTo);
|
tb.Select(deleteTo, tb.SelectionLength + tb.SelectionStart - deleteTo);
|
||||||
tb.SelectedText = string.Empty;
|
tb.SelectedText = string.Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (e.KeyCode == Keys.Back && e.Modifiers == Keys.None){
|
else if (e.KeyCode == Keys.Back && e.Modifiers == Keys.None){
|
||||||
int deleteTo = tb.SelectionStart;
|
int deleteTo = tb.SelectionStart;
|
||||||
|
|
||||||
if (deleteTo > 1 && text[deleteTo-1] == ' ' && text[deleteTo-2] == ' '){
|
if (deleteTo > 1 && text[deleteTo - 1] == ' ' && text[deleteTo - 2] == ' '){
|
||||||
e.SuppressKeyPress = true;
|
e.SuppressKeyPress = true;
|
||||||
|
|
||||||
tb.Select(deleteTo-2, 2);
|
tb.Select(deleteTo - 2, 2);
|
||||||
tb.SelectedText = string.Empty;
|
tb.SelectedText = string.Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -89,28 +89,28 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
|
|||||||
if (insertAt == 0){
|
if (insertAt == 0){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else if (text[insertAt-1] == '{'){
|
else if (text[insertAt - 1] == '{'){
|
||||||
insertText = Environment.NewLine+" ";
|
insertText = Environment.NewLine + " ";
|
||||||
|
|
||||||
int nextBracket = insertAt < text.Length ? text.IndexOfAny(new char[]{ '{', '}' }, insertAt+1) : -1;
|
int nextBracket = insertAt < text.Length ? text.IndexOfAny(new char[]{ '{', '}' }, insertAt + 1) : -1;
|
||||||
|
|
||||||
if (nextBracket == -1 || text[nextBracket] == '{'){
|
if (nextBracket == -1 || text[nextBracket] == '{'){
|
||||||
string insertExtra = Environment.NewLine+"}";
|
string insertExtra = Environment.NewLine + "}";
|
||||||
insertText += insertExtra;
|
insertText += insertExtra;
|
||||||
cursorOffset -= insertExtra.Length;
|
cursorOffset -= insertExtra.Length;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
int lineStart = text.LastIndexOf('\n', tb.SelectionStart-1);
|
int lineStart = text.LastIndexOf('\n', tb.SelectionStart - 1);
|
||||||
|
|
||||||
Match match = Regex.Match(text.Substring(lineStart == -1 ? 0 : lineStart+1), "^([ \t]+)");
|
Match match = Regex.Match(text.Substring(lineStart == -1 ? 0 : lineStart + 1), "^([ \t]+)");
|
||||||
insertText = match.Success ? Environment.NewLine+match.Groups[1].Value : null;
|
insertText = match.Success ? Environment.NewLine + match.Groups[1].Value : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(insertText)){
|
if (!string.IsNullOrEmpty(insertText)){
|
||||||
e.SuppressKeyPress = true;
|
e.SuppressKeyPress = true;
|
||||||
tb.Text = text.Insert(insertAt, insertText);
|
tb.Text = text.Insert(insertAt, insertText);
|
||||||
tb.SelectionStart = insertAt+cursorOffset+insertText.Length;
|
tb.SelectionStart = insertAt + cursorOffset + insertText.Length;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,19 +2,21 @@
|
|||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using TweetDuck.Core.Controls;
|
using TweetDuck.Core.Controls;
|
||||||
using TweetDuck.Core.Utils;
|
using TweetDuck.Core.Utils;
|
||||||
using TweetDuck.Data;
|
using TweetLib.Core.Collections;
|
||||||
|
|
||||||
namespace TweetDuck.Core.Other.Settings.Dialogs{
|
namespace TweetDuck.Core.Other.Settings.Dialogs{
|
||||||
sealed partial class DialogSettingsCefArgs : Form{
|
sealed partial class DialogSettingsCefArgs : Form{
|
||||||
public string CefArgs => textBoxArgs.Text;
|
public string CefArgs => textBoxArgs.Text;
|
||||||
|
|
||||||
public DialogSettingsCefArgs(){
|
private readonly string initialArgs;
|
||||||
|
|
||||||
|
public DialogSettingsCefArgs(string args){
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
Text = Program.BrandName+" Options - CEF Arguments";
|
Text = Program.BrandName + " Options - CEF Arguments";
|
||||||
|
|
||||||
textBoxArgs.EnableMultilineShortcuts();
|
textBoxArgs.EnableMultilineShortcuts();
|
||||||
textBoxArgs.Text = Program.UserConfig.CustomCefArgs ?? "";
|
textBoxArgs.Text = initialArgs = args ?? "";
|
||||||
textBoxArgs.Select(textBoxArgs.Text.Length, 0);
|
textBoxArgs.Select(textBoxArgs.Text.Length, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,16 +25,14 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void btnApply_Click(object sender, EventArgs e){
|
private void btnApply_Click(object sender, EventArgs e){
|
||||||
string prevArgs = Program.UserConfig.CustomCefArgs;
|
if (CefArgs == initialArgs){
|
||||||
|
|
||||||
if (CefArgs == prevArgs){
|
|
||||||
DialogResult = DialogResult.Cancel;
|
DialogResult = DialogResult.Cancel;
|
||||||
Close();
|
Close();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int count = CommandLineArgs.ReadCefArguments(CefArgs).Count;
|
int count = CommandLineArgs.ReadCefArguments(CefArgs).Count;
|
||||||
string prompt = count == 0 && !string.IsNullOrWhiteSpace(prevArgs) ? "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)){
|
||||||
DialogResult = DialogResult.OK;
|
DialogResult = DialogResult.OK;
|
||||||
|
@@ -4,8 +4,8 @@ using System.IO;
|
|||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using TweetDuck.Configuration;
|
using TweetDuck.Configuration;
|
||||||
using TweetDuck.Core.Management;
|
using TweetDuck.Core.Management;
|
||||||
using TweetDuck.Core.Utils;
|
using TweetLib.Core.Features.Plugins;
|
||||||
using TweetDuck.Plugins;
|
using TweetLib.Core.Utils;
|
||||||
|
|
||||||
namespace TweetDuck.Core.Other.Settings.Dialogs{
|
namespace TweetDuck.Core.Other.Settings.Dialogs{
|
||||||
sealed partial class DialogSettingsManage : Form{
|
sealed partial class DialogSettingsManage : Form{
|
||||||
@@ -33,6 +33,7 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
|
|||||||
|
|
||||||
private readonly PluginManager plugins;
|
private readonly PluginManager plugins;
|
||||||
private readonly Dictionary<CheckBox, ProfileManager.Items> checkBoxMap = new Dictionary<CheckBox, ProfileManager.Items>(4);
|
private readonly Dictionary<CheckBox, ProfileManager.Items> checkBoxMap = new Dictionary<CheckBox, ProfileManager.Items>(4);
|
||||||
|
private readonly bool openImportImmediately;
|
||||||
|
|
||||||
private State currentState;
|
private State currentState;
|
||||||
private ProfileManager importManager;
|
private ProfileManager importManager;
|
||||||
@@ -51,6 +52,8 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
|
|||||||
this.checkBoxMap[cbSession] = ProfileManager.Items.Session;
|
this.checkBoxMap[cbSession] = ProfileManager.Items.Session;
|
||||||
this.checkBoxMap[cbPluginData] = ProfileManager.Items.PluginData;
|
this.checkBoxMap[cbPluginData] = ProfileManager.Items.PluginData;
|
||||||
|
|
||||||
|
this.openImportImmediately = openImportImmediately;
|
||||||
|
|
||||||
if (openImportImmediately){
|
if (openImportImmediately){
|
||||||
radioImport.Checked = true;
|
radioImport.Checked = true;
|
||||||
btnContinue_Click(null, EventArgs.Empty);
|
btnContinue_Click(null, EventArgs.Empty);
|
||||||
@@ -88,6 +91,10 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
|
|||||||
Filter = "TweetDuck Profile (*.tdsettings)|*.tdsettings"
|
Filter = "TweetDuck Profile (*.tdsettings)|*.tdsettings"
|
||||||
}){
|
}){
|
||||||
if (dialog.ShowDialog() != DialogResult.OK){
|
if (dialog.ShowDialog() != DialogResult.OK){
|
||||||
|
if (openImportImmediately){
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,7 +124,7 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
|
|||||||
// Continue...
|
// Continue...
|
||||||
panelDecision.Visible = false;
|
panelDecision.Visible = false;
|
||||||
panelSelection.Visible = true;
|
panelSelection.Visible = true;
|
||||||
Height += panelSelection.Height-panelDecision.Height;
|
Height += panelSelection.Height - panelDecision.Height;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case State.Reset:
|
case State.Reset:
|
||||||
@@ -125,18 +132,19 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
|
|||||||
Program.Config.ProgramRestartRequested += Config_ProgramRestartRequested;
|
Program.Config.ProgramRestartRequested += Config_ProgramRestartRequested;
|
||||||
|
|
||||||
if (SelectedItems.HasFlag(ProfileManager.Items.UserConfig)){
|
if (SelectedItems.HasFlag(ProfileManager.Items.UserConfig)){
|
||||||
Program.UserConfig.Reset();
|
Program.Config.User.Reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SelectedItems.HasFlag(ProfileManager.Items.SystemConfig)){
|
if (SelectedItems.HasFlag(ProfileManager.Items.SystemConfig)){
|
||||||
Program.SystemConfig.Reset();
|
Program.Config.System.Reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
Program.Config.ProgramRestartRequested -= Config_ProgramRestartRequested;
|
Program.Config.ProgramRestartRequested -= Config_ProgramRestartRequested;
|
||||||
|
|
||||||
if (SelectedItems.HasFlag(ProfileManager.Items.PluginData)){
|
if (SelectedItems.HasFlag(ProfileManager.Items.PluginData)){
|
||||||
|
Program.Config.Plugins.Reset();
|
||||||
|
|
||||||
try{
|
try{
|
||||||
File.Delete(Program.PluginConfigFilePath);
|
|
||||||
Directory.Delete(Program.PluginDataPath, true);
|
Directory.Delete(Program.PluginDataPath, true);
|
||||||
}catch(Exception ex){
|
}catch(Exception ex){
|
||||||
Program.Reporter.HandleException("Profile Reset", "Could not delete plugin data.", true, ex);
|
Program.Reporter.HandleException("Profile Reset", "Could not delete plugin data.", true, ex);
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using TweetDuck.Configuration;
|
using TweetDuck.Configuration;
|
||||||
using TweetDuck.Data;
|
using TweetLib.Core.Collections;
|
||||||
|
|
||||||
namespace TweetDuck.Core.Other.Settings.Dialogs{
|
namespace TweetDuck.Core.Other.Settings.Dialogs{
|
||||||
sealed partial class DialogSettingsRestart : Form{
|
sealed partial class DialogSettingsRestart : Form{
|
||||||
@@ -18,13 +18,13 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
|
|||||||
tbDataFolder.Enabled = false;
|
tbDataFolder.Enabled = false;
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
tbDataFolder.Text = currentArgs.GetValue(Arguments.ArgDataFolder, string.Empty);
|
tbDataFolder.Text = currentArgs.GetValue(Arguments.ArgDataFolder) ?? string.Empty;
|
||||||
tbDataFolder.TextChanged += control_Change;
|
tbDataFolder.TextChanged += control_Change;
|
||||||
}
|
}
|
||||||
|
|
||||||
control_Change(this, EventArgs.Empty);
|
control_Change(this, EventArgs.Empty);
|
||||||
|
|
||||||
Text = Program.BrandName+" Arguments";
|
Text = Program.BrandName + " Arguments";
|
||||||
}
|
}
|
||||||
|
|
||||||
private void control_Change(object sender, EventArgs e){
|
private void control_Change(object sender, EventArgs e){
|
||||||
|
@@ -8,9 +8,9 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
|
|||||||
public DialogSettingsSearchEngine(){
|
public DialogSettingsSearchEngine(){
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
Text = Program.BrandName+" Options - Custom Search Engine";
|
Text = Program.BrandName + " Options - Custom Search Engine";
|
||||||
|
|
||||||
textBoxUrl.Text = Program.UserConfig.SearchEngineUrl ?? "";
|
textBoxUrl.Text = Program.Config.User.SearchEngineUrl ?? "";
|
||||||
textBoxUrl.Select(textBoxUrl.Text.Length, 0);
|
textBoxUrl.Select(textBoxUrl.Text.Length, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using TweetDuck.Configuration;
|
using TweetDuck.Configuration;
|
||||||
@@ -7,6 +6,7 @@ using TweetDuck.Core.Controls;
|
|||||||
using TweetDuck.Core.Management;
|
using TweetDuck.Core.Management;
|
||||||
using TweetDuck.Core.Other.Settings.Dialogs;
|
using TweetDuck.Core.Other.Settings.Dialogs;
|
||||||
using TweetDuck.Core.Utils;
|
using TweetDuck.Core.Utils;
|
||||||
|
using TweetLib.Core;
|
||||||
|
|
||||||
namespace TweetDuck.Core.Other.Settings{
|
namespace TweetDuck.Core.Other.Settings{
|
||||||
sealed partial class TabSettingsAdvanced : BaseTabSettings{
|
sealed partial class TabSettingsAdvanced : BaseTabSettings{
|
||||||
@@ -36,7 +36,7 @@ namespace TweetDuck.Core.Other.Settings{
|
|||||||
numClearCacheThreshold.SetValueSafe(SysConfig.ClearCacheThreshold);
|
numClearCacheThreshold.SetValueSafe(SysConfig.ClearCacheThreshold);
|
||||||
|
|
||||||
BrowserCache.GetCacheSize(task => {
|
BrowserCache.GetCacheSize(task => {
|
||||||
string text = task.Status == TaskStatus.RanToCompletion ? (int)Math.Ceiling(task.Result/(1024.0*1024.0))+" MB" : "unknown";
|
string text = task.Status == TaskStatus.RanToCompletion ? (int)Math.Ceiling(task.Result / (1024.0 * 1024.0)) + " MB" : "unknown";
|
||||||
this.InvokeSafe(() => btnClearCache.Text = $"Clear Cache ({text})");
|
this.InvokeSafe(() => btnClearCache.Text = $"Clear Cache ({text})");
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -67,11 +67,11 @@ namespace TweetDuck.Core.Other.Settings{
|
|||||||
#region Application
|
#region Application
|
||||||
|
|
||||||
private void btnOpenAppFolder_Click(object sender, EventArgs e){
|
private void btnOpenAppFolder_Click(object sender, EventArgs e){
|
||||||
using(Process.Start("explorer.exe", "\""+Program.ProgramPath+"\"")){}
|
App.SystemHandler.OpenFileExplorer(Program.ProgramPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void btnOpenDataFolder_Click(object sender, EventArgs e){
|
private void btnOpenDataFolder_Click(object sender, EventArgs e){
|
||||||
using(Process.Start("explorer.exe", "\""+Program.StoragePath+"\"")){}
|
App.SystemHandler.OpenFileExplorer(Program.StoragePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void btnRestart_Click(object sender, EventArgs e){
|
private void btnRestart_Click(object sender, EventArgs e){
|
||||||
@@ -103,7 +103,7 @@ namespace TweetDuck.Core.Other.Settings{
|
|||||||
#region Configuration
|
#region Configuration
|
||||||
|
|
||||||
private void btnEditCefArgs_Click(object sender, EventArgs e){
|
private void btnEditCefArgs_Click(object sender, EventArgs e){
|
||||||
DialogSettingsCefArgs form = new DialogSettingsCefArgs();
|
DialogSettingsCefArgs form = new DialogSettingsCefArgs(Config.CustomCefArgs);
|
||||||
|
|
||||||
form.VisibleChanged += (sender2, args2) => {
|
form.VisibleChanged += (sender2, args2) => {
|
||||||
form.MoveToCenter(ParentForm);
|
form.MoveToCenter(ParentForm);
|
||||||
@@ -124,7 +124,7 @@ namespace TweetDuck.Core.Other.Settings{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void btnEditCSS_Click(object sender, EventArgs e){
|
private void btnEditCSS_Click(object sender, EventArgs e){
|
||||||
DialogSettingsCSS form = new DialogSettingsCSS(reinjectBrowserCSS, openDevTools);
|
DialogSettingsCSS form = new DialogSettingsCSS(Config.CustomBrowserCSS, Config.CustomNotificationCSS, reinjectBrowserCSS, openDevTools);
|
||||||
|
|
||||||
form.VisibleChanged += (sender2, args2) => {
|
form.VisibleChanged += (sender2, args2) => {
|
||||||
form.MoveToCenter(ParentForm);
|
form.MoveToCenter(ParentForm);
|
||||||
|
@@ -3,7 +3,7 @@ using System.Windows.Forms;
|
|||||||
using TweetDuck.Core.Other.Analytics;
|
using TweetDuck.Core.Other.Analytics;
|
||||||
using TweetDuck.Core.Other.Settings.Dialogs;
|
using TweetDuck.Core.Other.Settings.Dialogs;
|
||||||
using TweetDuck.Core.Utils;
|
using TweetDuck.Core.Utils;
|
||||||
using TweetDuck.Plugins;
|
using TweetLib.Core.Features.Plugins;
|
||||||
|
|
||||||
namespace TweetDuck.Core.Other.Settings{
|
namespace TweetDuck.Core.Other.Settings{
|
||||||
sealed partial class TabSettingsFeedback : BaseTabSettings{
|
sealed partial class TabSettingsFeedback : BaseTabSettings{
|
||||||
@@ -24,7 +24,7 @@ namespace TweetDuck.Core.Other.Settings{
|
|||||||
|
|
||||||
if (analytics != null){
|
if (analytics != null){
|
||||||
string collectionTime = analyticsFile.LastCollectionMessage;
|
string collectionTime = analyticsFile.LastCollectionMessage;
|
||||||
labelDataCollectionMessage.Text = string.IsNullOrEmpty(collectionTime) ? "No collection yet" : "Last collection: "+collectionTime;
|
labelDataCollectionMessage.Text = string.IsNullOrEmpty(collectionTime) ? "No collection yet" : "Last collection: " + collectionTime;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
71
Core/Other/Settings/TabSettingsGeneral.Designer.cs
generated
71
Core/Other/Settings/TabSettingsGeneral.Designer.cs
generated
@@ -39,6 +39,7 @@
|
|||||||
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.checkFocusDmInput = new System.Windows.Forms.CheckBox();
|
||||||
this.checkKeepLikeFollowDialogsOpen = new System.Windows.Forms.CheckBox();
|
this.checkKeepLikeFollowDialogsOpen = new System.Windows.Forms.CheckBox();
|
||||||
this.labelTray = new System.Windows.Forms.Label();
|
this.labelTray = new System.Windows.Forms.Label();
|
||||||
this.comboBoxTrayType = new System.Windows.Forms.ComboBox();
|
this.comboBoxTrayType = new System.Windows.Forms.ComboBox();
|
||||||
@@ -82,11 +83,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, 393);
|
this.checkUpdateNotifications.Location = new System.Drawing.Point(6, 409);
|
||||||
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 = 13;
|
this.checkUpdateNotifications.TabIndex = 14;
|
||||||
this.checkUpdateNotifications.Text = "Check Updates Automatically";
|
this.checkUpdateNotifications.Text = "Check Updates Automatically";
|
||||||
this.checkUpdateNotifications.UseVisualStyleBackColor = true;
|
this.checkUpdateNotifications.UseVisualStyleBackColor = true;
|
||||||
//
|
//
|
||||||
@@ -94,12 +95,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, 417);
|
this.btnCheckUpdates.Location = new System.Drawing.Point(5, 433);
|
||||||
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 = 14;
|
this.btnCheckUpdates.TabIndex = 15;
|
||||||
this.btnCheckUpdates.Text = "Check Updates Now";
|
this.btnCheckUpdates.Text = "Check Updates Now";
|
||||||
this.btnCheckUpdates.UseVisualStyleBackColor = true;
|
this.btnCheckUpdates.UseVisualStyleBackColor = true;
|
||||||
//
|
//
|
||||||
@@ -119,11 +120,11 @@
|
|||||||
//
|
//
|
||||||
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, 98);
|
this.checkBestImageQuality.Location = new System.Drawing.Point(6, 122);
|
||||||
this.checkBestImageQuality.Margin = new System.Windows.Forms.Padding(6, 3, 3, 2);
|
this.checkBestImageQuality.Margin = new System.Windows.Forms.Padding(6, 3, 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(125, 19);
|
||||||
this.checkBestImageQuality.TabIndex = 4;
|
this.checkBestImageQuality.TabIndex = 5;
|
||||||
this.checkBestImageQuality.Text = "Best Image Quality";
|
this.checkBestImageQuality.Text = "Best Image Quality";
|
||||||
this.checkBestImageQuality.UseVisualStyleBackColor = true;
|
this.checkBestImageQuality.UseVisualStyleBackColor = true;
|
||||||
//
|
//
|
||||||
@@ -131,11 +132,11 @@
|
|||||||
//
|
//
|
||||||
this.checkOpenSearchInFirstColumn.AutoSize = true;
|
this.checkOpenSearchInFirstColumn.AutoSize = true;
|
||||||
this.checkOpenSearchInFirstColumn.Font = new System.Drawing.Font("Segoe UI", 9F);
|
this.checkOpenSearchInFirstColumn.Font = new System.Drawing.Font("Segoe UI", 9F);
|
||||||
this.checkOpenSearchInFirstColumn.Location = new System.Drawing.Point(6, 50);
|
this.checkOpenSearchInFirstColumn.Location = new System.Drawing.Point(6, 74);
|
||||||
this.checkOpenSearchInFirstColumn.Margin = new System.Windows.Forms.Padding(6, 3, 3, 2);
|
this.checkOpenSearchInFirstColumn.Margin = new System.Windows.Forms.Padding(6, 3, 3, 2);
|
||||||
this.checkOpenSearchInFirstColumn.Name = "checkOpenSearchInFirstColumn";
|
this.checkOpenSearchInFirstColumn.Name = "checkOpenSearchInFirstColumn";
|
||||||
this.checkOpenSearchInFirstColumn.Size = new System.Drawing.Size(245, 19);
|
this.checkOpenSearchInFirstColumn.Size = new System.Drawing.Size(245, 19);
|
||||||
this.checkOpenSearchInFirstColumn.TabIndex = 2;
|
this.checkOpenSearchInFirstColumn.TabIndex = 3;
|
||||||
this.checkOpenSearchInFirstColumn.Text = "Add Search Columns Before First Column";
|
this.checkOpenSearchInFirstColumn.Text = "Add Search Columns Before First Column";
|
||||||
this.checkOpenSearchInFirstColumn.UseVisualStyleBackColor = true;
|
this.checkOpenSearchInFirstColumn.UseVisualStyleBackColor = true;
|
||||||
//
|
//
|
||||||
@@ -158,11 +159,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, 155);
|
this.labelZoom.Location = new System.Drawing.Point(3, 179);
|
||||||
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 = 6;
|
this.labelZoom.TabIndex = 7;
|
||||||
this.labelZoom.Text = "Zoom";
|
this.labelZoom.Text = "Zoom";
|
||||||
//
|
//
|
||||||
// zoomUpdateTimer
|
// zoomUpdateTimer
|
||||||
@@ -185,21 +186,21 @@
|
|||||||
//
|
//
|
||||||
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, 171);
|
this.panelZoom.Location = new System.Drawing.Point(0, 195);
|
||||||
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 = 7;
|
this.panelZoom.TabIndex = 8;
|
||||||
//
|
//
|
||||||
// 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, 122);
|
this.checkAnimatedAvatars.Location = new System.Drawing.Point(6, 146);
|
||||||
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 = 5;
|
this.checkAnimatedAvatars.TabIndex = 6;
|
||||||
this.checkAnimatedAvatars.Text = "Enable Animated Avatars";
|
this.checkAnimatedAvatars.Text = "Enable Animated Avatars";
|
||||||
this.checkAnimatedAvatars.UseVisualStyleBackColor = true;
|
this.checkAnimatedAvatars.UseVisualStyleBackColor = true;
|
||||||
//
|
//
|
||||||
@@ -207,11 +208,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, 367);
|
this.labelUpdates.Location = new System.Drawing.Point(0, 383);
|
||||||
this.labelUpdates.Margin = new System.Windows.Forms.Padding(0, 30, 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 = 12;
|
this.labelUpdates.TabIndex = 13;
|
||||||
this.labelUpdates.Text = "UPDATES";
|
this.labelUpdates.Text = "UPDATES";
|
||||||
//
|
//
|
||||||
// flowPanelLeft
|
// flowPanelLeft
|
||||||
@@ -220,6 +221,7 @@
|
|||||||
| System.Windows.Forms.AnchorStyles.Left)));
|
| System.Windows.Forms.AnchorStyles.Left)));
|
||||||
this.flowPanelLeft.Controls.Add(this.labelUI);
|
this.flowPanelLeft.Controls.Add(this.labelUI);
|
||||||
this.flowPanelLeft.Controls.Add(this.checkExpandLinks);
|
this.flowPanelLeft.Controls.Add(this.checkExpandLinks);
|
||||||
|
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.checkBestImageQuality);
|
||||||
@@ -240,15 +242,27 @@
|
|||||||
this.flowPanelLeft.TabIndex = 0;
|
this.flowPanelLeft.TabIndex = 0;
|
||||||
this.flowPanelLeft.WrapContents = false;
|
this.flowPanelLeft.WrapContents = false;
|
||||||
//
|
//
|
||||||
|
// checkFocusDmInput
|
||||||
|
//
|
||||||
|
this.checkFocusDmInput.AutoSize = true;
|
||||||
|
this.checkFocusDmInput.Font = new System.Drawing.Font("Segoe UI", 9F);
|
||||||
|
this.checkFocusDmInput.Location = new System.Drawing.Point(6, 50);
|
||||||
|
this.checkFocusDmInput.Margin = new System.Windows.Forms.Padding(6, 3, 3, 2);
|
||||||
|
this.checkFocusDmInput.Name = "checkFocusDmInput";
|
||||||
|
this.checkFocusDmInput.Size = new System.Drawing.Size(282, 19);
|
||||||
|
this.checkFocusDmInput.TabIndex = 2;
|
||||||
|
this.checkFocusDmInput.Text = "Focus Input Field When Opening Direct Message";
|
||||||
|
this.checkFocusDmInput.UseVisualStyleBackColor = true;
|
||||||
|
//
|
||||||
// checkKeepLikeFollowDialogsOpen
|
// checkKeepLikeFollowDialogsOpen
|
||||||
//
|
//
|
||||||
this.checkKeepLikeFollowDialogsOpen.AutoSize = true;
|
this.checkKeepLikeFollowDialogsOpen.AutoSize = true;
|
||||||
this.checkKeepLikeFollowDialogsOpen.Font = new System.Drawing.Font("Segoe UI", 9F);
|
this.checkKeepLikeFollowDialogsOpen.Font = new System.Drawing.Font("Segoe UI", 9F);
|
||||||
this.checkKeepLikeFollowDialogsOpen.Location = new System.Drawing.Point(6, 74);
|
this.checkKeepLikeFollowDialogsOpen.Location = new System.Drawing.Point(6, 98);
|
||||||
this.checkKeepLikeFollowDialogsOpen.Margin = new System.Windows.Forms.Padding(6, 3, 3, 2);
|
this.checkKeepLikeFollowDialogsOpen.Margin = new System.Windows.Forms.Padding(6, 3, 3, 2);
|
||||||
this.checkKeepLikeFollowDialogsOpen.Name = "checkKeepLikeFollowDialogsOpen";
|
this.checkKeepLikeFollowDialogsOpen.Name = "checkKeepLikeFollowDialogsOpen";
|
||||||
this.checkKeepLikeFollowDialogsOpen.Size = new System.Drawing.Size(190, 19);
|
this.checkKeepLikeFollowDialogsOpen.Size = new System.Drawing.Size(190, 19);
|
||||||
this.checkKeepLikeFollowDialogsOpen.TabIndex = 3;
|
this.checkKeepLikeFollowDialogsOpen.TabIndex = 4;
|
||||||
this.checkKeepLikeFollowDialogsOpen.Text = "Keep Like/Follow Dialogs Open";
|
this.checkKeepLikeFollowDialogsOpen.Text = "Keep Like/Follow Dialogs Open";
|
||||||
this.checkKeepLikeFollowDialogsOpen.UseVisualStyleBackColor = true;
|
this.checkKeepLikeFollowDialogsOpen.UseVisualStyleBackColor = true;
|
||||||
//
|
//
|
||||||
@@ -256,11 +270,11 @@
|
|||||||
//
|
//
|
||||||
this.labelTray.AutoSize = true;
|
this.labelTray.AutoSize = true;
|
||||||
this.labelTray.Font = new System.Drawing.Font("Segoe UI Semibold", 10.5F, System.Drawing.FontStyle.Bold);
|
this.labelTray.Font = new System.Drawing.Font("Segoe UI Semibold", 10.5F, System.Drawing.FontStyle.Bold);
|
||||||
this.labelTray.Location = new System.Drawing.Point(0, 236);
|
this.labelTray.Location = new System.Drawing.Point(0, 255);
|
||||||
this.labelTray.Margin = new System.Windows.Forms.Padding(0, 30, 0, 1);
|
this.labelTray.Margin = new System.Windows.Forms.Padding(0, 25, 0, 1);
|
||||||
this.labelTray.Name = "labelTray";
|
this.labelTray.Name = "labelTray";
|
||||||
this.labelTray.Size = new System.Drawing.Size(99, 19);
|
this.labelTray.Size = new System.Drawing.Size(99, 19);
|
||||||
this.labelTray.TabIndex = 8;
|
this.labelTray.TabIndex = 9;
|
||||||
this.labelTray.Text = "SYSTEM TRAY";
|
this.labelTray.Text = "SYSTEM TRAY";
|
||||||
//
|
//
|
||||||
// comboBoxTrayType
|
// comboBoxTrayType
|
||||||
@@ -268,32 +282,32 @@
|
|||||||
this.comboBoxTrayType.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
|
this.comboBoxTrayType.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
|
||||||
this.comboBoxTrayType.Font = new System.Drawing.Font("Segoe UI", 9F);
|
this.comboBoxTrayType.Font = new System.Drawing.Font("Segoe UI", 9F);
|
||||||
this.comboBoxTrayType.FormattingEnabled = true;
|
this.comboBoxTrayType.FormattingEnabled = true;
|
||||||
this.comboBoxTrayType.Location = new System.Drawing.Point(5, 260);
|
this.comboBoxTrayType.Location = new System.Drawing.Point(5, 279);
|
||||||
this.comboBoxTrayType.Margin = new System.Windows.Forms.Padding(5, 4, 3, 3);
|
this.comboBoxTrayType.Margin = new System.Windows.Forms.Padding(5, 4, 3, 3);
|
||||||
this.comboBoxTrayType.Name = "comboBoxTrayType";
|
this.comboBoxTrayType.Name = "comboBoxTrayType";
|
||||||
this.comboBoxTrayType.Size = new System.Drawing.Size(144, 23);
|
this.comboBoxTrayType.Size = new System.Drawing.Size(144, 23);
|
||||||
this.comboBoxTrayType.TabIndex = 9;
|
this.comboBoxTrayType.TabIndex = 10;
|
||||||
//
|
//
|
||||||
// labelTrayIcon
|
// labelTrayIcon
|
||||||
//
|
//
|
||||||
this.labelTrayIcon.AutoSize = true;
|
this.labelTrayIcon.AutoSize = true;
|
||||||
this.labelTrayIcon.Font = new System.Drawing.Font("Segoe UI Semibold", 9F, System.Drawing.FontStyle.Bold);
|
this.labelTrayIcon.Font = new System.Drawing.Font("Segoe UI Semibold", 9F, System.Drawing.FontStyle.Bold);
|
||||||
this.labelTrayIcon.Location = new System.Drawing.Point(3, 295);
|
this.labelTrayIcon.Location = new System.Drawing.Point(3, 314);
|
||||||
this.labelTrayIcon.Margin = new System.Windows.Forms.Padding(3, 9, 3, 0);
|
this.labelTrayIcon.Margin = new System.Windows.Forms.Padding(3, 9, 3, 0);
|
||||||
this.labelTrayIcon.Name = "labelTrayIcon";
|
this.labelTrayIcon.Name = "labelTrayIcon";
|
||||||
this.labelTrayIcon.Size = new System.Drawing.Size(56, 15);
|
this.labelTrayIcon.Size = new System.Drawing.Size(56, 15);
|
||||||
this.labelTrayIcon.TabIndex = 10;
|
this.labelTrayIcon.TabIndex = 11;
|
||||||
this.labelTrayIcon.Text = "Tray Icon";
|
this.labelTrayIcon.Text = "Tray Icon";
|
||||||
//
|
//
|
||||||
// checkTrayHighlight
|
// checkTrayHighlight
|
||||||
//
|
//
|
||||||
this.checkTrayHighlight.AutoSize = true;
|
this.checkTrayHighlight.AutoSize = true;
|
||||||
this.checkTrayHighlight.Font = new System.Drawing.Font("Segoe UI", 9F);
|
this.checkTrayHighlight.Font = new System.Drawing.Font("Segoe UI", 9F);
|
||||||
this.checkTrayHighlight.Location = new System.Drawing.Point(6, 316);
|
this.checkTrayHighlight.Location = new System.Drawing.Point(6, 335);
|
||||||
this.checkTrayHighlight.Margin = new System.Windows.Forms.Padding(6, 6, 3, 2);
|
this.checkTrayHighlight.Margin = new System.Windows.Forms.Padding(6, 6, 3, 2);
|
||||||
this.checkTrayHighlight.Name = "checkTrayHighlight";
|
this.checkTrayHighlight.Name = "checkTrayHighlight";
|
||||||
this.checkTrayHighlight.Size = new System.Drawing.Size(114, 19);
|
this.checkTrayHighlight.Size = new System.Drawing.Size(114, 19);
|
||||||
this.checkTrayHighlight.TabIndex = 11;
|
this.checkTrayHighlight.TabIndex = 12;
|
||||||
this.checkTrayHighlight.Text = "Enable Highlight";
|
this.checkTrayHighlight.Text = "Enable Highlight";
|
||||||
this.checkTrayHighlight.UseVisualStyleBackColor = true;
|
this.checkTrayHighlight.UseVisualStyleBackColor = true;
|
||||||
//
|
//
|
||||||
@@ -548,5 +562,6 @@
|
|||||||
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 checkHardwareAcceleration;
|
||||||
|
private System.Windows.Forms.CheckBox checkFocusDmInput;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -6,7 +6,8 @@ using TweetDuck.Core.Controls;
|
|||||||
using TweetDuck.Core.Handling.General;
|
using TweetDuck.Core.Handling.General;
|
||||||
using TweetDuck.Core.Other.Settings.Dialogs;
|
using TweetDuck.Core.Other.Settings.Dialogs;
|
||||||
using TweetDuck.Core.Utils;
|
using TweetDuck.Core.Utils;
|
||||||
using TweetDuck.Updates;
|
using TweetLib.Core.Features.Updates;
|
||||||
|
using TweetLib.Core.Utils;
|
||||||
|
|
||||||
namespace TweetDuck.Core.Other.Settings{
|
namespace TweetDuck.Core.Other.Settings{
|
||||||
sealed partial class TabSettingsGeneral : BaseTabSettings{
|
sealed partial class TabSettingsGeneral : BaseTabSettings{
|
||||||
@@ -34,6 +35,7 @@ namespace TweetDuck.Core.Other.Settings{
|
|||||||
// user interface
|
// user interface
|
||||||
|
|
||||||
toolTip.SetToolTip(checkExpandLinks, "Expands links inside the tweets. If disabled,\r\nthe full links show up in a tooltip instead.");
|
toolTip.SetToolTip(checkExpandLinks, "Expands links inside the tweets. If disabled,\r\nthe full links show up in a tooltip instead.");
|
||||||
|
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(checkBestImageQuality, "When right-clicking a tweet image, the context menu options\r\nwill use links to the original image size (:orig in the URL).");
|
||||||
@@ -42,13 +44,14 @@ namespace TweetDuck.Core.Other.Settings{
|
|||||||
toolTip.SetToolTip(trackBarZoom, toolTip.GetToolTip(labelZoomValue));
|
toolTip.SetToolTip(trackBarZoom, toolTip.GetToolTip(labelZoomValue));
|
||||||
|
|
||||||
checkExpandLinks.Checked = Config.ExpandLinksOnHover;
|
checkExpandLinks.Checked = Config.ExpandLinksOnHover;
|
||||||
|
checkFocusDmInput.Checked = Config.FocusDmInput;
|
||||||
checkOpenSearchInFirstColumn.Checked = Config.OpenSearchInFirstColumn;
|
checkOpenSearchInFirstColumn.Checked = Config.OpenSearchInFirstColumn;
|
||||||
checkKeepLikeFollowDialogsOpen.Checked = Config.KeepLikeFollowDialogsOpen;
|
checkKeepLikeFollowDialogsOpen.Checked = Config.KeepLikeFollowDialogsOpen;
|
||||||
checkBestImageQuality.Checked = Config.BestImageQuality;
|
checkBestImageQuality.Checked = Config.BestImageQuality;
|
||||||
checkAnimatedAvatars.Checked = Config.EnableAnimatedImages;
|
checkAnimatedAvatars.Checked = Config.EnableAnimatedImages;
|
||||||
|
|
||||||
trackBarZoom.SetValueSafe(Config.ZoomLevel);
|
trackBarZoom.SetValueSafe(Config.ZoomLevel);
|
||||||
labelZoomValue.Text = trackBarZoom.Value+"%";
|
labelZoomValue.Text = trackBarZoom.Value + "%";
|
||||||
|
|
||||||
// system tray
|
// system tray
|
||||||
|
|
||||||
@@ -60,7 +63,7 @@ namespace TweetDuck.Core.Other.Settings{
|
|||||||
comboBoxTrayType.Items.Add("Minimize to Tray");
|
comboBoxTrayType.Items.Add("Minimize to Tray");
|
||||||
comboBoxTrayType.Items.Add("Close to Tray");
|
comboBoxTrayType.Items.Add("Close to Tray");
|
||||||
comboBoxTrayType.Items.Add("Combined");
|
comboBoxTrayType.Items.Add("Combined");
|
||||||
comboBoxTrayType.SelectedIndex = Math.Min(Math.Max((int)Config.TrayBehavior, 0), comboBoxTrayType.Items.Count-1);
|
comboBoxTrayType.SelectedIndex = Math.Min(Math.Max((int)Config.TrayBehavior, 0), comboBoxTrayType.Items.Count - 1);
|
||||||
|
|
||||||
checkTrayHighlight.Enabled = Config.TrayBehavior.ShouldDisplayIcon();
|
checkTrayHighlight.Enabled = Config.TrayBehavior.ShouldDisplayIcon();
|
||||||
checkTrayHighlight.Checked = Config.EnableTrayHighlight;
|
checkTrayHighlight.Checked = Config.EnableTrayHighlight;
|
||||||
@@ -127,6 +130,7 @@ namespace TweetDuck.Core.Other.Settings{
|
|||||||
|
|
||||||
public override void OnReady(){
|
public override void OnReady(){
|
||||||
checkExpandLinks.CheckedChanged += checkExpandLinks_CheckedChanged;
|
checkExpandLinks.CheckedChanged += checkExpandLinks_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;
|
checkBestImageQuality.CheckedChanged += checkBestImageQuality_CheckedChanged;
|
||||||
@@ -160,6 +164,10 @@ namespace TweetDuck.Core.Other.Settings{
|
|||||||
Config.ExpandLinksOnHover = checkExpandLinks.Checked;
|
Config.ExpandLinksOnHover = checkExpandLinks.Checked;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void checkFocusDmInput_CheckedChanged(object sender, EventArgs e){
|
||||||
|
Config.FocusDmInput = checkFocusDmInput.Checked;
|
||||||
|
}
|
||||||
|
|
||||||
private void checkOpenSearchInFirstColumn_CheckedChanged(object sender, EventArgs e){
|
private void checkOpenSearchInFirstColumn_CheckedChanged(object sender, EventArgs e){
|
||||||
Config.OpenSearchInFirstColumn = checkOpenSearchInFirstColumn.Checked;
|
Config.OpenSearchInFirstColumn = checkOpenSearchInFirstColumn.Checked;
|
||||||
}
|
}
|
||||||
@@ -181,7 +189,7 @@ namespace TweetDuck.Core.Other.Settings{
|
|||||||
if (trackBarZoom.AlignValueToTick()){
|
if (trackBarZoom.AlignValueToTick()){
|
||||||
zoomUpdateTimer.Stop();
|
zoomUpdateTimer.Stop();
|
||||||
zoomUpdateTimer.Start();
|
zoomUpdateTimer.Start();
|
||||||
labelZoomValue.Text = trackBarZoom.Value+"%";
|
labelZoomValue.Text = trackBarZoom.Value + "%";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using TweetDuck.Core.Controls;
|
using TweetDuck.Core.Controls;
|
||||||
using TweetDuck.Core.Notification;
|
|
||||||
using TweetDuck.Core.Notification.Example;
|
using TweetDuck.Core.Notification.Example;
|
||||||
|
using TweetLib.Core.Features.Notifications;
|
||||||
|
|
||||||
namespace TweetDuck.Core.Other.Settings{
|
namespace TweetDuck.Core.Other.Settings{
|
||||||
sealed partial class TabSettingsNotifications : BaseTabSettings{
|
sealed partial class TabSettingsNotifications : BaseTabSettings{
|
||||||
@@ -59,31 +59,31 @@ namespace TweetDuck.Core.Other.Settings{
|
|||||||
checkTimerCountDown.Checked = Config.NotificationTimerCountDown;
|
checkTimerCountDown.Checked = Config.NotificationTimerCountDown;
|
||||||
|
|
||||||
trackBarDuration.SetValueSafe(Config.NotificationDurationValue);
|
trackBarDuration.SetValueSafe(Config.NotificationDurationValue);
|
||||||
labelDurationValue.Text = Config.NotificationDurationValue+" ms/c";
|
labelDurationValue.Text = Config.NotificationDurationValue + " ms/c";
|
||||||
|
|
||||||
// location
|
// location
|
||||||
|
|
||||||
toolTip.SetToolTip(radioLocCustom, "Drag the example notification window to the desired location.");
|
toolTip.SetToolTip(radioLocCustom, "Drag the example notification window to the desired location.");
|
||||||
|
|
||||||
switch(Config.NotificationPosition){
|
switch(Config.NotificationPosition){
|
||||||
case TweetNotification.Position.TopLeft: radioLocTL.Checked = true; break;
|
case DesktopNotification.Position.TopLeft: radioLocTL.Checked = true; break;
|
||||||
case TweetNotification.Position.TopRight: radioLocTR.Checked = true; break;
|
case DesktopNotification.Position.TopRight: radioLocTR.Checked = true; break;
|
||||||
case TweetNotification.Position.BottomLeft: radioLocBL.Checked = true; break;
|
case DesktopNotification.Position.BottomLeft: radioLocBL.Checked = true; break;
|
||||||
case TweetNotification.Position.BottomRight: radioLocBR.Checked = true; break;
|
case DesktopNotification.Position.BottomRight: radioLocBR.Checked = true; break;
|
||||||
case TweetNotification.Position.Custom: radioLocCustom.Checked = true; break;
|
case DesktopNotification.Position.Custom: radioLocCustom.Checked = true; break;
|
||||||
}
|
}
|
||||||
|
|
||||||
comboBoxDisplay.Enabled = trackBarEdgeDistance.Enabled = !radioLocCustom.Checked;
|
comboBoxDisplay.Enabled = trackBarEdgeDistance.Enabled = !radioLocCustom.Checked;
|
||||||
comboBoxDisplay.Items.Add("(Same as TweetDuck)");
|
comboBoxDisplay.Items.Add("(Same as TweetDuck)");
|
||||||
|
|
||||||
foreach(Screen screen in Screen.AllScreens){
|
foreach(Screen screen in Screen.AllScreens){
|
||||||
comboBoxDisplay.Items.Add(screen.DeviceName.TrimStart('\\', '.')+" ("+screen.Bounds.Width+"x"+screen.Bounds.Height+")");
|
comboBoxDisplay.Items.Add($"{screen.DeviceName.TrimStart('\\', '.')} ({screen.Bounds.Width}x{screen.Bounds.Height})");
|
||||||
}
|
}
|
||||||
|
|
||||||
comboBoxDisplay.SelectedIndex = Math.Min(comboBoxDisplay.Items.Count-1, Config.NotificationDisplay);
|
comboBoxDisplay.SelectedIndex = Math.Min(comboBoxDisplay.Items.Count - 1, Config.NotificationDisplay);
|
||||||
|
|
||||||
trackBarEdgeDistance.SetValueSafe(Config.NotificationEdgeDistance);
|
trackBarEdgeDistance.SetValueSafe(Config.NotificationEdgeDistance);
|
||||||
labelEdgeDistanceValue.Text = trackBarEdgeDistance.Value+" px";
|
labelEdgeDistanceValue.Text = trackBarEdgeDistance.Value + " px";
|
||||||
|
|
||||||
// size
|
// size
|
||||||
|
|
||||||
@@ -91,12 +91,12 @@ namespace TweetDuck.Core.Other.Settings{
|
|||||||
toolTip.SetToolTip(radioSizeCustom, "Resize the example notification window to the desired size.");
|
toolTip.SetToolTip(radioSizeCustom, "Resize the example notification window to the desired size.");
|
||||||
|
|
||||||
switch(Config.NotificationSize){
|
switch(Config.NotificationSize){
|
||||||
case TweetNotification.Size.Auto: radioSizeAuto.Checked = true; break;
|
case DesktopNotification.Size.Auto: radioSizeAuto.Checked = true; break;
|
||||||
case TweetNotification.Size.Custom: radioSizeCustom.Checked = true; break;
|
case DesktopNotification.Size.Custom: radioSizeCustom.Checked = true; break;
|
||||||
}
|
}
|
||||||
|
|
||||||
trackBarScrollSpeed.SetValueSafe(Config.NotificationScrollSpeed);
|
trackBarScrollSpeed.SetValueSafe(Config.NotificationScrollSpeed);
|
||||||
labelScrollSpeedValue.Text = trackBarScrollSpeed.Value+"%";
|
labelScrollSpeedValue.Text = trackBarScrollSpeed.Value + "%";
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnReady(){
|
public override void OnReady(){
|
||||||
@@ -195,7 +195,7 @@ namespace TweetDuck.Core.Other.Settings{
|
|||||||
durationUpdateTimer.Start();
|
durationUpdateTimer.Start();
|
||||||
|
|
||||||
Config.NotificationDurationValue = trackBarDuration.Value;
|
Config.NotificationDurationValue = trackBarDuration.Value;
|
||||||
labelDurationValue.Text = Config.NotificationDurationValue+" ms/c";
|
labelDurationValue.Text = Config.NotificationDurationValue + " ms/c";
|
||||||
}
|
}
|
||||||
|
|
||||||
private void btnDurationShort_Click(object sender, EventArgs e){
|
private void btnDurationShort_Click(object sender, EventArgs e){
|
||||||
@@ -219,10 +219,10 @@ namespace TweetDuck.Core.Other.Settings{
|
|||||||
#region Location
|
#region Location
|
||||||
|
|
||||||
private void radioLoc_CheckedChanged(object sender, EventArgs e){
|
private void radioLoc_CheckedChanged(object sender, EventArgs e){
|
||||||
if (radioLocTL.Checked)Config.NotificationPosition = TweetNotification.Position.TopLeft;
|
if (radioLocTL.Checked)Config.NotificationPosition = DesktopNotification.Position.TopLeft;
|
||||||
else if (radioLocTR.Checked)Config.NotificationPosition = TweetNotification.Position.TopRight;
|
else if (radioLocTR.Checked)Config.NotificationPosition = DesktopNotification.Position.TopRight;
|
||||||
else if (radioLocBL.Checked)Config.NotificationPosition = TweetNotification.Position.BottomLeft;
|
else if (radioLocBL.Checked)Config.NotificationPosition = DesktopNotification.Position.BottomLeft;
|
||||||
else if (radioLocBR.Checked)Config.NotificationPosition = TweetNotification.Position.BottomRight;
|
else if (radioLocBR.Checked)Config.NotificationPosition = DesktopNotification.Position.BottomRight;
|
||||||
|
|
||||||
comboBoxDisplay.Enabled = trackBarEdgeDistance.Enabled = true;
|
comboBoxDisplay.Enabled = trackBarEdgeDistance.Enabled = true;
|
||||||
notification.ShowExampleNotification(false);
|
notification.ShowExampleNotification(false);
|
||||||
@@ -233,18 +233,18 @@ namespace TweetDuck.Core.Other.Settings{
|
|||||||
Config.CustomNotificationPosition = notification.Location;
|
Config.CustomNotificationPosition = notification.Location;
|
||||||
}
|
}
|
||||||
|
|
||||||
Config.NotificationPosition = TweetNotification.Position.Custom;
|
Config.NotificationPosition = DesktopNotification.Position.Custom;
|
||||||
|
|
||||||
comboBoxDisplay.Enabled = trackBarEdgeDistance.Enabled = false;
|
comboBoxDisplay.Enabled = trackBarEdgeDistance.Enabled = false;
|
||||||
notification.ShowExampleNotification(false);
|
notification.ShowExampleNotification(false);
|
||||||
|
|
||||||
if (notification.IsFullyOutsideView() && FormMessage.Question("Notification is Outside View", "The notification seems to be outside of view, would you like to reset its position?", FormMessage.Yes, FormMessage.No)){
|
if (notification.IsFullyOutsideView() && FormMessage.Question("Notification is Outside View", "The notification seems to be outside of view, would you like to reset its position?", FormMessage.Yes, FormMessage.No)){
|
||||||
Config.NotificationPosition = TweetNotification.Position.TopRight;
|
Config.NotificationPosition = DesktopNotification.Position.TopRight;
|
||||||
notification.MoveToVisibleLocation();
|
notification.MoveToVisibleLocation();
|
||||||
|
|
||||||
Config.CustomNotificationPosition = notification.Location;
|
Config.CustomNotificationPosition = notification.Location;
|
||||||
|
|
||||||
Config.NotificationPosition = TweetNotification.Position.Custom;
|
Config.NotificationPosition = DesktopNotification.Position.Custom;
|
||||||
notification.MoveToVisibleLocation();
|
notification.MoveToVisibleLocation();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -255,7 +255,7 @@ namespace TweetDuck.Core.Other.Settings{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void trackBarEdgeDistance_ValueChanged(object sender, EventArgs e){
|
private void trackBarEdgeDistance_ValueChanged(object sender, EventArgs e){
|
||||||
labelEdgeDistanceValue.Text = trackBarEdgeDistance.Value+" px";
|
labelEdgeDistanceValue.Text = trackBarEdgeDistance.Value + " px";
|
||||||
Config.NotificationEdgeDistance = trackBarEdgeDistance.Value;
|
Config.NotificationEdgeDistance = trackBarEdgeDistance.Value;
|
||||||
notification.ShowExampleNotification(false);
|
notification.ShowExampleNotification(false);
|
||||||
}
|
}
|
||||||
@@ -265,7 +265,7 @@ namespace TweetDuck.Core.Other.Settings{
|
|||||||
|
|
||||||
private void radioSize_CheckedChanged(object sender, EventArgs e){
|
private void radioSize_CheckedChanged(object sender, EventArgs e){
|
||||||
if (radioSizeAuto.Checked){
|
if (radioSizeAuto.Checked){
|
||||||
Config.NotificationSize = TweetNotification.Size.Auto;
|
Config.NotificationSize = DesktopNotification.Size.Auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
notification.ShowExampleNotification(false);
|
notification.ShowExampleNotification(false);
|
||||||
@@ -276,13 +276,13 @@ namespace TweetDuck.Core.Other.Settings{
|
|||||||
Config.CustomNotificationSize = notification.BrowserSize;
|
Config.CustomNotificationSize = notification.BrowserSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
Config.NotificationSize = TweetNotification.Size.Custom;
|
Config.NotificationSize = DesktopNotification.Size.Custom;
|
||||||
notification.ShowExampleNotification(false);
|
notification.ShowExampleNotification(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void trackBarScrollSpeed_ValueChanged(object sender, EventArgs e){
|
private void trackBarScrollSpeed_ValueChanged(object sender, EventArgs e){
|
||||||
if (trackBarScrollSpeed.AlignValueToTick()){
|
if (trackBarScrollSpeed.AlignValueToTick()){
|
||||||
labelScrollSpeedValue.Text = trackBarScrollSpeed.Value+"%";
|
labelScrollSpeedValue.Text = trackBarScrollSpeed.Value + "%";
|
||||||
Config.NotificationScrollSpeed = trackBarScrollSpeed.Value;
|
Config.NotificationScrollSpeed = trackBarScrollSpeed.Value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -20,7 +20,7 @@ namespace TweetDuck.Core.Other.Settings{
|
|||||||
toolTip.SetToolTip(tbCustomSound, "When empty, the default TweetDeck sound notification is used.");
|
toolTip.SetToolTip(tbCustomSound, "When empty, the default TweetDeck sound notification is used.");
|
||||||
|
|
||||||
trackBarVolume.SetValueSafe(Config.NotificationSoundVolume);
|
trackBarVolume.SetValueSafe(Config.NotificationSoundVolume);
|
||||||
labelVolumeValue.Text = trackBarVolume.Value+"%";
|
labelVolumeValue.Text = trackBarVolume.Value + "%";
|
||||||
|
|
||||||
tbCustomSound.Text = Config.NotificationSoundPath;
|
tbCustomSound.Text = Config.NotificationSoundPath;
|
||||||
tbCustomSound_TextChanged(tbCustomSound, EventArgs.Empty);
|
tbCustomSound_TextChanged(tbCustomSound, EventArgs.Empty);
|
||||||
@@ -83,7 +83,7 @@ namespace TweetDuck.Core.Other.Settings{
|
|||||||
private void trackBarVolume_ValueChanged(object sender, EventArgs e){
|
private void trackBarVolume_ValueChanged(object sender, EventArgs e){
|
||||||
volumeUpdateTimer.Stop();
|
volumeUpdateTimer.Stop();
|
||||||
volumeUpdateTimer.Start();
|
volumeUpdateTimer.Start();
|
||||||
labelVolumeValue.Text = trackBarVolume.Value+"%";
|
labelVolumeValue.Text = trackBarVolume.Value + "%";
|
||||||
}
|
}
|
||||||
|
|
||||||
private void volumeUpdateTimer_Tick(object sender, EventArgs e){
|
private void volumeUpdateTimer_Tick(object sender, EventArgs e){
|
||||||
|
@@ -10,7 +10,7 @@ namespace TweetDuck.Core.Other{
|
|||||||
Disabled, DisplayOnly, MinimizeToTray, CloseToTray, Combined
|
Disabled, DisplayOnly, MinimizeToTray, CloseToTray, Combined
|
||||||
}
|
}
|
||||||
|
|
||||||
private static UserConfig Config => Program.UserConfig;
|
private static UserConfig Config => Program.Config.User;
|
||||||
|
|
||||||
public event EventHandler ClickRestore;
|
public event EventHandler ClickRestore;
|
||||||
public event EventHandler ClickClose;
|
public event EventHandler ClickClose;
|
||||||
@@ -56,6 +56,7 @@ namespace TweetDuck.Core.Other{
|
|||||||
this.notifyIcon.Text = Program.BrandName;
|
this.notifyIcon.Text = Program.BrandName;
|
||||||
|
|
||||||
Config.MuteToggled += Config_MuteToggled;
|
Config.MuteToggled += Config_MuteToggled;
|
||||||
|
Disposed += (sender, args) => Config.MuteToggled -= Config_MuteToggled;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TrayIcon(IContainer container) : this(){
|
public TrayIcon(IContainer container) : this(){
|
||||||
|
@@ -5,17 +5,26 @@ using System.Windows.Forms;
|
|||||||
using CefSharp;
|
using CefSharp;
|
||||||
using CefSharp.WinForms;
|
using CefSharp.WinForms;
|
||||||
using TweetDuck.Configuration;
|
using TweetDuck.Configuration;
|
||||||
|
using TweetDuck.Core.Adapters;
|
||||||
using TweetDuck.Core.Bridge;
|
using TweetDuck.Core.Bridge;
|
||||||
using TweetDuck.Core.Controls;
|
using TweetDuck.Core.Controls;
|
||||||
using TweetDuck.Core.Handling;
|
using TweetDuck.Core.Handling;
|
||||||
using TweetDuck.Core.Handling.General;
|
using TweetDuck.Core.Handling.General;
|
||||||
using TweetDuck.Core.Notification;
|
using TweetDuck.Core.Notification;
|
||||||
using TweetDuck.Core.Other.Interfaces;
|
|
||||||
using TweetDuck.Core.Utils;
|
using TweetDuck.Core.Utils;
|
||||||
using TweetDuck.Resources;
|
using TweetDuck.Plugins;
|
||||||
|
using TweetLib.Core.Features.Plugins;
|
||||||
|
using TweetLib.Core.Features.Plugins.Enums;
|
||||||
|
using TweetLib.Core.Features.Twitter;
|
||||||
|
using TweetLib.Core.Utils;
|
||||||
|
|
||||||
namespace TweetDuck.Core{
|
namespace TweetDuck.Core{
|
||||||
sealed class TweetDeckBrowser : ITweetDeckBrowser, IDisposable{
|
sealed class TweetDeckBrowser : IDisposable{
|
||||||
|
private static UserConfig Config => Program.Config.User;
|
||||||
|
|
||||||
|
private const string ErrorUrl = "http://td/error";
|
||||||
|
private const string TwitterStyleUrl = "https://abs.twimg.com/tduck/css";
|
||||||
|
|
||||||
public bool Ready { get; private set; }
|
public bool Ready { get; private set; }
|
||||||
|
|
||||||
public bool Enabled{
|
public bool Enabled{
|
||||||
@@ -29,27 +38,31 @@ namespace TweetDuck.Core{
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
using(IFrame frame = browser.GetBrowser().MainFrame){
|
using IFrame frame = browser.GetBrowser().MainFrame;
|
||||||
return TwitterUtils.IsTweetDeckWebsite(frame);
|
return TwitterUrls.IsTweetDeck(frame.Url);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly ChromiumWebBrowser browser;
|
private readonly ChromiumWebBrowser browser;
|
||||||
|
private readonly ResourceHandlerFactory resourceHandlerFactory = new ResourceHandlerFactory();
|
||||||
|
|
||||||
private string prevSoundNotificationPath = null;
|
private string prevSoundNotificationPath = null;
|
||||||
|
|
||||||
public TweetDeckBrowser(FormBrowser owner, TweetDeckBridge tdBridge, UpdateBridge updateBridge){
|
public TweetDeckBrowser(FormBrowser owner, PluginManager plugins, TweetDeckBridge tdBridge, UpdateBridge updateBridge){
|
||||||
|
resourceHandlerFactory.RegisterHandler(FormNotificationBase.AppLogo);
|
||||||
|
resourceHandlerFactory.RegisterHandler(TwitterUtils.LoadingSpinner);
|
||||||
|
|
||||||
RequestHandlerBrowser requestHandler = new RequestHandlerBrowser();
|
RequestHandlerBrowser requestHandler = new RequestHandlerBrowser();
|
||||||
|
|
||||||
this.browser = new ChromiumWebBrowser(TwitterUtils.TweetDeckURL){
|
this.browser = new ChromiumWebBrowser(TwitterUrls.TweetDeck){
|
||||||
DialogHandler = new FileDialogHandler(),
|
DialogHandler = new FileDialogHandler(),
|
||||||
DragHandler = new DragHandlerBrowser(requestHandler),
|
DragHandler = new DragHandlerBrowser(requestHandler),
|
||||||
MenuHandler = new ContextMenuBrowser(owner),
|
MenuHandler = new ContextMenuBrowser(owner),
|
||||||
JsDialogHandler = new JavaScriptDialogHandler(),
|
JsDialogHandler = new JavaScriptDialogHandler(),
|
||||||
KeyboardHandler = new KeyboardHandlerBrowser(owner),
|
KeyboardHandler = new KeyboardHandlerBrowser(owner),
|
||||||
LifeSpanHandler = new LifeSpanHandler(),
|
LifeSpanHandler = new LifeSpanHandler(),
|
||||||
RequestHandler = requestHandler
|
RequestHandler = requestHandler,
|
||||||
|
ResourceHandlerFactory = resourceHandlerFactory
|
||||||
};
|
};
|
||||||
|
|
||||||
this.browser.LoadingStateChanged += browser_LoadingStateChanged;
|
this.browser.LoadingStateChanged += browser_LoadingStateChanged;
|
||||||
@@ -63,15 +76,13 @@ namespace TweetDuck.Core{
|
|||||||
this.browser.BrowserSettings.BackgroundColor = (uint)TwitterUtils.BackgroundColor.ToArgb();
|
this.browser.BrowserSettings.BackgroundColor = (uint)TwitterUtils.BackgroundColor.ToArgb();
|
||||||
this.browser.Dock = DockStyle.None;
|
this.browser.Dock = DockStyle.None;
|
||||||
this.browser.Location = ControlExtensions.InvisibleLocation;
|
this.browser.Location = ControlExtensions.InvisibleLocation;
|
||||||
|
this.browser.SetupZoomEvents();
|
||||||
this.browser.SetupResourceHandler(TweetNotification.AppLogo);
|
|
||||||
this.browser.SetupResourceHandler(TwitterUtils.LoadingSpinner);
|
|
||||||
|
|
||||||
owner.Controls.Add(browser);
|
|
||||||
|
|
||||||
Program.UserConfig.MuteToggled += UserConfig_MuteToggled;
|
owner.Controls.Add(browser);
|
||||||
Program.UserConfig.ZoomLevelChanged += UserConfig_ZoomLevelChanged;
|
plugins.Register(PluginEnvironment.Browser, new PluginDispatcher(browser));
|
||||||
Program.UserConfig.SoundNotificationChanged += UserConfig_SoundNotificationInfoChanged;
|
|
||||||
|
Config.MuteToggled += Config_MuteToggled;
|
||||||
|
Config.SoundNotificationChanged += Config_SoundNotificationInfoChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
// setup and management
|
// setup and management
|
||||||
@@ -89,30 +100,11 @@ namespace TweetDuck.Core{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose(){
|
public void Dispose(){
|
||||||
Program.UserConfig.MuteToggled -= UserConfig_MuteToggled;
|
Config.MuteToggled -= Config_MuteToggled;
|
||||||
Program.UserConfig.ZoomLevelChanged -= UserConfig_ZoomLevelChanged;
|
Config.SoundNotificationChanged -= Config_SoundNotificationInfoChanged;
|
||||||
Program.UserConfig.SoundNotificationChanged -= UserConfig_SoundNotificationInfoChanged;
|
|
||||||
|
|
||||||
browser.Dispose();
|
browser.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ITweetDeckBrowser.RegisterBridge(string name, object obj){
|
|
||||||
browser.RegisterAsyncJsObject(name, obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ITweetDeckBrowser.OnFrameLoaded(Action<IFrame> callback){
|
|
||||||
browser.FrameLoadEnd += (sender, args) => {
|
|
||||||
IFrame frame = args.Frame;
|
|
||||||
|
|
||||||
if (frame.IsMain && TwitterUtils.IsTweetDeckWebsite(frame)){
|
|
||||||
callback(frame);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
void ITweetDeckBrowser.ExecuteFunction(string name, params object[] args){
|
|
||||||
browser.ExecuteScriptAsync(name, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
// event handlers
|
// event handlers
|
||||||
|
|
||||||
@@ -131,43 +123,50 @@ namespace TweetDuck.Core{
|
|||||||
IFrame frame = e.Frame;
|
IFrame frame = e.Frame;
|
||||||
|
|
||||||
if (frame.IsMain){
|
if (frame.IsMain){
|
||||||
if (Program.UserConfig.ZoomLevel != 100){
|
string url = frame.Url;
|
||||||
BrowserUtils.SetZoomLevel(browser.GetBrowser(), Program.UserConfig.ZoomLevel);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (TwitterUtils.IsTwitterWebsite(frame)){
|
if (TwitterUrls.IsTwitter(url)){
|
||||||
ScriptLoader.ExecuteFile(frame, "twitter.js", browser);
|
string css = Program.Resources.Load("styles/twitter.css");
|
||||||
|
resourceHandlerFactory.RegisterHandler(TwitterStyleUrl, ResourceHandler.FromString(css, mimeType: "text/css"));
|
||||||
|
|
||||||
|
CefScriptExecutor.RunFile(frame, "twitter.js");
|
||||||
}
|
}
|
||||||
|
|
||||||
frame.ExecuteJavaScriptAsync(TwitterUtils.BackgroundColorOverride);
|
if (!TwitterUrls.IsTwitterLogin2Factor(url)){
|
||||||
|
frame.ExecuteJavaScriptAsync(TwitterUtils.BackgroundColorOverride);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
|
private void browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
|
||||||
IFrame frame = e.Frame;
|
IFrame frame = e.Frame;
|
||||||
|
string url = frame.Url;
|
||||||
|
|
||||||
if (frame.IsMain){
|
if (frame.IsMain){
|
||||||
if (TwitterUtils.IsTweetDeckWebsite(frame)){
|
if (TwitterUrls.IsTweetDeck(url)){
|
||||||
UpdateProperties();
|
UpdateProperties();
|
||||||
TweetDeckBridge.RestoreSessionData(frame);
|
CefScriptExecutor.RunFile(frame, "code.js");
|
||||||
ScriptLoader.ExecuteFile(frame, "code.js", browser);
|
|
||||||
|
|
||||||
InjectBrowserCSS();
|
InjectBrowserCSS();
|
||||||
ReinjectCustomCSS(Program.UserConfig.CustomBrowserCSS);
|
ReinjectCustomCSS(Config.CustomBrowserCSS);
|
||||||
UserConfig_SoundNotificationInfoChanged(null, EventArgs.Empty);
|
Config_SoundNotificationInfoChanged(null, EventArgs.Empty);
|
||||||
|
|
||||||
TweetDeckBridge.ResetStaticProperties();
|
TweetDeckBridge.ResetStaticProperties();
|
||||||
|
|
||||||
if (Arguments.HasFlag(Arguments.ArgIgnoreGDPR)){
|
if (Arguments.HasFlag(Arguments.ArgIgnoreGDPR)){
|
||||||
ScriptLoader.ExecuteScript(frame, "TD.storage.Account.prototype.requiresConsent = function(){ return false; }", "gen:gdpr");
|
CefScriptExecutor.RunScript(frame, "TD.storage.Account.prototype.requiresConsent = function(){ return false; }", "gen:gdpr");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Program.UserConfig.FirstRun){
|
if (Config.FirstRun){
|
||||||
ScriptLoader.ExecuteFile(frame, "introduction.js", browser);
|
CefScriptExecutor.RunFile(frame, "introduction.js");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ScriptLoader.ExecuteFile(frame, "update.js", browser);
|
CefScriptExecutor.RunFile(frame, "update.js");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url == ErrorUrl){
|
||||||
|
resourceHandlerFactory.UnregisterHandler(ErrorUrl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,32 +176,40 @@ namespace TweetDuck.Core{
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!e.FailedUrl.StartsWith("http://td/", StringComparison.Ordinal)){
|
if (!e.FailedUrl.StartsWith("http://td/", StringComparison.Ordinal)){
|
||||||
string errorPage = ScriptLoader.LoadResourceSilent("pages/error.html");
|
string errorPage = Program.Resources.LoadSilent("pages/error.html");
|
||||||
|
|
||||||
if (errorPage != null){
|
if (errorPage != null){
|
||||||
browser.LoadHtml(errorPage.Replace("{err}", BrowserUtils.GetErrorName(e.ErrorCode)), "http://td/error");
|
string errorName = Enum.GetName(typeof(CefErrorCode), e.ErrorCode);
|
||||||
|
string errorTitle = StringUtils.ConvertPascalCaseToScreamingSnakeCase(errorName ?? string.Empty);
|
||||||
|
|
||||||
|
resourceHandlerFactory.RegisterHandler(ErrorUrl, ResourceHandler.FromString(errorPage.Replace("{err}", errorTitle)));
|
||||||
|
browser.Load(ErrorUrl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UserConfig_MuteToggled(object sender, EventArgs e){
|
private void Config_MuteToggled(object sender, EventArgs e){
|
||||||
UpdateProperties();
|
UpdateProperties();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UserConfig_ZoomLevelChanged(object sender, EventArgs e){
|
private void Config_SoundNotificationInfoChanged(object sender, EventArgs e){
|
||||||
BrowserUtils.SetZoomLevel(browser.GetBrowser(), Program.UserConfig.ZoomLevel);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UserConfig_SoundNotificationInfoChanged(object sender, EventArgs e){
|
|
||||||
const string soundUrl = "https://ton.twimg.com/tduck/updatesnd";
|
const string soundUrl = "https://ton.twimg.com/tduck/updatesnd";
|
||||||
bool hasCustomSound = Program.UserConfig.IsCustomSoundNotificationSet;
|
|
||||||
|
|
||||||
if (prevSoundNotificationPath != Program.UserConfig.NotificationSoundPath){
|
bool hasCustomSound = Config.IsCustomSoundNotificationSet;
|
||||||
browser.SetupResourceHandler(soundUrl, hasCustomSound ? SoundNotification.CreateFileHandler(Program.UserConfig.NotificationSoundPath) : null);
|
string newNotificationPath = Config.NotificationSoundPath;
|
||||||
prevSoundNotificationPath = Program.UserConfig.NotificationSoundPath;
|
|
||||||
|
if (prevSoundNotificationPath != newNotificationPath){
|
||||||
|
prevSoundNotificationPath = newNotificationPath;
|
||||||
|
|
||||||
|
if (hasCustomSound){
|
||||||
|
resourceHandlerFactory.RegisterHandler(soundUrl, SoundNotification.CreateFileHandler(newNotificationPath));
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
resourceHandlerFactory.UnregisterHandler(soundUrl);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
browser.ExecuteScriptAsync("TDGF_setSoundNotificationData", hasCustomSound, Program.UserConfig.NotificationSoundVolume);
|
browser.ExecuteScriptAsync("TDGF_setSoundNotificationData", hasCustomSound, Config.NotificationSoundVolume);
|
||||||
}
|
}
|
||||||
|
|
||||||
// external handling
|
// external handling
|
||||||
@@ -218,7 +225,7 @@ namespace TweetDuck.Core{
|
|||||||
// javascript calls
|
// javascript calls
|
||||||
|
|
||||||
public void ReloadToTweetDeck(){
|
public void ReloadToTweetDeck(){
|
||||||
browser.ExecuteScriptAsync($"if(window.TDGF_reload)window.TDGF_reload();else window.location.href='{TwitterUtils.TweetDeckURL}'");
|
browser.ExecuteScriptAsync($"if(window.TDGF_reload)window.TDGF_reload();else window.location.href='{TwitterUrls.TweetDeck}'");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateProperties(){
|
public void UpdateProperties(){
|
||||||
@@ -226,7 +233,7 @@ namespace TweetDuck.Core{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void InjectBrowserCSS(){
|
public void InjectBrowserCSS(){
|
||||||
browser.ExecuteScriptAsync("TDGF_injectBrowserCSS", ScriptLoader.LoadResource("styles/browser.css", browser)?.TrimEnd() ?? string.Empty);
|
browser.ExecuteScriptAsync("TDGF_injectBrowserCSS", Program.Resources.Load("styles/browser.css")?.TrimEnd() ?? string.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ReinjectCustomCSS(string css){
|
public void ReinjectCustomCSS(string css){
|
||||||
|
@@ -3,30 +3,33 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Net;
|
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using CefSharp.WinForms;
|
using CefSharp.WinForms;
|
||||||
|
using TweetDuck.Configuration;
|
||||||
using TweetDuck.Core.Other;
|
using TweetDuck.Core.Other;
|
||||||
using TweetDuck.Data;
|
using TweetLib.Core.Features.Twitter;
|
||||||
|
|
||||||
namespace TweetDuck.Core.Utils{
|
namespace TweetDuck.Core.Utils{
|
||||||
static class BrowserUtils{
|
static class BrowserUtils{
|
||||||
public static string UserAgentVanilla => Program.BrandName+" "+Application.ProductVersion;
|
public static string UserAgentVanilla => Program.BrandName + " " + Application.ProductVersion;
|
||||||
public static string UserAgentChrome => "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/"+Cef.ChromiumVersion+" Safari/537.36";
|
public static string UserAgentChrome => "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/" + Cef.ChromiumVersion + " Safari/537.36";
|
||||||
|
|
||||||
public static readonly bool HasDevTools = File.Exists(Path.Combine(Program.ProgramPath, "devtools_resources.pak"));
|
public static readonly bool HasDevTools = File.Exists(Path.Combine(Program.ProgramPath, "devtools_resources.pak"));
|
||||||
|
|
||||||
|
private static UserConfig Config => Program.Config.User;
|
||||||
|
private static SystemConfig SysConfig => Program.Config.System;
|
||||||
|
|
||||||
public static void SetupCefArgs(IDictionary<string, string> args){
|
public static void SetupCefArgs(IDictionary<string, string> args){
|
||||||
if (!Program.SystemConfig.HardwareAcceleration){
|
if (!SysConfig.HardwareAcceleration){
|
||||||
args["disable-gpu"] = "1";
|
args["disable-gpu"] = "1";
|
||||||
args["disable-gpu-vsync"] = "1";
|
args["disable-gpu-vsync"] = "1";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Program.UserConfig.EnableSmoothScrolling){
|
if (Config.EnableSmoothScrolling){
|
||||||
args["disable-threaded-scrolling"] = "1";
|
args["disable-threaded-scrolling"] = "1";
|
||||||
|
|
||||||
if (args.TryGetValue("disable-features", out string disabledFeatures)){
|
if (args.TryGetValue("disable-features", out string disabledFeatures)){
|
||||||
args["disable-features"] = "TouchpadAndWheelScrollLatching,"+disabledFeatures;
|
args["disable-features"] = "TouchpadAndWheelScrollLatching," + disabledFeatures;
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
args["disable-features"] = "TouchpadAndWheelScrollLatching";
|
args["disable-features"] = "TouchpadAndWheelScrollLatching";
|
||||||
@@ -36,7 +39,7 @@ namespace TweetDuck.Core.Utils{
|
|||||||
args["disable-smooth-scrolling"] = "1";
|
args["disable-smooth-scrolling"] = "1";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Program.UserConfig.EnableTouchAdjustment){
|
if (!Config.EnableTouchAdjustment){
|
||||||
args["disable-touch-adjustment"] = "1";
|
args["disable-touch-adjustment"] = "1";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,7 +48,7 @@ namespace TweetDuck.Core.Utils{
|
|||||||
args["enable-system-flash"] = "0";
|
args["enable-system-flash"] = "0";
|
||||||
|
|
||||||
if (args.TryGetValue("js-flags", out string jsFlags)){
|
if (args.TryGetValue("js-flags", out string jsFlags)){
|
||||||
args["js-flags"] = "--expose-gc "+jsFlags;
|
args["js-flags"] = "--expose-gc " + jsFlags;
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
args["js-flags"] = "--expose-gc";
|
args["js-flags"] = "--expose-gc";
|
||||||
@@ -56,49 +59,35 @@ namespace TweetDuck.Core.Utils{
|
|||||||
return (ChromiumWebBrowser)browserControl;
|
return (ChromiumWebBrowser)browserControl;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void SetupResourceHandler(this ChromiumWebBrowser browser, string url, IResourceHandler handler){
|
public static void SetupZoomEvents(this ChromiumWebBrowser browser){
|
||||||
DefaultResourceHandlerFactory factory = (DefaultResourceHandlerFactory)browser.ResourceHandlerFactory;
|
static void SetZoomLevel(IBrowserHost host, int percentage){
|
||||||
|
host.SetZoomLevel(Math.Log(percentage / 100.0, 1.2));
|
||||||
if (handler == null){
|
|
||||||
factory.UnregisterHandler(url);
|
|
||||||
}
|
}
|
||||||
else{
|
|
||||||
factory.RegisterHandler(url, handler);
|
void UpdateZoomLevel(object sender, EventArgs args){
|
||||||
|
SetZoomLevel(browser.GetBrowserHost(), Config.ZoomLevel);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public static void SetupResourceHandler(this ChromiumWebBrowser browser, ResourceLink resource){
|
Config.ZoomLevelChanged += UpdateZoomLevel;
|
||||||
browser.SetupResourceHandler(resource.Url, resource.Handler);
|
browser.Disposed += (sender, args) => Config.ZoomLevelChanged -= UpdateZoomLevel;
|
||||||
}
|
|
||||||
|
|
||||||
private const string TwitterTrackingUrl = "t.co";
|
browser.FrameLoadStart += (sender, args) => {
|
||||||
|
if (args.Frame.IsMain && Config.ZoomLevel != 100){
|
||||||
public enum UrlCheckResult{
|
SetZoomLevel(args.Browser.GetHost(), Config.ZoomLevel);
|
||||||
Invalid, Tracking, Fine
|
|
||||||
}
|
|
||||||
|
|
||||||
public static UrlCheckResult CheckUrl(string url){
|
|
||||||
if (Uri.TryCreate(url, UriKind.Absolute, out Uri uri)){
|
|
||||||
string scheme = uri.Scheme;
|
|
||||||
|
|
||||||
if (scheme == Uri.UriSchemeHttps || scheme == Uri.UriSchemeHttp || scheme == Uri.UriSchemeFtp || scheme == Uri.UriSchemeMailto){
|
|
||||||
return uri.Host == TwitterTrackingUrl ? UrlCheckResult.Tracking : UrlCheckResult.Fine;
|
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
return UrlCheckResult.Invalid;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void OpenExternalBrowser(string url){
|
public static void OpenExternalBrowser(string url){
|
||||||
if (string.IsNullOrWhiteSpace(url))return;
|
if (string.IsNullOrWhiteSpace(url))return;
|
||||||
|
|
||||||
switch(CheckUrl(url)){
|
switch(TwitterUrls.Check(url)){
|
||||||
case UrlCheckResult.Fine:
|
case TwitterUrls.UrlType.Fine:
|
||||||
if (FormGuide.CheckGuideUrl(url, out string hash)){
|
if (FormGuide.CheckGuideUrl(url, out string hash)){
|
||||||
FormGuide.Show(hash);
|
FormGuide.Show(hash);
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
string browserPath = Program.UserConfig.BrowserPath;
|
string browserPath = Config.BrowserPath;
|
||||||
|
|
||||||
if (browserPath == null || !File.Exists(browserPath)){
|
if (browserPath == null || !File.Exists(browserPath)){
|
||||||
WindowsUtils.OpenAssociatedProgram(url);
|
WindowsUtils.OpenAssociatedProgram(url);
|
||||||
@@ -114,12 +103,12 @@ namespace TweetDuck.Core.Utils{
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case UrlCheckResult.Tracking:
|
case TwitterUrls.UrlType.Tracking:
|
||||||
if (Program.UserConfig.IgnoreTrackingUrlWarning){
|
if (Config.IgnoreTrackingUrlWarning){
|
||||||
goto case UrlCheckResult.Fine;
|
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)){
|
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.No, DialogResult.No, ControlType.Cancel | ControlType.Focused);
|
||||||
form.AddButton(FormMessage.Yes, DialogResult.Yes, ControlType.Accept);
|
form.AddButton(FormMessage.Yes, DialogResult.Yes, ControlType.Accept);
|
||||||
form.AddButton("Always Visit", DialogResult.Ignore);
|
form.AddButton("Always Visit", DialogResult.Ignore);
|
||||||
@@ -127,27 +116,29 @@ namespace TweetDuck.Core.Utils{
|
|||||||
DialogResult result = form.ShowDialog();
|
DialogResult result = form.ShowDialog();
|
||||||
|
|
||||||
if (result == DialogResult.Ignore){
|
if (result == DialogResult.Ignore){
|
||||||
Program.UserConfig.IgnoreTrackingUrlWarning = true;
|
Config.IgnoreTrackingUrlWarning = true;
|
||||||
Program.UserConfig.Save();
|
Config.Save();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result == DialogResult.Ignore || result == DialogResult.Yes){
|
if (result == DialogResult.Ignore || result == DialogResult.Yes){
|
||||||
goto case UrlCheckResult.Fine;
|
goto case TwitterUrls.UrlType.Fine;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case UrlCheckResult.Invalid:
|
case TwitterUrls.UrlType.Invalid:
|
||||||
FormMessage.Warning("Blocked URL", "A potentially malicious URL was blocked from opening:\n"+url, FormMessage.OK);
|
FormMessage.Warning("Blocked URL", "A potentially malicious URL was blocked from opening:\n" + url, FormMessage.OK);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void OpenExternalSearch(string query){
|
public static void OpenExternalSearch(string query){
|
||||||
if (string.IsNullOrWhiteSpace(query))return;
|
if (string.IsNullOrWhiteSpace(query)){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
string searchUrl = Program.UserConfig.SearchEngineUrl;
|
string searchUrl = Config.SearchEngineUrl;
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(searchUrl)){
|
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)){
|
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)){
|
||||||
@@ -160,63 +151,19 @@ namespace TweetDuck.Core.Utils{
|
|||||||
if (settings == null)return;
|
if (settings == null)return;
|
||||||
|
|
||||||
settings.FormClosed += (sender, args) => {
|
settings.FormClosed += (sender, args) => {
|
||||||
if (args.CloseReason == CloseReason.UserClosing && Program.UserConfig.SearchEngineUrl != searchUrl){
|
if (args.CloseReason == CloseReason.UserClosing && Config.SearchEngineUrl != searchUrl){
|
||||||
OpenExternalSearch(query);
|
OpenExternalSearch(query);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
OpenExternalBrowser(searchUrl+Uri.EscapeUriString(query));
|
OpenExternalBrowser(searchUrl + Uri.EscapeUriString(query));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GetFileNameFromUrl(string url){
|
|
||||||
string file = Path.GetFileName(new Uri(url).AbsolutePath);
|
|
||||||
return string.IsNullOrEmpty(file) ? null : file;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string GetErrorName(CefErrorCode code){
|
|
||||||
return StringUtils.ConvertPascalCaseToScreamingSnakeCase(Enum.GetName(typeof(CefErrorCode), code) ?? string.Empty);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static WebClient CreateWebClient(){
|
|
||||||
WindowsUtils.EnsureTLS12();
|
|
||||||
|
|
||||||
WebClient client = new WebClient{ Proxy = null };
|
|
||||||
client.Headers[HttpRequestHeader.UserAgent] = UserAgentVanilla;
|
|
||||||
return client;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static WebClient DownloadFileAsync(string url, string target, Action onSuccess, Action<Exception> onFailure){
|
|
||||||
WebClient client = CreateWebClient();
|
|
||||||
|
|
||||||
client.DownloadFileCompleted += (sender, args) => {
|
|
||||||
if (args.Cancelled){
|
|
||||||
try{
|
|
||||||
File.Delete(target);
|
|
||||||
}catch{
|
|
||||||
// didn't want it deleted anyways
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (args.Error != null){
|
|
||||||
onFailure?.Invoke(args.Error);
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
onSuccess?.Invoke();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
client.DownloadFileAsync(new Uri(url), target);
|
|
||||||
return client;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int Scale(int baseValue, double scaleFactor){
|
public static int Scale(int baseValue, double scaleFactor){
|
||||||
return (int)Math.Round(baseValue*scaleFactor);
|
return (int)Math.Round(baseValue * scaleFactor);
|
||||||
}
|
|
||||||
|
|
||||||
public static void SetZoomLevel(IBrowser browser, int percentage){
|
|
||||||
browser.GetHost().SetZoomLevel(Math.Log(percentage/100.0, 1.2));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -133,7 +133,7 @@ namespace TweetDuck.Core.Utils{
|
|||||||
ticks = (uint)Environment.TickCount;
|
ticks = (uint)Environment.TickCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
int seconds = (int)Math.Floor(TimeSpan.FromMilliseconds(ticks-info.dwTime).TotalSeconds);
|
int seconds = (int)Math.Floor(TimeSpan.FromMilliseconds(ticks - info.dwTime).TotalSeconds);
|
||||||
return Math.Max(0, seconds); // ignore rollover after several weeks of uptime
|
return Math.Max(0, seconds); // ignore rollover after several weeks of uptime
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -2,67 +2,53 @@
|
|||||||
using CefSharp;
|
using CefSharp;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
|
using TweetDuck.Core.Management;
|
||||||
using TweetDuck.Core.Other;
|
using TweetDuck.Core.Other;
|
||||||
using TweetDuck.Data;
|
using TweetDuck.Data;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using TweetLib.Core.Features.Twitter;
|
||||||
|
using TweetLib.Core.Utils;
|
||||||
|
using Cookie = CefSharp.Cookie;
|
||||||
|
|
||||||
namespace TweetDuck.Core.Utils{
|
namespace TweetDuck.Core.Utils{
|
||||||
static class TwitterUtils{
|
static class TwitterUtils{
|
||||||
public const string TweetDeckURL = "https://tweetdeck.twitter.com";
|
|
||||||
|
|
||||||
public static readonly Color BackgroundColor = Color.FromArgb(28, 99, 153);
|
public static readonly Color BackgroundColor = Color.FromArgb(28, 99, 153);
|
||||||
public const string BackgroundColorOverride = "setTimeout(function f(){let h=document.head;if(!h){setTimeout(f,5);return;}let e=document.createElement('style');e.innerHTML='body,body::before{background:#1c6399!important}';h.appendChild(e);},1)";
|
public const string BackgroundColorOverride = "setTimeout(function f(){let h=document.head;if(!h){setTimeout(f,5);return;}let e=document.createElement('style');e.innerHTML='body,body::before{background:#1c6399!important;margin:0}';h.appendChild(e);},1)";
|
||||||
|
|
||||||
public static readonly ResourceLink LoadingSpinner = new ResourceLink("https://ton.twimg.com/tduck/spinner", ResourceHandler.FromByteArray(Properties.Resources.spinner, "image/apng"));
|
public static readonly ResourceLink LoadingSpinner = new ResourceLink("https://ton.twimg.com/tduck/spinner", ResourceHandler.FromByteArray(Properties.Resources.spinner, "image/apng"));
|
||||||
|
|
||||||
private static readonly Lazy<Regex> RegexAccountLazy = new Lazy<Regex>(() => new Regex(@"^https?://twitter\.com/(?!signup$|tos$|privacy$|search$|search-)([^/?]+)/?$", RegexOptions.Compiled), false);
|
|
||||||
public static Regex RegexAccount => RegexAccountLazy.Value;
|
|
||||||
|
|
||||||
public static readonly string[] DictionaryWords = {
|
public static readonly string[] DictionaryWords = {
|
||||||
"tweetdeck", "TweetDeck", "tweetduck", "TweetDuck", "TD"
|
"tweetdeck", "TweetDeck", "tweetduck", "TweetDuck", "TD"
|
||||||
};
|
};
|
||||||
|
|
||||||
public static readonly string[] ValidImageExtensions = {
|
public static void ViewImage(string url, ImageQuality quality){
|
||||||
".jpg", ".jpeg", ".png", ".gif"
|
static void ViewImageInternal(string path){
|
||||||
};
|
string ext = Path.GetExtension(path);
|
||||||
|
|
||||||
public enum ImageQuality{
|
if (ImageUrl.ValidExtensions.Contains(ext)){
|
||||||
Default, Orig
|
WindowsUtils.OpenAssociatedProgram(path);
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsTweetDeckWebsite(IFrame frame){
|
|
||||||
return frame.Url.Contains("//tweetdeck.twitter.com/");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsTwitterWebsite(IFrame frame){
|
|
||||||
return frame.Url.Contains("//twitter.com/");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string ExtractMediaBaseLink(string url){
|
|
||||||
int slash = url.LastIndexOf('/');
|
|
||||||
return slash == -1 ? url : StringUtils.ExtractBefore(url, ':', slash);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string GetMediaLink(string url, ImageQuality quality){
|
|
||||||
if (quality == ImageQuality.Orig){
|
|
||||||
string result = ExtractMediaBaseLink(url);
|
|
||||||
|
|
||||||
if (result != url || url.Contains("//pbs.twimg.com/media/")){
|
|
||||||
result += ":orig";
|
|
||||||
}
|
}
|
||||||
|
else{
|
||||||
|
FormMessage.Error("Image Download", "Invalid file extension " + ext, FormMessage.OK);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
string file = Path.Combine(BrowserCache.CacheFolder, TwitterUrls.GetImageFileName(url) ?? Path.GetRandomFileName());
|
||||||
|
|
||||||
|
if (FileUtils.FileExistsAndNotEmpty(file)){
|
||||||
|
ViewImageInternal(file);
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
return url;
|
DownloadFileAuth(TwitterUrls.GetMediaLink(url, quality), file, () => {
|
||||||
|
ViewImageInternal(file);
|
||||||
|
}, ex => {
|
||||||
|
FormMessage.Error("Image Download", "An error occurred while downloading the image: " + ex.Message, FormMessage.OK);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GetImageFileName(string url){
|
|
||||||
return BrowserUtils.GetFileNameFromUrl(ExtractMediaBaseLink(url));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void DownloadImage(string url, string username, ImageQuality quality){
|
public static void DownloadImage(string url, string username, ImageQuality quality){
|
||||||
DownloadImages(new string[]{ url }, username, quality);
|
DownloadImages(new string[]{ url }, username, quality);
|
||||||
@@ -73,33 +59,33 @@ namespace TweetDuck.Core.Utils{
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
string firstImageLink = GetMediaLink(urls[0], quality);
|
string firstImageLink = TwitterUrls.GetMediaLink(urls[0], quality);
|
||||||
int qualityIndex = firstImageLink.IndexOf(':', firstImageLink.LastIndexOf('/'));
|
int qualityIndex = firstImageLink.IndexOf(':', firstImageLink.LastIndexOf('/'));
|
||||||
|
|
||||||
string filename = GetImageFileName(firstImageLink);
|
string filename = TwitterUrls.GetImageFileName(firstImageLink);
|
||||||
string ext = Path.GetExtension(filename); // includes dot
|
string ext = Path.GetExtension(filename); // includes dot
|
||||||
|
|
||||||
using(SaveFileDialog dialog = new SaveFileDialog{
|
using(SaveFileDialog dialog = new SaveFileDialog{
|
||||||
AutoUpgradeEnabled = true,
|
AutoUpgradeEnabled = true,
|
||||||
OverwritePrompt = urls.Length == 1,
|
OverwritePrompt = urls.Length == 1,
|
||||||
Title = "Save Image",
|
Title = "Save Image",
|
||||||
FileName = qualityIndex == -1 ? filename : $"{username} {Path.ChangeExtension(filename, null)} {firstImageLink.Substring(qualityIndex+1)}".Trim()+ext,
|
FileName = qualityIndex == -1 ? filename : $"{username} {Path.ChangeExtension(filename, null)} {firstImageLink.Substring(qualityIndex + 1)}".Trim() + ext,
|
||||||
Filter = (urls.Length == 1 ? "Image" : "Images")+(string.IsNullOrEmpty(ext) ? " (unknown)|*.*" : $" (*{ext})|*{ext}")
|
Filter = (urls.Length == 1 ? "Image" : "Images") + (string.IsNullOrEmpty(ext) ? " (unknown)|*.*" : $" (*{ext})|*{ext}")
|
||||||
}){
|
}){
|
||||||
if (dialog.ShowDialog() == DialogResult.OK){
|
if (dialog.ShowDialog() == DialogResult.OK){
|
||||||
void OnFailure(Exception ex){
|
static void OnFailure(Exception ex){
|
||||||
FormMessage.Error("Image Download", "An error occurred while downloading the image: "+ex.Message, FormMessage.OK);
|
FormMessage.Error("Image Download", "An error occurred while downloading the image: " + ex.Message, FormMessage.OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (urls.Length == 1){
|
if (urls.Length == 1){
|
||||||
BrowserUtils.DownloadFileAsync(firstImageLink, dialog.FileName, null, OnFailure);
|
DownloadFileAuth(firstImageLink, dialog.FileName, null, OnFailure);
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
string pathBase = Path.ChangeExtension(dialog.FileName, null);
|
string pathBase = Path.ChangeExtension(dialog.FileName, null);
|
||||||
string pathExt = Path.GetExtension(dialog.FileName);
|
string pathExt = Path.GetExtension(dialog.FileName);
|
||||||
|
|
||||||
for(int index = 0; index < urls.Length; index++){
|
for(int index = 0; index < urls.Length; index++){
|
||||||
BrowserUtils.DownloadFileAsync(GetMediaLink(urls[index], quality), $"{pathBase} {index+1}{pathExt}", null, OnFailure);
|
DownloadFileAuth(TwitterUrls.GetMediaLink(urls[index], quality), $"{pathBase} {index + 1}{pathExt}", null, OnFailure);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -107,7 +93,7 @@ namespace TweetDuck.Core.Utils{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void DownloadVideo(string url, string username){
|
public static void DownloadVideo(string url, string username){
|
||||||
string filename = BrowserUtils.GetFileNameFromUrl(url);
|
string filename = TwitterUrls.GetFileNameFromUrl(url);
|
||||||
string ext = Path.GetExtension(filename);
|
string ext = Path.GetExtension(filename);
|
||||||
|
|
||||||
using(SaveFileDialog dialog = new SaveFileDialog{
|
using(SaveFileDialog dialog = new SaveFileDialog{
|
||||||
@@ -115,14 +101,39 @@ namespace TweetDuck.Core.Utils{
|
|||||||
OverwritePrompt = true,
|
OverwritePrompt = true,
|
||||||
Title = "Save Video",
|
Title = "Save Video",
|
||||||
FileName = string.IsNullOrEmpty(username) ? filename : $"{username} {filename}".TrimStart(),
|
FileName = string.IsNullOrEmpty(username) ? filename : $"{username} {filename}".TrimStart(),
|
||||||
Filter = "Video"+(string.IsNullOrEmpty(ext) ? " (unknown)|*.*" : $" (*{ext})|*{ext}")
|
Filter = "Video" + (string.IsNullOrEmpty(ext) ? " (unknown)|*.*" : $" (*{ext})|*{ext}")
|
||||||
}){
|
}){
|
||||||
if (dialog.ShowDialog() == DialogResult.OK){
|
if (dialog.ShowDialog() == DialogResult.OK){
|
||||||
BrowserUtils.DownloadFileAsync(url, dialog.FileName, null, ex => {
|
DownloadFileAuth(url, dialog.FileName, null, ex => {
|
||||||
FormMessage.Error("Video Download", "An error occurred while downloading the video: "+ex.Message, FormMessage.OK);
|
FormMessage.Error("Video Download", "An error occurred while downloading the video: " + ex.Message, FormMessage.OK);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void DownloadFileAuth(string url, string target, Action onSuccess, Action<Exception> onFailure){
|
||||||
|
const string AuthCookieName = "auth_token";
|
||||||
|
|
||||||
|
TaskScheduler scheduler = TaskScheduler.FromCurrentSynchronizationContext();
|
||||||
|
|
||||||
|
using(ICookieManager cookies = Cef.GetGlobalCookieManager()){
|
||||||
|
cookies.VisitUrlCookiesAsync(url, true).ContinueWith(task => {
|
||||||
|
string cookieStr = null;
|
||||||
|
|
||||||
|
if (task.Status == TaskStatus.RanToCompletion){
|
||||||
|
Cookie found = task.Result?.Find(cookie => cookie.Name == AuthCookieName); // the list may be null
|
||||||
|
|
||||||
|
if (found != null){
|
||||||
|
cookieStr = $"{found.Name}={found.Value}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WebClient client = WebUtils.NewClient(BrowserUtils.UserAgentChrome);
|
||||||
|
client.Headers[HttpRequestHeader.Cookie] = cookieStr;
|
||||||
|
client.DownloadFileCompleted += WebUtils.FileDownloadCallback(target, onSuccess, onFailure);
|
||||||
|
client.DownloadFileAsync(new Uri(url), target);
|
||||||
|
}, scheduler);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -3,7 +3,6 @@ using System.Collections.Generic;
|
|||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Net;
|
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
@@ -12,58 +11,17 @@ using Microsoft.Win32;
|
|||||||
|
|
||||||
namespace TweetDuck.Core.Utils{
|
namespace TweetDuck.Core.Utils{
|
||||||
static class WindowsUtils{
|
static class WindowsUtils{
|
||||||
|
private static readonly bool IsWindows8OrNewer = OSVersionEquals(major: 6, minor: 2); // windows 8/10
|
||||||
|
|
||||||
|
public static bool ShouldAvoidToolWindow { get; } = IsWindows8OrNewer;
|
||||||
|
public static bool IsAeroEnabled => IsWindows8OrNewer || (NativeMethods.DwmIsCompositionEnabled(out bool isCompositionEnabled) == 0 && isCompositionEnabled);
|
||||||
|
|
||||||
private static readonly Lazy<Regex> RegexStripHtmlStyles = new Lazy<Regex>(() => new Regex(@"\s?(?:style|class)="".*?"""), false);
|
private static readonly Lazy<Regex> RegexStripHtmlStyles = new Lazy<Regex>(() => new Regex(@"\s?(?:style|class)="".*?"""), false);
|
||||||
private static readonly Lazy<Regex> RegexOffsetClipboardHtml = new Lazy<Regex>(() => new Regex(@"(?<=EndHTML:|EndFragment:)(\d+)"), false);
|
private static readonly Lazy<Regex> RegexOffsetClipboardHtml = new Lazy<Regex>(() => new Regex(@"(?<=EndHTML:|EndFragment:)(\d+)"), false);
|
||||||
|
|
||||||
private static readonly bool IsWindows8OrNewer;
|
private static bool OSVersionEquals(int major, int minor){
|
||||||
private static bool HasMicrosoftBeenBroughtTo2008Yet;
|
|
||||||
|
|
||||||
public static int CurrentProcessID { get; }
|
|
||||||
public static bool ShouldAvoidToolWindow { get; }
|
|
||||||
public static bool IsAeroEnabled => IsWindows8OrNewer || (NativeMethods.DwmIsCompositionEnabled(out bool isCompositionEnabled) == 0 && isCompositionEnabled);
|
|
||||||
|
|
||||||
static WindowsUtils(){
|
|
||||||
using(Process me = Process.GetCurrentProcess()){
|
|
||||||
CurrentProcessID = me.Id;
|
|
||||||
}
|
|
||||||
|
|
||||||
Version ver = Environment.OSVersion.Version;
|
Version ver = Environment.OSVersion.Version;
|
||||||
IsWindows8OrNewer = ver.Major == 6 && ver.Minor == 2; // windows 8/10
|
return ver.Major == major && ver.Minor == minor;
|
||||||
|
|
||||||
ShouldAvoidToolWindow = IsWindows8OrNewer;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void EnsureTLS12(){
|
|
||||||
if (!HasMicrosoftBeenBroughtTo2008Yet){
|
|
||||||
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;
|
|
||||||
ServicePointManager.SecurityProtocol &= ~(SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls | SecurityProtocolType.Tls11);
|
|
||||||
HasMicrosoftBeenBroughtTo2008Yet = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void CreateDirectoryForFile(string file){
|
|
||||||
string dir = Path.GetDirectoryName(file);
|
|
||||||
|
|
||||||
if (dir == null){
|
|
||||||
throw new ArgumentException("Invalid file path: "+file);
|
|
||||||
}
|
|
||||||
else if (dir.Length > 0){
|
|
||||||
Directory.CreateDirectory(dir);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool CheckFolderWritePermission(string path){
|
|
||||||
string testFile = Path.Combine(path, ".test");
|
|
||||||
|
|
||||||
try{
|
|
||||||
Directory.CreateDirectory(path);
|
|
||||||
|
|
||||||
using(File.Create(testFile)){}
|
|
||||||
File.Delete(testFile);
|
|
||||||
return true;
|
|
||||||
}catch{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool OpenAssociatedProgram(string file, string arguments = "", bool runElevated = false){
|
public static bool OpenAssociatedProgram(string file, string arguments = "", bool runElevated = false){
|
||||||
@@ -79,7 +37,7 @@ namespace TweetDuck.Core.Utils{
|
|||||||
}catch(Win32Exception e) when (e.NativeErrorCode == 0x000004C7){ // operation canceled by the user
|
}catch(Win32Exception e) when (e.NativeErrorCode == 0x000004C7){ // operation canceled by the user
|
||||||
return false;
|
return false;
|
||||||
}catch(Exception e){
|
}catch(Exception e){
|
||||||
Program.Reporter.HandleException("Error Opening Program", "Could not open the associated program for "+file, true, e);
|
Program.Reporter.HandleException("Error Opening Program", "Could not open the associated program for " + file, true, e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -121,8 +79,8 @@ namespace TweetDuck.Core.Utils{
|
|||||||
|
|
||||||
string updatedHtml = RegexStripHtmlStyles.Value.Replace(originalHtml, string.Empty);
|
string updatedHtml = RegexStripHtmlStyles.Value.Replace(originalHtml, string.Empty);
|
||||||
|
|
||||||
int removed = originalHtml.Length-updatedHtml.Length;
|
int removed = originalHtml.Length - updatedHtml.Length;
|
||||||
updatedHtml = RegexOffsetClipboardHtml.Value.Replace(updatedHtml, match => (int.Parse(match.Value)-removed).ToString().PadLeft(match.Value.Length, '0'));
|
updatedHtml = RegexOffsetClipboardHtml.Value.Replace(updatedHtml, match => (int.Parse(match.Value) - removed).ToString().PadLeft(match.Value.Length, '0'));
|
||||||
|
|
||||||
DataObject obj = new DataObject();
|
DataObject obj = new DataObject();
|
||||||
obj.SetText(originalText, TextDataFormat.UnicodeText);
|
obj.SetText(originalText, TextDataFormat.UnicodeText);
|
||||||
@@ -149,34 +107,34 @@ namespace TweetDuck.Core.Utils{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static IEnumerable<Browser> FindInstalledBrowsers(){
|
public static IEnumerable<Browser> FindInstalledBrowsers(){
|
||||||
IEnumerable<Browser> ReadBrowsersFromKey(RegistryHive hive){
|
static IEnumerable<Browser> ReadBrowsersFromKey(RegistryHive hive){
|
||||||
using(RegistryKey root = RegistryKey.OpenBaseKey(hive, RegistryView.Default))
|
using RegistryKey root = RegistryKey.OpenBaseKey(hive, RegistryView.Default);
|
||||||
using(RegistryKey browserList = root.OpenSubKey(@"SOFTWARE\Clients\StartMenuInternet", false)){
|
using RegistryKey browserList = root.OpenSubKey(@"SOFTWARE\Clients\StartMenuInternet", false);
|
||||||
if (browserList == null){
|
|
||||||
yield break;
|
if (browserList == null){
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach(string sub in browserList.GetSubKeyNames()){
|
||||||
|
using RegistryKey browserKey = browserList.OpenSubKey(sub, false);
|
||||||
|
using RegistryKey shellKey = browserKey?.OpenSubKey(@"shell\open\command");
|
||||||
|
|
||||||
|
if (shellKey == null){
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach(string sub in browserList.GetSubKeyNames()){
|
string browserName = browserKey.GetValue(null) as string;
|
||||||
using(RegistryKey browserKey = browserList.OpenSubKey(sub, false))
|
string browserPath = shellKey.GetValue(null) as string;
|
||||||
using(RegistryKey shellKey = browserKey?.OpenSubKey(@"shell\open\command")){
|
|
||||||
if (shellKey == null){
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
string browserName = browserKey.GetValue(null) as string;
|
if (string.IsNullOrEmpty(browserName) || string.IsNullOrEmpty(browserPath)){
|
||||||
string browserPath = shellKey.GetValue(null) as string;
|
continue;
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(browserName) || string.IsNullOrEmpty(browserPath)){
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (browserPath[0] == '"' && browserPath[browserPath.Length-1] == '"'){
|
|
||||||
browserPath = browserPath.Substring(1, browserPath.Length-2);
|
|
||||||
}
|
|
||||||
|
|
||||||
yield return new Browser(browserName, browserPath);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (browserPath[0] == '"' && browserPath[browserPath.Length - 1] == '"'){
|
||||||
|
browserPath = browserPath.Substring(1, browserPath.Length - 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
yield return new Browser(browserName, browserPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,8 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace TweetDuck.Data.Serialization{
|
|
||||||
interface ITypeConverter{
|
|
||||||
bool TryWriteType(Type type, object value, out string converted);
|
|
||||||
bool TryReadType(Type type, string value, out object converted);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,8 +1,8 @@
|
|||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using TweetDuck.Core.Controls;
|
using TweetDuck.Core.Controls;
|
||||||
using TweetDuck.Core.Utils;
|
using TweetLib.Core.Serialization.Converters;
|
||||||
using TweetDuck.Data.Serialization;
|
using TweetLib.Core.Utils;
|
||||||
|
|
||||||
namespace TweetDuck.Data{
|
namespace TweetDuck.Data{
|
||||||
sealed class WindowState{
|
sealed class WindowState{
|
||||||
|
58
Impl/LockHandler.cs
Normal file
58
Impl/LockHandler.cs
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
using System;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using TweetDuck.Core.Utils;
|
||||||
|
using TweetLib.Core.Application;
|
||||||
|
|
||||||
|
namespace TweetDuck.Impl{
|
||||||
|
class LockHandler : IAppLockHandler{
|
||||||
|
private const int WaitRetryDelay = 250;
|
||||||
|
private const int RestoreFailTimeout = 2000;
|
||||||
|
private const int CloseNaturallyTimeout = 10000;
|
||||||
|
private const int CloseKillTimeout = 5000;
|
||||||
|
|
||||||
|
bool IAppLockHandler.RestoreProcess(Process process){
|
||||||
|
if (process.MainWindowHandle == IntPtr.Zero){ // restore if the original process is in tray
|
||||||
|
NativeMethods.BroadcastMessage(Program.WindowRestoreMessage, (uint)process.Id, 0);
|
||||||
|
|
||||||
|
if (WindowsUtils.TrySleepUntil(() => CheckProcessExited(process) || (process.MainWindowHandle != IntPtr.Zero && process.Responding), RestoreFailTimeout, WaitRetryDelay)){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IAppLockHandler.CloseProcess(Process process){
|
||||||
|
try{
|
||||||
|
if (process.CloseMainWindow()){
|
||||||
|
// ReSharper disable once AccessToDisposedClosure
|
||||||
|
WindowsUtils.TrySleepUntil(() => CheckProcessExited(process), CloseNaturallyTimeout, WaitRetryDelay);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!process.HasExited){
|
||||||
|
process.Kill();
|
||||||
|
// ReSharper disable once AccessToDisposedClosure
|
||||||
|
WindowsUtils.TrySleepUntil(() => CheckProcessExited(process), CloseKillTimeout, WaitRetryDelay);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.HasExited){
|
||||||
|
process.Dispose();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}catch(Exception ex) when (ex is InvalidOperationException || ex is Win32Exception){
|
||||||
|
bool hasExited = CheckProcessExited(process);
|
||||||
|
process.Dispose();
|
||||||
|
return hasExited;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool CheckProcessExited(Process process){
|
||||||
|
process.Refresh();
|
||||||
|
return process.HasExited;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
16
Impl/SystemHandler.cs
Normal file
16
Impl/SystemHandler.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using TweetLib.Core.Application;
|
||||||
|
|
||||||
|
namespace TweetDuck.Impl{
|
||||||
|
class SystemHandler : IAppSystemHandler{
|
||||||
|
void IAppSystemHandler.OpenFileExplorer(string path){
|
||||||
|
if (File.Exists(path)){
|
||||||
|
using(Process.Start("explorer.exe", "/select,\"" + path.Replace('/', '\\') + "\"")){}
|
||||||
|
}
|
||||||
|
else if (Directory.Exists(path)){
|
||||||
|
using(Process.Start("explorer.exe", '"' + path.Replace('/', '\\') + '"')){}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,88 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace TweetDuck.Plugins.Enums{
|
|
||||||
[Flags]
|
|
||||||
enum PluginEnvironment{
|
|
||||||
None = 0,
|
|
||||||
Browser = 1,
|
|
||||||
Notification = 2
|
|
||||||
}
|
|
||||||
|
|
||||||
static class PluginEnvironmentExtensions{
|
|
||||||
public static IEnumerable<PluginEnvironment> Values{
|
|
||||||
get{
|
|
||||||
yield return PluginEnvironment.Browser;
|
|
||||||
yield return PluginEnvironment.Notification;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IncludesDisabledPlugins(this PluginEnvironment environment){
|
|
||||||
return environment == PluginEnvironment.Browser;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string GetPluginScriptFile(this PluginEnvironment environment){
|
|
||||||
switch(environment){
|
|
||||||
case PluginEnvironment.Browser: return "browser.js";
|
|
||||||
case PluginEnvironment.Notification: return "notification.js";
|
|
||||||
default: return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string GetPluginScriptVariables(this PluginEnvironment environment){
|
|
||||||
switch(environment){
|
|
||||||
case PluginEnvironment.Browser: return "$,$TD,$TDP,TD";
|
|
||||||
case PluginEnvironment.Notification: return "$TD,$TDP";
|
|
||||||
default: return string.Empty;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IReadOnlyDictionary<PluginEnvironment, T> Map<T>(T forNone, T forBrowser, T forNotification){
|
|
||||||
return new PluginEnvironmentDictionary<T>(forNone, forBrowser, forNotification);
|
|
||||||
}
|
|
||||||
|
|
||||||
[SuppressMessage("ReSharper", "MemberHidesStaticFromOuterClass")]
|
|
||||||
private sealed class PluginEnvironmentDictionary<T> : IReadOnlyDictionary<PluginEnvironment, T>{
|
|
||||||
private const int TotalKeys = 3;
|
|
||||||
|
|
||||||
public IEnumerable<PluginEnvironment> Keys => Enum.GetValues(typeof(PluginEnvironment)).Cast<PluginEnvironment>();
|
|
||||||
public IEnumerable<T> Values => data;
|
|
||||||
public int Count => TotalKeys;
|
|
||||||
|
|
||||||
public T this[PluginEnvironment key] => data[(int)key];
|
|
||||||
|
|
||||||
private readonly T[] data;
|
|
||||||
|
|
||||||
public PluginEnvironmentDictionary(T forNone, T forBrowser, T forNotification){
|
|
||||||
this.data = new T[TotalKeys];
|
|
||||||
this.data[(int)PluginEnvironment.None] = forNone;
|
|
||||||
this.data[(int)PluginEnvironment.Browser] = forBrowser;
|
|
||||||
this.data[(int)PluginEnvironment.Notification] = forNotification;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool ContainsKey(PluginEnvironment key){
|
|
||||||
return key >= 0 && (int)key < TotalKeys;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool TryGetValue(PluginEnvironment key, out T value){
|
|
||||||
if (ContainsKey(key)){
|
|
||||||
value = this[key];
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
value = default(T);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerator<KeyValuePair<PluginEnvironment, T>> GetEnumerator(){
|
|
||||||
return Keys.Select(key => new KeyValuePair<PluginEnvironment, T>(key, this[key])).GetEnumerator();
|
|
||||||
}
|
|
||||||
|
|
||||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,5 +0,0 @@
|
|||||||
namespace TweetDuck.Plugins.Enums{
|
|
||||||
enum PluginFolder{
|
|
||||||
Root, Data
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,23 +0,0 @@
|
|||||||
namespace TweetDuck.Plugins.Enums{
|
|
||||||
enum PluginGroup{
|
|
||||||
Official, Custom
|
|
||||||
}
|
|
||||||
|
|
||||||
static class PluginGroupExtensions{
|
|
||||||
public static string GetIdentifierPrefix(this PluginGroup group){
|
|
||||||
switch(group){
|
|
||||||
case PluginGroup.Official: return "official/";
|
|
||||||
case PluginGroup.Custom: return "custom/";
|
|
||||||
default: return "unknown/";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string GetIdentifierPrefixShort(this PluginGroup group){
|
|
||||||
switch(group){
|
|
||||||
case PluginGroup.Official: return "o/";
|
|
||||||
case PluginGroup.Custom: return "c/";
|
|
||||||
default: return "?/";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,127 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Text;
|
|
||||||
using TweetDuck.Core.Utils;
|
|
||||||
using TweetDuck.Data;
|
|
||||||
using TweetDuck.Plugins.Enums;
|
|
||||||
using TweetDuck.Plugins.Events;
|
|
||||||
|
|
||||||
namespace TweetDuck.Plugins{
|
|
||||||
sealed class PluginBridge{
|
|
||||||
private static string SanitizeCacheKey(string key){
|
|
||||||
return key.Replace('\\', '/').Trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly PluginManager manager;
|
|
||||||
private readonly TwoKeyDictionary<int, string, string> fileCache = new TwoKeyDictionary<int, string, string>(4, 2);
|
|
||||||
private readonly TwoKeyDictionary<int, string, InjectedHTML> notificationInjections = new TwoKeyDictionary<int, string, InjectedHTML>(4, 1);
|
|
||||||
|
|
||||||
public IEnumerable<InjectedHTML> NotificationInjections => notificationInjections.InnerValues;
|
|
||||||
public HashSet<Plugin> WithConfigureFunction { get; } = new HashSet<Plugin>();
|
|
||||||
|
|
||||||
public PluginBridge(PluginManager manager){
|
|
||||||
this.manager = manager;
|
|
||||||
this.manager.Reloaded += manager_Reloaded;
|
|
||||||
this.manager.Config.PluginChangedState += Config_PluginChangedState;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Event handlers
|
|
||||||
|
|
||||||
private void manager_Reloaded(object sender, PluginErrorEventArgs e){
|
|
||||||
fileCache.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Config_PluginChangedState(object sender, PluginChangedStateEventArgs e){
|
|
||||||
if (!e.IsEnabled){
|
|
||||||
int token = manager.GetTokenFromPlugin(e.Plugin);
|
|
||||||
|
|
||||||
fileCache.Remove(token);
|
|
||||||
notificationInjections.Remove(token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Utility methods
|
|
||||||
|
|
||||||
private string GetFullPathOrThrow(int token, PluginFolder folder, string path){
|
|
||||||
Plugin plugin = manager.GetPluginFromToken(token);
|
|
||||||
string fullPath = plugin == null ? string.Empty : plugin.GetFullPathIfSafe(folder, path);
|
|
||||||
|
|
||||||
if (fullPath.Length == 0){
|
|
||||||
switch(folder){
|
|
||||||
case PluginFolder.Data: throw new ArgumentException("File path has to be relative to the plugin data folder.");
|
|
||||||
case PluginFolder.Root: throw new ArgumentException("File path has to be relative to the plugin root folder.");
|
|
||||||
default: throw new ArgumentException("Invalid folder type "+folder+", this is a TweetDuck error.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
return fullPath;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private string ReadFileUnsafe(int token, string cacheKey, string fullPath, bool readCached){
|
|
||||||
cacheKey = SanitizeCacheKey(cacheKey);
|
|
||||||
|
|
||||||
if (readCached && fileCache.TryGetValue(token, cacheKey, out string cachedContents)){
|
|
||||||
return cachedContents;
|
|
||||||
}
|
|
||||||
|
|
||||||
try{
|
|
||||||
return fileCache[token, cacheKey] = File.ReadAllText(fullPath, Encoding.UTF8);
|
|
||||||
}catch(FileNotFoundException){
|
|
||||||
throw new FileNotFoundException("File not found.");
|
|
||||||
}catch(DirectoryNotFoundException){
|
|
||||||
throw new DirectoryNotFoundException("Directory not found.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Public methods
|
|
||||||
|
|
||||||
public void WriteFile(int token, string path, string contents){
|
|
||||||
string fullPath = GetFullPathOrThrow(token, PluginFolder.Data, path);
|
|
||||||
|
|
||||||
WindowsUtils.CreateDirectoryForFile(fullPath);
|
|
||||||
File.WriteAllText(fullPath, contents, Encoding.UTF8);
|
|
||||||
fileCache[token, SanitizeCacheKey(path)] = contents;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string ReadFile(int token, string path, bool cache){
|
|
||||||
return ReadFileUnsafe(token, path, GetFullPathOrThrow(token, PluginFolder.Data, path), cache);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void DeleteFile(int token, string path){
|
|
||||||
string fullPath = GetFullPathOrThrow(token, PluginFolder.Data, path);
|
|
||||||
|
|
||||||
fileCache.Remove(token, SanitizeCacheKey(path));
|
|
||||||
File.Delete(fullPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool CheckFileExists(int token, string path){
|
|
||||||
return File.Exists(GetFullPathOrThrow(token, PluginFolder.Data, path));
|
|
||||||
}
|
|
||||||
|
|
||||||
public string ReadFileRoot(int token, string path){
|
|
||||||
return ReadFileUnsafe(token, "root*"+path, GetFullPathOrThrow(token, PluginFolder.Root, path), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool CheckFileExistsRoot(int token, string path){
|
|
||||||
return File.Exists(GetFullPathOrThrow(token, PluginFolder.Root, path));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void InjectIntoNotificationsBefore(int token, string key, string search, string html){
|
|
||||||
notificationInjections[token, key] = new InjectedHTML(InjectedHTML.Position.Before, search, html);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void InjectIntoNotificationsAfter(int token, string key, string search, string html){
|
|
||||||
notificationInjections[token, key] = new InjectedHTML(InjectedHTML.Position.After, search, html);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetConfigurable(int token){
|
|
||||||
Plugin plugin = manager.GetPluginFromToken(token);
|
|
||||||
|
|
||||||
if (plugin != null){
|
|
||||||
WithConfigureFunction.Add(plugin);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,72 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Text;
|
|
||||||
using TweetDuck.Plugins.Events;
|
|
||||||
|
|
||||||
namespace TweetDuck.Plugins{
|
|
||||||
sealed class PluginConfig{
|
|
||||||
public event EventHandler<PluginChangedStateEventArgs> PluginChangedState;
|
|
||||||
|
|
||||||
public IEnumerable<string> DisabledPlugins => disabled;
|
|
||||||
public bool AnyDisabled => disabled.Count > 0;
|
|
||||||
|
|
||||||
private static readonly string[] DefaultDisabled = {
|
|
||||||
"official/clear-columns",
|
|
||||||
"official/reply-account"
|
|
||||||
};
|
|
||||||
|
|
||||||
private readonly HashSet<string> disabled = new HashSet<string>();
|
|
||||||
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ToggleEnabled(Plugin plugin){
|
|
||||||
SetEnabled(plugin, !IsEnabled(plugin));
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsEnabled(Plugin plugin){
|
|
||||||
return !disabled.Contains(plugin.Identifier);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Save(string file){
|
|
||||||
try{
|
|
||||||
using(StreamWriter writer = new StreamWriter(new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None), Encoding.UTF8)){
|
|
||||||
writer.WriteLine("#Disabled");
|
|
||||||
|
|
||||||
foreach(string identifier in disabled){
|
|
||||||
writer.WriteLine(identifier);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}catch(Exception e){
|
|
||||||
Program.Reporter.HandleException("Plugin Configuration Error", "Could not save the plugin configuration file.", true, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Load(string file){
|
|
||||||
try{
|
|
||||||
using(StreamReader reader = new StreamReader(new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read), Encoding.UTF8)){
|
|
||||||
string line = reader.ReadLine();
|
|
||||||
|
|
||||||
if (line == "#Disabled"){
|
|
||||||
disabled.Clear();
|
|
||||||
|
|
||||||
while((line = reader.ReadLine()) != null){
|
|
||||||
disabled.Add(line);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}catch(FileNotFoundException){
|
|
||||||
disabled.Clear();
|
|
||||||
disabled.UnionWith(DefaultDisabled);
|
|
||||||
Save(file);
|
|
||||||
}catch(DirectoryNotFoundException){
|
|
||||||
}catch(Exception e){
|
|
||||||
Program.Reporter.HandleException("Plugin Configuration Error", "Could not read the plugin configuration file. If you continue, the list of disabled plugins will be reset to default.", true, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,4 +1,4 @@
|
|||||||
namespace TweetDuck.Plugins.Controls {
|
namespace TweetDuck.Plugins {
|
||||||
partial class PluginControl {
|
partial class PluginControl {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Required designer variable.
|
/// Required designer variable.
|
@@ -3,9 +3,10 @@ using System.Drawing;
|
|||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using TweetDuck.Core.Controls;
|
using TweetDuck.Core.Controls;
|
||||||
using TweetDuck.Core.Utils;
|
using TweetDuck.Core.Utils;
|
||||||
using TweetDuck.Plugins.Enums;
|
using TweetLib.Core.Features.Plugins;
|
||||||
|
using TweetLib.Core.Features.Plugins.Enums;
|
||||||
|
|
||||||
namespace TweetDuck.Plugins.Controls{
|
namespace TweetDuck.Plugins{
|
||||||
sealed partial class PluginControl : UserControl{
|
sealed partial class PluginControl : UserControl{
|
||||||
private readonly PluginManager pluginManager;
|
private readonly PluginManager pluginManager;
|
||||||
private readonly Plugin plugin;
|
private readonly Plugin plugin;
|
||||||
@@ -26,7 +27,7 @@ namespace TweetDuck.Plugins.Controls{
|
|||||||
float dpiScale = this.GetDPIScale();
|
float dpiScale = this.GetDPIScale();
|
||||||
|
|
||||||
if (dpiScale > 1F){
|
if (dpiScale > 1F){
|
||||||
Size = MaximumSize = new Size(MaximumSize.Width, MaximumSize.Height+3);
|
Size = MaximumSize = new Size(MaximumSize.Width, MaximumSize.Height + 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.labelName.Text = plugin.Name;
|
this.labelName.Text = plugin.Name;
|
||||||
@@ -55,19 +56,19 @@ namespace TweetDuck.Plugins.Controls{
|
|||||||
private void panelDescription_Resize(object sender, EventArgs e){
|
private void panelDescription_Resize(object sender, EventArgs e){
|
||||||
SuspendLayout();
|
SuspendLayout();
|
||||||
|
|
||||||
int maxWidth = panelDescription.Width-(panelDescription.VerticalScroll.Visible ? SystemInformation.VerticalScrollBarWidth : 0);
|
int maxWidth = panelDescription.Width - (panelDescription.VerticalScroll.Visible ? SystemInformation.VerticalScrollBarWidth : 0);
|
||||||
labelDescription.MaximumSize = new Size(maxWidth, int.MaxValue);
|
labelDescription.MaximumSize = new Size(maxWidth, int.MaxValue);
|
||||||
|
|
||||||
Font font = labelDescription.Font;
|
Font font = labelDescription.Font;
|
||||||
int descriptionLines = TextRenderer.MeasureText(labelDescription.Text, font, new Size(maxWidth, int.MaxValue), TextFormatFlags.WordBreak).Height/(font.Height-1);
|
int descriptionLines = TextRenderer.MeasureText(labelDescription.Text, font, new Size(maxWidth, int.MaxValue), TextFormatFlags.WordBreak).Height / (font.Height - 1);
|
||||||
|
|
||||||
int requiredLines = Math.Max(descriptionLines, 1+(string.IsNullOrEmpty(labelVersion.Text) ? 0 : 1)+(isConfigurable ? 1 : 0));
|
int requiredLines = Math.Max(descriptionLines, 1 + (string.IsNullOrEmpty(labelVersion.Text) ? 0 : 1) + (isConfigurable ? 1 : 0));
|
||||||
|
|
||||||
switch(requiredLines){
|
nextHeight = requiredLines switch{
|
||||||
case 1: nextHeight = MaximumSize.Height-2*(font.Height-1); break;
|
1 => MaximumSize.Height - 2 * (font.Height - 1),
|
||||||
case 2: nextHeight = MaximumSize.Height-(font.Height-1); break;
|
2 => MaximumSize.Height - 1 * (font.Height - 1),
|
||||||
default: nextHeight = MaximumSize.Height; break;
|
_ => MaximumSize.Height
|
||||||
}
|
};
|
||||||
|
|
||||||
if (nextHeight != Height){
|
if (nextHeight != Height){
|
||||||
timerLayout.Start();
|
timerLayout.Start();
|
||||||
@@ -89,7 +90,7 @@ namespace TweetDuck.Plugins.Controls{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void btnToggleState_Click(object sender, EventArgs e){
|
private void btnToggleState_Click(object sender, EventArgs e){
|
||||||
pluginManager.Config.ToggleEnabled(plugin);
|
pluginManager.Config.SetEnabled(plugin, !pluginManager.Config.IsEnabled(plugin));
|
||||||
UpdatePluginState();
|
UpdatePluginState();
|
||||||
}
|
}
|
||||||
|
|
34
Plugins/PluginDispatcher.cs
Normal file
34
Plugins/PluginDispatcher.cs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
using System;
|
||||||
|
using CefSharp;
|
||||||
|
using TweetDuck.Core.Adapters;
|
||||||
|
using TweetLib.Core.Browser;
|
||||||
|
using TweetLib.Core.Features.Plugins;
|
||||||
|
using TweetLib.Core.Features.Plugins.Events;
|
||||||
|
using TweetLib.Core.Features.Twitter;
|
||||||
|
|
||||||
|
namespace TweetDuck.Plugins{
|
||||||
|
sealed class PluginDispatcher : IPluginDispatcher{
|
||||||
|
public event EventHandler<PluginDispatchEventArgs> Ready;
|
||||||
|
|
||||||
|
private readonly IWebBrowser browser;
|
||||||
|
private readonly IScriptExecutor executor;
|
||||||
|
|
||||||
|
public PluginDispatcher(IWebBrowser browser){
|
||||||
|
this.browser = browser;
|
||||||
|
this.browser.FrameLoadEnd += browser_FrameLoadEnd;
|
||||||
|
this.executor = new CefScriptExecutor(browser);
|
||||||
|
}
|
||||||
|
|
||||||
|
void IPluginDispatcher.AttachBridge(string name, object bridge){
|
||||||
|
browser.RegisterAsyncJsObject(name, bridge);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
|
||||||
|
IFrame frame = e.Frame;
|
||||||
|
|
||||||
|
if (frame.IsMain && TwitterUrls.IsTweetDeck(frame.Url)){
|
||||||
|
Ready?.Invoke(this, new PluginDispatchEventArgs(executor));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,70 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using TweetDuck.Plugins.Enums;
|
|
||||||
|
|
||||||
namespace TweetDuck.Plugins{
|
|
||||||
static class PluginLoader{
|
|
||||||
private static readonly string[] EndTag = { "[END]" };
|
|
||||||
|
|
||||||
public static Plugin FromFolder(string path, PluginGroup group){
|
|
||||||
string name = Path.GetFileName(path);
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(name)){
|
|
||||||
throw new ArgumentException("Could not extract directory name from path: "+path);
|
|
||||||
}
|
|
||||||
|
|
||||||
Plugin.Builder builder = new Plugin.Builder(group, name, path, Path.Combine(Program.PluginDataPath, group.GetIdentifierPrefix(), name));
|
|
||||||
|
|
||||||
foreach(string file in Directory.EnumerateFiles(path, "*.js", SearchOption.TopDirectoryOnly).Select(Path.GetFileName)){
|
|
||||||
builder.AddEnvironment(PluginEnvironmentExtensions.Values.FirstOrDefault(env => file.Equals(env.GetPluginScriptFile(), StringComparison.Ordinal)));
|
|
||||||
}
|
|
||||||
|
|
||||||
string metaFile = Path.Combine(path, ".meta");
|
|
||||||
|
|
||||||
if (!File.Exists(metaFile)){
|
|
||||||
throw new ArgumentException("Plugin is missing a .meta file");
|
|
||||||
}
|
|
||||||
|
|
||||||
string currentTag = null, currentContents = string.Empty;
|
|
||||||
|
|
||||||
foreach(string line in File.ReadAllLines(metaFile, Encoding.UTF8).Concat(EndTag).Select(line => line.TrimEnd()).Where(line => line.Length > 0)){
|
|
||||||
if (line[0] == '[' && line[line.Length-1] == ']'){
|
|
||||||
if (currentTag != null){
|
|
||||||
SetProperty(builder, currentTag, currentContents);
|
|
||||||
}
|
|
||||||
|
|
||||||
currentTag = line.Substring(1, line.Length-2).ToUpper();
|
|
||||||
currentContents = string.Empty;
|
|
||||||
|
|
||||||
if (line.Equals(EndTag[0])){
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (currentTag != null){
|
|
||||||
currentContents = currentContents.Length == 0 ? line : currentContents+Environment.NewLine+line;
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
throw new FormatException("Missing metadata tag before value: "+line);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder.BuildAndSetup();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void SetProperty(Plugin.Builder builder, string tag, string value){
|
|
||||||
switch(tag){
|
|
||||||
case "NAME": builder.Name = value; break;
|
|
||||||
case "DESCRIPTION": builder.Description = value; break;
|
|
||||||
case "AUTHOR": builder.Author = value; break;
|
|
||||||
case "VERSION": builder.Version = value; break;
|
|
||||||
case "WEBSITE": builder.Website = value; break;
|
|
||||||
case "CONFIGFILE": builder.ConfigFile = value; break;
|
|
||||||
case "CONFIGDEFAULT": builder.ConfigDefault = value; break;
|
|
||||||
case "REQUIRES": builder.RequiredVersion = Version.TryParse(value, out Version version) ? version : throw new FormatException("Invalid required minimum version: "+value); break;
|
|
||||||
default: throw new FormatException("Invalid metadata tag: "+tag);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,183 +0,0 @@
|
|||||||
using CefSharp;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Windows.Forms;
|
|
||||||
using TweetDuck.Core.Other.Interfaces;
|
|
||||||
using TweetDuck.Data;
|
|
||||||
using TweetDuck.Plugins.Enums;
|
|
||||||
using TweetDuck.Plugins.Events;
|
|
||||||
using TweetDuck.Resources;
|
|
||||||
|
|
||||||
namespace TweetDuck.Plugins{
|
|
||||||
sealed class PluginManager{
|
|
||||||
private static readonly IReadOnlyDictionary<PluginEnvironment, string> PluginSetupScriptNames = PluginEnvironmentExtensions.Map(null, "plugins.browser.js", "plugins.notification.js");
|
|
||||||
|
|
||||||
public string PathOfficialPlugins => Path.Combine(rootPath, "official");
|
|
||||||
public string PathCustomPlugins => Path.Combine(rootPath, "user");
|
|
||||||
|
|
||||||
public IEnumerable<Plugin> Plugins => plugins;
|
|
||||||
public IEnumerable<InjectedHTML> NotificationInjections => bridge.NotificationInjections;
|
|
||||||
|
|
||||||
public PluginConfig Config { get; }
|
|
||||||
|
|
||||||
public event EventHandler<PluginErrorEventArgs> Reloaded;
|
|
||||||
public event EventHandler<PluginErrorEventArgs> Executed;
|
|
||||||
|
|
||||||
private readonly string rootPath;
|
|
||||||
private readonly string configPath;
|
|
||||||
private readonly PluginBridge bridge;
|
|
||||||
|
|
||||||
private readonly HashSet<Plugin> plugins = new HashSet<Plugin>();
|
|
||||||
private readonly Dictionary<int, Plugin> tokens = new Dictionary<int, Plugin>();
|
|
||||||
private readonly Random rand = new Random();
|
|
||||||
|
|
||||||
private ITweetDeckBrowser mainBrowser;
|
|
||||||
|
|
||||||
public PluginManager(string rootPath, string configPath){
|
|
||||||
this.rootPath = rootPath;
|
|
||||||
this.configPath = configPath;
|
|
||||||
|
|
||||||
this.Config = new PluginConfig();
|
|
||||||
this.bridge = new PluginBridge(this);
|
|
||||||
|
|
||||||
Config.Load(configPath);
|
|
||||||
Config.PluginChangedState += Config_PluginChangedState;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Register(ITweetDeckBrowser browser, PluginEnvironment environment, Control sync, bool asMainBrowser = false){
|
|
||||||
browser.OnFrameLoaded(frame => ExecutePlugins(frame, environment, sync));
|
|
||||||
browser.RegisterBridge("$TDP", bridge);
|
|
||||||
|
|
||||||
if (asMainBrowser){
|
|
||||||
mainBrowser = browser;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Config_PluginChangedState(object sender, PluginChangedStateEventArgs e){
|
|
||||||
mainBrowser?.ExecuteFunction("TDPF_setPluginState", e.Plugin, e.IsEnabled);
|
|
||||||
Config.Save(configPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsPluginInstalled(string identifier){
|
|
||||||
return plugins.Any(plugin => plugin.Identifier.Equals(identifier));
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool HasAnyPlugin(PluginEnvironment environment){
|
|
||||||
return plugins.Any(plugin => plugin.Environments.HasFlag(environment));
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsPluginConfigurable(Plugin plugin){
|
|
||||||
return plugin.HasConfig || bridge.WithConfigureFunction.Contains(plugin);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ConfigurePlugin(Plugin plugin){
|
|
||||||
if (bridge.WithConfigureFunction.Contains(plugin)){
|
|
||||||
mainBrowser?.ExecuteFunction("TDPF_configurePlugin", plugin);
|
|
||||||
}
|
|
||||||
else if (plugin.HasConfig){
|
|
||||||
if (File.Exists(plugin.ConfigPath)){
|
|
||||||
using(Process.Start("explorer.exe", "/select,\""+plugin.ConfigPath.Replace('/', '\\')+"\"")){}
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
using(Process.Start("explorer.exe", '"'+plugin.GetPluginFolder(PluginFolder.Data).Replace('/', '\\')+'"')){}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int GetTokenFromPlugin(Plugin plugin){
|
|
||||||
foreach(KeyValuePair<int, Plugin> kvp in tokens){
|
|
||||||
if (kvp.Value.Equals(plugin)){
|
|
||||||
return kvp.Key;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int token, attempts = 1000;
|
|
||||||
|
|
||||||
do{
|
|
||||||
token = rand.Next();
|
|
||||||
}while(tokens.ContainsKey(token) && --attempts >= 0);
|
|
||||||
|
|
||||||
if (attempts < 0){
|
|
||||||
token = -tokens.Count-1;
|
|
||||||
}
|
|
||||||
|
|
||||||
tokens[token] = plugin;
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Plugin GetPluginFromToken(int token){
|
|
||||||
return tokens.TryGetValue(token, out Plugin plugin) ? plugin : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Reload(){
|
|
||||||
Config.Load(configPath);
|
|
||||||
|
|
||||||
plugins.Clear();
|
|
||||||
tokens.Clear();
|
|
||||||
|
|
||||||
List<string> loadErrors = new List<string>(2);
|
|
||||||
|
|
||||||
IEnumerable<Plugin> LoadPluginsFrom(string path, PluginGroup group){
|
|
||||||
if (!Directory.Exists(path)){
|
|
||||||
yield break;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach(string fullDir in Directory.EnumerateDirectories(path, "*", SearchOption.TopDirectoryOnly)){
|
|
||||||
Plugin plugin;
|
|
||||||
|
|
||||||
try{
|
|
||||||
plugin = PluginLoader.FromFolder(fullDir, group);
|
|
||||||
}catch(Exception e){
|
|
||||||
loadErrors.Add(group.GetIdentifierPrefix()+Path.GetFileName(fullDir)+": "+e.Message);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
yield return plugin;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
plugins.UnionWith(LoadPluginsFrom(PathOfficialPlugins, PluginGroup.Official));
|
|
||||||
plugins.UnionWith(LoadPluginsFrom(PathCustomPlugins, PluginGroup.Custom));
|
|
||||||
|
|
||||||
Reloaded?.Invoke(this, new PluginErrorEventArgs(loadErrors));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ExecutePlugins(IFrame frame, PluginEnvironment environment, Control sync){
|
|
||||||
if (!HasAnyPlugin(environment) || !ScriptLoader.ExecuteFile(frame, PluginSetupScriptNames[environment], sync)){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool includeDisabled = environment.IncludesDisabledPlugins();
|
|
||||||
|
|
||||||
if (includeDisabled){
|
|
||||||
ScriptLoader.ExecuteScript(frame, PluginScriptGenerator.GenerateConfig(Config), "gen:pluginconfig");
|
|
||||||
}
|
|
||||||
|
|
||||||
List<string> failedPlugins = new List<string>(1);
|
|
||||||
|
|
||||||
foreach(Plugin plugin in Plugins){
|
|
||||||
string path = plugin.GetScriptPath(environment);
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(path) || (!includeDisabled && !Config.IsEnabled(plugin)) || !plugin.CanRun){
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
string script;
|
|
||||||
|
|
||||||
try{
|
|
||||||
script = File.ReadAllText(path);
|
|
||||||
}catch(Exception e){
|
|
||||||
failedPlugins.Add(plugin.Identifier+" ("+Path.GetFileName(path)+"): "+e.Message);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
ScriptLoader.ExecuteScript(frame, PluginScriptGenerator.GeneratePlugin(plugin.Identifier, script, GetTokenFromPlugin(plugin), environment), "plugin:"+plugin);
|
|
||||||
}
|
|
||||||
|
|
||||||
Executed?.Invoke(this, new PluginErrorEventArgs(failedPlugins));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
79
Program.cs
79
Program.cs
@@ -1,25 +1,30 @@
|
|||||||
using CefSharp;
|
using CefSharp;
|
||||||
|
using CefSharp.WinForms;
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Globalization;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using TweetDuck.Configuration;
|
using TweetDuck.Configuration;
|
||||||
using TweetDuck.Core;
|
using TweetDuck.Core;
|
||||||
|
using TweetDuck.Core.Handling;
|
||||||
using TweetDuck.Core.Handling.General;
|
using TweetDuck.Core.Handling.General;
|
||||||
using TweetDuck.Core.Other;
|
using TweetDuck.Core.Other;
|
||||||
using TweetDuck.Core.Management;
|
using TweetDuck.Core.Management;
|
||||||
using TweetDuck.Core.Utils;
|
using TweetDuck.Core.Utils;
|
||||||
using TweetDuck.Data;
|
using TweetDuck.Impl;
|
||||||
|
using TweetDuck.Resources;
|
||||||
|
using TweetLib.Core;
|
||||||
|
using TweetLib.Core.Application.Helpers;
|
||||||
|
using TweetLib.Core.Collections;
|
||||||
|
using TweetLib.Core.Utils;
|
||||||
|
|
||||||
namespace TweetDuck{
|
namespace TweetDuck{
|
||||||
static class Program{
|
static class Program{
|
||||||
public const string BrandName = "TweetDuck";
|
public const string BrandName = Lib.BrandName;
|
||||||
public const string Website = "https://tweetduck.chylex.com";
|
public const string VersionTag = Lib.VersionTag;
|
||||||
|
|
||||||
public const string VersionTag = "1.15.1";
|
public const string Website = "https://tweetduck.chylex.com";
|
||||||
|
|
||||||
public static readonly string ProgramPath = AppDomain.CurrentDomain.BaseDirectory;
|
public static readonly string ProgramPath = AppDomain.CurrentDomain.BaseDirectory;
|
||||||
public static readonly bool IsPortable = File.Exists(Path.Combine(ProgramPath, "makeportable"));
|
public static readonly bool IsPortable = File.Exists(Path.Combine(ProgramPath, "makeportable"));
|
||||||
@@ -46,27 +51,28 @@ namespace TweetDuck{
|
|||||||
private static readonly LockManager LockManager = new LockManager(Path.Combine(StoragePath, ".lock"));
|
private static readonly LockManager LockManager = new LockManager(Path.Combine(StoragePath, ".lock"));
|
||||||
private static bool HasCleanedUp;
|
private static bool HasCleanedUp;
|
||||||
|
|
||||||
public static CultureInfo Culture { get; }
|
|
||||||
public static Reporter Reporter { get; }
|
public static Reporter Reporter { get; }
|
||||||
public static ConfigManager Config { get; }
|
public static ConfigManager Config { get; }
|
||||||
|
public static ScriptLoader Resources { get; }
|
||||||
// TODO
|
|
||||||
public static UserConfig UserConfig => Config.User;
|
|
||||||
public static SystemConfig SystemConfig => Config.System;
|
|
||||||
|
|
||||||
static Program(){
|
static Program(){
|
||||||
Culture = CultureInfo.CurrentCulture;
|
|
||||||
Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
|
|
||||||
CultureInfo.DefaultThreadCurrentCulture = CultureInfo.InvariantCulture;
|
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
CultureInfo.DefaultThreadCurrentUICulture = Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-us"); // force english exceptions
|
|
||||||
#endif
|
|
||||||
|
|
||||||
Reporter = new Reporter(ErrorLogFilePath);
|
Reporter = new Reporter(ErrorLogFilePath);
|
||||||
Reporter.SetupUnhandledExceptionHandler("TweetDuck Has Failed :(");
|
Reporter.SetupUnhandledExceptionHandler("TweetDuck Has Failed :(");
|
||||||
|
|
||||||
Config = new ConfigManager();
|
Config = new ConfigManager();
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
Resources = new ScriptLoaderDebug();
|
||||||
|
#else
|
||||||
|
Resources = new ScriptLoader();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
Lib.Initialize(new App.Builder{
|
||||||
|
ErrorHandler = Reporter,
|
||||||
|
LockHandler = new LockHandler(),
|
||||||
|
SystemHandler = new SystemHandler(),
|
||||||
|
ResourceHandler = Resources
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
[STAThread]
|
[STAThread]
|
||||||
@@ -77,8 +83,8 @@ namespace TweetDuck{
|
|||||||
|
|
||||||
WindowRestoreMessage = NativeMethods.RegisterWindowMessage("TweetDuckRestore");
|
WindowRestoreMessage = NativeMethods.RegisterWindowMessage("TweetDuckRestore");
|
||||||
|
|
||||||
if (!WindowsUtils.CheckFolderWritePermission(StoragePath)){
|
if (!FileUtils.CheckFolderWritePermission(StoragePath)){
|
||||||
FormMessage.Warning("Permission Error", "TweetDuck does not have write permissions to the storage folder: "+StoragePath, FormMessage.OK);
|
FormMessage.Warning("Permission Error", "TweetDuck does not have write permissions to the storage folder: " + StoragePath, FormMessage.OK);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,15 +107,17 @@ namespace TweetDuck{
|
|||||||
LockManager.Result lockResult = LockManager.Lock();
|
LockManager.Result lockResult = LockManager.Lock();
|
||||||
|
|
||||||
if (lockResult == LockManager.Result.HasProcess){
|
if (lockResult == LockManager.Result.HasProcess){
|
||||||
if (!LockManager.RestoreLockingProcess(2000) && FormMessage.Error("TweetDuck is Already Running", "Another instance of TweetDuck is already running.\nDo you want to close it?", FormMessage.Yes, FormMessage.No)){
|
if (!LockManager.RestoreLockingProcess() && FormMessage.Error("TweetDuck is Already Running", "Another instance of TweetDuck is already running.\nDo you want to close it?", FormMessage.Yes, FormMessage.No)){
|
||||||
if (!LockManager.CloseLockingProcess(10000, 5000)){
|
if (!LockManager.CloseLockingProcess()){
|
||||||
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
lockResult = LockManager.Lock();
|
lockResult = LockManager.Lock();
|
||||||
}
|
}
|
||||||
else return;
|
else{
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lockResult != LockManager.Result.Success){
|
if (lockResult != LockManager.Result.Success){
|
||||||
@@ -129,8 +137,16 @@ namespace TweetDuck{
|
|||||||
|
|
||||||
if (Arguments.HasFlag(Arguments.ArgUpdated)){
|
if (Arguments.HasFlag(Arguments.ArgUpdated)){
|
||||||
WindowsUtils.TryDeleteFolderWhenAble(InstallerPath, 8000);
|
WindowsUtils.TryDeleteFolderWhenAble(InstallerPath, 8000);
|
||||||
|
BrowserCache.TryClearNow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try{
|
||||||
|
RequestHandlerBase.LoadResourceRewriteRules(Arguments.GetValue(Arguments.ArgFreeze));
|
||||||
|
}catch(Exception e){
|
||||||
|
FormMessage.Error("Resource Freeze", "Error parsing resource rewrite rules: " + e.Message, FormMessage.OK);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
BrowserCache.RefreshTimer();
|
BrowserCache.RefreshTimer();
|
||||||
|
|
||||||
CefSharpSettings.WcfEnabled = false;
|
CefSharpSettings.WcfEnabled = false;
|
||||||
@@ -138,7 +154,7 @@ namespace TweetDuck{
|
|||||||
|
|
||||||
CefSettings settings = new CefSettings{
|
CefSettings settings = new CefSettings{
|
||||||
UserAgent = BrowserUtils.UserAgentChrome,
|
UserAgent = BrowserUtils.UserAgentChrome,
|
||||||
BrowserSubprocessPath = BrandName+".Browser.exe",
|
BrowserSubprocessPath = BrandName + ".Browser.exe",
|
||||||
CachePath = StoragePath,
|
CachePath = StoragePath,
|
||||||
UserDataPath = CefDataPath,
|
UserDataPath = CefDataPath,
|
||||||
LogFile = ConsoleLogFilePath,
|
LogFile = ConsoleLogFilePath,
|
||||||
@@ -147,7 +163,7 @@ namespace TweetDuck{
|
|||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
CommandLineArgs.ReadCefArguments(UserConfig.CustomCefArgs).ToDictionary(settings.CefCommandLineArgs);
|
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());
|
||||||
@@ -155,14 +171,15 @@ namespace TweetDuck{
|
|||||||
Application.ApplicationExit += (sender, args) => ExitCleanup();
|
Application.ApplicationExit += (sender, args) => ExitCleanup();
|
||||||
|
|
||||||
FormBrowser mainForm = new FormBrowser();
|
FormBrowser mainForm = new FormBrowser();
|
||||||
|
Resources.Initialize(mainForm);
|
||||||
Application.Run(mainForm);
|
Application.Run(mainForm);
|
||||||
|
|
||||||
if (mainForm.UpdateInstallerPath != null){
|
if (mainForm.UpdateInstallerPath != null){
|
||||||
ExitCleanup();
|
ExitCleanup();
|
||||||
|
|
||||||
// ProgramPath has a trailing backslash
|
// ProgramPath has a trailing backslash
|
||||||
string updaterArgs = "/SP- /SILENT /FORCECLOSEAPPLICATIONS /UPDATEPATH=\""+ProgramPath+"\" /RUNARGS=\""+Arguments.GetCurrentForInstallerCmd()+"\""+(IsPortable ? " /PORTABLE=1" : "");
|
string updaterArgs = "/SP- /SILENT /FORCECLOSEAPPLICATIONS /UPDATEPATH=\"" + ProgramPath + "\" /RUNARGS=\"" + Arguments.GetCurrentForInstallerCmd() + "\"" + (IsPortable ? " /PORTABLE=1" : "");
|
||||||
bool runElevated = !IsPortable || !WindowsUtils.CheckFolderWritePermission(ProgramPath);
|
bool runElevated = !IsPortable || !FileUtils.CheckFolderWritePermission(ProgramPath);
|
||||||
|
|
||||||
if (WindowsUtils.OpenAssociatedProgram(mainForm.UpdateInstallerPath, updaterArgs, runElevated)){
|
if (WindowsUtils.OpenAssociatedProgram(mainForm.UpdateInstallerPath, updaterArgs, runElevated)){
|
||||||
Application.Exit();
|
Application.Exit();
|
||||||
@@ -174,14 +191,14 @@ namespace TweetDuck{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static string GetDataStoragePath(){
|
private static string GetDataStoragePath(){
|
||||||
string custom = Arguments.GetValue(Arguments.ArgDataFolder, null);
|
string custom = Arguments.GetValue(Arguments.ArgDataFolder);
|
||||||
|
|
||||||
if (custom != null && (custom.Contains(Path.DirectorySeparatorChar) || custom.Contains(Path.AltDirectorySeparatorChar))){
|
if (custom != null && (custom.Contains(Path.DirectorySeparatorChar) || custom.Contains(Path.AltDirectorySeparatorChar))){
|
||||||
if (Path.GetInvalidPathChars().Any(custom.Contains)){
|
if (Path.GetInvalidPathChars().Any(custom.Contains)){
|
||||||
Reporter.HandleEarlyFailure("Data Folder Invalid", "The data folder contains invalid characters:\n"+custom);
|
Reporter.HandleEarlyFailure("Data Folder Invalid", "The data folder contains invalid characters:\n" + custom);
|
||||||
}
|
}
|
||||||
else if (!Path.IsPathRooted(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);
|
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);
|
return Environment.ExpandEnvironmentVariables(custom);
|
||||||
|
2
Properties/Resources.Designer.cs
generated
2
Properties/Resources.Designer.cs
generated
@@ -19,7 +19,7 @@ namespace TweetDuck.Properties {
|
|||||||
// class via a tool like ResGen or Visual Studio.
|
// class via a tool like ResGen or Visual Studio.
|
||||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||||
// with the /str option, or rebuild your VS project.
|
// with the /str option, or rebuild your VS project.
|
||||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")]
|
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
|
||||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||||
internal class Resources {
|
internal class Resources {
|
||||||
|
37
README.md
37
README.md
@@ -1,30 +1,28 @@
|
|||||||
# Support
|
# Support
|
||||||
|
|
||||||
[Follow TweetDuck on Twitter](https://twitter.com/TryMyAwesomeApp) | [Support via PayPal](https://paypal.me/chylex) | [Support via Patreon](https://www.patreon.com/chylex)
|
[Follow TweetDuck on Twitter](https://twitter.com/TryMyAwesomeApp) | [Support via Ko-fi](https://ko-fi.com/chylex) | [Support via Patreon](https://www.patreon.com/chylex)
|
||||||
|
|
||||||
# Build Instructions
|
# Build Instructions
|
||||||
|
|
||||||
### Setup
|
### Setup
|
||||||
|
|
||||||
The program was built using Visual Studio 2017. Before opening the solution, please make sure you have the following workloads and components installed (optional components that are not listed can be deselected to save space):
|
The program can be built using Visual Studio 2019. Before opening the solution, please make sure you have the following workloads and components installed (optional components that are not listed can be deselected to save space):
|
||||||
* **.NET desktop development**
|
* **.NET desktop development**
|
||||||
* .NET Framework 4 – 4.6 development tools
|
* .NET Framework 4.7.2 SDK
|
||||||
* F# desktop language support
|
* F# desktop language support
|
||||||
* **Desktop development with C++**
|
* **Desktop development with C++**
|
||||||
* VC++ 2017 latest v141 tools
|
* MSVC v142 - VS 2019 C++ x64/x86 build tools (v14.20)
|
||||||
|
|
||||||
After opening the solution, right-click the solution and select **Restore NuGet Packages**, or manually run this command in the **Package Manager Console**:
|
After opening the solution, right-click the solution and select **Restore NuGet Packages**, or manually run this command in the **Package Manager Console**:
|
||||||
```
|
```
|
||||||
PM> Install-Package CefSharp.WinForms -Version 67.0.0-CI2662 -Source https://www.myget.org/F/cefsharp/api/v3/index.json
|
PM> Install-Package CefSharp.WinForms -Version 67.0.0
|
||||||
```
|
```
|
||||||
|
|
||||||
Note that some pre-release builds of CefSharp are not available on NuGet. To correctly restore packages in that case, open **Package Manager Settings**, and add `https://www.myget.org/F/cefsharp/api/v3/index.json` to the list of package sources.
|
|
||||||
|
|
||||||
### Debug
|
### Debug
|
||||||
|
|
||||||
The `Debug` configuration uses a separate data folder by default (`%LOCALAPPDATA%\TweetDuckDebug`) to avoid affecting an existing installation of TweetDuck. You can modify this by opening **TweetDuck Properties** in Visual Studio, clicking the **Debug** tab, and changing the **Command line arguments** field.
|
The `Debug` configuration uses a separate data folder by default (`%LOCALAPPDATA%\TweetDuckDebug`) to avoid affecting an existing installation of TweetDuck. You can modify this by opening **TweetDuck Properties** in Visual Studio, clicking the **Debug** tab, and changing the **Command line arguments** field.
|
||||||
|
|
||||||
While debugging, opening the main menu and clicking **Reload browser** automatically rebuilds all resources in the `Resources/Scripts` and `Resources/Plugins` folders. This allows editing HTML/CSS/JS files and applying the changes without restarting the program, but it will cause a short delay between browser reloads.
|
While debugging, opening the main menu and clicking **Reload browser** automatically rebuilds all resources in `Resources/Scripts` and `Resources/Plugins`. This allows editing HTML/CSS/JS files without restarting the program, but it will cause a short delay between browser reloads.
|
||||||
|
|
||||||
### Release
|
### Release
|
||||||
|
|
||||||
@@ -32,7 +30,7 @@ Open **Batch Build**, tick all `Release` configurations with `x86` platform, and
|
|||||||
|
|
||||||
After the build succeeds, the `bin/x86/Release` folder will contain files intended for distribution (no debug symbols or other unnecessary files). You may package these files yourself, or see the [Installers](#installers) section for automated installer generation.
|
After the build succeeds, the `bin/x86/Release` folder will contain files intended for distribution (no debug symbols or other unnecessary files). You may package these files yourself, or see the [Installers](#installers) section for automated installer generation.
|
||||||
|
|
||||||
The `Release` configuration omits debug symbols and other unnecessary files. You can modify this behavior by opening `TweetDuck.csproj`, and editing the `<Target Name="AfterBuild" Condition="$(ConfigurationName) == Release">` section.
|
The `Release` configuration omits debug symbols and other unnecessary files, and it will automatically generate the [update installer](#installers) if the environment is setup correctly. You can modify this behavior by opening `TweetDuck.csproj`, and editing the `<Target Name="AfterBuild" Condition="$(ConfigurationName) == Release">` section.
|
||||||
|
|
||||||
If you decide to publicly release a custom version, please make it clear that it is not an official release of TweetDuck. There are many references to the official website and this repository, especially in the update system, so search for `chylex.com` and `github.com` in all files and replace them appropriately.
|
If you decide to publicly release a custom version, please make it clear that it is not an official release of TweetDuck. There are many references to the official website and this repository, especially in the update system, so search for `chylex.com` and `github.com` in all files and replace them appropriately.
|
||||||
|
|
||||||
@@ -40,37 +38,34 @@ If you decide to publicly release a custom version, please make it clear that it
|
|||||||
|
|
||||||
#### Error: The command (...) exited with code 1
|
#### Error: The command (...) exited with code 1
|
||||||
- This indicates a failed post-build event, open the **Output** tab for logs
|
- This indicates a failed post-build event, open the **Output** tab for logs
|
||||||
- Determine if there was an IO error from the `rmdir` commands, or whether the error was in the **PostBuild.ps1** script (`Encountered an error while running PostBuild.ps1 on line <xyz>`)
|
- Determine if there was an IO error from the `rmdir` commands, the custom MSBuild targets near the end of the [.csproj file](https://github.com/chylex/TweetDuck/blob/master/TweetDuck.csproj), or in the **PostBuild.fsx** script (`Encountered an error while running PostBuild`)
|
||||||
- Some files are checked for invalid characters:
|
- Some files are checked for invalid characters:
|
||||||
- `Resources/Plugins/emoji-keyboard/emoji-ordering.txt` line endings must be LF (line feed); any CR (carriage return) in the file will cause a failed build, and you will need to ensure correct line endings in your text editor
|
- `Resources/Plugins/emoji-keyboard/emoji-ordering.txt` line endings must be LF (line feed); any CR (carriage return) in the file will cause a failed build, and you will need to ensure correct line endings in your text editor
|
||||||
|
|
||||||
#### Error: The "EmbedAllSources" parameter is not supported by the "Csc" task
|
|
||||||
1. Open `C:\Program Files (x86)\Visual Studio\2017\<edition>\MSBuild\15.0\Bin\Microsoft.CSharp.CurrentVersion.targets` in a text editor
|
|
||||||
2. Remove line that says `EmbedAllSources="$(EmbedAllSources)"`
|
|
||||||
3. Hope the next Visual Studio update fixes it...
|
|
||||||
|
|
||||||
### Installers
|
### Installers
|
||||||
|
|
||||||
TweetDuck uses **Inno Setup** for installers and updates. First, download and install [InnoSetup QuickStart Pack](http://www.jrsoftware.org/isdl.php) (non-unicode; editor and encryption support not required) and the [Inno Download Plugin](https://code.google.com/archive/p/inno-download-plugin).
|
TweetDuck uses **Inno Setup** for installers and updates. First, download and install [InnoSetup 5.6.1](http://files.jrsoftware.org/is/5/innosetup-5.6.1.exe) (with Preprocessor support) and the [Inno Download Plugin 1.5.0](https://drive.google.com/folderview?id=0Bzw1xBVt0mokSXZrUEFIanV4azA&usp=sharing#list).
|
||||||
|
|
||||||
Next, add the Inno Setup installation folder (usually `C:\Program Files (x86)\Inno Setup 5`) into your **PATH** environment variable. You may need to restart File Explorer for the change to take place.
|
Next, add the Inno Setup installation folder (usually `C:\Program Files (x86)\Inno Setup 5`) into your **PATH** environment variable. You may need to restart File Explorer and Visual Studio for the change to take place.
|
||||||
|
|
||||||
Now you can generate installers after a build by running `bld/RUN BUILD.bat`. Note that this will only package the files, you still need to run the [release build](#release) in Visual Studio!
|
Now you can generate installers by running `bld/GEN INSTALLERS.bat`. Note that this will only package the files, you still need to run the [release build](#release) in Visual Studio first!
|
||||||
|
|
||||||
After the window closes, three installers will be generated inside the `bld/Output` folder:
|
After the window closes, three installers will be generated inside the `bld/Output` folder:
|
||||||
* **TweetDuck.exe**
|
* **TweetDuck.exe**
|
||||||
* This is the main installer that creates entries in the Start Menu & Programs and Features, and an optional desktop icon
|
* This is the main installer that creates entries in the Start Menu & Programs and Features, and an optional desktop icon
|
||||||
* **TweetDuck.Update.exe**
|
* **TweetDuck.Update.exe**
|
||||||
* This is a lightweight update installer that only contains the most important files that usually change across releases
|
* This is a lightweight update installer that only contains the most important files that usually change across releases
|
||||||
* It will automatically download and apply the full installer if the user's current version of CEF does not match (the download link is in `gen_upd.iss` and points to this repository by default)
|
* It will automatically download and apply the full installer if the user's current version of CEF does not match
|
||||||
* **TweetDuck.Portable.exe**
|
* **TweetDuck.Portable.exe**
|
||||||
* This is a portable installer that does not need administrator privileges
|
* This is a portable installer that does not need administrator privileges
|
||||||
* It automatically creates a `makeportable` file in the program folder, which forces TweetDuck to run in portable mode
|
* It automatically creates a `makeportable` file in the program folder, which forces TweetDuck to run in portable mode
|
||||||
|
|
||||||
|
The installers are built for GitHub Releases, where the main and portable installers can download and install Visual C++ if it's missing, and the update installer will download and apply the full installer when needed. If you plan to distribute your own installers via GitHub, you can change the variables in the installer files (`.iss`) and in the update system to point to your repository, and use the power of the existing update system.
|
||||||
|
|
||||||
#### Notes
|
#### Notes
|
||||||
|
|
||||||
> When opening **Batch Build**, you will also see `x64` and `AnyCPU` configurations. These are visible due to what I consider a Visual Studio bug, and will not work without significant changes to the project. Manually running the `Resources/PostCefUpdate.ps1` PowerShell script modifies the downloaded CefSharp packages, and removes the invalid configurations.
|
> When opening **Batch Build**, you will also see `x64` and `AnyCPU` configurations. These are visible due to what I consider a Visual Studio bug, and will not work without significant changes to the project. Manually running the `Resources/PostCefUpdate.ps1` PowerShell script modifies the downloaded CefSharp packages, and removes the invalid configurations.
|
||||||
|
|
||||||
> There is a small chance running `RUN BUILD.bat` immediately shows a resource error. If that happens, close the console window (which terminates all Inno Setup processes and leaves corrupted installers in the output folder), and run it again.
|
> There is a small chance running `GEN INSTALLERS.bat` immediately shows a resource error. If that happens, close the console window (which terminates all Inno Setup processes and leaves corrupted installers in the output folder), and run it again.
|
||||||
|
|
||||||
> Running `RUN BUILD.bat` uses about 400 MB of RAM due to high compression. You can lower this to about 140 MB by opening `gen_full.iss` and `gen_port.iss`, and changing `LZMADictionarySize=15360` to `LZMADictionarySize=4096`.
|
> Running `GEN INSTALLERS.bat` uses about 400 MB of RAM due to high compression. You can lower this to about 140 MB by opening `gen_full.iss` and `gen_port.iss`, and changing `LZMADictionarySize=15360` to `LZMADictionarySize=4096`.
|
||||||
|
31
Reporter.cs
31
Reporter.cs
@@ -4,10 +4,13 @@ using System.Drawing;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
|
using TweetDuck.Configuration;
|
||||||
using TweetDuck.Core.Other;
|
using TweetDuck.Core.Other;
|
||||||
|
using TweetLib.Core;
|
||||||
|
using TweetLib.Core.Application;
|
||||||
|
|
||||||
namespace TweetDuck{
|
namespace TweetDuck{
|
||||||
sealed class Reporter{
|
sealed class Reporter : IAppErrorHandler{
|
||||||
private readonly string logFile;
|
private readonly string logFile;
|
||||||
|
|
||||||
public Reporter(string logFile){
|
public Reporter(string logFile){
|
||||||
@@ -16,15 +19,23 @@ namespace TweetDuck{
|
|||||||
|
|
||||||
public void SetupUnhandledExceptionHandler(string caption){
|
public void SetupUnhandledExceptionHandler(string caption){
|
||||||
AppDomain.CurrentDomain.UnhandledException += (sender, args) => {
|
AppDomain.CurrentDomain.UnhandledException += (sender, args) => {
|
||||||
if (args.ExceptionObject is Exception ex) {
|
if (args.ExceptionObject is Exception ex){
|
||||||
HandleException(caption, "An unhandled exception has occurred.", false, ex);
|
HandleException(caption, "An unhandled exception has occurred.", false, ex);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Log(string data){
|
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
|
#if DEBUG
|
||||||
Debug.WriteLine(data);
|
Debug.WriteLine(text);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
StringBuilder build = new StringBuilder();
|
StringBuilder build = new StringBuilder();
|
||||||
@@ -33,8 +44,8 @@ namespace TweetDuck{
|
|||||||
build.Append("Please, report all issues to: https://github.com/chylex/TweetDuck/issues\r\n\r\n");
|
build.Append("Please, report all issues to: https://github.com/chylex/TweetDuck/issues\r\n\r\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
build.Append("[").Append(DateTime.Now.ToString("G", Program.Culture)).Append("]\r\n");
|
build.Append("[").Append(DateTime.Now.ToString("G", Lib.Culture)).Append("]\r\n");
|
||||||
build.Append(data).Append("\r\n\r\n");
|
build.Append(text).Append("\r\n\r\n");
|
||||||
|
|
||||||
try{
|
try{
|
||||||
File.AppendAllText(logFile, build.ToString(), Encoding.UTF8);
|
File.AppendAllText(logFile, build.ToString(), Encoding.UTF8);
|
||||||
@@ -45,10 +56,10 @@ namespace TweetDuck{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void HandleException(string caption, string message, bool canIgnore, Exception e){
|
public void HandleException(string caption, string message, bool canIgnore, Exception e){
|
||||||
bool loggedSuccessfully = Log(e.ToString());
|
bool loggedSuccessfully = LogImportant(e.ToString());
|
||||||
|
|
||||||
string exceptionText = e is ExpandedLogException ? e.Message+"\n\nDetails with potentially sensitive information are in the Error Log." : e.Message;
|
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);
|
||||||
|
|
||||||
Button btnExit = form.AddButton(FormMessage.Exit);
|
Button btnExit = form.AddButton(FormMessage.Exit);
|
||||||
Button btnIgnore = form.AddButton(FormMessage.Ignore, DialogResult.Ignore, ControlType.Cancel);
|
Button btnIgnore = form.AddButton(FormMessage.Ignore, DialogResult.Ignore, ControlType.Cancel);
|
||||||
@@ -104,7 +115,7 @@ namespace TweetDuck{
|
|||||||
this.details = details;
|
this.details = details;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString() => base.ToString()+"\r\n"+details;
|
public override string ToString() => base.ToString() + "\r\n" + details;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
3
Resources/Plugins/.debug/notification.js
Normal file
3
Resources/Plugins/.debug/notification.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
run(){
|
||||||
|
console.info("executed debug plugin in notification");
|
||||||
|
}
|
@@ -87,14 +87,14 @@ enabled(){
|
|||||||
// update UI
|
// update UI
|
||||||
|
|
||||||
this.btnClearAllHTML = `
|
this.btnClearAllHTML = `
|
||||||
<a class="clear-columns-btn-all-parent js-header-action link-clean cf app-nav-link padding-h--10" data-title="Clear columns (hold Shift to restore)" data-action="td-clearcolumns-doall">
|
<a class="clear-columns-btn-all-parent js-header-action link-clean cf app-nav-link padding-h--16 padding-v--2" data-title="Clear columns (hold Shift to restore)" data-action="td-clearcolumns-doall">
|
||||||
<div class="obj-left margin-l--2"><i class="icon icon-medium icon-clear-timeline"></i></div>
|
<div class="obj-left margin-l--2"><i class="icon icon-medium icon-clear-timeline"></i></div>
|
||||||
<div class="clear-columns-btn-all nbfc padding-ts hide-condensed txt-size--16 app-nav-link-text">Clear columns</div>
|
<div class="clear-columns-btn-all nbfc padding-ts hide-condensed txt-size--14 app-nav-link-text">Clear columns</div>
|
||||||
</a>`;
|
</a>`;
|
||||||
|
|
||||||
this.btnClearOneHTML = `
|
this.btnClearOneHTML = `
|
||||||
<a class="js-action-header-button column-header-link" href="#" data-action="td-clearcolumns-dosingle">
|
<a class="js-action-header-button column-header-link" href="#" data-action="td-clearcolumns-dosingle">
|
||||||
<i class="icon icon-clear-timeline js-show-tip" data-placement="bottom" data-original-title="Clear column (hold Shift to restore)" data-action="td-clearcolumns-dosingle"></i>
|
<i class="icon icon-clear-timeline js-show-tip" data-placement="bottom" data-original-title="Clear column (hold Shift to restore)" data-action="td-clearcolumns-dosingle"></i>
|
||||||
</a>`;
|
</a>`;
|
||||||
|
|
||||||
this.prevNavMenuMustache = TD.mustaches["menus/column_nav_menu.mustache"];
|
this.prevNavMenuMustache = TD.mustaches["menus/column_nav_menu.mustache"];
|
||||||
@@ -110,16 +110,28 @@ enabled(){
|
|||||||
|
|
||||||
// styles
|
// styles
|
||||||
|
|
||||||
|
if (!document.getElementById("td-clearcolumns-workaround")){
|
||||||
|
// TD started caching mustaches so disabling the plugin doesn't update the column headers properly...
|
||||||
|
let workaround = document.createElement("style");
|
||||||
|
workaround.id = "td-clearcolumns-workaround";
|
||||||
|
workaround.innerText = "#tduck a[data-action='td-clearcolumns-dosingle'] { display: none }";
|
||||||
|
document.head.appendChild(workaround);
|
||||||
|
}
|
||||||
|
|
||||||
this.css = window.TDPF_createCustomStyle(this);
|
this.css = window.TDPF_createCustomStyle(this);
|
||||||
|
|
||||||
this.css.insert(".js-app-add-column.is-hidden + .clear-columns-btn-all-parent { display: none; }");
|
this.css.insert(".js-app-add-column.is-hidden + .clear-columns-btn-all-parent { display: none; }");
|
||||||
this.css.insert(".column-header-links { min-width: 51px !important; }");
|
|
||||||
this.css.insert("[data-td-icon='icon-message'] .column-header-links { min-width: 110px !important; }");
|
|
||||||
this.css.insert(".column-navigator-overflow .clear-columns-btn-all-parent { display: none !important; }");
|
this.css.insert(".column-navigator-overflow .clear-columns-btn-all-parent { display: none !important; }");
|
||||||
this.css.insert(".column-navigator-overflow { bottom: 224px !important; }");
|
this.css.insert(".column-navigator-overflow { bottom: 224px !important; }");
|
||||||
this.css.insert("[data-action='td-clearcolumns-dosingle'] { padding: 3px 0 !important; }");
|
this.css.insert(".app-navigator .clear-columns-btn-all-parent { font-weight: 700; }");
|
||||||
this.css.insert("[data-action='clear'].btn-options-tray { display: none !important; }");
|
|
||||||
this.css.insert("[data-td-icon='icon-schedule'] .td-clear-column-shortcut { display: none; }");
|
this.css.insert(".column-header-links { min-width: 51px !important; }");
|
||||||
this.css.insert("[data-td-icon='icon-custom-timeline'] .td-clear-column-shortcut { display: none; }");
|
this.css.insert(".column[data-td-icon='icon-message'] .column-header-links { min-width: 110px !important; }");
|
||||||
|
this.css.insert(".btn-options-tray[data-action='clear'] { display: none !important; }");
|
||||||
|
|
||||||
|
this.css.insert("#tduck a[data-action='td-clearcolumns-dosingle'] { display: inline-block; }");
|
||||||
|
this.css.insert("#tduck .column[data-td-icon='icon-schedule'] a[data-action='td-clearcolumns-dosingle'] { display: none; }");
|
||||||
|
this.css.insert("#tduck .column[data-td-icon='icon-custom-timeline'] a[data-action='td-clearcolumns-dosingle'] { display: none; }");
|
||||||
}
|
}
|
||||||
|
|
||||||
ready(){
|
ready(){
|
||||||
|
@@ -2,19 +2,20 @@ enabled(){
|
|||||||
// elements & data
|
// elements & data
|
||||||
this.css = null;
|
this.css = null;
|
||||||
this.icons = null;
|
this.icons = null;
|
||||||
this.htmlModal = null;
|
|
||||||
this.config = null;
|
this.config = null;
|
||||||
|
|
||||||
this.defaultConfig = {
|
this.defaultConfig = {
|
||||||
_theme: "light",
|
_theme: "light",
|
||||||
themeOverride: false,
|
themeOverride: false,
|
||||||
columnWidth: "310px",
|
columnWidth: "310px",
|
||||||
|
composerWidth: "default",
|
||||||
fontSize: "12px",
|
fontSize: "12px",
|
||||||
hideTweetActions: true,
|
hideTweetActions: true,
|
||||||
moveTweetActionsToRight: true,
|
moveTweetActionsToRight: true,
|
||||||
themeColorTweaks: true,
|
themeColorTweaks: true,
|
||||||
revertIcons: true,
|
revertIcons: true,
|
||||||
showCharacterCount: true,
|
showCharacterCount: true,
|
||||||
|
forceArialFont: true,
|
||||||
increaseQuoteTextSize: false,
|
increaseQuoteTextSize: false,
|
||||||
smallComposeTextSize: false,
|
smallComposeTextSize: false,
|
||||||
optimizeAnimations: true,
|
optimizeAnimations: true,
|
||||||
@@ -41,13 +42,6 @@ enabled(){
|
|||||||
|
|
||||||
const me = this;
|
const me = this;
|
||||||
|
|
||||||
// modal dialog loading
|
|
||||||
$TDP.readFileRoot(this.$token, "modal.html").then(contents => {
|
|
||||||
this.htmlModal = contents;
|
|
||||||
}).catch(err => {
|
|
||||||
$TD.alert("error", "Problem loading data for the design edit plugin: "+err.message);
|
|
||||||
});
|
|
||||||
|
|
||||||
// configuration
|
// configuration
|
||||||
const configFile = "config.json";
|
const configFile = "config.json";
|
||||||
|
|
||||||
@@ -112,7 +106,7 @@ enabled(){
|
|||||||
|
|
||||||
// settings click event
|
// settings click event
|
||||||
this.onSettingsMenuClickedEvent = () => {
|
this.onSettingsMenuClickedEvent = () => {
|
||||||
return if this.htmlModal === null || this.config === null;
|
return if this.config === null;
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
let menu = $(".js-dropdown-content").children("ul").first();
|
let menu = $(".js-dropdown-content").children("ul").first();
|
||||||
@@ -181,20 +175,26 @@ enabled(){
|
|||||||
// SELECTS
|
// SELECTS
|
||||||
else if (tag === "SELECT"){
|
else if (tag === "SELECT"){
|
||||||
let optionCustom = item.find("option[value^='custom']");
|
let optionCustom = item.find("option[value^='custom']");
|
||||||
|
let optionCustomNew = item.find("option[value^='change-custom']");
|
||||||
|
|
||||||
let resetMyValue = () => {
|
let resetMyValue = () => {
|
||||||
if (!item.val(me.config[key]).val() && optionCustom.length === 1){
|
if (!item.val(me.config[key]).val() && optionCustom.length === 1){
|
||||||
item.val(optionCustom.attr("value"));
|
item.val(optionCustom.attr("value"));
|
||||||
optionCustom.text(getTextForCustom(key));
|
optionCustom.text(getTextForCustom(key));
|
||||||
|
optionCustomNew.show();
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
optionCustom.text("Custom");
|
||||||
|
optionCustomNew.hide();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
resetMyValue();
|
resetMyValue();
|
||||||
|
|
||||||
item.change(function(){ // TODO change doesn't fire when Custom is already selected
|
item.change(function(){
|
||||||
let val = item.val();
|
let val = item.val();
|
||||||
|
|
||||||
if (val === "custom-px"){
|
if (val.endsWith("custom-px")){
|
||||||
val = (prompt("Enter custom value (px):") || "").trim();
|
val = (prompt("Enter custom value (px):") || "").trim();
|
||||||
|
|
||||||
if (val){
|
if (val){
|
||||||
@@ -204,21 +204,17 @@ enabled(){
|
|||||||
|
|
||||||
if (/^[0-9]+$/.test(val)){
|
if (/^[0-9]+$/.test(val)){
|
||||||
updateKey(key, val+"px");
|
updateKey(key, val+"px");
|
||||||
optionCustom.text(getTextForCustom(key));
|
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
alert("Invalid value, only px values are supported.");
|
alert("Invalid value, only px values are supported.");
|
||||||
resetMyValue();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else{
|
|
||||||
resetMyValue();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
updateKey(key, item.val());
|
updateKey(key, item.val());
|
||||||
optionCustom.text("Custom");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resetMyValue();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// CUSTOM ELEMENTS
|
// CUSTOM ELEMENTS
|
||||||
@@ -260,7 +256,7 @@ enabled(){
|
|||||||
|
|
||||||
setTimeout(function(){
|
setTimeout(function(){
|
||||||
if (theme != TD.settings.getTheme()){
|
if (theme != TD.settings.getTheme()){
|
||||||
$(document).trigger("uiToggleTheme");
|
TD.settings.setTheme(theme);
|
||||||
}
|
}
|
||||||
|
|
||||||
me.saveConfig();
|
me.saveConfig();
|
||||||
@@ -268,13 +264,17 @@ enabled(){
|
|||||||
}, 1);
|
}, 1);
|
||||||
});
|
});
|
||||||
}).methods({
|
}).methods({
|
||||||
_render: () => $(this.htmlModal),
|
_render: function(){
|
||||||
|
return $(me.htmlModal);
|
||||||
|
},
|
||||||
destroy: function(){
|
destroy: function(){
|
||||||
if (this.reloadPage){
|
if (this.reloadPage){
|
||||||
window.TDPF_requestReload();
|
window.TDPF_requestReload();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
delete me.htmlModal;
|
||||||
|
|
||||||
$("#td-design-plugin-modal").hide();
|
$("#td-design-plugin-modal").hide();
|
||||||
this.supr();
|
this.supr();
|
||||||
}
|
}
|
||||||
@@ -381,8 +381,15 @@ enabled(){
|
|||||||
this.css.insert("#general_settings .cf { display: none !important }");
|
this.css.insert("#general_settings .cf { display: none !important }");
|
||||||
this.css.insert("#settings-modal .js-setting-list li:nth-child(3) { border-bottom: 1px solid #ccd6dd }");
|
this.css.insert("#settings-modal .js-setting-list li:nth-child(3) { border-bottom: 1px solid #ccd6dd }");
|
||||||
|
|
||||||
this.css.insert("html[data-td-font] { font-size: "+this.config.fontSize+" !important }");
|
this.css.insert(`html[data-td-font] { font-size: ${this.config.fontSize} !important }`);
|
||||||
this.css.insert(".avatar { border-radius: "+this.config.avatarRadius+"% !important }");
|
this.css.insert(`.avatar { border-radius: ${this.config.avatarRadius}% !important }`);
|
||||||
|
|
||||||
|
if (this.config.composerWidth !== "default"){
|
||||||
|
const width = this.config.composerWidth;
|
||||||
|
this.css.insert(`.js-app-content.is-open { margin-right: ${width} !important; transform: translateX(${width}) !important }`);
|
||||||
|
this.css.insert(`#tduck .js-app-content.tduck-is-opening { margin-right: 0 !important }`);
|
||||||
|
this.css.insert(`.js-drawer { width: ${width} !important; left: -${width} !important }`);
|
||||||
|
}
|
||||||
|
|
||||||
let currentTheme = TD.settings.getTheme();
|
let currentTheme = TD.settings.getTheme();
|
||||||
|
|
||||||
@@ -432,6 +439,11 @@ enabled(){
|
|||||||
this.css.insert("#tduck .tweet-actions > li:nth-child(4) { margin-right: 2px !important }");
|
this.css.insert("#tduck .tweet-actions > li:nth-child(4) { margin-right: 2px !important }");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.config.forceArialFont){
|
||||||
|
this.css.insert("#tduck { font-family: Arial, sans-serif; font-weight: 400 }");
|
||||||
|
this.css.insert("#tduck input, #tduck label, #tduck select, #tduck textarea { font-family: Arial }")
|
||||||
|
}
|
||||||
|
|
||||||
if (this.config.increaseQuoteTextSize){
|
if (this.config.increaseQuoteTextSize){
|
||||||
this.css.insert(".quoted-tweet { font-size: 1em !important }");
|
this.css.insert(".quoted-tweet { font-size: 1em !important }");
|
||||||
}
|
}
|
||||||
@@ -538,11 +550,13 @@ ${iconData.map(entry => `#tduck .icon-${entry[0]}:before{content:\"\\f0${entry[1
|
|||||||
|
|
||||||
.drawer .btn .icon, .app-header .btn .icon { line-height: 1em !important }
|
.drawer .btn .icon, .app-header .btn .icon { line-height: 1em !important }
|
||||||
.app-search-fake .icon { margin-top: -3px !important }
|
.app-search-fake .icon { margin-top: -3px !important }
|
||||||
#tduck .js-docked-compose .js-drawer-close { margin: 20px 0 0 !important }
|
|
||||||
#tduck .search-input-control .icon { font-size: 20px !important; top: -4px !important }
|
#tduck .search-input-control .icon { font-size: 20px !important; top: -4px !important }
|
||||||
|
#tduck .js-docked-compose .js-drawer-close { margin: 20px 0 0 !important }
|
||||||
|
#tduck .compose-media-bar-remove .icon-close, #tduck .compose-media-grid-remove .icon-close { padding: 3px 2px 1px !important }
|
||||||
|
|
||||||
.js-column-header .column-type-icon { margin-top: 0 !important }
|
.js-column-header .column-type-icon { margin-top: 0 !important }
|
||||||
.inline-reply .pull-left .Button--link { margin-top: 3px !important }
|
.inline-reply .pull-left .Button--link { margin-top: 3px !important }
|
||||||
|
.js-inline-compose-pop .icon-popout { font-size: 23px !important }
|
||||||
|
|
||||||
.tweet-action-item .icon-favorite-toggle { font-size: 16px !important; }
|
.tweet-action-item .icon-favorite-toggle { font-size: 16px !important; }
|
||||||
.tweet-action-item .heartsprite { top: -260% !important; left: -260% !important; transform: scale(0.4, 0.39) translateY(0.5px) !important; }
|
.tweet-action-item .heartsprite { top: -260% !important; left: -260% !important; transform: scale(0.4, 0.39) translateY(0.5px) !important; }
|
||||||
@@ -564,7 +578,7 @@ ${iconData.map(entry => `#tduck .icon-${entry[0]}:before{content:\"\\f0${entry[1
|
|||||||
let cols = this.config.columnWidth.slice(1);
|
let cols = this.config.columnWidth.slice(1);
|
||||||
|
|
||||||
this.css.insert(".column { width: calc((100vw - 205px) / "+cols+" - 6px) !important; min-width: 160px }");
|
this.css.insert(".column { width: calc((100vw - 205px) / "+cols+" - 6px) !important; min-width: 160px }");
|
||||||
this.css.insert(".is-condensed .column { width: calc((100vw - 55px) / "+cols+" - 6px) !important }");
|
this.css.insert(".is-condensed .column { width: calc((100vw - 65px) / "+cols+" - 6px) !important }");
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
this.css.insert(".column { width: "+this.config.columnWidth+" !important }");
|
this.css.insert(".column { width: "+this.config.columnWidth+" !important }");
|
||||||
@@ -597,6 +611,10 @@ ${iconData.map(entry => `#tduck .icon-${entry[0]}:before{content:\"\\f0${entry[1
|
|||||||
html[data-td-font] { font-size: ${this.config.fontSize} !important }
|
html[data-td-font] { font-size: ${this.config.fontSize} !important }
|
||||||
.avatar { border-radius: ${this.config.avatarRadius}% !important }
|
.avatar { border-radius: ${this.config.avatarRadius}% !important }
|
||||||
|
|
||||||
|
${this.config.forceArialFont ? `
|
||||||
|
#tduck { font-family: Arial, sans-serif; font-weight: 400 }
|
||||||
|
` : ``}
|
||||||
|
|
||||||
${this.config.increaseQuoteTextSize ? `
|
${this.config.increaseQuoteTextSize ? `
|
||||||
.quoted-tweet { font-size: 1em !important }
|
.quoted-tweet { font-size: 1em !important }
|
||||||
` : ``}
|
` : ``}
|
||||||
@@ -628,6 +646,13 @@ ${notificationScrollbarColor ? `
|
|||||||
$(".js-dropdown.pos-r").toggleClass("pos-r pos-l");
|
$(".js-dropdown.pos-r").toggleClass("pos-r pos-l");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.uiDrawerActiveEvent = (e, data) => {
|
||||||
|
return if data.activeDrawer === null || this.config.composerWidth === "default";
|
||||||
|
|
||||||
|
const ele = $(".js-app-content").addClass("tduck-is-opening");
|
||||||
|
setTimeout(() => ele.removeClass("tduck-is-opening"), 250);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
ready(){
|
ready(){
|
||||||
@@ -637,6 +662,7 @@ ready(){
|
|||||||
|
|
||||||
// layout events
|
// layout events
|
||||||
$(document).on("uiShowActionsMenu", this.uiShowActionsMenuEvent);
|
$(document).on("uiShowActionsMenu", this.uiShowActionsMenuEvent);
|
||||||
|
$(document).on("uiDrawerActive", this.uiDrawerActiveEvent);
|
||||||
|
|
||||||
// modal
|
// modal
|
||||||
$("[data-action='settings-menu']").on("click", this.onSettingsMenuClickedEvent);
|
$("[data-action='settings-menu']").on("click", this.onSettingsMenuClickedEvent);
|
||||||
@@ -670,7 +696,12 @@ ready(){
|
|||||||
}
|
}
|
||||||
|
|
||||||
configure(){
|
configure(){
|
||||||
new this.customDesignModal();
|
$TDP.readFileRoot(this.$token, "modal.html").then(contents => {
|
||||||
|
this.htmlModal = contents;
|
||||||
|
new this.customDesignModal();
|
||||||
|
}).catch(err => {
|
||||||
|
$TD.alert("error", "Error loading the configuration dialog: "+err.message);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
disabled(){
|
disabled(){
|
||||||
@@ -694,10 +725,12 @@ disabled(){
|
|||||||
window.clearTimeout(this.optimizationTimer);
|
window.clearTimeout(this.optimizationTimer);
|
||||||
}
|
}
|
||||||
|
|
||||||
$(document).off("uiShowActionsMenu", this.uiShowActionsMenuEvent);
|
|
||||||
$(window).off("focus", this.onWindowFocusEvent);
|
$(window).off("focus", this.onWindowFocusEvent);
|
||||||
$(window).off("blur", this.onWindowBlurEvent);
|
$(window).off("blur", this.onWindowBlurEvent);
|
||||||
|
|
||||||
|
$(document).off("uiShowActionsMenu", this.uiShowActionsMenuEvent);
|
||||||
|
$(document).off("uiDrawerActive", this.uiDrawerActiveEvent);
|
||||||
|
|
||||||
TD.components.GlobalSettings.prototype.getInfo = this.prevFuncSettingsGetInfo;
|
TD.components.GlobalSettings.prototype.getInfo = this.prevFuncSettingsGetInfo;
|
||||||
TD.components.GlobalSettings.prototype.switchTab = this.prevFuncSettingsSwitchTab;
|
TD.components.GlobalSettings.prototype.switchTab = this.prevFuncSettingsSwitchTab;
|
||||||
|
|
||||||
|
@@ -41,6 +41,7 @@
|
|||||||
<option value="350px">Wide (350px)</option>
|
<option value="350px">Wide (350px)</option>
|
||||||
<option value="400px">Extreme (400px)</option>
|
<option value="400px">Extreme (400px)</option>
|
||||||
<option value="custom-px">Custom</option>
|
<option value="custom-px">Custom</option>
|
||||||
|
<option value="change-custom-px">Change custom value...</option>
|
||||||
</optgroup>
|
</optgroup>
|
||||||
<option disabled></option>
|
<option disabled></option>
|
||||||
<optgroup label="Dynamic width">
|
<optgroup label="Dynamic width">
|
||||||
@@ -54,6 +55,20 @@
|
|||||||
<option disabled></option>
|
<option disabled></option>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<!-- COMPOSER SIZE -->
|
||||||
|
|
||||||
|
<label class="txt-uppercase touch-larger-label">
|
||||||
|
<b>New Tweet panel</b>
|
||||||
|
</label>
|
||||||
|
<select data-td-key="composerWidth">
|
||||||
|
<option value="default">Default</option>
|
||||||
|
<option value="270px">Narrow (270px)</option>
|
||||||
|
<option value="310px">Medium (310px)</option>
|
||||||
|
<option value="350px">Wide (350px)</option>
|
||||||
|
<option value="custom-px">Custom</option>
|
||||||
|
<option value="change-custom-px">Change custom value...</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
<!-- FONT SIZE -->
|
<!-- FONT SIZE -->
|
||||||
|
|
||||||
<label class="txt-uppercase touch-larger-label">
|
<label class="txt-uppercase touch-larger-label">
|
||||||
@@ -66,6 +81,7 @@
|
|||||||
<option value="15px">Large (15px)</option>
|
<option value="15px">Large (15px)</option>
|
||||||
<option value="16px">Largest (16px)</option>
|
<option value="16px">Largest (16px)</option>
|
||||||
<option value="custom-px">Custom</option>
|
<option value="custom-px">Custom</option>
|
||||||
|
<option value="change-custom-px">Change custom value...</option>
|
||||||
</select>
|
</select>
|
||||||
<label class="checkbox">
|
<label class="checkbox">
|
||||||
<input data-td-key="increaseQuoteTextSize" class="js-theme-checkbox touch-larger-label" type="checkbox">
|
<input data-td-key="increaseQuoteTextSize" class="js-theme-checkbox touch-larger-label" type="checkbox">
|
||||||
@@ -110,6 +126,10 @@
|
|||||||
<input data-td-key="themeColorTweaks" class="js-theme-checkbox touch-larger-label" type="checkbox">
|
<input data-td-key="themeColorTweaks" class="js-theme-checkbox touch-larger-label" type="checkbox">
|
||||||
Theme color tweaks
|
Theme color tweaks
|
||||||
</label>
|
</label>
|
||||||
|
<label class="checkbox">
|
||||||
|
<input data-td-key="forceArialFont" class="js-theme-checkbox touch-larger-label" type="checkbox">
|
||||||
|
Use Arial as default font
|
||||||
|
</label>
|
||||||
|
|
||||||
<!-- ADVANCED -->
|
<!-- ADVANCED -->
|
||||||
|
|
||||||
@@ -166,13 +186,24 @@
|
|||||||
|
|
||||||
#edit-design-panel {
|
#edit-design-panel {
|
||||||
width: 693px;
|
width: 693px;
|
||||||
height: 380px;
|
height: 424px;
|
||||||
|
background-color: #FFF;
|
||||||
|
box-shadow: 0 0 10px rgba(17, 17, 17, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
#edit-design-panel .mdl-header {
|
||||||
|
color: #8899A6;
|
||||||
}
|
}
|
||||||
|
|
||||||
#edit-design-panel .mdl-inner {
|
#edit-design-panel .mdl-inner {
|
||||||
padding-top: 0;
|
padding-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#edit-design-panel .mdl-content {
|
||||||
|
border: 1px solid #CCD6DD;
|
||||||
|
background: #EAEAEA;
|
||||||
|
}
|
||||||
|
|
||||||
#edit-design-panel-inner-cols {
|
#edit-design-panel-inner-cols {
|
||||||
padding: 0 6px;
|
padding: 0 6px;
|
||||||
}
|
}
|
||||||
@@ -200,7 +231,7 @@
|
|||||||
|
|
||||||
#edit-design-panel-content label.radio {
|
#edit-design-panel-content label.radio {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin: 0 16px 5px 4px;
|
margin: 0 16px 0 4px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -117,7 +117,7 @@ html.dark .account-settings-bt{border-top:1px solid #e1e8ed}
|
|||||||
html.dark .account-settings-bb{border-bottom:1px solid #e1e8ed}
|
html.dark .account-settings-bb{border-bottom:1px solid #e1e8ed}
|
||||||
html.dark .account-stats a{color:#66757f}
|
html.dark .account-stats a{color:#66757f}
|
||||||
html.dark .account-stats a:hover{color:#2b7bb9}
|
html.dark .account-stats a:hover{color:#2b7bb9}
|
||||||
html.dark .column{background-color:#222426}
|
html.dark .column-panel{background-color:#222426}
|
||||||
html.dark .column.is-focused{box-shadow:0 0 0 6px #7aa2c0}
|
html.dark .column.is-focused{box-shadow:0 0 0 6px #7aa2c0}
|
||||||
html.dark .column-background-fill{background-color:#F5F8FA}
|
html.dark .column-background-fill{background-color:#F5F8FA}
|
||||||
html.dark .more-tweets-glow{background-color:#55acee;background:radial-gradient(ellipse farthest-corner at 50% 100%,#55acee 0%,#55acee 25%,rgba(255,255,255,0) 75%)}
|
html.dark .more-tweets-glow{background-color:#55acee;background:radial-gradient(ellipse farthest-corner at 50% 100%,#55acee 0%,#55acee 25%,rgba(255,255,255,0) 75%)}
|
||||||
@@ -135,7 +135,6 @@ html.dark .column-header{background-color:#292F33}
|
|||||||
html.dark .is-inverted-dark .column-header{border-bottom:1px solid #ddd}
|
html.dark .is-inverted-dark .column-header{border-bottom:1px solid #ddd}
|
||||||
html.dark .is-inverted-dark .column-title-edit-box{color:#111;background-color:#fff;border-color:#e1e8ed}
|
html.dark .is-inverted-dark .column-title-edit-box{color:#111;background-color:#fff;border-color:#e1e8ed}
|
||||||
html.dark .column-header{border-bottom:1px solid #222426}
|
html.dark .column-header{border-bottom:1px solid #222426}
|
||||||
html.dark .column-header-temp{border-bottom:1px solid #ddd}
|
|
||||||
html.dark .column-title-edit-box{color:#e1e8ed;background-color:#14171A;border-color:#111}
|
html.dark .column-title-edit-box{color:#e1e8ed;background-color:#14171A;border-color:#111}
|
||||||
html.dark .column-number{color:#66757f}
|
html.dark .column-number{color:#66757f}
|
||||||
html.dark .is-new .column-type-icon{color:#55acee}
|
html.dark .is-new .column-type-icon{color:#55acee}
|
||||||
@@ -428,8 +427,7 @@ html.dark .list-account .username{color:#8899a6}
|
|||||||
html.dark .list-listmember .username{color:#8899a6}
|
html.dark .list-listmember .username{color:#8899a6}
|
||||||
html.dark .list-listmember .bio{color:#657786}
|
html.dark .list-listmember .bio{color:#657786}
|
||||||
html.dark .divider-bar{background-color:#ddd}
|
html.dark .divider-bar{background-color:#ddd}
|
||||||
html.dark select{background-color:#fff}
|
html.dark input,html.dark textarea,html.dark select{color:#111;border:1px solid #e1e8ed;background-color:#fff}
|
||||||
html.dark input,html.dark textarea,html.dark select{color:#111;border:1px solid #e1e8ed}
|
|
||||||
html.dark input:disabled{background-color:#eaeaea;border-color:#e1e8ed}
|
html.dark input:disabled{background-color:#eaeaea;border-color:#e1e8ed}
|
||||||
html.dark select:disabled{background-color:#f5f8fa}
|
html.dark select:disabled{background-color:#f5f8fa}
|
||||||
html.dark input:focus,html.dark select:focus,html.dark textarea:focus,html.dark .focus{border-color:rgba(80,165,230,0.8);box-shadow:inset 0 1px 3px rgba(17,17,17,0.1),0 0 8px rgba(80,165,230,0.6)}
|
html.dark input:focus,html.dark select:focus,html.dark textarea:focus,html.dark .focus{border-color:rgba(80,165,230,0.8);box-shadow:inset 0 1px 3px rgba(17,17,17,0.1),0 0 8px rgba(80,165,230,0.6)}
|
||||||
@@ -508,18 +506,6 @@ html.dark .lst-group .selected a:hover{background:#55acee}
|
|||||||
html.dark .lst-group .selected .fullname,html.dark .lst-group .selected .inner strong,html.dark .lst-group .selected .list-link,html.dark .lst-group .selected .list-twitter-list,html.dark .lst-group .selected .list-subtitle,html.dark .lst-group .selected .list-account,html.dark .lst-group .selected .list-listmember,html.dark .lst-group .selected .txt-ellipsis{color:#F5F8FA}
|
html.dark .lst-group .selected .fullname,html.dark .lst-group .selected .inner strong,html.dark .lst-group .selected .list-link,html.dark .lst-group .selected .list-twitter-list,html.dark .lst-group .selected .list-subtitle,html.dark .lst-group .selected .list-account,html.dark .lst-group .selected .list-listmember,html.dark .lst-group .selected .txt-ellipsis{color:#F5F8FA}
|
||||||
html.dark .lst-group .selected .username,html.dark .lst-group .selected .bytext,html.dark .lst-group .selected .subtitle,html.dark .lst-group .selected .icon-protected{color:#eef3f7}
|
html.dark .lst-group .selected .username,html.dark .lst-group .selected .bytext,html.dark .lst-group .selected .subtitle,html.dark .lst-group .selected .icon-protected{color:#eef3f7}
|
||||||
html.dark .itm-remove{border-top:1px solid #ddd}
|
html.dark .itm-remove{border-top:1px solid #ddd}
|
||||||
html.dark .caret-outer{border-bottom:7px solid rgba(17,17,17,0.1)}
|
|
||||||
html.dark .caret-inner{border-bottom:6px solid #fff}
|
|
||||||
html.dark .drp-h-divider{border-bottom:1px solid #ddd}
|
|
||||||
html.dark .dropdown-menu .typeahead-item,html.dark .dropdown-menu [data-action]{color:#292F33}
|
|
||||||
html.dark .dropdown-menu .is-selected{background:#55acee;color:#fff}
|
|
||||||
html.dark .dropdown-menu .is-selected [data-action]{color:#fff}
|
|
||||||
html.dark .dropdown-menu .is-selected a:not(:hover):not(:focus){color:#fff}
|
|
||||||
html.dark .dropdown-menu a:not(:hover):not(:focus){color:#292F33}
|
|
||||||
html.dark .dropdown-menu-old li:hover{background:#55acee}
|
|
||||||
html.dark .dropdown-menu-old li:hover a{color:#fff}
|
|
||||||
html.dark .dropdown-menu-old li:hover .attribution{color:#fff}
|
|
||||||
html.dark .non-selectable-item{color:#292F33}
|
|
||||||
html.dark .update-available-item:before{background-color:#FFAD1F}
|
html.dark .update-available-item:before{background-color:#FFAD1F}
|
||||||
html.dark .is-selected .update-available-item:before{background-color:rgba(41,47,51,0.2)}
|
html.dark .is-selected .update-available-item:before{background-color:rgba(41,47,51,0.2)}
|
||||||
html.dark .popover{background-color:#fff;box-shadow:0 0 10px rgba(17,17,17,0.7)}
|
html.dark .popover{background-color:#fff;box-shadow:0 0 10px rgba(17,17,17,0.7)}
|
||||||
@@ -802,5 +788,12 @@ html.dark .NotificationList .Notification-body{color:#14171A}
|
|||||||
html.dark .DrawerModal{color:#14171A}
|
html.dark .DrawerModal{color:#14171A}
|
||||||
/* fixes */
|
/* fixes */
|
||||||
html.dark .app-search-fake{border-color:transparent}
|
html.dark .app-search-fake{border-color:transparent}
|
||||||
html.dark .spinner-small,html.dark .spinner-large{filter:grayscale(80%)brightness(93%)}
|
html.dark .spinner-small,html.dark .spinner-large{filter:grayscale(85%)brightness(117%)}
|
||||||
html.dark .tweet>.color-twitter-blue{color:#8bd!important}
|
html.dark .tweet>.color-twitter-blue{color:#8bd!important}
|
||||||
|
html.dark .hw-card-container>div{border-color:#292F33;background:transparent}
|
||||||
|
html.dark .hw-card-container>div>div{border-color:#292F33}
|
||||||
|
html.dark .modal-content,html.dark .lst-group,html.dark #actions-modal{color:#292F33}
|
||||||
|
html.dark #actions-modal .account-link{color:#38444d}
|
||||||
|
html.dark .mdl-accent{background-color:transparent}
|
||||||
|
html.dark .lst-launcher a span{color:#657786!important}
|
||||||
|
html.dark .social-proof-names a{color:#3b94d9}
|
||||||
|
@@ -56,11 +56,13 @@ enabled(){
|
|||||||
this.prevComposeMustache = TD.mustaches["compose/docked_compose.mustache"];
|
this.prevComposeMustache = TD.mustaches["compose/docked_compose.mustache"];
|
||||||
window.TDPF_injectMustache("compose/docked_compose.mustache", "append", '<div class="cf margin-t--12 margin-b--30">', buttonHTML);
|
window.TDPF_injectMustache("compose/docked_compose.mustache", "append", '<div class="cf margin-t--12 margin-b--30">', buttonHTML);
|
||||||
|
|
||||||
let maybeDockedComposePanel = $(".js-docked-compose");
|
this.getDrawerInput = () => {
|
||||||
|
return $(".js-compose-text", me.composeDrawer);
|
||||||
|
};
|
||||||
|
|
||||||
if (maybeDockedComposePanel.length){
|
this.getDrawerScroller = () => {
|
||||||
maybeDockedComposePanel.find(".cf.margin-t--12.margin-b--30").first().append(buttonHTML);
|
return $(".js-compose-scroller > .scroll-v", me.composeDrawer);
|
||||||
}
|
};
|
||||||
|
|
||||||
// keyboard generation
|
// keyboard generation
|
||||||
|
|
||||||
@@ -79,12 +81,12 @@ enabled(){
|
|||||||
|
|
||||||
this.currentKeywords = [];
|
this.currentKeywords = [];
|
||||||
|
|
||||||
this.composePanelScroller.trigger("scroll");
|
this.getDrawerScroller().trigger("scroll");
|
||||||
|
|
||||||
$(".emoji-keyboard-popup-btn").removeClass("is-selected");
|
$(".emoji-keyboard-popup-btn").removeClass("is-selected");
|
||||||
|
|
||||||
if (refocus){
|
if (refocus){
|
||||||
this.composeInput.focus();
|
this.getDrawerInput().focus();
|
||||||
|
|
||||||
if (lastEmojiKeyword && lastEmojiPosition === 0){
|
if (lastEmojiKeyword && lastEmojiPosition === 0){
|
||||||
document.execCommand("insertText", false, lastEmojiKeyword);
|
document.execCommand("insertText", false, lastEmojiKeyword);
|
||||||
@@ -229,16 +231,16 @@ enabled(){
|
|||||||
this.currentSpanner.style.height = ($(this.currentKeyboard).height()-10)+"px";
|
this.currentSpanner.style.height = ($(this.currentKeyboard).height()-10)+"px";
|
||||||
$(".emoji-keyboard-popup-btn").parent().after(this.currentSpanner);
|
$(".emoji-keyboard-popup-btn").parent().after(this.currentSpanner);
|
||||||
|
|
||||||
this.composePanelScroller.trigger("scroll");
|
this.getDrawerScroller().trigger("scroll");
|
||||||
};
|
};
|
||||||
|
|
||||||
const getKeyboardTop = () => {
|
const getKeyboardTop = () => {
|
||||||
let button = $(".emoji-keyboard-popup-btn");
|
let button = $(".emoji-keyboard-popup-btn");
|
||||||
return button.offset().top+button.outerHeight()+me.composePanelScroller.scrollTop()+8;
|
return button.offset().top + button.outerHeight() + me.getDrawerScroller().scrollTop() + 8;
|
||||||
};
|
};
|
||||||
|
|
||||||
const insertEmoji = (src, alt) => {
|
const insertEmoji = (src, alt) => {
|
||||||
let input = this.composeInput;
|
let input = this.getDrawerInput();
|
||||||
|
|
||||||
let val = input.val();
|
let val = input.val();
|
||||||
let posStart = input[0].selectionStart;
|
let posStart = input[0].selectionStart;
|
||||||
@@ -378,6 +380,11 @@ enabled(){
|
|||||||
hideKeyboard();
|
hideKeyboard();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.composerActiveEvent = function(e){
|
||||||
|
$(".emoji-keyboard-popup-btn", me.composeDrawer).on("click", me.emojiKeyboardButtonClickEvent);
|
||||||
|
$(".js-docked-compose .js-compose-scroller > .scroll-v", me.composeDrawer).on("scroll", me.composerScrollEvent);
|
||||||
|
};
|
||||||
|
|
||||||
this.documentClickEvent = function(e){
|
this.documentClickEvent = function(e){
|
||||||
if (me.currentKeyboard && $(e.target).closest(".compose-text-container").length === 0){
|
if (me.currentKeyboard && $(e.target).closest(".compose-text-container").length === 0){
|
||||||
hideKeyboard();
|
hideKeyboard();
|
||||||
@@ -396,20 +403,26 @@ enabled(){
|
|||||||
me.currentKeyboard.style.top = getKeyboardTop()+"px";
|
me.currentKeyboard.style.top = getKeyboardTop()+"px";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// re-enabling
|
||||||
|
|
||||||
|
let maybeDockedComposePanel = $(".js-docked-compose");
|
||||||
|
|
||||||
|
if (maybeDockedComposePanel.length){
|
||||||
|
maybeDockedComposePanel.find(".cf.margin-t--12.margin-b--30").first().append(buttonHTML);
|
||||||
|
this.composerActiveEvent();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ready(){
|
ready(){
|
||||||
this.composeDrawer = $("[data-drawer='compose']");
|
this.composeDrawer = $(".js-drawer[data-drawer='compose']");
|
||||||
this.composeInput = $(".js-compose-text", ".js-docked-compose");
|
|
||||||
this.composeSelector = ".js-compose-text,.js-reply-tweetbox";
|
this.composeSelector = ".js-compose-text,.js-reply-tweetbox";
|
||||||
|
|
||||||
this.composePanelScroller = $(".js-compose-scroller", ".js-docked-compose").first().children().first();
|
|
||||||
this.composePanelScroller.on("scroll", this.composerScrollEvent);
|
|
||||||
|
|
||||||
$(".emoji-keyboard-popup-btn").on("click", this.emojiKeyboardButtonClickEvent);
|
|
||||||
$(document).on("click", this.documentClickEvent);
|
$(document).on("click", this.documentClickEvent);
|
||||||
$(document).on("keydown", this.documentKeyEvent);
|
$(document).on("keydown", this.documentKeyEvent);
|
||||||
|
$(document).on("tduckOldComposerActive", this.composerActiveEvent);
|
||||||
$(document).on("uiComposeImageAdded", this.uploadFilesEvent);
|
$(document).on("uiComposeImageAdded", this.uploadFilesEvent);
|
||||||
|
|
||||||
this.composeDrawer.on("uiComposeTweetSending", this.composerSendingEvent);
|
this.composeDrawer.on("uiComposeTweetSending", this.composerSendingEvent);
|
||||||
|
|
||||||
$(document).on("keydown", this.composeSelector, this.composeInputKeyDownEvent);
|
$(document).on("keydown", this.composeSelector, this.composeInputKeyDownEvent);
|
||||||
@@ -459,7 +472,7 @@ ready(){
|
|||||||
};
|
};
|
||||||
|
|
||||||
// line reading
|
// line reading
|
||||||
|
|
||||||
let skinToneState = 0;
|
let skinToneState = 0;
|
||||||
|
|
||||||
for(let line of contents.split("\n")){
|
for(let line of contents.split("\n")){
|
||||||
@@ -529,14 +542,13 @@ disabled(){
|
|||||||
$(this.currentSpanner).remove();
|
$(this.currentSpanner).remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.composePanelScroller.off("scroll", this.composerScrollEvent);
|
|
||||||
|
|
||||||
$(".emoji-keyboard-popup-btn").off("click", this.emojiKeyboardButtonClickEvent);
|
|
||||||
$(".emoji-keyboard-popup-btn").remove();
|
$(".emoji-keyboard-popup-btn").remove();
|
||||||
|
|
||||||
$(document).off("click", this.documentClickEvent);
|
$(document).off("click", this.documentClickEvent);
|
||||||
$(document).off("keydown", this.documentKeyEvent);
|
$(document).off("keydown", this.documentKeyEvent);
|
||||||
|
$(document).off("tduckOldComposerActive", this.composerActiveEvent);
|
||||||
$(document).off("uiComposeImageAdded", this.uploadFilesEvent);
|
$(document).off("uiComposeImageAdded", this.uploadFilesEvent);
|
||||||
|
|
||||||
this.composeDrawer.off("uiComposeTweetSending", this.composerSendingEvent);
|
this.composeDrawer.off("uiComposeTweetSending", this.composerSendingEvent);
|
||||||
|
|
||||||
$(document).off("keydown", this.composeSelector, this.composeInputKeyDownEvent);
|
$(document).off("keydown", this.composeSelector, this.composeInputKeyDownEvent);
|
||||||
|
@@ -376,22 +376,25 @@ enabled(){
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.drawerToggleEvent = function(e, data){
|
this.drawerToggleEvent = function(e, data){
|
||||||
if (data.activeDrawer === null){
|
if (typeof data === "undefined" || data.activeDrawer !== "compose"){
|
||||||
hideTemplateModal();
|
hideTemplateModal();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
ready(){
|
ready(){
|
||||||
$(".manage-templates-btn").on("click", this.manageTemplatesButtonClickEvent);
|
$(".js-drawer[data-drawer='compose']").on("click", ".manage-templates-btn", this.manageTemplatesButtonClickEvent);
|
||||||
$(document).on("uiDrawerActive", this.drawerToggleEvent);
|
$(document).on("uiDrawerActive", this.drawerToggleEvent);
|
||||||
|
$(document).on("click", ".js-new-composer-opt-in", this.drawerToggleEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
disabled(){
|
disabled(){
|
||||||
$(".manage-templates-btn").remove();
|
$(".manage-templates-btn").remove();
|
||||||
$("#templates-modal-wrap").remove();
|
$("#templates-modal-wrap").remove();
|
||||||
|
|
||||||
|
$(".js-drawer[data-drawer='compose']").off("click", ".manage-templates-btn", this.manageTemplatesButtonClickEvent);
|
||||||
$(document).off("uiDrawerActive", this.drawerToggleEvent);
|
$(document).off("uiDrawerActive", this.drawerToggleEvent);
|
||||||
|
$(document).off("click", ".js-new-composer-opt-in", this.drawerToggleEvent);
|
||||||
|
|
||||||
TD.mustaches["compose/docked_compose.mustache"] = this.prevComposeMustache;
|
TD.mustaches["compose/docked_compose.mustache"] = this.prevComposeMustache;
|
||||||
}
|
}
|
||||||
|
@@ -171,14 +171,14 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.template-editor-form input, .template-editor-form textarea {
|
.template-editor-form input, .template-editor-form textarea {
|
||||||
color: #111;
|
color: #111 !important;
|
||||||
background-color: #fff;
|
background-color: #fff !important;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.template-editor-form input:focus, .template-editor-form textarea:focus {
|
.template-editor-form input:focus, .template-editor-form textarea:focus {
|
||||||
box-shadow: inset 0 1px 3px rgba(17, 17, 17, 0.1), 0 0 8px rgba(80, 165, 230, 0.6);
|
box-shadow: inset 0 1px 3px rgba(17, 17, 17, 0.1), 0 0 8px rgba(80, 165, 230, 0.6) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.template-editor-form textarea {
|
.template-editor-form textarea {
|
||||||
|
249
Resources/PostBuild.fsx
Normal file
249
Resources/PostBuild.fsx
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
open System
|
||||||
|
open System.Collections.Generic
|
||||||
|
open System.Diagnostics
|
||||||
|
open System.IO
|
||||||
|
open System.Text.RegularExpressions
|
||||||
|
open System.Threading.Tasks
|
||||||
|
|
||||||
|
// "$(DevEnvDir)CommonExtensions\Microsoft\FSharp\fsi.exe" "$(ProjectDir)Resources\PostBuild.fsx" --exec --nologo -- "$(TargetDir)\" "$(ProjectDir)\" "$(ConfigurationName)"
|
||||||
|
// "$(ProjectDir)bld\post_build.exe" "$(TargetDir)\" "$(ProjectDir)\" "$(ConfigurationName)"
|
||||||
|
|
||||||
|
exception ArgumentUsage of string
|
||||||
|
|
||||||
|
#if !INTERACTIVE
|
||||||
|
[<EntryPoint>]
|
||||||
|
#endif
|
||||||
|
let main (argv: string[]) =
|
||||||
|
try
|
||||||
|
if argv.Length < 2 then
|
||||||
|
#if INTERACTIVE
|
||||||
|
raise (ArgumentUsage "fsi.exe PostBuild.fsx --exec --nologo -- <TargetDir> <ProjectDir> [ConfigurationName] [VersionTag]")
|
||||||
|
#else
|
||||||
|
raise (ArgumentUsage "PostBuild.exe <TargetDir> <ProjectDir> [ConfigurationName] [VersionTag]")
|
||||||
|
#endif
|
||||||
|
|
||||||
|
let _time name func =
|
||||||
|
let sw = Stopwatch.StartNew()
|
||||||
|
func()
|
||||||
|
sw.Stop()
|
||||||
|
printfn "[%s took %i ms]" name (int (Math.Round(sw.Elapsed.TotalMilliseconds)))
|
||||||
|
|
||||||
|
let (+/) path1 path2 =
|
||||||
|
Path.Combine(path1, path2)
|
||||||
|
|
||||||
|
let sw = Stopwatch.StartNew()
|
||||||
|
|
||||||
|
// Setup
|
||||||
|
|
||||||
|
let targetDir = argv.[0]
|
||||||
|
let projectDir = argv.[1]
|
||||||
|
|
||||||
|
let configuration = if argv.Length >= 3 then argv.[2]
|
||||||
|
else "Release"
|
||||||
|
|
||||||
|
let version = if argv.Length >= 4 then argv.[3]
|
||||||
|
else ((targetDir +/ "TweetDuck.exe") |> FileVersionInfo.GetVersionInfo).FileVersion
|
||||||
|
|
||||||
|
printfn "--------------------------"
|
||||||
|
printfn "TweetDuck version %s" version
|
||||||
|
printfn "--------------------------"
|
||||||
|
|
||||||
|
let localesDir = targetDir +/ "locales"
|
||||||
|
let scriptsDir = targetDir +/ "scripts"
|
||||||
|
let pluginsDir = targetDir +/ "plugins"
|
||||||
|
let importsDir = scriptsDir +/ "imports"
|
||||||
|
|
||||||
|
// Functions (Strings)
|
||||||
|
|
||||||
|
let filterNotEmpty =
|
||||||
|
Seq.filter (not << String.IsNullOrEmpty)
|
||||||
|
|
||||||
|
let replaceRegex (pattern: string) (replacement: string) input =
|
||||||
|
Regex.Replace(input, pattern, replacement)
|
||||||
|
|
||||||
|
let collapseLines separator (sequence: string seq) =
|
||||||
|
String.Join(separator, sequence)
|
||||||
|
|
||||||
|
let trimStart (line: string) =
|
||||||
|
line.TrimStart()
|
||||||
|
|
||||||
|
// Functions (File Management)
|
||||||
|
let copyFile source target =
|
||||||
|
File.Copy(source, target, true)
|
||||||
|
|
||||||
|
let createDirectory path =
|
||||||
|
Directory.CreateDirectory(path) |> ignore
|
||||||
|
|
||||||
|
let rec copyDirectoryContentsFiltered source target (filter: string -> bool) =
|
||||||
|
if not (Directory.Exists(target)) then
|
||||||
|
Directory.CreateDirectory(target) |> ignore
|
||||||
|
|
||||||
|
let src = DirectoryInfo(source)
|
||||||
|
|
||||||
|
for file in src.EnumerateFiles() do
|
||||||
|
if filter file.Name then
|
||||||
|
file.CopyTo(target +/ file.Name) |> ignore
|
||||||
|
|
||||||
|
for dir in src.EnumerateDirectories() do
|
||||||
|
if filter dir.Name then
|
||||||
|
copyDirectoryContentsFiltered dir.FullName (target +/ dir.Name) filter
|
||||||
|
|
||||||
|
let copyDirectoryContents source target =
|
||||||
|
copyDirectoryContentsFiltered source target (fun _ -> true)
|
||||||
|
|
||||||
|
// Functions (File Processing)
|
||||||
|
|
||||||
|
let byPattern path pattern =
|
||||||
|
Directory.EnumerateFiles(path, pattern, SearchOption.AllDirectories)
|
||||||
|
|
||||||
|
let exceptEndingWith name =
|
||||||
|
Seq.filter (fun (file: string) -> not (file.EndsWith(name)))
|
||||||
|
|
||||||
|
let iterateFiles (files: string seq) (func: string -> unit) =
|
||||||
|
Parallel.ForEach(files, func) |> ignore
|
||||||
|
|
||||||
|
let readFile file = seq {
|
||||||
|
use stream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 0x1000, FileOptions.SequentialScan)
|
||||||
|
use reader = new StreamReader(stream)
|
||||||
|
let mutable cont = true
|
||||||
|
|
||||||
|
while cont do
|
||||||
|
let line = reader.ReadLine()
|
||||||
|
|
||||||
|
if line = null then
|
||||||
|
cont <- false
|
||||||
|
else
|
||||||
|
yield line
|
||||||
|
}
|
||||||
|
|
||||||
|
let writeFile (fullPath: string) (lines: string seq) =
|
||||||
|
let relativePath = fullPath.[(targetDir.Length)..]
|
||||||
|
let includeVersion = relativePath.StartsWith(@"scripts\") && not (relativePath.StartsWith(@"scripts\imports\"))
|
||||||
|
let finalLines = if includeVersion then seq { yield "#" + version; yield! lines } else lines
|
||||||
|
|
||||||
|
File.WriteAllLines(fullPath, finalLines |> filterNotEmpty |> Seq.toArray)
|
||||||
|
printfn "Processed %s" relativePath
|
||||||
|
|
||||||
|
let processFiles (files: string seq) (extProcessors: IDictionary<string, (string seq -> string seq)>) =
|
||||||
|
let rec processFileContents file =
|
||||||
|
readFile file
|
||||||
|
|> extProcessors.[Path.GetExtension(file)]
|
||||||
|
|> Seq.map (fun line ->
|
||||||
|
Regex.Replace(line, @"#import ""(.*?)""", (fun matchInfo ->
|
||||||
|
processFileContents(importsDir +/ matchInfo.Groups.[1].Value.Trim())
|
||||||
|
|> collapseLines (Environment.NewLine)
|
||||||
|
|> (fun contents -> contents.TrimEnd())
|
||||||
|
))
|
||||||
|
)
|
||||||
|
|
||||||
|
iterateFiles files (fun file ->
|
||||||
|
processFileContents file
|
||||||
|
|> (writeFile file)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Build
|
||||||
|
|
||||||
|
copyFile (projectDir +/ "bld/Resources/LICENSES.txt") (targetDir +/ "LICENSES.txt")
|
||||||
|
|
||||||
|
copyDirectoryContents (projectDir +/ "Resources/Scripts") scriptsDir
|
||||||
|
|
||||||
|
createDirectory (pluginsDir +/ "official")
|
||||||
|
createDirectory (pluginsDir +/ "user")
|
||||||
|
|
||||||
|
copyDirectoryContentsFiltered
|
||||||
|
(projectDir +/ "Resources/Plugins")
|
||||||
|
(pluginsDir +/ "official")
|
||||||
|
(fun name -> name <> ".debug" && name <> "emoji-instructions.txt")
|
||||||
|
|
||||||
|
if configuration = "Debug" then
|
||||||
|
copyDirectoryContents
|
||||||
|
(projectDir +/ "Resources/Plugins/.debug")
|
||||||
|
(pluginsDir +/ "user/.debug")
|
||||||
|
|
||||||
|
if Directory.Exists(localesDir) || configuration = "Release" then
|
||||||
|
Directory.EnumerateFiles(localesDir, "*.pak")
|
||||||
|
|> exceptEndingWith @"\en-US.pak"
|
||||||
|
|> Seq.iter (File.Delete)
|
||||||
|
|
||||||
|
// Validation
|
||||||
|
|
||||||
|
if File.ReadAllText(pluginsDir +/ "official/emoji-keyboard/emoji-ordering.txt").IndexOf('\r') <> -1 then
|
||||||
|
raise (FormatException("emoji-ordering.txt must not have any carriage return characters"))
|
||||||
|
else
|
||||||
|
printfn "Verified emoji-ordering.txt"
|
||||||
|
|
||||||
|
// Processing
|
||||||
|
|
||||||
|
let fileProcessors =
|
||||||
|
dict [
|
||||||
|
".js", (fun (lines: string seq) ->
|
||||||
|
lines
|
||||||
|
|> Seq.map (fun line ->
|
||||||
|
line
|
||||||
|
|> trimStart
|
||||||
|
|> replaceRegex @"^(.*?)((?<=^|[;{}()])\s?//(?:\s.*|$))?$" "$1"
|
||||||
|
|> replaceRegex @"(?<!\w)(return|throw)(\s.*?)? if (.*?);" "if ($3)$1$2;"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
".css", (fun (lines: string seq) ->
|
||||||
|
lines
|
||||||
|
|> Seq.map (fun line ->
|
||||||
|
line
|
||||||
|
|> replaceRegex @"\s*/\*.*?\*/" ""
|
||||||
|
|> replaceRegex @"^(\S.*) {$" "$1{"
|
||||||
|
|> replaceRegex @"^\s+(.+?):\s*(.+?)(?:\s*(!important))?;$" "$1:$2$3;"
|
||||||
|
)
|
||||||
|
|> filterNotEmpty
|
||||||
|
|> collapseLines " "
|
||||||
|
|> replaceRegex @"([{};])\s" "$1"
|
||||||
|
|> replaceRegex @";}" "}"
|
||||||
|
|> Seq.singleton
|
||||||
|
);
|
||||||
|
|
||||||
|
".html", (fun (lines: string seq) ->
|
||||||
|
lines
|
||||||
|
|> Seq.map trimStart
|
||||||
|
);
|
||||||
|
|
||||||
|
".meta", (fun (lines: string seq) ->
|
||||||
|
lines
|
||||||
|
|> Seq.map (fun line -> line.Replace("{version}", version))
|
||||||
|
);
|
||||||
|
]
|
||||||
|
|
||||||
|
processFiles ((byPattern targetDir "*.js") |> exceptEndingWith @"\configuration.default.js") fileProcessors
|
||||||
|
processFiles (byPattern targetDir "*.css") fileProcessors
|
||||||
|
processFiles (byPattern targetDir "*.html") fileProcessors
|
||||||
|
processFiles (byPattern pluginsDir "*.meta") fileProcessors
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
|
||||||
|
Directory.Delete(importsDir, true)
|
||||||
|
|
||||||
|
// Finished
|
||||||
|
|
||||||
|
sw.Stop()
|
||||||
|
printfn "------------------"
|
||||||
|
printfn "Finished in %i ms" (int (Math.Round(sw.Elapsed.TotalMilliseconds)))
|
||||||
|
printfn "------------------"
|
||||||
|
0
|
||||||
|
|
||||||
|
with
|
||||||
|
| ArgumentUsage message ->
|
||||||
|
printfn ""
|
||||||
|
printfn "Build script usage:"
|
||||||
|
printfn "%s" message
|
||||||
|
printfn ""
|
||||||
|
1
|
||||||
|
| ex ->
|
||||||
|
printfn ""
|
||||||
|
printfn "Encountered an error while running PostBuild:"
|
||||||
|
printfn "%A" ex
|
||||||
|
printfn ""
|
||||||
|
1
|
||||||
|
|
||||||
|
#if INTERACTIVE
|
||||||
|
printfn "Running PostBuild in interpreter..."
|
||||||
|
main (fsi.CommandLineArgs |> Array.skip (1 + (fsi.CommandLineArgs |> Array.findIndex (fun arg -> arg = "--"))))
|
||||||
|
#endif
|
@@ -1,166 +0,0 @@
|
|||||||
Param(
|
|
||||||
[Parameter(Mandatory = $True, Position = 1)][string] $targetDir,
|
|
||||||
[Parameter(Mandatory = $True, Position = 2)][string] $projectDir,
|
|
||||||
[Parameter(Position = 3)][string] $configuration = "Release",
|
|
||||||
[Parameter(Position = 4)][string] $version = ""
|
|
||||||
)
|
|
||||||
|
|
||||||
$ErrorActionPreference = "Stop"
|
|
||||||
|
|
||||||
try{
|
|
||||||
$sw = [Diagnostics.Stopwatch]::StartNew()
|
|
||||||
|
|
||||||
if ($version.Equals("")){
|
|
||||||
$version = (Get-Item (Join-Path $targetDir "TweetDuck.exe")).VersionInfo.FileVersion
|
|
||||||
}
|
|
||||||
|
|
||||||
Write-Host "--------------------------"
|
|
||||||
Write-Host "TweetDuck version" $version
|
|
||||||
Write-Host "--------------------------"
|
|
||||||
|
|
||||||
# Cleanup
|
|
||||||
|
|
||||||
if (Test-Path (Join-Path $targetDir "locales")){
|
|
||||||
Remove-Item -Path (Join-Path $targetDir "locales\*.pak") -Exclude "en-US.pak"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Copy resources
|
|
||||||
|
|
||||||
Copy-Item (Join-Path $projectDir "bld\Resources\LICENSES.txt") -Destination $targetDir -Force
|
|
||||||
|
|
||||||
New-Item -ItemType directory -Path $targetDir -Name "scripts" | Out-Null
|
|
||||||
New-Item -ItemType directory -Path $targetDir -Name "plugins" | Out-Null
|
|
||||||
New-Item -ItemType directory -Path $targetDir -Name "plugins\official" | Out-Null
|
|
||||||
New-Item -ItemType directory -Path $targetDir -Name "plugins\user" | Out-Null
|
|
||||||
|
|
||||||
Copy-Item (Join-Path $projectDir "Resources\Scripts\*") -Recurse -Destination (Join-Path $targetDir "scripts")
|
|
||||||
Copy-Item (Join-Path $projectDir "Resources\Plugins\*") -Recurse -Destination (Join-Path $targetDir "plugins\official") -Exclude ".debug", "emoji-instructions.txt"
|
|
||||||
|
|
||||||
if ($configuration -eq "Debug"){
|
|
||||||
New-Item -ItemType directory -Path $targetDir -Name "plugins\user\.debug" | Out-Null
|
|
||||||
Copy-Item (Join-Path $projectDir "Resources\Plugins\.debug\*") -Recurse -Destination (Join-Path $targetDir "plugins\user\.debug")
|
|
||||||
}
|
|
||||||
|
|
||||||
# Helper functions
|
|
||||||
|
|
||||||
function Remove-Empty-Lines{
|
|
||||||
Param([Parameter(Mandatory = $True, Position = 1)] $lines)
|
|
||||||
|
|
||||||
foreach($line in $lines){
|
|
||||||
if ($line -ne ''){
|
|
||||||
$line
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function Check-Carriage-Return{
|
|
||||||
Param([Parameter(Mandatory = $True, Position = 1)] $file)
|
|
||||||
|
|
||||||
if (!(Test-Path $file)){
|
|
||||||
Throw "$file does not exist"
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((Get-Content -Path $file -Raw).Contains("`r")){
|
|
||||||
Throw "$file must not have any carriage return characters"
|
|
||||||
}
|
|
||||||
|
|
||||||
Write-Host "Verified" $file.Substring($targetDir.Length)
|
|
||||||
}
|
|
||||||
|
|
||||||
function Rewrite-File{
|
|
||||||
Param([Parameter(Mandatory = $True, Position = 1)] $file,
|
|
||||||
[Parameter(Mandatory = $True, Position = 2)] $lines,
|
|
||||||
[Parameter(Mandatory = $True, Position = 3)] $imports)
|
|
||||||
|
|
||||||
$lines = Remove-Empty-Lines($lines)
|
|
||||||
$relativePath = $file.FullName.Substring($targetDir.Length)
|
|
||||||
|
|
||||||
foreach($line in $lines){
|
|
||||||
if ($line.Contains('#import ')){
|
|
||||||
$imports.Add($file.FullName)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($relativePath.StartsWith("scripts\")){
|
|
||||||
$lines = (,("#" + $version) + $lines)
|
|
||||||
}
|
|
||||||
|
|
||||||
[IO.File]::WriteAllLines($file.FullName, $lines)
|
|
||||||
Write-Host "Processed" $relativePath
|
|
||||||
}
|
|
||||||
|
|
||||||
# Validation
|
|
||||||
|
|
||||||
Check-Carriage-Return(Join-Path $targetDir "plugins\official\emoji-keyboard\emoji-ordering.txt")
|
|
||||||
|
|
||||||
# Processing
|
|
||||||
|
|
||||||
$imports = New-Object "System.Collections.Generic.List[string]"
|
|
||||||
|
|
||||||
foreach($file in Get-ChildItem -Path $targetDir -Filter "*.js" -Exclude "configuration.default.js" -Recurse){
|
|
||||||
$lines = [IO.File]::ReadLines($file.FullName)
|
|
||||||
$lines = $lines | ForEach-Object { $_.TrimStart() }
|
|
||||||
$lines = $lines -Replace '^(.*?)((?<=^|[;{}()])\s?//(?:\s.*|$))?$', '$1'
|
|
||||||
$lines = $lines -Replace '(?<!\w)(return|throw)(\s.*?)? if (.*?);', 'if ($3)$1$2;'
|
|
||||||
Rewrite-File $file $lines $imports
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach($file in Get-ChildItem -Path $targetDir -Filter "*.css" -Recurse){
|
|
||||||
$lines = [IO.File]::ReadLines($file.FullName)
|
|
||||||
$lines = $lines -Replace '\s*/\*.*?\*/', ''
|
|
||||||
$lines = $lines -Replace '^(\S.*) {$', '$1{'
|
|
||||||
$lines = $lines -Replace '^\s+(.+?):\s*(.+?)(?:\s*(!important))?;$', '$1:$2$3;'
|
|
||||||
$lines = @((Remove-Empty-Lines($lines)) -Join ' ')
|
|
||||||
$lines = $lines -Replace '([{};])\s', '$1'
|
|
||||||
$lines = $lines -Replace ';}', '}'
|
|
||||||
Rewrite-File $file $lines $imports
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach($file in Get-ChildItem -Path $targetDir -Filter "*.html" -Recurse){
|
|
||||||
$lines = [IO.File]::ReadLines($file.FullName)
|
|
||||||
$lines = $lines | ForEach-Object { $_.TrimStart() }
|
|
||||||
Rewrite-File $file $lines $imports
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach($file in Get-ChildItem -Path (Join-Path $targetDir "plugins") -Filter "*.meta" -Recurse){
|
|
||||||
$lines = [IO.File]::ReadLines($file.FullName)
|
|
||||||
$lines = $lines -Replace '\{version\}', $version
|
|
||||||
Rewrite-File $file $lines $imports
|
|
||||||
}
|
|
||||||
|
|
||||||
# Imports
|
|
||||||
|
|
||||||
$importFolder = Join-Path $targetDir "scripts\imports"
|
|
||||||
|
|
||||||
foreach($path in $imports){
|
|
||||||
$text = [IO.File]::ReadAllText($path)
|
|
||||||
$text = [Regex]::Replace($text, '#import "(.*?)"', {
|
|
||||||
$importPath = Join-Path $importFolder ($args[0].Groups[1].Value.Trim())
|
|
||||||
$importStr = [IO.File]::ReadAllText($importPath).TrimEnd()
|
|
||||||
|
|
||||||
if ($importStr[0] -eq '#'){
|
|
||||||
$importStr = $importStr.Substring($importStr.IndexOf("`n") + 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
return $importStr
|
|
||||||
}, [System.Text.RegularExpressions.RegexOptions]::MultiLine)
|
|
||||||
|
|
||||||
[IO.File]::WriteAllText($path, $text)
|
|
||||||
Write-Host "Resolved" $path.Substring($targetDir.Length)
|
|
||||||
}
|
|
||||||
|
|
||||||
[IO.Directory]::Delete($importFolder, $True)
|
|
||||||
|
|
||||||
# Finished
|
|
||||||
|
|
||||||
$sw.Stop()
|
|
||||||
Write-Host "------------------"
|
|
||||||
Write-Host "Finished in" $([math]::Round($sw.Elapsed.TotalMilliseconds)) "ms"
|
|
||||||
Write-Host "------------------"
|
|
||||||
|
|
||||||
}catch{
|
|
||||||
Write-Host "Encountered an error while running PostBuild.ps1 on line" $_.InvocationInfo.ScriptLineNumber
|
|
||||||
Write-Host $_
|
|
||||||
Exit 1
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user