mirror of
https://github.com/chylex/TweetDuck.git
synced 2025-09-14 01:32:10 +02:00
Compare commits
119 Commits
Author | SHA1 | Date | |
---|---|---|---|
52f1f4c4eb | |||
6c1782a038 | |||
8b8f5f5473 | |||
61d3ed891a | |||
b1abf87320 | |||
9aedfc2799 | |||
ad6240a067 | |||
9539eb076a | |||
c808e7bd83 | |||
13ea388f5e | |||
c46dc0f1a3 | |||
2ae311007d | |||
9344e02bff | |||
40ad836fc3 | |||
e8604a261d | |||
2a41d21a29 | |||
4c62aa067b | |||
49db3074c6 | |||
f5e3b34f30 | |||
f0affa4aec | |||
4f5075ac54 | |||
20f0445b10 | |||
c77c974455 | |||
44397b2d45 | |||
943d4d4d72 | |||
6468c03465 | |||
8141a5a5c5 | |||
26a1779310 | |||
45d18ffafe | |||
5f1c30609c | |||
7266d705d3 | |||
ee6bb782d6 | |||
8ae6e2c886 | |||
dd3a0d3890 | |||
8d8e2da57e | |||
e60d204302 | |||
3d642d8ad2 | |||
8db6e8a090 | |||
8153fcde85 | |||
96469cfca5 | |||
7601645c12 | |||
c28615d548 | |||
b515add94e | |||
9fd5e9443d | |||
b2ddb1fab2 | |||
fdac42947c | |||
eeaf6949c5 | |||
d7ad62d476 | |||
cd87a329fc | |||
8c0d306823 | |||
d5c3ea0862 | |||
83c962a7a4 | |||
40ef9a42dd | |||
868af5ac6a | |||
625227d0ce | |||
064627961e | |||
de0321cb2d | |||
0d71a33b28 | |||
6d779f17b3 | |||
05510d7bc1 | |||
8e162fe031 | |||
7ea7366a43 | |||
445e6fcec0 | |||
42f4d97d5d | |||
6357708533 | |||
59c9801437 | |||
d691bef1fb | |||
442d74d0cb | |||
588bb9a093 | |||
380e580d65 | |||
4e306661f8 | |||
9f3f33da93 | |||
69cd96a37c | |||
1293a2a533 | |||
d24b7bbcb9 | |||
b55b47b689 | |||
c4c032b4d5 | |||
970cd21964 | |||
8ca9d242b2 | |||
6f0518edcc | |||
e2d15dd7e3 | |||
5c310e8647 | |||
01dca0bc66 | |||
8b54fbdb2f | |||
663d0a633e | |||
ccd5edb0e4 | |||
c6190db918 | |||
3d4cec3b22 | |||
5ed970b5a0 | |||
c22934336b | |||
a3a52e0a1c | |||
68dca6e3d9 | |||
017f883e0b | |||
77b5c95f75 | |||
9d052c8339 | |||
d67623a657 | |||
c740b3dd46 | |||
2ef5f7f96f | |||
404568d795 | |||
b5a6337a0c | |||
82170c3fbd | |||
e6d6275fcc | |||
97c865a127 | |||
1ff21f0ee0 | |||
2a3dca4467 | |||
d4ecfcceec | |||
ec5d503e4d | |||
346391ca2d | |||
9074cdf340 | |||
2fcf3604a8 | |||
34e5185fa1 | |||
e09e0e69ca | |||
963c98e588 | |||
92acb823a4 | |||
b967b1288f | |||
1db271ce90 | |||
58c64025e3 | |||
643a7a87aa | |||
b2be530f6b |
@@ -12,6 +12,7 @@ namespace TweetDuck.Configuration{
|
||||
// internal args
|
||||
public const string ArgRestart = "-restart";
|
||||
public const string ArgImportCookies = "-importcookies";
|
||||
public const string ArgDeleteCookies = "-deletecookies";
|
||||
public const string ArgUpdated = "-updated";
|
||||
|
||||
// class data and methods
|
||||
@@ -29,6 +30,7 @@ namespace TweetDuck.Configuration{
|
||||
CommandLineArgs args = Current.Clone();
|
||||
args.RemoveFlag(ArgRestart);
|
||||
args.RemoveFlag(ArgImportCookies);
|
||||
args.RemoveFlag(ArgDeleteCookies);
|
||||
args.RemoveFlag(ArgUpdated);
|
||||
return args;
|
||||
}
|
||||
|
@@ -1,9 +1,10 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetLib.Communication;
|
||||
|
||||
namespace TweetDuck.Configuration{
|
||||
sealed class LockManager{
|
||||
@@ -137,7 +138,7 @@ namespace TweetDuck.Configuration{
|
||||
public bool RestoreLockingProcess(int failTimeout){
|
||||
if (lockingProcess != null){
|
||||
if (lockingProcess.MainWindowHandle == IntPtr.Zero){ // restore if the original process is in tray
|
||||
NativeMethods.PostMessage(NativeMethods.HWND_BROADCAST, Program.WindowRestoreMessage, new UIntPtr((uint)lockingProcess.Id), IntPtr.Zero);
|
||||
Comms.BroadcastMessage(Program.WindowRestoreMessage, (uint)lockingProcess.Id, 0);
|
||||
|
||||
if (WindowsUtils.TrySleepUntil(() => CheckLockingProcessExited() || (lockingProcess.MainWindowHandle != IntPtr.Zero && lockingProcess.Responding), failTimeout, RetryDelay)){
|
||||
return true;
|
||||
|
@@ -5,9 +5,7 @@ using TweetDuck.Data.Serialization;
|
||||
|
||||
namespace TweetDuck.Configuration{
|
||||
sealed class SystemConfig{
|
||||
private static readonly FileSerializer<SystemConfig> Serializer = new FileSerializer<SystemConfig>{
|
||||
// HandleUnknownProperties = (obj, data) => {}
|
||||
};
|
||||
private static readonly FileSerializer<SystemConfig> Serializer = new FileSerializer<SystemConfig>();
|
||||
|
||||
public static readonly bool IsHardwareAccelerationSupported = File.Exists(Path.Combine(Program.ProgramPath, "libEGL.dll")) &&
|
||||
File.Exists(Path.Combine(Program.ProgramPath, "libGLESv2.dll"));
|
||||
@@ -50,19 +48,8 @@ namespace TweetDuck.Configuration{
|
||||
|
||||
try{
|
||||
Serializer.Read(file, config);
|
||||
return config;
|
||||
}catch(FileNotFoundException){
|
||||
}catch(DirectoryNotFoundException){
|
||||
}catch(FormatException){
|
||||
try{
|
||||
using(Stream stream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read)){
|
||||
config.HardwareAcceleration = stream.ReadByte() > 0;
|
||||
}
|
||||
|
||||
config.Save();
|
||||
}catch(Exception e){
|
||||
Program.Reporter.HandleException("Configuration Error", "Could not update the system configuration file.", true, e);
|
||||
}
|
||||
}catch(Exception e){
|
||||
Program.Reporter.HandleException("Configuration Error", "Could not open the system configuration file. If you continue, you will lose system specific configuration such as Hardware Acceleration.", true, e);
|
||||
}
|
||||
|
@@ -14,16 +14,6 @@ namespace TweetDuck.Configuration{
|
||||
private static readonly FileSerializer<UserConfig> Serializer = new FileSerializer<UserConfig>{ HandleUnknownProperties = HandleUnknownProperties };
|
||||
|
||||
private static void HandleUnknownProperties(UserConfig obj, Dictionary<string, string> data){
|
||||
if (data.TryGetValue("EnableBrowserGCReload", out string propGCReload) && data.TryGetValue("BrowserMemoryThreshold", out string propMemThreshold)){
|
||||
if (bool.TryParse(propGCReload, out bool isGCReloadEnabled) && isGCReloadEnabled && int.TryParse(propMemThreshold, out int memThreshold)){
|
||||
// SystemConfig initialization was moved before UserConfig to allow for this
|
||||
// TODO remove the migration soon
|
||||
Program.SystemConfig.EnableBrowserGCReload = true;
|
||||
Program.SystemConfig.BrowserMemoryThreshold = memThreshold;
|
||||
Program.SystemConfig.Save();
|
||||
}
|
||||
}
|
||||
|
||||
data.Remove("EnableBrowserGCReload");
|
||||
data.Remove("BrowserMemoryThreshold");
|
||||
|
||||
@@ -65,8 +55,8 @@ namespace TweetDuck.Configuration{
|
||||
private int _zoomLevel = 100;
|
||||
private bool _muteNotifications;
|
||||
|
||||
private TrayIcon.Behavior _trayBehavior = TrayIcon.Behavior.Disabled;
|
||||
public bool EnableTrayHighlight { get; set; } = true;
|
||||
private TrayIcon.Behavior _trayBehavior = TrayIcon.Behavior.Disabled;
|
||||
public bool EnableTrayHighlight { get; set; } = true;
|
||||
|
||||
public bool EnableUpdateCheck { get; set; } = true;
|
||||
public string DismissedUpdate { get; set; } = null;
|
||||
@@ -90,6 +80,7 @@ namespace TweetDuck.Configuration{
|
||||
public Size CustomNotificationSize { get; set; } = Size.Empty;
|
||||
public int NotificationScrollSpeed { get; set; } = 10;
|
||||
|
||||
public int NotificationSoundVolume { get; set; } = 100;
|
||||
private string _notificationSoundPath;
|
||||
|
||||
public string CustomCefArgs { get; set; } = null;
|
||||
@@ -114,7 +105,7 @@ namespace TweetDuck.Configuration{
|
||||
set{
|
||||
if (_muteNotifications != value){
|
||||
_muteNotifications = value;
|
||||
MuteToggled?.Invoke(this, new EventArgs());
|
||||
MuteToggled?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -125,7 +116,7 @@ namespace TweetDuck.Configuration{
|
||||
set{
|
||||
if (_zoomLevel != value){
|
||||
_zoomLevel = value;
|
||||
ZoomLevelChanged?.Invoke(this, new EventArgs());
|
||||
ZoomLevelChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -138,7 +129,7 @@ namespace TweetDuck.Configuration{
|
||||
set{
|
||||
if (_trayBehavior != value){
|
||||
_trayBehavior = value;
|
||||
TrayBehaviorChanged?.Invoke(this, new EventArgs());
|
||||
TrayBehaviorChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -153,7 +144,7 @@ namespace TweetDuck.Configuration{
|
||||
|
||||
private readonly string file;
|
||||
|
||||
public UserConfig(string file){ // TODO make private after removing UserConfigLegacy
|
||||
private UserConfig(string file){
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
@@ -174,6 +165,29 @@ namespace TweetDuck.Configuration{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Reload(){
|
||||
try{
|
||||
LoadInternal(false);
|
||||
return true;
|
||||
}catch(FileNotFoundException){
|
||||
try{
|
||||
Serializer.Write(file, new UserConfig(file));
|
||||
LoadInternal(false);
|
||||
return true;
|
||||
}catch(Exception e){
|
||||
Program.Reporter.HandleException("Configuration Error", "Could not regenerate configuration file.", true, e);
|
||||
return false;
|
||||
}
|
||||
}catch(Exception e){
|
||||
Program.Reporter.HandleException("Configuration Error", "Could not reload configuration file.", true, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadInternal(bool backup){
|
||||
Serializer.Read(backup ? GetBackupFile(file) : file, this);
|
||||
}
|
||||
|
||||
public static UserConfig Load(string file){
|
||||
Exception firstException = null;
|
||||
@@ -181,22 +195,23 @@ namespace TweetDuck.Configuration{
|
||||
for(int attempt = 0; attempt < 2; attempt++){
|
||||
try{
|
||||
UserConfig config = new UserConfig(file);
|
||||
Serializer.Read(attempt == 0 ? file : GetBackupFile(file), config);
|
||||
config.LoadInternal(attempt > 0);
|
||||
return config;
|
||||
}catch(FileNotFoundException){
|
||||
}catch(DirectoryNotFoundException){
|
||||
break;
|
||||
}catch(FormatException){
|
||||
UserConfig config = UserConfigLegacy.Load(file);
|
||||
config.Save();
|
||||
return config;
|
||||
}catch(Exception e){
|
||||
if (attempt == 0){
|
||||
firstException = e;
|
||||
Program.Reporter.Log(e.ToString());
|
||||
}
|
||||
else if (firstException is FormatException){
|
||||
Program.Reporter.HandleException("Configuration Error", "The configuration file is outdated or corrupted. If you continue, your program options will be reset.", true, e);
|
||||
return new UserConfig(file);
|
||||
}
|
||||
else if (firstException != null){
|
||||
Program.Reporter.HandleException("Configuration Error", "Could not open the backup configuration file. If you continue, your program options will be reset.", true, e);
|
||||
return new UserConfig(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,210 +0,0 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Runtime.Serialization.Formatters.Binary;
|
||||
using TweetDuck.Core;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Notification;
|
||||
using TweetDuck.Data;
|
||||
|
||||
namespace TweetDuck.Configuration{
|
||||
[Serializable]
|
||||
sealed class UserConfigLegacy{ // TODO remove eventually
|
||||
private static readonly IFormatter Formatter = new BinaryFormatter{ Binder = new LegacyBinder() };
|
||||
|
||||
private class LegacyBinder : SerializationBinder{
|
||||
public override Type BindToType(string assemblyName, string typeName){
|
||||
return Type.GetType(string.Format("{0}, {1}", typeName.Replace("TweetDck", "TweetDuck").Replace(".UserConfig", ".UserConfigLegacy").Replace("Core.Utils.WindowState", "Data.WindowState"), assemblyName.Replace("TweetDck", "TweetDuck")));
|
||||
}
|
||||
}
|
||||
|
||||
private const int CurrentFileVersion = 11;
|
||||
|
||||
// START OF CONFIGURATION
|
||||
|
||||
public WindowState BrowserWindow { get; set; }
|
||||
public WindowState PluginsWindow { get; set; }
|
||||
|
||||
public bool DisplayNotificationColumn { get; set; }
|
||||
public bool DisplayNotificationTimer { get; set; }
|
||||
public bool NotificationTimerCountDown { get; set; }
|
||||
public bool NotificationSkipOnLinkClick { get; set; }
|
||||
public bool NotificationNonIntrusiveMode { get; set; }
|
||||
|
||||
public int NotificationIdlePauseSeconds { get; set; }
|
||||
public int NotificationDurationValue { get; set; }
|
||||
public int NotificationScrollSpeed { get; set; }
|
||||
|
||||
public TweetNotification.Position NotificationPosition { get; set; }
|
||||
public Point CustomNotificationPosition { get; set; }
|
||||
public int NotificationEdgeDistance { get; set; }
|
||||
public int NotificationDisplay { get; set; }
|
||||
|
||||
public TweetNotification.Size NotificationSize { get; set; }
|
||||
public Size CustomNotificationSize { get; set; }
|
||||
|
||||
public bool EnableSpellCheck { get; set; }
|
||||
public bool ExpandLinksOnHover { get; set; }
|
||||
public bool SwitchAccountSelectors { get; set; }
|
||||
public bool EnableTrayHighlight { get; set; }
|
||||
|
||||
public bool EnableUpdateCheck { get; set; }
|
||||
public string DismissedUpdate { get; set; }
|
||||
|
||||
public string CustomCefArgs { get; set; }
|
||||
public string CustomBrowserCSS { get; set; }
|
||||
public string CustomNotificationCSS { get; set; }
|
||||
|
||||
public string NotificationSoundPath{
|
||||
get => string.IsNullOrEmpty(notificationSoundPath) ? string.Empty : notificationSoundPath;
|
||||
set => notificationSoundPath = value;
|
||||
}
|
||||
|
||||
public bool MuteNotifications{
|
||||
get => muteNotifications;
|
||||
set => muteNotifications = value;
|
||||
}
|
||||
|
||||
public int ZoomLevel{
|
||||
get => zoomLevel;
|
||||
set => zoomLevel = value;
|
||||
}
|
||||
|
||||
public TrayIcon.Behavior TrayBehavior{
|
||||
get => trayBehavior;
|
||||
set => trayBehavior = value;
|
||||
}
|
||||
|
||||
// END OF CONFIGURATION
|
||||
|
||||
[NonSerialized]
|
||||
private string file;
|
||||
|
||||
private int fileVersion;
|
||||
private bool muteNotifications;
|
||||
private int zoomLevel;
|
||||
private string notificationSoundPath;
|
||||
private TrayIcon.Behavior trayBehavior;
|
||||
|
||||
private UserConfigLegacy(string file){
|
||||
this.file = file;
|
||||
|
||||
BrowserWindow = new WindowState();
|
||||
ZoomLevel = 100;
|
||||
DisplayNotificationTimer = true;
|
||||
NotificationNonIntrusiveMode = true;
|
||||
NotificationPosition = TweetNotification.Position.TopRight;
|
||||
CustomNotificationPosition = ControlExtensions.InvisibleLocation;
|
||||
NotificationSize = TweetNotification.Size.Auto;
|
||||
NotificationEdgeDistance = 8;
|
||||
NotificationDurationValue = 25;
|
||||
NotificationScrollSpeed = 100;
|
||||
EnableUpdateCheck = true;
|
||||
ExpandLinksOnHover = true;
|
||||
SwitchAccountSelectors = true;
|
||||
EnableTrayHighlight = true;
|
||||
PluginsWindow = new WindowState();
|
||||
}
|
||||
|
||||
private void UpgradeFile(){
|
||||
if (fileVersion == CurrentFileVersion){
|
||||
return;
|
||||
}
|
||||
|
||||
// if outdated, cycle through all versions
|
||||
if (fileVersion <= 5){
|
||||
DisplayNotificationTimer = true;
|
||||
EnableUpdateCheck = true;
|
||||
ExpandLinksOnHover = true;
|
||||
BrowserWindow = new WindowState();
|
||||
PluginsWindow = new WindowState();
|
||||
EnableTrayHighlight = true;
|
||||
NotificationDurationValue = 25;
|
||||
fileVersion = 6;
|
||||
}
|
||||
|
||||
if (fileVersion == 6){
|
||||
NotificationNonIntrusiveMode = true;
|
||||
++fileVersion;
|
||||
}
|
||||
|
||||
if (fileVersion == 7){
|
||||
ZoomLevel = 100;
|
||||
++fileVersion;
|
||||
}
|
||||
|
||||
if (fileVersion == 8){
|
||||
SwitchAccountSelectors = true;
|
||||
++fileVersion;
|
||||
}
|
||||
|
||||
if (fileVersion == 9){
|
||||
NotificationScrollSpeed = 100;
|
||||
++fileVersion;
|
||||
}
|
||||
|
||||
if (fileVersion == 10){
|
||||
NotificationSize = TweetNotification.Size.Auto;
|
||||
++fileVersion;
|
||||
}
|
||||
|
||||
// update the version
|
||||
fileVersion = CurrentFileVersion;
|
||||
}
|
||||
|
||||
public UserConfig ConvertLegacy(){
|
||||
return new UserConfig(file){
|
||||
BrowserWindow = BrowserWindow,
|
||||
PluginsWindow = PluginsWindow,
|
||||
DisplayNotificationColumn = DisplayNotificationColumn,
|
||||
DisplayNotificationTimer = DisplayNotificationTimer,
|
||||
NotificationTimerCountDown = NotificationTimerCountDown,
|
||||
NotificationSkipOnLinkClick = NotificationSkipOnLinkClick,
|
||||
NotificationNonIntrusiveMode = NotificationNonIntrusiveMode,
|
||||
NotificationIdlePauseSeconds = NotificationIdlePauseSeconds,
|
||||
NotificationDurationValue = NotificationDurationValue,
|
||||
NotificationScrollSpeed = NotificationScrollSpeed,
|
||||
NotificationPosition = NotificationPosition,
|
||||
CustomNotificationPosition = CustomNotificationPosition,
|
||||
NotificationEdgeDistance = NotificationEdgeDistance,
|
||||
NotificationDisplay = NotificationDisplay,
|
||||
NotificationSize = NotificationSize,
|
||||
CustomNotificationSize = CustomNotificationSize,
|
||||
EnableSpellCheck = EnableSpellCheck,
|
||||
ExpandLinksOnHover = ExpandLinksOnHover,
|
||||
SwitchAccountSelectors = SwitchAccountSelectors,
|
||||
EnableTrayHighlight = EnableTrayHighlight,
|
||||
EnableUpdateCheck = EnableUpdateCheck,
|
||||
DismissedUpdate = DismissedUpdate,
|
||||
CustomCefArgs = CustomCefArgs,
|
||||
CustomBrowserCSS = CustomBrowserCSS,
|
||||
CustomNotificationCSS = CustomNotificationCSS,
|
||||
NotificationSoundPath = NotificationSoundPath,
|
||||
MuteNotifications = MuteNotifications,
|
||||
ZoomLevel = ZoomLevel,
|
||||
TrayBehavior = TrayBehavior
|
||||
};
|
||||
}
|
||||
|
||||
public static UserConfig Load(string file){
|
||||
UserConfigLegacy config = null;
|
||||
|
||||
try{
|
||||
using(Stream stream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read)){
|
||||
if ((config = Formatter.Deserialize(stream) as UserConfigLegacy) != null){
|
||||
config.file = file;
|
||||
}
|
||||
}
|
||||
|
||||
config?.UpgradeFile();
|
||||
}catch(FileNotFoundException){
|
||||
}catch(DirectoryNotFoundException){
|
||||
}catch(Exception e){
|
||||
Program.Reporter.HandleException("Configuration Error", "Could not open the configuration file.", true, e);
|
||||
}
|
||||
|
||||
return (config ?? new UserConfigLegacy(file)).ConvertLegacy();
|
||||
}
|
||||
}
|
||||
}
|
@@ -3,6 +3,7 @@ using System.Text;
|
||||
using System.Windows.Forms;
|
||||
using CefSharp;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Handling;
|
||||
using TweetDuck.Core.Notification;
|
||||
using TweetDuck.Core.Other;
|
||||
using TweetDuck.Core.Utils;
|
||||
@@ -10,17 +11,18 @@ using TweetDuck.Resources;
|
||||
|
||||
namespace TweetDuck.Core.Bridge{
|
||||
sealed class TweetDeckBridge{
|
||||
public static string LastRightClickedLink = string.Empty;
|
||||
public static string LastRightClickedImage = string.Empty;
|
||||
public static string LastHighlightedTweet = string.Empty;
|
||||
public static string LastHighlightedQuotedTweet = string.Empty;
|
||||
public static string LastHighlightedTweetAuthor = string.Empty;
|
||||
public static string[] LastHighlightedTweetImages = StringUtils.EmptyArray;
|
||||
public static Dictionary<string, string> SessionData = new Dictionary<string, string>(2);
|
||||
public static string LastHighlightedTweetUrl = string.Empty;
|
||||
public static string LastHighlightedQuoteUrl = string.Empty;
|
||||
private static string LastHighlightedTweetAuthors = string.Empty;
|
||||
private static string LastHighlightedTweetImages = string.Empty;
|
||||
|
||||
public static string[] LastHighlightedTweetAuthorsArray => LastHighlightedTweetAuthors.Split(';');
|
||||
public static string[] LastHighlightedTweetImagesArray => LastHighlightedTweetImages.Split(';');
|
||||
|
||||
private static readonly Dictionary<string, string> SessionData = new Dictionary<string, string>(2);
|
||||
|
||||
public static void ResetStaticProperties(){
|
||||
LastRightClickedLink = LastRightClickedImage = LastHighlightedTweet = LastHighlightedQuotedTweet = LastHighlightedTweetAuthor = string.Empty;
|
||||
LastHighlightedTweetImages = StringUtils.EmptyArray;
|
||||
LastHighlightedTweetUrl = LastHighlightedQuoteUrl = LastHighlightedTweetAuthors = LastHighlightedTweetImages = string.Empty;
|
||||
}
|
||||
|
||||
public static void RestoreSessionData(IFrame frame){
|
||||
@@ -56,20 +58,16 @@ namespace TweetDuck.Core.Bridge{
|
||||
});
|
||||
}
|
||||
|
||||
public void SetLastRightClickedLink(string link){
|
||||
form.InvokeAsyncSafe(() => LastRightClickedLink = link);
|
||||
public void SetLastRightClickInfo(string type, string link){
|
||||
form.InvokeAsyncSafe(() => ContextMenuBase.SetContextInfo(type, link));
|
||||
}
|
||||
|
||||
public void SetLastRightClickedImage(string link){
|
||||
form.InvokeAsyncSafe(() => LastRightClickedImage = link);
|
||||
}
|
||||
|
||||
public void SetLastHighlightedTweet(string link, string quotedLink, string author, string imageList){
|
||||
public void SetLastHighlightedTweet(string tweetUrl, string quoteUrl, string authors, string imageList){
|
||||
form.InvokeAsyncSafe(() => {
|
||||
LastHighlightedTweet = link;
|
||||
LastHighlightedQuotedTweet = quotedLink;
|
||||
LastHighlightedTweetAuthor = author;
|
||||
LastHighlightedTweetImages = imageList.Split(';');
|
||||
LastHighlightedTweetUrl = tweetUrl;
|
||||
LastHighlightedQuoteUrl = quoteUrl;
|
||||
LastHighlightedTweetAuthors = authors;
|
||||
LastHighlightedTweetImages = imageList;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -77,10 +75,10 @@ namespace TweetDuck.Core.Bridge{
|
||||
form.InvokeAsyncSafe(form.OpenContextMenu);
|
||||
}
|
||||
|
||||
public void OnTweetPopup(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(() => {
|
||||
form.OnTweetNotification();
|
||||
notification.ShowNotification(new TweetNotification(columnName, tweetHtml, tweetCharacters, tweetUrl, quoteUrl));
|
||||
notification.ShowNotification(new TweetNotification(columnId, chirpId, columnName, tweetHtml, tweetCharacters, tweetUrl, quoteUrl));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -122,12 +120,12 @@ namespace TweetDuck.Core.Bridge{
|
||||
form.InvokeAsyncSafe(WindowsUtils.ClipboardStripHtmlStyles);
|
||||
}
|
||||
|
||||
public int GetIdleSeconds(){
|
||||
return NativeMethods.GetIdleSeconds();
|
||||
public void OpenBrowser(string url){
|
||||
form.InvokeAsyncSafe(() => BrowserUtils.OpenExternalBrowser(url));
|
||||
}
|
||||
|
||||
public void OpenBrowser(string url){
|
||||
BrowserUtils.OpenExternalBrowser(url);
|
||||
public int GetIdleSeconds(){
|
||||
return NativeMethods.GetIdleSeconds();
|
||||
}
|
||||
|
||||
public void Alert(string type, string contents){
|
||||
|
@@ -1,8 +1,7 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Core.Controls{
|
||||
static class ControlExtensions{
|
||||
@@ -67,12 +66,6 @@ namespace TweetDuck.Core.Controls{
|
||||
else return true;
|
||||
}
|
||||
|
||||
public static void SetElevated(this Button button){
|
||||
button.Text = " "+button.Text;
|
||||
button.FlatStyle = FlatStyle.System;
|
||||
NativeMethods.SendMessage(button.Handle, NativeMethods.BCM_SETSHIELD, new UIntPtr(0), new IntPtr(1));
|
||||
}
|
||||
|
||||
public static void EnableMultilineShortcuts(this TextBox textBox){
|
||||
textBox.KeyDown += (sender, args) => {
|
||||
if (args.Control && args.KeyCode == Keys.A){
|
||||
|
@@ -2,7 +2,7 @@
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace TweetDuck.Core.Controls{
|
||||
class FlatButton : Button{
|
||||
sealed class FlatButton : Button{
|
||||
protected override bool ShowFocusCues => false;
|
||||
|
||||
public FlatButton(){
|
||||
|
4
Core/FormBrowser.Designer.cs
generated
4
Core/FormBrowser.Designer.cs
generated
@@ -39,10 +39,10 @@
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.BackColor = TweetDuck.Core.Utils.TwitterUtils.BackgroundColor;
|
||||
this.ClientSize = new System.Drawing.Size(324, 386);
|
||||
this.ClientSize = new System.Drawing.Size(400, 386);
|
||||
this.Icon = Properties.Resources.icon;
|
||||
this.Location = TweetDuck.Core.Controls.ControlExtensions.InvisibleLocation;
|
||||
this.MinimumSize = new System.Drawing.Size(340, 424);
|
||||
this.MinimumSize = new System.Drawing.Size(416, 424);
|
||||
this.Name = "FormBrowser";
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.Manual;
|
||||
this.Activated += new System.EventHandler(this.FormBrowser_Activated);
|
||||
|
@@ -19,7 +19,6 @@ using TweetDuck.Plugins.Enums;
|
||||
using TweetDuck.Plugins.Events;
|
||||
using TweetDuck.Resources;
|
||||
using TweetDuck.Updates;
|
||||
using TweetDuck.Updates.Events;
|
||||
using TweetLib.Audio;
|
||||
|
||||
namespace TweetDuck.Core{
|
||||
@@ -85,8 +84,10 @@ namespace TweetDuck.Core{
|
||||
this.notification.Show();
|
||||
|
||||
this.browser = new ChromiumWebBrowser("https://tweetdeck.twitter.com/"){
|
||||
DialogHandler = new FileDialogHandler(),
|
||||
MenuHandler = new ContextMenuBrowser(this),
|
||||
JsDialogHandler = new JavaScriptDialogHandler(),
|
||||
KeyboardHandler = new KeyboardHandlerBrowser(this),
|
||||
LifeSpanHandler = new LifeSpanHandler(),
|
||||
RequestHandler = new RequestHandlerBrowser()
|
||||
};
|
||||
@@ -336,7 +337,7 @@ namespace TweetDuck.Core{
|
||||
browser.ExecuteScriptAsync("window.TDPF_setPluginState", e.Plugin, e.IsEnabled);
|
||||
}
|
||||
|
||||
private void updates_UpdateAccepted(object sender, UpdateAcceptedEventArgs e){
|
||||
private void updates_UpdateAccepted(object sender, UpdateEventArgs e){
|
||||
this.InvokeAsyncSafe(() => {
|
||||
FormManager.CloseAllDialogs();
|
||||
|
||||
@@ -350,9 +351,9 @@ namespace TweetDuck.Core{
|
||||
});
|
||||
}
|
||||
|
||||
private void updates_UpdateDismissed(object sender, UpdateDismissedEventArgs e){
|
||||
private void updates_UpdateDismissed(object sender, UpdateEventArgs e){
|
||||
this.InvokeAsyncSafe(() => {
|
||||
Config.DismissedUpdate = e.VersionTag;
|
||||
Config.DismissedUpdate = e.UpdateInfo.VersionTag;
|
||||
Config.Save();
|
||||
});
|
||||
}
|
||||
@@ -377,7 +378,7 @@ namespace TweetDuck.Core{
|
||||
if (isLoaded){
|
||||
if (m.Msg == Program.WindowRestoreMessage){
|
||||
if (WindowsUtils.CurrentProcessID == m.WParam.ToInt32()){
|
||||
trayIcon_ClickRestore(trayIcon, new EventArgs());
|
||||
trayIcon_ClickRestore(trayIcon, EventArgs.Empty);
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -389,16 +390,6 @@ namespace TweetDuck.Core{
|
||||
BrowserProcesses.Link(m.LParam.ToInt32(), processId);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
else if (m.Msg == Program.VideoPlayerMessage){
|
||||
int volume = m.WParam.ToInt32();
|
||||
|
||||
if (Handle == m.LParam && volume != Config.VideoPlayerVolume){
|
||||
Config.VideoPlayerVolume = volume;
|
||||
Config.Save();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -463,8 +454,10 @@ namespace TweetDuck.Core{
|
||||
|
||||
form.FormClosed += (sender, args) => {
|
||||
if (!prevEnableUpdateCheck && Config.EnableUpdateCheck){
|
||||
updates.DismissUpdate(string.Empty);
|
||||
updates.Check(false);
|
||||
Config.DismissedUpdate = null;
|
||||
Config.Save();
|
||||
|
||||
updates.Check(true);
|
||||
}
|
||||
|
||||
if (!Config.EnableTrayHighlight){
|
||||
@@ -478,7 +471,13 @@ namespace TweetDuck.Core{
|
||||
memoryUsageTracker.Stop();
|
||||
}
|
||||
|
||||
UpdateProperties(PropertyBridge.Environment.Browser);
|
||||
if (form.ShouldReloadBrowser){
|
||||
FormManager.TryFind<FormPlugins>()?.Close();
|
||||
plugins.Reload(); // also reloads the browser
|
||||
}
|
||||
else{
|
||||
UpdateProperties(PropertyBridge.Environment.Browser);
|
||||
}
|
||||
|
||||
notification.RequiresResize = true;
|
||||
form.Dispose();
|
||||
@@ -516,6 +515,7 @@ namespace TweetDuck.Core{
|
||||
soundNotification.PlaybackError += soundNotification_PlaybackError;
|
||||
}
|
||||
|
||||
soundNotification.SetVolume(Config.NotificationSoundVolume);
|
||||
soundNotification.Play(Config.NotificationSoundPath);
|
||||
}
|
||||
|
||||
@@ -541,6 +541,29 @@ namespace TweetDuck.Core{
|
||||
browser.ExecuteScriptAsync("$('#td-video-player-overlay').remove()");
|
||||
}
|
||||
|
||||
public bool ProcessBrowserKey(Keys key){
|
||||
if (videoPlayer != null && videoPlayer.Running){
|
||||
videoPlayer.SendKeyEvent(key);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void ShowTweetDetail(string columnId, string chirpId, string fallbackUrl){
|
||||
Activate();
|
||||
|
||||
using(IFrame frame = browser.GetBrowser().MainFrame){
|
||||
if (!TwitterUtils.IsTweetDeckWebsite(frame)){
|
||||
FormMessage.Error("View Tweet Detail", "TweetDeck is not currently loaded.", FormMessage.OK);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
notification.FinishCurrentNotification();
|
||||
browser.ExecuteScriptAsync("window.TDGF_showTweetDetail", columnId, chirpId, fallbackUrl);
|
||||
}
|
||||
|
||||
public void OnTweetScreenshotReady(string html, int width, int height){
|
||||
if (notificationScreenshotManager == null){
|
||||
notificationScreenshotManager = new TweetScreenshotManager(this, plugins);
|
||||
|
@@ -4,8 +4,12 @@ using TweetDuck.Core.Other;
|
||||
|
||||
namespace TweetDuck.Core{
|
||||
static class FormManager{
|
||||
public static T TryFind<T>() where T : Form{
|
||||
return Application.OpenForms.OfType<T>().FirstOrDefault();
|
||||
}
|
||||
|
||||
public static bool TryBringToFront<T>() where T : Form{
|
||||
T form = Application.OpenForms.OfType<T>().FirstOrDefault();
|
||||
T form = TryFind<T>();
|
||||
|
||||
if (form != null){
|
||||
form.BringToFront();
|
||||
|
@@ -6,70 +6,85 @@ using CefSharp;
|
||||
using TweetDuck.Core.Bridge;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Utils;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace TweetDuck.Core.Handling{
|
||||
abstract class ContextMenuBase : IContextMenuHandler{
|
||||
protected static readonly bool HasDevTools = File.Exists(Path.Combine(Program.ProgramPath, "devtools_resources.pak"));
|
||||
|
||||
private static TwitterUtils.ImageQuality ImageQuality => Program.UserConfig.TwitterImageQuality;
|
||||
|
||||
private static string GetLink(IContextMenuParams parameters){
|
||||
return string.IsNullOrEmpty(TweetDeckBridge.LastRightClickedLink) ? parameters.UnfilteredLinkUrl : TweetDeckBridge.LastRightClickedLink;
|
||||
}
|
||||
|
||||
private static string GetImage(IContextMenuParams parameters){
|
||||
return string.IsNullOrEmpty(TweetDeckBridge.LastRightClickedImage) ? parameters.SourceUrl : TweetDeckBridge.LastRightClickedImage;
|
||||
}
|
||||
|
||||
private const int MenuOpenLinkUrl = 26500;
|
||||
private const int MenuCopyLinkUrl = 26501;
|
||||
private const int MenuCopyUsername = 26502;
|
||||
private const int MenuOpenImageUrl = 26503;
|
||||
private const int MenuCopyImageUrl = 26504;
|
||||
private const int MenuSaveImage = 26505;
|
||||
private const int MenuSaveAllImages = 26506;
|
||||
private const int MenuOpenDevTools = 26599;
|
||||
|
||||
private readonly Form form;
|
||||
|
||||
private string lastHighlightedTweetAuthor;
|
||||
private static KeyValuePair<string, string> ContextInfo;
|
||||
private static bool IsLink => ContextInfo.Key == "link";
|
||||
private static bool IsImage => ContextInfo.Key == "image";
|
||||
private static bool IsVideo => ContextInfo.Key == "video";
|
||||
|
||||
public static void SetContextInfo(string type, string link){
|
||||
ContextInfo = new KeyValuePair<string, string>(string.IsNullOrEmpty(link) ? null : type, link);
|
||||
}
|
||||
|
||||
private static string GetMediaLink(IContextMenuParams parameters){
|
||||
return IsImage || IsVideo ? ContextInfo.Value : parameters.SourceUrl;
|
||||
}
|
||||
|
||||
private const CefMenuCommand MenuOpenLinkUrl = (CefMenuCommand)26500;
|
||||
private const CefMenuCommand MenuCopyLinkUrl = (CefMenuCommand)26501;
|
||||
private const CefMenuCommand MenuCopyUsername = (CefMenuCommand)26502;
|
||||
private const CefMenuCommand MenuOpenMediaUrl = (CefMenuCommand)26503;
|
||||
private const CefMenuCommand MenuCopyMediaUrl = (CefMenuCommand)26504;
|
||||
private const CefMenuCommand MenuSaveMedia = (CefMenuCommand)26505;
|
||||
private const CefMenuCommand MenuSaveTweetImages = (CefMenuCommand)26506;
|
||||
private const CefMenuCommand MenuOpenDevTools = (CefMenuCommand)26599;
|
||||
|
||||
private string[] lastHighlightedTweetAuthors;
|
||||
private string[] lastHighlightedTweetImageList;
|
||||
|
||||
protected ContextMenuBase(Form form){
|
||||
this.form = form;
|
||||
}
|
||||
|
||||
public virtual void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){
|
||||
bool hasTweetImage = !string.IsNullOrEmpty(TweetDeckBridge.LastRightClickedImage);
|
||||
lastHighlightedTweetAuthor = TweetDeckBridge.LastHighlightedTweetAuthor;
|
||||
lastHighlightedTweetImageList = TweetDeckBridge.LastHighlightedTweetImages;
|
||||
|
||||
if (!TwitterUtils.IsTweetDeckWebsite(frame) || browser.IsLoading){
|
||||
lastHighlightedTweetAuthor = string.Empty;
|
||||
lastHighlightedTweetAuthors = StringUtils.EmptyArray;
|
||||
lastHighlightedTweetImageList = StringUtils.EmptyArray;
|
||||
ContextInfo = default(KeyValuePair<string, string>);
|
||||
}
|
||||
else{
|
||||
lastHighlightedTweetAuthors = TweetDeckBridge.LastHighlightedTweetAuthorsArray;
|
||||
lastHighlightedTweetImageList = TweetDeckBridge.LastHighlightedTweetImagesArray;
|
||||
}
|
||||
|
||||
if (parameters.TypeFlags.HasFlag(ContextMenuType.Link) && !parameters.UnfilteredLinkUrl.EndsWith("tweetdeck.twitter.com/#", StringComparison.Ordinal) && !hasTweetImage){
|
||||
bool hasTweetImage = IsImage;
|
||||
bool hasTweetVideo = IsVideo;
|
||||
|
||||
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 (TwitterUtils.RegexAccount.IsMatch(parameters.UnfilteredLinkUrl)){
|
||||
model.AddItem((CefMenuCommand)MenuOpenLinkUrl, "Open account in browser");
|
||||
model.AddItem((CefMenuCommand)MenuCopyLinkUrl, "Copy account address");
|
||||
model.AddItem((CefMenuCommand)MenuCopyUsername, "Copy account username");
|
||||
model.AddItem(MenuOpenLinkUrl, TextOpen("account"));
|
||||
model.AddItem(MenuCopyLinkUrl, TextCopy("account"));
|
||||
model.AddItem(MenuCopyUsername, "Copy account username");
|
||||
}
|
||||
else{
|
||||
model.AddItem((CefMenuCommand)MenuOpenLinkUrl, "Open link in browser");
|
||||
model.AddItem((CefMenuCommand)MenuCopyLinkUrl, "Copy link address");
|
||||
model.AddItem(MenuOpenLinkUrl, TextOpen("link"));
|
||||
model.AddItem(MenuCopyLinkUrl, TextCopy("link"));
|
||||
}
|
||||
|
||||
model.AddSeparator();
|
||||
}
|
||||
|
||||
if ((parameters.TypeFlags.HasFlag(ContextMenuType.Media) && parameters.HasImageContents) || hasTweetImage){
|
||||
model.AddItem((CefMenuCommand)MenuOpenImageUrl, "Open image in browser");
|
||||
model.AddItem((CefMenuCommand)MenuCopyImageUrl, "Copy image address");
|
||||
model.AddItem((CefMenuCommand)MenuSaveImage, "Save image as...");
|
||||
if (hasTweetVideo){
|
||||
model.AddItem(MenuOpenMediaUrl, TextOpen("video"));
|
||||
model.AddItem(MenuCopyMediaUrl, TextCopy("video"));
|
||||
model.AddItem(MenuSaveMedia, TextSave("video"));
|
||||
model.AddSeparator();
|
||||
}
|
||||
else if ((parameters.TypeFlags.HasFlag(ContextMenuType.Media) && parameters.HasImageContents) || hasTweetImage){
|
||||
model.AddItem(MenuOpenMediaUrl, TextOpen("image"));
|
||||
model.AddItem(MenuCopyMediaUrl, TextCopy("image"));
|
||||
model.AddItem(MenuSaveMedia, TextSave("image"));
|
||||
|
||||
if (lastHighlightedTweetImageList.Length > 1){
|
||||
model.AddItem((CefMenuCommand)MenuSaveAllImages, "Save all images as...");
|
||||
model.AddItem(MenuSaveTweetImages, TextSave("all images"));
|
||||
}
|
||||
|
||||
model.AddSeparator();
|
||||
@@ -77,34 +92,40 @@ namespace TweetDuck.Core.Handling{
|
||||
}
|
||||
|
||||
public virtual bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags){
|
||||
switch((int)commandId){
|
||||
switch(commandId){
|
||||
case MenuOpenLinkUrl:
|
||||
BrowserUtils.OpenExternalBrowser(parameters.LinkUrl);
|
||||
OpenBrowser(browserControl.AsControl(), IsLink ? ContextInfo.Value : parameters.LinkUrl);
|
||||
break;
|
||||
|
||||
case MenuCopyLinkUrl:
|
||||
SetClipboardText(GetLink(parameters));
|
||||
break;
|
||||
|
||||
case MenuOpenImageUrl:
|
||||
BrowserUtils.OpenExternalBrowser(TwitterUtils.GetImageLink(GetImage(parameters), ImageQuality));
|
||||
break;
|
||||
|
||||
case MenuSaveImage:
|
||||
TwitterUtils.DownloadImage(GetImage(parameters), lastHighlightedTweetAuthor, ImageQuality);
|
||||
break;
|
||||
|
||||
case MenuSaveAllImages:
|
||||
TwitterUtils.DownloadImages(lastHighlightedTweetImageList, lastHighlightedTweetAuthor, ImageQuality);
|
||||
break;
|
||||
|
||||
case MenuCopyImageUrl:
|
||||
SetClipboardText(TwitterUtils.GetImageLink(GetImage(parameters), ImageQuality));
|
||||
SetClipboardText(browserControl.AsControl(), IsLink ? ContextInfo.Value : parameters.UnfilteredLinkUrl);
|
||||
break;
|
||||
|
||||
case MenuCopyUsername:
|
||||
Match match = TwitterUtils.RegexAccount.Match(parameters.UnfilteredLinkUrl);
|
||||
SetClipboardText(match.Success ? match.Groups[1].Value : parameters.UnfilteredLinkUrl);
|
||||
SetClipboardText(browserControl.AsControl(), match.Success ? match.Groups[1].Value : parameters.UnfilteredLinkUrl);
|
||||
break;
|
||||
|
||||
case MenuOpenMediaUrl:
|
||||
OpenBrowser(browserControl.AsControl(), TwitterUtils.GetMediaLink(GetMediaLink(parameters), ImageQuality));
|
||||
break;
|
||||
|
||||
case MenuCopyMediaUrl:
|
||||
SetClipboardText(browserControl.AsControl(), TwitterUtils.GetMediaLink(GetMediaLink(parameters), ImageQuality));
|
||||
break;
|
||||
|
||||
case MenuSaveMedia:
|
||||
if (IsVideo){
|
||||
TwitterUtils.DownloadVideo(GetMediaLink(parameters));
|
||||
}
|
||||
else{
|
||||
TwitterUtils.DownloadImage(GetMediaLink(parameters), lastHighlightedTweetAuthors.LastOrDefault(), ImageQuality);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case MenuSaveTweetImages:
|
||||
TwitterUtils.DownloadImages(lastHighlightedTweetImageList, lastHighlightedTweetAuthors.LastOrDefault(), ImageQuality);
|
||||
break;
|
||||
|
||||
case MenuOpenDevTools:
|
||||
@@ -116,20 +137,23 @@ namespace TweetDuck.Core.Handling{
|
||||
}
|
||||
|
||||
public virtual void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame){
|
||||
TweetDeckBridge.LastRightClickedLink = string.Empty;
|
||||
TweetDeckBridge.LastRightClickedImage = string.Empty;
|
||||
ContextInfo = default(KeyValuePair<string, string>);
|
||||
}
|
||||
|
||||
public virtual bool RunContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model, IRunContextMenuCallback callback){
|
||||
return false;
|
||||
}
|
||||
|
||||
protected void SetClipboardText(string text){
|
||||
form.InvokeAsyncSafe(() => WindowsUtils.SetClipboard(text, TextDataFormat.UnicodeText));
|
||||
protected void OpenBrowser(Control control, string url){
|
||||
control.InvokeAsyncSafe(() => BrowserUtils.OpenExternalBrowser(url));
|
||||
}
|
||||
|
||||
protected void SetClipboardText(Control control, string text){
|
||||
control.InvokeAsyncSafe(() => WindowsUtils.SetClipboard(text, TextDataFormat.UnicodeText));
|
||||
}
|
||||
|
||||
protected static void AddDebugMenuItems(IMenuModel model){
|
||||
model.AddItem((CefMenuCommand)MenuOpenDevTools, "Open dev tools");
|
||||
model.AddItem(MenuOpenDevTools, "Open dev tools");
|
||||
}
|
||||
|
||||
protected static void RemoveSeparatorIfLast(IMenuModel model){
|
||||
|
@@ -5,18 +5,18 @@ using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Core.Handling{
|
||||
class ContextMenuBrowser : ContextMenuBase{
|
||||
private const int MenuGlobal = 26600;
|
||||
private const int MenuMute = 26601;
|
||||
private const int MenuSettings = 26602;
|
||||
private const int MenuPlugins = 26003;
|
||||
private const int MenuAbout = 26604;
|
||||
sealed class ContextMenuBrowser : ContextMenuBase{
|
||||
private const CefMenuCommand MenuGlobal = (CefMenuCommand)26600;
|
||||
private const CefMenuCommand MenuMute = (CefMenuCommand)26601;
|
||||
private const CefMenuCommand MenuSettings = (CefMenuCommand)26602;
|
||||
private const CefMenuCommand MenuPlugins = (CefMenuCommand)26003;
|
||||
private const CefMenuCommand MenuAbout = (CefMenuCommand)26604;
|
||||
|
||||
private const int MenuOpenTweetUrl = 26610;
|
||||
private const int MenuCopyTweetUrl = 26611;
|
||||
private const int MenuOpenQuotedTweetUrl = 26612;
|
||||
private const int MenuCopyQuotedTweetUrl = 26613;
|
||||
private const int MenuScreenshotTweet = 26614;
|
||||
private const CefMenuCommand MenuOpenTweetUrl = (CefMenuCommand)26610;
|
||||
private const CefMenuCommand MenuCopyTweetUrl = (CefMenuCommand)26611;
|
||||
private const CefMenuCommand MenuOpenQuotedTweetUrl = (CefMenuCommand)26612;
|
||||
private const CefMenuCommand MenuCopyQuotedTweetUrl = (CefMenuCommand)26613;
|
||||
private const CefMenuCommand MenuScreenshotTweet = (CefMenuCommand)26614;
|
||||
|
||||
private const string TitleReloadBrowser = "Reload browser";
|
||||
private const string TitleMuteNotifications = "Mute notifications";
|
||||
@@ -26,10 +26,10 @@ namespace TweetDuck.Core.Handling{
|
||||
|
||||
private readonly FormBrowser form;
|
||||
|
||||
private string lastHighlightedTweet;
|
||||
private string lastHighlightedQuotedTweet;
|
||||
private string lastHighlightedTweetUrl;
|
||||
private string lastHighlightedQuoteUrl;
|
||||
|
||||
public ContextMenuBrowser(FormBrowser form) : base(form){
|
||||
public ContextMenuBrowser(FormBrowser form){
|
||||
this.form = form;
|
||||
}
|
||||
|
||||
@@ -46,23 +46,23 @@ namespace TweetDuck.Core.Handling{
|
||||
|
||||
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
|
||||
|
||||
lastHighlightedTweet = TweetDeckBridge.LastHighlightedTweet;
|
||||
lastHighlightedQuotedTweet = TweetDeckBridge.LastHighlightedQuotedTweet;
|
||||
lastHighlightedTweetUrl = TweetDeckBridge.LastHighlightedTweetUrl;
|
||||
lastHighlightedQuoteUrl = TweetDeckBridge.LastHighlightedQuoteUrl;
|
||||
|
||||
if (!TwitterUtils.IsTweetDeckWebsite(frame) || browser.IsLoading){
|
||||
lastHighlightedTweet = string.Empty;
|
||||
lastHighlightedQuotedTweet = string.Empty;
|
||||
lastHighlightedTweetUrl = string.Empty;
|
||||
lastHighlightedQuoteUrl = string.Empty;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(lastHighlightedTweet) && (parameters.TypeFlags & (ContextMenuType.Editable | ContextMenuType.Selection)) == 0){
|
||||
model.AddItem((CefMenuCommand)MenuOpenTweetUrl, "Open tweet in browser");
|
||||
model.AddItem((CefMenuCommand)MenuCopyTweetUrl, "Copy tweet address");
|
||||
model.AddItem((CefMenuCommand)MenuScreenshotTweet, "Screenshot tweet to clipboard");
|
||||
if (!string.IsNullOrEmpty(lastHighlightedTweetUrl) && (parameters.TypeFlags & (ContextMenuType.Editable | ContextMenuType.Selection)) == 0){
|
||||
model.AddItem(MenuOpenTweetUrl, "Open tweet in browser");
|
||||
model.AddItem(MenuCopyTweetUrl, "Copy tweet address");
|
||||
model.AddItem(MenuScreenshotTweet, "Screenshot tweet to clipboard");
|
||||
|
||||
if (!string.IsNullOrEmpty(lastHighlightedQuotedTweet)){
|
||||
if (!string.IsNullOrEmpty(lastHighlightedQuoteUrl)){
|
||||
model.AddSeparator();
|
||||
model.AddItem((CefMenuCommand)MenuOpenQuotedTweetUrl, "Open quoted tweet in browser");
|
||||
model.AddItem((CefMenuCommand)MenuCopyQuotedTweetUrl, "Copy quoted tweet address");
|
||||
model.AddItem(MenuOpenQuotedTweetUrl, "Open quoted tweet in browser");
|
||||
model.AddItem(MenuCopyQuotedTweetUrl, "Copy quoted tweet address");
|
||||
}
|
||||
|
||||
model.AddSeparator();
|
||||
@@ -71,16 +71,16 @@ namespace TweetDuck.Core.Handling{
|
||||
if ((parameters.TypeFlags & (ContextMenuType.Editable | ContextMenuType.Selection)) == 0){
|
||||
AddSeparator(model);
|
||||
|
||||
IMenuModel globalMenu = model.Count == 0 ? model : model.AddSubMenu((CefMenuCommand)MenuGlobal, Program.BrandName);
|
||||
IMenuModel globalMenu = model.Count == 0 ? model : model.AddSubMenu(MenuGlobal, Program.BrandName);
|
||||
|
||||
globalMenu.AddItem(CefMenuCommand.Reload, TitleReloadBrowser);
|
||||
globalMenu.AddCheckItem((CefMenuCommand)MenuMute, TitleMuteNotifications);
|
||||
globalMenu.SetChecked((CefMenuCommand)MenuMute, Program.UserConfig.MuteNotifications);
|
||||
globalMenu.AddCheckItem(MenuMute, TitleMuteNotifications);
|
||||
globalMenu.SetChecked(MenuMute, Program.UserConfig.MuteNotifications);
|
||||
globalMenu.AddSeparator();
|
||||
|
||||
globalMenu.AddItem((CefMenuCommand)MenuSettings, TitleSettings);
|
||||
globalMenu.AddItem((CefMenuCommand)MenuPlugins, TitlePlugins);
|
||||
globalMenu.AddItem((CefMenuCommand)MenuAbout, TitleAboutProgram);
|
||||
globalMenu.AddItem(MenuSettings, TitleSettings);
|
||||
globalMenu.AddItem(MenuPlugins, TitlePlugins);
|
||||
globalMenu.AddItem(MenuAbout, TitleAboutProgram);
|
||||
|
||||
if (HasDevTools){
|
||||
globalMenu.AddSeparator();
|
||||
@@ -96,8 +96,8 @@ namespace TweetDuck.Core.Handling{
|
||||
return true;
|
||||
}
|
||||
|
||||
switch((int)commandId){
|
||||
case (int)CefMenuCommand.Reload:
|
||||
switch(commandId){
|
||||
case CefMenuCommand.Reload:
|
||||
form.InvokeAsyncSafe(form.ReloadToTweetDeck);
|
||||
return true;
|
||||
|
||||
@@ -118,11 +118,11 @@ namespace TweetDuck.Core.Handling{
|
||||
return true;
|
||||
|
||||
case MenuOpenTweetUrl:
|
||||
BrowserUtils.OpenExternalBrowser(lastHighlightedTweet);
|
||||
OpenBrowser(form, lastHighlightedTweetUrl);
|
||||
return true;
|
||||
|
||||
case MenuCopyTweetUrl:
|
||||
SetClipboardText(lastHighlightedTweet);
|
||||
SetClipboardText(form, lastHighlightedTweetUrl);
|
||||
return true;
|
||||
|
||||
case MenuScreenshotTweet:
|
||||
@@ -130,11 +130,11 @@ namespace TweetDuck.Core.Handling{
|
||||
return true;
|
||||
|
||||
case MenuOpenQuotedTweetUrl:
|
||||
BrowserUtils.OpenExternalBrowser(lastHighlightedQuotedTweet);
|
||||
OpenBrowser(form, lastHighlightedQuoteUrl);
|
||||
return true;
|
||||
|
||||
case MenuCopyQuotedTweetUrl:
|
||||
SetClipboardText(lastHighlightedQuotedTweet);
|
||||
SetClipboardText(form, lastHighlightedQuoteUrl);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@@ -3,16 +3,17 @@ using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Notification;
|
||||
|
||||
namespace TweetDuck.Core.Handling{
|
||||
class ContextMenuNotification : ContextMenuBase{
|
||||
private const int MenuSkipTweet = 26600;
|
||||
private const int MenuFreeze = 26601;
|
||||
private const int MenuCopyTweetUrl = 26602;
|
||||
private const int MenuCopyQuotedTweetUrl = 26603;
|
||||
sealed class ContextMenuNotification : ContextMenuBase{
|
||||
private const CefMenuCommand MenuViewDetail = (CefMenuCommand)26600;
|
||||
private const CefMenuCommand MenuSkipTweet = (CefMenuCommand)26601;
|
||||
private const CefMenuCommand MenuFreeze = (CefMenuCommand)26602;
|
||||
private const CefMenuCommand MenuCopyTweetUrl = (CefMenuCommand)26603;
|
||||
private const CefMenuCommand MenuCopyQuotedTweetUrl = (CefMenuCommand)26604;
|
||||
|
||||
private readonly FormNotificationBase form;
|
||||
private readonly bool enableCustomMenu;
|
||||
|
||||
public ContextMenuNotification(FormNotificationBase form, bool enableCustomMenu) : base(form){
|
||||
public ContextMenuNotification(FormNotificationBase form, bool enableCustomMenu){
|
||||
this.form = form;
|
||||
this.enableCustomMenu = enableCustomMenu;
|
||||
}
|
||||
@@ -28,23 +29,26 @@ namespace TweetDuck.Core.Handling{
|
||||
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
|
||||
|
||||
if (enableCustomMenu){
|
||||
model.AddItem((CefMenuCommand)MenuSkipTweet, "Skip tweet");
|
||||
model.AddCheckItem((CefMenuCommand)MenuFreeze, "Freeze");
|
||||
model.SetChecked((CefMenuCommand)MenuFreeze, form.FreezeTimer);
|
||||
model.AddSeparator();
|
||||
if (form.CanViewDetail){
|
||||
model.AddItem(MenuViewDetail, "View detail");
|
||||
}
|
||||
|
||||
model.AddItem(MenuSkipTweet, "Skip tweet");
|
||||
model.AddCheckItem(MenuFreeze, "Freeze");
|
||||
model.SetChecked(MenuFreeze, form.FreezeTimer);
|
||||
|
||||
if (!string.IsNullOrEmpty(form.CurrentTweetUrl)){
|
||||
model.AddItem((CefMenuCommand)MenuCopyTweetUrl, "Copy tweet address");
|
||||
model.AddSeparator();
|
||||
model.AddItem(MenuCopyTweetUrl, "Copy tweet address");
|
||||
|
||||
if (!string.IsNullOrEmpty(form.CurrentQuoteUrl)){
|
||||
model.AddItem((CefMenuCommand)MenuCopyQuotedTweetUrl, "Copy quoted tweet address");
|
||||
model.AddItem(MenuCopyQuotedTweetUrl, "Copy quoted tweet address");
|
||||
}
|
||||
|
||||
model.AddSeparator();
|
||||
}
|
||||
}
|
||||
|
||||
if (HasDevTools){
|
||||
model.AddSeparator();
|
||||
AddDebugMenuItems(model);
|
||||
}
|
||||
|
||||
@@ -58,7 +62,7 @@ namespace TweetDuck.Core.Handling{
|
||||
return true;
|
||||
}
|
||||
|
||||
switch((int)commandId){
|
||||
switch(commandId){
|
||||
case MenuSkipTweet:
|
||||
form.InvokeAsyncSafe(form.FinishCurrentNotification);
|
||||
return true;
|
||||
@@ -67,12 +71,16 @@ namespace TweetDuck.Core.Handling{
|
||||
form.InvokeAsyncSafe(() => form.FreezeTimer = !form.FreezeTimer);
|
||||
return true;
|
||||
|
||||
case MenuViewDetail:
|
||||
form.InvokeSafe(form.ShowTweetDetail);
|
||||
return true;
|
||||
|
||||
case MenuCopyTweetUrl:
|
||||
SetClipboardText(form.CurrentTweetUrl);
|
||||
SetClipboardText(form, form.CurrentTweetUrl);
|
||||
return true;
|
||||
|
||||
case MenuCopyQuotedTweetUrl:
|
||||
SetClipboardText(form.CurrentQuoteUrl);
|
||||
SetClipboardText(form, form.CurrentQuoteUrl);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@@ -2,7 +2,7 @@
|
||||
using CefSharp;
|
||||
|
||||
namespace TweetDuck.Core.Handling.General{
|
||||
class BrowserProcessHandler : IBrowserProcessHandler{
|
||||
sealed class BrowserProcessHandler : IBrowserProcessHandler{
|
||||
void IBrowserProcessHandler.OnContextInitialized(){
|
||||
using(IRequestContext ctx = Cef.GetGlobalRequestContext()){
|
||||
ctx.SetPreference("browser.enable_spellchecking", Program.UserConfig.EnableSpellCheck, out string _);
|
||||
|
40
Core/Handling/General/FileDialogHandler.cs
Normal file
40
Core/Handling/General/FileDialogHandler.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
using CefSharp;
|
||||
|
||||
namespace TweetDuck.Core.Handling.General{
|
||||
sealed class FileDialogHandler : IDialogHandler{
|
||||
public bool OnFileDialog(IWebBrowser browserControl, IBrowser browser, CefFileDialogMode mode, string title, string defaultFilePath, List<string> acceptFilters, int selectedAcceptFilter, IFileDialogCallback callback){
|
||||
CefFileDialogMode dialogType = mode & CefFileDialogMode.TypeMask;
|
||||
|
||||
if (dialogType == CefFileDialogMode.Open || dialogType == CefFileDialogMode.OpenMultiple){
|
||||
string allFilters = string.Join(";", acceptFilters.Select(filter => "*"+filter));
|
||||
|
||||
using(OpenFileDialog dialog = new OpenFileDialog{
|
||||
AutoUpgradeEnabled = true,
|
||||
DereferenceLinks = true,
|
||||
Multiselect = dialogType == CefFileDialogMode.OpenMultiple,
|
||||
Title = "Open Files",
|
||||
Filter = $"All Supported Formats ({allFilters})|{allFilters}|All Files (*.*)|*.*"
|
||||
}){
|
||||
if (dialog.ShowDialog() == DialogResult.OK){
|
||||
callback.Continue(acceptFilters.FindIndex(filter => filter == Path.GetExtension(dialog.FileName)), dialog.FileNames.ToList());
|
||||
}
|
||||
else{
|
||||
callback.Cancel();
|
||||
}
|
||||
|
||||
callback.Dispose();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else{
|
||||
callback.Dispose();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -7,32 +7,52 @@ using TweetDuck.Core.Other;
|
||||
using TweetDuck.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Core.Handling.General{
|
||||
class JavaScriptDialogHandler : IJsDialogHandler{
|
||||
sealed class JavaScriptDialogHandler : IJsDialogHandler{
|
||||
private static FormMessage CreateMessageForm(string caption, string text){
|
||||
MessageBoxIcon icon = MessageBoxIcon.None;
|
||||
int pipe = text.IndexOf('|');
|
||||
|
||||
if (pipe != -1){
|
||||
switch(text.Substring(0, pipe)){
|
||||
case "error": icon = MessageBoxIcon.Error; break;
|
||||
case "warning": icon = MessageBoxIcon.Warning; break;
|
||||
case "info": icon = MessageBoxIcon.Information; break;
|
||||
case "question": icon = MessageBoxIcon.Question; break;
|
||||
default: return new FormMessage(caption, text, icon);
|
||||
}
|
||||
|
||||
text = text.Substring(pipe+1);
|
||||
}
|
||||
|
||||
return new FormMessage(caption, text, icon);
|
||||
}
|
||||
|
||||
bool IJsDialogHandler.OnJSDialog(IWebBrowser browserControl, IBrowser browser, string originUrl, CefJsDialogType dialogType, string messageText, string defaultPromptText, IJsDialogCallback callback, ref bool suppressMessage){
|
||||
((ChromiumWebBrowser)browserControl).InvokeSafe(() => {
|
||||
FormMessage form;
|
||||
TextBox input = null;
|
||||
|
||||
if (dialogType == CefJsDialogType.Alert){
|
||||
form = new FormMessage("Browser Message", messageText, MessageBoxIcon.None);
|
||||
form = CreateMessageForm("Browser Message", messageText);
|
||||
form.AddButton(FormMessage.OK, ControlType.Accept | ControlType.Focused);
|
||||
}
|
||||
else if (dialogType == CefJsDialogType.Confirm){
|
||||
form = new FormMessage("Browser Confirmation", messageText, MessageBoxIcon.None);
|
||||
form = CreateMessageForm("Browser Confirmation", messageText);
|
||||
form.AddButton(FormMessage.No, DialogResult.No, ControlType.Cancel);
|
||||
form.AddButton(FormMessage.Yes, ControlType.Focused);
|
||||
}
|
||||
else if (dialogType == CefJsDialogType.Prompt){
|
||||
form = new FormMessage("Browser Prompt", messageText, MessageBoxIcon.None);
|
||||
form = CreateMessageForm("Browser Prompt", messageText);
|
||||
form.AddButton(FormMessage.Cancel, DialogResult.Cancel, ControlType.Cancel);
|
||||
form.AddButton(FormMessage.OK, ControlType.Accept | ControlType.Focused);
|
||||
|
||||
float dpiScale = form.GetDPIScale();
|
||||
int inputPad = form.HasIcon ? 43 : 0;
|
||||
|
||||
input = new TextBox{
|
||||
Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Bottom,
|
||||
Location = new Point(BrowserUtils.Scale(22, dpiScale), form.ActionPanelY-BrowserUtils.Scale(46, dpiScale)),
|
||||
Size = new Size(form.ClientSize.Width-BrowserUtils.Scale(44, dpiScale), 20)
|
||||
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), 20)
|
||||
};
|
||||
|
||||
form.Controls.Add(input);
|
||||
|
@@ -1,8 +1,9 @@
|
||||
using CefSharp;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Core.Handling.General{
|
||||
class LifeSpanHandler : ILifeSpanHandler{
|
||||
sealed class LifeSpanHandler : ILifeSpanHandler{
|
||||
public bool OnBeforePopup(IWebBrowser browserControl, IBrowser browser, IFrame frame, string targetUrl, string targetFrameName, WindowOpenDisposition targetDisposition, bool userGesture, IPopupFeatures popupFeatures, IWindowInfo windowInfo, IBrowserSettings browserSettings, ref bool noJavascriptAccess, out IWebBrowser newBrowser){
|
||||
newBrowser = null;
|
||||
|
||||
@@ -11,7 +12,7 @@ namespace TweetDuck.Core.Handling.General{
|
||||
case WindowOpenDisposition.NewForegroundTab:
|
||||
case WindowOpenDisposition.NewPopup:
|
||||
case WindowOpenDisposition.NewWindow:
|
||||
BrowserUtils.OpenExternalBrowser(targetUrl);
|
||||
browserControl.AsControl().InvokeAsyncSafe(() => BrowserUtils.OpenExternalBrowser(targetUrl));
|
||||
return true;
|
||||
|
||||
default:
|
||||
|
20
Core/Handling/KeyboardHandlerBrowser.cs
Normal file
20
Core/Handling/KeyboardHandlerBrowser.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System.Windows.Forms;
|
||||
using CefSharp;
|
||||
|
||||
namespace TweetDuck.Core.Handling{
|
||||
sealed class KeyboardHandlerBrowser : IKeyboardHandler{
|
||||
private readonly FormBrowser form;
|
||||
|
||||
public KeyboardHandlerBrowser(FormBrowser form){
|
||||
this.form = form;
|
||||
}
|
||||
|
||||
bool IKeyboardHandler.OnPreKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey, ref bool isKeyboardShortcut){
|
||||
return type == KeyType.RawKeyDown && form.ProcessBrowserKey((Keys)windowsKeyCode);
|
||||
}
|
||||
|
||||
bool IKeyboardHandler.OnKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@@ -2,7 +2,7 @@
|
||||
using TweetDuck.Core.Handling.General;
|
||||
|
||||
namespace TweetDuck.Core.Handling{
|
||||
class RequestHandlerBrowser : RequestHandlerBase{
|
||||
sealed class RequestHandlerBrowser : RequestHandlerBase{
|
||||
public override void OnRenderProcessTerminated(IWebBrowser browserControl, IBrowser browser, CefTerminationStatus status){
|
||||
browser.Reload();
|
||||
}
|
||||
|
@@ -4,7 +4,7 @@ using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace TweetDuck.Core.Handling{
|
||||
class ResourceHandlerNotification : IResourceHandler{
|
||||
sealed class ResourceHandlerNotification : IResourceHandler{
|
||||
private readonly NameValueCollection headers = new NameValueCollection(0);
|
||||
private MemoryStream dataIn;
|
||||
|
||||
|
@@ -73,25 +73,27 @@ namespace TweetDuck.Core.Notification{
|
||||
|
||||
protected double SizeScale => dpiScale*Program.UserConfig.ZoomMultiplier;
|
||||
|
||||
protected readonly Form owner;
|
||||
protected readonly FormBrowser owner;
|
||||
protected readonly ChromiumWebBrowser browser;
|
||||
|
||||
private readonly ResourceHandlerNotification resourceHandler = new ResourceHandlerNotification();
|
||||
private readonly float dpiScale;
|
||||
|
||||
private string currentColumn;
|
||||
private TweetNotification currentNotification;
|
||||
private int pauseCounter;
|
||||
|
||||
public string CurrentTweetUrl => currentNotification?.TweetUrl;
|
||||
public string CurrentQuoteUrl => currentNotification?.QuoteUrl;
|
||||
|
||||
public bool CanViewDetail => currentNotification != null && !string.IsNullOrEmpty(currentNotification.ColumnId) && !string.IsNullOrEmpty(currentNotification.ChirpId);
|
||||
public bool IsPaused => pauseCounter > 0;
|
||||
|
||||
|
||||
public bool FreezeTimer { get; set; }
|
||||
public bool ContextMenuOpen { get; set; }
|
||||
public string CurrentTweetUrl { get; private set; }
|
||||
public string CurrentQuoteUrl { get; private set; }
|
||||
|
||||
public event EventHandler Initialized;
|
||||
|
||||
public FormNotificationBase(Form owner, bool enableContextMenu){
|
||||
protected FormNotificationBase(FormBrowser owner, bool enableContextMenu){
|
||||
InitializeComponent();
|
||||
|
||||
this.owner = owner;
|
||||
@@ -143,7 +145,7 @@ namespace TweetDuck.Core.Notification{
|
||||
|
||||
private void Browser_IsBrowserInitializedChanged(object sender, IsBrowserInitializedChangedEventArgs e){
|
||||
if (e.IsBrowserInitialized){
|
||||
Initialized?.Invoke(this, new EventArgs());
|
||||
Initialized?.Invoke(this, EventArgs.Empty);
|
||||
|
||||
int identifier = browser.GetBrowser().Identifier;
|
||||
Disposed += (sender2, args2) => BrowserProcesses.Forget(identifier);
|
||||
@@ -158,7 +160,7 @@ namespace TweetDuck.Core.Notification{
|
||||
}
|
||||
|
||||
Location = ControlExtensions.InvisibleLocation;
|
||||
currentColumn = null;
|
||||
currentNotification = null;
|
||||
}
|
||||
|
||||
public virtual void FinishCurrentNotification(){}
|
||||
@@ -181,10 +183,7 @@ namespace TweetDuck.Core.Notification{
|
||||
}
|
||||
|
||||
protected virtual void LoadTweet(TweetNotification tweet){
|
||||
CurrentTweetUrl = tweet.TweetUrl;
|
||||
CurrentQuoteUrl = tweet.QuoteUrl;
|
||||
currentColumn = tweet.Column;
|
||||
|
||||
currentNotification = tweet;
|
||||
resourceHandler.SetHTML(GetTweetHTML(tweet));
|
||||
browser.Load(TwitterUtils.TweetDeckURL);
|
||||
}
|
||||
@@ -193,12 +192,13 @@ namespace TweetDuck.Core.Notification{
|
||||
browser.ClientSize = ClientSize = new Size(BrowserUtils.Scale(width, SizeScale), BrowserUtils.Scale(height, SizeScale));
|
||||
}
|
||||
|
||||
protected virtual void OnNotificationReady(){
|
||||
MoveToVisibleLocation();
|
||||
protected virtual void UpdateTitle(){
|
||||
string title = currentNotification?.ColumnTitle;
|
||||
Text = string.IsNullOrEmpty(title) || !Program.UserConfig.DisplayNotificationColumn ? Program.BrandName : Program.BrandName+" - "+title;
|
||||
}
|
||||
|
||||
protected virtual void UpdateTitle(){
|
||||
Text = string.IsNullOrEmpty(currentColumn) || !Program.UserConfig.DisplayNotificationColumn ? Program.BrandName : Program.BrandName+" - "+currentColumn;
|
||||
public void ShowTweetDetail(){
|
||||
owner.ShowTweetDetail(currentNotification.ColumnId, currentNotification.ChirpId, currentNotification.TweetUrl);
|
||||
}
|
||||
|
||||
public void MoveToVisibleLocation(){
|
||||
|
@@ -279,7 +279,7 @@ namespace TweetDuck.Core.Notification{
|
||||
StartMouseHook();
|
||||
}
|
||||
|
||||
protected override void OnNotificationReady(){
|
||||
protected virtual void OnNotificationReady(){
|
||||
PrepareAndDisplayWindow();
|
||||
timerProgress.Start();
|
||||
}
|
||||
|
@@ -14,7 +14,7 @@ namespace TweetDuck.Core.Notification.Screenshot{
|
||||
sealed class FormNotificationScreenshotable : FormNotificationBase{
|
||||
private readonly PluginManager plugins;
|
||||
|
||||
public FormNotificationScreenshotable(Action callback, Form owner, PluginManager pluginManager) : base(owner, false){
|
||||
public FormNotificationScreenshotable(Action callback, FormBrowser owner, PluginManager pluginManager) : base(owner, false){
|
||||
this.plugins = pluginManager;
|
||||
|
||||
browser.RegisterAsyncJsObject("$TD_NotificationScreenshot", new CallbackBridge(this, callback));
|
||||
|
@@ -46,7 +46,7 @@ namespace TweetDuck.Core.Notification.Screenshot{
|
||||
CanMoveWindow = () => false
|
||||
};
|
||||
|
||||
screenshot.LoadNotificationForScreenshot(new TweetNotification(string.Empty, html, 0, string.Empty, string.Empty), width, height);
|
||||
screenshot.LoadNotificationForScreenshot(new TweetNotification(string.Empty, string.Empty, string.Empty, html, 0, string.Empty, string.Empty), width, height);
|
||||
screenshot.Show();
|
||||
timeout.Start();
|
||||
|
||||
|
@@ -17,6 +17,10 @@ namespace TweetDuck.Core.Notification{
|
||||
player.Play(file);
|
||||
}
|
||||
|
||||
public bool SetVolume(int volume){
|
||||
return player.SetVolume(volume);
|
||||
}
|
||||
|
||||
private void Player_PlaybackError(object sender, PlaybackErrorEventArgs e){
|
||||
PlaybackError?.Invoke(this, e);
|
||||
}
|
||||
|
@@ -35,7 +35,7 @@ namespace TweetDuck.Core.Notification{
|
||||
#endif
|
||||
}
|
||||
|
||||
return new TweetNotification("Home", ExampleTweetHTML, 95, string.Empty, string.Empty, true);
|
||||
return new TweetNotification(string.Empty, string.Empty, "Home", ExampleTweetHTML, 95, string.Empty, string.Empty, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +55,10 @@ namespace TweetDuck.Core.Notification{
|
||||
Auto, Custom
|
||||
}
|
||||
|
||||
public string Column { get; }
|
||||
public string ColumnId { get; }
|
||||
public string ChirpId { get; }
|
||||
|
||||
public string ColumnTitle { get; }
|
||||
public string TweetUrl { get; }
|
||||
public string QuoteUrl { get; }
|
||||
|
||||
@@ -63,10 +66,13 @@ namespace TweetDuck.Core.Notification{
|
||||
private readonly int characters;
|
||||
private readonly bool isExample;
|
||||
|
||||
public TweetNotification(string column, string html, int characters, string tweetUrl, string quoteUrl) : this(column, html, characters, tweetUrl, quoteUrl, false){}
|
||||
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 column, string html, int characters, string tweetUrl, string quoteUrl, bool isExample){
|
||||
this.Column = column;
|
||||
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;
|
||||
|
||||
|
@@ -60,6 +60,7 @@ namespace TweetDuck.Core.Other{
|
||||
|
||||
public Button ClickedButton { get; private set; }
|
||||
|
||||
public bool HasIcon => icon != null;
|
||||
public int ActionPanelY => panelActions.Location.Y;
|
||||
|
||||
private int ClientWidth{
|
||||
|
@@ -19,6 +19,8 @@ namespace TweetDuck.Core.Other{
|
||||
private readonly Dictionary<Type, SettingsTab> tabs = new Dictionary<Type, SettingsTab>(4);
|
||||
private SettingsTab currentTab;
|
||||
|
||||
public bool ShouldReloadBrowser { get; private set; }
|
||||
|
||||
public FormSettings(FormBrowser browser, PluginManager plugins, UpdateHandler updates, Type startTab){
|
||||
InitializeComponent();
|
||||
|
||||
@@ -52,13 +54,19 @@ namespace TweetDuck.Core.Other{
|
||||
}
|
||||
|
||||
private void btnManageOptions_Click(object sender, EventArgs e){
|
||||
using(DialogSettingsManage dialog = new DialogSettingsManage(plugins)){
|
||||
if (dialog.ShowDialog() == DialogResult.OK && dialog.ShouldReloadUI){
|
||||
foreach(SettingsTab tab in tabs.Values){
|
||||
tab.Control = null;
|
||||
}
|
||||
foreach(SettingsTab tab in tabs.Values){
|
||||
if (tab.IsInitialized){
|
||||
tab.Control.OnClosing();
|
||||
}
|
||||
}
|
||||
|
||||
SelectTab(currentTab);
|
||||
using(DialogSettingsManage dialog = new DialogSettingsManage(plugins)){
|
||||
if (dialog.ShowDialog() == DialogResult.OK){
|
||||
FormClosing -= FormSettings_FormClosing;
|
||||
browser.ResumeNotification();
|
||||
|
||||
ShouldReloadBrowser = dialog.ShouldReloadBrowser;
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -120,11 +128,16 @@ namespace TweetDuck.Core.Other{
|
||||
tab.Control.OnReady();
|
||||
}
|
||||
|
||||
panelContents.VerticalScroll.Enabled = false; // required to stop animation that would otherwise break everything
|
||||
panelContents.PerformLayout();
|
||||
|
||||
panelContents.SuspendLayout();
|
||||
panelContents.VerticalScroll.Value = 0; // https://gfycat.com/GrotesqueTastyAstarte
|
||||
panelContents.Controls.Clear();
|
||||
panelContents.Controls.Add(tab.Control);
|
||||
panelContents.ResumeLayout(true);
|
||||
|
||||
panelContents.VerticalScroll.Enabled = true;
|
||||
panelContents.Focus();
|
||||
|
||||
currentTab = tab;
|
||||
@@ -139,7 +152,7 @@ namespace TweetDuck.Core.Other{
|
||||
panelContents.Focus();
|
||||
}
|
||||
|
||||
private class SettingsTab{
|
||||
private sealed class SettingsTab{
|
||||
public Button Button { get; }
|
||||
|
||||
public BaseTabSettings Control{
|
||||
|
@@ -4,6 +4,7 @@ using System.IO;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetLib.Communication;
|
||||
|
||||
namespace TweetDuck.Core.Other.Management{
|
||||
sealed class VideoPlayer : IDisposable{
|
||||
@@ -23,23 +24,32 @@ namespace TweetDuck.Core.Other.Management{
|
||||
public event EventHandler ProcessExited;
|
||||
|
||||
private readonly Form owner;
|
||||
private Process currentProcess;
|
||||
private string lastUrl;
|
||||
|
||||
private Process currentProcess;
|
||||
private DuplexPipe.Server currentPipe;
|
||||
private bool isClosing;
|
||||
|
||||
public VideoPlayer(Form owner){
|
||||
this.owner = owner;
|
||||
this.owner.FormClosing += owner_FormClosing;
|
||||
}
|
||||
|
||||
public void Launch(string url){
|
||||
Close();
|
||||
if (Running){
|
||||
Destroy();
|
||||
isClosing = false;
|
||||
}
|
||||
|
||||
lastUrl = url;
|
||||
|
||||
try{
|
||||
currentPipe = DuplexPipe.CreateServer();
|
||||
currentPipe.DataIn += currentPipe_DataIn;
|
||||
|
||||
if ((currentProcess = Process.Start(new ProcessStartInfo{
|
||||
FileName = PlayerExe,
|
||||
Arguments = $"{owner.Handle} {Program.UserConfig.VideoPlayerVolume} \"{url}\"",
|
||||
Arguments = $"{owner.Handle} {Program.UserConfig.VideoPlayerVolume} \"{url}\" \"{currentPipe.GenerateToken()}\"",
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = true
|
||||
})) != null){
|
||||
@@ -51,15 +61,69 @@ namespace TweetDuck.Core.Other.Management{
|
||||
currentProcess.OutputDataReceived += (sender, args) => Debug.WriteLine("VideoPlayer: "+args.Data);
|
||||
#endif
|
||||
}
|
||||
|
||||
currentPipe.DisposeToken();
|
||||
}catch(Exception e){
|
||||
Program.Reporter.HandleException("Video Playback Error", "Error launching video player.", true, e);
|
||||
}
|
||||
}
|
||||
|
||||
public void SendKeyEvent(Keys key){
|
||||
currentPipe?.Write("key", ((int)key).ToString());
|
||||
}
|
||||
|
||||
private void currentPipe_DataIn(object sender, DuplexPipe.PipeReadEventArgs e){
|
||||
owner.InvokeSafe(() => {
|
||||
switch(e.Key){
|
||||
case "vol":
|
||||
if (int.TryParse(e.Data, out int volume) && volume != Program.UserConfig.VideoPlayerVolume){
|
||||
Program.UserConfig.VideoPlayerVolume = volume;
|
||||
Program.UserConfig.Save();
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case "download":
|
||||
TwitterUtils.DownloadVideo(lastUrl);
|
||||
break;
|
||||
|
||||
case "rip":
|
||||
currentPipe.Dispose();
|
||||
currentPipe = null;
|
||||
|
||||
currentProcess.Dispose();
|
||||
currentProcess = null;
|
||||
|
||||
isClosing = false;
|
||||
TriggerProcessExitEventUnsafe();
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void Close(){
|
||||
if (currentProcess != null){
|
||||
currentProcess.Exited -= process_Exited;
|
||||
if (isClosing){
|
||||
Destroy();
|
||||
isClosing = false;
|
||||
}
|
||||
else{
|
||||
isClosing = true;
|
||||
currentProcess.Exited -= process_Exited;
|
||||
currentPipe.Write("die");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose(){
|
||||
ProcessExited = null;
|
||||
|
||||
isClosing = true;
|
||||
Destroy();
|
||||
}
|
||||
|
||||
private void Destroy(){
|
||||
if (currentProcess != null){
|
||||
try{
|
||||
currentProcess.Kill();
|
||||
}catch{
|
||||
@@ -68,16 +132,14 @@ namespace TweetDuck.Core.Other.Management{
|
||||
|
||||
currentProcess.Dispose();
|
||||
currentProcess = null;
|
||||
|
||||
currentPipe.Dispose();
|
||||
currentPipe = null;
|
||||
|
||||
owner.InvokeAsyncSafe(TriggerProcessExitEventUnsafe);
|
||||
TriggerProcessExitEventUnsafe();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose(){
|
||||
ProcessExited = null;
|
||||
Close();
|
||||
}
|
||||
|
||||
private void owner_FormClosing(object sender, FormClosingEventArgs e){
|
||||
if (currentProcess != null){
|
||||
currentProcess.Exited -= process_Exited;
|
||||
@@ -103,12 +165,15 @@ namespace TweetDuck.Core.Other.Management{
|
||||
|
||||
currentProcess.Dispose();
|
||||
currentProcess = null;
|
||||
|
||||
currentPipe.Dispose();
|
||||
currentPipe = null;
|
||||
|
||||
owner.InvokeAsyncSafe(TriggerProcessExitEventUnsafe);
|
||||
}
|
||||
|
||||
private void TriggerProcessExitEventUnsafe(){
|
||||
ProcessExited?.Invoke(this, new EventArgs());
|
||||
ProcessExited?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -17,7 +17,7 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
}
|
||||
}
|
||||
|
||||
public BaseTabSettings(){
|
||||
protected BaseTabSettings(){
|
||||
Padding = new Padding(6);
|
||||
}
|
||||
|
||||
|
@@ -1,12 +1,14 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Configuration;
|
||||
using TweetDuck.Core.Other.Settings.Export;
|
||||
using TweetDuck.Plugins;
|
||||
|
||||
namespace TweetDuck.Core.Other.Settings.Dialogs{
|
||||
sealed partial class DialogSettingsManage : Form{
|
||||
private enum State{
|
||||
Deciding, Import, Export
|
||||
Deciding, Reset, Import, Export
|
||||
}
|
||||
|
||||
public ExportFileFlags Flags{
|
||||
@@ -20,7 +22,7 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
|
||||
}
|
||||
}
|
||||
|
||||
public bool ShouldReloadUI { get; private set; }
|
||||
public bool ShouldReloadBrowser { get; private set; }
|
||||
|
||||
private readonly PluginManager plugins;
|
||||
private State currentState;
|
||||
@@ -58,15 +60,10 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
|
||||
case State.Deciding:
|
||||
// Reset
|
||||
if (radioReset.Checked){
|
||||
if (FormMessage.Warning("Reset TweetDuck Options", "This will reset all of your program options. Plugins will not be affected. Do you want to proceed?", FormMessage.Yes, FormMessage.No)){
|
||||
Program.ResetConfig();
|
||||
currentState = State.Reset;
|
||||
|
||||
ShouldReloadUI = true;
|
||||
DialogResult = DialogResult.OK;
|
||||
Close();
|
||||
}
|
||||
|
||||
return;
|
||||
Text = "Restore Defaults";
|
||||
Flags = ExportFileFlags.Config;
|
||||
}
|
||||
|
||||
// Import
|
||||
@@ -109,10 +106,43 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
|
||||
panelExport.Visible = true;
|
||||
break;
|
||||
|
||||
case State.Reset:
|
||||
if (FormMessage.Warning("Reset TweetDuck Options", "This will reset the selected items. Are you sure you want to proceed?", FormMessage.Yes, FormMessage.No)){
|
||||
if (Flags.HasFlag(ExportFileFlags.Config)){
|
||||
Program.ResetConfig();
|
||||
}
|
||||
|
||||
if (Flags.HasFlag(ExportFileFlags.PluginData)){
|
||||
try{
|
||||
File.Delete(Program.PluginConfigFilePath);
|
||||
Directory.Delete(Program.PluginDataPath, true);
|
||||
}catch(Exception ex){
|
||||
Program.Reporter.HandleException("Plugin Data Reset Error", "Could not delete plugin data.", true, ex);
|
||||
}
|
||||
}
|
||||
|
||||
if (Flags.HasFlag(ExportFileFlags.Session)){
|
||||
Program.Restart(Arguments.ArgDeleteCookies);
|
||||
}
|
||||
else{
|
||||
ShouldReloadBrowser = true;
|
||||
}
|
||||
|
||||
DialogResult = DialogResult.OK;
|
||||
Close();
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case State.Import:
|
||||
if (importManager.Import(Flags)){
|
||||
if (!importManager.IsRestarting){
|
||||
ShouldReloadUI = true;
|
||||
Program.UserConfig.Reload();
|
||||
|
||||
if (importManager.IsRestarting){
|
||||
Program.Restart(Arguments.ArgImportCookies);
|
||||
}
|
||||
else{
|
||||
ShouldReloadBrowser = true;
|
||||
}
|
||||
}
|
||||
else{
|
||||
@@ -165,6 +195,9 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
|
||||
if (currentState == State.Import){
|
||||
btnContinue.Text = selectedFlags.HasFlag(ExportFileFlags.Session) ? "Import && Restart" : "Import Profile";
|
||||
}
|
||||
else if (currentState == State.Reset){
|
||||
btnContinue.Text = selectedFlags.HasFlag(ExportFileFlags.Session) ? "Restore && Restart" : "Restore Defaults";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -29,20 +29,22 @@
|
||||
this.cbLogging = new System.Windows.Forms.CheckBox();
|
||||
this.toolTip = new System.Windows.Forms.ToolTip(this.components);
|
||||
this.cbDebugUpdates = new System.Windows.Forms.CheckBox();
|
||||
this.labelLocale = new System.Windows.Forms.Label();
|
||||
this.comboLocale = new System.Windows.Forms.ComboBox();
|
||||
this.labelDataFolder = new System.Windows.Forms.Label();
|
||||
this.tbDataFolder = new System.Windows.Forms.TextBox();
|
||||
this.tbShortcutTarget = new System.Windows.Forms.TextBox();
|
||||
this.labelLocale = new System.Windows.Forms.Label();
|
||||
this.labelDataFolder = new System.Windows.Forms.Label();
|
||||
this.labelShortcutTarget = new System.Windows.Forms.Label();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// btnCancel
|
||||
//
|
||||
this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.btnCancel.Location = new System.Drawing.Point(160, 171);
|
||||
this.btnCancel.Location = new System.Drawing.Point(216, 217);
|
||||
this.btnCancel.Name = "btnCancel";
|
||||
this.btnCancel.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
|
||||
this.btnCancel.Size = new System.Drawing.Size(56, 23);
|
||||
this.btnCancel.TabIndex = 7;
|
||||
this.btnCancel.TabIndex = 9;
|
||||
this.btnCancel.Text = "Cancel";
|
||||
this.btnCancel.UseVisualStyleBackColor = true;
|
||||
this.btnCancel.Click += new System.EventHandler(this.btnCancel_Click);
|
||||
@@ -50,11 +52,11 @@
|
||||
// btnRestart
|
||||
//
|
||||
this.btnRestart.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.btnRestart.Location = new System.Drawing.Point(97, 171);
|
||||
this.btnRestart.Location = new System.Drawing.Point(153, 217);
|
||||
this.btnRestart.Name = "btnRestart";
|
||||
this.btnRestart.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
|
||||
this.btnRestart.Size = new System.Drawing.Size(57, 23);
|
||||
this.btnRestart.TabIndex = 6;
|
||||
this.btnRestart.TabIndex = 8;
|
||||
this.btnRestart.Text = "Restart";
|
||||
this.btnRestart.UseVisualStyleBackColor = true;
|
||||
this.btnRestart.Click += new System.EventHandler(this.btnRestart_Click);
|
||||
@@ -67,7 +69,7 @@
|
||||
this.cbLogging.Size = new System.Drawing.Size(64, 17);
|
||||
this.cbLogging.TabIndex = 0;
|
||||
this.cbLogging.Text = "Logging";
|
||||
this.toolTip.SetToolTip(this.cbLogging, "Logging JavaScript output into a\r\ndebug.txt file in the data folder.");
|
||||
this.toolTip.SetToolTip(this.cbLogging, "Logging JavaScript output into TD_Console.txt file in the data folder.");
|
||||
this.cbLogging.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// cbDebugUpdates
|
||||
@@ -81,6 +83,40 @@
|
||||
this.toolTip.SetToolTip(this.cbDebugUpdates, "Allows updating to pre-releases.");
|
||||
this.cbDebugUpdates.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// comboLocale
|
||||
//
|
||||
this.comboLocale.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.comboLocale.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
|
||||
this.comboLocale.FormattingEnabled = true;
|
||||
this.comboLocale.Location = new System.Drawing.Point(15, 83);
|
||||
this.comboLocale.Name = "comboLocale";
|
||||
this.comboLocale.Size = new System.Drawing.Size(257, 21);
|
||||
this.comboLocale.TabIndex = 3;
|
||||
this.toolTip.SetToolTip(this.comboLocale, "Language used for spell checking.");
|
||||
//
|
||||
// tbDataFolder
|
||||
//
|
||||
this.tbDataFolder.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.tbDataFolder.Location = new System.Drawing.Point(15, 135);
|
||||
this.tbDataFolder.Name = "tbDataFolder";
|
||||
this.tbDataFolder.Size = new System.Drawing.Size(257, 20);
|
||||
this.tbDataFolder.TabIndex = 5;
|
||||
this.toolTip.SetToolTip(this.tbDataFolder, "Path to the data folder. Must be either an absolute path,\r\nor a simple folder name that will be created in LocalAppData.");
|
||||
//
|
||||
// tbShortcutTarget
|
||||
//
|
||||
this.tbShortcutTarget.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.tbShortcutTarget.Cursor = System.Windows.Forms.Cursors.Hand;
|
||||
this.tbShortcutTarget.Location = new System.Drawing.Point(15, 186);
|
||||
this.tbShortcutTarget.Name = "tbShortcutTarget";
|
||||
this.tbShortcutTarget.ReadOnly = true;
|
||||
this.tbShortcutTarget.Size = new System.Drawing.Size(257, 20);
|
||||
this.tbShortcutTarget.TabIndex = 7;
|
||||
this.tbShortcutTarget.Click += new System.EventHandler(this.tbShortcutTarget_Click);
|
||||
//
|
||||
// labelLocale
|
||||
//
|
||||
this.labelLocale.AutoSize = true;
|
||||
@@ -91,16 +127,6 @@
|
||||
this.labelLocale.TabIndex = 2;
|
||||
this.labelLocale.Text = "Locale";
|
||||
//
|
||||
// comboLocale
|
||||
//
|
||||
this.comboLocale.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.comboLocale.FormattingEnabled = true;
|
||||
this.comboLocale.Location = new System.Drawing.Point(15, 83);
|
||||
this.comboLocale.Name = "comboLocale";
|
||||
this.comboLocale.Size = new System.Drawing.Size(201, 21);
|
||||
this.comboLocale.TabIndex = 3;
|
||||
//
|
||||
// labelDataFolder
|
||||
//
|
||||
this.labelDataFolder.AutoSize = true;
|
||||
@@ -111,20 +137,23 @@
|
||||
this.labelDataFolder.TabIndex = 4;
|
||||
this.labelDataFolder.Text = "Data Folder";
|
||||
//
|
||||
// tbDataFolder
|
||||
// labelShortcutTarget
|
||||
//
|
||||
this.tbDataFolder.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.tbDataFolder.Location = new System.Drawing.Point(15, 135);
|
||||
this.tbDataFolder.Name = "tbDataFolder";
|
||||
this.tbDataFolder.Size = new System.Drawing.Size(201, 20);
|
||||
this.tbDataFolder.TabIndex = 5;
|
||||
this.labelShortcutTarget.AutoSize = true;
|
||||
this.labelShortcutTarget.Location = new System.Drawing.Point(12, 170);
|
||||
this.labelShortcutTarget.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
|
||||
this.labelShortcutTarget.Name = "labelShortcutTarget";
|
||||
this.labelShortcutTarget.Size = new System.Drawing.Size(155, 13);
|
||||
this.labelShortcutTarget.TabIndex = 6;
|
||||
this.labelShortcutTarget.Text = "Shortcut Target (click to select)";
|
||||
//
|
||||
// DialogSettingsRestart
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.ClientSize = new System.Drawing.Size(228, 206);
|
||||
this.ClientSize = new System.Drawing.Size(284, 252);
|
||||
this.Controls.Add(this.tbShortcutTarget);
|
||||
this.Controls.Add(this.labelShortcutTarget);
|
||||
this.Controls.Add(this.tbDataFolder);
|
||||
this.Controls.Add(this.labelDataFolder);
|
||||
this.Controls.Add(this.comboLocale);
|
||||
@@ -155,5 +184,7 @@
|
||||
private System.Windows.Forms.ComboBox comboLocale;
|
||||
private System.Windows.Forms.Label labelDataFolder;
|
||||
private System.Windows.Forms.TextBox tbDataFolder;
|
||||
private System.Windows.Forms.TextBox tbShortcutTarget;
|
||||
private System.Windows.Forms.Label labelShortcutTarget;
|
||||
}
|
||||
}
|
@@ -24,12 +24,26 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
|
||||
cbLogging.Checked = currentArgs.HasFlag(Arguments.ArgLogging);
|
||||
cbDebugUpdates.Checked = currentArgs.HasFlag(Arguments.ArgDebugUpdates);
|
||||
comboLocale.SelectedItem = currentArgs.GetValue(Arguments.ArgLocale, DefaultLocale);
|
||||
tbDataFolder.Text = currentArgs.GetValue(Arguments.ArgDataFolder, string.Empty);
|
||||
|
||||
cbLogging.CheckedChanged += control_Change;
|
||||
cbDebugUpdates.CheckedChanged += control_Change;
|
||||
comboLocale.SelectedValueChanged += control_Change;
|
||||
|
||||
if (Program.IsPortable){
|
||||
tbDataFolder.Text = "Not available in portable version";
|
||||
tbDataFolder.Enabled = false;
|
||||
}
|
||||
else{
|
||||
tbDataFolder.Text = currentArgs.GetValue(Arguments.ArgDataFolder, string.Empty);
|
||||
tbDataFolder.TextChanged += control_Change;
|
||||
}
|
||||
|
||||
control_Change(this, EventArgs.Empty);
|
||||
|
||||
Text = Program.BrandName+" Arguments";
|
||||
}
|
||||
|
||||
private void btnRestart_Click(object sender, EventArgs e){
|
||||
private void control_Change(object sender, EventArgs e){
|
||||
Args = new CommandLineArgs();
|
||||
|
||||
if (cbLogging.Checked){
|
||||
@@ -46,10 +60,21 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
|
||||
Args.SetValue(Arguments.ArgLocale, locale);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(tbDataFolder.Text)){
|
||||
if (!string.IsNullOrWhiteSpace(tbDataFolder.Text) && tbDataFolder.Enabled){
|
||||
Args.SetValue(Arguments.ArgDataFolder, tbDataFolder.Text);
|
||||
}
|
||||
|
||||
tbShortcutTarget.Text = $@"""{Application.ExecutablePath}""{(Args.Count > 0 ? " " : "")}{Args}";
|
||||
tbShortcutTarget.Select(tbShortcutTarget.Text.Length, 0);
|
||||
}
|
||||
|
||||
private void tbShortcutTarget_Click(object sender, EventArgs e){
|
||||
if (tbShortcutTarget.SelectionLength == 0){
|
||||
tbShortcutTarget.SelectAll();
|
||||
}
|
||||
}
|
||||
|
||||
private void btnRestart_Click(object sender, EventArgs e){
|
||||
DialogResult = DialogResult.OK;
|
||||
Close();
|
||||
}
|
||||
|
@@ -2,7 +2,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using TweetDuck.Configuration;
|
||||
using TweetDuck.Data;
|
||||
using TweetDuck.Plugins;
|
||||
using TweetDuck.Plugins.Enums;
|
||||
@@ -141,13 +140,6 @@ namespace TweetDuck.Core.Other.Settings.Export{
|
||||
FormMessage.Information("Importing TweetDuck Profile", "Detected missing plugins when importing plugin data:\n"+string.Join("\n", missingPlugins), FormMessage.OK);
|
||||
}
|
||||
|
||||
if (IsRestarting){
|
||||
Program.Restart(Arguments.ArgImportCookies);
|
||||
}
|
||||
else{
|
||||
Program.ReloadConfig();
|
||||
}
|
||||
|
||||
return true;
|
||||
}catch(Exception e){
|
||||
LastException = e;
|
||||
@@ -169,6 +161,16 @@ namespace TweetDuck.Core.Other.Settings.Export{
|
||||
}
|
||||
}
|
||||
|
||||
public static void DeleteCookies(){
|
||||
try{
|
||||
if (File.Exists(CookiesPath)){
|
||||
File.Delete(CookiesPath);
|
||||
}
|
||||
}catch(Exception e){
|
||||
Program.Reporter.HandleException("Session Reset Error", "Could not remove the cookie file to reset the login session.", true, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<PathInfo> EnumerateFilesRelative(string root){
|
||||
return Directory.Exists(root) ? Directory.EnumerateFiles(root, "*.*", SearchOption.AllDirectories).Select(fullPath => new PathInfo{
|
||||
Full = fullPath,
|
||||
@@ -176,7 +178,7 @@ namespace TweetDuck.Core.Other.Settings.Export{
|
||||
}) : Enumerable.Empty<PathInfo>();
|
||||
}
|
||||
|
||||
private class PathInfo{
|
||||
private sealed class PathInfo{
|
||||
public string Full { get; set; }
|
||||
public string Relative { get; set; }
|
||||
}
|
||||
|
@@ -7,7 +7,7 @@ using TweetDuck.Core.Other.Settings.Dialogs;
|
||||
using TweetDuck.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Core.Other.Settings{
|
||||
partial class TabSettingsAdvanced : BaseTabSettings{
|
||||
sealed partial class TabSettingsAdvanced : BaseTabSettings{
|
||||
private static SystemConfig SysConfig => Program.SystemConfig;
|
||||
|
||||
private readonly Action<string> reinjectBrowserCSS;
|
||||
|
@@ -123,7 +123,7 @@
|
||||
// labelZoomValue
|
||||
//
|
||||
this.labelZoomValue.BackColor = System.Drawing.Color.Transparent;
|
||||
this.labelZoomValue.Location = new System.Drawing.Point(141, 123);
|
||||
this.labelZoomValue.Location = new System.Drawing.Point(147, 123);
|
||||
this.labelZoomValue.Margin = new System.Windows.Forms.Padding(0, 0, 3, 0);
|
||||
this.labelZoomValue.Name = "labelZoomValue";
|
||||
this.labelZoomValue.Size = new System.Drawing.Size(38, 13);
|
||||
@@ -204,8 +204,8 @@
|
||||
this.panelUI.Controls.Add(this.checkSwitchAccountSelectors);
|
||||
this.panelUI.Controls.Add(this.checkSpellCheck);
|
||||
this.panelUI.Controls.Add(this.labelZoom);
|
||||
this.panelUI.Controls.Add(this.labelZoomValue);
|
||||
this.panelUI.Controls.Add(this.trackBarZoom);
|
||||
this.panelUI.Controls.Add(this.labelZoomValue);
|
||||
this.panelUI.Location = new System.Drawing.Point(9, 31);
|
||||
this.panelUI.Name = "panelUI";
|
||||
this.panelUI.Size = new System.Drawing.Size(322, 157);
|
||||
|
@@ -1,10 +1,9 @@
|
||||
using System;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Updates;
|
||||
using TweetDuck.Updates.Events;
|
||||
|
||||
namespace TweetDuck.Core.Other.Settings{
|
||||
partial class TabSettingsGeneral : BaseTabSettings{
|
||||
sealed partial class TabSettingsGeneral : BaseTabSettings{
|
||||
private readonly UpdateHandler updates;
|
||||
private int updateCheckEventId = -1;
|
||||
|
||||
@@ -30,6 +29,8 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
checkSwitchAccountSelectors.Checked = Config.SwitchAccountSelectors;
|
||||
checkBestImageQuality.Checked = Config.BestImageQuality;
|
||||
checkSpellCheck.Checked = Config.EnableSpellCheck;
|
||||
|
||||
checkTrayHighlight.Enabled = Config.TrayBehavior.ShouldDisplayIcon();
|
||||
checkTrayHighlight.Checked = Config.EnableTrayHighlight;
|
||||
|
||||
checkUpdateNotifications.Checked = Config.EnableUpdateCheck;
|
||||
@@ -80,6 +81,7 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
|
||||
private void comboBoxTrayType_SelectedIndexChanged(object sender, EventArgs e){
|
||||
Config.TrayBehavior = (TrayIcon.Behavior)comboBoxTrayType.SelectedIndex;
|
||||
checkTrayHighlight.Enabled = Config.TrayBehavior.ShouldDisplayIcon();
|
||||
}
|
||||
|
||||
private void checkTrayHighlight_CheckedChanged(object sender, EventArgs e){
|
||||
@@ -91,23 +93,18 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
}
|
||||
|
||||
private void btnCheckUpdates_Click(object sender, EventArgs e){
|
||||
updateCheckEventId = updates.Check(true);
|
||||
Config.DismissedUpdate = null;
|
||||
|
||||
if (updateCheckEventId == -1){
|
||||
FormMessage.Warning("Unsupported System", "Sorry, your system is no longer supported.", FormMessage.OK);
|
||||
}
|
||||
else{
|
||||
btnCheckUpdates.Enabled = false;
|
||||
updates.DismissUpdate(string.Empty);
|
||||
}
|
||||
btnCheckUpdates.Enabled = false;
|
||||
updateCheckEventId = updates.Check(true);
|
||||
}
|
||||
|
||||
private void updates_CheckFinished(object sender, UpdateCheckEventArgs e){
|
||||
private void updates_CheckFinished(object sender, UpdateEventArgs e){
|
||||
this.InvokeAsyncSafe(() => {
|
||||
if (e.EventId == updateCheckEventId){
|
||||
btnCheckUpdates.Enabled = true;
|
||||
|
||||
if (!e.UpdateAvailable){
|
||||
if (!e.IsUpdateAvailable){
|
||||
FormMessage.Information("No Updates Available", "Your version of TweetDuck is up to date.", FormMessage.OK);
|
||||
}
|
||||
}
|
||||
|
@@ -79,7 +79,7 @@
|
||||
this.labelEdgeDistanceValue.Location = new System.Drawing.Point(147, 129);
|
||||
this.labelEdgeDistanceValue.Margin = new System.Windows.Forms.Padding(0, 0, 3, 0);
|
||||
this.labelEdgeDistanceValue.Name = "labelEdgeDistanceValue";
|
||||
this.labelEdgeDistanceValue.Size = new System.Drawing.Size(34, 13);
|
||||
this.labelEdgeDistanceValue.Size = new System.Drawing.Size(40, 13);
|
||||
this.labelEdgeDistanceValue.TabIndex = 9;
|
||||
this.labelEdgeDistanceValue.Text = "0 px";
|
||||
this.labelEdgeDistanceValue.TextAlign = System.Drawing.ContentAlignment.TopRight;
|
||||
@@ -253,7 +253,7 @@
|
||||
this.labelDurationValue.Location = new System.Drawing.Point(147, 77);
|
||||
this.labelDurationValue.Margin = new System.Windows.Forms.Padding(0, 0, 3, 0);
|
||||
this.labelDurationValue.Name = "labelDurationValue";
|
||||
this.labelDurationValue.Size = new System.Drawing.Size(48, 13);
|
||||
this.labelDurationValue.Size = new System.Drawing.Size(52, 13);
|
||||
this.labelDurationValue.TabIndex = 4;
|
||||
this.labelDurationValue.Text = "0 ms/c";
|
||||
this.labelDurationValue.TextAlign = System.Drawing.ContentAlignment.TopRight;
|
||||
@@ -406,7 +406,7 @@
|
||||
this.labelScrollSpeedValue.Location = new System.Drawing.Point(147, 53);
|
||||
this.labelScrollSpeedValue.Margin = new System.Windows.Forms.Padding(0, 0, 3, 0);
|
||||
this.labelScrollSpeedValue.Name = "labelScrollSpeedValue";
|
||||
this.labelScrollSpeedValue.Size = new System.Drawing.Size(34, 13);
|
||||
this.labelScrollSpeedValue.Size = new System.Drawing.Size(38, 13);
|
||||
this.labelScrollSpeedValue.TabIndex = 4;
|
||||
this.labelScrollSpeedValue.Text = "100%";
|
||||
this.labelScrollSpeedValue.TextAlign = System.Drawing.ContentAlignment.TopRight;
|
||||
@@ -450,7 +450,6 @@
|
||||
//
|
||||
this.panelLocation.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.panelLocation.Controls.Add(this.labelEdgeDistanceValue);
|
||||
this.panelLocation.Controls.Add(this.radioLocTL);
|
||||
this.panelLocation.Controls.Add(this.labelDisplay);
|
||||
this.panelLocation.Controls.Add(this.trackBarEdgeDistance);
|
||||
@@ -460,6 +459,7 @@
|
||||
this.panelLocation.Controls.Add(this.radioLocBL);
|
||||
this.panelLocation.Controls.Add(this.radioLocCustom);
|
||||
this.panelLocation.Controls.Add(this.radioLocBR);
|
||||
this.panelLocation.Controls.Add(this.labelEdgeDistanceValue);
|
||||
this.panelLocation.Location = new System.Drawing.Point(9, 418);
|
||||
this.panelLocation.Name = "panelLocation";
|
||||
this.panelLocation.Size = new System.Drawing.Size(322, 165);
|
||||
|
@@ -4,7 +4,7 @@ using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Notification;
|
||||
|
||||
namespace TweetDuck.Core.Other.Settings{
|
||||
partial class TabSettingsNotifications : BaseTabSettings{
|
||||
sealed partial class TabSettingsNotifications : BaseTabSettings{
|
||||
private static readonly int[] IdlePauseSeconds = { 0, 30, 60, 120, 300 };
|
||||
|
||||
private readonly FormNotificationMain notification;
|
||||
|
72
Core/Other/Settings/TabSettingsSounds.Designer.cs
generated
72
Core/Other/Settings/TabSettingsSounds.Designer.cs
generated
@@ -25,15 +25,40 @@
|
||||
private void InitializeComponent() {
|
||||
this.components = new System.ComponentModel.Container();
|
||||
this.toolTip = new System.Windows.Forms.ToolTip(this.components);
|
||||
this.tbCustomSound = new System.Windows.Forms.TextBox();
|
||||
this.labelVolumeValue = new System.Windows.Forms.Label();
|
||||
this.btnPlaySound = new System.Windows.Forms.Button();
|
||||
this.btnResetSound = new System.Windows.Forms.Button();
|
||||
this.btnBrowseSound = new System.Windows.Forms.Button();
|
||||
this.tbCustomSound = new System.Windows.Forms.TextBox();
|
||||
this.labelSoundNotification = new System.Windows.Forms.Label();
|
||||
this.panelSoundNotification = new System.Windows.Forms.Panel();
|
||||
this.labelVolume = new System.Windows.Forms.Label();
|
||||
this.trackBarVolume = new System.Windows.Forms.TrackBar();
|
||||
this.panelSoundNotification.SuspendLayout();
|
||||
((System.ComponentModel.ISupportInitialize)(this.trackBarVolume)).BeginInit();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// tbCustomSound
|
||||
//
|
||||
this.tbCustomSound.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.tbCustomSound.Location = new System.Drawing.Point(3, 3);
|
||||
this.tbCustomSound.Name = "tbCustomSound";
|
||||
this.tbCustomSound.Size = new System.Drawing.Size(316, 20);
|
||||
this.tbCustomSound.TabIndex = 0;
|
||||
this.toolTip.SetToolTip(this.tbCustomSound, "When empty, the default TweetDeck sound notification is used.");
|
||||
//
|
||||
// labelVolumeValue
|
||||
//
|
||||
this.labelVolumeValue.BackColor = System.Drawing.Color.Transparent;
|
||||
this.labelVolumeValue.Location = new System.Drawing.Point(147, 84);
|
||||
this.labelVolumeValue.Margin = new System.Windows.Forms.Padding(0, 0, 3, 0);
|
||||
this.labelVolumeValue.Name = "labelVolumeValue";
|
||||
this.labelVolumeValue.Size = new System.Drawing.Size(38, 13);
|
||||
this.labelVolumeValue.TabIndex = 6;
|
||||
this.labelVolumeValue.Text = "100%";
|
||||
this.labelVolumeValue.TextAlign = System.Drawing.ContentAlignment.TopRight;
|
||||
//
|
||||
// btnPlaySound
|
||||
//
|
||||
this.btnPlaySound.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
|
||||
@@ -69,16 +94,6 @@
|
||||
this.btnBrowseSound.Text = "Browse...";
|
||||
this.btnBrowseSound.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// tbCustomSound
|
||||
//
|
||||
this.tbCustomSound.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.tbCustomSound.Location = new System.Drawing.Point(3, 3);
|
||||
this.tbCustomSound.Name = "tbCustomSound";
|
||||
this.tbCustomSound.Size = new System.Drawing.Size(316, 20);
|
||||
this.tbCustomSound.TabIndex = 0;
|
||||
this.toolTip.SetToolTip(this.tbCustomSound, "When empty, the default TweetDeck sound notification is used.");
|
||||
//
|
||||
// labelSoundNotification
|
||||
//
|
||||
this.labelSoundNotification.AutoSize = true;
|
||||
@@ -94,15 +109,42 @@
|
||||
//
|
||||
this.panelSoundNotification.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.panelSoundNotification.Controls.Add(this.labelVolume);
|
||||
this.panelSoundNotification.Controls.Add(this.trackBarVolume);
|
||||
this.panelSoundNotification.Controls.Add(this.btnPlaySound);
|
||||
this.panelSoundNotification.Controls.Add(this.tbCustomSound);
|
||||
this.panelSoundNotification.Controls.Add(this.btnResetSound);
|
||||
this.panelSoundNotification.Controls.Add(this.btnBrowseSound);
|
||||
this.panelSoundNotification.Controls.Add(this.labelVolumeValue);
|
||||
this.panelSoundNotification.Location = new System.Drawing.Point(9, 31);
|
||||
this.panelSoundNotification.Name = "panelSoundNotification";
|
||||
this.panelSoundNotification.Size = new System.Drawing.Size(322, 56);
|
||||
this.panelSoundNotification.Size = new System.Drawing.Size(322, 119);
|
||||
this.panelSoundNotification.TabIndex = 2;
|
||||
//
|
||||
// labelVolume
|
||||
//
|
||||
this.labelVolume.AutoSize = true;
|
||||
this.labelVolume.Location = new System.Drawing.Point(3, 67);
|
||||
this.labelVolume.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
|
||||
this.labelVolume.Name = "labelVolume";
|
||||
this.labelVolume.Size = new System.Drawing.Size(42, 13);
|
||||
this.labelVolume.TabIndex = 4;
|
||||
this.labelVolume.Text = "Volume";
|
||||
//
|
||||
// trackBarVolume
|
||||
//
|
||||
this.trackBarVolume.AutoSize = false;
|
||||
this.trackBarVolume.BackColor = System.Drawing.SystemColors.Control;
|
||||
this.trackBarVolume.Location = new System.Drawing.Point(3, 83);
|
||||
this.trackBarVolume.Maximum = 100;
|
||||
this.trackBarVolume.Name = "trackBarVolume";
|
||||
this.trackBarVolume.Size = new System.Drawing.Size(148, 30);
|
||||
this.trackBarVolume.SmallChange = 1;
|
||||
this.trackBarVolume.TabIndex = 5;
|
||||
this.trackBarVolume.TickFrequency = 10;
|
||||
this.trackBarVolume.Value = 100;
|
||||
this.trackBarVolume.ValueChanged += new System.EventHandler(this.trackBarVolume_ValueChanged);
|
||||
//
|
||||
// TabSettingsSounds
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
@@ -110,9 +152,10 @@
|
||||
this.Controls.Add(this.panelSoundNotification);
|
||||
this.Controls.Add(this.labelSoundNotification);
|
||||
this.Name = "TabSettingsSounds";
|
||||
this.Size = new System.Drawing.Size(340, 97);
|
||||
this.Size = new System.Drawing.Size(340, 160);
|
||||
this.panelSoundNotification.ResumeLayout(false);
|
||||
this.panelSoundNotification.PerformLayout();
|
||||
((System.ComponentModel.ISupportInitialize)(this.trackBarVolume)).EndInit();
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
|
||||
@@ -127,5 +170,8 @@
|
||||
private System.Windows.Forms.Button btnPlaySound;
|
||||
private System.Windows.Forms.Label labelSoundNotification;
|
||||
private System.Windows.Forms.Panel panelSoundNotification;
|
||||
private System.Windows.Forms.Label labelVolume;
|
||||
private System.Windows.Forms.Label labelVolumeValue;
|
||||
private System.Windows.Forms.TrackBar trackBarVolume;
|
||||
}
|
||||
}
|
||||
|
@@ -2,21 +2,29 @@
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Notification;
|
||||
using TweetLib.Audio;
|
||||
|
||||
namespace TweetDuck.Core.Other.Settings{
|
||||
partial class TabSettingsSounds : BaseTabSettings{
|
||||
sealed partial class TabSettingsSounds : BaseTabSettings{
|
||||
private readonly SoundNotification soundNotification;
|
||||
private readonly bool supportsChangingVolume;
|
||||
|
||||
public TabSettingsSounds(){
|
||||
InitializeComponent();
|
||||
|
||||
soundNotification = new SoundNotification();
|
||||
soundNotification.PlaybackError += sound_PlaybackError;
|
||||
|
||||
supportsChangingVolume = soundNotification.SetVolume(Config.NotificationSoundVolume);
|
||||
|
||||
trackBarVolume.Enabled = supportsChangingVolume && !string.IsNullOrEmpty(Config.NotificationSoundPath);
|
||||
trackBarVolume.SetValueSafe(Config.NotificationSoundVolume);
|
||||
labelVolumeValue.Text = trackBarVolume.Value+"%";
|
||||
|
||||
tbCustomSound.Text = Config.NotificationSoundPath;
|
||||
tbCustomSound_TextChanged(tbCustomSound, new EventArgs());
|
||||
tbCustomSound_TextChanged(tbCustomSound, EventArgs.Empty);
|
||||
|
||||
Disposed += (sender, args) => soundNotification.Dispose();
|
||||
}
|
||||
@@ -37,6 +45,7 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
tbCustomSound.ForeColor = isEmpty || File.Exists(tbCustomSound.Text) ? SystemColors.WindowText : Color.Red;
|
||||
btnPlaySound.Enabled = !isEmpty;
|
||||
btnResetSound.Enabled = !isEmpty;
|
||||
trackBarVolume.Enabled = supportsChangingVolume && !isEmpty;
|
||||
}
|
||||
|
||||
private void btnPlaySound_Click(object sender, EventArgs e){
|
||||
@@ -63,5 +72,11 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
private void btnResetSound_Click(object sender, EventArgs e){
|
||||
tbCustomSound.Text = string.Empty;
|
||||
}
|
||||
|
||||
private void trackBarVolume_ValueChanged(object sender, EventArgs e){
|
||||
Config.NotificationSoundVolume = trackBarVolume.Value;
|
||||
soundNotification.SetVolume(Config.NotificationSoundVolume);
|
||||
labelVolumeValue.Text = Config.NotificationSoundVolume+"%";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -3,7 +3,7 @@ using System.ComponentModel;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace TweetDuck.Core{
|
||||
partial class TrayIcon : Component{
|
||||
sealed partial class TrayIcon : Component{
|
||||
public enum Behavior{ // keep order
|
||||
Disabled, DisplayOnly, MinimizeToTray, CloseToTray, Combined
|
||||
}
|
||||
|
@@ -5,6 +5,7 @@ using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Windows.Forms;
|
||||
using CefSharp.WinForms;
|
||||
using TweetDuck.Core.Other;
|
||||
|
||||
namespace TweetDuck.Core.Utils{
|
||||
@@ -42,23 +43,46 @@ namespace TweetDuck.Core.Utils{
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsValidUrl(string url){
|
||||
public static ChromiumWebBrowser AsControl(this IWebBrowser browserControl){
|
||||
return (ChromiumWebBrowser)browserControl;
|
||||
}
|
||||
|
||||
private const string TwitterTrackingUrl = "t.co";
|
||||
|
||||
public enum UrlCheckResult{
|
||||
Invalid, Tracking, Fine
|
||||
}
|
||||
|
||||
public static UrlCheckResult CheckUrl(string url){
|
||||
if (Uri.TryCreate(url, UriKind.Absolute, out Uri uri)){
|
||||
string scheme = uri.Scheme;
|
||||
return scheme == Uri.UriSchemeHttp || scheme == Uri.UriSchemeHttps || scheme == Uri.UriSchemeFtp || scheme == Uri.UriSchemeMailto;
|
||||
|
||||
if (scheme == Uri.UriSchemeHttps || scheme == Uri.UriSchemeHttp || scheme == Uri.UriSchemeFtp || scheme == Uri.UriSchemeMailto){
|
||||
return uri.Host == TwitterTrackingUrl ? UrlCheckResult.Tracking : UrlCheckResult.Fine;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return UrlCheckResult.Invalid;
|
||||
}
|
||||
|
||||
public static void OpenExternalBrowser(string url){
|
||||
if (string.IsNullOrWhiteSpace(url))return;
|
||||
|
||||
if (IsValidUrl(url)){
|
||||
OpenExternalBrowserUnsafe(url);
|
||||
}
|
||||
else{
|
||||
FormMessage.Warning("Blocked URL", "A potentially malicious URL was blocked from opening:\n"+url, FormMessage.OK);
|
||||
switch(CheckUrl(url)){
|
||||
case UrlCheckResult.Fine:
|
||||
OpenExternalBrowserUnsafe(url);
|
||||
break;
|
||||
|
||||
case UrlCheckResult.Tracking:
|
||||
if (FormMessage.Warning("Blocked URL", "TweetDuck has blocked a tracking url due to privacy concerns. Do you want to visit it anyway?\n"+url, FormMessage.Yes, FormMessage.No)){
|
||||
OpenExternalBrowserUnsafe(url);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case UrlCheckResult.Invalid:
|
||||
FormMessage.Warning("Blocked URL", "A potentially malicious URL was blocked from opening:\n"+url, FormMessage.OK);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -8,7 +8,6 @@ namespace TweetDuck.Core.Utils{
|
||||
[SuppressMessage("ReSharper", "FieldCanBeMadeReadOnly.Local")]
|
||||
[SuppressMessage("ReSharper", "MemberCanBePrivate.Local")]
|
||||
static class NativeMethods{
|
||||
public static readonly IntPtr HWND_BROADCAST = new IntPtr(0xFFFF);
|
||||
public static readonly IntPtr HOOK_HANDLED = new IntPtr(-1);
|
||||
|
||||
public const int HWND_TOPMOST = -1;
|
||||
@@ -17,7 +16,6 @@ namespace TweetDuck.Core.Utils{
|
||||
public const int GWL_STYLE = -16;
|
||||
|
||||
public const int SB_HORZ = 0;
|
||||
public const int BCM_SETSHIELD = 0x160C;
|
||||
|
||||
public const int WM_MOUSE_LL = 14;
|
||||
public const int WM_MOUSEWHEEL = 0x020A;
|
||||
@@ -65,16 +63,7 @@ namespace TweetDuck.Core.Utils{
|
||||
[DllImport("gdi32.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool BitBlt(IntPtr hdc, int nXDest, int nYDest, int nWidth, int nHeight, IntPtr hdcSrc, int nXSrc, int nYSrc, uint dwRop);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
public static extern IntPtr SendMessage(IntPtr hWnd, uint msg, UIntPtr wParam, IntPtr lParam);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
public static extern bool PostMessage(IntPtr hWnd, uint msg, UIntPtr wParam, IntPtr lParam);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
public static extern uint RegisterWindowMessage(string messageName);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static extern bool ShowScrollBar(IntPtr hWnd, int wBar, bool bShow);
|
||||
|
@@ -14,7 +14,7 @@ namespace TweetDuck.Core.Utils{
|
||||
public static readonly Color BackgroundColor = Color.FromArgb(28, 99, 153);
|
||||
public const string BackgroundColorFix = "let e=document.createElement('style');document.head.appendChild(e);e.innerHTML='body::before{background:#1c6399!important}'";
|
||||
|
||||
private static readonly Lazy<Regex> RegexAccountLazy = new Lazy<Regex>(() => new Regex(@"^https?://twitter\.com/([^/]+)/?$", RegexOptions.Compiled), false);
|
||||
private static readonly Lazy<Regex> RegexAccountLazy = new Lazy<Regex>(() => new Regex(@"^https?://twitter\.com/(?!signup$|tos$|privacy$)([^/]+)/?$", RegexOptions.Compiled), false);
|
||||
public static Regex RegexAccount => RegexAccountLazy.Value;
|
||||
|
||||
public static readonly string[] DictionaryWords = {
|
||||
@@ -33,14 +33,14 @@ namespace TweetDuck.Core.Utils{
|
||||
return frame.Url.Contains("//twitter.com/");
|
||||
}
|
||||
|
||||
private static string ExtractImageBaseLink(string url){
|
||||
private static string ExtractMediaBaseLink(string url){
|
||||
int dot = url.LastIndexOf('/');
|
||||
return dot == -1 ? url : StringUtils.ExtractBefore(url, ':', dot);
|
||||
}
|
||||
|
||||
public static string GetImageLink(string url, ImageQuality quality){
|
||||
public static string GetMediaLink(string url, ImageQuality quality){
|
||||
if (quality == ImageQuality.Orig){
|
||||
string result = ExtractImageBaseLink(url);
|
||||
string result = ExtractMediaBaseLink(url);
|
||||
|
||||
if (result != url || url.Contains("//pbs.twimg.com/media/")){
|
||||
result += ":orig";
|
||||
@@ -62,10 +62,10 @@ namespace TweetDuck.Core.Utils{
|
||||
return;
|
||||
}
|
||||
|
||||
string firstImageLink = GetImageLink(urls[0], quality);
|
||||
string firstImageLink = GetMediaLink(urls[0], quality);
|
||||
int qualityIndex = firstImageLink.IndexOf(':', firstImageLink.LastIndexOf('/'));
|
||||
|
||||
string file = BrowserUtils.GetFileNameFromUrl(ExtractImageBaseLink(firstImageLink));
|
||||
string file = BrowserUtils.GetFileNameFromUrl(ExtractMediaBaseLink(firstImageLink));
|
||||
string ext = Path.GetExtension(file); // includes dot
|
||||
|
||||
string[] fileNameParts = qualityIndex == -1 ? new string[]{
|
||||
@@ -96,11 +96,30 @@ namespace TweetDuck.Core.Utils{
|
||||
string pathExt = Path.GetExtension(dialog.FileName);
|
||||
|
||||
for(int index = 0; index < urls.Length; index++){
|
||||
BrowserUtils.DownloadFileAsync(GetImageLink(urls[index], quality), $"{pathBase} {index+1}{pathExt}", null, OnFailure);
|
||||
BrowserUtils.DownloadFileAsync(GetMediaLink(urls[index], quality), $"{pathBase} {index+1}{pathExt}", null, OnFailure);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void DownloadVideo(string url){
|
||||
string filename = BrowserUtils.GetFileNameFromUrl(url);
|
||||
string ext = Path.GetExtension(filename);
|
||||
|
||||
using(SaveFileDialog dialog = new SaveFileDialog{
|
||||
AutoUpgradeEnabled = true,
|
||||
OverwritePrompt = true,
|
||||
Title = "Save video",
|
||||
FileName = filename,
|
||||
Filter = "Video"+(string.IsNullOrEmpty(ext) ? " (unknown)|*.*" : $" (*{ext})|*{ext}")
|
||||
}){
|
||||
if (dialog.ShowDialog() == DialogResult.OK){
|
||||
BrowserUtils.DownloadFileAsync(url, dialog.FileName, null, ex => {
|
||||
FormMessage.Error("Image Download", "An error occurred while downloading the image: "+ex.Message, FormMessage.OK);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -91,7 +91,7 @@ namespace TweetDuck.Data{
|
||||
stream.Dispose();
|
||||
}
|
||||
|
||||
public class Entry{
|
||||
public sealed class Entry{
|
||||
public string Identifier { get; }
|
||||
|
||||
public string KeyName{
|
||||
|
@@ -90,12 +90,12 @@ namespace TweetDuck.Data.Serialization{
|
||||
HandleUnknownProperties?.Invoke(obj, unknownProperties);
|
||||
|
||||
if (unknownProperties.Count > 0){
|
||||
throw new SerializationException($"Invalid file format, unknown properties: {string.Join(", ", unknownProperties.Keys)}+");
|
||||
throw new SerializationException($"Invalid file format, unknown properties: {string.Join(", ", unknownProperties.Keys)}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class BasicTypeConverter : ITypeConverter{
|
||||
private sealed class BasicTypeConverter : ITypeConverter{
|
||||
bool ITypeConverter.TryWriteType(Type type, object value, out string converted){
|
||||
switch(Type.GetTypeCode(type)){
|
||||
case TypeCode.Boolean:
|
||||
|
@@ -1,12 +1,10 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetDuck.Data.Serialization;
|
||||
|
||||
namespace TweetDuck.Data{
|
||||
[Serializable] // TODO remove attribute with UserConfigLegacy
|
||||
sealed class WindowState{
|
||||
private Rectangle rect;
|
||||
private bool isMaximized;
|
||||
|
@@ -8,7 +8,7 @@ using TweetDuck.Core.Utils;
|
||||
using TweetDuck.Plugins.Enums;
|
||||
|
||||
namespace TweetDuck.Plugins.Controls{
|
||||
partial class PluginControl : UserControl{
|
||||
sealed partial class PluginControl : UserControl{
|
||||
private readonly PluginManager pluginManager;
|
||||
private readonly Plugin plugin;
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
using System;
|
||||
|
||||
namespace TweetDuck.Plugins.Events{
|
||||
class PluginChangedStateEventArgs : EventArgs{
|
||||
sealed class PluginChangedStateEventArgs : EventArgs{
|
||||
public Plugin Plugin { get; }
|
||||
public bool IsEnabled { get; }
|
||||
|
||||
|
@@ -2,10 +2,10 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace TweetDuck.Plugins.Events{
|
||||
class PluginErrorEventArgs : EventArgs{
|
||||
sealed class PluginErrorEventArgs : EventArgs{
|
||||
public bool HasErrors => Errors.Count > 0;
|
||||
|
||||
public IList<string> Errors;
|
||||
public IList<string> Errors { get; }
|
||||
|
||||
public PluginErrorEventArgs(IList<string> errors){
|
||||
this.Errors = errors;
|
||||
|
@@ -7,6 +7,9 @@ using TweetDuck.Plugins.Enums;
|
||||
|
||||
namespace TweetDuck.Plugins{
|
||||
sealed class Plugin{
|
||||
private static readonly Version AppVersion = new Version(Program.VersionTag);
|
||||
private const string VersionWildcard = "*";
|
||||
|
||||
public string Identifier { get; }
|
||||
public PluginGroup Group { get; }
|
||||
public PluginEnvironment Environments { get; private set; }
|
||||
@@ -50,7 +53,7 @@ namespace TweetDuck.Plugins{
|
||||
{ "WEBSITE", "" },
|
||||
{ "CONFIGFILE", "" },
|
||||
{ "CONFIGDEFAULT", "" },
|
||||
{ "REQUIRES", "*" }
|
||||
{ "REQUIRES", VersionWildcard }
|
||||
};
|
||||
|
||||
private bool? canRun;
|
||||
@@ -209,7 +212,7 @@ namespace TweetDuck.Plugins{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (plugin.RequiredVersion.Length == 0 || !(plugin.RequiredVersion.Equals("*") || System.Version.TryParse(plugin.RequiredVersion, out Version _))){
|
||||
if (plugin.RequiredVersion.Length == 0 || !(plugin.RequiredVersion == VersionWildcard || System.Version.TryParse(plugin.RequiredVersion, out Version _))){
|
||||
error = "Plugin contains invalid version: "+plugin.RequiredVersion;
|
||||
return false;
|
||||
}
|
||||
@@ -221,7 +224,7 @@ namespace TweetDuck.Plugins{
|
||||
}
|
||||
|
||||
private static bool CheckRequiredVersion(string requires){
|
||||
return requires.Equals("*", StringComparison.Ordinal) || Program.Version >= new Version(requires);
|
||||
return requires == VersionWildcard || AppVersion >= new Version(requires);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -8,22 +8,24 @@ namespace TweetDuck.Plugins{
|
||||
sealed class PluginConfig{
|
||||
public event EventHandler<PluginChangedStateEventArgs> InternalPluginChangedState; // should only be accessed from PluginManager
|
||||
|
||||
public IEnumerable<string> DisabledPlugins => Disabled;
|
||||
public bool AnyDisabled => Disabled.Count > 0;
|
||||
public IEnumerable<string> DisabledPlugins => disabled;
|
||||
public bool AnyDisabled => disabled.Count > 0;
|
||||
|
||||
private readonly HashSet<string> Disabled = new HashSet<string>{
|
||||
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))){
|
||||
if ((enabled && disabled.Remove(plugin.Identifier)) || (!enabled && disabled.Add(plugin.Identifier))){
|
||||
InternalPluginChangedState?.Invoke(this, new PluginChangedStateEventArgs(plugin, enabled));
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsEnabled(Plugin plugin){
|
||||
return !Disabled.Contains(plugin.Identifier);
|
||||
return !disabled.Contains(plugin.Identifier);
|
||||
}
|
||||
|
||||
public void Load(string file){
|
||||
@@ -32,14 +34,20 @@ namespace TweetDuck.Plugins{
|
||||
string line = reader.ReadLine();
|
||||
|
||||
if (line == "#Disabled"){
|
||||
Disabled.Clear();
|
||||
disabled.Clear();
|
||||
|
||||
while((line = reader.ReadLine()) != null){
|
||||
Disabled.Add(line);
|
||||
disabled.Add(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
}catch(FileNotFoundException){
|
||||
disabled.Clear();
|
||||
|
||||
foreach(string identifier in DefaultDisabled){
|
||||
disabled.Add(identifier);
|
||||
}
|
||||
|
||||
Save(file);
|
||||
}catch(DirectoryNotFoundException){
|
||||
}catch(Exception e){
|
||||
@@ -52,8 +60,8 @@ namespace TweetDuck.Plugins{
|
||||
using(StreamWriter writer = new StreamWriter(new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None), Encoding.UTF8)){
|
||||
writer.WriteLine("#Disabled");
|
||||
|
||||
foreach(string disabled in Disabled){
|
||||
writer.WriteLine(disabled);
|
||||
foreach(string identifier in disabled){
|
||||
writer.WriteLine(identifier);
|
||||
}
|
||||
}
|
||||
}catch(Exception e){
|
||||
|
@@ -45,14 +45,7 @@ namespace TweetDuck.Plugins{
|
||||
this.Bridge = new PluginBridge(this);
|
||||
|
||||
Config.Load(configPath);
|
||||
|
||||
Config.InternalPluginChangedState += Config_InternalPluginChangedState;
|
||||
Program.UserConfigReplaced += Program_UserConfigReplaced;
|
||||
}
|
||||
|
||||
private void Program_UserConfigReplaced(object sender, EventArgs e){
|
||||
Config.Load(configPath);
|
||||
Reload();
|
||||
}
|
||||
|
||||
private void Config_InternalPluginChangedState(object sender, PluginChangedStateEventArgs e){
|
||||
@@ -83,6 +76,8 @@ namespace TweetDuck.Plugins{
|
||||
}
|
||||
|
||||
public void Reload(){
|
||||
Config.Load(configPath);
|
||||
|
||||
plugins.Clear();
|
||||
tokens.Clear();
|
||||
|
||||
|
35
Program.cs
35
Program.cs
@@ -1,4 +1,4 @@
|
||||
using CefSharp;
|
||||
using CefSharp;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
@@ -14,16 +14,15 @@ using TweetDuck.Core.Other.Settings.Export;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetDuck.Data;
|
||||
using TweetDuck.Updates;
|
||||
using TweetLib.Communication;
|
||||
|
||||
namespace TweetDuck{
|
||||
static class Program{
|
||||
public const string BrandName = "TweetDuck";
|
||||
public const string Website = "https://tweetduck.chylex.com";
|
||||
|
||||
public const string VersionTag = "1.8.5.1";
|
||||
public const string VersionFull = "1.8.5.1";
|
||||
public const string VersionTag = "1.9.1";
|
||||
|
||||
public static readonly Version Version = new Version(VersionTag);
|
||||
public static readonly bool IsPortable = File.Exists("makeportable");
|
||||
|
||||
public static readonly string ProgramPath = AppDomain.CurrentDomain.BaseDirectory;
|
||||
@@ -44,23 +43,24 @@ namespace TweetDuck{
|
||||
|
||||
public static uint WindowRestoreMessage;
|
||||
public static uint SubProcessMessage;
|
||||
public static uint VideoPlayerMessage;
|
||||
|
||||
private static readonly LockManager LockManager = new LockManager(Path.Combine(StoragePath, ".lock"));
|
||||
private static bool HasCleanedUp;
|
||||
|
||||
|
||||
public static UserConfig UserConfig { get; private set; }
|
||||
public static SystemConfig SystemConfig { get; private set; }
|
||||
public static Reporter Reporter { get; }
|
||||
public static CultureInfo Culture { get; }
|
||||
|
||||
public static event EventHandler UserConfigReplaced;
|
||||
|
||||
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.SetupUnhandledExceptionHandler("TweetDuck Has Failed :(");
|
||||
}
|
||||
@@ -70,9 +70,8 @@ namespace TweetDuck{
|
||||
Application.EnableVisualStyles();
|
||||
Application.SetCompatibleTextRenderingDefault(false);
|
||||
|
||||
WindowRestoreMessage = NativeMethods.RegisterWindowMessage("TweetDuckRestore");
|
||||
SubProcessMessage = NativeMethods.RegisterWindowMessage("TweetDuckSubProcess");
|
||||
VideoPlayerMessage = NativeMethods.RegisterWindowMessage("TweetDuckVideoPlayer");
|
||||
WindowRestoreMessage = Comms.RegisterMessage("TweetDuckRestore");
|
||||
SubProcessMessage = Comms.RegisterMessage("TweetDuckSubProcess");
|
||||
|
||||
if (!WindowsUtils.CheckFolderWritePermission(StoragePath)){
|
||||
FormMessage.Warning("Permission Error", "TweetDuck does not have write permissions to the storage folder: "+StoragePath, FormMessage.OK);
|
||||
@@ -115,12 +114,15 @@ namespace TweetDuck{
|
||||
}
|
||||
}
|
||||
|
||||
UserConfig = UserConfig.Load(UserConfigFilePath);
|
||||
SystemConfig = SystemConfig.Load(SystemConfigFilePath);
|
||||
ReloadConfig();
|
||||
|
||||
if (Arguments.HasFlag(Arguments.ArgImportCookies)){
|
||||
ExportManager.ImportCookies();
|
||||
}
|
||||
else if (Arguments.HasFlag(Arguments.ArgDeleteCookies)){
|
||||
ExportManager.DeleteCookies();
|
||||
}
|
||||
|
||||
if (Arguments.HasFlag(Arguments.ArgUpdated)){
|
||||
WindowsUtils.TryDeleteFolderWhenAble(InstallerPath, 8000);
|
||||
@@ -187,11 +189,6 @@ namespace TweetDuck{
|
||||
}
|
||||
}
|
||||
|
||||
public static void ReloadConfig(){
|
||||
UserConfig = UserConfig.Load(UserConfigFilePath);
|
||||
UserConfigReplaced?.Invoke(UserConfig, new EventArgs());
|
||||
}
|
||||
|
||||
public static void ResetConfig(){
|
||||
try{
|
||||
File.Delete(UserConfigFilePath);
|
||||
@@ -200,8 +197,8 @@ namespace TweetDuck{
|
||||
Reporter.HandleException("Configuration Reset Error", "Could not delete configuration files to reset the options.", true, e);
|
||||
return;
|
||||
}
|
||||
|
||||
ReloadConfig();
|
||||
|
||||
UserConfig.Reload();
|
||||
}
|
||||
|
||||
public static void Restart(params string[] extraArgs){
|
||||
|
@@ -34,8 +34,8 @@ using TweetDuck;
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion(Program.VersionFull)]
|
||||
[assembly: AssemblyFileVersion(Program.VersionFull)]
|
||||
[assembly: AssemblyVersion(Program.VersionTag)]
|
||||
[assembly: AssemblyFileVersion(Program.VersionTag)]
|
||||
|
||||
[assembly: NeutralResourcesLanguage("en")]
|
||||
|
||||
|
@@ -53,9 +53,7 @@ enabled(){
|
||||
};
|
||||
|
||||
this.eventKeyDown = function(e){
|
||||
if (!(document.activeElement === null || document.activeElement === document.body)){
|
||||
return;
|
||||
}
|
||||
return if !(document.activeElement === null || document.activeElement === document.body);
|
||||
|
||||
updateShiftState(e.shiftKey);
|
||||
|
||||
|
@@ -102,16 +102,14 @@ enabled(){
|
||||
|
||||
// settings click event
|
||||
this.onSettingsMenuClickedEvent = () => {
|
||||
if (this.htmlModal === null || this.config === null){
|
||||
return;
|
||||
}
|
||||
return if this.htmlModal === null || this.config === null;
|
||||
|
||||
setTimeout(() => {
|
||||
let menu = $(".js-dropdown-content").children("ul").first();
|
||||
if (menu.length === 0)return;
|
||||
return if menu.length === 0;
|
||||
|
||||
let itemTD = menu.children("[data-std]").first();
|
||||
if (itemTD.length === 0)return;
|
||||
return if itemTD.length === 0;
|
||||
|
||||
if (!itemTD.prev().hasClass("drp-h-divider")){
|
||||
itemTD.before('<li class="drp-h-divider"></li>');
|
||||
@@ -336,17 +334,22 @@ enabled(){
|
||||
this.css.insert(".txt-base-smallest:not(.icon), .txt-base-largest:not(.icon) { font-size: "+this.config.fontSize+" !important }");
|
||||
this.css.insert(".avatar { border-radius: "+this.config.avatarRadius+"% !important }");
|
||||
|
||||
let notificationScrollbarColor = null;
|
||||
|
||||
if (this.config.themeColorTweaks){
|
||||
switch(TD.settings.getTheme()){
|
||||
case "dark":
|
||||
this.css.insert(".app-content, .app-columns-container { background-color: #444448 }");
|
||||
this.css.insert(".column-drag-handle { opacity: 0.5 }");
|
||||
this.css.insert(".column-drag-handle:hover { opacity: 1 }");
|
||||
this.css.insert(".scroll-styled-v::-webkit-scrollbar-thumb, .scroll-styled-h::-webkit-scrollbar-thumb { background-color: #666 }");
|
||||
notificationScrollbarColor = "666";
|
||||
break;
|
||||
|
||||
case "light":
|
||||
this.css.insert(".scroll-styled-v::-webkit-scrollbar-thumb, .scroll-styled-h::-webkit-scrollbar-thumb { background-color: #d2d6da }");
|
||||
this.css.insert(".app-columns-container.scroll-styled-h::-webkit-scrollbar-thumb:not(:hover) { background-color: #a5aeb5 }");
|
||||
notificationScrollbarColor = "a5aeb5";
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -462,6 +465,9 @@ enabled(){
|
||||
.drawer .btn .icon, .app-header .btn .icon { line-height: 1em !important }
|
||||
.column-header .column-type-icon { bottom: 26px !important }
|
||||
.is-options-open .column-type-icon { bottom: 25px !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-footer { margin-top: 6px !important }`;
|
||||
|
||||
document.head.appendChild(this.icons);
|
||||
@@ -513,6 +519,10 @@ ${this.config.revertIcons ? `
|
||||
.icon-user-filled:before{content:"\\f035";font-family:tweetdeckold}
|
||||
.icon-user-dd:before{content:"\\f01a";font-family:tweetdeckold}
|
||||
` : ``}
|
||||
|
||||
${notificationScrollbarColor ? `
|
||||
.scroll-styled-v::-webkit-scrollbar-thumb, .scroll-styled-h::-webkit-scrollbar-thumb { background-color: #${notificationScrollbarColor} }
|
||||
` : ``}
|
||||
</style>`);
|
||||
};
|
||||
|
||||
|
@@ -9,7 +9,7 @@ Emoji keyboard
|
||||
chylex
|
||||
|
||||
[version]
|
||||
1.2.2
|
||||
1.3.1
|
||||
|
||||
[website]
|
||||
https://tweetduck.chylex.com
|
||||
|
@@ -1,4 +1,6 @@
|
||||
enabled(){
|
||||
this.ENABLE_CUSTOM_KEYBOARD = false;
|
||||
|
||||
this.selectedSkinTone = "";
|
||||
this.currentKeywords = [];
|
||||
|
||||
@@ -30,16 +32,21 @@ enabled(){
|
||||
|
||||
this.css = window.TDPF_createCustomStyle(this);
|
||||
this.css.insert(".emoji-keyboard { position: absolute; width: 15.35em; background-color: white; border-radius: 1px; font-size: 24px; z-index: 9999 }");
|
||||
|
||||
this.css.insert(".emoji-keyboard-list { height: 10.14em; padding: 0.1em; box-sizing: border-box; overflow-y: auto }");
|
||||
this.css.insert(".emoji-keyboard-list .separator { height: 26px }");
|
||||
this.css.insert(".emoji-keyboard-list img { padding: 0.1em !important; width: 1em; height: 1em; vertical-align: -0.1em; cursor: pointer }");
|
||||
|
||||
this.css.insert(".emoji-keyboard-search { height: auto; padding: 4px 10px 8px; background-color: #292f33; border-radius: 1px 1px 0 0 }");
|
||||
this.css.insert(".emoji-keyboard-search input { width: 100%; border-radius: 1px; }");
|
||||
|
||||
this.css.insert(".emoji-keyboard-skintones { height: 1.3em; text-align: center; background-color: #292f33; border-radius: 0 0 1px 1px }");
|
||||
this.css.insert(".emoji-keyboard-skintones div { width: 0.8em; height: 0.8em; margin: 0.25em 0.1em; border-radius: 50%; display: inline-block; box-sizing: border-box; cursor: pointer }");
|
||||
this.css.insert(".emoji-keyboard-skintones .sel { border: 2px solid rgba(0, 0, 0, 0.35); box-shadow: 0 0 2px 0 rgba(255, 255, 255, 0.65), 0 0 1px 0 rgba(255, 255, 255, 0.4) inset }");
|
||||
this.css.insert(".emoji-keyboard-skintones :hover { border: 2px solid rgba(0, 0, 0, 0.25); box-shadow: 0 0 1px 0 rgba(255, 255, 255, 0.65), 0 0 1px 0 rgba(255, 255, 255, 0.25) inset }");
|
||||
|
||||
this.css.insert(".js-compose-text { font-family: \"Twitter Color Emoji\", Helvetica, Arial, Verdana, sans-serif; }");
|
||||
|
||||
// layout
|
||||
|
||||
var buttonHTML = '<button class="needsclick btn btn-on-blue txt-left padding-v--9 emoji-keyboard-popup-btn"><i class="icon icon-heart"></i></button>';
|
||||
@@ -47,10 +54,10 @@ enabled(){
|
||||
this.prevComposeMustache = TD.mustaches["compose/docked_compose.mustache"];
|
||||
TD.mustaches["compose/docked_compose.mustache"] = TD.mustaches["compose/docked_compose.mustache"].replace('<div class="cf margin-t--12 margin-b--30">', '<div class="cf margin-t--12 margin-b--30">'+buttonHTML);
|
||||
|
||||
var dockedComposePanel = $(".js-docked-compose");
|
||||
var maybeDockedComposePanel = $(".js-docked-compose");
|
||||
|
||||
if (dockedComposePanel.length){
|
||||
dockedComposePanel.find(".cf.margin-t--12.margin-b--30").first().append(buttonHTML);
|
||||
if (maybeDockedComposePanel.length){
|
||||
maybeDockedComposePanel.find(".cf.margin-t--12.margin-b--30").first().append(buttonHTML);
|
||||
}
|
||||
|
||||
// keyboard generation
|
||||
@@ -58,7 +65,9 @@ enabled(){
|
||||
this.currentKeyboard = null;
|
||||
this.currentSpanner = null;
|
||||
|
||||
var hideKeyboard = () => {
|
||||
var wasSearchFocused = false;
|
||||
|
||||
var hideKeyboard = (refocus) => {
|
||||
$(this.currentKeyboard).remove();
|
||||
this.currentKeyboard = null;
|
||||
|
||||
@@ -70,12 +79,15 @@ enabled(){
|
||||
this.composePanelScroller.trigger("scroll");
|
||||
|
||||
$(".emoji-keyboard-popup-btn").removeClass("is-selected");
|
||||
$(".js-compose-text").first().focus();
|
||||
|
||||
if (refocus){
|
||||
$(".js-compose-text", ".js-docked-compose").focus();
|
||||
}
|
||||
};
|
||||
|
||||
var generateEmojiHTML = skinTone => {
|
||||
let index = 0;
|
||||
let html = [ "<p style='font-size:13px;color:#444;margin:4px;text-align:center'>Please, note that most emoji will not show up properly in the text box above, but they will display in the tweet.</p>" ];
|
||||
let html = [ "<p style='font-size:13px;color:#444;margin:4px;text-align:center'>Please, note that some emoji may not show up correctly in the text box above, but they will display in the tweet.</p>" ];
|
||||
|
||||
for(let array of [ this.emojiData1, this.emojiData2[skinTone], this.emojiData3 ]){
|
||||
for(let emoji of array){
|
||||
@@ -124,7 +136,7 @@ enabled(){
|
||||
updateFilters();
|
||||
};
|
||||
|
||||
this.generateKeyboard = (input, left, top) => {
|
||||
this.generateKeyboard = (left, top) => {
|
||||
var outer = document.createElement("div");
|
||||
outer.classList.add("emoji-keyboard");
|
||||
outer.style.left = left+"px";
|
||||
@@ -134,18 +146,10 @@ enabled(){
|
||||
keyboard.classList.add("emoji-keyboard-list");
|
||||
|
||||
keyboard.addEventListener("click", function(e){
|
||||
if (e.target.tagName === "IMG"){
|
||||
var val = input.val();
|
||||
var inserted = e.target.getAttribute("alt");
|
||||
var posStart = input[0].selectionStart;
|
||||
var posEnd = input[0].selectionEnd;
|
||||
|
||||
input.val(val.slice(0, posStart)+inserted+val.slice(posStart));
|
||||
input.trigger("change");
|
||||
input.focus();
|
||||
|
||||
input[0].selectionStart = posStart+inserted.length;
|
||||
input[0].selectionEnd = posEnd+inserted.length;
|
||||
let ele = e.target;
|
||||
|
||||
if (ele.tagName === "IMG"){
|
||||
insertEmoji(ele.getAttribute("src"), ele.getAttribute("alt"));
|
||||
}
|
||||
|
||||
e.stopPropagation();
|
||||
@@ -179,16 +183,36 @@ enabled(){
|
||||
var searchInput = search.children[0];
|
||||
searchInput.focus();
|
||||
|
||||
wasSearchFocused = false;
|
||||
|
||||
searchInput.addEventListener("input", function(e){
|
||||
me.currentKeywords = e.target.value.split(" ").filter(kw => kw.length > 0).map(kw => kw.toLowerCase());
|
||||
updateFilters();
|
||||
|
||||
wasSearchFocused = $(this).val().length > 0;
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
searchInput.addEventListener("focus", function(){
|
||||
searchInput.addEventListener("keydown", function(e){
|
||||
if (e.keyCode === 13 && $(this).val().length){ // enter
|
||||
let ele = $(".emoji-keyboard-list").children("img").filter(":visible").first();
|
||||
|
||||
if (ele.length > 0){
|
||||
insertEmoji(ele[0].getAttribute("src"), ele[0].getAttribute("alt"));
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
searchInput.addEventListener("click", function(){
|
||||
$(this).select();
|
||||
});
|
||||
|
||||
searchInput.addEventListener("focus", function(){
|
||||
wasSearchFocused = true;
|
||||
});
|
||||
|
||||
this.currentKeyboard = outer;
|
||||
selectSkinTone(this.selectedSkinTone);
|
||||
|
||||
@@ -204,15 +228,36 @@ enabled(){
|
||||
return button.offset().top+button.outerHeight()+me.composePanelScroller.scrollTop()+8;
|
||||
};
|
||||
|
||||
// event handlers
|
||||
var insertEmoji = (src, alt) => {
|
||||
let input = $(".js-compose-text", ".js-docked-compose");
|
||||
|
||||
let val = input.val();
|
||||
let posStart = input[0].selectionStart;
|
||||
let posEnd = input[0].selectionEnd;
|
||||
|
||||
input.val(val.slice(0, posStart)+alt+val.slice(posEnd));
|
||||
input.trigger("change");
|
||||
|
||||
input[0].selectionStart = posStart+alt.length;
|
||||
input[0].selectionEnd = posStart+alt.length;
|
||||
|
||||
if (wasSearchFocused){
|
||||
$(".emoji-keyboard-search").children("input").focus();
|
||||
}
|
||||
else{
|
||||
input.focus();
|
||||
}
|
||||
};
|
||||
|
||||
// general event handlers
|
||||
|
||||
this.emojiKeyboardButtonClickEvent = function(e){
|
||||
if (me.currentKeyboard){
|
||||
hideKeyboard();
|
||||
$(this).blur();
|
||||
hideKeyboard(true);
|
||||
}
|
||||
else{
|
||||
me.generateKeyboard($(".js-compose-text").first(), $(this).offset().left, getKeyboardTop());
|
||||
me.generateKeyboard($(this).offset().left, getKeyboardTop());
|
||||
$(this).addClass("is-selected");
|
||||
}
|
||||
|
||||
@@ -225,19 +270,23 @@ enabled(){
|
||||
}
|
||||
};
|
||||
|
||||
this.composeInputFocusEvent = function(e){
|
||||
wasSearchFocused = false;
|
||||
};
|
||||
|
||||
this.composerSendingEvent = function(e){
|
||||
hideKeyboard();
|
||||
};
|
||||
|
||||
this.documentClickEvent = function(e){
|
||||
if (me.currentKeyboard && !e.target.classList.contains("js-compose-text")){
|
||||
if (me.currentKeyboard && $(e.target).closest(".compose-text-container").length === 0){
|
||||
hideKeyboard();
|
||||
}
|
||||
};
|
||||
|
||||
this.documentKeyEvent = function(e){
|
||||
if (me.currentKeyboard && e.keyCode === 27){ // escape
|
||||
hideKeyboard();
|
||||
hideKeyboard(true);
|
||||
e.stopPropagation();
|
||||
}
|
||||
};
|
||||
@@ -246,7 +295,7 @@ enabled(){
|
||||
if (me.currentKeyboard){
|
||||
me.currentKeyboard.style.top = getKeyboardTop()+"px";
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
ready(){
|
||||
@@ -256,6 +305,7 @@ ready(){
|
||||
this.composePanelScroller.on("scroll", this.composerScrollEvent);
|
||||
|
||||
$(".emoji-keyboard-popup-btn").on("click", this.emojiKeyboardButtonClickEvent);
|
||||
$(".js-compose-text", ".js-docked-compose").on("focus", this.composeInputFocusEvent);
|
||||
$(document).on("click", this.documentClickEvent);
|
||||
$(document).on("keydown", this.documentKeyEvent);
|
||||
$(document).on("uiComposeImageAdded", this.uploadFilesEvent);
|
||||
@@ -280,12 +330,14 @@ ready(){
|
||||
|
||||
// declaration inserters
|
||||
|
||||
let mapUnicode = pt => convUnicode(parseInt(pt, 16));
|
||||
|
||||
let addDeclaration1 = decl => {
|
||||
this.emojiData1.push(decl.split(" ").map(pt => convUnicode(parseInt(pt, 16))).join(""));
|
||||
this.emojiData1.push(decl.split(" ").map(mapUnicode).join(""));
|
||||
};
|
||||
|
||||
let addDeclaration2 = (tone, decl) => {
|
||||
let gen = decl.split(" ").map(pt => convUnicode(parseInt(pt, 16))).join("");
|
||||
let gen = decl.split(" ").map(mapUnicode).join("");
|
||||
|
||||
if (tone === null){
|
||||
for(let skinTone of this.skinToneList){
|
||||
@@ -298,7 +350,7 @@ ready(){
|
||||
};
|
||||
|
||||
let addDeclaration3 = decl => {
|
||||
this.emojiData3.push(decl.split(" ").map(pt => convUnicode(parseInt(pt, 16))).join(""));
|
||||
this.emojiData3.push(decl.split(" ").map(mapUnicode).join(""));
|
||||
};
|
||||
|
||||
// line reading
|
||||
@@ -377,6 +429,7 @@ disabled(){
|
||||
$(".emoji-keyboard-popup-btn").off("click", this.emojiKeyboardButtonClickEvent);
|
||||
$(".emoji-keyboard-popup-btn").remove();
|
||||
|
||||
$(".js-compose-text", ".js-docked-compose").off("focus", this.composeInputFocusEvent);
|
||||
$(document).off("click", this.documentClickEvent);
|
||||
$(document).off("keydown", this.documentKeyEvent);
|
||||
$(document).off("uiComposeImageAdded", this.uploadFilesEvent);
|
||||
|
@@ -6,9 +6,7 @@ enabled(){
|
||||
this.lastSelectedAccount = null;
|
||||
|
||||
this.uiComposeTweetEvent = (e, data) => {
|
||||
if (data.type !== "reply" || data.popFromInline || !("element" in data)){
|
||||
return;
|
||||
}
|
||||
return if data.type !== "reply" || data.popFromInline || !("element" in data);
|
||||
|
||||
var query;
|
||||
|
||||
@@ -83,9 +81,7 @@ enabled(){
|
||||
break;
|
||||
|
||||
case "#last":
|
||||
if (this.lastSelectedAccount === null){
|
||||
return;
|
||||
}
|
||||
return if this.lastSelectedAccount === null;
|
||||
|
||||
identifier = this.lastSelectedAccount;
|
||||
break;
|
||||
|
@@ -8,7 +8,7 @@ Templates
|
||||
chylex
|
||||
|
||||
[version]
|
||||
1.0.1
|
||||
1.0.2
|
||||
|
||||
[website]
|
||||
https://tweetduck.chylex.com
|
||||
|
@@ -177,7 +177,7 @@ enabled(){
|
||||
|
||||
var useTemplate = (contents, append) => {
|
||||
let ele = $(".js-compose-text");
|
||||
if (ele.length === 0)return;
|
||||
return if ele.length === 0;
|
||||
|
||||
let value = append ? ele.val()+contents : contents;
|
||||
let prevLength = value.length;
|
||||
@@ -267,7 +267,7 @@ enabled(){
|
||||
<ul></ul>
|
||||
|
||||
<div class="templates-modal-bottom">
|
||||
<button data-action="close" class="btn" style="background-color:#d2d2d2;border-color:#ccd0d2"><i class="icon icon-close icon-small padding-rs"></i><span class="label">Close</span></button>
|
||||
<button data-action="close" class="btn"><i class="icon icon-close icon-small padding-rs"></i><span class="label">Close</span></button>
|
||||
<button data-action="new-template" class="btn btn-positive"><i class="icon icon-plus icon-small padding-rs"></i><span class="label">New Template</span></button>
|
||||
</div>
|
||||
</div>
|
||||
|
23
Resources/PostBuild.ps1
Normal file
23
Resources/PostBuild.ps1
Normal file
@@ -0,0 +1,23 @@
|
||||
Param([Parameter(Mandatory = $True, Position = 1)][string] $dir)
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
Set-Location $dir
|
||||
|
||||
function Rewrite-File{
|
||||
[CmdletBinding()]
|
||||
Param([Parameter(Mandatory = $True, ValueFromPipeline = $True)][array] $lines, [Parameter(Mandatory = $True, Position = 1)] $file)
|
||||
|
||||
$lines | Where { $_ -ne '' } | Set-Content -Path $file.FullName
|
||||
Write-Host "Processed" $file.FullName.Substring($dir.Length)
|
||||
}
|
||||
|
||||
ForEach($file in Get-ChildItem -Include *.js -Recurse){
|
||||
$lines = Get-Content -Path $file.FullName
|
||||
$lines = ($lines | % { $_.TrimStart() }) -Replace '^(.*?)((?<=^|[;{}()])\s?//(?:\s.*|$))?$', '$1' -Replace '(?<!\w)return(\s.*?)? if (.*?);', 'if ($2)return$1;'
|
||||
,$lines | Rewrite-File $file
|
||||
}
|
||||
|
||||
ForEach($file in Get-ChildItem -Include *.html -Recurse){
|
||||
$lines = Get-Content -Path $file.FullName
|
||||
,($lines | % { $_.TrimStart() }) | Rewrite-File $file
|
||||
}
|
@@ -1,5 +1,4 @@
|
||||
using CefSharp;
|
||||
using CefSharp.WinForms;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
@@ -21,28 +20,16 @@ namespace TweetDuck.Resources{
|
||||
}
|
||||
}
|
||||
|
||||
public static void ExecuteFile(ChromiumWebBrowser browser, string file){
|
||||
ExecuteScript(browser, LoadResource(file), GetRootIdentifier(file));
|
||||
}
|
||||
|
||||
public static void ExecuteFile(IFrame frame, string file){
|
||||
ExecuteScript(frame, LoadResource(file), GetRootIdentifier(file));
|
||||
}
|
||||
|
||||
public static void ExecuteScript(ChromiumWebBrowser browser, string script, string identifier){
|
||||
if (script == null)return;
|
||||
|
||||
using(IFrame frame = browser.GetMainFrame()){
|
||||
public static void ExecuteScript(IFrame frame, string script, string identifier){
|
||||
if (script != null){
|
||||
frame.ExecuteJavaScriptAsync(script, UrlPrefix+identifier, 1);
|
||||
}
|
||||
}
|
||||
|
||||
public static void ExecuteScript(IFrame frame, string script, string identifier){
|
||||
if (script == null)return;
|
||||
|
||||
frame.ExecuteJavaScriptAsync(script, UrlPrefix+identifier, 1);
|
||||
}
|
||||
|
||||
public static string GetRootIdentifier(string file){
|
||||
return "root:"+Path.GetFileNameWithoutExtension(file);
|
||||
}
|
||||
|
@@ -112,9 +112,7 @@
|
||||
};
|
||||
|
||||
let checkTweetCache = (set, id) => {
|
||||
if (set.has(id)){
|
||||
return true;
|
||||
}
|
||||
return true if set.has(id);
|
||||
|
||||
if (set.size > 50){
|
||||
set.clear();
|
||||
@@ -130,18 +128,17 @@
|
||||
|
||||
return function(column, tweet){
|
||||
if (tweet instanceof TD.services.TwitterConversation || tweet instanceof TD.services.TwitterConversationMessageEvent){
|
||||
if (checkTweetCache(recentMessages, tweet.id)){
|
||||
return;
|
||||
}
|
||||
return if checkTweetCache(recentMessages, tweet.id);
|
||||
}
|
||||
else if (checkTweetCache(recentTweets, tweet.id)){
|
||||
return;
|
||||
else{
|
||||
return if checkTweetCache(recentTweets, tweet.id);
|
||||
}
|
||||
|
||||
startRecentTweetTimer();
|
||||
|
||||
if (column.model.getHasNotification()){
|
||||
let previews = $TDX.notificationMediaPreviews;
|
||||
let sensitive = (tweet.getRelatedTweet() && tweet.getRelatedTweet().possiblySensitive || (tweet.quotedTweet && tweet.quotedTweet.possiblySensitive));
|
||||
let previews = $TDX.notificationMediaPreviews && (!sensitive || TD.settings.getDisplaySensitiveMedia());
|
||||
|
||||
let html = $(tweet.render({
|
||||
withFooter: false,
|
||||
@@ -184,18 +181,27 @@
|
||||
|
||||
if (type === "follow"){
|
||||
html.find(".js-user-actions-menu").parent().remove();
|
||||
html.find(".account-bio").removeClass("padding-t--5").css("padding-top", "2px");
|
||||
}
|
||||
else if (type.includes("list_member")){
|
||||
html.find(".activity-header").first().css("margin-top", "2px");
|
||||
html.find(".activity-header").css("margin-top", "2px");
|
||||
html.find(".avatar").first().css("margin-bottom", "0");
|
||||
}
|
||||
|
||||
|
||||
if (sensitive){
|
||||
html.find(".media-badge").each(function(){
|
||||
$(this)[0].lastChild.textContent += " (possibly sensitive)";
|
||||
});
|
||||
}
|
||||
|
||||
let source = tweet.getRelatedTweet();
|
||||
let duration = source ? source.text.length+(source.quotedTweet ? source.quotedTweet.text.length : 0) : tweet.text.length;
|
||||
|
||||
let chirpId = source ? source.id : "";
|
||||
let tweetUrl = source ? source.getChirpURL() : "";
|
||||
let quoteUrl = source && source.quotedTweet ? source.quotedTweet.getChirpURL() : "";
|
||||
|
||||
$TD.onTweetPopup(columnTypes[column.getColumnType()] || "", html.html(), duration, tweetUrl, quoteUrl);
|
||||
$TD.onTweetPopup(column.model.privateState.apiid, chirpId, columnTypes[column.getColumnType()] || "", html.html(), duration, tweetUrl, quoteUrl);
|
||||
}
|
||||
|
||||
if (column.model.getHasSound()){
|
||||
@@ -204,6 +210,53 @@
|
||||
};
|
||||
})();
|
||||
|
||||
//
|
||||
// Function: Shows tweet detail, used in notification context menu.
|
||||
//
|
||||
(function(){
|
||||
var showTweetDetailInternal = function(column, chirp){
|
||||
TD.ui.updates.showDetailView(column, chirp, column.findChirp(chirp) || chirp);
|
||||
TD.controller.columnManager.showColumn(column.model.privateState.key);
|
||||
|
||||
$(document).trigger("uiGridClearSelection");
|
||||
};
|
||||
|
||||
window.TDGF_showTweetDetail = function(columnId, chirpId, fallbackUrl){
|
||||
if (!TD.ready){
|
||||
onAppReady.push(function(){
|
||||
window.TDGF_showTweetDetail(columnId, chirpId, fallbackUrl);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let column = TD.controller.columnManager.getByApiid(columnId);
|
||||
|
||||
if (!column){
|
||||
if (confirm("error|The column which contained the tweet no longer exists. Would you like to open the tweet in your browser instead?")){
|
||||
$TD.openBrowser(fallbackUrl);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let chirp = column.findMostInterestingChirp(chirpId);
|
||||
|
||||
if (chirp){
|
||||
showTweetDetailInternal(column, chirp);
|
||||
}
|
||||
else{
|
||||
TD.controller.clients.getPreferredClient().show(chirpId, function(chirp){
|
||||
showTweetDetailInternal(column, chirp);
|
||||
}, function(){
|
||||
if (confirm("error|Could not retrieve the requested tweet. Would you like to open the tweet in your browser instead?")){
|
||||
$TD.openBrowser(fallbackUrl);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
//
|
||||
// Function: Retrieves the tags to be put into <head> for notification HTML code.
|
||||
//
|
||||
@@ -217,7 +270,7 @@
|
||||
tags.push("<style type='text/css'>");
|
||||
tags.push("body { background: "+getClassStyleProperty("column", "background-color")+" }"); // set background color
|
||||
tags.push("a[data-full-url] { word-break: break-all }"); // break long urls
|
||||
tags.push(".txt-base-smallest .badge-verified:before { height: 13px !important }"); // fix cut off badge icon
|
||||
tags.push(".txt-base-smallest .badge-verified:before { width: 13px !important; height: 13px !important; background-position: -223px -98px !important; }"); // fix cut off badge icon
|
||||
tags.push(".activity-header { align-items: center !important; margin-bottom: 4px }"); // tweak alignment of avatar and text in notifications
|
||||
tags.push(".activity-header .tweet-timestamp { line-height: unset }"); // fix timestamp position in notifications
|
||||
tags.push("</style>");
|
||||
@@ -273,7 +326,7 @@
|
||||
$("[data-action='settings-menu']").click(function(){
|
||||
setTimeout(function(){
|
||||
let menu = $(".js-dropdown-content").children("ul").first();
|
||||
if (menu.length === 0)return;
|
||||
return if menu.length === 0;
|
||||
|
||||
menu.children(".drp-h-divider").last().before('<li class="is-selectable" data-std><a href="#" data-action="tweetduck">TweetDuck</a></li>');
|
||||
|
||||
@@ -303,15 +356,11 @@
|
||||
var prevMouseX = -1, prevMouseY = -1;
|
||||
var tooltipTimer, tooltipDisplayed;
|
||||
|
||||
$(document.body).delegate("a[data-full-url]", "mouseenter mouseleave mousemove", function(e){
|
||||
var me = $(this);
|
||||
|
||||
if (e.type === "mouseenter"){
|
||||
$(document.body).delegate("a[data-full-url]", {
|
||||
mouseenter: function(){
|
||||
let me = $(this);
|
||||
let text = me.text();
|
||||
|
||||
if (text.charCodeAt(text.length-1) !== 8230){ // horizontal ellipsis
|
||||
return;
|
||||
}
|
||||
return if text.charCodeAt(text.length-1) !== 8230; // horizontal ellipsis
|
||||
|
||||
if ($TDX.expandLinksOnHover){
|
||||
tooltipTimer = window.setTimeout(function(){
|
||||
@@ -330,14 +379,13 @@
|
||||
tooltipDisplayed = true;
|
||||
}, 400);
|
||||
}
|
||||
}
|
||||
else if (e.type === "mouseleave"){
|
||||
if ($TDX.expandLinksOnHover){
|
||||
let prevText = me.attr("td-prev-text");
|
||||
|
||||
if (prevText){
|
||||
me.text(prevText);
|
||||
}
|
||||
},
|
||||
|
||||
mouseleave: function(){
|
||||
let me = $(this);
|
||||
|
||||
if (me[0].hasAttribute("td-prev-text")){
|
||||
me.text(me.attr("td-prev-text"));
|
||||
}
|
||||
|
||||
window.clearTimeout(tooltipTimer);
|
||||
@@ -346,10 +394,11 @@
|
||||
tooltipDisplayed = false;
|
||||
$TD.displayTooltip(null, false);
|
||||
}
|
||||
}
|
||||
else if (e.type === "mousemove"){
|
||||
},
|
||||
|
||||
mousemove: function(e){
|
||||
if (tooltipDisplayed && (prevMouseX !== e.clientX || prevMouseY !== e.clientY)){
|
||||
$TD.displayTooltip(me.attr("data-full-url"), false);
|
||||
$TD.displayTooltip($(this).attr("data-full-url"), false);
|
||||
prevMouseX = e.clientX;
|
||||
prevMouseY = e.clientY;
|
||||
}
|
||||
@@ -358,33 +407,77 @@
|
||||
})();
|
||||
|
||||
//
|
||||
// Block: Allow bypassing of t.co and include media previews in context menus.
|
||||
// Block: Bypass t.co when clicking links and media.
|
||||
//
|
||||
$(document.body).delegate("a", "contextmenu", function(){
|
||||
$TD.setLastRightClickedLink($(this).attr("data-full-url") || "");
|
||||
$(document.body).delegate("a[data-full-url]", "click", function(e){
|
||||
$TD.openBrowser($(this).attr("data-full-url"));
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
$(document.body).delegate("a.js-media-image-link", "contextmenu", function(){
|
||||
if (ensurePropertyExists(TD, "services", "TwitterUser", "prototype", "fromJSONObject")){
|
||||
let prevFunc = TD.services.TwitterUser.prototype.fromJSONObject;
|
||||
|
||||
TD.services.TwitterUser.prototype.fromJSONObject = function(){
|
||||
let obj = prevFunc.apply(this, arguments);
|
||||
let e = arguments[0].entities;
|
||||
|
||||
if (e && e.url && e.url.urls && e.url.urls.length && e.url.urls[0].expanded_url){
|
||||
obj.url = e.url.urls[0].expanded_url;
|
||||
}
|
||||
|
||||
return obj;
|
||||
};
|
||||
}
|
||||
|
||||
if (ensurePropertyExists(TD, "services", "TwitterMedia", "prototype", "fromMediaEntity")){
|
||||
let prevFunc = TD.services.TwitterMedia.prototype.fromMediaEntity;
|
||||
|
||||
TD.services.TwitterMedia.prototype.fromMediaEntity = function(){
|
||||
let obj = prevFunc.apply(this, arguments);
|
||||
let e = arguments[0];
|
||||
|
||||
if (e.expanded_url){
|
||||
if (obj.url === obj.shortUrl){
|
||||
obj.shortUrl = e.expanded_url;
|
||||
}
|
||||
|
||||
obj.url = e.expanded_url;
|
||||
}
|
||||
|
||||
return obj;
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
// Block: Include additional information in context menus.
|
||||
//
|
||||
$(document.body).delegate("a", "contextmenu", function(){
|
||||
let me = $(this)[0];
|
||||
|
||||
if (me.firstElementChild){
|
||||
$TD.setLastRightClickedImage(me.firstElementChild.getAttribute("src"));
|
||||
if (me.classList.contains("js-media-image-link") && highlightedTweetObj){
|
||||
let media = (highlightedTweetObj.quotedTweet || highlightedTweetObj).getMedia().find(media => media.mediaId === me.getAttribute("data-media-entity-id"));
|
||||
|
||||
if ((media.isVideo && media.service === "twitter") || media.isAnimatedGif){
|
||||
$TD.setLastRightClickInfo("video", media.chooseVideoVariant().url);
|
||||
}
|
||||
else{
|
||||
$TD.setLastRightClickInfo("image", media.large());
|
||||
}
|
||||
}
|
||||
else if (me.classList.contains("js-gif-play")){
|
||||
$TD.setLastRightClickInfo("video", $(this).closest(".js-media-gif-container").find("video").attr("src"));
|
||||
}
|
||||
else{
|
||||
$TD.setLastRightClickedImage(me.style.backgroundImage.replace(/url\(['"]?(.*?)['"]?\)/, "$1"));
|
||||
$TD.setLastRightClickInfo("link", me.getAttribute("data-full-url"));
|
||||
}
|
||||
});
|
||||
|
||||
//
|
||||
// Block: Hook into the notification sound effect.
|
||||
//
|
||||
(function(){
|
||||
let soundEle = document.getElementById("update-sound");
|
||||
|
||||
soundEle.play = prependToFunction(soundEle.play, function(){
|
||||
return $TDX.muteNotifications || $TDX.hasCustomNotificationSound;
|
||||
});
|
||||
})();
|
||||
HTMLAudioElement.prototype.play = prependToFunction(HTMLAudioElement.prototype.play, function(){
|
||||
return $TDX.muteNotifications || $TDX.hasCustomNotificationSound;
|
||||
});
|
||||
|
||||
//
|
||||
// Block: Update highlighted column and tweet for context menu and other functionality.
|
||||
@@ -398,53 +491,54 @@
|
||||
return !!highlightedColumnObj;
|
||||
};
|
||||
|
||||
var updateHighlightedTweet = function(ele, obj, link, embeddedLink, author, imageList){
|
||||
var updateHighlightedTweet = function(ele, obj, tweetUrl, quoteUrl, authors, imageList){
|
||||
highlightedTweetEle = ele;
|
||||
highlightedTweetObj = obj;
|
||||
|
||||
if (lastTweet !== link){
|
||||
$TD.setLastHighlightedTweet(link, embeddedLink, author, imageList);
|
||||
lastTweet = link;
|
||||
if (lastTweet !== tweetUrl){
|
||||
$TD.setLastHighlightedTweet(tweetUrl, quoteUrl, authors, imageList);
|
||||
lastTweet = tweetUrl;
|
||||
}
|
||||
};
|
||||
|
||||
app.delegate("section.js-column", "mouseenter mouseleave", function(e){
|
||||
if (e.type === "mouseenter"){
|
||||
var processMedia = function(media){
|
||||
return media.filter(item => !item.isAnimatedGif).map(item => item.entity.media_url_https+":small").join(";");
|
||||
};
|
||||
|
||||
app.delegate("section.js-column", {
|
||||
mouseenter: function(){
|
||||
if (!highlightedColumnObj){
|
||||
updateHighlightedColumn($(this));
|
||||
}
|
||||
}
|
||||
else if (e.type === "mouseleave"){
|
||||
},
|
||||
|
||||
mouseleave: function(){
|
||||
updateHighlightedColumn(null);
|
||||
}
|
||||
});
|
||||
|
||||
app.delegate("article.js-stream-item", "mouseenter mouseleave", function(e){
|
||||
if (e.type === "mouseenter"){
|
||||
app.delegate("article.js-stream-item", {
|
||||
mouseenter: function(){
|
||||
let me = $(this);
|
||||
|
||||
if (!me[0].hasAttribute("data-account-key") || (!highlightedColumnObj && !updateHighlightedColumn(me.closest("section.js-column")))){
|
||||
return;
|
||||
}
|
||||
return if !me[0].hasAttribute("data-account-key") || (!highlightedColumnObj && !updateHighlightedColumn(me.closest("section.js-column")));
|
||||
|
||||
let tweet = highlightedColumnObj.findChirp(me.attr("data-tweet-id")) || highlightedColumnObj.findChirp(me.attr("data-key"));
|
||||
return if !tweet;
|
||||
|
||||
if (tweet){
|
||||
if (tweet.chirpType === TD.services.ChirpBase.TWEET){
|
||||
let link = tweet.getChirpURL();
|
||||
let embedded = tweet.quotedTweet ? tweet.quotedTweet.getChirpURL() : "";
|
||||
let username = tweet.getMainUser().screenName;
|
||||
let images = tweet.hasImage() ? tweet.getMedia().filter(item => !item.isAnimatedGif).map(item => item.entity.media_url_https+":small").join(";") : "";
|
||||
// TODO maybe handle embedded images too?
|
||||
|
||||
updateHighlightedTweet(me, tweet, link || "", embedded || "", username, images);
|
||||
}
|
||||
else{
|
||||
updateHighlightedTweet(me, tweet, "", "", "", "");
|
||||
}
|
||||
if (tweet.chirpType === TD.services.ChirpBase.TWEET){
|
||||
let tweetUrl = tweet.getChirpURL();
|
||||
let quoteUrl = tweet.quotedTweet ? tweet.quotedTweet.getChirpURL() : "";
|
||||
let authors = tweet.quotedTweet ? [ tweet.getMainUser().screenName, tweet.quotedTweet.getMainUser().screenName ].join(";") : tweet.getMainUser().screenName;
|
||||
let imageList = tweet.quotedTweet ? processMedia(tweet.quotedTweet.getMedia()) : tweet.hasImage() ? processMedia(tweet.getMedia()) : "";
|
||||
|
||||
updateHighlightedTweet(me, tweet, tweetUrl || "", quoteUrl || "", authors, imageList);
|
||||
}
|
||||
}
|
||||
else if (e.type === "mouseleave"){
|
||||
else{
|
||||
updateHighlightedTweet(me, tweet, "", "", "", "");
|
||||
}
|
||||
},
|
||||
|
||||
mouseleave: function(){
|
||||
updateHighlightedTweet(null, null, "", "", "", "");
|
||||
}
|
||||
});
|
||||
@@ -526,13 +620,11 @@
|
||||
onAppReady.push(function(){
|
||||
var uploader = $._data(document, "events")["uiComposeAddImageClick"][0].handler.context;
|
||||
|
||||
app.delegate(".js-compose-text,.js-reply-tweetbox", "paste", function(e){
|
||||
app.delegate(".js-compose-text,.js-reply-tweetbox,.td-detect-image-paste", "paste", function(e){
|
||||
for(let item of e.originalEvent.clipboardData.items){
|
||||
if (item.type.startsWith("image/")){
|
||||
if (!$(this).closest(".rpl").find(".js-reply-popout").click().length){ // popout direct messages
|
||||
if ($(".js-add-image-button").is(".is-disabled")){ // tweetdeck does not check upload count properly
|
||||
return;
|
||||
}
|
||||
return if $(".js-add-image-button").is(".is-disabled"); // tweetdeck does not check upload count properly
|
||||
}
|
||||
|
||||
uploader.addFilesToUpload([ item.getAsFile() ]);
|
||||
@@ -631,9 +723,7 @@
|
||||
}
|
||||
});
|
||||
|
||||
if (!ensurePropertyExists(TD, "vo", "Column", "prototype", "clear")){
|
||||
return;
|
||||
}
|
||||
return if !ensurePropertyExists(TD, "vo", "Column", "prototype", "clear");
|
||||
|
||||
TD.vo.Column.prototype.clear = prependToFunction(TD.vo.Column.prototype.clear, function(){
|
||||
window.setTimeout(resetActiveFocus, 0); // unfocuses the Clear button, otherwise it steals keyboard input
|
||||
@@ -660,9 +750,7 @@
|
||||
|
||||
$(".js-drawer[data-drawer='compose']").delegate(".js-account-list > .js-account-item", "click", onAccountClick);
|
||||
|
||||
if (!ensurePropertyExists(TD, "components", "AccountSelector", "prototype", "refreshPostingAccounts")){
|
||||
return;
|
||||
}
|
||||
return if !ensurePropertyExists(TD, "components", "AccountSelector", "prototype", "refreshPostingAccounts");
|
||||
|
||||
TD.components.AccountSelector.prototype.refreshPostingAccounts = appendToFunction(TD.components.AccountSelector.prototype.refreshPostingAccounts, function(){
|
||||
if (!this.$node.attr("td-account-selector-hook")){
|
||||
@@ -718,9 +806,10 @@
|
||||
|
||||
addRule(".column-nav-link .attribution { position: absolute; }"); // fix cut off account names
|
||||
addRule(".txt-base-smallest .sprite-verified-mini { width: 13px !important; height: 13px !important; background-position: -223px -99px !important; }"); // fix cut off badge icon when zoomed in
|
||||
addRule(".txt-base-smallest .badge-verified:before { width: 13px !important; height: 13px !important; background-position: -223px -98px !important; }"); // fix cut off badge icon in notifications
|
||||
|
||||
addRule(".btn, .mdl, .mdl-content, .app-search-fake, .app-search-input, .popover, .lst-modal, .media-item, .tooltip-inner { border-radius: 1px !important; }"); // square-ify buttons, inputs, dialogs, menus, media previews
|
||||
addRule(".dropdown-menu, .list-item-last, .quoted-tweet { border-radius: 0 !important; }"); // square-ify dropdowns, quoted tweets, and account selectors
|
||||
addRule(".btn, .mdl, .mdl-content, .app-search-fake, .app-search-input, .popover, .lst-modal, .media-item, .media-preview, .tooltip-inner { border-radius: 1px !important; }"); // square-ify buttons, dialogs, menus, media previews
|
||||
addRule(".compose-text-container, .dropdown-menu, .list-item-last, .quoted-tweet, .input-group-button, input, textarea, select { border-radius: 0 !important; }"); // square-ify dropdowns, inputs, quoted tweets, and account selectors
|
||||
addRule(".prf-header { border-radius: 0; }"); // fix user account header border
|
||||
|
||||
addRule(".accs li, .accs img { border-radius: 0 !important; }"); // square-ify retweet account selector
|
||||
@@ -749,6 +838,7 @@
|
||||
|
||||
addRule(".activity-header { align-items: center !important; margin-bottom: 4px; }"); // tweak alignment of avatar and text in notifications
|
||||
addRule(".activity-header .tweet-timestamp { line-height: unset; }"); // fix timestamp position in notifications
|
||||
addRule(".account-bio.padding-t--5 { padding-top: 2px !important; }"); // decrease padding on follow notifications
|
||||
|
||||
addRule("html[data-td-theme='light'] .stream-item:not(:hover) .js-user-actions-menu { color: #000; border-color: #000; opacity: 0.25; }"); // make follow notification button nicer
|
||||
addRule("html[data-td-theme='dark'] .stream-item:not(:hover) .js-user-actions-menu { color: #fff; border-color: #fff; opacity: 0.25; }"); // make follow notification button nicer
|
||||
@@ -762,6 +852,7 @@
|
||||
addRule("#td-compose-drawer-pin { margin: 17px 4px 0 0; transition: transform 0.1s ease; fill: #fff; float: right; cursor: pointer; }"); // replace 'stay open' checkbox with a pin icon
|
||||
addRule(".js-docked-compose footer { display: none; }"); // replace 'stay open' checkbox with a pin icon
|
||||
addRule(".compose-content { bottom: 0 !important; }"); // replace 'stay open' checkbox with a pin icon
|
||||
addRule(".js-docked-compose .js-drawer-close { margin: 20px 0 0 !important; }"); // fix close drawer button because twitter is fucking incompetent
|
||||
|
||||
window.TDGF_reinjectCustomCSS = function(styles){
|
||||
$("#tweetduck-custom-css").remove();
|
||||
@@ -797,7 +888,6 @@
|
||||
var playVideo = function(url){
|
||||
$('<div id="td-video-player-overlay" class="ovl" style="display:block"></div>').on("click contextmenu", function(){
|
||||
$TD.playVideo(null);
|
||||
$(this).remove();
|
||||
}).appendTo(app);
|
||||
|
||||
$TD.playVideo(url);
|
||||
@@ -830,7 +920,7 @@
|
||||
TD.components.MediaGallery.prototype._loadTweet = appendToFunction(TD.components.MediaGallery.prototype._loadTweet, function(){
|
||||
let media = this.chirp.getMedia().find(media => media.mediaId === this.clickedMediaEntityId);
|
||||
|
||||
if (media && media.isVideo && media.service !== "youtube"){
|
||||
if (media && media.isVideo && media.service === "twitter"){
|
||||
playVideo(media.chooseVideoVariant().url);
|
||||
cancelModal = true;
|
||||
}
|
||||
@@ -914,6 +1004,44 @@
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
// Block: Detect and notify about connection issues.
|
||||
//
|
||||
(function(){
|
||||
let onConnectionError = function(){
|
||||
return if $("#tweetduck-conn-issues").length;
|
||||
|
||||
let ele = $(`
|
||||
<div id="tweetduck-conn-issues" class="Layer NotificationListLayer">
|
||||
<ul class="NotificationList">
|
||||
<li class="Notification Notification--red" style="height:63px;">
|
||||
<div class="Notification-inner">
|
||||
<div class="Notification-icon"><span class="Icon Icon--medium Icon--circleError"></span></div>
|
||||
<div class="Notification-content"><div class="Notification-body">Experiencing connection issues</div></div>
|
||||
<button type="button" class="Notification-closeButton" aria-label="Close"><span class="Icon Icon--smallest Icon--close" aria-hidden="true"></span></button>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
`).appendTo(document.body);
|
||||
|
||||
ele.find("button").click(function(){
|
||||
ele.fadeOut(200);
|
||||
});
|
||||
};
|
||||
|
||||
let onConnectionFine = function(){
|
||||
let ele = $("#tweetduck-conn-issues");
|
||||
|
||||
ele.fadeOut(200, function(){
|
||||
ele.remove();
|
||||
});
|
||||
};
|
||||
|
||||
window.addEventListener("offline", onConnectionError);
|
||||
window.addEventListener("online", onConnectionFine);
|
||||
})();
|
||||
|
||||
//
|
||||
// Block: Custom reload function with memory cleanup.
|
||||
//
|
||||
@@ -929,6 +1057,12 @@
|
||||
};
|
||||
|
||||
window.TDGF_tryRunCleanup = function(){
|
||||
// no modals are visible
|
||||
return false if $("#open-modal").is(":visible") || !$(".js-modals-container").is(":empty");
|
||||
|
||||
// all columns are in a default state
|
||||
return false if $("section.js-column").is(".is-shifted-1,.is-shifted-2");
|
||||
|
||||
// all textareas are empty
|
||||
if ($("textarea").is(function(){
|
||||
return $(this).val().length > 0;
|
||||
@@ -936,16 +1070,6 @@
|
||||
return false;
|
||||
}
|
||||
|
||||
// no modals are visible
|
||||
if ($("#open-modal").is(":visible") || !$(".js-modals-container").is(":empty")){
|
||||
return false;
|
||||
}
|
||||
|
||||
// all columns are in a default state
|
||||
if ($("section.js-column").is(".is-shifted-1,.is-shifted-2")){
|
||||
return false;
|
||||
}
|
||||
|
||||
// all columns are scrolled to top
|
||||
if ($(".js-column-scroller").is(function(){
|
||||
return $(this).scrollTop() > 0;
|
||||
|
@@ -14,14 +14,16 @@
|
||||
};
|
||||
|
||||
//
|
||||
// Block: Hook into links to bypass default open function.
|
||||
// Block: Hook into links to bypass default open function and t.co.
|
||||
//
|
||||
addEventListener(links, "click", function(e){
|
||||
$TD.openBrowser(e.currentTarget.getAttribute("href"));
|
||||
let ele = e.currentTarget;
|
||||
|
||||
$TD.openBrowser(ele.hasAttribute("data-full-url") ? ele.getAttribute("data-full-url") : ele.getAttribute("href"));
|
||||
e.preventDefault();
|
||||
|
||||
if ($TDX.skipOnLinkClick){
|
||||
let parentClasses = e.currentTarget.parentNode.classList;
|
||||
let parentClasses = ele.parentNode.classList;
|
||||
|
||||
if (parentClasses.contains("js-tweet-text") || parentClasses.contains("js-quoted-tweet-text") || parentClasses.contains("js-timestamp")){
|
||||
$TD.loadNextNotification();
|
||||
@@ -33,7 +35,7 @@
|
||||
// Block: Allow bypassing of t.co in context menus.
|
||||
//
|
||||
addEventListener(links, "contextmenu", function(e){
|
||||
$TD.setLastRightClickedLink(e.currentTarget.getAttribute("data-full-url") || "");
|
||||
$TD.setLastRightClickInfo("link", e.currentTarget.getAttribute("data-full-url"));
|
||||
});
|
||||
|
||||
//
|
||||
@@ -51,13 +53,10 @@
|
||||
var me = e.currentTarget;
|
||||
|
||||
var url = me.getAttribute("data-full-url");
|
||||
if (!url)return;
|
||||
return if !url;
|
||||
|
||||
var text = me.textContent;
|
||||
|
||||
if (text.charCodeAt(text.length-1) !== 8230){ // horizontal ellipsis
|
||||
return;
|
||||
}
|
||||
return if text.charCodeAt(text.length-1) !== 8230; // horizontal ellipsis
|
||||
|
||||
if ($TDX.expandLinksOnHover){
|
||||
tooltipTimer = window.setTimeout(function(){
|
||||
@@ -79,7 +78,7 @@
|
||||
});
|
||||
|
||||
addEventListener(links, "mouseleave", function(e){
|
||||
if (!e.currentTarget.hasAttribute("data-full-url"))return;
|
||||
return if !e.currentTarget.hasAttribute("data-full-url");
|
||||
|
||||
if ($TDX.expandLinksOnHover){
|
||||
var prevText = e.currentTarget.getAttribute("td-prev-text");
|
||||
@@ -100,7 +99,7 @@
|
||||
addEventListener(links, "mousemove", function(e){
|
||||
if (tooltipDisplayed && (prevMouseX !== e.clientX || prevMouseY !== e.clientY)){
|
||||
var url = e.currentTarget.getAttribute("data-full-url");
|
||||
if (!url)return;
|
||||
return if !url;
|
||||
|
||||
$TD.displayTooltip(url, true);
|
||||
prevMouseX = e.clientX;
|
||||
@@ -112,11 +111,7 @@
|
||||
//
|
||||
// Block: Setup a skip button.
|
||||
//
|
||||
(function(){
|
||||
if (document.body.hasAttribute("td-example-notification")){
|
||||
return;
|
||||
}
|
||||
|
||||
if (!document.body.hasAttribute("td-example-notification")){
|
||||
document.body.insertAdjacentHTML("afterbegin", [
|
||||
'<svg id="td-skip" xmlns="http://www.w3.org/2000/svg" width="10" height="17" viewBox="0 0 350 600" style="position:fixed;left:30px;bottom:10px;z-index:1000">',
|
||||
'<path fill="#888" d="M0,151.656l102.208-102.22l247.777,247.775L102.208,544.986L0,442.758l145.546-145.547">',
|
||||
@@ -126,7 +121,7 @@
|
||||
document.getElementById("td-skip").addEventListener("click", function(){
|
||||
$TD.loadNextNotification();
|
||||
});
|
||||
})();
|
||||
}
|
||||
|
||||
//
|
||||
// Block: Setup a hover class on body.
|
||||
|
@@ -39,4 +39,25 @@
|
||||
};
|
||||
|
||||
setTimeout(injectCSS, 1);
|
||||
|
||||
//
|
||||
// Block: Make login page links external.
|
||||
//
|
||||
if (location.pathname === "/login"){
|
||||
document.addEventListener("DOMContentLoaded", function(){
|
||||
let openLinkExternally = function(e){
|
||||
let href = e.currentTarget.getAttribute("href");
|
||||
$TD.openBrowser(href[0] === '/' ? location.origin+href : href);
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
let links = document.getElementsByTagName("A");
|
||||
|
||||
for(let index = 0; index < links.length; index++){
|
||||
links[index].addEventListener("click", openLinkExternally);
|
||||
}
|
||||
});
|
||||
}
|
||||
})();
|
||||
|
@@ -28,8 +28,6 @@
|
||||
// Function: Creates the update notification element. Removes the old one if already exists.
|
||||
//
|
||||
var displayNotification = function(version, download, changelog){
|
||||
var outdated = version === "unsupported";
|
||||
|
||||
// styles
|
||||
var css = $("#tweetduck-update-css");
|
||||
|
||||
@@ -143,8 +141,8 @@
|
||||
display: list-item;
|
||||
}
|
||||
|
||||
#tweetduck-changelog .l2 {
|
||||
margin-left: 50px;
|
||||
#tweetduck-changelog p.l2 {
|
||||
margin-left: 50px !important;
|
||||
}
|
||||
|
||||
#tweetduck-changelog a {
|
||||
@@ -183,16 +181,7 @@
|
||||
ele.remove();
|
||||
}
|
||||
|
||||
ele = $(outdated ? `
|
||||
<div id='tweetduck-update'>
|
||||
<p class='tdu-title'>Unsupported System</p>
|
||||
<p class='tdu-info'>You will not receive updates</p>
|
||||
<div class='tdu-buttons'>
|
||||
<button class='tdu-btn-unsupported'>Read more</button>
|
||||
<button class='tdu-btn-ignore'>Dismiss</button>
|
||||
</div>
|
||||
</div>
|
||||
` : `
|
||||
ele = $(`
|
||||
<div id='tweetduck-update'>
|
||||
<p class='tdu-title'>T weetDuck Update ${version}</p>
|
||||
<p class='tdu-info tdu-showlog'>View update information</p>
|
||||
@@ -244,11 +233,7 @@
|
||||
slide();
|
||||
});
|
||||
|
||||
buttonDiv.children(".tdu-btn-unsupported").click(function(){
|
||||
$TDU.openBrowser("https://github.com/chylex/TweetDuck/wiki/Supported-Systems");
|
||||
});
|
||||
|
||||
buttonDiv.children(".tdu-btn-ignore,.tdu-btn-unsupported").click(function(){
|
||||
buttonDiv.children(".tdu-btn-ignore").click(function(){
|
||||
$TDU.onUpdateDismissed();
|
||||
slide();
|
||||
});
|
||||
@@ -283,7 +268,7 @@
|
||||
.replace(/\*\*(.*?)\*\*/g, "<strong>$1</strong>")
|
||||
.replace(/\*(.*?)\*/g, "<em>$1</em>")
|
||||
.replace(/`(.*?)`/g, "<code>$1</code>")
|
||||
.replace(/\[(.*?)\]\((.*?)\)/g, "<a href='$2'>$1</a>")
|
||||
.replace(/\[(.*?)\]\((.*?)\)/g, "<a href='$2' target='_blank'>$1</a>")
|
||||
.replace(/^([a-z0-9].*?)$/gmi, "<p>$1</p>")
|
||||
.replace(/\n\r?\n\r?/g, "<br>");
|
||||
};
|
||||
@@ -318,6 +303,5 @@
|
||||
//
|
||||
// Block: Setup global functions.
|
||||
//
|
||||
window.TDUF_displayNotification = displayNotification;
|
||||
window.TDUF_runUpdateCheck = runUpdateCheck;
|
||||
})($, $TDU);
|
||||
|
@@ -73,7 +73,6 @@
|
||||
<Compile Include="Configuration\LockManager.cs" />
|
||||
<Compile Include="Configuration\SystemConfig.cs" />
|
||||
<Compile Include="Configuration\UserConfig.cs" />
|
||||
<Compile Include="Configuration\UserConfigLegacy.cs" />
|
||||
<Compile Include="Core\Bridge\PropertyBridge.cs" />
|
||||
<Compile Include="Core\Controls\ControlExtensions.cs" />
|
||||
<Compile Include="Core\Controls\FlatButton.cs">
|
||||
@@ -98,6 +97,8 @@
|
||||
<Compile Include="Core\FormBrowser.Designer.cs">
|
||||
<DependentUpon>FormBrowser.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Core\Handling\General\FileDialogHandler.cs" />
|
||||
<Compile Include="Core\Handling\KeyboardHandlerBrowser.cs" />
|
||||
<Compile Include="Core\Handling\KeyboardHandlerNotification.cs" />
|
||||
<Compile Include="Core\Handling\RequestHandlerBrowser.cs" />
|
||||
<Compile Include="Core\Handling\General\RequestHandlerBase.cs" />
|
||||
@@ -242,7 +243,6 @@
|
||||
<Compile Include="Plugins\PluginManager.cs" />
|
||||
<Compile Include="Plugins\PluginScriptGenerator.cs" />
|
||||
<Compile Include="Reporter.cs" />
|
||||
<Compile Include="Updates\Events\UpdateDismissedEventArgs.cs" />
|
||||
<Compile Include="Updates\FormUpdateDownload.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
@@ -258,8 +258,6 @@
|
||||
<Compile Include="Core\Utils\BrowserCache.cs" />
|
||||
<Compile Include="Core\Utils\BrowserUtils.cs" />
|
||||
<Compile Include="Core\Utils\NativeMethods.cs" />
|
||||
<Compile Include="Updates\Events\UpdateAcceptedEventArgs.cs" />
|
||||
<Compile Include="Updates\Events\UpdateCheckEventArgs.cs" />
|
||||
<Compile Include="Updates\UpdateDownloadStatus.cs" />
|
||||
<Compile Include="Updates\UpdateHandler.cs" />
|
||||
<Compile Include="Updates\UpdateInfo.cs" />
|
||||
@@ -271,6 +269,7 @@
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Resources\ScriptLoader.cs" />
|
||||
<Compile Include="Updates\UpdateEventArgs.cs" />
|
||||
<Compile Include="Updates\UpdaterSettings.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
@@ -304,9 +303,6 @@
|
||||
<DependentUpon>FormAbout.cs</DependentUpon>
|
||||
<SubType>Designer</SubType>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="Updates\FormUpdateDownload.resx">
|
||||
<DependentUpon>FormUpdateDownload.cs</DependentUpon>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="Properties\Resources.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||
@@ -323,6 +319,7 @@
|
||||
<None Include="Resources\icon-small.ico" />
|
||||
<None Include="Resources\icon-tray-new.ico" />
|
||||
<None Include="Resources\icon-tray.ico" />
|
||||
<None Include="Resources\PostBuild.ps1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Resources\Plugins\" />
|
||||
@@ -347,10 +344,14 @@
|
||||
<Project>{b10b0017-819e-4f71-870f-8256b36a26aa}</Project>
|
||||
<Name>TweetDuck.Browser</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="video\TweetDuck.Video\TweetDuck.Video.csproj">
|
||||
<ProjectReference Include="video\TweetDuck.Video.csproj">
|
||||
<Project>{278b2d11-402d-44b6-b6a1-8fa67db65565}</Project>
|
||||
<Name>TweetDuck.Video</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="lib\TweetLib.Communication\TweetLib.Communication.csproj">
|
||||
<Project>{72473763-4b9d-4fb6-a923-9364b2680f06}</Project>
|
||||
<Name>TweetLib.Communication</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<PropertyGroup>
|
||||
@@ -378,7 +379,9 @@ if $(ConfigurationName) == Debug (
|
||||
rmdir "$(TargetDir)plugins\official\.debug" /S /Q
|
||||
mkdir "$(TargetDir)plugins\user\.debug"
|
||||
xcopy "$(ProjectDir)Resources\Plugins\.debug\*" "$(TargetDir)plugins\user\.debug\" /E /Y
|
||||
)</PostBuildEvent>
|
||||
)
|
||||
|
||||
powershell -ExecutionPolicy Unrestricted -File "$(ProjectDir)Resources\PostBuild.ps1" "$(TargetDir)\"</PostBuildEvent>
|
||||
</PropertyGroup>
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
@@ -402,4 +405,7 @@ if $(ConfigurationName) == Debug (
|
||||
<Delete Files="$(TargetDir)devtools_resources.pak" />
|
||||
<Delete Files="$(TargetDir)widevinecdmadapter.dll" />
|
||||
</Target>
|
||||
<PropertyGroup>
|
||||
<PreBuildEvent>powershell Get-Process TweetDuck.Browser -ErrorAction SilentlyContinue ^| Where-Object {$_.Path -eq '$(TargetDir)TweetDuck.Browser.exe'} ^| Stop-Process; Exit 0</PreBuildEvent>
|
||||
</PropertyGroup>
|
||||
</Project>
|
@@ -10,7 +10,9 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests", "tests\UnitTest
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetLib.Audio", "lib\TweetLib.Audio\TweetLib.Audio.csproj", "{E9E1FD1B-F480-45B7-9970-BE2ECFD309AC}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetDuck.Video", "video\TweetDuck.Video\TweetDuck.Video.csproj", "{278B2D11-402D-44B6-B6A1-8FA67DB65565}"
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetDuck.Video", "video\TweetDuck.Video.csproj", "{278B2D11-402D-44B6-B6A1-8FA67DB65565}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetLib.Communication", "lib\TweetLib.Communication\TweetLib.Communication.csproj", "{72473763-4B9D-4FB6-A923-9364B2680F06}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
@@ -38,6 +40,10 @@ Global
|
||||
{278B2D11-402D-44B6-B6A1-8FA67DB65565}.Debug|x86.Build.0 = Debug|x86
|
||||
{278B2D11-402D-44B6-B6A1-8FA67DB65565}.Release|x86.ActiveCfg = Release|x86
|
||||
{278B2D11-402D-44B6-B6A1-8FA67DB65565}.Release|x86.Build.0 = Release|x86
|
||||
{72473763-4B9D-4FB6-A923-9364B2680F06}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{72473763-4B9D-4FB6-A923-9364B2680F06}.Debug|x86.Build.0 = Debug|x86
|
||||
{72473763-4B9D-4FB6-A923-9364B2680F06}.Release|x86.ActiveCfg = Release|x86
|
||||
{72473763-4B9D-4FB6-A923-9364B2680F06}.Release|x86.Build.0 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
@@ -1,11 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace TweetDuck.Updates.Events{
|
||||
class UpdateAcceptedEventArgs : EventArgs{
|
||||
public readonly UpdateInfo UpdateInfo;
|
||||
|
||||
public UpdateAcceptedEventArgs(UpdateInfo info){
|
||||
this.UpdateInfo = info;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,15 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace TweetDuck.Updates.Events{
|
||||
class UpdateCheckEventArgs : EventArgs{
|
||||
public int EventId { get; }
|
||||
public UpdateInfo UpdateInfo { get; }
|
||||
|
||||
public bool UpdateAvailable => UpdateInfo != null;
|
||||
|
||||
public UpdateCheckEventArgs(int eventId, UpdateInfo updateInfo){
|
||||
EventId = eventId;
|
||||
UpdateInfo = updateInfo;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,11 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace TweetDuck.Updates.Events{
|
||||
class UpdateDismissedEventArgs : EventArgs{
|
||||
public readonly string VersionTag;
|
||||
|
||||
public UpdateDismissedEventArgs(string versionTag){
|
||||
this.VersionTag = versionTag;
|
||||
}
|
||||
}
|
||||
}
|
3
Updates/FormUpdateDownload.Designer.cs
generated
3
Updates/FormUpdateDownload.Designer.cs
generated
@@ -24,7 +24,6 @@
|
||||
/// </summary>
|
||||
private void InitializeComponent() {
|
||||
this.components = new System.ComponentModel.Container();
|
||||
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(FormUpdateDownload));
|
||||
this.btnCancel = new System.Windows.Forms.Button();
|
||||
this.labelDescription = new System.Windows.Forms.Label();
|
||||
this.timerDownloadCheck = new System.Windows.Forms.Timer(this.components);
|
||||
@@ -65,7 +64,7 @@
|
||||
this.Controls.Add(this.btnCancel);
|
||||
this.DoubleBuffered = true;
|
||||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
|
||||
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
|
||||
this.Icon = Properties.Resources.icon;
|
||||
this.MaximizeBox = false;
|
||||
this.MinimizeBox = false;
|
||||
this.Name = "FormUpdateDownload";
|
||||
|
@@ -1,380 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<metadata name="timerDownloadCheck.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>17, 17</value>
|
||||
</metadata>
|
||||
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
|
||||
<data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>
|
||||
AAABAAMAMDAAAAEAIACoJQAANgAAACAgAAABACAAqBAAAN4lAAAQEAAAAQAgAGgEAACGNgAAKAAAADAA
|
||||
AABgAAAAAQAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqHg5IrF+OlS+hjyMw4g7tcmM
|
||||
PM/MjTzezY485s2OPObMjjzfyIw80MOJPLe8hDyNs387WKx8OyQAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAApHU4FrB8OGXGijzAzo889dKR
|
||||
PP/SkTz/0pE8/9GRPP/RkDz/0JA8/9CQPP/RkDz/0ZE8/9KRPP/TkTz/0pE8/86PPPbGijzCt4E8bKp6
|
||||
OxoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKN1OB+9hTuLzI486NOR
|
||||
PP/SkTz/0ZA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9GQ
|
||||
PP/SkTz/0pE8/82OPOu9hTuOq3w8IgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACidTkFuoM8csyN
|
||||
POrTkjz/0ZE8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
|
||||
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9GRPP/Tkjz/zY887bqEPHimeDsHAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKp6
|
||||
OynDiDvA05I8/9GRPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
|
||||
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0ZA8/9ORPP/GijzFr308LgAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAArXw7UM2OPOvTkTz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
|
||||
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
|
||||
PP/SkTz/zo887rOAPFYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAACxfjxm0ZA8/NGRPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
|
||||
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
|
||||
PP/QkDz/0JA8/9CQPP/QkDz/0ZE8/9GQPP64gjxtAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAALaBPGXRkTz/0ZE8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
|
||||
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
|
||||
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9GQPP/SkTz/uIM8bAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqnk6TtCQPPzRkTz/0JA8/9CQPP/QkDz/0JA8/9CQ
|
||||
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
|
||||
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/RkDz/0ZA8/rSA
|
||||
PFUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACtfDwmzo887dKRPP/QkDz/0JA8/9CQ
|
||||
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
|
||||
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
|
||||
PP/QkDz/0ZE8/86PPPGtfDwrAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKZ4PAXFijzC0pE8/9CQ
|
||||
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CURf/TpGT/2baJ/9/GpP/l0bf/59W//+fVvf/kz7P/3sOg/9mz
|
||||
hP/ToWH/0JND/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
|
||||
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9KRPP/EiTvJo3Y7CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALiC
|
||||
PG/Tkjz/0JA8/9CQPP/QkDz/0JA8/9CSQf/Vq3P/5tO7//j07v//////////////////////////////
|
||||
////////////////////////9vHq/+TQtf/UqG7/0JA+/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
|
||||
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/Tkjz/uYM8dgAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAArXw8Gc2OPOzRkTz/0JA8/9CQPP/QlEX/27qP//fz7P//////////////////////////////
|
||||
////////////////////////////////////////////////////////8ObZ/9auev/QkDz/0JA8/9CQ
|
||||
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/RkTz/zI488J1x
|
||||
OB8AAAAAAAAAAAAAAAAAAAAAuoM7h9OSPP/QkDz/0JA8/9GXTf/TpGb/27uS/9u6kP/ewZz/59W9//Xv
|
||||
5///////////////////////////////////////////////////////////////////////////////
|
||||
///r3cr/0ZxV/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
|
||||
PP/QkDz/1JI8/7mDO5AAAAAAAAAAAAAAAADFj0cR0pNB6s+PO//Ojjv/0JA8/9CQPP/QkDz/0JA8/9CQ
|
||||
PP/QkDz/0JA8/9CRQP/TpGf/5NC2//38+v//////////////////////////////////////////////
|
||||
////////////////////////+vf0/9auev/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
|
||||
PP/QkDz/0JA8/9CQPP/QkDz/0ZE8/8uOPO6SajYVAAAAAAAAAADzr1df7qxV/+WkT//ZmUT/0JA9/86O
|
||||
O//Pjzv/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9GaUv/l07r/////////////////////////
|
||||
///////////////////////////////////////////////////buo//0JA8/9CQPP/QkDz/0JA8/9CQ
|
||||
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9OSPP+ldjdoAAAAAAAAAADurFW67qxV//Ct
|
||||
Vv/vrVb/6adR/92dSP/Skz//zo47/8+PO//QkDz/0JA8/9CQPP/QkDz/0JJB/9Smaf/dv5j/+fXw////
|
||||
////////////////////////////////////////////////////////////////////////2riM/9CQ
|
||||
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9KRPP/FiTzGoHQ6Ae6s
|
||||
VRrurFXz7qxV/+6sVf/urFX/761W//CuVv/sqlT/4qFM/9aVQv/Pjzv/zo47/9CQPP/VqnH/8+vh////
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/////////////9aueP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9GQ
|
||||
PP/Ojzz4oHM5Iu6sVU3urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/vrVX/8K5W/+6sVf/mpU//2plF/9q6
|
||||
j///////////////////////////////////////////////////////////////////////////////
|
||||
//////////////////////////////n18P/Rm1P/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
|
||||
PP/QkDz/0JA8/9CQPP/SkTz/rXs6Vu6sVYPurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
|
||||
Vf/wrVb/6r2B////////////////////////////////////////////////////////////////////
|
||||
///////////////////////////////////////////////////o18L/0JA8/9CQPP/QkDz/0JA8/9CQ
|
||||
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/SkTz/v4Y8j+6sVa3urFX/7qxV/+6sVf/urFX/7qxV/+6s
|
||||
Vf/urFX/7qxV/+6sVf/urFb/9Onb///////+/v3/////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////1qx2/9CQ
|
||||
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/SkTz/wYc7uO6sVcrurFX/7qxV/+6s
|
||||
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/trlr/67Zy/+m+h//y5NP/////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
////////7+TW/9CQPf/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/RkTz/yYw80e6s
|
||||
VdvurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/6sme//v49P//////////////
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/////////////////////////////9arc//QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
|
||||
PP/RkDz/zI083+6sVeLurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/u2b3/////////
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/////////////////////////////////////////////+fVvv/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
|
||||
PP/QkDz/0JA8/9CQPP/QkDz/zo885u6sVeLurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+vL
|
||||
of//////////////////////////////////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////r28v/Rl0r/0JA8/9CQ
|
||||
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/zo885u6sVdrurFX/7qxV/+6sVf/urFX/7qxV/+6s
|
||||
Vf/urFX/7LBj//r28f//////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
///Vq3P/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/RkDz/zI083+6sVcrurFX/7qxV/+6s
|
||||
Vf/urFX/7qxV/+6sVf/urFX/6cKO////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
///////////////////ewp7/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/RkTz/yYw80O6s
|
||||
VazurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7NOy//z59v/u2b7/+fPr////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
///////////////////////////////////n1sD/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
|
||||
PP/SkTz/xIo8uO6sVYHurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/67Zx/+2uWf/qy6H/////////
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
///////////////////////////////////////////////////v5dj/0JA8/9CQPP/QkDz/0JA8/9CQ
|
||||
PP/QkDz/0JA8/9CQPP/SkTz/vYU8je6sVUvurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+nC
|
||||
kP///////////////////////////////////////////////////////fz6//fu5P/w4Mv/793F//7+
|
||||
/v/////////////////////////////////////////////////////////////////17eT/0JA//86O
|
||||
Ov/Pjzv/0JA8/9CQPP/QkDz/0JA8/9CQPP/SkTz/tYA8Vu6sVRnurFXy7qxV/+6sVf/urFX/7qxV/+6s
|
||||
Vf/urFX/7a5b//fw5///////////////////////////////////////+/jz/+7av//pwIr/67Jn/+6t
|
||||
Vv/urFX/6rd1////////////////////////////////////////////////////////////////////
|
||||
///48ej/6KhU/92cR//Tkj7/zo47/8+PO//QkDz/0JA8/9GQPP/Pjzz3rXw8IAAAAADurFW37qxV/+6s
|
||||
Vf/urFX/7qxV/+6sVf/urFX/6b2D//////////////////////////////////nz6//qyZ3/7K9h/+6s
|
||||
Vf/urFX/7qxV/+6sVf/urFX/6rl6////////////////////////////////////////////////////
|
||||
///////////////////9/Pr/6r6C//CuV//sqlT/4aBL/9aWQv/Pjzv/zo47/9GRPP/GijzCp3g8AQAA
|
||||
AADurFVb7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/68yk///////////////////////+/fz/68+q/+2v
|
||||
XP/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/6rNr//7+/f//////////////////////////////
|
||||
/////////////////////////////////////////////+nEk//vrVX/8K5W/+6sVf/lpE//2ZlF/9KR
|
||||
Pf+zfzpoAAAAAAAAAADurFUP7qxV5u6sVf/urFX/7qxV/+6sVf/urFX/7NGu//////////////////fu
|
||||
5P/quHb/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxW//Xr3///////////////
|
||||
///////////////////////////////////////////////////7+PP/+vXu///////pxJL/7qxV/+6s
|
||||
Vf/wrVb/8K1W/+mnUevUmUsUAAAAAAAAAAAAAAAA7qxVgO6sVf/urFX/7qxV/+6sVf/urFX/6sqg////
|
||||
////////8eLO/+yvX//urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+nF
|
||||
lf/////////////////////////////////////////////////////////////////8+vf/6cWV/+q5
|
||||
ev/s07H/6rd1/+6sVf/urFX/7qxV/++tVogAAAAAAAAAAAAAAAAAAAAA7qxVFe6sVejurFX/7qxV/+6s
|
||||
Vf/urFX/6bp8///////w4Mn/7a1Z/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
|
||||
Vf/urFX/7qxV/+6sVf/x4cz////////////////////////////////////////////////////+//7+
|
||||
/v/////////+/+m8gP/urFX/7a1Y/+6sVf/urFX/7qxV7O6sVRoAAAAAAAAAAAAAAAAAAAAAAAAAAO6s
|
||||
VWburFX/7qxV/+6sVf/urFX/7a5b/+zTs//sr1//7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
|
||||
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/sr1//8uPQ////////////////////////////////////
|
||||
////////6suj/+qzav/pxpj/9Onb//n07f/rr2D/7qxV/+6sVf/urFX/7qxVbwAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAO6sVQPurFW67qxV/+6sVf/urFX/7qxV/+6sVv/urFX/7qxV/+6sVf/urFX/7qxV/+6s
|
||||
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7axY/+nGl//27uP/////////
|
||||
/////////fz6//Lj0P/qunv/7qxV/+6sVf/urFX/7qxV/+m9gv/qt3X/7qxV/+6sVf/urFXB7qxVBQAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADurFUg7qxV6O6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
|
||||
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
|
||||
Vf/qtnP/6sqg/+3WuP/s07P/6cSS/+uyZf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVv/trVr/7qxV/+6s
|
||||
VezurFUlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7qxVRu6sVfjurFX/7qxV/+6s
|
||||
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
|
||||
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
|
||||
Vf/urFX/7qxV++6sVUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAO6s
|
||||
VVzurFX97qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
|
||||
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
|
||||
Vf/urFX/7qxV/+6sVf/urFX/7qxVYwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAADurFVd7qxV+O6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
|
||||
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
|
||||
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVfvurFVjAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7qxVR+6sVeXurFX/7qxV/+6sVf/urFX/7qxV/+6s
|
||||
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
|
||||
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV6O6sVU0AAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAO6sVSPurFW37qxV/+6s
|
||||
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
|
||||
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFW77qxVJgAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AADurFUB7qxVae6sVeTurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
|
||||
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV5+6sVW3urFUDAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAO6sVRjurFWA7qxV4u6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
|
||||
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVeTurFWE7qxVGwAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7qxVEu6sVV7urFW17qxV7+6s
|
||||
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVfDurFW47qxVX+6s
|
||||
VRMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAA7qxVG+6sVUzurFWA7qxVrO6sVcnurFXa7qxV4u6sVeLurFXa7qxVye6sVa3urFWC7qxVTe6s
|
||||
VR0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAP//gAH//6gr//wAAD//qCv/8AAAD/+oK//AAAAD/6gr/4AAAAH/qCv/AAAAAP+oK/4A
|
||||
AAAAf6gr/AAAAAA/qCv4AAAAAB+oK/AAAAAAD6gr4AAAAAAHqCvgAAAAAAeoK8AAAAAAA6grwAAAAAAD
|
||||
qCuAAAAAAAGoK4AAAAAAAagrgAAAAAAAqCsAAAAAAACoKwAAAAAAAKgrAAAAAAAAqCsAAAAAAACoKwAA
|
||||
AAAAAKgrAAAAAAAAqCsAAAAAAACoKwAAAAAAAKgrAAAAAAAAqCsAAAAAAACoKwAAAAAAAKgrAAAAAAAA
|
||||
qCsAAAAAAACoKwAAAAAAAKgrgAAAAAAAqCuAAAAAAAGoK4AAAAAAAagrwAAAAAADqCvAAAAAAAOoK+AA
|
||||
AAAAB6gr4AAAAAAHqCvwAAAAAA+oK/gAAAAAH6gr/AAAAAA/qCv+AAAAAH+oK/8AAAAA/6gr/4AAAAH/
|
||||
qCv/wAAAA/+oK//wAAAP/6gr//wAAD//qCv//4AB//+oKygAAAAgAAAAQAAAAAEAIAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAApHY4FrR/
|
||||
Ole+hjuVxos7w8uNPNvOjzznzo8858uNPNzGijzEvoU7l7WBO1mpejsYAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqHg5Kr6F
|
||||
O5XLjTvo0pE8/9ORPP/SkTz/0ZE8/9CQPP/QkDz/0ZE8/9KRPP/TkTz/0pE8/82OPOq/hjyZrXw7LAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAo3U6CbuE
|
||||
O4bPjzz005I8/9GRPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0ZE8/9OS
|
||||
PP/PkDz1vYU8iah6PAsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKR3
|
||||
OibGizvG05E8/9GQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
|
||||
PP/QkDz/0JA8/9GQPP/TkTz/yIs8yqx8PCkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AACsezsxy40849KRPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
|
||||
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/SkTz/zI485rKAPTUAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAApXc6JMuNPOTSkTz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
|
||||
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/SkTz/zI485659PCcAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAKd4OwnHizzJ0pE8/9CQPP/QkDz/0JA9/9OgYP/YtIX/38Sh/+LMr//iy67/3sOf/9iy
|
||||
gv/Tn13/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/SkTz/x4s8zaV4
|
||||
OwsAAAAAAAAAAAAAAAAAAAAAu4Q8hNOSPP/QkDz/0JA8/9GXS//YtIX/6NfC//Xv5//8+vj/////////
|
||||
///8+vf/9O3k/+fVvv/YsYH/0JRF/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
|
||||
PP/Tkjz/u4Q7iQAAAAAAAAAAAAAAAKx7OyTPjzz10ZA8/9GXS//Ztoj/8une////////////////////
|
||||
///////////////////////////////////38uz/3L2V/9CRP//QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
|
||||
PP/QkDz/0JA8/9GRPP/Ojzz3onU5KQAAAAAAAAAAxo1BlNGQO//Ojjv/0JNC/9GWSP/QlET/0ZpR/9au
|
||||
eP/n1b3//v79////////////////////////////////////////////7+TV/9GdWP/QkDz/0JA8/9CQ
|
||||
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9SSPP+4gjqaAAAAAPGuVhHvrVXq5qRO/9qZRP/QkT3/zo46/8+P
|
||||
O//QkDz/0JA8/9CQPP/To2P/8+vh////////////////////////////////////////////+fXw/9Oh
|
||||
Yf/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0pE8/8qMO+2WbTYV7qxVUe6sVf/wrVb/761W/+mo
|
||||
Uv/enUj/05M//86PO//PkD7/3cCZ//Lp3v/7+PT/////////////////////////////////////////
|
||||
////////+PTv/9GcVf/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/05I8/699OljurFWR7qxV/+6s
|
||||
Vf/urFX/761W//CuV//sqlT/4qJQ/+zdyf//////////////////////////////////////////////
|
||||
////////////////////////7+TV/9CRP//QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/Tkjz/vIQ7mO6s
|
||||
Vb/urFX/7qxV/+6sVf/urFX/7qxV/++tVf/ry5///v79//7+/f//////////////////////////////
|
||||
////////////////////////////////////////2rqO/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9KR
|
||||
PP/GijvF7qxV2u6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+u1bv/t1bb//fv4////////////////////
|
||||
///////////////////////////////////////////////////48+3/0ZRG/9CQPP/QkDz/0JA8/9CQ
|
||||
PP/QkDz/0ZA8/8uNPNzurFXl7qxV/+6sVf/urFX/7qxV/+6sVf/rsWP/9OjZ////////////////////
|
||||
///////////////////////////////////////////////////////////////////YtIX/0JA8/9CQ
|
||||
PP/QkDz/0JA8/9CQPP/QkDz/zo886O6sVeXurFX/7qxV/+6sVf/urFX/7qxW//Hiz///////////////
|
||||
/////////////////////////////////////////////////////////////////////////////+rb
|
||||
yP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/Ojzzo7qxV2e6sVf/urFX/7qxV/+6sVf/quHb/////////
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
////////+fXw/9CURf/QkDz/0JA8/9CQPP/QkDz/0ZA8/8yOPNzurFW/7qxV/+6sVf/urFX/7qxV/+nA
|
||||
if/s1LT/9Onb////////////////////////////////////////////////////////////////////
|
||||
////////////////////////0Z5b/9CQPP/QkDz/0JA8/9CQPP/SkTz/x4s8xe6sVY/urFX/7qxV/+6s
|
||||
Vf/urFX/7qxV/+nEk//+/fz////////////////////////////+/v3/+PHo//To2f/+/v7/////////
|
||||
///////////////////////////////////VqGz/zo46/8+PO//QkDz/0JA8/9KRPP+/hjyX7qxVUO6s
|
||||
Vf/urFX/7qxV/+6sVf/ssGH//Pr2///////////////////////y49D/6cOR/+qyaf/urVb/6rJp////
|
||||
/////////////////////////////////////////////+e7f//goEr/1ZVB/8+PO//Ojzv/0pE8/7WB
|
||||
PFfurFUQ7qxV6O6sVf/urFX/7qxV/+m+hv/////////////////z5tX/6rl6/+6sVf/urFX/7qxV/+6s
|
||||
Vf/qs2r////+////////////////////////////////////////////+PLp/+y4c//urFX/5aRO/9qZ
|
||||
RP/Mjjztp3g6FQAAAADurFWQ7qxV/+6sVf/urFX/6cKP////////////682m/+6sVf/urFX/7qxV/+6s
|
||||
Vf/urFX/7qxV/+2sV//17N////////////////////////////////////////7+/v/06Nn/8uTT/+qz
|
||||
a//wrVb/761W/+alUZYAAAAAAAAAAO6sVSHurFX07qxV/+6sVf/quXj//////+nDkf/urFX/7qxV/+6s
|
||||
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+m+hP////////////////////////////////////////////jy
|
||||
6v/qtnH/67Fj/+6sVf/urFX2761WJQAAAAAAAAAAAAAAAO6sVX7urFX/7qxV/+2vW//pvID/7qxV/+6s
|
||||
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+nEkv/7+PT//////////////////fz6/+rJ
|
||||
n//rtW7/682n/+zTs//urFb/7qxV/+6sVYQAAAAAAAAAAAAAAAAAAAAA7qxVB+6sVcTurFX/7qxV/+6s
|
||||
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+u0bP/qy6H/7da4/+vN
|
||||
pv/qtnP/7qxV/+6sVf/urFX/7K9f/+2sWP/urFXI7qxVCQAAAAAAAAAAAAAAAAAAAAAAAAAA7qxVIO6s
|
||||
VeDurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
|
||||
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV4+6sVSMAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAA7qxVLO6sVd/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
|
||||
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVeLurFUwAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAA7qxVIu6sVcHurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
|
||||
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFXE7qxVJAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7qxVBu6sVX/urFXw7qxV/+6sVf/urFX/7qxV/+6s
|
||||
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFXy7qxVgu6sVQgAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAO6sVSXurFWP7qxV5e6s
|
||||
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFXl7qxVke6sVScAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AADurFUS7qxVUO6sVY7urFW97qxV2O6sVeXurFXl7qxV2O6sVb7urFWP7qxVUu6sVRMAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/8AD//8AAP/8AAA/+AAAH/AAAA/gAAAHwAAAA8AA
|
||||
AAOAAAABgAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAA
|
||||
AAGAAAABwAAAA8AAAAPgAAAH8AAAD/gAAB/8AAA//wAA///AA/8oAAAAEAAAACAAAAABACAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKx7Oja9hTqTyYw8zs6PPOfOjzznyYw8zr6G
|
||||
PJSvfjw3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAo3Y7CbyEO5PRkDz805I8/9GRPP/QkDz/0JA8/9GR
|
||||
PP/TkTz/0ZA8/b+GPJWrfDwKAAAAAAAAAAAAAAAAp3g8CcSJPLfTkjz/0ZA8/9CQPP/QkDz/0JA8/9CQ
|
||||
PP/QkDz/0JA8/9CQPP/Tkjz/xoo8uqx8PQoAAAAAAAAAAL2FPJPUk0D/06Rm/92/mf/izK//4sut/9u6
|
||||
j//Snlr/0JA8/9CQPP/QkDz/0JA8/9OSPP+9hTyWAAAAALiEQDLQkDz90JpS/93Amf/u4tP/////////
|
||||
/////////fv5/+DIqP/QkkH/0JA8/9CQPP/RkDz/0JA8/qN1OTTtq1WS56VP/9qaRf/RkT3/0JdO/+rc
|
||||
yP//////////////////////693L/9CSQf/QkDz/0JA8/9SSPP+3gTqW761Vz++tVv/wrVb/6K5h//fy
|
||||
6v/////////////////////////////////gx6b/0JA8/9CQPP/RkTz/yYw80e6sVefurFX/7qxV/+q+
|
||||
hP/69vD//////////////////////////////////fz7/9GbU//QkDz/0JA8/8+PPOnurFXn7qxV/+yw
|
||||
Y//79/P////////////////////////////////////////////ZuIz/0JA8/9CQPP/Pjzzp7qxVz+6s
|
||||
Vf/sr2H/8eLO/////////////v7+//n07f/+/v7/////////////////48yu/8+PO//Qjzv/yYs80e6s
|
||||
VZLurFX/67Jm//7+/f/8+fb/68yl/+qza//rsmf//v37//////////////////Pn2P/ip1n/2phC/8CH
|
||||
PJXurFUw7qxV/eq2c//169//6rNr/+6sVf/urFX/7q1W//Pm1f/////////////////8+fX/67+G/++t
|
||||
Vf3lpVEyAAAAAO6sVZHtrVr/7a9c/+6sVf/urFX/7qxV/+6sVf/rsWP/7de5//Lj0P/pxJL/67Zy/+qz
|
||||
av/urFWTAAAAAAAAAADurFUI7qxVtO6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
|
||||
Vf/urFW37qxVCQAAAAAAAAAAAAAAAO6sVQjurFWQ7qxV++6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
|
||||
VfvurFWS7qxVCQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAO6sVTPurFWQ7qxVzO6sVeburFXm7qxVzO6s
|
||||
VZDurFU0AAAAAAAAAAAAAAAAAAAAAPAPrEHAA6xBgAGsQYABrEEAAKxBAACsQQAArEEAAKxBAACsQQAA
|
||||
rEEAAKxBAACsQYABrEGAAaxBwAOsQfAPrEE=
|
||||
</value>
|
||||
</data>
|
||||
</root>
|
20
Updates/UpdateEventArgs.cs
Normal file
20
Updates/UpdateEventArgs.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
|
||||
namespace TweetDuck.Updates{
|
||||
sealed class UpdateEventArgs : EventArgs{
|
||||
public int EventId { get; }
|
||||
public UpdateInfo UpdateInfo { get; }
|
||||
|
||||
public bool IsUpdateAvailable => UpdateInfo != null;
|
||||
|
||||
public UpdateEventArgs(int eventId, UpdateInfo updateInfo){
|
||||
this.EventId = eventId;
|
||||
this.UpdateInfo = updateInfo;
|
||||
}
|
||||
|
||||
public UpdateEventArgs(UpdateInfo updateInfo){
|
||||
this.EventId = updateInfo.EventId;
|
||||
this.UpdateInfo = updateInfo;
|
||||
}
|
||||
}
|
||||
}
|
@@ -5,18 +5,15 @@ using System.Windows.Forms;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetDuck.Resources;
|
||||
using TweetDuck.Updates.Events;
|
||||
|
||||
namespace TweetDuck.Updates{
|
||||
sealed class UpdateHandler{
|
||||
private static bool IsSystemSupported => true; // Environment.OSVersion.Version >= new Version("6.1"); // 6.1 NT version = Windows 7
|
||||
|
||||
private readonly ChromiumWebBrowser browser;
|
||||
private readonly UpdaterSettings settings;
|
||||
|
||||
public event EventHandler<UpdateAcceptedEventArgs> UpdateAccepted;
|
||||
public event EventHandler<UpdateDismissedEventArgs> UpdateDismissed;
|
||||
public event EventHandler<UpdateCheckEventArgs> CheckFinished;
|
||||
public event EventHandler<UpdateEventArgs> UpdateAccepted;
|
||||
public event EventHandler<UpdateEventArgs> UpdateDismissed;
|
||||
public event EventHandler<UpdateEventArgs> CheckFinished;
|
||||
|
||||
private int lastEventId;
|
||||
private UpdateInfo lastUpdateInfo;
|
||||
@@ -37,21 +34,16 @@ namespace TweetDuck.Updates{
|
||||
}
|
||||
|
||||
public int Check(bool force){
|
||||
if (IsSystemSupported){
|
||||
if (Program.UserConfig.EnableUpdateCheck || force){
|
||||
string dismissedUpdate = force || settings.DismissedUpdate == null ? string.Empty : settings.DismissedUpdate;
|
||||
|
||||
browser.ExecuteScriptAsync("TDUF_runUpdateCheck", ++lastEventId, Program.VersionTag, dismissedUpdate, settings.AllowPreReleases);
|
||||
return lastEventId;
|
||||
if (Program.UserConfig.EnableUpdateCheck || force){
|
||||
if (force){
|
||||
settings.DismissedUpdate = null;
|
||||
}
|
||||
|
||||
return 0;
|
||||
browser.ExecuteScriptAsync("TDUF_runUpdateCheck", ++lastEventId, Program.VersionTag, settings.DismissedUpdate ?? string.Empty, settings.AllowPreReleases);
|
||||
return lastEventId;
|
||||
}
|
||||
else if (settings.DismissedUpdate != "unsupported"){
|
||||
browser.ExecuteScriptAsync("TDUF_displayNotification", "unsupported");
|
||||
}
|
||||
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void BeginUpdateDownload(Form ownerForm, UpdateInfo updateInfo, Action<UpdateInfo> onSuccess){
|
||||
@@ -88,24 +80,20 @@ namespace TweetDuck.Updates{
|
||||
}
|
||||
}
|
||||
|
||||
public void DismissUpdate(string tag){
|
||||
TriggerUpdateDismissedEvent(new UpdateDismissedEventArgs(tag));
|
||||
}
|
||||
|
||||
private void TriggerUpdateAcceptedEvent(UpdateAcceptedEventArgs args){
|
||||
private void TriggerUpdateAcceptedEvent(UpdateEventArgs args){
|
||||
UpdateAccepted?.Invoke(this, args);
|
||||
}
|
||||
|
||||
private void TriggerUpdateDismissedEvent(UpdateDismissedEventArgs args){
|
||||
settings.DismissedUpdate = args.VersionTag;
|
||||
private void TriggerUpdateDismissedEvent(UpdateEventArgs args){
|
||||
settings.DismissedUpdate = args.UpdateInfo.VersionTag;
|
||||
UpdateDismissed?.Invoke(this, args);
|
||||
}
|
||||
|
||||
private void TriggerCheckFinishedEvent(UpdateCheckEventArgs args){
|
||||
private void TriggerCheckFinishedEvent(UpdateEventArgs args){
|
||||
CheckFinished?.Invoke(this, args);
|
||||
}
|
||||
|
||||
public class Bridge{
|
||||
public sealed class Bridge{
|
||||
private readonly UpdateHandler owner;
|
||||
|
||||
public Bridge(UpdateHandler owner){
|
||||
@@ -119,22 +107,22 @@ namespace TweetDuck.Updates{
|
||||
public void OnUpdateCheckFinished(int eventId, string versionTag, string downloadUrl){
|
||||
if (versionTag != null && (owner.lastUpdateInfo == null || owner.lastUpdateInfo.VersionTag != versionTag)){
|
||||
owner.CleanupDownload();
|
||||
owner.lastUpdateInfo = new UpdateInfo(owner.settings, versionTag, downloadUrl);
|
||||
owner.lastUpdateInfo = new UpdateInfo(owner.settings, eventId, versionTag, downloadUrl);
|
||||
owner.lastUpdateInfo.BeginSilentDownload();
|
||||
}
|
||||
|
||||
owner.TriggerCheckFinishedEvent(new UpdateCheckEventArgs(eventId, owner.lastUpdateInfo));
|
||||
owner.TriggerCheckFinishedEvent(new UpdateEventArgs(eventId, owner.lastUpdateInfo));
|
||||
}
|
||||
|
||||
public void OnUpdateAccepted(){
|
||||
if (owner.lastUpdateInfo != null){
|
||||
owner.TriggerUpdateAcceptedEvent(new UpdateAcceptedEventArgs(owner.lastUpdateInfo));
|
||||
owner.TriggerUpdateAcceptedEvent(new UpdateEventArgs(owner.lastUpdateInfo));
|
||||
}
|
||||
}
|
||||
|
||||
public void OnUpdateDismissed(){
|
||||
if (owner.lastUpdateInfo != null){
|
||||
owner.TriggerUpdateDismissedEvent(new UpdateDismissedEventArgs(owner.lastUpdateInfo.VersionTag));
|
||||
owner.TriggerUpdateDismissedEvent(new UpdateEventArgs(owner.lastUpdateInfo));
|
||||
owner.CleanupDownload();
|
||||
}
|
||||
}
|
||||
|
@@ -5,6 +5,7 @@ using TweetDuck.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Updates{
|
||||
sealed class UpdateInfo{
|
||||
public int EventId { get; }
|
||||
public string VersionTag { get; }
|
||||
public string InstallerPath { get; }
|
||||
|
||||
@@ -15,10 +16,11 @@ namespace TweetDuck.Updates{
|
||||
private readonly string downloadUrl;
|
||||
private WebClient currentDownload;
|
||||
|
||||
public UpdateInfo(UpdaterSettings settings, string versionTag, string downloadUrl){
|
||||
public UpdateInfo(UpdaterSettings settings, int eventId, string versionTag, string downloadUrl){
|
||||
this.installerFolder = settings.InstallerDownloadFolder;
|
||||
this.downloadUrl = downloadUrl;
|
||||
|
||||
this.EventId = eventId;
|
||||
this.VersionTag = versionTag;
|
||||
this.InstallerPath = Path.Combine(installerFolder, "TweetDuck."+versionTag+".exe");
|
||||
}
|
||||
|
@@ -30,7 +30,7 @@ namespace TweetLib.Audio{
|
||||
public abstract event EventHandler<PlaybackErrorEventArgs> PlaybackError;
|
||||
|
||||
public abstract void Play(string file);
|
||||
public abstract void Stop();
|
||||
public abstract bool SetVolume(int volume);
|
||||
protected abstract void Dispose(bool disposing);
|
||||
|
||||
public void Dispose(){
|
||||
|
@@ -34,8 +34,8 @@ namespace TweetLib.Audio.Impl{
|
||||
}
|
||||
}
|
||||
|
||||
public override void Stop(){
|
||||
player.Stop();
|
||||
public override bool SetVolume(int volume){
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing){
|
||||
|
@@ -1,6 +1,6 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using TweetLib.Audio.Utils;
|
||||
using System.Windows.Forms;
|
||||
using WMPLib;
|
||||
|
||||
namespace TweetLib.Audio.Impl{
|
||||
@@ -9,56 +9,56 @@ namespace TweetLib.Audio.Impl{
|
||||
|
||||
public override event EventHandler<PlaybackErrorEventArgs> PlaybackError;
|
||||
|
||||
private readonly WindowsMediaPlayer player;
|
||||
private readonly Form owner;
|
||||
private readonly ControlWMP wmp;
|
||||
private bool wasTryingToPlay;
|
||||
private bool ignorePlaybackError;
|
||||
|
||||
// changing the player volume also affects the value in the Windows mixer
|
||||
// however, the initial value is always 50 or some other stupid shit
|
||||
// so we have to tell the player to set its volume to whatever the mixer is set to
|
||||
// using the most code required for the least functionality with a sorry excuse for an API
|
||||
// thanks, Microsoft
|
||||
|
||||
private WindowsMediaPlayer Player => wmp.Ocx;
|
||||
|
||||
public SoundPlayerImplWMP(){
|
||||
player = new WindowsMediaPlayer();
|
||||
player.settings.autoStart = false;
|
||||
player.settings.enableErrorDialogs = false;
|
||||
player.settings.invokeURLs = false;
|
||||
player.settings.volume = (int)Math.Floor(100.0*NativeCoreAudio.GetMixerVolumeForCurrentProcess());
|
||||
player.MediaChange += player_MediaChange;
|
||||
player.MediaError += player_MediaError;
|
||||
owner = new Form();
|
||||
wmp = new ControlWMP();
|
||||
wmp.BeginInit();
|
||||
owner.Controls.Add(wmp);
|
||||
wmp.EndInit();
|
||||
|
||||
Player.uiMode = "none";
|
||||
Player.settings.autoStart = false;
|
||||
Player.settings.enableErrorDialogs = false;
|
||||
Player.settings.invokeURLs = false;
|
||||
Player.settings.volume = 0;
|
||||
Player.MediaChange += player_MediaChange;
|
||||
Player.MediaError += player_MediaError;
|
||||
}
|
||||
|
||||
public override void Play(string file){
|
||||
wasTryingToPlay = true;
|
||||
|
||||
try{
|
||||
if (player.URL != file){
|
||||
player.close();
|
||||
player.URL = file;
|
||||
if (Player.URL != file){
|
||||
Player.close();
|
||||
Player.URL = file;
|
||||
ignorePlaybackError = false;
|
||||
}
|
||||
else{
|
||||
player.controls.stop();
|
||||
Player.controls.stop();
|
||||
}
|
||||
|
||||
player.controls.play();
|
||||
Player.controls.play();
|
||||
}catch(Exception e){
|
||||
OnNotificationSoundError("An error occurred in Windows Media Player: "+e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Stop(){
|
||||
try{
|
||||
player.controls.stop();
|
||||
}catch{
|
||||
// ignore
|
||||
}
|
||||
public override bool SetVolume(int volume){
|
||||
Player.settings.volume = volume;
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing){
|
||||
player.close();
|
||||
Marshal.ReleaseComObject(player);
|
||||
wmp.Dispose();
|
||||
owner.Dispose();
|
||||
}
|
||||
|
||||
private void player_MediaChange(object item){
|
||||
@@ -109,5 +109,16 @@ namespace TweetLib.Audio.Impl{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Clsid("{6bf52a52-394a-11d3-b153-00c04f79faa6}")]
|
||||
private sealed class ControlWMP : AxHost{
|
||||
public WindowsMediaPlayer Ocx { get; private set; }
|
||||
|
||||
public ControlWMP() : base("6bf52a52-394a-11d3-b153-00c04f79faa6"){}
|
||||
|
||||
protected override void AttachInterfaces(){
|
||||
Ocx = (WindowsMediaPlayer)GetOcx();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -31,5 +31,5 @@ using System.Runtime.InteropServices;
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||
[assembly: AssemblyVersion("1.1.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.1.0.0")]
|
||||
|
@@ -26,10 +26,10 @@
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="AudioPlayer.cs" />
|
||||
<Compile Include="Utils\NativeCoreAudio.cs" />
|
||||
<Compile Include="PlaybackErrorEventArgs.cs" />
|
||||
<Compile Include="Impl\SoundPlayerImplFallback.cs" />
|
||||
<Compile Include="Impl\SoundPlayerImplWMP.cs" />
|
||||
|
@@ -1,127 +0,0 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace TweetLib.Audio.Utils{
|
||||
static class NativeCoreAudio{
|
||||
private const int EDATAFLOW_RENDER = 0;
|
||||
private const int EROLE_MULTIMEDIA = 1;
|
||||
|
||||
[ComImport]
|
||||
[Guid("BCDE0395-E52F-467C-8E3D-C4579291692E")]
|
||||
private class MMDeviceEnumerator{}
|
||||
|
||||
[Guid("A95664D2-9614-4F35-A746-DE8DB63617E6")]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
private interface IMMDeviceEnumerator{
|
||||
int Unimpl_EnumAudioEndpoints();
|
||||
IMMDevice GetDefaultAudioEndpoint(int dataFlow, int role);
|
||||
}
|
||||
|
||||
[Guid("D666063F-1587-4E43-81F1-B948E807363F")]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
private interface IMMDevice{
|
||||
[return:MarshalAs(UnmanagedType.IUnknown)]
|
||||
object Activate(ref Guid id, int clsCtx, IntPtr activationParams);
|
||||
}
|
||||
|
||||
[Guid("77AA99A0-1BD6-484F-8BC7-2C654C9A9B6F")]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
private interface IAudioSessionManager2{
|
||||
int Unimpl_FindWillToLive();
|
||||
int Unimpl_HelloDarknessMyOldFriend();
|
||||
IAudioSessionEnumerator GetSessionEnumerator();
|
||||
}
|
||||
|
||||
[Guid("E2F5BB11-0570-40CA-ACDD-3AA01277DEE8")]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
private interface IAudioSessionEnumerator{
|
||||
int GetCount();
|
||||
IAudioSessionControl GetSession(int sessionIndex);
|
||||
}
|
||||
|
||||
[Guid("F4B1A599-7266-4319-A8CA-E70ACB11E8CD")]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
private interface IAudioSessionControl{}
|
||||
|
||||
[Guid("BFB7FF88-7239-4FC9-8FA2-07C950BE9C6D")]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
private interface IAudioSessionControl2{
|
||||
int Unimpl_GetState();
|
||||
int Unimpl_GetDisplayName();
|
||||
int Unimpl_SetDisplayName();
|
||||
int Unimpl_GetIconPath();
|
||||
int Unimpl_SetIconPath();
|
||||
int Unimpl_GetGroupingParam();
|
||||
int Unimpl_SetGroupingParam();
|
||||
int Unimpl_RegisterAudioSessionNotification();
|
||||
int Unimpl_UnregisterAudioSessionNotification();
|
||||
|
||||
[return:MarshalAs(UnmanagedType.LPWStr)]
|
||||
string GetSessionIdentifier();
|
||||
}
|
||||
|
||||
[Guid("87CE5498-68D6-44E5-9215-6DA47EF883D8")]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
private interface ISimpleAudioVolume{
|
||||
void SetMasterVolume(float level, ref Guid eventContext);
|
||||
float GetMasterVolume();
|
||||
}
|
||||
|
||||
[SuppressMessage("ReSharper", "SuspiciousTypeConversion.Global")]
|
||||
private static ISimpleAudioVolume GetVolumeObject(string name){
|
||||
IMMDeviceEnumerator devices = (IMMDeviceEnumerator)new MMDeviceEnumerator();
|
||||
IMMDevice device = devices.GetDefaultAudioEndpoint(EDATAFLOW_RENDER, EROLE_MULTIMEDIA);
|
||||
|
||||
Guid sessionManagerGUID = typeof(IAudioSessionManager2).GUID;
|
||||
IAudioSessionManager2 manager = (IAudioSessionManager2)device.Activate(ref sessionManagerGUID, 0, IntPtr.Zero);
|
||||
IAudioSessionEnumerator sessions = manager.GetSessionEnumerator();
|
||||
|
||||
ISimpleAudioVolume volumeObj = null;
|
||||
|
||||
for(int index = sessions.GetCount()-1; index >= 0; index--){
|
||||
if (sessions.GetSession(index) is IAudioSessionControl2 ctl){
|
||||
string identifier = ctl.GetSessionIdentifier();
|
||||
|
||||
if (identifier != null && identifier.Contains(name)){
|
||||
volumeObj = ctl as ISimpleAudioVolume;
|
||||
break;
|
||||
}
|
||||
|
||||
Marshal.ReleaseComObject(ctl);
|
||||
}
|
||||
}
|
||||
|
||||
Marshal.ReleaseComObject(devices);
|
||||
Marshal.ReleaseComObject(device);
|
||||
Marshal.ReleaseComObject(manager);
|
||||
Marshal.ReleaseComObject(sessions);
|
||||
return volumeObj;
|
||||
}
|
||||
|
||||
public static double GetMixerVolume(string appPath){
|
||||
ISimpleAudioVolume obj = GetVolumeObject(appPath);
|
||||
float level = 1F;
|
||||
|
||||
if (obj != null){
|
||||
level = obj.GetMasterVolume();
|
||||
Marshal.ReleaseComObject(obj);
|
||||
}
|
||||
|
||||
return Math.Round(level, 2);
|
||||
}
|
||||
|
||||
public static double GetMixerVolumeForCurrentProcess(){
|
||||
string path;
|
||||
|
||||
using(Process process = Process.GetCurrentProcess()){
|
||||
path = process.MainModule.FileName;
|
||||
path = path.Substring(Path.GetPathRoot(path).Length);
|
||||
}
|
||||
|
||||
return GetMixerVolume(path);
|
||||
}
|
||||
}
|
||||
}
|
14
lib/TweetLib.Communication/Comms.cs
Normal file
14
lib/TweetLib.Communication/Comms.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using TweetLib.Communication.Utils;
|
||||
|
||||
namespace TweetLib.Communication{
|
||||
public static class Comms{
|
||||
public static void BroadcastMessage(uint msg, uint wParam, int lParam){
|
||||
NativeMethods.PostMessage(NativeMethods.HWND_BROADCAST, msg, new UIntPtr(wParam), new IntPtr(lParam));
|
||||
}
|
||||
|
||||
public static uint RegisterMessage(string name){
|
||||
return NativeMethods.RegisterWindowMessage(name);
|
||||
}
|
||||
}
|
||||
}
|
108
lib/TweetLib.Communication/DuplexPipe.cs
Normal file
108
lib/TweetLib.Communication/DuplexPipe.cs
Normal file
@@ -0,0 +1,108 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Pipes;
|
||||
using System.Threading;
|
||||
|
||||
namespace TweetLib.Communication{
|
||||
public abstract class DuplexPipe : IDisposable{
|
||||
private const string Separator = "\x1F";
|
||||
|
||||
public static Server CreateServer(){
|
||||
return new Server();
|
||||
}
|
||||
|
||||
public static Client CreateClient(string token){
|
||||
int space = token.IndexOf(' ');
|
||||
return new Client(token.Substring(0, space), token.Substring(space+1));
|
||||
}
|
||||
|
||||
protected readonly PipeStream pipeIn;
|
||||
protected readonly PipeStream pipeOut;
|
||||
|
||||
private readonly Thread readerThread;
|
||||
private readonly StreamWriter writerStream;
|
||||
|
||||
public event EventHandler<PipeReadEventArgs> DataIn;
|
||||
|
||||
protected DuplexPipe(PipeStream pipeIn, PipeStream pipeOut){
|
||||
this.pipeIn = pipeIn;
|
||||
this.pipeOut = pipeOut;
|
||||
|
||||
this.readerThread = new Thread(ReaderThread){
|
||||
IsBackground = true
|
||||
};
|
||||
|
||||
this.readerThread.Start();
|
||||
this.writerStream = new StreamWriter(this.pipeOut);
|
||||
}
|
||||
|
||||
private void ReaderThread(){
|
||||
using(StreamReader read = new StreamReader(pipeIn)){
|
||||
string data;
|
||||
|
||||
while((data = read.ReadLine()) != null){
|
||||
DataIn?.Invoke(this, new PipeReadEventArgs(data));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Write(string key){
|
||||
writerStream.WriteLine(key);
|
||||
writerStream.Flush();
|
||||
}
|
||||
|
||||
public void Write(string key, string data){
|
||||
writerStream.WriteLine(string.Concat(key, Separator, data));
|
||||
writerStream.Flush();
|
||||
}
|
||||
|
||||
public void Dispose(){
|
||||
try{
|
||||
readerThread.Abort();
|
||||
}catch{
|
||||
// /shrug
|
||||
}
|
||||
|
||||
pipeIn.Dispose();
|
||||
writerStream.Dispose();
|
||||
}
|
||||
|
||||
public sealed class Server : DuplexPipe{
|
||||
private AnonymousPipeServerStream ServerPipeIn => (AnonymousPipeServerStream)pipeIn;
|
||||
private AnonymousPipeServerStream ServerPipeOut => (AnonymousPipeServerStream)pipeOut;
|
||||
|
||||
internal Server() : base(new AnonymousPipeServerStream(PipeDirection.In, HandleInheritability.Inheritable), new AnonymousPipeServerStream(PipeDirection.Out, HandleInheritability.Inheritable)){}
|
||||
|
||||
public string GenerateToken(){
|
||||
return ServerPipeIn.GetClientHandleAsString()+" "+ServerPipeOut.GetClientHandleAsString();
|
||||
}
|
||||
|
||||
public void DisposeToken(){
|
||||
ServerPipeIn.DisposeLocalCopyOfClientHandle();
|
||||
ServerPipeOut.DisposeLocalCopyOfClientHandle();
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class Client : DuplexPipe{
|
||||
internal Client(string handleOut, string handleIn) : base(new AnonymousPipeClientStream(PipeDirection.In, handleIn), new AnonymousPipeClientStream(PipeDirection.Out, handleOut)){}
|
||||
}
|
||||
|
||||
public sealed class PipeReadEventArgs : EventArgs{
|
||||
public string Key { get; }
|
||||
public string Data { get; }
|
||||
|
||||
internal PipeReadEventArgs(string line){
|
||||
int separatorIndex = line.IndexOf(Separator, StringComparison.Ordinal);
|
||||
|
||||
if (separatorIndex == -1){
|
||||
Key = line;
|
||||
Data = string.Empty;
|
||||
}
|
||||
else{
|
||||
Key = line.Substring(0, separatorIndex);
|
||||
Data = line.Substring(separatorIndex+1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
35
lib/TweetLib.Communication/Properties/AssemblyInfo.cs
Normal file
35
lib/TweetLib.Communication/Properties/AssemblyInfo.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("TweetDuck Communication Library")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("TweetDuck Communication Library")]
|
||||
[assembly: AssemblyCopyright("")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("72473763-4b9d-4fb6-a923-9364b2680f06")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.1.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.1.0.0")]
|
44
lib/TweetLib.Communication/TweetLib.Communication.csproj
Normal file
44
lib/TweetLib.Communication/TweetLib.Communication.csproj
Normal file
@@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{72473763-4B9D-4FB6-A923-9364B2680F06}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>TweetLib.Communication</RootNamespace>
|
||||
<AssemblyName>TweetLib.Communication</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<OutputPath>bin\x86\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<DebugType>full</DebugType>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
|
||||
<OutputPath>bin\x86\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
<DebugType>none</DebugType>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="DuplexPipe.cs" />
|
||||
<Compile Include="Utils\NativeMethods.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Comms.cs" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
@@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace TweetDuck.Browser{
|
||||
namespace TweetLib.Communication.Utils{
|
||||
static class NativeMethods{
|
||||
public static readonly IntPtr HWND_BROADCAST = new IntPtr(0xFFFF);
|
||||
|
@@ -1,10 +1,13 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using CefSharp;
|
||||
using CefSharp.BrowserSubprocess;
|
||||
using TweetLib.Communication;
|
||||
|
||||
namespace TweetDuck.Browser{
|
||||
static class Program{
|
||||
internal const string Version = "1.2.0.0";
|
||||
|
||||
private static int Main(string[] args){
|
||||
SubProcess.EnableHighDPISupport();
|
||||
|
||||
@@ -19,14 +22,15 @@ namespace TweetDuck.Browser{
|
||||
else return SubProcess.ExecuteProcess();
|
||||
}
|
||||
|
||||
private class RendererProcess : SubProcess{
|
||||
private sealed class RendererProcess : SubProcess{
|
||||
// ReSharper disable once ParameterTypeCanBeEnumerable.Local
|
||||
public RendererProcess(string[] args) : base(args){}
|
||||
|
||||
public override void OnBrowserCreated(CefBrowserWrapper wrapper){
|
||||
base.OnBrowserCreated(wrapper);
|
||||
|
||||
using(Process me = Process.GetCurrentProcess()){
|
||||
NativeMethods.PostMessage(NativeMethods.HWND_BROADCAST, NativeMethods.RegisterWindowMessage("TweetDuckSubProcess"), new UIntPtr((uint)me.Id), new IntPtr(wrapper.BrowserId));
|
||||
Comms.BroadcastMessage(Comms.RegisterMessage("TweetDuckSubProcess"), (uint)me.Id, wrapper.BrowserId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using TweetDuck.Browser;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
@@ -31,5 +32,5 @@ using System.Runtime.InteropServices;
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.1.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.1.0.0")]
|
||||
[assembly: AssemblyVersion(Program.Version)]
|
||||
[assembly: AssemblyFileVersion(Program.Version)]
|
||||
|
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
@@ -31,10 +31,15 @@
|
||||
<Reference Include="System" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="NativeMethods.cs" />
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\lib\TweetLib.Communication\TweetLib.Communication.csproj">
|
||||
<Project>{72473763-4b9d-4fb6-a923-9364b2680f06}</Project>
|
||||
<Name>TweetLib.Communication</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<PropertyGroup>
|
||||
<PostBuildEvent>call "$(DevEnvDir)..\..\VC\Auxiliary\Build\vcvars32.bat"
|
||||
|
138
tests/Configuration/TestUserConfig.cs
Normal file
138
tests/Configuration/TestUserConfig.cs
Normal file
@@ -0,0 +1,138 @@
|
||||
using System;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.IO;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using TweetDuck.Configuration;
|
||||
using TweetDuck.Core;
|
||||
|
||||
namespace UnitTests.Configuration{
|
||||
[TestClass]
|
||||
public class TestUserConfig : UnitTestIO{
|
||||
private static void WriteTestConfig(string file, bool withBackup){
|
||||
UserConfig cfg = UserConfig.Load(file);
|
||||
cfg.ZoomLevel = 123;
|
||||
cfg.Save();
|
||||
|
||||
if (withBackup){
|
||||
cfg.Save();
|
||||
}
|
||||
}
|
||||
|
||||
[Pure] // used to display a warning when not using the return value
|
||||
private static bool CheckTestConfig(string file){
|
||||
return UserConfig.Load(file).ZoomLevel == 123;
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestMissing(){
|
||||
Assert.IsNotNull(UserConfig.Load("missing"));
|
||||
Assert.IsFalse(File.Exists("missing"));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestBasic(){
|
||||
Assert.IsFalse(CheckTestConfig("basic"));
|
||||
WriteTestConfig("basic", false);
|
||||
Assert.IsTrue(CheckTestConfig("basic"));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestBackupName(){
|
||||
Assert.AreEqual("name.bak", UserConfig.GetBackupFile("name"));
|
||||
Assert.AreEqual("name.cfg.bak", UserConfig.GetBackupFile("name.cfg"));
|
||||
Assert.AreEqual("name.bak.bak", UserConfig.GetBackupFile("name.bak"));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestBackupCreate(){
|
||||
WriteTestConfig("nobackup", false);
|
||||
Assert.IsTrue(File.Exists("nobackup"));
|
||||
Assert.IsFalse(File.Exists(UserConfig.GetBackupFile("nobackup")));
|
||||
|
||||
WriteTestConfig("withbackup", true);
|
||||
Assert.IsTrue(File.Exists("withbackup"));
|
||||
Assert.IsTrue(File.Exists(UserConfig.GetBackupFile("withbackup")));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestBackupRestore(){
|
||||
WriteTestConfig("gone", true);
|
||||
Assert.IsTrue(File.Exists("gone"));
|
||||
Assert.IsTrue(File.Exists(UserConfig.GetBackupFile("gone")));
|
||||
File.Delete("gone");
|
||||
Assert.IsTrue(CheckTestConfig("gone"));
|
||||
|
||||
WriteTestConfig("corrupted", true);
|
||||
Assert.IsTrue(File.Exists("corrupted"));
|
||||
Assert.IsTrue(File.Exists(UserConfig.GetBackupFile("corrupted")));
|
||||
File.WriteAllText("corrupted", "oh no corrupt");
|
||||
Assert.IsTrue(CheckTestConfig("corrupted"));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestReload(){
|
||||
UserConfig cfg = UserConfig.Load("reloaded");
|
||||
cfg.ZoomLevel = 123;
|
||||
cfg.Save();
|
||||
|
||||
cfg.ZoomLevel = 200;
|
||||
Assert.IsTrue(cfg.Reload());
|
||||
Assert.AreEqual(123, cfg.ZoomLevel);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestReset(){
|
||||
UserConfig cfg = UserConfig.Load("reset");
|
||||
cfg.ZoomLevel = 123;
|
||||
cfg.Save();
|
||||
|
||||
File.Delete("reset");
|
||||
Assert.IsTrue(cfg.Reload());
|
||||
Assert.AreEqual(100, cfg.ZoomLevel);
|
||||
Assert.IsTrue(File.Exists("reset"));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestEventsNoTrigger(){
|
||||
void Fail(object sender, EventArgs args) => Assert.Fail();
|
||||
|
||||
UserConfig cfg = UserConfig.Load("events");
|
||||
cfg.MuteNotifications = true;
|
||||
cfg.TrayBehavior = TrayIcon.Behavior.Combined;
|
||||
cfg.ZoomLevel = 99;
|
||||
|
||||
cfg.MuteToggled += Fail;
|
||||
cfg.TrayBehaviorChanged += Fail;
|
||||
cfg.ZoomLevelChanged += Fail;
|
||||
|
||||
cfg.MuteNotifications = true;
|
||||
cfg.TrayBehavior = TrayIcon.Behavior.Combined;
|
||||
cfg.ZoomLevel = 99;
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestEventsTrigger(){
|
||||
int triggers = 0;
|
||||
void Trigger(object sender, EventArgs args) => ++triggers;
|
||||
|
||||
UserConfig cfg = UserConfig.Load("events");
|
||||
cfg.MuteNotifications = false;
|
||||
cfg.TrayBehavior = TrayIcon.Behavior.Disabled;
|
||||
cfg.ZoomLevel = 100;
|
||||
|
||||
cfg.MuteToggled += Trigger;
|
||||
cfg.TrayBehaviorChanged += Trigger;
|
||||
cfg.ZoomLevelChanged += Trigger;
|
||||
|
||||
cfg.MuteNotifications = true;
|
||||
cfg.TrayBehavior = TrayIcon.Behavior.Combined;
|
||||
cfg.ZoomLevel = 99;
|
||||
|
||||
cfg.MuteNotifications = false;
|
||||
cfg.TrayBehavior = TrayIcon.Behavior.Disabled;
|
||||
cfg.ZoomLevel = 100;
|
||||
|
||||
Assert.AreEqual(6, triggers);
|
||||
}
|
||||
}
|
||||
}
|
@@ -6,33 +6,36 @@ namespace UnitTests.Core{
|
||||
public class TestBrowserUtils{
|
||||
[TestMethod]
|
||||
public void TestIsValidUrl(){
|
||||
Assert.IsTrue(BrowserUtils.IsValidUrl("http://google.com")); // base
|
||||
Assert.IsTrue(BrowserUtils.IsValidUrl("http://www.google.com")); // www.
|
||||
Assert.IsTrue(BrowserUtils.IsValidUrl("http://google.co.uk")); // co.uk
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Fine, BrowserUtils.CheckUrl("http://google.com")); // base
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Fine, BrowserUtils.CheckUrl("http://www.google.com")); // www.
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Fine, BrowserUtils.CheckUrl("http://google.co.uk")); // co.uk
|
||||
|
||||
Assert.IsTrue(BrowserUtils.IsValidUrl("https://google.com")); // https
|
||||
Assert.IsTrue(BrowserUtils.IsValidUrl("ftp://google.com")); // ftp
|
||||
Assert.IsTrue(BrowserUtils.IsValidUrl("mailto:someone@google.com")); // mailto
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Fine, BrowserUtils.CheckUrl("https://google.com")); // https
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Fine, BrowserUtils.CheckUrl("ftp://google.com")); // ftp
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Fine, BrowserUtils.CheckUrl("mailto:someone@google.com")); // mailto
|
||||
|
||||
Assert.IsTrue(BrowserUtils.IsValidUrl("http://google.com/")); // trailing slash
|
||||
Assert.IsTrue(BrowserUtils.IsValidUrl("http://google.com/?")); // trailing question mark
|
||||
Assert.IsTrue(BrowserUtils.IsValidUrl("http://google.com/?a=5&b=x")); // parameters
|
||||
Assert.IsTrue(BrowserUtils.IsValidUrl("http://google.com/#hash")); // parameters + hash
|
||||
Assert.IsTrue(BrowserUtils.IsValidUrl("http://google.com/?a=5&b=x#hash")); // parameters + hash
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Fine, BrowserUtils.CheckUrl("http://google.com/")); // trailing slash
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Fine, BrowserUtils.CheckUrl("http://google.com/?")); // trailing question mark
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Fine, BrowserUtils.CheckUrl("http://google.com/?a=5&b=x")); // parameters
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Fine, BrowserUtils.CheckUrl("http://google.com/#hash")); // parameters + hash
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Fine, BrowserUtils.CheckUrl("http://google.com/?a=5&b=x#hash")); // parameters + hash
|
||||
|
||||
foreach(string tld in new string[]{ "accountants", "blackfriday", "cancerresearch", "coffee", "cool", "foo", "travelersinsurance" }){
|
||||
Assert.IsTrue(BrowserUtils.IsValidUrl("http://test."+tld)); // long and unusual TLDs
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Fine, BrowserUtils.CheckUrl("http://test."+tld)); // long and unusual TLDs
|
||||
}
|
||||
|
||||
Assert.IsFalse(BrowserUtils.IsValidUrl("explorer")); // file
|
||||
Assert.IsFalse(BrowserUtils.IsValidUrl("explorer.exe")); // file
|
||||
Assert.IsFalse(BrowserUtils.IsValidUrl("://explorer.exe")); // file-sorta
|
||||
Assert.IsFalse(BrowserUtils.IsValidUrl("file://explorer.exe")); // file-proper
|
||||
|
||||
Assert.IsFalse(BrowserUtils.IsValidUrl("")); // empty
|
||||
Assert.IsFalse(BrowserUtils.IsValidUrl("lol")); // random
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Tracking, BrowserUtils.CheckUrl("http://t.co/abc")); // tracking
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Tracking, BrowserUtils.CheckUrl("https://t.co/abc")); // tracking
|
||||
|
||||
Assert.IsFalse(BrowserUtils.IsValidUrl("gopher://nobody.cares")); // lmao rekt
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Invalid, BrowserUtils.CheckUrl("explorer")); // file
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Invalid, BrowserUtils.CheckUrl("explorer.exe")); // file
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Invalid, BrowserUtils.CheckUrl("://explorer.exe")); // file-sorta
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Invalid, BrowserUtils.CheckUrl("file://explorer.exe")); // file-proper
|
||||
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Invalid, BrowserUtils.CheckUrl("")); // empty
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Invalid, BrowserUtils.CheckUrl("lol")); // random
|
||||
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Invalid, BrowserUtils.CheckUrl("gopher://nobody.cares")); // lmao rekt
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
|
@@ -26,20 +26,20 @@ namespace UnitTests.Core{
|
||||
|
||||
[TestMethod]
|
||||
public void TestImageQualityLink(){
|
||||
Assert.AreEqual("https://pbs.twimg.com/profile_images/123", TwitterUtils.GetImageLink("https://pbs.twimg.com/profile_images/123", TwitterUtils.ImageQuality.Default));
|
||||
Assert.AreEqual("https://pbs.twimg.com/profile_images/123", TwitterUtils.GetImageLink("https://pbs.twimg.com/profile_images/123", TwitterUtils.ImageQuality.Orig));
|
||||
Assert.AreEqual("https://pbs.twimg.com/profile_images/123", TwitterUtils.GetMediaLink("https://pbs.twimg.com/profile_images/123", TwitterUtils.ImageQuality.Default));
|
||||
Assert.AreEqual("https://pbs.twimg.com/profile_images/123", TwitterUtils.GetMediaLink("https://pbs.twimg.com/profile_images/123", TwitterUtils.ImageQuality.Orig));
|
||||
|
||||
Assert.AreEqual("https://pbs.twimg.com/profile_images/123.jpg", TwitterUtils.GetImageLink("https://pbs.twimg.com/profile_images/123.jpg", TwitterUtils.ImageQuality.Default));
|
||||
Assert.AreEqual("https://pbs.twimg.com/profile_images/123.jpg", TwitterUtils.GetImageLink("https://pbs.twimg.com/profile_images/123.jpg", TwitterUtils.ImageQuality.Orig));
|
||||
Assert.AreEqual("https://pbs.twimg.com/profile_images/123.jpg", TwitterUtils.GetMediaLink("https://pbs.twimg.com/profile_images/123.jpg", TwitterUtils.ImageQuality.Default));
|
||||
Assert.AreEqual("https://pbs.twimg.com/profile_images/123.jpg", TwitterUtils.GetMediaLink("https://pbs.twimg.com/profile_images/123.jpg", TwitterUtils.ImageQuality.Orig));
|
||||
|
||||
Assert.AreEqual("https://pbs.twimg.com/media/123.jpg", TwitterUtils.GetImageLink("https://pbs.twimg.com/media/123.jpg", TwitterUtils.ImageQuality.Default));
|
||||
Assert.AreEqual("https://pbs.twimg.com/media/123.jpg:orig", TwitterUtils.GetImageLink("https://pbs.twimg.com/media/123.jpg", TwitterUtils.ImageQuality.Orig));
|
||||
Assert.AreEqual("https://pbs.twimg.com/media/123.jpg", TwitterUtils.GetMediaLink("https://pbs.twimg.com/media/123.jpg", TwitterUtils.ImageQuality.Default));
|
||||
Assert.AreEqual("https://pbs.twimg.com/media/123.jpg:orig", TwitterUtils.GetMediaLink("https://pbs.twimg.com/media/123.jpg", TwitterUtils.ImageQuality.Orig));
|
||||
|
||||
Assert.AreEqual("https://pbs.twimg.com/media/123.jpg:small", TwitterUtils.GetImageLink("https://pbs.twimg.com/media/123.jpg:small", TwitterUtils.ImageQuality.Default));
|
||||
Assert.AreEqual("https://pbs.twimg.com/media/123.jpg:orig", TwitterUtils.GetImageLink("https://pbs.twimg.com/media/123.jpg:small", TwitterUtils.ImageQuality.Orig));
|
||||
Assert.AreEqual("https://pbs.twimg.com/media/123.jpg:small", TwitterUtils.GetMediaLink("https://pbs.twimg.com/media/123.jpg:small", TwitterUtils.ImageQuality.Default));
|
||||
Assert.AreEqual("https://pbs.twimg.com/media/123.jpg:orig", TwitterUtils.GetMediaLink("https://pbs.twimg.com/media/123.jpg:small", TwitterUtils.ImageQuality.Orig));
|
||||
|
||||
Assert.AreEqual("https://pbs.twimg.com/media/123.jpg:large", TwitterUtils.GetImageLink("https://pbs.twimg.com/media/123.jpg:large", TwitterUtils.ImageQuality.Default));
|
||||
Assert.AreEqual("https://pbs.twimg.com/media/123.jpg:orig", TwitterUtils.GetImageLink("https://pbs.twimg.com/media/123.jpg:large", TwitterUtils.ImageQuality.Orig));
|
||||
Assert.AreEqual("https://pbs.twimg.com/media/123.jpg:large", TwitterUtils.GetMediaLink("https://pbs.twimg.com/media/123.jpg:large", TwitterUtils.ImageQuality.Default));
|
||||
Assert.AreEqual("https://pbs.twimg.com/media/123.jpg:orig", TwitterUtils.GetMediaLink("https://pbs.twimg.com/media/123.jpg:large", TwitterUtils.ImageQuality.Orig));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -6,39 +6,39 @@ using TweetDuck.Data;
|
||||
|
||||
namespace UnitTests.Data{
|
||||
[TestClass]
|
||||
public class TestCombinedFileStream{
|
||||
public class TestCombinedFileStream : UnitTestIO{
|
||||
[TestMethod]
|
||||
public void TestNoFiles(){
|
||||
using(CombinedFileStream cfs = new CombinedFileStream(TestUtils.WriteFile("cfs_empty"))){
|
||||
using(CombinedFileStream cfs = new CombinedFileStream(File.OpenWrite("empty"))){
|
||||
cfs.Flush();
|
||||
}
|
||||
|
||||
Assert.IsTrue(File.Exists("cfs_empty"));
|
||||
Assert.IsTrue(File.Exists("empty"));
|
||||
|
||||
using(CombinedFileStream cfs = new CombinedFileStream(TestUtils.ReadFile("cfs_empty"))){
|
||||
using(CombinedFileStream cfs = new CombinedFileStream(File.OpenRead("empty"))){
|
||||
Assert.IsNull(cfs.ReadFile());
|
||||
}
|
||||
|
||||
using(CombinedFileStream cfs = new CombinedFileStream(TestUtils.ReadFile("cfs_empty"))){
|
||||
using(CombinedFileStream cfs = new CombinedFileStream(File.OpenRead("empty"))){
|
||||
Assert.IsNull(cfs.SkipFile());
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestEmptyFiles(){
|
||||
TestUtils.WriteText("cfs_input_empty_1", string.Empty);
|
||||
TestUtils.WriteText("cfs_input_empty_2", string.Empty);
|
||||
File.WriteAllText("input_empty_1", string.Empty);
|
||||
File.WriteAllText("input_empty_2", string.Empty);
|
||||
|
||||
using(CombinedFileStream cfs = new CombinedFileStream(TestUtils.WriteFile("cfs_blank_files"))){
|
||||
cfs.WriteFile("id1", "cfs_input_empty_1");
|
||||
cfs.WriteFile("id2", "cfs_input_empty_2");
|
||||
cfs.WriteFile("id2_clone", "cfs_input_empty_2");
|
||||
using(CombinedFileStream cfs = new CombinedFileStream(File.OpenWrite("blank_files"))){
|
||||
cfs.WriteFile("id1", "input_empty_1");
|
||||
cfs.WriteFile("id2", "input_empty_2");
|
||||
cfs.WriteFile("id2_clone", "input_empty_2");
|
||||
cfs.Flush();
|
||||
}
|
||||
|
||||
Assert.IsTrue(File.Exists("cfs_blank_files"));
|
||||
Assert.IsTrue(File.Exists("blank_files"));
|
||||
|
||||
using(CombinedFileStream cfs = new CombinedFileStream(TestUtils.ReadFile("cfs_blank_files"))){
|
||||
using(CombinedFileStream cfs = new CombinedFileStream(File.OpenRead("blank_files"))){
|
||||
CombinedFileStream.Entry entry1 = cfs.ReadFile();
|
||||
string entry2key = cfs.SkipFile();
|
||||
CombinedFileStream.Entry entry3 = cfs.ReadFile();
|
||||
@@ -56,31 +56,29 @@ namespace UnitTests.Data{
|
||||
Assert.AreEqual("id2_clone", entry3.Identifier);
|
||||
CollectionAssert.AreEqual(new string[0], entry3.KeyValue);
|
||||
|
||||
entry1.WriteToFile("cfs_blank_file_1");
|
||||
entry3.WriteToFile("cfs_blank_file_2");
|
||||
TestUtils.DeleteFileOnExit("cfs_blank_file_1");
|
||||
TestUtils.DeleteFileOnExit("cfs_blank_file_2");
|
||||
entry1.WriteToFile("blank_file_1");
|
||||
entry3.WriteToFile("blank_file_2");
|
||||
}
|
||||
|
||||
Assert.IsTrue(File.Exists("cfs_blank_file_1"));
|
||||
Assert.IsTrue(File.Exists("cfs_blank_file_2"));
|
||||
Assert.AreEqual(string.Empty, TestUtils.ReadText("cfs_blank_file_1"));
|
||||
Assert.AreEqual(string.Empty, TestUtils.ReadText("cfs_blank_file_2"));
|
||||
Assert.IsTrue(File.Exists("blank_file_1"));
|
||||
Assert.IsTrue(File.Exists("blank_file_2"));
|
||||
Assert.AreEqual(string.Empty, File.ReadAllText("blank_file_1"));
|
||||
Assert.AreEqual(string.Empty, File.ReadAllText("blank_file_2"));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestTextFilesAndComplexKeys(){
|
||||
TestUtils.WriteText("cfs_input_text_1", "Hello World!"+Environment.NewLine);
|
||||
File.WriteAllText("input_text_1", "Hello World!"+Environment.NewLine);
|
||||
|
||||
using(CombinedFileStream cfs = new CombinedFileStream(TestUtils.WriteFile("cfs_text_files"))){
|
||||
cfs.WriteFile(new string[]{ "key1", "a", "bb", "ccc", "dddd" }, "cfs_input_text_1");
|
||||
cfs.WriteFile(new string[]{ "key2", "a", "bb", "ccc", "dddd" }, "cfs_input_text_1");
|
||||
using(CombinedFileStream cfs = new CombinedFileStream(File.OpenWrite("text_files"))){
|
||||
cfs.WriteFile(new string[]{ "key1", "a", "bb", "ccc", "dddd" }, "input_text_1");
|
||||
cfs.WriteFile(new string[]{ "key2", "a", "bb", "ccc", "dddd" }, "input_text_1");
|
||||
cfs.Flush();
|
||||
}
|
||||
|
||||
Assert.IsTrue(File.Exists("cfs_text_files"));
|
||||
Assert.IsTrue(File.Exists("text_files"));
|
||||
|
||||
using(CombinedFileStream cfs = new CombinedFileStream(TestUtils.ReadFile("cfs_text_files"))){
|
||||
using(CombinedFileStream cfs = new CombinedFileStream(File.OpenRead("text_files"))){
|
||||
CombinedFileStream.Entry entry = cfs.ReadFile();
|
||||
|
||||
Assert.AreEqual("key2", cfs.SkipFile());
|
||||
@@ -91,68 +89,65 @@ namespace UnitTests.Data{
|
||||
Assert.AreEqual("key1", entry.KeyName);
|
||||
CollectionAssert.AreEqual(new string[]{ "a", "bb", "ccc", "dddd" }, entry.KeyValue);
|
||||
|
||||
entry.WriteToFile("cfs_text_file_1");
|
||||
TestUtils.DeleteFileOnExit("cfs_text_file_1");
|
||||
entry.WriteToFile("text_file_1");
|
||||
}
|
||||
|
||||
Assert.IsTrue(File.Exists("cfs_text_file_1"));
|
||||
Assert.AreEqual("Hello World!"+Environment.NewLine, TestUtils.ReadText("cfs_text_file_1"));
|
||||
Assert.IsTrue(File.Exists("text_file_1"));
|
||||
Assert.AreEqual("Hello World!"+Environment.NewLine, File.ReadAllText("text_file_1"));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestEntryWriteWithDirectory(){
|
||||
if (Directory.Exists("cfs_directory")){
|
||||
Directory.Delete("cfs_directory", true);
|
||||
if (Directory.Exists("directory")){
|
||||
Directory.Delete("directory", true);
|
||||
}
|
||||
|
||||
TestUtils.WriteText("cfs_input_dir_1", "test");
|
||||
File.WriteAllText("input_dir_1", "test");
|
||||
|
||||
using(CombinedFileStream cfs = new CombinedFileStream(TestUtils.WriteFile("cfs_dir_test"))){
|
||||
cfs.WriteFile("key1", "cfs_input_dir_1");
|
||||
cfs.WriteFile("key2", "cfs_input_dir_1");
|
||||
cfs.WriteFile("key3", "cfs_input_dir_1");
|
||||
cfs.WriteFile("key4", "cfs_input_dir_1");
|
||||
using(CombinedFileStream cfs = new CombinedFileStream(File.OpenWrite("dir_test"))){
|
||||
cfs.WriteFile("key1", "input_dir_1");
|
||||
cfs.WriteFile("key2", "input_dir_1");
|
||||
cfs.WriteFile("key3", "input_dir_1");
|
||||
cfs.WriteFile("key4", "input_dir_1");
|
||||
cfs.Flush();
|
||||
}
|
||||
|
||||
Assert.IsTrue(File.Exists("cfs_dir_test"));
|
||||
Assert.IsTrue(File.Exists("dir_test"));
|
||||
|
||||
using(CombinedFileStream cfs = new CombinedFileStream(TestUtils.ReadFile("cfs_dir_test"))){
|
||||
using(CombinedFileStream cfs = new CombinedFileStream(File.OpenRead("dir_test"))){
|
||||
try{
|
||||
cfs.ReadFile().WriteToFile("cfs_directory/cfs_dir_test_file", false);
|
||||
cfs.ReadFile().WriteToFile("directory/dir_test_file", false);
|
||||
Assert.Fail("WriteToFile did not trigger an exception.");
|
||||
}catch(DirectoryNotFoundException){}
|
||||
|
||||
cfs.ReadFile().WriteToFile("cfs_directory/cfs_dir_test_file", true);
|
||||
cfs.ReadFile().WriteToFile("cfs_dir_test_file", true);
|
||||
cfs.ReadFile().WriteToFile("cfs_dir_test_file.txt", true);
|
||||
TestUtils.DeleteFileOnExit("cfs_dir_test_file");
|
||||
TestUtils.DeleteFileOnExit("cfs_dir_test_file.txt");
|
||||
cfs.ReadFile().WriteToFile("directory/dir_test_file", true);
|
||||
cfs.ReadFile().WriteToFile("dir_test_file", true);
|
||||
cfs.ReadFile().WriteToFile("dir_test_file.txt", true);
|
||||
}
|
||||
|
||||
Assert.IsTrue(Directory.Exists("cfs_directory"));
|
||||
Assert.IsTrue(File.Exists("cfs_directory/cfs_dir_test_file"));
|
||||
Assert.IsTrue(File.Exists("cfs_dir_test_file"));
|
||||
Assert.IsTrue(File.Exists("cfs_dir_test_file.txt"));
|
||||
Assert.AreEqual("test", TestUtils.ReadText("cfs_directory/cfs_dir_test_file"));
|
||||
Assert.AreEqual("test", TestUtils.ReadText("cfs_dir_test_file"));
|
||||
Assert.AreEqual("test", TestUtils.ReadText("cfs_dir_test_file.txt"));
|
||||
Assert.IsTrue(Directory.Exists("directory"));
|
||||
Assert.IsTrue(File.Exists("directory/dir_test_file"));
|
||||
Assert.IsTrue(File.Exists("dir_test_file"));
|
||||
Assert.IsTrue(File.Exists("dir_test_file.txt"));
|
||||
Assert.AreEqual("test", File.ReadAllText("directory/dir_test_file"));
|
||||
Assert.AreEqual("test", File.ReadAllText("dir_test_file"));
|
||||
Assert.AreEqual("test", File.ReadAllText("dir_test_file.txt"));
|
||||
|
||||
Directory.Delete("cfs_directory", true);
|
||||
Directory.Delete("directory", true);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestLongIdentifierSuccess(){
|
||||
TestUtils.WriteText("cfs_long_identifier_fail_in", "test");
|
||||
File.WriteAllText("long_identifier_fail_in", "test");
|
||||
|
||||
string identifier = string.Join("", Enumerable.Repeat("x", 255));
|
||||
|
||||
using(CombinedFileStream cfs = new CombinedFileStream(TestUtils.WriteFile("cfs_long_identifier_success"))){
|
||||
cfs.WriteFile(identifier, "cfs_long_identifier_fail_in");
|
||||
using(CombinedFileStream cfs = new CombinedFileStream(File.OpenWrite("long_identifier_success"))){
|
||||
cfs.WriteFile(identifier, "long_identifier_fail_in");
|
||||
cfs.Flush();
|
||||
}
|
||||
|
||||
using(CombinedFileStream cfs = new CombinedFileStream(TestUtils.ReadFile("cfs_long_identifier_success"))){
|
||||
using(CombinedFileStream cfs = new CombinedFileStream(File.OpenRead("long_identifier_success"))){
|
||||
Assert.AreEqual(identifier, cfs.ReadFile().Identifier);
|
||||
}
|
||||
}
|
||||
@@ -160,10 +155,10 @@ namespace UnitTests.Data{
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void TestLongIdentifierFail(){
|
||||
TestUtils.WriteText("cfs_long_identifier_fail_in", "test");
|
||||
File.WriteAllText("long_identifier_fail_in", "test");
|
||||
|
||||
using(CombinedFileStream cfs = new CombinedFileStream(TestUtils.WriteFile("cfs_long_identifier_fail"))){
|
||||
cfs.WriteFile(string.Join("", Enumerable.Repeat("x", 256)), "cfs_long_identifier_fail_in");
|
||||
using(CombinedFileStream cfs = new CombinedFileStream(File.OpenWrite("long_identifier_fail"))){
|
||||
cfs.WriteFile(string.Join("", Enumerable.Repeat("x", 256)), "long_identifier_fail_in");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -5,7 +5,7 @@ using TweetDuck.Data.Serialization;
|
||||
|
||||
namespace UnitTests.Data{
|
||||
[TestClass]
|
||||
public class TestFileSerializer{
|
||||
public class TestFileSerializer : UnitTestIO{
|
||||
private enum TestEnum{
|
||||
A, B, C, D, E
|
||||
}
|
||||
@@ -30,13 +30,11 @@ namespace UnitTests.Data{
|
||||
TestEnum = TestEnum.D
|
||||
};
|
||||
|
||||
serializer.Write("serialized_basic", write);
|
||||
|
||||
Assert.IsTrue(File.Exists("serialized_basic"));
|
||||
TestUtils.DeleteFileOnExit("serialized_basic");
|
||||
serializer.Write("basic_wr", write);
|
||||
Assert.IsTrue(File.Exists("basic_wr"));
|
||||
|
||||
SerializationTestBasic read = new SerializationTestBasic();
|
||||
serializer.Read("serialized_basic", read);
|
||||
serializer.Read("basic_wr", read);
|
||||
|
||||
Assert.IsTrue(read.TestBool);
|
||||
Assert.AreEqual(-100, read.TestInt);
|
||||
|
@@ -1,65 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Linq;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace UnitTests{
|
||||
public static class TestUtils{
|
||||
private static readonly HashSet<string> CreatedFiles = new HashSet<string>();
|
||||
|
||||
public static void WriteText(string file, string text){
|
||||
DeleteFileOnExit(file);
|
||||
File.WriteAllText(file, text, Encoding.UTF8);
|
||||
}
|
||||
|
||||
public static void WriteLines(string file, IEnumerable<string> lines){
|
||||
DeleteFileOnExit(file);
|
||||
File.WriteAllLines(file, lines, Encoding.UTF8);
|
||||
}
|
||||
|
||||
public static FileStream WriteFile(string file){
|
||||
DeleteFileOnExit(file);
|
||||
return new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None);
|
||||
}
|
||||
|
||||
public static string ReadText(string file){
|
||||
try{
|
||||
return File.ReadAllText(file, Encoding.UTF8);
|
||||
}catch(Exception){
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<string> ReadLines(string file){
|
||||
try{
|
||||
return File.ReadLines(file, Encoding.UTF8);
|
||||
}catch(Exception){
|
||||
return Enumerable.Empty<string>();
|
||||
}
|
||||
}
|
||||
|
||||
public static FileStream ReadFile(string file){
|
||||
return new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.None);
|
||||
}
|
||||
|
||||
public static void DeleteFileOnExit(string file){
|
||||
CreatedFiles.Add(file);
|
||||
}
|
||||
|
||||
[TestClass]
|
||||
public static class Cleanup{
|
||||
[AssemblyCleanup]
|
||||
public static void DeleteFilesOnExit(){
|
||||
foreach(string file in CreatedFiles){
|
||||
try{
|
||||
File.Delete(file);
|
||||
}catch(Exception){
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user