mirror of
https://github.com/chylex/TweetDuck.git
synced 2025-09-14 01:32:10 +02:00
Compare commits
322 Commits
Author | SHA1 | Date | |
---|---|---|---|
c74c168c96 | |||
40b53fa40c | |||
3481cc0349 | |||
09abd889e9 | |||
330bbfbb31 | |||
6b7b690476 | |||
cea72801a7 | |||
04369e22a7 | |||
f1b16eab9a | |||
13646d9c90 | |||
17d762ce91 | |||
edb40adaa1 | |||
bc0809994a | |||
a3e3d517b0 | |||
d8b63a54ca | |||
b81e7583eb | |||
51f9ba3642 | |||
296626f7c7 | |||
5b2daf9746 | |||
9a6b615174 | |||
18f8d5b269 | |||
2867a875c9 | |||
ee2f5ae8cb | |||
bd5c301fb9 | |||
6df68629f7 | |||
be08fd4445 | |||
6f1afb94fb | |||
7401b8a52d | |||
c83b62ebaa | |||
108cf8923e | |||
4e26fd9d56 | |||
8c9168a4bf | |||
97da0b79e4 | |||
d7e5f6876b | |||
1b92b112e2 | |||
ca55119531 | |||
d9da14b5dc | |||
512b5666ac | |||
64977964e8 | |||
2bc13e0de6 | |||
b90c5f17cf | |||
7d8d0bd43b | |||
![]() |
54c1137927 | ||
e6655219ee | |||
5896f8e35a | |||
934cba7251 | |||
9cc1a11bef | |||
c1bc956d6d | |||
351b174b86 | |||
0b4aaf80dc | |||
c10c185817 | |||
327ef1cbee | |||
15eb823c7f | |||
54613e5242 | |||
df1352cbe3 | |||
0559afd972 | |||
afffca020e | |||
d663cc3f64 | |||
110d41e393 | |||
1a8823f592 | |||
6374a852b0 | |||
a10c7dd7c3 | |||
547c7ea417 | |||
760607995a | |||
4704197c09 | |||
093ac1ac40 | |||
9ed8b0d904 | |||
7346ce370d | |||
adefdadc19 | |||
703bce2d00 | |||
97928ecd84 | |||
be9ea7f64a | |||
ec2aaa8789 | |||
ab14b72526 | |||
d8e304f3c1 | |||
ea53ce361f | |||
2fce80b347 | |||
373c0b1cc3 | |||
e5e1b7e608 | |||
7e9221c9e0 | |||
6b849f854e | |||
831f6bc744 | |||
d282a7a537 | |||
fb2f1e3031 | |||
00a0da3df3 | |||
8c447b1ffb | |||
a4841175e8 | |||
9b139132a1 | |||
4a404ecabc | |||
aee758b559 | |||
be060d0386 | |||
0195378c10 | |||
bc804c6a53 | |||
76b15f1971 | |||
c4d43c9d5b | |||
e8d3e530de | |||
e145adec58 | |||
e2dad3e477 | |||
27bdbde171 | |||
e9ec27169c | |||
2e24cb634c | |||
beb9046055 | |||
e57301952c | |||
7411279e48 | |||
16acfa85b5 | |||
41ef37f3f0 | |||
00d8538726 | |||
6eeb3f9895 | |||
d19dca6ea5 | |||
2008ccdaa4 | |||
ba2e62de3a | |||
2b62eb254d | |||
31f72b7957 | |||
fdc4616875 | |||
b7de261d25 | |||
ae78a5a026 | |||
fd2cf5d4d7 | |||
9f0997be1a | |||
dbade7f854 | |||
3cdc1e190a | |||
36bede7211 | |||
46689bb700 | |||
13e1a6543c | |||
820ce9e845 | |||
f17806f4e8 | |||
3f5ffc9e10 | |||
aeb0842ab4 | |||
38837ae84c | |||
a4eb6935af | |||
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 | |||
5e9ed5d713 | |||
78e492c764 | |||
59c2a3642b | |||
40ca923745 | |||
03af6cecaa | |||
3992e447f4 | |||
14a9edeb73 | |||
92f1e9f7ec | |||
19c294c53e | |||
fe88ea5c05 | |||
c9d551213a | |||
1e86a33ceb | |||
551dd229f5 | |||
5ecf3c4147 | |||
91bb2f4df0 | |||
ae3a0ae83d | |||
63ce7523de | |||
9e3b92bfc1 | |||
bc1767fb84 | |||
f917096cc7 | |||
308926a2ae | |||
76f2b1a454 | |||
d899e4b38b | |||
e1422e35cc | |||
2c00c6bb81 | |||
7e56ba6408 | |||
8ceb70e67d | |||
37d5efef1d | |||
924065c26e | |||
58cc7ea10d | |||
f93e275ddf | |||
06d2a5f715 | |||
3a7455eafe | |||
8b676fe6ce | |||
54d12686af | |||
f231256402 | |||
410ead66f8 | |||
c833a810af | |||
50f1336b1d | |||
60ed0b8cde | |||
cc55a81c1b | |||
f832e04e9e | |||
fc760b9a0c | |||
9addff0521 | |||
dcaa3aab19 | |||
628785c68c | |||
a5aa396fda | |||
f53a9f05e3 | |||
7749b14156 | |||
c15f339718 | |||
775f590bfa | |||
76408ea56f | |||
a391d8ee83 | |||
48c38f6e1d | |||
37c5fba162 | |||
23e99b1d44 | |||
8432240a47 | |||
a4bab743d6 | |||
60766789ab | |||
ca014f881c | |||
886eabe26c | |||
65b7167b5f | |||
abbdde851e | |||
54ac54aba6 | |||
184340f400 | |||
93dd6813e8 | |||
b689b08711 | |||
1479a097d6 | |||
b2be530f6b | |||
e4967ea46d | |||
3f28f18fb4 | |||
1b90e0f65e | |||
756ed649e6 | |||
fbc423e2a7 | |||
f04cdb6a13 |
5
.gitattributes
vendored
Normal file
5
.gitattributes
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
||||
|
||||
*.txt text eof=lf
|
||||
*.cs diff=csharp
|
@@ -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,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
@@ -135,13 +135,11 @@ namespace TweetDuck.Configuration{
|
||||
// Locking process
|
||||
|
||||
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);
|
||||
if (lockingProcess != null && lockingProcess.MainWindowHandle == IntPtr.Zero){ // restore if the original process is in tray
|
||||
NativeMethods.BroadcastMessage(Program.WindowRestoreMessage, (uint)lockingProcess.Id, 0);
|
||||
|
||||
if (WindowsUtils.TrySleepUntil(() => CheckLockingProcessExited() || (lockingProcess.MainWindowHandle != IntPtr.Zero && lockingProcess.Responding), failTimeout, RetryDelay)){
|
||||
return true;
|
||||
}
|
||||
if (WindowsUtils.TrySleepUntil(() => CheckLockingProcessExited() || (lockingProcess.MainWindowHandle != IntPtr.Zero && lockingProcess.Responding), failTimeout, RetryDelay)){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,15 +163,12 @@ namespace TweetDuck.Configuration{
|
||||
lockingProcess = null;
|
||||
return true;
|
||||
}
|
||||
}catch(Exception ex){
|
||||
if (ex is InvalidOperationException || ex is Win32Exception){
|
||||
if (lockingProcess != null){
|
||||
bool hasExited = CheckLockingProcessExited();
|
||||
lockingProcess.Dispose();
|
||||
return hasExited;
|
||||
}
|
||||
}catch(Exception ex) when (ex is InvalidOperationException || ex is Win32Exception){
|
||||
if (lockingProcess != null){
|
||||
bool hasExited = CheckLockingProcessExited();
|
||||
lockingProcess.Dispose();
|
||||
return hasExited;
|
||||
}
|
||||
else throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,13 +1,10 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetDuck.Data.Serialization;
|
||||
|
||||
namespace TweetDuck.Configuration{
|
||||
sealed class SystemConfig{
|
||||
private static readonly FileSerializer<SystemConfig> Serializer = new FileSerializer<SystemConfig>{
|
||||
OnReadUnknownProperty = (obj, property, value) => false
|
||||
};
|
||||
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"));
|
||||
@@ -16,6 +13,9 @@ namespace TweetDuck.Configuration{
|
||||
|
||||
private bool _hardwareAcceleration = true;
|
||||
|
||||
public bool EnableBrowserGCReload { get; set; } = true;
|
||||
public int BrowserMemoryThreshold { get; set; } = 400;
|
||||
|
||||
// SPECIAL PROPERTIES
|
||||
|
||||
public bool HardwareAcceleration{
|
||||
@@ -31,14 +31,11 @@ namespace TweetDuck.Configuration{
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
public bool Save(){
|
||||
public void Save(){
|
||||
try{
|
||||
WindowsUtils.CreateDirectoryForFile(file);
|
||||
Serializer.Write(file, this);
|
||||
return true;
|
||||
}catch(Exception e){
|
||||
Program.Reporter.HandleException("Configuration Error", "Could not save the system configuration file.", true, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,20 +43,7 @@ namespace TweetDuck.Configuration{
|
||||
SystemConfig config = new SystemConfig(file);
|
||||
|
||||
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);
|
||||
}
|
||||
Serializer.ReadIfExists(file, config);
|
||||
}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);
|
||||
}
|
||||
|
@@ -1,18 +1,16 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using TweetDuck.Core;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Notification;
|
||||
using TweetDuck.Core.Other;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetDuck.Data;
|
||||
using TweetDuck.Data.Serialization;
|
||||
|
||||
namespace TweetDuck.Configuration{
|
||||
sealed class UserConfig{
|
||||
private static readonly FileSerializer<UserConfig> Serializer = new FileSerializer<UserConfig>{
|
||||
OnReadUnknownProperty = (obj, property, value) => false
|
||||
};
|
||||
private static readonly FileSerializer<UserConfig> Serializer = new FileSerializer<UserConfig>();
|
||||
|
||||
static UserConfig(){
|
||||
Serializer.RegisterTypeConverter(typeof(WindowState), WindowState.Converter);
|
||||
@@ -36,18 +34,25 @@ namespace TweetDuck.Configuration{
|
||||
|
||||
// CONFIGURATION DATA
|
||||
|
||||
public bool FirstRun { get; set; } = true;
|
||||
public bool AllowDataCollection { get; set; } = false;
|
||||
public bool ShowDataCollectionNotification { get; set; } = true;
|
||||
|
||||
public WindowState BrowserWindow { get; set; } = new WindowState();
|
||||
public WindowState PluginsWindow { get; set; } = new WindowState();
|
||||
|
||||
public bool ExpandLinksOnHover { get; set; } = true;
|
||||
public bool SwitchAccountSelectors { get; set; } = true;
|
||||
public bool BestImageQuality { get; set; } = true;
|
||||
public bool EnableSpellCheck { get; set; } = false;
|
||||
private int _zoomLevel = 100;
|
||||
public bool ExpandLinksOnHover { get; set; } = true;
|
||||
public bool SwitchAccountSelectors { get; set; } = true;
|
||||
public bool OpenSearchInFirstColumn { get; set; } = true;
|
||||
public bool BestImageQuality { get; set; } = true;
|
||||
public bool EnableAnimatedImages { get; set; } = true;
|
||||
public bool EnableSpellCheck { get; set; } = false;
|
||||
public int VideoPlayerVolume { get; set; } = 50;
|
||||
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;
|
||||
@@ -71,14 +76,12 @@ 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;
|
||||
public string CustomBrowserCSS { get; set; } = null;
|
||||
public string CustomNotificationCSS { get; set; } = null;
|
||||
|
||||
public bool EnableBrowserGCReload { get; set; } = false;
|
||||
public int BrowserMemoryThreshold { get; set; } = 350;
|
||||
|
||||
// SPECIAL PROPERTIES
|
||||
|
||||
@@ -98,7 +101,7 @@ namespace TweetDuck.Configuration{
|
||||
set{
|
||||
if (_muteNotifications != value){
|
||||
_muteNotifications = value;
|
||||
MuteToggled?.Invoke(this, new EventArgs());
|
||||
MuteToggled?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -109,20 +112,18 @@ namespace TweetDuck.Configuration{
|
||||
set{
|
||||
if (_zoomLevel != value){
|
||||
_zoomLevel = value;
|
||||
ZoomLevelChanged?.Invoke(this, new EventArgs());
|
||||
ZoomLevelChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public double ZoomMultiplier => _zoomLevel/100.0;
|
||||
|
||||
|
||||
public TrayIcon.Behavior TrayBehavior{
|
||||
get => _trayBehavior;
|
||||
|
||||
set{
|
||||
if (_trayBehavior != value){
|
||||
_trayBehavior = value;
|
||||
TrayBehaviorChanged?.Invoke(this, new EventArgs());
|
||||
TrayBehaviorChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -137,14 +138,12 @@ namespace TweetDuck.Configuration{
|
||||
|
||||
private readonly string file;
|
||||
|
||||
public UserConfig(string file){ // TODO make private after removing UserConfigLegacy
|
||||
private UserConfig(string file){
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
public bool Save(){
|
||||
public void Save(){
|
||||
try{
|
||||
WindowsUtils.CreateDirectoryForFile(file);
|
||||
|
||||
if (File.Exists(file)){
|
||||
string backupFile = GetBackupFile(file);
|
||||
File.Delete(backupFile);
|
||||
@@ -152,12 +151,29 @@ namespace TweetDuck.Configuration{
|
||||
}
|
||||
|
||||
Serializer.Write(file, this);
|
||||
return true;
|
||||
}catch(Exception e){
|
||||
Program.Reporter.HandleException("Configuration Error", "Could not save the configuration file.", true, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void Reload(){
|
||||
try{
|
||||
LoadInternal(false);
|
||||
}catch(FileNotFoundException){
|
||||
try{
|
||||
Serializer.Write(file, new UserConfig(file));
|
||||
LoadInternal(false);
|
||||
}catch(Exception e){
|
||||
Program.Reporter.HandleException("Configuration Error", "Could not regenerate configuration file.", true, e);
|
||||
}
|
||||
}catch(Exception e){
|
||||
Program.Reporter.HandleException("Configuration Error", "Could not reload configuration file.", true, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadInternal(bool backup){
|
||||
Serializer.Read(backup ? GetBackupFile(file) : file, this);
|
||||
}
|
||||
|
||||
public static UserConfig Load(string file){
|
||||
Exception firstException = null;
|
||||
@@ -165,22 +181,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();
|
||||
}
|
||||
}
|
||||
}
|
@@ -8,25 +8,26 @@ namespace TweetDuck.Core.Bridge{
|
||||
|
||||
public static string GenerateScript(Environment environment){
|
||||
string Bool(bool value){
|
||||
return value ? "true," : "false,";
|
||||
return value ? "true;" : "false;";
|
||||
}
|
||||
|
||||
StringBuilder build = new StringBuilder().Append("window.$TDX={");
|
||||
StringBuilder build = new StringBuilder().Append("(function(x){");
|
||||
|
||||
build.Append("expandLinksOnHover:").Append(Bool(Program.UserConfig.ExpandLinksOnHover));
|
||||
build.Append("x.expandLinksOnHover=").Append(Bool(Program.UserConfig.ExpandLinksOnHover));
|
||||
|
||||
if (environment == Environment.Browser){
|
||||
build.Append("switchAccountSelectors:").Append(Bool(Program.UserConfig.SwitchAccountSelectors));
|
||||
build.Append("muteNotifications:").Append(Bool(Program.UserConfig.MuteNotifications));
|
||||
build.Append("hasCustomNotificationSound:").Append(Bool(Program.UserConfig.NotificationSoundPath.Length > 0));
|
||||
build.Append("notificationMediaPreviews:").Append(Bool(Program.UserConfig.NotificationMediaPreviews));
|
||||
build.Append("x.switchAccountSelectors=").Append(Bool(Program.UserConfig.SwitchAccountSelectors));
|
||||
build.Append("x.openSearchInFirstColumn=").Append(Bool(Program.UserConfig.OpenSearchInFirstColumn));
|
||||
build.Append("x.muteNotifications=").Append(Bool(Program.UserConfig.MuteNotifications));
|
||||
build.Append("x.hasCustomNotificationSound=").Append(Bool(Program.UserConfig.NotificationSoundPath.Length > 0));
|
||||
build.Append("x.notificationMediaPreviews=").Append(Bool(Program.UserConfig.NotificationMediaPreviews));
|
||||
}
|
||||
|
||||
if (environment == Environment.Notification){
|
||||
build.Append("skipOnLinkClick:").Append(Bool(Program.UserConfig.NotificationSkipOnLinkClick));
|
||||
build.Append("x.skipOnLinkClick=").Append(Bool(Program.UserConfig.NotificationSkipOnLinkClick));
|
||||
}
|
||||
|
||||
return build.Append("}").ToString();
|
||||
return build.Append("})(window.$TDX=window.$TDX||{})").ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,20 +1,45 @@
|
||||
using System.Windows.Forms;
|
||||
using System.Collections.Generic;
|
||||
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;
|
||||
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[] LastHighlightedTweetImages = StringUtils.EmptyArray;
|
||||
public static string FontSize { get; private set; }
|
||||
public static string NotificationHeadLayout { get; private set; }
|
||||
|
||||
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 = string.Empty;
|
||||
LastHighlightedTweetImages = StringUtils.EmptyArray;
|
||||
FontSize = NotificationHeadLayout = null;
|
||||
LastHighlightedTweetUrl = LastHighlightedQuoteUrl = LastHighlightedTweetAuthors = LastHighlightedTweetImages = string.Empty;
|
||||
}
|
||||
|
||||
public static void RestoreSessionData(IFrame frame){
|
||||
if (SessionData.Count > 0){
|
||||
StringBuilder build = new StringBuilder().Append("window.TD_SESSION={");
|
||||
|
||||
foreach(KeyValuePair<string, string> kvp in SessionData){
|
||||
build.Append(kvp.Key).Append(":'").Append(kvp.Value.Replace("'", "\\'")).Append("',");
|
||||
}
|
||||
|
||||
ScriptLoader.ExecuteScript(frame, build.Append("}").ToString(), "gen:session");
|
||||
SessionData.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private readonly FormBrowser form;
|
||||
@@ -25,31 +50,29 @@ namespace TweetDuck.Core.Bridge{
|
||||
this.notification = notification;
|
||||
}
|
||||
|
||||
public void LoadFontSizeClass(string fsClass){
|
||||
public void OnIntroductionClosed(bool showGuide, bool allowDataCollection){
|
||||
form.InvokeAsyncSafe(() => {
|
||||
TweetNotification.SetFontSizeClass(fsClass);
|
||||
form.OnIntroductionClosed(showGuide, allowDataCollection);
|
||||
});
|
||||
}
|
||||
|
||||
public void LoadNotificationHeadContents(string headContents){
|
||||
public void LoadNotificationLayout(string fontSize, string headLayout){
|
||||
form.InvokeAsyncSafe(() => {
|
||||
TweetNotification.SetHeadTag(headContents);
|
||||
FontSize = fontSize;
|
||||
NotificationHeadLayout = headLayout;
|
||||
});
|
||||
}
|
||||
|
||||
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 imageList){
|
||||
public void SetLastHighlightedTweet(string tweetUrl, string quoteUrl, string authors, string imageList){
|
||||
form.InvokeAsyncSafe(() => {
|
||||
LastHighlightedTweet = link;
|
||||
LastHighlightedQuotedTweet = quotedLink;
|
||||
LastHighlightedTweetImages = imageList.Split(';');
|
||||
LastHighlightedTweetUrl = tweetUrl;
|
||||
LastHighlightedQuoteUrl = quoteUrl;
|
||||
LastHighlightedTweetAuthors = authors;
|
||||
LastHighlightedTweetImages = imageList;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -57,10 +80,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));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -80,6 +103,12 @@ namespace TweetDuck.Core.Bridge{
|
||||
}
|
||||
}
|
||||
|
||||
public void SetSessionData(string key, string value){
|
||||
form.InvokeSafe(() => { // do not use InvokeAsyncSafe, return only after invocation
|
||||
SessionData.Add(key, value);
|
||||
});
|
||||
}
|
||||
|
||||
public void LoadNextNotification(){
|
||||
notification.InvokeAsyncSafe(notification.FinishCurrentNotification);
|
||||
}
|
||||
@@ -88,16 +117,20 @@ namespace TweetDuck.Core.Bridge{
|
||||
form.InvokeAsyncSafe(() => form.OnTweetScreenshotReady(html, width, height));
|
||||
}
|
||||
|
||||
public void PlayVideo(string url, string username){
|
||||
form.InvokeAsyncSafe(() => form.PlayVideo(url, username));
|
||||
}
|
||||
|
||||
public void FixClipboard(){
|
||||
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){
|
||||
@@ -115,13 +148,9 @@ namespace TweetDuck.Core.Bridge{
|
||||
|
||||
public void CrashDebug(string message){
|
||||
#if DEBUG
|
||||
Log(message);
|
||||
System.Diagnostics.Debug.WriteLine(message);
|
||||
System.Diagnostics.Debugger.Break();
|
||||
#endif
|
||||
}
|
||||
|
||||
public void Log(string data){
|
||||
System.Diagnostics.Debug.WriteLine(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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(){
|
||||
|
@@ -1,4 +1,5 @@
|
||||
using System.Drawing;
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace TweetDuck.Core.Controls{
|
||||
@@ -13,7 +14,7 @@ namespace TweetDuck.Core.Controls{
|
||||
}
|
||||
|
||||
public void SetValueInstant(int value){
|
||||
ControlExtensions.SetValueInstant(this, value);
|
||||
ControlExtensions.SetValueInstant(this, Math.Max(Minimum, Math.Min(Maximum, value)));
|
||||
}
|
||||
|
||||
protected override void OnPaint(PaintEventArgs e){
|
||||
@@ -23,7 +24,7 @@ namespace TweetDuck.Core.Controls{
|
||||
|
||||
Rectangle rect = e.ClipRectangle;
|
||||
rect.Width = (int)(rect.Width*((double)Value/Maximum));
|
||||
e.Graphics.FillRectangle(brush,rect);
|
||||
e.Graphics.FillRectangle(brush, rect);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing){
|
||||
|
8
Core/FormBrowser.Designer.cs
generated
8
Core/FormBrowser.Designer.cs
generated
@@ -24,7 +24,7 @@
|
||||
/// </summary>
|
||||
private void InitializeComponent() {
|
||||
this.components = new System.ComponentModel.Container();
|
||||
this.trayIcon = new TweetDuck.Core.TrayIcon(this.components);
|
||||
this.trayIcon = new TweetDuck.Core.Other.TrayIcon(this.components);
|
||||
this.toolTip = new System.Windows.Forms.ToolTip(this.components);
|
||||
this.timerResize = new System.Windows.Forms.Timer(this.components);
|
||||
this.SuspendLayout();
|
||||
@@ -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);
|
||||
@@ -57,7 +57,7 @@
|
||||
|
||||
#endregion
|
||||
|
||||
private TrayIcon trayIcon;
|
||||
private TweetDuck.Core.Other.TrayIcon trayIcon;
|
||||
private System.Windows.Forms.ToolTip toolTip;
|
||||
private System.Windows.Forms.Timer timerResize;
|
||||
}
|
||||
|
@@ -1,25 +1,20 @@
|
||||
using CefSharp;
|
||||
using CefSharp.WinForms;
|
||||
using System;
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Configuration;
|
||||
using TweetDuck.Core.Bridge;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Handling;
|
||||
using TweetDuck.Core.Handling.General;
|
||||
using TweetDuck.Core.Notification;
|
||||
using TweetDuck.Core.Notification.Screenshot;
|
||||
using TweetDuck.Core.Other;
|
||||
using TweetDuck.Core.Other.Analytics;
|
||||
using TweetDuck.Core.Other.Management;
|
||||
using TweetDuck.Core.Other.Settings;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetDuck.Plugins;
|
||||
using TweetDuck.Plugins.Enums;
|
||||
using TweetDuck.Plugins.Events;
|
||||
using TweetDuck.Resources;
|
||||
using TweetDuck.Updates;
|
||||
using TweetDuck.Updates.Events;
|
||||
using TweetLib.Audio;
|
||||
|
||||
namespace TweetDuck.Core{
|
||||
@@ -45,75 +40,48 @@ namespace TweetDuck.Core{
|
||||
|
||||
public string UpdateInstallerPath { get; private set; }
|
||||
|
||||
private readonly ChromiumWebBrowser browser;
|
||||
private readonly TweetDeckBrowser browser;
|
||||
private readonly PluginManager plugins;
|
||||
private readonly UpdateHandler updates;
|
||||
private readonly FormNotificationTweet notification;
|
||||
private readonly ContextMenu contextMenu;
|
||||
private readonly MemoryUsageTracker memoryUsageTracker;
|
||||
|
||||
private bool isLoaded;
|
||||
private bool isBrowserReady;
|
||||
private FormWindowState prevState;
|
||||
|
||||
|
||||
private TweetScreenshotManager notificationScreenshotManager;
|
||||
private SoundNotification soundNotification;
|
||||
private VideoPlayer videoPlayer;
|
||||
private AnalyticsManager analytics;
|
||||
|
||||
public FormBrowser(PluginManager pluginManager, UpdaterSettings updaterSettings){
|
||||
public FormBrowser(UpdaterSettings updaterSettings){
|
||||
InitializeComponent();
|
||||
|
||||
Text = Program.BrandName;
|
||||
|
||||
this.plugins = pluginManager;
|
||||
this.plugins = new PluginManager(Program.PluginPath, Program.PluginConfigFilePath);
|
||||
this.plugins.Reloaded += plugins_Reloaded;
|
||||
this.plugins.PluginChangedState += plugins_PluginChangedState;
|
||||
this.plugins.Executed += plugins_Executed;
|
||||
this.plugins.Reload();
|
||||
|
||||
this.notification = new FormNotificationTweet(this, plugins);
|
||||
this.notification.Show();
|
||||
|
||||
this.browser = new TweetDeckBrowser(this, plugins, new TweetDeckBridge(this, notification));
|
||||
this.browser.PageLoaded += browser_PageLoaded;
|
||||
|
||||
this.contextMenu = ContextMenuBrowser.CreateMenu(this);
|
||||
this.memoryUsageTracker = new MemoryUsageTracker("TDGF_tryRunCleanup");
|
||||
|
||||
this.notification = new FormNotificationTweet(this, plugins){
|
||||
#if DEBUG
|
||||
CanMoveWindow = () => (ModifierKeys & Keys.Alt) == Keys.Alt
|
||||
#else
|
||||
CanMoveWindow = () => false
|
||||
#endif
|
||||
};
|
||||
|
||||
this.notification.Show();
|
||||
|
||||
this.browser = new ChromiumWebBrowser("https://tweetdeck.twitter.com/"){
|
||||
MenuHandler = new ContextMenuBrowser(this),
|
||||
JsDialogHandler = new JavaScriptDialogHandler(),
|
||||
LifeSpanHandler = new LifeSpanHandler(),
|
||||
RequestHandler = new RequestHandlerBrowser()
|
||||
};
|
||||
|
||||
#if DEBUG
|
||||
this.browser.ConsoleMessage += BrowserUtils.HandleConsoleMessage;
|
||||
#endif
|
||||
|
||||
this.browser.LoadingStateChanged += browser_LoadingStateChanged;
|
||||
this.browser.FrameLoadStart += browser_FrameLoadStart;
|
||||
this.browser.FrameLoadEnd += browser_FrameLoadEnd;
|
||||
this.browser.LoadError += browser_LoadError;
|
||||
this.browser.RegisterAsyncJsObject("$TD", new TweetDeckBridge(this, notification));
|
||||
this.browser.RegisterAsyncJsObject("$TDP", plugins.Bridge);
|
||||
|
||||
browser.BrowserSettings.BackgroundColor = (uint)TwitterUtils.BackgroundColor.ToArgb();
|
||||
browser.Dock = DockStyle.None;
|
||||
browser.Location = ControlExtensions.InvisibleLocation;
|
||||
Controls.Add(browser);
|
||||
|
||||
Controls.Add(new MenuStrip{ Visible = false }); // fixes Alt freezing the program in Win 10 Anniversary Update
|
||||
|
||||
Disposed += (sender, args) => {
|
||||
memoryUsageTracker.Dispose();
|
||||
|
||||
browser.Dispose();
|
||||
contextMenu.Dispose();
|
||||
|
||||
notificationScreenshotManager?.Dispose();
|
||||
soundNotification?.Dispose();
|
||||
videoPlayer?.Dispose();
|
||||
analytics?.Dispose();
|
||||
};
|
||||
|
||||
this.trayIcon.ClickRestore += trayIcon_ClickRestore;
|
||||
@@ -122,26 +90,13 @@ namespace TweetDuck.Core{
|
||||
|
||||
UpdateTrayIcon();
|
||||
|
||||
Config.MuteToggled += Config_MuteToggled;
|
||||
Config.ZoomLevelChanged += Config_ZoomLevelChanged;
|
||||
|
||||
this.updates = new UpdateHandler(browser, updaterSettings);
|
||||
this.updates = browser.CreateUpdateHandler(updaterSettings);
|
||||
this.updates.UpdateAccepted += updates_UpdateAccepted;
|
||||
this.updates.UpdateDismissed += updates_UpdateDismissed;
|
||||
|
||||
RestoreWindow();
|
||||
}
|
||||
|
||||
private bool TryBringToFront<T>() where T : Form{
|
||||
T form = Application.OpenForms.OfType<T>().FirstOrDefault();
|
||||
|
||||
if (form != null){
|
||||
form.BringToFront();
|
||||
return true;
|
||||
}
|
||||
else return false;
|
||||
}
|
||||
|
||||
private void ShowChildForm(Form form){
|
||||
form.VisibleChanged += (sender, args) => form.MoveToCenter(this);
|
||||
form.Show(this);
|
||||
@@ -158,13 +113,9 @@ namespace TweetDuck.Core{
|
||||
Config.BrowserWindow.Restore(this, true);
|
||||
prevState = WindowState;
|
||||
isLoaded = true;
|
||||
}
|
||||
|
||||
private void OnBrowserReady(){
|
||||
if (!isBrowserReady){
|
||||
browser.Location = Point.Empty;
|
||||
browser.Dock = DockStyle.Fill;
|
||||
isBrowserReady = true;
|
||||
if (Config.AllowDataCollection){
|
||||
analytics = new AnalyticsManager(this, plugins, Program.AnalyticsFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,68 +123,7 @@ namespace TweetDuck.Core{
|
||||
trayIcon.Visible = Config.TrayBehavior.ShouldDisplayIcon();
|
||||
}
|
||||
|
||||
// active event handlers
|
||||
|
||||
private void browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e){
|
||||
if (!e.IsLoading){
|
||||
foreach(string word in TwitterUtils.DictionaryWords){
|
||||
browser.AddWordToDictionary(word);
|
||||
}
|
||||
|
||||
BeginInvoke(new Action(OnBrowserReady));
|
||||
browser.LoadingStateChanged -= browser_LoadingStateChanged;
|
||||
}
|
||||
}
|
||||
|
||||
private void browser_FrameLoadStart(object sender, FrameLoadStartEventArgs e){
|
||||
if (e.Frame.IsMain){
|
||||
memoryUsageTracker.Stop();
|
||||
|
||||
if (Config.ZoomLevel != 100){
|
||||
BrowserUtils.SetZoomLevel(browser.GetBrowser(), Config.ZoomLevel);
|
||||
}
|
||||
|
||||
if (TwitterUtils.IsTwitterWebsite(e.Frame)){
|
||||
ScriptLoader.ExecuteFile(e.Frame, "twitter.js");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
|
||||
if (e.Frame.IsMain && TwitterUtils.IsTweetDeckWebsite(e.Frame)){
|
||||
e.Frame.ExecuteJavaScriptAsync(TwitterUtils.BackgroundColorFix);
|
||||
|
||||
UpdateProperties(PropertyBridge.Environment.Browser);
|
||||
ScriptLoader.ExecuteFile(e.Frame, "code.js");
|
||||
ReinjectCustomCSS(Config.CustomBrowserCSS);
|
||||
|
||||
if (plugins.HasAnyPlugin(PluginEnvironment.Browser)){
|
||||
ScriptLoader.ExecuteFile(e.Frame, PluginManager.PluginBrowserScriptFile);
|
||||
ScriptLoader.ExecuteFile(e.Frame, PluginManager.PluginGlobalScriptFile);
|
||||
plugins.ExecutePlugins(e.Frame, PluginEnvironment.Browser, true);
|
||||
}
|
||||
|
||||
TweetDeckBridge.ResetStaticProperties();
|
||||
|
||||
if (Config.EnableBrowserGCReload){
|
||||
memoryUsageTracker.Start(this, e.Browser, Config.BrowserMemoryThreshold);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void browser_LoadError(object sender, LoadErrorEventArgs e){
|
||||
if (e.ErrorCode == CefErrorCode.Aborted){
|
||||
return;
|
||||
}
|
||||
|
||||
if (!e.FailedUrl.StartsWith("http://td/", StringComparison.Ordinal)){
|
||||
string errorPage = ScriptLoader.LoadResource("pages/error.html", true);
|
||||
|
||||
if (errorPage != null){
|
||||
browser.LoadHtml(errorPage.Replace("{err}", BrowserUtils.GetErrorName(e.ErrorCode)), "http://td/error");
|
||||
}
|
||||
}
|
||||
}
|
||||
// event handlers
|
||||
|
||||
private void timerResize_Tick(object sender, EventArgs e){
|
||||
FormBrowser_ResizeEnd(this, e); // also stops timer
|
||||
@@ -303,14 +193,6 @@ namespace TweetDuck.Core{
|
||||
}
|
||||
}
|
||||
|
||||
private void Config_MuteToggled(object sender, EventArgs e){
|
||||
UpdateProperties(PropertyBridge.Environment.Browser);
|
||||
}
|
||||
|
||||
private void Config_ZoomLevelChanged(object sender, EventArgs e){
|
||||
BrowserUtils.SetZoomLevel(browser.GetBrowser(), Config.ZoomLevel);
|
||||
}
|
||||
|
||||
private void Config_TrayBehaviorChanged(object sender, EventArgs e){
|
||||
UpdateTrayIcon();
|
||||
}
|
||||
@@ -325,22 +207,58 @@ namespace TweetDuck.Core{
|
||||
private void trayIcon_ClickClose(object sender, EventArgs e){
|
||||
ForceClose();
|
||||
}
|
||||
|
||||
private void browser_PageLoaded(object sender, EventArgs e){
|
||||
if (Config.ShowDataCollectionNotification){
|
||||
this.InvokeAsyncSafe(() => {
|
||||
if (!Config.FirstRun && Config.AllowDataCollection){
|
||||
FormMessage form = new FormMessage("Anonymous Data Update", "Hi! You can now review your anonymous data report, and opt-out if you've changed your mind. Collected data will be used to focus development on most commonly used features. If you want to opt-out but still support the project, any feedback and donations are appreciated.", MessageBoxIcon.Information);
|
||||
form.AddButton("OK", ControlType.Accept | ControlType.Focused);
|
||||
|
||||
Button btnReviewSettings = new Button{
|
||||
Anchor = AnchorStyles.Bottom | AnchorStyles.Left,
|
||||
Font = SystemFonts.MessageBoxFont,
|
||||
Location = new Point(9, 12),
|
||||
Margin = new Padding(0, 0, 48, 0),
|
||||
Size = new Size(160, 26),
|
||||
Text = "Review Feedback Options",
|
||||
UseVisualStyleBackColor = true
|
||||
};
|
||||
|
||||
btnReviewSettings.Click += (sender2, args2) => {
|
||||
form.Close();
|
||||
OpenSettings(typeof(TabSettingsFeedback));
|
||||
};
|
||||
|
||||
form.AddActionControl(btnReviewSettings);
|
||||
ShowChildForm(form);
|
||||
}
|
||||
|
||||
Config.ShowDataCollectionNotification = false;
|
||||
Config.Save();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void plugins_Reloaded(object sender, PluginErrorEventArgs e){
|
||||
browser.GetBrowser().Reload();
|
||||
if (e.HasErrors){
|
||||
FormMessage.Error("Error Loading Plugins", "The following plugins will not be available until the issues are resolved:\n\n"+string.Join("\n\n", e.Errors), FormMessage.OK);
|
||||
}
|
||||
|
||||
if (isLoaded){
|
||||
ReloadToTweetDeck();
|
||||
}
|
||||
}
|
||||
|
||||
private static void plugins_Executed(object sender, PluginErrorEventArgs e){
|
||||
if (e.HasErrors){
|
||||
FormMessage.Error("Error Executing Plugins", "Failed to execute the following plugins:\n\n"+string.Join("\n\n", e.Errors), FormMessage.OK);
|
||||
}
|
||||
}
|
||||
|
||||
private void plugins_PluginChangedState(object sender, PluginChangedStateEventArgs e){
|
||||
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(() => {
|
||||
foreach(Form form in Application.OpenForms.Cast<Form>().Reverse()){
|
||||
if (form is FormSettings || form is FormPlugins || form is FormAbout){
|
||||
form.Close();
|
||||
}
|
||||
}
|
||||
FormManager.CloseAllDialogs();
|
||||
|
||||
updates.BeginUpdateDownload(this, e.UpdateInfo, update => {
|
||||
if (update.DownloadStatus == UpdateDownloadStatus.Done){
|
||||
@@ -352,9 +270,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();
|
||||
});
|
||||
}
|
||||
@@ -379,7 +297,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;
|
||||
@@ -395,19 +313,22 @@ namespace TweetDuck.Core{
|
||||
}
|
||||
}
|
||||
|
||||
if (isBrowserReady && m.Msg == NativeMethods.WM_PARENTNOTIFY && (m.WParam.ToInt32() & 0xFFFF) == NativeMethods.WM_XBUTTONDOWN){
|
||||
browser.ExecuteScriptAsync("TDGF_onMouseClickExtra", (m.WParam.ToInt32() >> 16) & 0xFFFF);
|
||||
if (browser.Ready && m.Msg == NativeMethods.WM_PARENTNOTIFY && (m.WParam.ToInt32() & 0xFFFF) == NativeMethods.WM_XBUTTONDOWN){
|
||||
if (videoPlayer != null && videoPlayer.Running){
|
||||
videoPlayer.Close();
|
||||
}
|
||||
else{
|
||||
browser.OnMouseClickExtra(m.WParam);
|
||||
TriggerAnalyticsEvent(AnalyticsFile.Event.BrowserExtraMouseButton);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
base.WndProc(ref m);
|
||||
}
|
||||
|
||||
// notification helpers
|
||||
|
||||
public FormNotificationMain CreateNotificationForm(bool enableContextMenu){
|
||||
return new FormNotificationMain(this, plugins, enableContextMenu);
|
||||
}
|
||||
// bridge methods
|
||||
|
||||
public void PauseNotification(){
|
||||
notification.PauseNotification();
|
||||
@@ -416,22 +337,49 @@ namespace TweetDuck.Core{
|
||||
public void ResumeNotification(){
|
||||
notification.ResumeNotification();
|
||||
}
|
||||
|
||||
// javascript calls
|
||||
|
||||
|
||||
public void ReinjectCustomCSS(string css){
|
||||
browser.ExecuteScriptAsync("TDGF_reinjectCustomCSS", css?.Replace(Environment.NewLine, " ") ?? string.Empty);
|
||||
}
|
||||
|
||||
public void UpdateProperties(PropertyBridge.Environment environment){
|
||||
browser.ExecuteScriptAsync(PropertyBridge.GenerateScript(environment));
|
||||
browser.ReinjectCustomCSS(css);
|
||||
}
|
||||
|
||||
public void ReloadToTweetDeck(){
|
||||
browser.ExecuteScriptAsync($"gc&&gc();window.location.href='{TwitterUtils.TweetDeckURL}'");
|
||||
browser.ReloadToTweetDeck();
|
||||
}
|
||||
|
||||
public void TriggerTweetScreenshot(){
|
||||
browser.TriggerTweetScreenshot();
|
||||
}
|
||||
|
||||
public void ReloadColumns(){
|
||||
browser.ReloadColumns();
|
||||
}
|
||||
|
||||
public void ApplyROT13(){
|
||||
browser.ApplyROT13();
|
||||
}
|
||||
|
||||
public void TriggerAnalyticsEvent(AnalyticsFile.Event e){
|
||||
analytics?.TriggerEvent(e);
|
||||
}
|
||||
|
||||
// callback handlers
|
||||
|
||||
public void OnIntroductionClosed(bool showGuide, bool allowDataCollection){
|
||||
if (Config.FirstRun){
|
||||
Config.FirstRun = false;
|
||||
Config.AllowDataCollection = allowDataCollection;
|
||||
Config.ShowDataCollectionNotification = false;
|
||||
Config.Save();
|
||||
|
||||
if (allowDataCollection && analytics == null){
|
||||
analytics = new AnalyticsManager(this, plugins, Program.AnalyticsFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
if (showGuide){
|
||||
ShowChildForm(new FormGuide());
|
||||
}
|
||||
}
|
||||
|
||||
public void OpenContextMenu(){
|
||||
contextMenu.Show(this, PointToClient(Cursor.Position));
|
||||
@@ -442,46 +390,62 @@ namespace TweetDuck.Core{
|
||||
}
|
||||
|
||||
public void OpenSettings(Type startTab){
|
||||
if (!TryBringToFront<FormSettings>()){
|
||||
if (!FormManager.TryBringToFront<FormSettings>()){
|
||||
bool prevEnableUpdateCheck = Config.EnableUpdateCheck;
|
||||
|
||||
FormSettings form = new FormSettings(this, plugins, updates, startTab);
|
||||
FormSettings form = new FormSettings(this, plugins, updates, analytics, startTab);
|
||||
|
||||
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){
|
||||
trayIcon.HasNotifications = false;
|
||||
}
|
||||
|
||||
if (Config.EnableBrowserGCReload){
|
||||
memoryUsageTracker.Start(this, browser.GetBrowser(), Config.BrowserMemoryThreshold);
|
||||
browser.RefreshMemoryTracker();
|
||||
|
||||
if (Config.AllowDataCollection){
|
||||
if (analytics == null){
|
||||
analytics = new AnalyticsManager(this, plugins, Program.AnalyticsFilePath);
|
||||
}
|
||||
}
|
||||
else{
|
||||
memoryUsageTracker.Stop();
|
||||
else if (analytics != null){
|
||||
analytics.Dispose();
|
||||
analytics = null;
|
||||
}
|
||||
|
||||
UpdateProperties(PropertyBridge.Environment.Browser);
|
||||
if (form.ShouldReloadBrowser){
|
||||
FormManager.TryFind<FormPlugins>()?.Close();
|
||||
plugins.Reload(); // also reloads the browser
|
||||
}
|
||||
else{
|
||||
browser.UpdateProperties();
|
||||
}
|
||||
|
||||
notification.RequiresResize = true;
|
||||
form.Dispose();
|
||||
};
|
||||
|
||||
|
||||
TriggerAnalyticsEvent(AnalyticsFile.Event.OpenOptions);
|
||||
ShowChildForm(form);
|
||||
}
|
||||
}
|
||||
|
||||
public void OpenAbout(){
|
||||
if (!TryBringToFront<FormAbout>()){
|
||||
if (!FormManager.TryBringToFront<FormAbout>()){
|
||||
TriggerAnalyticsEvent(AnalyticsFile.Event.OpenAbout);
|
||||
ShowChildForm(new FormAbout());
|
||||
}
|
||||
}
|
||||
|
||||
public void OpenPlugins(){
|
||||
if (!TryBringToFront<FormPlugins>()){
|
||||
if (!FormManager.TryBringToFront<FormPlugins>()){
|
||||
TriggerAnalyticsEvent(AnalyticsFile.Event.OpenPlugins);
|
||||
ShowChildForm(new FormPlugins(plugins));
|
||||
}
|
||||
}
|
||||
@@ -502,7 +466,50 @@ namespace TweetDuck.Core{
|
||||
soundNotification.PlaybackError += soundNotification_PlaybackError;
|
||||
}
|
||||
|
||||
soundNotification.SetVolume(Config.NotificationSoundVolume);
|
||||
soundNotification.Play(Config.NotificationSoundPath);
|
||||
|
||||
TriggerAnalyticsEvent(AnalyticsFile.Event.SoundNotification);
|
||||
}
|
||||
|
||||
public void PlayVideo(string url, string username){
|
||||
if (string.IsNullOrEmpty(url)){
|
||||
videoPlayer?.Close();
|
||||
return;
|
||||
}
|
||||
|
||||
if (videoPlayer == null){
|
||||
videoPlayer = new VideoPlayer(this);
|
||||
|
||||
videoPlayer.ProcessExited += (sender, args) => {
|
||||
browser.HideVideoOverlay(true);
|
||||
};
|
||||
}
|
||||
|
||||
videoPlayer.Launch(url, username);
|
||||
TriggerAnalyticsEvent(AnalyticsFile.Event.VideoPlay);
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
if (!browser.IsTweetDeckWebsite){
|
||||
FormMessage.Error("View Tweet Detail", "TweetDeck is not currently loaded.", FormMessage.OK);
|
||||
return;
|
||||
}
|
||||
|
||||
notification.FinishCurrentNotification();
|
||||
browser.ShowTweetDetail(columnId, chirpId, fallbackUrl);
|
||||
TriggerAnalyticsEvent(AnalyticsFile.Event.TweetDetail);
|
||||
}
|
||||
|
||||
public void OnTweetScreenshotReady(string html, int width, int height){
|
||||
@@ -511,6 +518,7 @@ namespace TweetDuck.Core{
|
||||
}
|
||||
|
||||
notificationScreenshotManager.Trigger(html, width, height);
|
||||
TriggerAnalyticsEvent(AnalyticsFile.Event.TweetScreenshot);
|
||||
}
|
||||
|
||||
public void DisplayTooltip(string text){
|
||||
@@ -523,9 +531,5 @@ namespace TweetDuck.Core{
|
||||
toolTip.Show(text, this, position);
|
||||
}
|
||||
}
|
||||
|
||||
public void TriggerTweetScreenshot(){
|
||||
browser.ExecuteScriptAsync("TDGF_triggerScreenshot()");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
29
Core/FormManager.cs
Normal file
29
Core/FormManager.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
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 = TryFind<T>();
|
||||
|
||||
if (form != null){
|
||||
form.BringToFront();
|
||||
return true;
|
||||
}
|
||||
else return false;
|
||||
}
|
||||
|
||||
public static void CloseAllDialogs(){
|
||||
foreach(Form form in Application.OpenForms.Cast<Form>().Reverse()){
|
||||
if (form is FormSettings || form is FormPlugins || form is FormAbout || form is FormGuide){
|
||||
form.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -6,67 +6,88 @@ using CefSharp;
|
||||
using TweetDuck.Core.Bridge;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Utils;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using TweetDuck.Core.Other;
|
||||
|
||||
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 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 MenuViewImage = (CefMenuCommand)26503;
|
||||
private const CefMenuCommand MenuOpenMediaUrl = (CefMenuCommand)26504;
|
||||
private const CefMenuCommand MenuCopyMediaUrl = (CefMenuCommand)26505;
|
||||
private const CefMenuCommand MenuSaveMedia = (CefMenuCommand)26506;
|
||||
private const CefMenuCommand MenuSaveTweetImages = (CefMenuCommand)26507;
|
||||
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);
|
||||
lastHighlightedTweetImageList = TweetDeckBridge.LastHighlightedTweetImages;
|
||||
|
||||
if (!TwitterUtils.IsTweetDeckWebsite(frame) || browser.IsLoading){
|
||||
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(MenuViewImage, "View image in photo viewer");
|
||||
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();
|
||||
@@ -74,34 +95,66 @@ 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), ImageQuality);
|
||||
break;
|
||||
|
||||
case MenuSaveAllImages:
|
||||
TwitterUtils.DownloadImages(lastHighlightedTweetImageList, 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 MenuViewImage:
|
||||
string url = GetMediaLink(parameters);
|
||||
string file = Path.Combine(BrowserCache.CacheFolder, TwitterUtils.GetImageFileName(url));
|
||||
|
||||
void ViewFile(){
|
||||
string ext = Path.GetExtension(file);
|
||||
|
||||
if (TwitterUtils.ValidImageExtensions.Contains(ext)){
|
||||
WindowsUtils.OpenAssociatedProgram(file);
|
||||
}
|
||||
else{
|
||||
FormMessage.Error("Image Download", "Invalid file extension "+ext, FormMessage.OK);
|
||||
}
|
||||
}
|
||||
|
||||
if (File.Exists(file)){
|
||||
ViewFile();
|
||||
}
|
||||
else{
|
||||
BrowserUtils.DownloadFileAsync(TwitterUtils.GetMediaLink(url, ImageQuality), file, ViewFile, ex => {
|
||||
FormMessage.Error("Image Download", "An error occurred while downloading the image: "+ex.Message, FormMessage.OK);
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case MenuSaveMedia:
|
||||
if (IsVideo){
|
||||
TwitterUtils.DownloadVideo(GetMediaLink(parameters), lastHighlightedTweetAuthors.LastOrDefault());
|
||||
}
|
||||
else{
|
||||
TwitterUtils.DownloadImage(GetMediaLink(parameters), lastHighlightedTweetAuthors.LastOrDefault(), ImageQuality);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case MenuSaveTweetImages:
|
||||
TwitterUtils.DownloadImages(lastHighlightedTweetImageList, lastHighlightedTweetAuthors.LastOrDefault(), ImageQuality);
|
||||
break;
|
||||
|
||||
case MenuOpenDevTools:
|
||||
@@ -113,20 +166,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){
|
||||
|
@@ -2,21 +2,23 @@
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Core.Bridge;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Other.Analytics;
|
||||
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 CefMenuCommand MenuInputApplyROT13 = (CefMenuCommand)26615;
|
||||
|
||||
private const string TitleReloadBrowser = "Reload browser";
|
||||
private const string TitleMuteNotifications = "Mute notifications";
|
||||
@@ -26,10 +28,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;
|
||||
}
|
||||
|
||||
@@ -41,28 +43,33 @@ namespace TweetDuck.Core.Handling{
|
||||
RemoveSeparatorIfLast(model);
|
||||
|
||||
if (parameters.TypeFlags.HasFlag(ContextMenuType.Selection)){
|
||||
if (parameters.TypeFlags.HasFlag(ContextMenuType.Editable)){
|
||||
model.AddSeparator();
|
||||
model.AddItem(MenuInputApplyROT13, "Apply ROT13");
|
||||
}
|
||||
|
||||
model.AddSeparator();
|
||||
}
|
||||
|
||||
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 +78,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();
|
||||
@@ -89,6 +96,8 @@ namespace TweetDuck.Core.Handling{
|
||||
}
|
||||
|
||||
RemoveSeparatorIfLast(model);
|
||||
|
||||
form.InvokeAsyncSafe(() => form.TriggerAnalyticsEvent(AnalyticsFile.Event.BrowserContextMenu));
|
||||
}
|
||||
|
||||
public override bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags){
|
||||
@@ -96,8 +105,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 +127,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 +139,15 @@ 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;
|
||||
|
||||
case MenuInputApplyROT13:
|
||||
form.InvokeAsyncSafe(form.ApplyROT13);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -153,6 +166,7 @@ namespace TweetDuck.Core.Handling{
|
||||
|
||||
menu.Popup += (sender, args) => {
|
||||
menu.MenuItems[1].Checked = Program.UserConfig.MuteNotifications;
|
||||
form.TriggerAnalyticsEvent(AnalyticsFile.Event.BrowserContextMenu);
|
||||
};
|
||||
|
||||
return menu;
|
||||
|
15
Core/Handling/ContextMenuGuide.cs
Normal file
15
Core/Handling/ContextMenuGuide.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using CefSharp;
|
||||
|
||||
namespace TweetDuck.Core.Handling{
|
||||
sealed class ContextMenuGuide : ContextMenuBase{
|
||||
public override void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){
|
||||
model.Clear();
|
||||
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
|
||||
|
||||
if (HasDevTools){
|
||||
AddSeparator(model);
|
||||
AddDebugMenuItems(model);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,18 +1,20 @@
|
||||
using CefSharp;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Notification;
|
||||
using TweetDuck.Core.Other.Analytics;
|
||||
|
||||
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,29 +30,35 @@ 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){
|
||||
AddSeparator(model);
|
||||
AddDebugMenuItems(model);
|
||||
}
|
||||
|
||||
RemoveSeparatorIfLast(model);
|
||||
|
||||
form.InvokeAsyncSafe(() => form.ContextMenuOpen = true);
|
||||
form.InvokeAsyncSafe(() => {
|
||||
form.ContextMenuOpen = true;
|
||||
form.TriggerAnalyticsEvent(AnalyticsFile.Event.NotificationContextMenu);
|
||||
});
|
||||
}
|
||||
|
||||
public override bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags){
|
||||
@@ -58,7 +66,7 @@ namespace TweetDuck.Core.Handling{
|
||||
return true;
|
||||
}
|
||||
|
||||
switch((int)commandId){
|
||||
switch(commandId){
|
||||
case MenuSkipTweet:
|
||||
form.InvokeAsyncSafe(form.FinishCurrentNotification);
|
||||
return true;
|
||||
@@ -67,12 +75,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;
|
||||
}
|
||||
|
||||
|
26
Core/Handling/DragHandlerBrowser.cs
Normal file
26
Core/Handling/DragHandlerBrowser.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using System.Collections.Generic;
|
||||
using CefSharp;
|
||||
|
||||
namespace TweetDuck.Core.Handling{
|
||||
sealed class DragHandlerBrowser : IDragHandler{
|
||||
public bool OnDragEnter(IWebBrowser browserControl, IBrowser browser, IDragData dragData, DragOperationsMask mask){
|
||||
void TriggerDragStart(string type, string data = null){
|
||||
browserControl.ExecuteScriptAsync("window.TDGF_onGlobalDragStart", type, data);
|
||||
}
|
||||
|
||||
if (dragData.IsLink){
|
||||
TriggerDragStart("link", dragData.LinkUrl);
|
||||
}
|
||||
else if (dragData.IsFragment){
|
||||
TriggerDragStart("text", dragData.FragmentText.Trim());
|
||||
}
|
||||
else{
|
||||
TriggerDragStart("unknown");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void OnDraggableRegionsChanged(IWebBrowser browserControl, IBrowser browser, IList<DraggableRegion> regions){}
|
||||
}
|
||||
}
|
@@ -1,14 +1,24 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using CefSharp;
|
||||
|
||||
namespace TweetDuck.Core.Handling.General{
|
||||
class BrowserProcessHandler : IBrowserProcessHandler{
|
||||
void IBrowserProcessHandler.OnContextInitialized(){
|
||||
sealed class BrowserProcessHandler : IBrowserProcessHandler{
|
||||
public static Task UpdatePrefs(){
|
||||
return Cef.UIThreadTaskFactory.StartNew(UpdatePrefsInternal);
|
||||
}
|
||||
|
||||
private static void UpdatePrefsInternal(){
|
||||
using(IRequestContext ctx = Cef.GetGlobalRequestContext()){
|
||||
ctx.SetPreference("browser.enable_spellchecking", Program.UserConfig.EnableSpellCheck, out string _);
|
||||
ctx.SetPreference("settings.a11y.animation_policy", Program.UserConfig.EnableAnimatedImages ? "allowed" : "none", out string _);
|
||||
}
|
||||
}
|
||||
|
||||
void IBrowserProcessHandler.OnContextInitialized(){
|
||||
UpdatePrefsInternal();
|
||||
}
|
||||
|
||||
void IBrowserProcessHandler.OnScheduleMessagePumpWork(long delay){}
|
||||
void IDisposable.Dispose(){}
|
||||
}
|
||||
|
42
Core/Handling/General/FileDialogHandler.cs
Normal file
42
Core/Handling/General/FileDialogHandler.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
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){
|
||||
string ext = Path.GetExtension(dialog.FileName);
|
||||
callback.Continue(acceptFilters.FindIndex(filter => filter.Equals(ext, StringComparison.OrdinalIgnoreCase)), 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,6 +2,7 @@
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Notification;
|
||||
using TweetDuck.Core.Other.Analytics;
|
||||
|
||||
namespace TweetDuck.Core.Handling {
|
||||
sealed class KeyboardHandlerNotification : IKeyboardHandler{
|
||||
@@ -11,19 +12,26 @@ namespace TweetDuck.Core.Handling {
|
||||
this.notification = notification;
|
||||
}
|
||||
|
||||
private void TriggerKeyboardShortcutAnalytics(){
|
||||
notification.InvokeAsyncSafe(() => notification.TriggerAnalyticsEvent(AnalyticsFile.Event.NotificationKeyboardShortcut));
|
||||
}
|
||||
|
||||
bool IKeyboardHandler.OnPreKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey, ref bool isKeyboardShortcut){
|
||||
if (type == KeyType.RawKeyDown && !browser.FocusedFrame.Url.StartsWith("chrome-devtools://")){
|
||||
switch((Keys)windowsKeyCode){
|
||||
case Keys.Enter:
|
||||
notification.InvokeAsyncSafe(notification.FinishCurrentNotification);
|
||||
TriggerKeyboardShortcutAnalytics();
|
||||
return true;
|
||||
|
||||
case Keys.Escape:
|
||||
notification.InvokeAsyncSafe(() => notification.HideNotification(true));
|
||||
notification.InvokeAsyncSafe(notification.HideNotification);
|
||||
TriggerKeyboardShortcutAnalytics();
|
||||
return true;
|
||||
|
||||
case Keys.Space:
|
||||
notification.InvokeAsyncSafe(() => notification.FreezeTimer = !notification.FreezeTimer);
|
||||
TriggerKeyboardShortcutAnalytics();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
|
||||
|
46
Core/Notification/Example/FormNotificationExample.cs
Normal file
46
Core/Notification/Example/FormNotificationExample.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Plugins;
|
||||
using TweetDuck.Resources;
|
||||
|
||||
namespace TweetDuck.Core.Notification.Example{
|
||||
sealed class FormNotificationExample : FormNotificationMain{
|
||||
public override bool RequiresResize => true;
|
||||
protected override bool CanDragWindow => Program.UserConfig.NotificationPosition == TweetNotification.Position.Custom;
|
||||
|
||||
protected override FormBorderStyle NotificationBorderStyle{
|
||||
get{
|
||||
if (Program.UserConfig.NotificationSize == TweetNotification.Size.Custom){
|
||||
switch(base.NotificationBorderStyle){
|
||||
case FormBorderStyle.FixedSingle: return FormBorderStyle.Sizable;
|
||||
case FormBorderStyle.FixedToolWindow: return FormBorderStyle.SizableToolWindow;
|
||||
}
|
||||
}
|
||||
|
||||
return base.NotificationBorderStyle;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly TweetNotification exampleNotification;
|
||||
|
||||
public FormNotificationExample(FormBrowser owner, PluginManager pluginManager) : base(owner, pluginManager, false){
|
||||
string exampleTweetHTML = ScriptLoader.LoadResource("pages/example.html", true);
|
||||
|
||||
#if DEBUG
|
||||
exampleTweetHTML = exampleTweetHTML.Replace("</p>", @"</p><div style='margin-top:256px'>Scrollbar test padding...</div>");
|
||||
#endif
|
||||
|
||||
exampleNotification = TweetNotification.Example(exampleTweetHTML, 95);
|
||||
}
|
||||
|
||||
public void ShowExampleNotification(bool reset){
|
||||
if (reset){
|
||||
LoadTweet(exampleNotification);
|
||||
}
|
||||
else{
|
||||
PrepareAndDisplayWindow();
|
||||
}
|
||||
|
||||
UpdateTitle();
|
||||
}
|
||||
}
|
||||
}
|
@@ -4,14 +4,29 @@ using System;
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Configuration;
|
||||
using TweetDuck.Core.Bridge;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Handling;
|
||||
using TweetDuck.Core.Handling.General;
|
||||
using TweetDuck.Core.Other.Analytics;
|
||||
using TweetDuck.Core.Other.Management;
|
||||
using TweetDuck.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Core.Notification{
|
||||
partial class FormNotificationBase : Form{
|
||||
protected Point PrimaryLocation{
|
||||
protected static int FontSizeLevel{
|
||||
get{
|
||||
switch(TweetDeckBridge.FontSize){
|
||||
case "largest": return 4;
|
||||
case "large": return 3;
|
||||
case "small": return 1;
|
||||
case "smallest": return 0;
|
||||
default: return 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual Point PrimaryLocation{
|
||||
get{
|
||||
UserConfig config = Program.UserConfig;
|
||||
Screen screen;
|
||||
@@ -52,45 +67,55 @@ namespace TweetDuck.Core.Notification{
|
||||
}
|
||||
|
||||
public bool IsNotificationVisible => Location != ControlExtensions.InvisibleLocation;
|
||||
protected virtual bool CanDragWindow => true;
|
||||
|
||||
public new Point Location{
|
||||
get => base.Location;
|
||||
get{
|
||||
return base.Location;
|
||||
}
|
||||
|
||||
set{
|
||||
Visible = (base.Location = value) != ControlExtensions.InvisibleLocation;
|
||||
FormBorderStyle = GetBorderStyle(CanResizeWindow);
|
||||
FormBorderStyle = NotificationBorderStyle;
|
||||
}
|
||||
}
|
||||
|
||||
public bool CanResizeWindow{
|
||||
get => FormBorderStyle == FormBorderStyle.Sizable || FormBorderStyle == FormBorderStyle.SizableToolWindow;
|
||||
set => FormBorderStyle = GetBorderStyle(value);
|
||||
protected virtual FormBorderStyle NotificationBorderStyle{
|
||||
get{
|
||||
if (WindowsUtils.ShouldAvoidToolWindow && Visible){ // Visible = workaround for alt+tab
|
||||
return FormBorderStyle.FixedSingle;
|
||||
}
|
||||
else{
|
||||
return FormBorderStyle.FixedToolWindow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Func<bool> CanMoveWindow { get; set; } = () => true;
|
||||
protected override bool ShowWithoutActivation => true;
|
||||
|
||||
protected float DpiScale { get; }
|
||||
protected double SizeScale => DpiScale*Program.UserConfig.ZoomLevel/100.0;
|
||||
|
||||
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;
|
||||
@@ -104,13 +129,13 @@ namespace TweetDuck.Core.Notification{
|
||||
|
||||
this.browser.Dock = DockStyle.None;
|
||||
this.browser.ClientSize = ClientSize;
|
||||
this.browser.IsBrowserInitializedChanged += Browser_IsBrowserInitializedChanged;
|
||||
this.browser.IsBrowserInitializedChanged += browser_IsBrowserInitializedChanged;
|
||||
|
||||
#if DEBUG
|
||||
this.browser.ConsoleMessage += BrowserUtils.HandleConsoleMessage;
|
||||
#endif
|
||||
|
||||
this.dpiScale = this.GetDPIScale();
|
||||
DpiScale = this.GetDPIScale();
|
||||
|
||||
DefaultResourceHandlerFactory handlerFactory = (DefaultResourceHandlerFactory)browser.ResourceHandlerFactory;
|
||||
handlerFactory.RegisterHandler(TwitterUtils.TweetDeckURL, this.resourceHandler);
|
||||
@@ -127,22 +152,26 @@ namespace TweetDuck.Core.Notification{
|
||||
}
|
||||
|
||||
protected override void WndProc(ref Message m){
|
||||
if (m.Msg == 0x0112 && (m.WParam.ToInt32() & 0xFFF0) == 0xF010 && !CanMoveWindow()){ // WM_SYSCOMMAND, SC_MOVE
|
||||
if (m.Msg == 0x0112 && (m.WParam.ToInt32() & 0xFFF0) == 0xF010 && !CanDragWindow){ // WM_SYSCOMMAND, SC_MOVE
|
||||
return;
|
||||
}
|
||||
|
||||
base.WndProc(ref m);
|
||||
}
|
||||
|
||||
public void TriggerAnalyticsEvent(AnalyticsFile.Event e){
|
||||
owner.TriggerAnalyticsEvent(e);
|
||||
}
|
||||
|
||||
// event handlers
|
||||
|
||||
private void owner_FormClosed(object sender, FormClosedEventArgs e){
|
||||
Close();
|
||||
}
|
||||
|
||||
private void Browser_IsBrowserInitializedChanged(object sender, IsBrowserInitializedChangedEventArgs e){
|
||||
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);
|
||||
@@ -151,13 +180,10 @@ namespace TweetDuck.Core.Notification{
|
||||
|
||||
// notification methods
|
||||
|
||||
public virtual void HideNotification(bool loadBlank){
|
||||
if (loadBlank){
|
||||
browser.Load("about:blank");
|
||||
}
|
||||
|
||||
public virtual void HideNotification(){
|
||||
browser.Load("about:blank");
|
||||
Location = ControlExtensions.InvisibleLocation;
|
||||
currentColumn = null;
|
||||
currentNotification = null;
|
||||
}
|
||||
|
||||
public virtual void FinishCurrentNotification(){}
|
||||
@@ -180,10 +206,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);
|
||||
}
|
||||
@@ -192,12 +215,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(){
|
||||
@@ -219,14 +243,5 @@ namespace TweetDuck.Core.Notification{
|
||||
toolTip.Show(text, this, position);
|
||||
}
|
||||
}
|
||||
|
||||
private FormBorderStyle GetBorderStyle(bool sizable){
|
||||
if (WindowsUtils.ShouldAvoidToolWindow && Visible){ // Visible = workaround for alt+tab
|
||||
return sizable ? FormBorderStyle.Sizable : FormBorderStyle.FixedSingle;
|
||||
}
|
||||
else{
|
||||
return sizable ? FormBorderStyle.SizableToolWindow : FormBorderStyle.FixedToolWindow;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -49,7 +49,7 @@
|
||||
this.progressBarTimer.Margin = new System.Windows.Forms.Padding(0);
|
||||
this.progressBarTimer.Maximum = 1000;
|
||||
this.progressBarTimer.Name = "progressBarTimer";
|
||||
this.progressBarTimer.Size = new System.Drawing.Size(284, TimerBarHeight);
|
||||
this.progressBarTimer.Size = new System.Drawing.Size(284, 4);
|
||||
this.progressBarTimer.TabIndex = 1;
|
||||
//
|
||||
// FormNotification
|
||||
|
@@ -5,6 +5,7 @@ using System.Windows.Forms;
|
||||
using TweetDuck.Core.Bridge;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Handling;
|
||||
using TweetDuck.Core.Other.Analytics;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetDuck.Data;
|
||||
using TweetDuck.Plugins;
|
||||
@@ -12,17 +13,14 @@ using TweetDuck.Plugins.Enums;
|
||||
using TweetDuck.Resources;
|
||||
|
||||
namespace TweetDuck.Core.Notification{
|
||||
partial class FormNotificationMain : FormNotificationBase{
|
||||
abstract partial class FormNotificationMain : FormNotificationBase{
|
||||
private const string NotificationScriptFile = "notification.js";
|
||||
private const int TimerBarHeight = 4;
|
||||
|
||||
private static readonly string NotificationScriptIdentifier = ScriptLoader.GetRootIdentifier(NotificationScriptFile);
|
||||
private static readonly string PluginScriptIdentifier = ScriptLoader.GetRootIdentifier(PluginManager.PluginNotificationScriptFile);
|
||||
|
||||
private static readonly string NotificationJS = ScriptLoader.LoadResource(NotificationScriptFile);
|
||||
private static readonly string PluginJS = ScriptLoader.LoadResource(PluginManager.PluginNotificationScriptFile);
|
||||
|
||||
private readonly PluginManager plugins;
|
||||
private readonly int timerBarHeight;
|
||||
|
||||
protected int timeLeft, totalTime;
|
||||
protected bool pausedDuringNotification;
|
||||
@@ -34,9 +32,9 @@ namespace TweetDuck.Core.Notification{
|
||||
private bool? prevDisplayTimer;
|
||||
private int? prevFontSize;
|
||||
|
||||
public bool RequiresResize{
|
||||
public virtual bool RequiresResize{
|
||||
get{
|
||||
return !prevDisplayTimer.HasValue || !prevFontSize.HasValue || prevDisplayTimer != Program.UserConfig.DisplayNotificationTimer || prevFontSize != TweetNotification.FontSizeLevel || CanResizeWindow;
|
||||
return !prevDisplayTimer.HasValue || !prevFontSize.HasValue || prevDisplayTimer != Program.UserConfig.DisplayNotificationTimer || prevFontSize != FontSizeLevel;
|
||||
}
|
||||
|
||||
set{
|
||||
@@ -46,7 +44,7 @@ namespace TweetDuck.Core.Notification{
|
||||
}
|
||||
else{
|
||||
prevDisplayTimer = Program.UserConfig.DisplayNotificationTimer;
|
||||
prevFontSize = TweetNotification.FontSizeLevel;
|
||||
prevFontSize = FontSizeLevel;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -55,7 +53,7 @@ namespace TweetDuck.Core.Notification{
|
||||
get{
|
||||
switch(Program.UserConfig.NotificationSize){
|
||||
default:
|
||||
return BrowserUtils.Scale(284, SizeScale*(1.0+0.05*TweetNotification.FontSizeLevel));
|
||||
return BrowserUtils.Scale(284, SizeScale*(1.0+0.05*FontSizeLevel));
|
||||
|
||||
case TweetNotification.Size.Custom:
|
||||
return Program.UserConfig.CustomNotificationSize.Width;
|
||||
@@ -67,22 +65,21 @@ namespace TweetDuck.Core.Notification{
|
||||
get{
|
||||
switch(Program.UserConfig.NotificationSize){
|
||||
default:
|
||||
return BrowserUtils.Scale(118, SizeScale*(1.0+0.075*TweetNotification.FontSizeLevel));
|
||||
return BrowserUtils.Scale(122, SizeScale*(1.0+0.075*FontSizeLevel));
|
||||
|
||||
case TweetNotification.Size.Custom:
|
||||
return Program.UserConfig.CustomNotificationSize.Height;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Size BrowserSize => Program.UserConfig.DisplayNotificationTimer ? new Size(ClientSize.Width, ClientSize.Height-timerBarHeight) : ClientSize;
|
||||
|
||||
public Size BrowserSize{
|
||||
get => Program.UserConfig.DisplayNotificationTimer ? new Size(ClientSize.Width, ClientSize.Height-TimerBarHeight) : ClientSize;
|
||||
}
|
||||
|
||||
public FormNotificationMain(FormBrowser owner, PluginManager pluginManager, bool enableContextMenu) : base(owner, enableContextMenu){
|
||||
protected FormNotificationMain(FormBrowser owner, PluginManager pluginManager, bool enableContextMenu) : base(owner, enableContextMenu){
|
||||
InitializeComponent();
|
||||
|
||||
this.plugins = pluginManager;
|
||||
this.timerBarHeight = BrowserUtils.Scale(4, DpiScale);
|
||||
|
||||
browser.KeyboardHandler = new KeyboardHandlerNotification(this);
|
||||
|
||||
@@ -131,6 +128,7 @@ namespace TweetDuck.Core.Notification{
|
||||
}
|
||||
|
||||
blockXButtonUp = true;
|
||||
this.InvokeAsyncSafe(() => TriggerAnalyticsEvent(AnalyticsFile.Event.NotificationExtraMouseButton));
|
||||
return NativeMethods.HOOK_HANDLED;
|
||||
}
|
||||
else if (eventType == NativeMethods.WM_XBUTTONUP && blockXButtonUp){
|
||||
@@ -151,7 +149,7 @@ namespace TweetDuck.Core.Notification{
|
||||
|
||||
private void FormNotification_FormClosing(object sender, FormClosingEventArgs e){
|
||||
if (e.CloseReason == CloseReason.UserClosing){
|
||||
HideNotification(true);
|
||||
HideNotification();
|
||||
e.Cancel = true;
|
||||
}
|
||||
}
|
||||
@@ -169,12 +167,7 @@ namespace TweetDuck.Core.Notification{
|
||||
if (e.Frame.IsMain && NotificationJS != null && browser.Address != "about:blank"){
|
||||
e.Frame.ExecuteJavaScriptAsync(PropertyBridge.GenerateScript(PropertyBridge.Environment.Notification));
|
||||
ScriptLoader.ExecuteScript(e.Frame, NotificationJS, NotificationScriptIdentifier);
|
||||
|
||||
if (plugins.HasAnyPlugin(PluginEnvironment.Notification)){
|
||||
ScriptLoader.ExecuteScript(e.Frame, PluginJS, PluginScriptIdentifier);
|
||||
ScriptLoader.ExecuteFile(e.Frame, PluginManager.PluginGlobalScriptFile);
|
||||
plugins.ExecutePlugins(e.Frame, PluginEnvironment.Notification, false);
|
||||
}
|
||||
plugins.ExecutePlugins(e.Frame, PluginEnvironment.Notification);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,12 +177,14 @@ namespace TweetDuck.Core.Notification{
|
||||
}
|
||||
|
||||
private void timerHideProgress_Tick(object sender, EventArgs e){
|
||||
if (Bounds.Contains(Cursor.Position) || FreezeTimer || ContextMenuOpen)return;
|
||||
if (Bounds.Contains(Cursor.Position) || FreezeTimer || ContextMenuOpen){
|
||||
return;
|
||||
}
|
||||
|
||||
timeLeft -= timerProgress.Interval;
|
||||
|
||||
int value = BrowserUtils.Scale(1025, (totalTime-timeLeft)/(double)totalTime);
|
||||
progressBarTimer.SetValueInstant(Math.Min(1000, Math.Max(0, Program.UserConfig.NotificationTimerCountDown ? 1000-value : value)));
|
||||
int value = BrowserUtils.Scale(progressBarTimer.Maximum+25, (totalTime-timeLeft)/(double)totalTime);
|
||||
progressBarTimer.SetValueInstant(Program.UserConfig.NotificationTimerCountDown ? progressBarTimer.Maximum-value : value);
|
||||
|
||||
if (timeLeft <= 0){
|
||||
FinishCurrentNotification();
|
||||
@@ -202,21 +197,10 @@ namespace TweetDuck.Core.Notification{
|
||||
LoadTweet(notification);
|
||||
}
|
||||
|
||||
public void ShowNotificationForSettings(bool reset){
|
||||
if (reset){
|
||||
LoadTweet(TweetNotification.ExampleTweet);
|
||||
}
|
||||
else{
|
||||
PrepareAndDisplayWindow();
|
||||
}
|
||||
|
||||
UpdateTitle();
|
||||
}
|
||||
|
||||
public override void HideNotification(bool loadBlank){
|
||||
base.HideNotification(loadBlank);
|
||||
public override void HideNotification(){
|
||||
base.HideNotification();
|
||||
|
||||
progressBarTimer.Value = Program.UserConfig.NotificationTimerCountDown ? 1000 : 0;
|
||||
progressBarTimer.Value = Program.UserConfig.NotificationTimerCountDown ? progressBarTimer.Maximum : progressBarTimer.Minimum;
|
||||
timerProgress.Stop();
|
||||
totalTime = 0;
|
||||
|
||||
@@ -259,14 +243,14 @@ namespace TweetDuck.Core.Notification{
|
||||
protected override void LoadTweet(TweetNotification tweet){
|
||||
timerProgress.Stop();
|
||||
totalTime = timeLeft = tweet.GetDisplayDuration(Program.UserConfig.NotificationDurationValue);
|
||||
progressBarTimer.Value = Program.UserConfig.NotificationTimerCountDown ? 1000 : 0;
|
||||
progressBarTimer.Value = Program.UserConfig.NotificationTimerCountDown ? progressBarTimer.Maximum : progressBarTimer.Minimum;
|
||||
|
||||
base.LoadTweet(tweet);
|
||||
}
|
||||
|
||||
protected override void SetNotificationSize(int width, int height){
|
||||
if (Program.UserConfig.DisplayNotificationTimer){
|
||||
ClientSize = new Size(width, height+TimerBarHeight);
|
||||
ClientSize = new Size(width, height+timerBarHeight);
|
||||
progressBarTimer.Visible = true;
|
||||
}
|
||||
else{
|
||||
@@ -277,7 +261,7 @@ namespace TweetDuck.Core.Notification{
|
||||
browser.ClientSize = new Size(width, height);
|
||||
}
|
||||
|
||||
private void PrepareAndDisplayWindow(){
|
||||
protected void PrepareAndDisplayWindow(){
|
||||
if (RequiresResize){
|
||||
RequiresResize = false;
|
||||
SetNotificationSize(BaseClientWidth, BaseClientHeight);
|
||||
@@ -287,7 +271,7 @@ namespace TweetDuck.Core.Notification{
|
||||
StartMouseHook();
|
||||
}
|
||||
|
||||
protected override void OnNotificationReady(){
|
||||
protected virtual void OnNotificationReady(){
|
||||
PrepareAndDisplayWindow();
|
||||
timerProgress.Start();
|
||||
}
|
||||
|
@@ -40,7 +40,6 @@ namespace TweetDuck.Core.Notification {
|
||||
//
|
||||
// FormNotificationTweet
|
||||
//
|
||||
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.FormNotificationTweet_FormClosing);
|
||||
this.ResumeLayout(true);
|
||||
|
||||
}
|
||||
|
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using TweetDuck.Plugins;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Core.Other.Analytics;
|
||||
using TweetDuck.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Core.Notification{
|
||||
@@ -10,10 +11,24 @@ namespace TweetDuck.Core.Notification{
|
||||
private const int NonIntrusiveIdleLimit = 30;
|
||||
private const int TrimMinimum = 32;
|
||||
|
||||
protected override Point PrimaryLocation => hasTemporarilyMoved && IsNotificationVisible ? Location : base.PrimaryLocation;
|
||||
private bool IsCursorOverNotificationArea => new Rectangle(PrimaryLocation, Size).Contains(Cursor.Position);
|
||||
|
||||
protected override bool CanDragWindow{
|
||||
get{
|
||||
if (ModifierKeys.HasFlag(Keys.Alt)){
|
||||
hasTemporarilyMoved = true;
|
||||
return true;
|
||||
}
|
||||
else{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private readonly Queue<TweetNotification> tweetQueue = new Queue<TweetNotification>(4);
|
||||
private bool needsTrim;
|
||||
private bool hasTemporarilyMoved;
|
||||
|
||||
public FormNotificationTweet(FormBrowser owner, PluginManager pluginManager) : base(owner, pluginManager, true){
|
||||
InitializeComponent();
|
||||
@@ -26,18 +41,18 @@ namespace TweetDuck.Core.Notification{
|
||||
}
|
||||
}
|
||||
|
||||
private void FormNotificationTweet_FormClosing(object sender, FormClosingEventArgs e){
|
||||
if (e.CloseReason == CloseReason.UserClosing){
|
||||
tweetQueue.Clear(); // already canceled
|
||||
TrimQueue();
|
||||
}
|
||||
}
|
||||
protected override void WndProc(ref Message m){
|
||||
if (m.Msg == 0x00A7){ // WM_NCMBUTTONDOWN
|
||||
int hitTest = m.WParam.ToInt32();
|
||||
|
||||
private void TrimQueue(){
|
||||
if (needsTrim){
|
||||
tweetQueue.TrimExcess();
|
||||
needsTrim = false;
|
||||
if (hitTest == 2 || hitTest == 20){ // HTCAPTION, HTCLOSE
|
||||
hasTemporarilyMoved = false;
|
||||
MoveToVisibleLocation();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
base.WndProc(ref m);
|
||||
}
|
||||
|
||||
// event handlers
|
||||
@@ -68,11 +83,9 @@ namespace TweetDuck.Core.Notification{
|
||||
// notification methods
|
||||
|
||||
public override void ShowNotification(TweetNotification notification){
|
||||
if (IsPaused){
|
||||
tweetQueue.Enqueue(notification);
|
||||
}
|
||||
else{
|
||||
tweetQueue.Enqueue(notification);
|
||||
tweetQueue.Enqueue(notification);
|
||||
|
||||
if (!IsPaused){
|
||||
UpdateTitle();
|
||||
|
||||
if (totalTime == 0){
|
||||
@@ -81,6 +94,19 @@ namespace TweetDuck.Core.Notification{
|
||||
}
|
||||
|
||||
needsTrim |= tweetQueue.Count >= TrimMinimum;
|
||||
TriggerAnalyticsEvent(AnalyticsFile.Event.DesktopNotification);
|
||||
}
|
||||
|
||||
public override void HideNotification(){
|
||||
base.HideNotification();
|
||||
tweetQueue.Clear();
|
||||
|
||||
if (needsTrim){
|
||||
tweetQueue.TrimExcess();
|
||||
needsTrim = false;
|
||||
}
|
||||
|
||||
hasTemporarilyMoved = false;
|
||||
}
|
||||
|
||||
public override void FinishCurrentNotification(){
|
||||
@@ -88,8 +114,7 @@ namespace TweetDuck.Core.Notification{
|
||||
LoadNextNotification();
|
||||
}
|
||||
else{
|
||||
HideNotification(true);
|
||||
TrimQueue();
|
||||
HideNotification();
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -12,9 +12,11 @@ using TweetDuck.Resources;
|
||||
|
||||
namespace TweetDuck.Core.Notification.Screenshot{
|
||||
sealed class FormNotificationScreenshotable : FormNotificationBase{
|
||||
protected override bool CanDragWindow => false;
|
||||
|
||||
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));
|
||||
@@ -22,7 +24,7 @@ namespace TweetDuck.Core.Notification.Screenshot{
|
||||
browser.LoadingStateChanged += (sender, args) => {
|
||||
if (!args.IsLoading){
|
||||
using(IFrame frame = args.Browser.MainFrame){
|
||||
ScriptLoader.ExecuteScript(frame, "window.setTimeout($TD_NotificationScreenshot.trigger, 129)", "gen:screenshot");
|
||||
ScriptLoader.ExecuteScript(frame, "window.setTimeout($TD_NotificationScreenshot.trigger, document.getElementsByTagName('iframe').length ? 267 : 67)", "gen:screenshot");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -42,11 +42,8 @@ namespace TweetDuck.Core.Notification.Screenshot{
|
||||
return;
|
||||
}
|
||||
|
||||
screenshot = new FormNotificationScreenshotable(Callback, owner, plugins){
|
||||
CanMoveWindow = () => false
|
||||
};
|
||||
|
||||
screenshot.LoadNotificationForScreenshot(new TweetNotification(string.Empty, html, 0, string.Empty, string.Empty), width, height);
|
||||
screenshot = new FormNotificationScreenshotable(Callback, owner, plugins);
|
||||
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);
|
||||
}
|
||||
|
@@ -1,50 +1,15 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using TweetDuck.Core.Bridge;
|
||||
using TweetDuck.Resources;
|
||||
|
||||
namespace TweetDuck.Core.Notification{
|
||||
sealed class TweetNotification{
|
||||
private static string FontSizeClass { get; set; }
|
||||
private static string HeadTag { get; set; }
|
||||
private const string DefaultHeadLayout = @"<html id='tduck' class='os-windows txt-size--14' data-td-font='medium' data-td-theme='dark'><head><meta charset='utf-8'><meta http-equiv='X-UA-Compatible' content='chrome=1'><link rel='stylesheet' href='https://ton.twimg.com/tweetdeck-web/web/css/font.5ef884f9f9.css'><link rel='stylesheet' href='https://ton.twimg.com/tweetdeck-web/web/css/app-dark.5631e0dd42.css'><style type='text/css'>body{background:#222426}</style>";
|
||||
private static readonly string CustomCSS = ScriptLoader.LoadResource("styles/notification.css");
|
||||
|
||||
private const string DefaultFontSizeClass = "medium";
|
||||
private const string DefaultHeadTag = @"<meta charset='utf-8'><meta http-equiv='X-UA-Compatible' content='chrome=1'><link rel='stylesheet' href='https://ton.twimg.com/tweetdeck-web/web/css/font.5ef884f9f9.css'><link rel='stylesheet' href='https://ton.twimg.com/tweetdeck-web/web/css/app-dark.5631e0dd42.css'><style type='text/css'>body{background:#222426}</style>";
|
||||
private const string CustomCSS = @"body:before{content:none}body{overflow-y:auto}.scroll-styled-v::-webkit-scrollbar{width:7px}.scroll-styled-v::-webkit-scrollbar-thumb{border-radius:0}.scroll-styled-v::-webkit-scrollbar-track{border-left:0}#td-skip{opacity:0;cursor:pointer;transition:opacity 0.15s ease}.td-hover #td-skip{opacity:0.75}#td-skip:hover{opacity:1}.media-size-medium{height:calc(100vh - 16px)!important;max-height:240px;border-radius:1px!important}.js-quote-detail .media-size-medium{height:calc(100vh - 28px)!important;}.js-media.margin-vm, .js-media-preview-container.margin-vm{margin-bottom:0!important}";
|
||||
|
||||
public static int FontSizeLevel{
|
||||
get{
|
||||
switch(FontSizeClass){
|
||||
case "largest": return 4;
|
||||
case "large": return 3;
|
||||
case "medium": return 2;
|
||||
case "small": return 1;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string ExampleTweetHTML;
|
||||
|
||||
public static TweetNotification ExampleTweet{
|
||||
get{
|
||||
if (ExampleTweetHTML == null){
|
||||
ExampleTweetHTML = ScriptLoader.LoadResource("pages/example.html", true);
|
||||
|
||||
#if DEBUG
|
||||
ExampleTweetHTML = ExampleTweetHTML.Replace("</p>", @"</p><div style='margin-top:256px'>Scrollbar test padding...</div>");
|
||||
#endif
|
||||
}
|
||||
|
||||
return new TweetNotification("Home", ExampleTweetHTML, 95, string.Empty, string.Empty, true);
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetFontSizeClass(string newFSClass){
|
||||
FontSizeClass = newFSClass;
|
||||
}
|
||||
|
||||
public static void SetHeadTag(string headContents){
|
||||
HeadTag = headContents;
|
||||
public static TweetNotification Example(string html, int characters){
|
||||
return new TweetNotification(string.Empty, string.Empty, "Home", html, characters, string.Empty, string.Empty, true);
|
||||
}
|
||||
|
||||
public enum Position{
|
||||
@@ -55,7 +20,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 +31,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;
|
||||
|
||||
@@ -82,8 +53,7 @@ namespace TweetDuck.Core.Notification{
|
||||
public string GenerateHtml(string bodyClasses = null, bool enableCustomCSS = true){
|
||||
StringBuilder build = new StringBuilder();
|
||||
build.Append("<!DOCTYPE html>");
|
||||
build.Append("<html class='os-windows txt-base-").Append(FontSizeClass ?? DefaultFontSizeClass).Append("'>");
|
||||
build.Append("<head>").Append(HeadTag ?? DefaultHeadTag);
|
||||
build.Append(TweetDeckBridge.NotificationHeadLayout ?? DefaultHeadLayout);
|
||||
|
||||
if (enableCustomCSS){
|
||||
build.Append("<style type='text/css'>").Append(CustomCSS).Append("</style>");
|
||||
@@ -100,7 +70,7 @@ namespace TweetDuck.Core.Notification{
|
||||
build.Append(' ').Append(bodyClasses);
|
||||
}
|
||||
|
||||
build.Append('\'').Append(isExample ? " td-example-notification" : "").Append("><div class='column' style='width:100%;height:auto;overflow:initial;'>");
|
||||
build.Append('\'').Append(isExample ? " td-example-notification" : "").Append("><div class='column' style='width:100%!important;height:auto!important;overflow:initial!important;'>");
|
||||
build.Append(html);
|
||||
build.Append("</div></body>");
|
||||
build.Append("</html>");
|
||||
|
101
Core/Other/Analytics/AnalyticsFile.cs
Normal file
101
Core/Other/Analytics/AnalyticsFile.cs
Normal file
@@ -0,0 +1,101 @@
|
||||
using System;
|
||||
using TweetDuck.Data.Serialization;
|
||||
|
||||
namespace TweetDuck.Core.Other.Analytics{
|
||||
sealed class AnalyticsFile{
|
||||
private static readonly FileSerializer<AnalyticsFile> Serializer = new FileSerializer<AnalyticsFile>();
|
||||
|
||||
static AnalyticsFile(){
|
||||
Serializer.RegisterTypeConverter(typeof(DateTime), new SingleTypeConverter<DateTime>{
|
||||
ConvertToString = value => value.ToBinary().ToString(),
|
||||
ConvertToObject = value => DateTime.FromBinary(long.Parse(value))
|
||||
});
|
||||
}
|
||||
|
||||
public enum Event{
|
||||
OpenOptions, OpenPlugins, OpenAbout, OpenGuide,
|
||||
DesktopNotification, SoundNotification,
|
||||
BrowserContextMenu, BrowserExtraMouseButton,
|
||||
NotificationContextMenu, NotificationExtraMouseButton, NotificationKeyboardShortcut,
|
||||
TweetScreenshot, TweetDetail, VideoPlay, GCReload
|
||||
}
|
||||
|
||||
// STATE PROPERTIES
|
||||
|
||||
public DateTime LastDataCollection { get; set; } = DateTime.MinValue;
|
||||
public string LastCollectionVersion { get; set; } = null;
|
||||
public string LastCollectionMessage { get; set; } = null;
|
||||
|
||||
// USAGE DATA
|
||||
|
||||
public int CountOpenOptions { get; private set; } = 0;
|
||||
public int CountOpenPlugins { get; private set; } = 0;
|
||||
public int CountOpenAbout { get; private set; } = 0;
|
||||
public int CountOpenGuide { get; private set; } = 0;
|
||||
|
||||
public int CountDesktopNotifications { get; private set; } = 0;
|
||||
public int CountSoundNotifications { get; private set; } = 0;
|
||||
|
||||
public int CountBrowserContextMenus { get; private set; } = 0;
|
||||
public int CountBrowserExtraMouseButtons { get; private set; } = 0;
|
||||
public int CountNotificationContextMenus { get; private set; } = 0;
|
||||
public int CountNotificationExtraMouseButtons { get; private set; } = 0;
|
||||
public int CountNotificationKeyboardShortcuts { get; private set; } = 0;
|
||||
|
||||
public int CountTweetScreenshots { get; private set; } = 0;
|
||||
public int CountTweetDetails { get; private set; } = 0;
|
||||
public int CountVideoPlays { get; private set; } = 0;
|
||||
public int CountGCReloads { get; private set; } = 0;
|
||||
|
||||
// END OF DATA
|
||||
|
||||
private readonly string file;
|
||||
|
||||
private AnalyticsFile(string file){
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
public void TriggerEvent(Event e){
|
||||
switch(e){
|
||||
case Event.OpenOptions: ++CountOpenOptions; break;
|
||||
case Event.OpenPlugins: ++CountOpenPlugins; break;
|
||||
case Event.OpenAbout: ++CountOpenAbout; break;
|
||||
case Event.OpenGuide: ++CountOpenGuide; break;
|
||||
|
||||
case Event.DesktopNotification: ++CountDesktopNotifications; break;
|
||||
case Event.SoundNotification: ++CountSoundNotifications; break;
|
||||
|
||||
case Event.BrowserContextMenu: ++CountBrowserContextMenus; break;
|
||||
case Event.BrowserExtraMouseButton: ++CountBrowserExtraMouseButtons; break;
|
||||
case Event.NotificationContextMenu: ++CountNotificationContextMenus; break;
|
||||
case Event.NotificationExtraMouseButton: ++CountNotificationExtraMouseButtons; break;
|
||||
case Event.NotificationKeyboardShortcut: ++CountNotificationKeyboardShortcuts; break;
|
||||
|
||||
case Event.TweetScreenshot: ++CountTweetScreenshots; break;
|
||||
case Event.TweetDetail: ++CountTweetDetails; break;
|
||||
case Event.VideoPlay: ++CountVideoPlays; break;
|
||||
case Event.GCReload: ++CountGCReloads; break;
|
||||
}
|
||||
}
|
||||
|
||||
public void Save(){
|
||||
try{
|
||||
Serializer.Write(file, this);
|
||||
}catch(Exception e){
|
||||
Program.Reporter.HandleException("Analytics File Error", "Could not save the analytics file.", true, e);
|
||||
}
|
||||
}
|
||||
|
||||
public static AnalyticsFile Load(string file){
|
||||
AnalyticsFile config = new AnalyticsFile(file);
|
||||
|
||||
try{
|
||||
Serializer.ReadIfExists(file, config);
|
||||
}catch(Exception e){
|
||||
Program.Reporter.HandleException("Analytics File Error", "Could not open the analytics file.", true, e);
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
}
|
||||
}
|
127
Core/Other/Analytics/AnalyticsManager.cs
Normal file
127
Core/Other/Analytics/AnalyticsManager.cs
Normal file
@@ -0,0 +1,127 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using System.Timers;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetDuck.Plugins;
|
||||
|
||||
namespace TweetDuck.Core.Other.Analytics{
|
||||
sealed class AnalyticsManager : IDisposable{
|
||||
private static readonly TimeSpan CollectionInterval = TimeSpan.FromDays(7);
|
||||
private static readonly Uri CollectionUrl = new Uri("https://tweetduck.chylex.com/breadcrumb/report");
|
||||
|
||||
public AnalyticsFile File { get; }
|
||||
|
||||
private readonly FormBrowser browser;
|
||||
private readonly PluginManager plugins;
|
||||
private readonly Timer currentTimer, saveTimer;
|
||||
|
||||
public AnalyticsManager(FormBrowser browser, PluginManager plugins, string file){
|
||||
this.browser = browser;
|
||||
this.plugins = plugins;
|
||||
this.File = AnalyticsFile.Load(file);
|
||||
|
||||
this.currentTimer = new Timer{ SynchronizingObject = browser };
|
||||
this.currentTimer.Elapsed += currentTimer_Elapsed;
|
||||
|
||||
this.saveTimer = new Timer{ SynchronizingObject = browser, Interval = 60000 };
|
||||
this.saveTimer.Elapsed += saveTimer_Elapsed;
|
||||
|
||||
if (this.File.LastCollectionVersion != Program.VersionTag){
|
||||
ScheduleReportIn(TimeSpan.FromHours(12), string.Empty);
|
||||
}
|
||||
else{
|
||||
RestartTimer();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose(){
|
||||
if (saveTimer.Enabled){
|
||||
File.Save();
|
||||
}
|
||||
|
||||
currentTimer.Dispose();
|
||||
saveTimer.Dispose();
|
||||
}
|
||||
|
||||
public void TriggerEvent(AnalyticsFile.Event e){
|
||||
File.TriggerEvent(e);
|
||||
saveTimer.Enabled = true;
|
||||
}
|
||||
|
||||
private void saveTimer_Elapsed(object sender, ElapsedEventArgs e){
|
||||
saveTimer.Stop();
|
||||
File.Save();
|
||||
}
|
||||
|
||||
private void ScheduleReportIn(TimeSpan delay, string message = null){
|
||||
SetLastDataCollectionTime(DateTime.Now.Subtract(CollectionInterval).Add(delay), message);
|
||||
}
|
||||
|
||||
private void SetLastDataCollectionTime(DateTime dt, string message = null){
|
||||
File.LastDataCollection = new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, 0, dt.Kind);
|
||||
File.LastCollectionVersion = Program.VersionTag;
|
||||
File.LastCollectionMessage = message ?? dt.ToString("g", Program.Culture);
|
||||
|
||||
File.Save();
|
||||
RestartTimer();
|
||||
}
|
||||
|
||||
private void RestartTimer(){
|
||||
TimeSpan diff = DateTime.Now.Subtract(File.LastDataCollection);
|
||||
int minutesTillNext = (int)(CollectionInterval.TotalMinutes-Math.Floor(diff.TotalMinutes));
|
||||
|
||||
currentTimer.Interval = Math.Max(minutesTillNext, 1)*60000;
|
||||
currentTimer.Start();
|
||||
}
|
||||
|
||||
private void currentTimer_Elapsed(object sender, ElapsedEventArgs e){
|
||||
currentTimer.Stop();
|
||||
|
||||
TimeSpan diff = DateTime.Now.Subtract(File.LastDataCollection);
|
||||
|
||||
if (Math.Floor(diff.TotalMinutes) >= CollectionInterval.TotalMinutes){
|
||||
SendReport();
|
||||
}
|
||||
else{
|
||||
RestartTimer();
|
||||
}
|
||||
}
|
||||
|
||||
private void SendReport(){
|
||||
AnalyticsReportGenerator.ExternalInfo info = AnalyticsReportGenerator.ExternalInfo.From(browser);
|
||||
|
||||
Task.Factory.StartNew(() => {
|
||||
AnalyticsReport report = AnalyticsReportGenerator.Create(File, info, plugins);
|
||||
BrowserUtils.CreateWebClient().UploadValues(CollectionUrl, "POST", report.ToNameValueCollection());
|
||||
}).ContinueWith(task => browser.InvokeAsyncSafe(() => {
|
||||
if (task.Status == TaskStatus.RanToCompletion){
|
||||
SetLastDataCollectionTime(DateTime.Now);
|
||||
}
|
||||
else if (task.Exception != null){
|
||||
string message = null;
|
||||
|
||||
if (task.Exception.InnerException is WebException e){
|
||||
switch(e.Status){
|
||||
case WebExceptionStatus.ConnectFailure:
|
||||
message = "Connection Error";
|
||||
break;
|
||||
|
||||
case WebExceptionStatus.NameResolutionFailure:
|
||||
message = "DNS Error";
|
||||
break;
|
||||
|
||||
case WebExceptionStatus.ProtocolError:
|
||||
HttpWebResponse response = e.Response as HttpWebResponse;
|
||||
message = "HTTP Error "+(response != null ? $"{(int)response.StatusCode} ({response.StatusDescription})" : "(unknown code)");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ScheduleReportIn(TimeSpan.FromHours(4), message ?? "Error: "+(task.Exception.InnerException?.Message ?? task.Exception.Message));
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
57
Core/Other/Analytics/AnalyticsReport.cs
Normal file
57
Core/Other/Analytics/AnalyticsReport.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Specialized;
|
||||
using System.Text;
|
||||
|
||||
namespace TweetDuck.Core.Other.Analytics{
|
||||
sealed class AnalyticsReport : IEnumerable{
|
||||
private OrderedDictionary data = new OrderedDictionary(32);
|
||||
private int separators;
|
||||
|
||||
public void Add(int ignored){ // adding separators to pretty print
|
||||
data.Add((++separators).ToString(), null);
|
||||
}
|
||||
|
||||
public void Add(string key, string value){
|
||||
data.Add(key, value);
|
||||
}
|
||||
|
||||
public AnalyticsReport FinalizeReport(){
|
||||
if (!data.IsReadOnly){
|
||||
data = data.AsReadOnly();
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public IEnumerator GetEnumerator(){
|
||||
return data.GetEnumerator();
|
||||
}
|
||||
|
||||
public NameValueCollection ToNameValueCollection(){
|
||||
NameValueCollection collection = new NameValueCollection();
|
||||
|
||||
foreach(DictionaryEntry entry in data){
|
||||
if (entry.Value != null){
|
||||
collection.Add(((string)entry.Key).ToLower().Replace(' ', '_'), (string)entry.Value);
|
||||
}
|
||||
}
|
||||
|
||||
return collection;
|
||||
}
|
||||
|
||||
public override string ToString(){
|
||||
StringBuilder build = new StringBuilder();
|
||||
|
||||
foreach(DictionaryEntry entry in data){
|
||||
if (entry.Value == null){
|
||||
build.AppendLine();
|
||||
}
|
||||
else{
|
||||
build.AppendLine(entry.Key+": "+entry.Value);
|
||||
}
|
||||
}
|
||||
|
||||
return build.ToString();
|
||||
}
|
||||
}
|
||||
}
|
297
Core/Other/Analytics/AnalyticsReportGenerator.cs
Normal file
297
Core/Other/Analytics/AnalyticsReportGenerator.cs
Normal file
@@ -0,0 +1,297 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Windows.Forms;
|
||||
using Microsoft.Win32;
|
||||
using TweetDuck.Configuration;
|
||||
using System.Linq;
|
||||
using System.Management;
|
||||
using System.Text.RegularExpressions;
|
||||
using TweetDuck.Core.Notification;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetDuck.Plugins;
|
||||
|
||||
namespace TweetDuck.Core.Other.Analytics{
|
||||
static class AnalyticsReportGenerator{
|
||||
public static AnalyticsReport Create(AnalyticsFile file, ExternalInfo info, PluginManager plugins){
|
||||
Dictionary<string, string> editLayoutDesign = EditLayoutDesignPluginData;
|
||||
|
||||
return new AnalyticsReport{
|
||||
{ "App Version" , Program.VersionTag },
|
||||
{ "App Type" , Program.IsPortable ? "portable" : "installed" },
|
||||
0,
|
||||
{ "System Name" , SystemName },
|
||||
{ "System Edition" , SystemEdition },
|
||||
{ "System Environment" , Environment.Is64BitOperatingSystem ? "64-bit" : "32-bit" },
|
||||
{ "System Build" , SystemBuild },
|
||||
{ "System Locale" , Program.Culture.Name.ToLower() },
|
||||
0,
|
||||
{ "RAM" , Exact(RamSize) },
|
||||
{ "GPU" , GpuVendor },
|
||||
0,
|
||||
{ "Screen Count" , Exact(Screen.AllScreens.Length) },
|
||||
{ "Screen Resolution" , info.Resolution ?? "(unknown)" },
|
||||
{ "Screen DPI" , info.DPI != null ? Exact(info.DPI.Value) : "(unknown)" },
|
||||
0,
|
||||
{ "Hardware Acceleration" , Bool(SysConfig.HardwareAcceleration) },
|
||||
{ "Browser GC Reload" , Bool(SysConfig.EnableBrowserGCReload) },
|
||||
{ "Browser GC Threshold" , Exact(SysConfig.BrowserMemoryThreshold) },
|
||||
0,
|
||||
{ "Expand Links" , Bool(UserConfig.ExpandLinksOnHover) },
|
||||
{ "Switch Account Selectors" , Bool(UserConfig.SwitchAccountSelectors) },
|
||||
{ "Search In First Column" , Bool(UserConfig.OpenSearchInFirstColumn) },
|
||||
{ "Best Image Quality" , Bool(UserConfig.BestImageQuality) },
|
||||
{ "Spell Check" , Bool(UserConfig.EnableSpellCheck) },
|
||||
{ "Zoom" , Exact(UserConfig.ZoomLevel) },
|
||||
0,
|
||||
{ "Updates" , Bool(UserConfig.EnableUpdateCheck) },
|
||||
{ "Update Dismissed" , Bool(!string.IsNullOrEmpty(UserConfig.DismissedUpdate)) },
|
||||
0,
|
||||
{ "Tray" , TrayMode },
|
||||
{ "Tray Highlight" , Bool(UserConfig.EnableTrayHighlight) },
|
||||
0,
|
||||
{ "Notification Position" , NotificationPosition },
|
||||
{ "Notification Size" , NotificationSize },
|
||||
{ "Notification Timer" , NotificationTimer },
|
||||
{ "Notification Timer Speed" , RoundUp(UserConfig.NotificationDurationValue, 5) },
|
||||
{ "Notification Scroll Speed" , Exact(UserConfig.NotificationScrollSpeed) },
|
||||
{ "Notification Column Title" , Bool(UserConfig.DisplayNotificationColumn) },
|
||||
{ "Notification Media Previews" , Bool(UserConfig.NotificationMediaPreviews) },
|
||||
{ "Notification Link Skip" , Bool(UserConfig.NotificationSkipOnLinkClick) },
|
||||
{ "Notification Non-Intrusive" , Bool(UserConfig.NotificationNonIntrusiveMode) },
|
||||
{ "Notification Idle Pause" , Exact(UserConfig.NotificationIdlePauseSeconds) },
|
||||
{ "Custom Sound Notification" , string.IsNullOrEmpty(UserConfig.NotificationSoundPath) ? "off" : Path.GetExtension(UserConfig.NotificationSoundPath) },
|
||||
0,
|
||||
{ "Program Arguments" , List(ProgramArguments) },
|
||||
{ "Custom CEF Arguments" , RoundUp((UserConfig.CustomCefArgs ?? string.Empty).Length, 10) },
|
||||
{ "Custom Browser CSS" , RoundUp((UserConfig.CustomBrowserCSS ?? string.Empty).Length, 50) },
|
||||
{ "Custom Notification CSS" , RoundUp((UserConfig.CustomNotificationCSS ?? string.Empty).Length, 50) },
|
||||
0,
|
||||
{ "Plugins All" , List(plugins.Plugins.Select(plugin => plugin.Identifier)) },
|
||||
{ "Plugins Enabled" , List(plugins.Plugins.Where(plugin => plugins.Config.IsEnabled(plugin)).Select(plugin => plugin.Identifier)) },
|
||||
0,
|
||||
{ "Theme" , Dict(editLayoutDesign, "_theme", "light/def") },
|
||||
{ "Column Width" , Dict(editLayoutDesign, "columnWidth", "310px/def") },
|
||||
{ "Font Size" , Dict(editLayoutDesign, "fontSize", "12px/def") },
|
||||
{ "Large Quote Font" , Dict(editLayoutDesign, "increaseQuoteTextSize", "false/def") },
|
||||
{ "Small Compose Font" , Dict(editLayoutDesign, "smallComposeTextSize", "false/def") },
|
||||
{ "Avatar Radius" , Dict(editLayoutDesign, "avatarRadius", "2/def") },
|
||||
{ "Hide Tweet Actions" , Dict(editLayoutDesign, "hideTweetActions", "true/def") },
|
||||
{ "Move Tweet Actions" , Dict(editLayoutDesign, "moveTweetActionsToRight", "true/def") },
|
||||
{ "Theme Color Tweaks" , Dict(editLayoutDesign, "themeColorTweaks", "true/def") },
|
||||
{ "Revert Icons" , Dict(editLayoutDesign, "revertIcons", "true/def") },
|
||||
{ "Optimize Animations" , Dict(editLayoutDesign, "optimizeAnimations", "true/def") },
|
||||
{ "Reply Account Mode" , ReplyAccountConfigFromPlugin },
|
||||
{ "Template Count" , Exact(TemplateCountFromPlugin) },
|
||||
0,
|
||||
{ "Opened Options" , LogRound(file.CountOpenOptions, 4) },
|
||||
{ "Opened Plugins" , LogRound(file.CountOpenPlugins, 4) },
|
||||
{ "Opened About" , LogRound(file.CountOpenAbout, 4) },
|
||||
{ "Opened Guide" , LogRound(file.CountOpenGuide, 4) },
|
||||
{ "Desktop Notifications" , LogRound(file.CountDesktopNotifications, 5) },
|
||||
{ "Sound Notifications" , LogRound(file.CountSoundNotifications, 5) },
|
||||
{ "Browser Context Menus" , LogRound(file.CountBrowserContextMenus, 2) },
|
||||
{ "Browser Extra Mouse Buttons" , LogRound(file.CountBrowserExtraMouseButtons, 2) },
|
||||
{ "Notification Context Menus" , LogRound(file.CountNotificationContextMenus, 2) },
|
||||
{ "Notification Extra Mouse Buttons" , LogRound(file.CountNotificationExtraMouseButtons, 2) },
|
||||
{ "Notification Keyboard Shortcuts" , LogRound(file.CountNotificationKeyboardShortcuts, 2) },
|
||||
{ "Tweet Screenshots" , LogRound(file.CountTweetScreenshots, 2) },
|
||||
{ "Tweet Details" , LogRound(file.CountTweetDetails, 2) },
|
||||
{ "Video Plays" , LogRound(file.CountVideoPlays, 4) },
|
||||
{ "GC Reloads" , LogRound(file.CountGCReloads, 4) }
|
||||
}.FinalizeReport();
|
||||
}
|
||||
|
||||
private static UserConfig UserConfig => Program.UserConfig;
|
||||
private static SystemConfig SysConfig => Program.SystemConfig;
|
||||
|
||||
private static string Bool(bool value) => value ? "on" : "off";
|
||||
private static string Exact(int value) => value.ToString();
|
||||
private static string RoundUp(int value, int multiple) => (multiple*(int)Math.Ceiling((double)value/multiple)).ToString();
|
||||
private static string LogRound(int value, int logBase) => (value <= 0 ? 0 : (int)Math.Pow(logBase, Math.Floor(Math.Log(value, logBase)))).ToString();
|
||||
private static string Dict(Dictionary<string, string> dict, string key, string def = "(unknown)") => dict.TryGetValue(key, out string value) ? value : def;
|
||||
private static string List(IEnumerable<string> list) => string.Join("|", list.DefaultIfEmpty("(none)"));
|
||||
|
||||
private static string SystemName { get; }
|
||||
private static string SystemEdition { get; }
|
||||
private static string SystemBuild { get; }
|
||||
private static int RamSize { get; }
|
||||
private static string GpuVendor { get; }
|
||||
private static string[] ProgramArguments { get; }
|
||||
|
||||
static AnalyticsReportGenerator(){
|
||||
string osName, osEdition, osBuild;
|
||||
|
||||
try{
|
||||
using(RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion", false)){
|
||||
// ReSharper disable once PossibleNullReferenceException
|
||||
osName = key.GetValue("ProductName") as string;
|
||||
osEdition = key.GetValue("EditionID") as string;
|
||||
osBuild = key.GetValue("CurrentBuild") as string;
|
||||
|
||||
if (osName != null && osEdition != null){
|
||||
osName = osName.Replace(osEdition, "").TrimEnd();
|
||||
}
|
||||
}
|
||||
}catch{
|
||||
osName = osEdition = osBuild = null;
|
||||
}
|
||||
|
||||
SystemName = osName ?? "Windows (unknown)";
|
||||
SystemEdition = osEdition ?? "(unknown)";
|
||||
SystemBuild = osBuild ?? "(unknown)";
|
||||
|
||||
try{
|
||||
using(ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT Capacity FROM Win32_PhysicalMemory")){
|
||||
foreach(ManagementBaseObject obj in searcher.Get()){
|
||||
RamSize += (int)((ulong)obj["Capacity"]/(1024L*1024L));
|
||||
}
|
||||
}
|
||||
}catch{
|
||||
RamSize = 0;
|
||||
}
|
||||
|
||||
string gpu = null;
|
||||
|
||||
try{
|
||||
using(ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT Caption FROM Win32_VideoController")){
|
||||
foreach(ManagementBaseObject obj in searcher.Get()){
|
||||
string vendor = obj["Caption"] as string;
|
||||
|
||||
if (!string.IsNullOrEmpty(vendor)){
|
||||
gpu = vendor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}catch{
|
||||
// rip
|
||||
}
|
||||
|
||||
GpuVendor = gpu ?? "(unknown)";
|
||||
|
||||
Dictionary<string, string> args = new Dictionary<string, string>();
|
||||
Arguments.GetCurrentClean().ToDictionary(args);
|
||||
ProgramArguments = args.Keys.Select(key => key.TrimStart('-')).ToArray();
|
||||
}
|
||||
|
||||
private static string TrayMode{
|
||||
get{
|
||||
switch(UserConfig.TrayBehavior){
|
||||
case TrayIcon.Behavior.DisplayOnly: return "icon";
|
||||
case TrayIcon.Behavior.MinimizeToTray: return "minimize";
|
||||
case TrayIcon.Behavior.CloseToTray: return "close";
|
||||
case TrayIcon.Behavior.Combined: return "combined";
|
||||
default: return "off";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string NotificationPosition{
|
||||
get{
|
||||
switch(UserConfig.NotificationPosition){
|
||||
case TweetNotification.Position.TopLeft: return "top left";
|
||||
case TweetNotification.Position.TopRight: return "top right";
|
||||
case TweetNotification.Position.BottomLeft: return "bottom left";
|
||||
case TweetNotification.Position.BottomRight: return "bottom right";
|
||||
default: return "custom";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string NotificationSize{
|
||||
get{
|
||||
switch(UserConfig.NotificationSize){
|
||||
case TweetNotification.Size.Auto: return "auto";
|
||||
default: return RoundUp(UserConfig.CustomNotificationSize.Width, 20)+"x"+RoundUp(UserConfig.CustomNotificationSize.Height, 20);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string NotificationTimer{
|
||||
get{
|
||||
if (!UserConfig.DisplayNotificationTimer){
|
||||
return "off";
|
||||
}
|
||||
else{
|
||||
return UserConfig.NotificationTimerCountDown ? "count down" : "count up";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Dictionary<string, string> EditLayoutDesignPluginData{
|
||||
get{
|
||||
Dictionary<string, string> dict = new Dictionary<string, string>();
|
||||
|
||||
try{
|
||||
string data = File.ReadAllText(Path.Combine(Program.PluginDataPath, "official", "edit-design", "config.json"));
|
||||
|
||||
foreach(Match match in Regex.Matches(data, "\"(\\w+?)\":(.*?)[,}]")){
|
||||
dict[match.Groups[1].Value] = match.Groups[2].Value.Trim('"');
|
||||
}
|
||||
}catch{
|
||||
// rip
|
||||
}
|
||||
|
||||
return dict;
|
||||
}
|
||||
}
|
||||
|
||||
private static int TemplateCountFromPlugin{
|
||||
get{
|
||||
try{
|
||||
string data = File.ReadAllText(Path.Combine(Program.PluginDataPath, "official", "templates", "config.json"));
|
||||
return Math.Min(StringUtils.CountOccurrences(data, "{\"name\":"), StringUtils.CountOccurrences(data, ",\"contents\":"));
|
||||
}catch{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string ReplyAccountConfigFromPlugin{
|
||||
get{
|
||||
try{
|
||||
string data = File.ReadAllText(Path.Combine(Program.PluginDataPath, "official", "reply-account", "configuration.js")).Replace(" ", "");
|
||||
|
||||
Match matchType = Regex.Match(data, "defaultAccount:\"([#@])(.*?)\"(?:,|$)");
|
||||
Match matchAdvanced = Regex.Match(data, "useAdvancedSelector:(.*?)(?:,|$)", RegexOptions.Multiline);
|
||||
|
||||
if (!matchType.Success){
|
||||
return "(unknown)";
|
||||
}
|
||||
|
||||
string accType = matchType.Groups[1].Value == "#" ? matchType.Groups[2].Value : "account";
|
||||
return matchAdvanced.Success && !matchAdvanced.Value.Contains("false") ? "advanced/"+accType : accType;
|
||||
}catch{
|
||||
return "(unknown)";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class ExternalInfo{
|
||||
public static ExternalInfo From(Form form){
|
||||
if (form == null){
|
||||
return new ExternalInfo();
|
||||
}
|
||||
else{
|
||||
Screen screen = Screen.FromControl(form);
|
||||
int dpi;
|
||||
|
||||
using(Graphics graphics = form.CreateGraphics()){
|
||||
dpi = (int)graphics.DpiY;
|
||||
}
|
||||
|
||||
return new ExternalInfo{
|
||||
Resolution = screen.Bounds.Width+"x"+screen.Bounds.Height,
|
||||
DPI = dpi
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public string Resolution { get; private set; }
|
||||
public int? DPI { get; private set; }
|
||||
|
||||
private ExternalInfo(){}
|
||||
}
|
||||
}
|
||||
}
|
3
Core/Other/FormAbout.Designer.cs
generated
3
Core/Other/FormAbout.Designer.cs
generated
@@ -131,11 +131,14 @@ namespace TweetDuck.Core.Other {
|
||||
this.Controls.Add(this.labelDescription);
|
||||
this.Controls.Add(this.pictureLogo);
|
||||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
|
||||
this.HelpButton = true;
|
||||
this.MaximizeBox = false;
|
||||
this.MinimizeBox = false;
|
||||
this.Name = "FormAbout";
|
||||
this.ShowIcon = false;
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
||||
this.HelpButtonClicked += new System.ComponentModel.CancelEventHandler(this.FormAbout_HelpButtonClicked);
|
||||
this.HelpRequested += new System.Windows.Forms.HelpEventHandler(this.FormAbout_HelpRequested);
|
||||
((System.ComponentModel.ISupportInitialize)(this.pictureLogo)).EndInit();
|
||||
this.tablePanelLinks.ResumeLayout(false);
|
||||
this.tablePanelLinks.PerformLayout();
|
||||
|
@@ -1,4 +1,5 @@
|
||||
using System.Windows.Forms;
|
||||
using System.ComponentModel;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Core.Other{
|
||||
@@ -19,7 +20,21 @@ namespace TweetDuck.Core.Other{
|
||||
}
|
||||
|
||||
private void OnLinkClicked(object sender, LinkLabelLinkClickedEventArgs e){
|
||||
BrowserUtils.OpenExternalBrowserUnsafe(e.Link.LinkData as string);
|
||||
BrowserUtils.OpenExternalBrowser(e.Link.LinkData as string);
|
||||
}
|
||||
|
||||
private void FormAbout_HelpRequested(object sender, HelpEventArgs hlpevent){
|
||||
ShowGuide();
|
||||
}
|
||||
|
||||
private void FormAbout_HelpButtonClicked(object sender, CancelEventArgs e){
|
||||
e.Cancel = true;
|
||||
ShowGuide();
|
||||
}
|
||||
|
||||
private void ShowGuide(){
|
||||
new FormGuide().Show();
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
44
Core/Other/FormGuide.Designer.cs
generated
Normal file
44
Core/Other/FormGuide.Designer.cs
generated
Normal file
@@ -0,0 +1,44 @@
|
||||
namespace TweetDuck.Core.Other {
|
||||
partial class FormGuide {
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing) {
|
||||
if (disposing && (components != null)) {
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent() {
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// FormGuide
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(34)))), ((int)(((byte)(34)))), ((int)(((byte)(34)))));
|
||||
this.ClientSize = new System.Drawing.Size(424, 282);
|
||||
this.Icon = global::TweetDuck.Properties.Resources.icon;
|
||||
this.MinimumSize = new System.Drawing.Size(440, 320);
|
||||
this.Name = "FormGuide";
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
|
||||
this.ResumeLayout(false);
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
74
Core/Other/FormGuide.cs
Normal file
74
Core/Other/FormGuide.cs
Normal file
@@ -0,0 +1,74 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
using CefSharp;
|
||||
using CefSharp.WinForms;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Handling;
|
||||
using TweetDuck.Core.Handling.General;
|
||||
using TweetDuck.Core.Other.Analytics;
|
||||
using TweetDuck.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Core.Other{
|
||||
sealed partial class FormGuide : Form{
|
||||
private const string GuideUrl = "https://tweetduck.chylex.com/guide/v2/";
|
||||
|
||||
private readonly ChromiumWebBrowser browser;
|
||||
|
||||
public FormGuide(){
|
||||
InitializeComponent();
|
||||
|
||||
Text = Program.BrandName+" Guide";
|
||||
|
||||
FormBrowser owner = FormManager.TryFind<FormBrowser>();
|
||||
|
||||
if (owner != null){
|
||||
Size = new Size(owner.Size.Width*3/4, owner.Size.Height*3/4);
|
||||
VisibleChanged += (sender, args) => this.MoveToCenter(owner);
|
||||
|
||||
owner.TriggerAnalyticsEvent(AnalyticsFile.Event.OpenGuide);
|
||||
}
|
||||
|
||||
this.browser = new ChromiumWebBrowser(GuideUrl){
|
||||
MenuHandler = new ContextMenuGuide(),
|
||||
JsDialogHandler = new JavaScriptDialogHandler(),
|
||||
LifeSpanHandler = new LifeSpanHandler(),
|
||||
RequestHandler = new RequestHandlerBrowser()
|
||||
};
|
||||
|
||||
browser.LoadingStateChanged += browser_LoadingStateChanged;
|
||||
browser.FrameLoadStart += browser_FrameLoadStart;
|
||||
|
||||
browser.BrowserSettings.BackgroundColor = (uint)BackColor.ToArgb();
|
||||
browser.Dock = DockStyle.None;
|
||||
browser.Location = ControlExtensions.InvisibleLocation;
|
||||
Controls.Add(browser);
|
||||
|
||||
Disposed += (sender, args) => {
|
||||
Program.UserConfig.ZoomLevelChanged -= Config_ZoomLevelChanged;
|
||||
browser.Dispose();
|
||||
};
|
||||
|
||||
Program.UserConfig.ZoomLevelChanged += Config_ZoomLevelChanged;
|
||||
}
|
||||
|
||||
private void browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e){
|
||||
if (!e.IsLoading){
|
||||
this.InvokeAsyncSafe(() => {
|
||||
browser.Location = Point.Empty;
|
||||
browser.Dock = DockStyle.Fill;
|
||||
});
|
||||
|
||||
browser.LoadingStateChanged -= browser_LoadingStateChanged;
|
||||
}
|
||||
}
|
||||
|
||||
private void browser_FrameLoadStart(object sender, FrameLoadStartEventArgs e){
|
||||
BrowserUtils.SetZoomLevel(browser.GetBrowser(), Program.UserConfig.ZoomLevel);
|
||||
}
|
||||
|
||||
private void Config_ZoomLevelChanged(object sender, EventArgs e){
|
||||
BrowserUtils.SetZoomLevel(browser.GetBrowser(), Program.UserConfig.ZoomLevel);
|
||||
}
|
||||
}
|
||||
}
|
@@ -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{
|
||||
|
@@ -35,8 +35,8 @@ namespace TweetDuck.Core.Other{
|
||||
}
|
||||
|
||||
private void ReloadPluginList(){
|
||||
flowLayoutPlugins.SuspendLayout();
|
||||
flowLayoutPlugins.Controls.Clear();
|
||||
flowLayoutPlugins.SuspendLayout();
|
||||
|
||||
foreach(Plugin plugin in pluginManager.Plugins.OrderBy(GetPluginOrderIndex).ThenBy(plugin => plugin.Name)){
|
||||
flowLayoutPlugins.Controls.Add(new PluginControl(pluginManager, plugin));
|
||||
|
@@ -3,6 +3,9 @@ using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Handling.General;
|
||||
using TweetDuck.Core.Notification.Example;
|
||||
using TweetDuck.Core.Other.Analytics;
|
||||
using TweetDuck.Core.Other.Settings;
|
||||
using TweetDuck.Core.Other.Settings.Dialogs;
|
||||
using TweetDuck.Core.Utils;
|
||||
@@ -19,7 +22,9 @@ namespace TweetDuck.Core.Other{
|
||||
private readonly Dictionary<Type, SettingsTab> tabs = new Dictionary<Type, SettingsTab>(4);
|
||||
private SettingsTab currentTab;
|
||||
|
||||
public FormSettings(FormBrowser browser, PluginManager plugins, UpdateHandler updates, Type startTab){
|
||||
public bool ShouldReloadBrowser { get; private set; }
|
||||
|
||||
public FormSettings(FormBrowser browser, PluginManager plugins, UpdateHandler updates, AnalyticsManager analytics, Type startTab){
|
||||
InitializeComponent();
|
||||
|
||||
Text = Program.BrandName+" Options";
|
||||
@@ -31,18 +36,21 @@ namespace TweetDuck.Core.Other{
|
||||
|
||||
this.buttonHeight = BrowserUtils.Scale(39, this.GetDPIScale()) | 1;
|
||||
|
||||
AddButton("General", () => new TabSettingsGeneral(updates));
|
||||
AddButton("Notifications", () => new TabSettingsNotifications(browser.CreateNotificationForm(false)));
|
||||
AddButton("General", () => new TabSettingsGeneral(this.browser, updates));
|
||||
AddButton("System Tray", () => new TabSettingsTray());
|
||||
AddButton("Notifications", () => new TabSettingsNotifications(new FormNotificationExample(this.browser, this.plugins)));
|
||||
AddButton("Sounds", () => new TabSettingsSounds());
|
||||
AddButton("Advanced", () => new TabSettingsAdvanced(browser.ReinjectCustomCSS));
|
||||
AddButton("Feedback", () => new TabSettingsFeedback(analytics, AnalyticsReportGenerator.ExternalInfo.From(this.browser), this.plugins));
|
||||
AddButton("Advanced", () => new TabSettingsAdvanced(this.browser.ReinjectCustomCSS));
|
||||
|
||||
SelectTab(tabs[startTab ?? typeof(TabSettingsGeneral)]);
|
||||
}
|
||||
|
||||
private void FormSettings_FormClosing(object sender, FormClosingEventArgs e){
|
||||
currentTab.Control.OnClosing();
|
||||
|
||||
foreach(SettingsTab tab in tabs.Values){
|
||||
if (tab.IsInitialized){
|
||||
tab.Control.OnClosing();
|
||||
tab.Control.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -52,13 +60,20 @@ 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;
|
||||
}
|
||||
currentTab.Control.OnClosing();
|
||||
|
||||
SelectTab(currentTab);
|
||||
using(DialogSettingsManage dialog = new DialogSettingsManage(plugins)){
|
||||
FormClosing -= FormSettings_FormClosing;
|
||||
|
||||
if (dialog.ShowDialog() == DialogResult.OK){
|
||||
browser.ResumeNotification();
|
||||
|
||||
BrowserProcessHandler.UpdatePrefs();
|
||||
ShouldReloadBrowser = dialog.ShouldReloadBrowser;
|
||||
Close();
|
||||
}
|
||||
else{
|
||||
FormClosing += FormSettings_FormClosing;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -103,6 +118,7 @@ namespace TweetDuck.Core.Other{
|
||||
private void SelectTab(SettingsTab tab){
|
||||
if (currentTab != null){
|
||||
currentTab.Button.BackColor = SystemColors.Control;
|
||||
currentTab.Control.OnClosing();
|
||||
}
|
||||
|
||||
tab.Button.BackColor = tab.Button.FlatAppearance.MouseDownBackColor;
|
||||
@@ -120,11 +136,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,14 +160,10 @@ namespace TweetDuck.Core.Other{
|
||||
panelContents.Focus();
|
||||
}
|
||||
|
||||
private class SettingsTab{
|
||||
private sealed class SettingsTab{
|
||||
public Button Button { get; }
|
||||
|
||||
public BaseTabSettings Control{
|
||||
get => control ?? (control = constructor());
|
||||
set => control = value;
|
||||
}
|
||||
|
||||
public BaseTabSettings Control => control ?? (control = constructor());
|
||||
public bool IsInitialized => control != null;
|
||||
|
||||
private readonly Func<BaseTabSettings> constructor;
|
||||
|
@@ -1,120 +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>
|
||||
</root>
|
@@ -1,8 +1,9 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using CefSharp;
|
||||
using TweetDuck.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Core.Utils{
|
||||
namespace TweetDuck.Core.Other.Management{
|
||||
static class BrowserProcesses{
|
||||
private static readonly Dictionary<int, int> PIDs = new Dictionary<int, int>();
|
||||
|
@@ -1,18 +1,19 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Timers;
|
||||
using System.Windows.Forms;
|
||||
using CefSharp;
|
||||
using CefSharp.WinForms;
|
||||
using TweetDuck.Core.Controls;
|
||||
using Timer = System.Timers.Timer;
|
||||
|
||||
namespace TweetDuck.Core.Utils{
|
||||
namespace TweetDuck.Core.Other.Management{
|
||||
sealed class MemoryUsageTracker : IDisposable{
|
||||
private const int IntervalMemoryCheck = 60000*30; // 30 minutes
|
||||
private const int IntervalCleanupAttempt = 60000*5; // 5 minutes
|
||||
|
||||
private readonly string script;
|
||||
private readonly Timer timer;
|
||||
private Form owner;
|
||||
private FormBrowser owner;
|
||||
private IBrowser browser;
|
||||
|
||||
private long threshold;
|
||||
@@ -25,11 +26,11 @@ namespace TweetDuck.Core.Utils{
|
||||
this.timer.Elapsed += timer_Elapsed;
|
||||
}
|
||||
|
||||
public void Start(Form owner, IBrowser browser, int thresholdMB){
|
||||
public void Start(ChromiumWebBrowser control, int thresholdMB){
|
||||
Stop();
|
||||
|
||||
this.owner = owner;
|
||||
this.browser = browser;
|
||||
this.owner = (FormBrowser)control.Parent; // TODO ugly
|
||||
this.browser = control.GetBrowser();
|
||||
this.threshold = thresholdMB*1024L*1024L;
|
||||
this.timer.SynchronizingObject = owner;
|
||||
this.timer.Start();
|
||||
@@ -70,6 +71,7 @@ namespace TweetDuck.Core.Utils{
|
||||
|
||||
if (response.Success && (response.Result as bool? ?? false)){
|
||||
SetNeedsCleanup(false);
|
||||
owner.InvokeAsyncSafe(() => owner.TriggerAnalyticsEvent(Analytics.AnalyticsFile.Event.GCReload));
|
||||
}
|
||||
});
|
||||
}
|
181
Core/Other/Management/VideoPlayer.cs
Normal file
181
Core/Other/Management/VideoPlayer.cs
Normal file
@@ -0,0 +1,181 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
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{
|
||||
public bool Running{
|
||||
get{
|
||||
if (currentProcess == null){
|
||||
return false;
|
||||
}
|
||||
|
||||
currentProcess.Refresh();
|
||||
return !currentProcess.HasExited;
|
||||
}
|
||||
}
|
||||
|
||||
public event EventHandler ProcessExited;
|
||||
|
||||
private readonly Form owner;
|
||||
private string lastUrl;
|
||||
private string lastUsername;
|
||||
|
||||
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, string username){
|
||||
if (Running){
|
||||
Destroy();
|
||||
isClosing = false;
|
||||
}
|
||||
|
||||
lastUrl = url;
|
||||
lastUsername = username;
|
||||
|
||||
try{
|
||||
currentPipe = DuplexPipe.CreateServer();
|
||||
currentPipe.DataIn += currentPipe_DataIn;
|
||||
|
||||
if ((currentProcess = Process.Start(new ProcessStartInfo{
|
||||
FileName = Path.Combine(Program.ProgramPath, "TweetDuck.Video.exe"),
|
||||
Arguments = $"{owner.Handle} {Program.UserConfig.VideoPlayerVolume} \"{url}\" \"{currentPipe.GenerateToken()}\"",
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = true
|
||||
})) != null){
|
||||
currentProcess.EnableRaisingEvents = true;
|
||||
currentProcess.Exited += process_Exited;
|
||||
|
||||
#if DEBUG
|
||||
currentProcess.BeginOutputReadLine();
|
||||
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, lastUsername);
|
||||
break;
|
||||
|
||||
case "rip":
|
||||
currentPipe.Dispose();
|
||||
currentPipe = null;
|
||||
|
||||
currentProcess.Dispose();
|
||||
currentProcess = null;
|
||||
|
||||
isClosing = false;
|
||||
TriggerProcessExitEventUnsafe();
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void Close(){
|
||||
if (currentProcess != null){
|
||||
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{
|
||||
// kill me instead then
|
||||
}
|
||||
|
||||
currentProcess.Dispose();
|
||||
currentProcess = null;
|
||||
|
||||
currentPipe.Dispose();
|
||||
currentPipe = null;
|
||||
|
||||
TriggerProcessExitEventUnsafe();
|
||||
}
|
||||
}
|
||||
|
||||
private void owner_FormClosing(object sender, FormClosingEventArgs e){
|
||||
if (currentProcess != null){
|
||||
currentProcess.Exited -= process_Exited;
|
||||
}
|
||||
}
|
||||
|
||||
private void process_Exited(object sender, EventArgs e){
|
||||
int exitCode = currentProcess.ExitCode;
|
||||
|
||||
currentProcess.Dispose();
|
||||
currentProcess = null;
|
||||
|
||||
currentPipe.Dispose();
|
||||
currentPipe = null;
|
||||
|
||||
switch(exitCode){
|
||||
case 3: // CODE_LAUNCH_FAIL
|
||||
if (FormMessage.Error("Video Playback Error", "Error launching video player, this may be caused by missing Windows Media Player. Do you want to open the video in your browser?", FormMessage.Yes, FormMessage.No)){
|
||||
BrowserUtils.OpenExternalBrowser(lastUrl);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 4: // CODE_MEDIA_ERROR
|
||||
if (FormMessage.Error("Video Playback Error", "The video could not be loaded, most likely due to unknown format. Do you want to open the video in your browser?", FormMessage.Yes, FormMessage.No)){
|
||||
BrowserUtils.OpenExternalBrowser(lastUrl);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
owner.InvokeAsyncSafe(TriggerProcessExitEventUnsafe);
|
||||
}
|
||||
|
||||
private void TriggerProcessExitEventUnsafe(){
|
||||
ProcessExited?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
}
|
@@ -17,7 +17,7 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
}
|
||||
}
|
||||
|
||||
public BaseTabSettings(){
|
||||
protected BaseTabSettings(){
|
||||
Padding = new Padding(6);
|
||||
}
|
||||
|
||||
|
90
Core/Other/Settings/Dialogs/DialogSettingsAnalytics.Designer.cs
generated
Normal file
90
Core/Other/Settings/Dialogs/DialogSettingsAnalytics.Designer.cs
generated
Normal file
@@ -0,0 +1,90 @@
|
||||
namespace TweetDuck.Core.Other.Settings.Dialogs {
|
||||
partial class DialogSettingsAnalytics {
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing) {
|
||||
if (disposing && (components != null)) {
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent() {
|
||||
this.textBoxReport = new System.Windows.Forms.TextBox();
|
||||
this.btnClose = new System.Windows.Forms.Button();
|
||||
this.labelInfo = new System.Windows.Forms.Label();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// textBoxReport
|
||||
//
|
||||
this.textBoxReport.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
||||
| System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.textBoxReport.Font = new System.Drawing.Font("Courier New", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
|
||||
this.textBoxReport.Location = new System.Drawing.Point(12, 41);
|
||||
this.textBoxReport.Multiline = true;
|
||||
this.textBoxReport.Name = "textBoxReport";
|
||||
this.textBoxReport.ReadOnly = true;
|
||||
this.textBoxReport.ScrollBars = System.Windows.Forms.ScrollBars.Both;
|
||||
this.textBoxReport.Size = new System.Drawing.Size(460, 480);
|
||||
this.textBoxReport.TabIndex = 1;
|
||||
//
|
||||
// btnClose
|
||||
//
|
||||
this.btnClose.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.btnClose.Location = new System.Drawing.Point(416, 527);
|
||||
this.btnClose.Name = "btnClose";
|
||||
this.btnClose.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
|
||||
this.btnClose.Size = new System.Drawing.Size(56, 23);
|
||||
this.btnClose.TabIndex = 2;
|
||||
this.btnClose.Text = "Close";
|
||||
this.btnClose.UseVisualStyleBackColor = true;
|
||||
this.btnClose.Click += new System.EventHandler(this.btnClose_Click);
|
||||
//
|
||||
// labelInfo
|
||||
//
|
||||
this.labelInfo.Location = new System.Drawing.Point(12, 9);
|
||||
this.labelInfo.Margin = new System.Windows.Forms.Padding(3, 0, 3, 3);
|
||||
this.labelInfo.Name = "labelInfo";
|
||||
this.labelInfo.Size = new System.Drawing.Size(460, 26);
|
||||
this.labelInfo.TabIndex = 0;
|
||||
this.labelInfo.Text = "When enabled, this data will be sent over a secure network roughly once every wee" +
|
||||
"k.\r\nSome numbers in the report were made imprecise on purpose.";
|
||||
//
|
||||
// DialogSettingsAnalytics
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.ClientSize = new System.Drawing.Size(484, 562);
|
||||
this.Controls.Add(this.labelInfo);
|
||||
this.Controls.Add(this.btnClose);
|
||||
this.Controls.Add(this.textBoxReport);
|
||||
this.MinimumSize = new System.Drawing.Size(450, 340);
|
||||
this.Name = "DialogSettingsAnalytics";
|
||||
this.ShowIcon = false;
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.TextBox textBoxReport;
|
||||
private System.Windows.Forms.Button btnClose;
|
||||
private System.Windows.Forms.Label labelInfo;
|
||||
}
|
||||
}
|
24
Core/Other/Settings/Dialogs/DialogSettingsAnalytics.cs
Normal file
24
Core/Other/Settings/Dialogs/DialogSettingsAnalytics.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Other.Analytics;
|
||||
|
||||
namespace TweetDuck.Core.Other.Settings.Dialogs{
|
||||
sealed partial class DialogSettingsAnalytics : Form{
|
||||
public string CefArgs => textBoxReport.Text;
|
||||
|
||||
public DialogSettingsAnalytics(AnalyticsReport report){
|
||||
InitializeComponent();
|
||||
|
||||
Text = Program.BrandName+" Options - Analytics Report";
|
||||
|
||||
textBoxReport.EnableMultilineShortcuts();
|
||||
textBoxReport.Text = report.ToString().TrimEnd();
|
||||
textBoxReport.Select(0, 0);
|
||||
}
|
||||
|
||||
private void btnClose_Click(object sender, EventArgs e){
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
@@ -35,7 +35,7 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
|
||||
}
|
||||
|
||||
private void btnOpenWiki_Click(object sender, EventArgs e){
|
||||
BrowserUtils.OpenExternalBrowserUnsafe("https://github.com/chylex/TweetDuck/wiki");
|
||||
BrowserUtils.OpenExternalBrowser("https://github.com/chylex/TweetDuck/wiki");
|
||||
}
|
||||
|
||||
private void btnApply_Click(object sender, EventArgs e){
|
||||
|
@@ -19,7 +19,7 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
|
||||
}
|
||||
|
||||
private void btnHelp_Click(object sender, EventArgs e){
|
||||
BrowserUtils.OpenExternalBrowserUnsafe("http://peter.sh/experiments/chromium-command-line-switches/");
|
||||
BrowserUtils.OpenExternalBrowser("http://peter.sh/experiments/chromium-command-line-switches/");
|
||||
}
|
||||
|
||||
private void btnApply_Click(object sender, EventArgs e){
|
||||
|
@@ -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{
|
||||
@@ -14,13 +16,13 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
|
||||
|
||||
set{
|
||||
// this will call events and SetFlag, which also updates the UI
|
||||
cbConfig.Checked = value.HasFlag(ExportFileFlags.Config);
|
||||
cbConfig.Checked = value.HasFlag(ExportFileFlags.UserConfig);
|
||||
cbSession.Checked = value.HasFlag(ExportFileFlags.Session);
|
||||
cbPluginData.Checked = value.HasFlag(ExportFileFlags.PluginData);
|
||||
}
|
||||
}
|
||||
|
||||
public bool ShouldReloadUI { get; private set; }
|
||||
public bool ShouldReloadBrowser { get; private set; }
|
||||
|
||||
private readonly PluginManager plugins;
|
||||
private State currentState;
|
||||
@@ -40,7 +42,7 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
|
||||
}
|
||||
|
||||
private void cbConfig_CheckedChanged(object sender, EventArgs e){
|
||||
SetFlag(ExportFileFlags.Config, cbConfig.Checked);
|
||||
SetFlag(ExportFileFlags.UserConfig, cbConfig.Checked);
|
||||
}
|
||||
|
||||
private void cbSession_CheckedChanged(object sender, EventArgs e){
|
||||
@@ -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.UserConfig;
|
||||
}
|
||||
|
||||
// Import
|
||||
@@ -109,10 +106,59 @@ 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.UserConfig)){
|
||||
Program.ResetConfig();
|
||||
}
|
||||
|
||||
if (Flags.HasFlag(ExportFileFlags.SystemConfig)){
|
||||
try{
|
||||
File.Delete(Program.SystemConfigFilePath);
|
||||
}catch(Exception ex){
|
||||
Program.Reporter.HandleException("System Config Reset Error", "Could not delete system config.", true, ex);
|
||||
}
|
||||
}
|
||||
|
||||
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 if (Flags.HasFlag(ExportFileFlags.SystemConfig)){
|
||||
Program.Restart();
|
||||
}
|
||||
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){
|
||||
if (Flags.HasFlag(ExportFileFlags.Session)){
|
||||
Program.Restart(Arguments.ArgImportCookies);
|
||||
}
|
||||
else if (Flags.HasFlag(ExportFileFlags.SystemConfig)){
|
||||
Program.Restart();
|
||||
}
|
||||
}
|
||||
else{
|
||||
ShouldReloadBrowser = true;
|
||||
}
|
||||
}
|
||||
else{
|
||||
@@ -141,6 +187,8 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
|
||||
}
|
||||
|
||||
Program.UserConfig.Save();
|
||||
Program.SystemConfig.Save();
|
||||
|
||||
ExportManager manager = new ExportManager(file, plugins);
|
||||
|
||||
if (!manager.Export(Flags)){
|
||||
@@ -165,6 +213,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();
|
||||
}
|
||||
|
@@ -4,9 +4,10 @@ namespace TweetDuck.Core.Other.Settings.Export{
|
||||
[Flags]
|
||||
enum ExportFileFlags{
|
||||
None = 0,
|
||||
Config = 1,
|
||||
Session = 2,
|
||||
PluginData = 4,
|
||||
All = Config|Session|PluginData
|
||||
UserConfig = 1,
|
||||
SystemConfig = 2, // TODO implement later
|
||||
Session = 4,
|
||||
PluginData = 8,
|
||||
All = UserConfig|SystemConfig|Session|PluginData
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
@@ -26,10 +25,14 @@ namespace TweetDuck.Core.Other.Settings.Export{
|
||||
public bool Export(ExportFileFlags flags){
|
||||
try{
|
||||
using(CombinedFileStream stream = new CombinedFileStream(new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None))){
|
||||
if (flags.HasFlag(ExportFileFlags.Config)){
|
||||
if (flags.HasFlag(ExportFileFlags.UserConfig)){
|
||||
stream.WriteFile("config", Program.UserConfigFilePath);
|
||||
}
|
||||
|
||||
if (flags.HasFlag(ExportFileFlags.SystemConfig)){
|
||||
stream.WriteFile("system", Program.SystemConfigFilePath);
|
||||
}
|
||||
|
||||
if (flags.HasFlag(ExportFileFlags.PluginData)){
|
||||
stream.WriteFile("plugin.config", Program.PluginConfigFilePath);
|
||||
|
||||
@@ -68,7 +71,11 @@ namespace TweetDuck.Core.Other.Settings.Export{
|
||||
while((key = stream.SkipFile()) != null){
|
||||
switch(key){
|
||||
case "config":
|
||||
flags |= ExportFileFlags.Config;
|
||||
flags |= ExportFileFlags.UserConfig;
|
||||
break;
|
||||
|
||||
case "system":
|
||||
flags |= ExportFileFlags.SystemConfig;
|
||||
break;
|
||||
|
||||
case "plugin.config":
|
||||
@@ -100,12 +107,20 @@ namespace TweetDuck.Core.Other.Settings.Export{
|
||||
while((entry = stream.ReadFile()) != null){
|
||||
switch(entry.KeyName){
|
||||
case "config":
|
||||
if (flags.HasFlag(ExportFileFlags.Config)){
|
||||
if (flags.HasFlag(ExportFileFlags.UserConfig)){
|
||||
entry.WriteToFile(Program.UserConfigFilePath);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case "system":
|
||||
if (flags.HasFlag(ExportFileFlags.SystemConfig)){
|
||||
entry.WriteToFile(Program.SystemConfigFilePath);
|
||||
IsRestarting = true;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case "plugin.config":
|
||||
if (flags.HasFlag(ExportFileFlags.PluginData)){
|
||||
entry.WriteToFile(Program.PluginConfigFilePath);
|
||||
@@ -141,13 +156,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 +177,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 +194,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; }
|
||||
}
|
||||
|
22
Core/Other/Settings/TabSettingsAdvanced.Designer.cs
generated
22
Core/Other/Settings/TabSettingsAdvanced.Designer.cs
generated
@@ -68,7 +68,7 @@
|
||||
this.checkHardwareAcceleration.Size = new System.Drawing.Size(134, 17);
|
||||
this.checkHardwareAcceleration.TabIndex = 0;
|
||||
this.checkHardwareAcceleration.Text = "Hardware Acceleration";
|
||||
this.toolTip.SetToolTip(this.checkHardwareAcceleration, "Uses your graphics card to improve performance.\r\nDisable if you experience issues with rendering.");
|
||||
this.toolTip.SetToolTip(this.checkHardwareAcceleration, "Uses graphics card to improve performance. Disable if you experience\r\nvisual glitches. This option will not be exported in a profile.");
|
||||
this.checkHardwareAcceleration.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// btnEditCefArgs
|
||||
@@ -142,26 +142,14 @@
|
||||
0,
|
||||
0});
|
||||
this.numMemoryThreshold.Location = new System.Drawing.Point(202, 82);
|
||||
this.numMemoryThreshold.Maximum = new decimal(new int[] {
|
||||
3000,
|
||||
0,
|
||||
0,
|
||||
0});
|
||||
this.numMemoryThreshold.Minimum = new decimal(new int[] {
|
||||
200,
|
||||
0,
|
||||
0,
|
||||
0});
|
||||
this.numMemoryThreshold.Maximum = 2000;
|
||||
this.numMemoryThreshold.Minimum = 200;
|
||||
this.numMemoryThreshold.Name = "numMemoryThreshold";
|
||||
this.numMemoryThreshold.Size = new System.Drawing.Size(97, 20);
|
||||
this.numMemoryThreshold.TabIndex = 4;
|
||||
this.numMemoryThreshold.TextSuffix = " MB";
|
||||
this.toolTip.SetToolTip(this.numMemoryThreshold, "Minimum amount of memory usage by the browser process to trigger the cleanup.\r\nThis is not a limit, the usage is allowed to exceed this value.");
|
||||
this.numMemoryThreshold.Value = new decimal(new int[] {
|
||||
350,
|
||||
0,
|
||||
0,
|
||||
0});
|
||||
this.numMemoryThreshold.Value = 400;
|
||||
//
|
||||
// checkBrowserGCReload
|
||||
//
|
||||
@@ -172,7 +160,7 @@
|
||||
this.checkBrowserGCReload.Size = new System.Drawing.Size(190, 17);
|
||||
this.checkBrowserGCReload.TabIndex = 3;
|
||||
this.checkBrowserGCReload.Text = "Enable Browser Memory Threshold";
|
||||
this.toolTip.SetToolTip(this.checkBrowserGCReload, "Automatically reloads TweetDeck to save memory. This option only works\r\nif the browser is in a \'default state\', i.e. all modals and drawers are closed,\r\nand all columns are scrolled to top. Some notifications may be lost.");
|
||||
this.toolTip.SetToolTip(this.checkBrowserGCReload, "Automatically reloads TweetDeck to save memory. This option only works if\r\nthe browser is in a \'default state\', i.e. all modals and drawers are closed, and\r\nall columns are scrolled to top. This option will not be exported in a profile.");
|
||||
this.checkBrowserGCReload.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// labelApp
|
||||
|
@@ -7,7 +7,9 @@ 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;
|
||||
|
||||
public TabSettingsAdvanced(Action<string> reinjectBrowserCSS){
|
||||
@@ -16,25 +18,21 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
this.reinjectBrowserCSS = reinjectBrowserCSS;
|
||||
|
||||
if (SystemConfig.IsHardwareAccelerationSupported){
|
||||
checkHardwareAcceleration.Checked = Program.SystemConfig.HardwareAcceleration;
|
||||
checkHardwareAcceleration.Checked = SysConfig.HardwareAcceleration;
|
||||
}
|
||||
else{
|
||||
checkHardwareAcceleration.Enabled = false;
|
||||
checkHardwareAcceleration.Checked = false;
|
||||
}
|
||||
|
||||
checkBrowserGCReload.Checked = Config.EnableBrowserGCReload;
|
||||
checkBrowserGCReload.Checked = SysConfig.EnableBrowserGCReload;
|
||||
numMemoryThreshold.Enabled = checkBrowserGCReload.Checked;
|
||||
numMemoryThreshold.SetValueSafe(Config.BrowserMemoryThreshold);
|
||||
numMemoryThreshold.SetValueSafe(SysConfig.BrowserMemoryThreshold);
|
||||
|
||||
BrowserCache.CalculateCacheSize(bytes => this.InvokeSafe(() => {
|
||||
if (bytes == -1L){
|
||||
btnClearCache.Text = "Clear Cache (unknown size)";
|
||||
}
|
||||
else{
|
||||
btnClearCache.Text = "Clear Cache ("+(int)Math.Ceiling(bytes/(1024.0*1024.0))+" MB)";
|
||||
}
|
||||
}));
|
||||
BrowserCache.CalculateCacheSize(task => {
|
||||
string text = task.IsCompleted ? (int)Math.Ceiling(task.Result/(1024.0*1024.0))+" MB" : "(unknown size)";
|
||||
this.InvokeSafe(() => btnClearCache.Text = $"Clear Cache ({text})");
|
||||
});
|
||||
}
|
||||
|
||||
public override void OnReady(){
|
||||
@@ -53,6 +51,10 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
btnRestartArgs.Click += btnRestartArgs_Click;
|
||||
}
|
||||
|
||||
public override void OnClosing(){
|
||||
SysConfig.Save();
|
||||
}
|
||||
|
||||
private void btnClearCache_Click(object sender, EventArgs e){
|
||||
btnClearCache.Enabled = false;
|
||||
BrowserCache.SetClearOnExit();
|
||||
@@ -60,18 +62,17 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
}
|
||||
|
||||
private void checkHardwareAcceleration_CheckedChanged(object sender, EventArgs e){
|
||||
Program.SystemConfig.HardwareAcceleration = checkHardwareAcceleration.Checked;
|
||||
Program.SystemConfig.Save();
|
||||
PromptRestart();
|
||||
SysConfig.HardwareAcceleration = checkHardwareAcceleration.Checked;
|
||||
PromptRestart(); // calls OnClosing
|
||||
}
|
||||
|
||||
private void checkBrowserGCReload_CheckedChanged(object sender, EventArgs e){
|
||||
Config.EnableBrowserGCReload = checkBrowserGCReload.Checked;
|
||||
SysConfig.EnableBrowserGCReload = checkBrowserGCReload.Checked;
|
||||
numMemoryThreshold.Enabled = checkBrowserGCReload.Checked;
|
||||
}
|
||||
|
||||
private void numMemoryThreshold_ValueChanged(object sender, EventArgs e){
|
||||
Config.BrowserMemoryThreshold = (int)numMemoryThreshold.Value;
|
||||
SysConfig.BrowserMemoryThreshold = (int)numMemoryThreshold.Value;
|
||||
}
|
||||
|
||||
private void btnEditCefArgs_Click(object sender, EventArgs e){
|
||||
@@ -82,7 +83,7 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
};
|
||||
|
||||
form.FormClosed += (sender2, args2) => {
|
||||
NativeMethods.SetFormDisabled(ParentForm, false);
|
||||
RestoreParentForm();
|
||||
|
||||
if (form.DialogResult == DialogResult.OK){
|
||||
Config.CustomCefArgs = form.CefArgs;
|
||||
@@ -104,7 +105,7 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
};
|
||||
|
||||
form.FormClosed += (sender2, args2) => {
|
||||
NativeMethods.SetFormDisabled(ParentForm, false);
|
||||
RestoreParentForm();
|
||||
|
||||
if (form.DialogResult == DialogResult.OK){
|
||||
Config.CustomBrowserCSS = form.BrowserCSS;
|
||||
@@ -138,5 +139,11 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RestoreParentForm(){
|
||||
if (ParentForm != null){ // when the parent is closed first, ParentForm is null in FormClosed event
|
||||
NativeMethods.SetFormDisabled(ParentForm, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
158
Core/Other/Settings/TabSettingsFeedback.Designer.cs
generated
Normal file
158
Core/Other/Settings/TabSettingsFeedback.Designer.cs
generated
Normal file
@@ -0,0 +1,158 @@
|
||||
namespace TweetDuck.Core.Other.Settings {
|
||||
partial class TabSettingsFeedback {
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing) {
|
||||
if (disposing && (components != null)) {
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Component Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent() {
|
||||
this.components = new System.ComponentModel.Container();
|
||||
this.panelFeedback = new System.Windows.Forms.Panel();
|
||||
this.labelDataCollectionMessage = new System.Windows.Forms.Label();
|
||||
this.btnViewReport = new System.Windows.Forms.Button();
|
||||
this.btnSendFeedback = new System.Windows.Forms.Button();
|
||||
this.labelDataCollectionLink = new System.Windows.Forms.LinkLabel();
|
||||
this.checkDataCollection = new System.Windows.Forms.CheckBox();
|
||||
this.labelDataCollection = new System.Windows.Forms.Label();
|
||||
this.labelFeedback = new System.Windows.Forms.Label();
|
||||
this.toolTip = new System.Windows.Forms.ToolTip(this.components);
|
||||
this.panelFeedback.SuspendLayout();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// panelFeedback
|
||||
//
|
||||
this.panelFeedback.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.panelFeedback.Controls.Add(this.labelDataCollectionMessage);
|
||||
this.panelFeedback.Controls.Add(this.btnViewReport);
|
||||
this.panelFeedback.Controls.Add(this.btnSendFeedback);
|
||||
this.panelFeedback.Controls.Add(this.labelDataCollectionLink);
|
||||
this.panelFeedback.Controls.Add(this.checkDataCollection);
|
||||
this.panelFeedback.Controls.Add(this.labelDataCollection);
|
||||
this.panelFeedback.Location = new System.Drawing.Point(9, 31);
|
||||
this.panelFeedback.Name = "panelFeedback";
|
||||
this.panelFeedback.Size = new System.Drawing.Size(322, 188);
|
||||
this.panelFeedback.TabIndex = 1;
|
||||
//
|
||||
// labelDataCollectionMessage
|
||||
//
|
||||
this.labelDataCollectionMessage.Location = new System.Drawing.Point(6, 114);
|
||||
this.labelDataCollectionMessage.Margin = new System.Windows.Forms.Padding(6);
|
||||
this.labelDataCollectionMessage.Name = "labelDataCollectionMessage";
|
||||
this.labelDataCollectionMessage.Size = new System.Drawing.Size(310, 67);
|
||||
this.labelDataCollectionMessage.TabIndex = 5;
|
||||
//
|
||||
// btnViewReport
|
||||
//
|
||||
this.btnViewReport.AutoSize = true;
|
||||
this.btnViewReport.Location = new System.Drawing.Point(6, 82);
|
||||
this.btnViewReport.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3);
|
||||
this.btnViewReport.Name = "btnViewReport";
|
||||
this.btnViewReport.Size = new System.Drawing.Size(144, 23);
|
||||
this.btnViewReport.TabIndex = 4;
|
||||
this.btnViewReport.Text = "View My Analytics Report";
|
||||
this.btnViewReport.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// btnSendFeedback
|
||||
//
|
||||
this.btnSendFeedback.AutoSize = true;
|
||||
this.btnSendFeedback.Location = new System.Drawing.Point(5, 3);
|
||||
this.btnSendFeedback.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3);
|
||||
this.btnSendFeedback.Name = "btnSendFeedback";
|
||||
this.btnSendFeedback.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
|
||||
this.btnSendFeedback.Size = new System.Drawing.Size(164, 23);
|
||||
this.btnSendFeedback.TabIndex = 0;
|
||||
this.btnSendFeedback.Text = "Send Feedback / Bug Report";
|
||||
this.btnSendFeedback.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// labelDataCollectionLink
|
||||
//
|
||||
this.labelDataCollectionLink.AutoSize = true;
|
||||
this.labelDataCollectionLink.LinkArea = new System.Windows.Forms.LinkArea(1, 10);
|
||||
this.labelDataCollectionLink.LinkBehavior = System.Windows.Forms.LinkBehavior.HoverUnderline;
|
||||
this.labelDataCollectionLink.Location = new System.Drawing.Point(141, 60);
|
||||
this.labelDataCollectionLink.Margin = new System.Windows.Forms.Padding(0, 0, 3, 0);
|
||||
this.labelDataCollectionLink.Name = "labelDataCollectionLink";
|
||||
this.labelDataCollectionLink.Size = new System.Drawing.Size(66, 17);
|
||||
this.labelDataCollectionLink.TabIndex = 3;
|
||||
this.labelDataCollectionLink.TabStop = true;
|
||||
this.labelDataCollectionLink.Text = "(learn more)";
|
||||
this.labelDataCollectionLink.UseCompatibleTextRendering = true;
|
||||
//
|
||||
// checkDataCollection
|
||||
//
|
||||
this.checkDataCollection.AutoSize = true;
|
||||
this.checkDataCollection.Location = new System.Drawing.Point(6, 59);
|
||||
this.checkDataCollection.Margin = new System.Windows.Forms.Padding(6, 5, 0, 3);
|
||||
this.checkDataCollection.Name = "checkDataCollection";
|
||||
this.checkDataCollection.Size = new System.Drawing.Size(135, 17);
|
||||
this.checkDataCollection.TabIndex = 2;
|
||||
this.checkDataCollection.Text = "Send Anonymous Data";
|
||||
this.checkDataCollection.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// labelDataCollection
|
||||
//
|
||||
this.labelDataCollection.AutoSize = true;
|
||||
this.labelDataCollection.Location = new System.Drawing.Point(3, 41);
|
||||
this.labelDataCollection.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
|
||||
this.labelDataCollection.Name = "labelDataCollection";
|
||||
this.labelDataCollection.Size = new System.Drawing.Size(79, 13);
|
||||
this.labelDataCollection.TabIndex = 1;
|
||||
this.labelDataCollection.Text = "Data Collection";
|
||||
//
|
||||
// labelFeedback
|
||||
//
|
||||
this.labelFeedback.AutoSize = true;
|
||||
this.labelFeedback.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
|
||||
this.labelFeedback.Location = new System.Drawing.Point(6, 8);
|
||||
this.labelFeedback.Margin = new System.Windows.Forms.Padding(0, 2, 0, 0);
|
||||
this.labelFeedback.Name = "labelFeedback";
|
||||
this.labelFeedback.Size = new System.Drawing.Size(80, 20);
|
||||
this.labelFeedback.TabIndex = 0;
|
||||
this.labelFeedback.Text = "Feedback";
|
||||
//
|
||||
// TabSettingsFeedback
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.Controls.Add(this.panelFeedback);
|
||||
this.Controls.Add(this.labelFeedback);
|
||||
this.Name = "TabSettingsFeedback";
|
||||
this.Size = new System.Drawing.Size(340, 230);
|
||||
this.panelFeedback.ResumeLayout(false);
|
||||
this.panelFeedback.PerformLayout();
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.Panel panelFeedback;
|
||||
private System.Windows.Forms.CheckBox checkDataCollection;
|
||||
private System.Windows.Forms.Label labelDataCollection;
|
||||
private System.Windows.Forms.Label labelFeedback;
|
||||
private System.Windows.Forms.ToolTip toolTip;
|
||||
private System.Windows.Forms.LinkLabel labelDataCollectionLink;
|
||||
private System.Windows.Forms.Button btnSendFeedback;
|
||||
private System.Windows.Forms.Button btnViewReport;
|
||||
private System.Windows.Forms.Label labelDataCollectionMessage;
|
||||
}
|
||||
}
|
54
Core/Other/Settings/TabSettingsFeedback.cs
Normal file
54
Core/Other/Settings/TabSettingsFeedback.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Core.Other.Analytics;
|
||||
using TweetDuck.Core.Other.Settings.Dialogs;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetDuck.Plugins;
|
||||
|
||||
namespace TweetDuck.Core.Other.Settings{
|
||||
sealed partial class TabSettingsFeedback : BaseTabSettings{
|
||||
private readonly AnalyticsFile analyticsFile;
|
||||
private readonly AnalyticsReportGenerator.ExternalInfo analyticsInfo;
|
||||
private readonly PluginManager plugins;
|
||||
|
||||
public TabSettingsFeedback(AnalyticsManager analytics, AnalyticsReportGenerator.ExternalInfo analyticsInfo, PluginManager plugins){
|
||||
InitializeComponent();
|
||||
|
||||
this.analyticsFile = analytics?.File ?? AnalyticsFile.Load(Program.AnalyticsFilePath);
|
||||
this.analyticsInfo = analyticsInfo;
|
||||
this.plugins = plugins;
|
||||
|
||||
checkDataCollection.Checked = Config.AllowDataCollection;
|
||||
|
||||
if (analytics != null){
|
||||
string collectionTime = analyticsFile.LastCollectionMessage;
|
||||
labelDataCollectionMessage.Text = string.IsNullOrEmpty(collectionTime) ? "No collection yet" : "Last collection: "+collectionTime;
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnReady(){
|
||||
btnSendFeedback.Click += btnSendFeedback_Click;
|
||||
checkDataCollection.CheckedChanged += checkDataCollection_CheckedChanged;
|
||||
labelDataCollectionLink.LinkClicked += labelDataCollectionLink_LinkClicked;
|
||||
btnViewReport.Click += btnViewReport_Click;
|
||||
}
|
||||
|
||||
private void btnSendFeedback_Click(object sender, EventArgs e){
|
||||
BrowserUtils.OpenExternalBrowser("https://github.com/chylex/TweetDuck/issues/new");
|
||||
}
|
||||
|
||||
private void checkDataCollection_CheckedChanged(object sender, EventArgs e){
|
||||
Config.AllowDataCollection = checkDataCollection.Checked;
|
||||
}
|
||||
|
||||
private void labelDataCollectionLink_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e){
|
||||
BrowserUtils.OpenExternalBrowser("https://github.com/chylex/TweetDuck/wiki/Send-anonymous-data");
|
||||
}
|
||||
|
||||
private void btnViewReport_Click(object sender, EventArgs e){
|
||||
using(DialogSettingsAnalytics dialog = new DialogSettingsAnalytics(AnalyticsReportGenerator.Create(analyticsFile, analyticsInfo, plugins))){
|
||||
dialog.ShowDialog();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
149
Core/Other/Settings/TabSettingsGeneral.Designer.cs
generated
149
Core/Other/Settings/TabSettingsGeneral.Designer.cs
generated
@@ -25,29 +25,25 @@
|
||||
private void InitializeComponent() {
|
||||
this.components = new System.ComponentModel.Container();
|
||||
this.checkExpandLinks = new System.Windows.Forms.CheckBox();
|
||||
this.comboBoxTrayType = new System.Windows.Forms.ComboBox();
|
||||
this.toolTip = new System.Windows.Forms.ToolTip(this.components);
|
||||
this.checkTrayHighlight = new System.Windows.Forms.CheckBox();
|
||||
this.checkSpellCheck = new System.Windows.Forms.CheckBox();
|
||||
this.checkUpdateNotifications = new System.Windows.Forms.CheckBox();
|
||||
this.btnCheckUpdates = new System.Windows.Forms.Button();
|
||||
this.labelZoomValue = new System.Windows.Forms.Label();
|
||||
this.checkSwitchAccountSelectors = new System.Windows.Forms.CheckBox();
|
||||
this.labelTrayIcon = new System.Windows.Forms.Label();
|
||||
this.checkBestImageQuality = new System.Windows.Forms.CheckBox();
|
||||
this.checkOpenSearchInFirstColumn = new System.Windows.Forms.CheckBox();
|
||||
this.trackBarZoom = new System.Windows.Forms.TrackBar();
|
||||
this.labelZoom = new System.Windows.Forms.Label();
|
||||
this.zoomUpdateTimer = new System.Windows.Forms.Timer(this.components);
|
||||
this.labelUI = new System.Windows.Forms.Label();
|
||||
this.panelUI = new System.Windows.Forms.Panel();
|
||||
this.labelTray = new System.Windows.Forms.Label();
|
||||
this.panelUpdates = new System.Windows.Forms.Panel();
|
||||
this.panelTray = new System.Windows.Forms.Panel();
|
||||
this.labelUpdates = new System.Windows.Forms.Label();
|
||||
this.checkBestImageQuality = new System.Windows.Forms.CheckBox();
|
||||
this.checkAnimatedAvatars = new System.Windows.Forms.CheckBox();
|
||||
((System.ComponentModel.ISupportInitialize)(this.trackBarZoom)).BeginInit();
|
||||
this.panelUI.SuspendLayout();
|
||||
this.panelUpdates.SuspendLayout();
|
||||
this.panelTray.SuspendLayout();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// checkExpandLinks
|
||||
@@ -62,37 +58,14 @@
|
||||
this.toolTip.SetToolTip(this.checkExpandLinks, "Expands links inside the tweets. If disabled,\r\nthe full links show up in a tooltip instead.");
|
||||
this.checkExpandLinks.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// comboBoxTrayType
|
||||
//
|
||||
this.comboBoxTrayType.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
|
||||
this.comboBoxTrayType.FormattingEnabled = true;
|
||||
this.comboBoxTrayType.Location = new System.Drawing.Point(5, 5);
|
||||
this.comboBoxTrayType.Margin = new System.Windows.Forms.Padding(5, 5, 3, 3);
|
||||
this.comboBoxTrayType.Name = "comboBoxTrayType";
|
||||
this.comboBoxTrayType.Size = new System.Drawing.Size(144, 21);
|
||||
this.comboBoxTrayType.TabIndex = 0;
|
||||
this.toolTip.SetToolTip(this.comboBoxTrayType, "Changes behavior of the Tray icon.\r\nRight-click the icon for an action menu.");
|
||||
//
|
||||
// checkTrayHighlight
|
||||
//
|
||||
this.checkTrayHighlight.AutoSize = true;
|
||||
this.checkTrayHighlight.Location = new System.Drawing.Point(6, 56);
|
||||
this.checkTrayHighlight.Margin = new System.Windows.Forms.Padding(6, 5, 3, 3);
|
||||
this.checkTrayHighlight.Name = "checkTrayHighlight";
|
||||
this.checkTrayHighlight.Size = new System.Drawing.Size(103, 17);
|
||||
this.checkTrayHighlight.TabIndex = 2;
|
||||
this.checkTrayHighlight.Text = "Enable Highlight";
|
||||
this.toolTip.SetToolTip(this.checkTrayHighlight, "Highlights the tray icon if there are new tweets.\r\nOnly works for columns with popup or audio notifications.\r\nThe icon resets when the main window is restored.");
|
||||
this.checkTrayHighlight.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// checkSpellCheck
|
||||
//
|
||||
this.checkSpellCheck.AutoSize = true;
|
||||
this.checkSpellCheck.Location = new System.Drawing.Point(6, 74);
|
||||
this.checkSpellCheck.Location = new System.Drawing.Point(6, 120);
|
||||
this.checkSpellCheck.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
|
||||
this.checkSpellCheck.Name = "checkSpellCheck";
|
||||
this.checkSpellCheck.Size = new System.Drawing.Size(119, 17);
|
||||
this.checkSpellCheck.TabIndex = 3;
|
||||
this.checkSpellCheck.TabIndex = 5;
|
||||
this.checkSpellCheck.Text = "Enable Spell Check";
|
||||
this.toolTip.SetToolTip(this.checkSpellCheck, "Underlines words that are spelled incorrectly.");
|
||||
this.checkSpellCheck.UseVisualStyleBackColor = true;
|
||||
@@ -123,11 +96,11 @@
|
||||
// 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, 169);
|
||||
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);
|
||||
this.labelZoomValue.TabIndex = 6;
|
||||
this.labelZoomValue.TabIndex = 8;
|
||||
this.labelZoomValue.Text = "100%";
|
||||
this.labelZoomValue.TextAlign = System.Drawing.ContentAlignment.TopRight;
|
||||
this.toolTip.SetToolTip(this.labelZoomValue, "Changes the zoom level.\r\nAlso affects notifications and screenshots.");
|
||||
@@ -144,39 +117,53 @@
|
||||
this.toolTip.SetToolTip(this.checkSwitchAccountSelectors, "When (re)tweeting, click to select a single account or hold Shift to\r\nselect multiple accounts, instead of TweetDeck\'s default behavior.");
|
||||
this.checkSwitchAccountSelectors.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// labelTrayIcon
|
||||
// checkBestImageQuality
|
||||
//
|
||||
this.labelTrayIcon.AutoSize = true;
|
||||
this.labelTrayIcon.Location = new System.Drawing.Point(3, 38);
|
||||
this.labelTrayIcon.Margin = new System.Windows.Forms.Padding(3, 9, 3, 0);
|
||||
this.labelTrayIcon.Name = "labelTrayIcon";
|
||||
this.labelTrayIcon.Size = new System.Drawing.Size(52, 13);
|
||||
this.labelTrayIcon.TabIndex = 1;
|
||||
this.labelTrayIcon.Text = "Tray Icon";
|
||||
this.checkBestImageQuality.AutoSize = true;
|
||||
this.checkBestImageQuality.Location = new System.Drawing.Point(6, 74);
|
||||
this.checkBestImageQuality.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
|
||||
this.checkBestImageQuality.Name = "checkBestImageQuality";
|
||||
this.checkBestImageQuality.Size = new System.Drawing.Size(114, 17);
|
||||
this.checkBestImageQuality.TabIndex = 3;
|
||||
this.checkBestImageQuality.Text = "Best Image Quality";
|
||||
this.toolTip.SetToolTip(this.checkBestImageQuality, "When right-clicking a tweet image, the context menu options\r\nwill use links to the original image size (:orig in the URL).");
|
||||
this.checkBestImageQuality.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// checkOpenSearchInFirstColumn
|
||||
//
|
||||
this.checkOpenSearchInFirstColumn.AutoSize = true;
|
||||
this.checkOpenSearchInFirstColumn.Location = new System.Drawing.Point(6, 51);
|
||||
this.checkOpenSearchInFirstColumn.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
|
||||
this.checkOpenSearchInFirstColumn.Name = "checkOpenSearchInFirstColumn";
|
||||
this.checkOpenSearchInFirstColumn.Size = new System.Drawing.Size(219, 17);
|
||||
this.checkOpenSearchInFirstColumn.TabIndex = 2;
|
||||
this.checkOpenSearchInFirstColumn.Text = "Add Search Columns Before First Column";
|
||||
this.toolTip.SetToolTip(this.checkOpenSearchInFirstColumn, "By default, TweetDeck adds Search columns at the end.\r\nThis option makes them appear before the first column instead.");
|
||||
this.checkOpenSearchInFirstColumn.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// trackBarZoom
|
||||
//
|
||||
this.trackBarZoom.AutoSize = false;
|
||||
this.trackBarZoom.BackColor = System.Drawing.SystemColors.Control;
|
||||
this.trackBarZoom.LargeChange = 25;
|
||||
this.trackBarZoom.Location = new System.Drawing.Point(3, 122);
|
||||
this.trackBarZoom.Location = new System.Drawing.Point(3, 168);
|
||||
this.trackBarZoom.Maximum = 200;
|
||||
this.trackBarZoom.Minimum = 50;
|
||||
this.trackBarZoom.Name = "trackBarZoom";
|
||||
this.trackBarZoom.Size = new System.Drawing.Size(148, 30);
|
||||
this.trackBarZoom.SmallChange = 5;
|
||||
this.trackBarZoom.TabIndex = 5;
|
||||
this.trackBarZoom.TabIndex = 7;
|
||||
this.trackBarZoom.TickFrequency = 25;
|
||||
this.trackBarZoom.Value = 100;
|
||||
//
|
||||
// labelZoom
|
||||
//
|
||||
this.labelZoom.AutoSize = true;
|
||||
this.labelZoom.Location = new System.Drawing.Point(3, 106);
|
||||
this.labelZoom.Location = new System.Drawing.Point(3, 152);
|
||||
this.labelZoom.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
|
||||
this.labelZoom.Name = "labelZoom";
|
||||
this.labelZoom.Size = new System.Drawing.Size(34, 13);
|
||||
this.labelZoom.TabIndex = 4;
|
||||
this.labelZoom.TabIndex = 6;
|
||||
this.labelZoom.Text = "Zoom";
|
||||
//
|
||||
// zoomUpdateTimer
|
||||
@@ -199,94 +186,69 @@
|
||||
//
|
||||
this.panelUI.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.panelUI.Controls.Add(this.checkAnimatedAvatars);
|
||||
this.panelUI.Controls.Add(this.checkOpenSearchInFirstColumn);
|
||||
this.panelUI.Controls.Add(this.checkBestImageQuality);
|
||||
this.panelUI.Controls.Add(this.checkExpandLinks);
|
||||
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);
|
||||
this.panelUI.Size = new System.Drawing.Size(322, 205);
|
||||
this.panelUI.TabIndex = 1;
|
||||
//
|
||||
// labelTray
|
||||
//
|
||||
this.labelTray.AutoSize = true;
|
||||
this.labelTray.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
|
||||
this.labelTray.Location = new System.Drawing.Point(6, 212);
|
||||
this.labelTray.Margin = new System.Windows.Forms.Padding(0, 21, 0, 0);
|
||||
this.labelTray.Name = "labelTray";
|
||||
this.labelTray.Size = new System.Drawing.Size(96, 20);
|
||||
this.labelTray.TabIndex = 2;
|
||||
this.labelTray.Text = "System Tray";
|
||||
//
|
||||
// panelUpdates
|
||||
//
|
||||
this.panelUpdates.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.panelUpdates.Controls.Add(this.checkUpdateNotifications);
|
||||
this.panelUpdates.Controls.Add(this.btnCheckUpdates);
|
||||
this.panelUpdates.Location = new System.Drawing.Point(9, 358);
|
||||
this.panelUpdates.Location = new System.Drawing.Point(8, 283);
|
||||
this.panelUpdates.Name = "panelUpdates";
|
||||
this.panelUpdates.Size = new System.Drawing.Size(322, 55);
|
||||
this.panelUpdates.TabIndex = 5;
|
||||
//
|
||||
// panelTray
|
||||
//
|
||||
this.panelTray.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.panelTray.Controls.Add(this.checkTrayHighlight);
|
||||
this.panelTray.Controls.Add(this.comboBoxTrayType);
|
||||
this.panelTray.Controls.Add(this.labelTrayIcon);
|
||||
this.panelTray.Location = new System.Drawing.Point(9, 235);
|
||||
this.panelTray.Name = "panelTray";
|
||||
this.panelTray.Size = new System.Drawing.Size(322, 76);
|
||||
this.panelTray.TabIndex = 3;
|
||||
this.panelUpdates.TabIndex = 3;
|
||||
//
|
||||
// labelUpdates
|
||||
//
|
||||
this.labelUpdates.AutoSize = true;
|
||||
this.labelUpdates.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
|
||||
this.labelUpdates.Location = new System.Drawing.Point(6, 335);
|
||||
this.labelUpdates.Location = new System.Drawing.Point(5, 260);
|
||||
this.labelUpdates.Margin = new System.Windows.Forms.Padding(0, 21, 0, 0);
|
||||
this.labelUpdates.Name = "labelUpdates";
|
||||
this.labelUpdates.Size = new System.Drawing.Size(70, 20);
|
||||
this.labelUpdates.TabIndex = 4;
|
||||
this.labelUpdates.TabIndex = 2;
|
||||
this.labelUpdates.Text = "Updates";
|
||||
//
|
||||
// checkBestImageQuality
|
||||
// checkAnimatedAvatars
|
||||
//
|
||||
this.checkBestImageQuality.AutoSize = true;
|
||||
this.checkBestImageQuality.Location = new System.Drawing.Point(6, 51);
|
||||
this.checkBestImageQuality.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
|
||||
this.checkBestImageQuality.Name = "checkBestImageQuality";
|
||||
this.checkBestImageQuality.Size = new System.Drawing.Size(114, 17);
|
||||
this.checkBestImageQuality.TabIndex = 2;
|
||||
this.checkBestImageQuality.Text = "Best Image Quality";
|
||||
this.toolTip.SetToolTip(this.checkBestImageQuality, "When right-clicking a tweet image, the context menu options\r\nwill use links to the original image size (:orig in the URL).");
|
||||
this.checkBestImageQuality.UseVisualStyleBackColor = true;
|
||||
this.checkAnimatedAvatars.AutoSize = true;
|
||||
this.checkAnimatedAvatars.Location = new System.Drawing.Point(6, 97);
|
||||
this.checkAnimatedAvatars.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
|
||||
this.checkAnimatedAvatars.Name = "checkAnimatedAvatars";
|
||||
this.checkAnimatedAvatars.Size = new System.Drawing.Size(145, 17);
|
||||
this.checkAnimatedAvatars.TabIndex = 4;
|
||||
this.checkAnimatedAvatars.Text = "Enable Animated Avatars";
|
||||
this.toolTip.SetToolTip(this.checkAnimatedAvatars, "Some old Twitter avatars could be uploaded as animated GIFs.");
|
||||
this.checkAnimatedAvatars.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// TabSettingsGeneral
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.Controls.Add(this.labelUpdates);
|
||||
this.Controls.Add(this.panelTray);
|
||||
this.Controls.Add(this.panelUpdates);
|
||||
this.Controls.Add(this.labelTray);
|
||||
this.Controls.Add(this.panelUI);
|
||||
this.Controls.Add(this.labelUI);
|
||||
this.Name = "TabSettingsGeneral";
|
||||
this.Size = new System.Drawing.Size(340, 422);
|
||||
this.Size = new System.Drawing.Size(340, 348);
|
||||
((System.ComponentModel.ISupportInitialize)(this.trackBarZoom)).EndInit();
|
||||
this.panelUI.ResumeLayout(false);
|
||||
this.panelUI.PerformLayout();
|
||||
this.panelUpdates.ResumeLayout(false);
|
||||
this.panelUpdates.PerformLayout();
|
||||
this.panelTray.ResumeLayout(false);
|
||||
this.panelTray.PerformLayout();
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
|
||||
@@ -295,10 +257,7 @@
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.CheckBox checkExpandLinks;
|
||||
private System.Windows.Forms.ComboBox comboBoxTrayType;
|
||||
private System.Windows.Forms.ToolTip toolTip;
|
||||
private System.Windows.Forms.Label labelTrayIcon;
|
||||
private System.Windows.Forms.CheckBox checkTrayHighlight;
|
||||
private System.Windows.Forms.CheckBox checkSpellCheck;
|
||||
private System.Windows.Forms.CheckBox checkUpdateNotifications;
|
||||
private System.Windows.Forms.Button btnCheckUpdates;
|
||||
@@ -309,10 +268,10 @@
|
||||
private System.Windows.Forms.CheckBox checkSwitchAccountSelectors;
|
||||
private System.Windows.Forms.Label labelUI;
|
||||
private System.Windows.Forms.Panel panelUI;
|
||||
private System.Windows.Forms.Label labelTray;
|
||||
private System.Windows.Forms.Panel panelUpdates;
|
||||
private System.Windows.Forms.Panel panelTray;
|
||||
private System.Windows.Forms.Label labelUpdates;
|
||||
private System.Windows.Forms.CheckBox checkBestImageQuality;
|
||||
private System.Windows.Forms.CheckBox checkOpenSearchInFirstColumn;
|
||||
private System.Windows.Forms.CheckBox checkAnimatedAvatars;
|
||||
}
|
||||
}
|
||||
|
@@ -1,36 +1,33 @@
|
||||
using System;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Handling.General;
|
||||
using TweetDuck.Updates;
|
||||
using TweetDuck.Updates.Events;
|
||||
|
||||
namespace TweetDuck.Core.Other.Settings{
|
||||
partial class TabSettingsGeneral : BaseTabSettings{
|
||||
sealed partial class TabSettingsGeneral : BaseTabSettings{
|
||||
private readonly FormBrowser browser;
|
||||
private readonly UpdateHandler updates;
|
||||
private int updateCheckEventId = -1;
|
||||
|
||||
public TabSettingsGeneral(UpdateHandler updates){
|
||||
public TabSettingsGeneral(FormBrowser browser, UpdateHandler updates){
|
||||
InitializeComponent();
|
||||
|
||||
this.browser = browser;
|
||||
|
||||
this.updates = updates;
|
||||
this.updates.CheckFinished += updates_CheckFinished;
|
||||
Disposed += (sender, args) => this.updates.CheckFinished -= updates_CheckFinished;
|
||||
|
||||
comboBoxTrayType.Items.Add("Disabled");
|
||||
comboBoxTrayType.Items.Add("Display Icon Only");
|
||||
comboBoxTrayType.Items.Add("Minimize to Tray");
|
||||
comboBoxTrayType.Items.Add("Close to Tray");
|
||||
comboBoxTrayType.Items.Add("Combined");
|
||||
comboBoxTrayType.SelectedIndex = Math.Min(Math.Max((int)Config.TrayBehavior, 0), comboBoxTrayType.Items.Count-1);
|
||||
|
||||
toolTip.SetToolTip(trackBarZoom, toolTip.GetToolTip(labelZoomValue));
|
||||
trackBarZoom.SetValueSafe(Config.ZoomLevel);
|
||||
labelZoomValue.Text = trackBarZoom.Value+"%";
|
||||
|
||||
checkExpandLinks.Checked = Config.ExpandLinksOnHover;
|
||||
checkSwitchAccountSelectors.Checked = Config.SwitchAccountSelectors;
|
||||
checkOpenSearchInFirstColumn.Checked = Config.OpenSearchInFirstColumn;
|
||||
checkBestImageQuality.Checked = Config.BestImageQuality;
|
||||
checkAnimatedAvatars.Checked = Config.EnableAnimatedImages;
|
||||
checkSpellCheck.Checked = Config.EnableSpellCheck;
|
||||
checkTrayHighlight.Checked = Config.EnableTrayHighlight;
|
||||
|
||||
checkUpdateNotifications.Checked = Config.EnableUpdateCheck;
|
||||
}
|
||||
@@ -38,13 +35,12 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
public override void OnReady(){
|
||||
checkExpandLinks.CheckedChanged += checkExpandLinks_CheckedChanged;
|
||||
checkSwitchAccountSelectors.CheckedChanged += checkSwitchAccountSelectors_CheckedChanged;
|
||||
checkOpenSearchInFirstColumn.CheckedChanged += checkOpenSearchInFirstColumn_CheckedChanged;
|
||||
checkBestImageQuality.CheckedChanged += checkBestImageQuality_CheckedChanged;
|
||||
checkAnimatedAvatars.CheckedChanged += checkAnimatedAvatars_CheckedChanged;
|
||||
checkSpellCheck.CheckedChanged += checkSpellCheck_CheckedChanged;
|
||||
trackBarZoom.ValueChanged += trackBarZoom_ValueChanged;
|
||||
|
||||
comboBoxTrayType.SelectedIndexChanged += comboBoxTrayType_SelectedIndexChanged;
|
||||
checkTrayHighlight.CheckedChanged += checkTrayHighlight_CheckedChanged;
|
||||
|
||||
checkUpdateNotifications.CheckedChanged += checkUpdateNotifications_CheckedChanged;
|
||||
btnCheckUpdates.Click += btnCheckUpdates_Click;
|
||||
}
|
||||
@@ -61,13 +57,22 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
Config.SwitchAccountSelectors = checkSwitchAccountSelectors.Checked;
|
||||
}
|
||||
|
||||
private void checkOpenSearchInFirstColumn_CheckedChanged(object sender, EventArgs e){
|
||||
Config.OpenSearchInFirstColumn = checkOpenSearchInFirstColumn.Checked;
|
||||
}
|
||||
|
||||
private void checkBestImageQuality_CheckedChanged(object sender, EventArgs e){
|
||||
Config.BestImageQuality = checkBestImageQuality.Checked;
|
||||
}
|
||||
|
||||
private void checkAnimatedAvatars_CheckedChanged(object sender, EventArgs e){
|
||||
Config.EnableAnimatedImages = checkAnimatedAvatars.Checked;
|
||||
BrowserProcessHandler.UpdatePrefs().ContinueWith(task => browser.ReloadColumns());
|
||||
}
|
||||
|
||||
private void checkSpellCheck_CheckedChanged(object sender, EventArgs e){
|
||||
Config.EnableSpellCheck = checkSpellCheck.Checked;
|
||||
PromptRestart();
|
||||
BrowserProcessHandler.UpdatePrefs();
|
||||
}
|
||||
|
||||
private void trackBarZoom_ValueChanged(object sender, EventArgs e){
|
||||
@@ -78,36 +83,23 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
}
|
||||
}
|
||||
|
||||
private void comboBoxTrayType_SelectedIndexChanged(object sender, EventArgs e){
|
||||
Config.TrayBehavior = (TrayIcon.Behavior)comboBoxTrayType.SelectedIndex;
|
||||
}
|
||||
|
||||
private void checkTrayHighlight_CheckedChanged(object sender, EventArgs e){
|
||||
Config.EnableTrayHighlight = checkTrayHighlight.Checked;
|
||||
}
|
||||
|
||||
private void checkUpdateNotifications_CheckedChanged(object sender, EventArgs e){
|
||||
Config.EnableUpdateCheck = checkUpdateNotifications.Checked;
|
||||
}
|
||||
|
||||
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);
|
||||
|
@@ -2,21 +2,22 @@
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Notification;
|
||||
using TweetDuck.Core.Notification.Example;
|
||||
|
||||
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;
|
||||
private readonly FormNotificationExample notification;
|
||||
|
||||
public TabSettingsNotifications(FormNotificationMain notification){
|
||||
public TabSettingsNotifications(FormNotificationExample notification){
|
||||
InitializeComponent();
|
||||
|
||||
this.notification = notification;
|
||||
|
||||
this.notification.Initialized += (sender, args) => {
|
||||
this.InvokeAsyncSafe(() => {
|
||||
this.notification.ShowNotificationForSettings(true);
|
||||
this.notification.ShowExampleNotification(true);
|
||||
this.notification.Move += notification_Move;
|
||||
this.notification.ResizeEnd += notification_ResizeEnd;
|
||||
});
|
||||
@@ -73,9 +74,6 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
trackBarEdgeDistance.SetValueSafe(Config.NotificationEdgeDistance);
|
||||
labelEdgeDistanceValue.Text = trackBarEdgeDistance.Value+" px";
|
||||
|
||||
this.notification.CanMoveWindow = () => radioLocCustom.Checked;
|
||||
this.notification.CanResizeWindow = radioSizeCustom.Checked;
|
||||
|
||||
Disposed += (sender, args) => this.notification.Dispose();
|
||||
}
|
||||
|
||||
@@ -110,10 +108,10 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
|
||||
private void TabSettingsNotifications_ParentChanged(object sender, EventArgs e){
|
||||
if (Parent == null){
|
||||
notification.HideNotification(false);
|
||||
notification.HideNotification();
|
||||
}
|
||||
else{
|
||||
notification.ShowNotificationForSettings(true);
|
||||
notification.ShowExampleNotification(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,7 +129,7 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
private void notification_ResizeEnd(object sender, EventArgs e){
|
||||
if (radioSizeCustom.Checked){
|
||||
Config.CustomNotificationSize = notification.BrowserSize;
|
||||
notification.ShowNotificationForSettings(false);
|
||||
notification.ShowExampleNotification(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,7 +140,7 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
else if (radioLocBR.Checked)Config.NotificationPosition = TweetNotification.Position.BottomRight;
|
||||
|
||||
comboBoxDisplay.Enabled = trackBarEdgeDistance.Enabled = true;
|
||||
notification.ShowNotificationForSettings(false);
|
||||
notification.ShowExampleNotification(false);
|
||||
}
|
||||
|
||||
private void radioLocCustom_Click(object sender, EventArgs e){
|
||||
@@ -153,7 +151,7 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
Config.NotificationPosition = TweetNotification.Position.Custom;
|
||||
|
||||
comboBoxDisplay.Enabled = trackBarEdgeDistance.Enabled = false;
|
||||
notification.ShowNotificationForSettings(false);
|
||||
notification.ShowExampleNotification(false);
|
||||
|
||||
if (notification.IsFullyOutsideView() && FormMessage.Question("Notification is outside view", "The notification seems to be outside of view, would you like to reset its position?", FormMessage.Yes, FormMessage.No)){
|
||||
Config.NotificationPosition = TweetNotification.Position.TopRight;
|
||||
@@ -167,10 +165,11 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
}
|
||||
|
||||
private void radioSize_CheckedChanged(object sender, EventArgs e){
|
||||
if (radioSizeAuto.Checked)Config.NotificationSize = TweetNotification.Size.Auto;
|
||||
if (radioSizeAuto.Checked){
|
||||
Config.NotificationSize = TweetNotification.Size.Auto;
|
||||
}
|
||||
|
||||
notification.ShowNotificationForSettings(false);
|
||||
notification.CanResizeWindow = false; // must be after ShowNotificationForSettings
|
||||
notification.ShowExampleNotification(false);
|
||||
}
|
||||
|
||||
private void radioSizeCustom_Click(object sender, EventArgs e){
|
||||
@@ -179,9 +178,7 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
}
|
||||
|
||||
Config.NotificationSize = TweetNotification.Size.Custom;
|
||||
|
||||
notification.CanResizeWindow = true;
|
||||
notification.ShowNotificationForSettings(false);
|
||||
notification.ShowExampleNotification(false);
|
||||
}
|
||||
|
||||
private void trackBarDuration_ValueChanged(object sender, EventArgs e){
|
||||
@@ -206,18 +203,18 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
|
||||
private void checkColumnName_CheckedChanged(object sender, EventArgs e){
|
||||
Config.DisplayNotificationColumn = checkColumnName.Checked;
|
||||
notification.ShowNotificationForSettings(false);
|
||||
notification.ShowExampleNotification(false);
|
||||
}
|
||||
|
||||
private void checkNotificationTimer_CheckedChanged(object sender, EventArgs e){
|
||||
Config.DisplayNotificationTimer = checkNotificationTimer.Checked;
|
||||
checkTimerCountDown.Enabled = checkNotificationTimer.Checked;
|
||||
notification.ShowNotificationForSettings(true);
|
||||
notification.ShowExampleNotification(true);
|
||||
}
|
||||
|
||||
private void checkTimerCountDown_CheckedChanged(object sender, EventArgs e){
|
||||
Config.NotificationTimerCountDown = checkTimerCountDown.Checked;
|
||||
notification.ShowNotificationForSettings(true);
|
||||
notification.ShowExampleNotification(true);
|
||||
}
|
||||
|
||||
private void checkMediaPreviews_CheckedChanged(object sender, EventArgs e){
|
||||
@@ -245,17 +242,17 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
|
||||
private void comboBoxDisplay_SelectedValueChanged(object sender, EventArgs e){
|
||||
Config.NotificationDisplay = comboBoxDisplay.SelectedIndex;
|
||||
notification.ShowNotificationForSettings(false);
|
||||
notification.ShowExampleNotification(false);
|
||||
}
|
||||
|
||||
private void trackBarEdgeDistance_ValueChanged(object sender, EventArgs e){
|
||||
labelEdgeDistanceValue.Text = trackBarEdgeDistance.Value+" px";
|
||||
Config.NotificationEdgeDistance = trackBarEdgeDistance.Value;
|
||||
notification.ShowNotificationForSettings(false);
|
||||
notification.ShowExampleNotification(false);
|
||||
}
|
||||
|
||||
private void durationUpdateTimer_Tick(object sender, EventArgs e){
|
||||
notification.ShowNotificationForSettings(true);
|
||||
notification.ShowExampleNotification(true);
|
||||
durationUpdateTimer.Stop();
|
||||
}
|
||||
}
|
||||
|
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();
|
||||
}
|
||||
@@ -34,9 +42,10 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
|
||||
private void tbCustomSound_TextChanged(object sender, EventArgs e){
|
||||
bool isEmpty = string.IsNullOrEmpty(tbCustomSound.Text);
|
||||
tbCustomSound.ForeColor = isEmpty || File.Exists(tbCustomSound.Text) ? SystemColors.WindowText : Color.Maroon;
|
||||
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+"%";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
116
Core/Other/Settings/TabSettingsTray.Designer.cs
generated
Normal file
116
Core/Other/Settings/TabSettingsTray.Designer.cs
generated
Normal file
@@ -0,0 +1,116 @@
|
||||
namespace TweetDuck.Core.Other.Settings {
|
||||
partial class TabSettingsTray {
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing) {
|
||||
if (disposing && (components != null)) {
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Component Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent() {
|
||||
this.components = new System.ComponentModel.Container();
|
||||
this.panelTray = new System.Windows.Forms.Panel();
|
||||
this.checkTrayHighlight = new System.Windows.Forms.CheckBox();
|
||||
this.comboBoxTrayType = new System.Windows.Forms.ComboBox();
|
||||
this.labelTrayIcon = new System.Windows.Forms.Label();
|
||||
this.labelTray = new System.Windows.Forms.Label();
|
||||
this.toolTip = new System.Windows.Forms.ToolTip(this.components);
|
||||
this.panelTray.SuspendLayout();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// panelTray
|
||||
//
|
||||
this.panelTray.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.panelTray.Controls.Add(this.checkTrayHighlight);
|
||||
this.panelTray.Controls.Add(this.comboBoxTrayType);
|
||||
this.panelTray.Controls.Add(this.labelTrayIcon);
|
||||
this.panelTray.Location = new System.Drawing.Point(9, 31);
|
||||
this.panelTray.Name = "panelTray";
|
||||
this.panelTray.Size = new System.Drawing.Size(322, 76);
|
||||
this.panelTray.TabIndex = 1;
|
||||
//
|
||||
// checkTrayHighlight
|
||||
//
|
||||
this.checkTrayHighlight.AutoSize = true;
|
||||
this.checkTrayHighlight.Location = new System.Drawing.Point(6, 56);
|
||||
this.checkTrayHighlight.Margin = new System.Windows.Forms.Padding(6, 5, 3, 3);
|
||||
this.checkTrayHighlight.Name = "checkTrayHighlight";
|
||||
this.checkTrayHighlight.Size = new System.Drawing.Size(103, 17);
|
||||
this.checkTrayHighlight.TabIndex = 2;
|
||||
this.checkTrayHighlight.Text = "Enable Highlight";
|
||||
this.toolTip.SetToolTip(this.checkTrayHighlight, "Highlights the tray icon if there are new tweets.\r\nOnly works for columns with popup or audio notifications.\r\nThe icon resets when the main window is restored.");
|
||||
this.checkTrayHighlight.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// comboBoxTrayType
|
||||
//
|
||||
this.comboBoxTrayType.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
|
||||
this.comboBoxTrayType.FormattingEnabled = true;
|
||||
this.comboBoxTrayType.Location = new System.Drawing.Point(5, 5);
|
||||
this.comboBoxTrayType.Margin = new System.Windows.Forms.Padding(5, 5, 3, 3);
|
||||
this.comboBoxTrayType.Name = "comboBoxTrayType";
|
||||
this.comboBoxTrayType.Size = new System.Drawing.Size(144, 21);
|
||||
this.comboBoxTrayType.TabIndex = 0;
|
||||
this.toolTip.SetToolTip(this.comboBoxTrayType, "Changes behavior of the Tray icon.\r\nRight-click the icon for an action menu.");
|
||||
//
|
||||
// labelTrayIcon
|
||||
//
|
||||
this.labelTrayIcon.AutoSize = true;
|
||||
this.labelTrayIcon.Location = new System.Drawing.Point(3, 38);
|
||||
this.labelTrayIcon.Margin = new System.Windows.Forms.Padding(3, 9, 3, 0);
|
||||
this.labelTrayIcon.Name = "labelTrayIcon";
|
||||
this.labelTrayIcon.Size = new System.Drawing.Size(52, 13);
|
||||
this.labelTrayIcon.TabIndex = 1;
|
||||
this.labelTrayIcon.Text = "Tray Icon";
|
||||
//
|
||||
// labelTray
|
||||
//
|
||||
this.labelTray.AutoSize = true;
|
||||
this.labelTray.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
|
||||
this.labelTray.Location = new System.Drawing.Point(6, 8);
|
||||
this.labelTray.Margin = new System.Windows.Forms.Padding(0, 2, 0, 0);
|
||||
this.labelTray.Name = "labelTray";
|
||||
this.labelTray.Size = new System.Drawing.Size(96, 20);
|
||||
this.labelTray.TabIndex = 0;
|
||||
this.labelTray.Text = "System Tray";
|
||||
//
|
||||
// TabSettingsTray
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.Controls.Add(this.panelTray);
|
||||
this.Controls.Add(this.labelTray);
|
||||
this.Name = "TabSettingsTray";
|
||||
this.Size = new System.Drawing.Size(340, 119);
|
||||
this.panelTray.ResumeLayout(false);
|
||||
this.panelTray.PerformLayout();
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.Panel panelTray;
|
||||
private System.Windows.Forms.CheckBox checkTrayHighlight;
|
||||
private System.Windows.Forms.ComboBox comboBoxTrayType;
|
||||
private System.Windows.Forms.Label labelTrayIcon;
|
||||
private System.Windows.Forms.Label labelTray;
|
||||
private System.Windows.Forms.ToolTip toolTip;
|
||||
}
|
||||
}
|
33
Core/Other/Settings/TabSettingsTray.cs
Normal file
33
Core/Other/Settings/TabSettingsTray.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
|
||||
namespace TweetDuck.Core.Other.Settings{
|
||||
sealed partial class TabSettingsTray : BaseTabSettings{
|
||||
public TabSettingsTray(){
|
||||
InitializeComponent();
|
||||
|
||||
comboBoxTrayType.Items.Add("Disabled");
|
||||
comboBoxTrayType.Items.Add("Display Icon Only");
|
||||
comboBoxTrayType.Items.Add("Minimize to Tray");
|
||||
comboBoxTrayType.Items.Add("Close to Tray");
|
||||
comboBoxTrayType.Items.Add("Combined");
|
||||
comboBoxTrayType.SelectedIndex = Math.Min(Math.Max((int)Config.TrayBehavior, 0), comboBoxTrayType.Items.Count-1);
|
||||
|
||||
checkTrayHighlight.Enabled = Config.TrayBehavior.ShouldDisplayIcon();
|
||||
checkTrayHighlight.Checked = Config.EnableTrayHighlight;
|
||||
}
|
||||
|
||||
public override void OnReady(){
|
||||
comboBoxTrayType.SelectedIndexChanged += comboBoxTrayType_SelectedIndexChanged;
|
||||
checkTrayHighlight.CheckedChanged += checkTrayHighlight_CheckedChanged;
|
||||
}
|
||||
|
||||
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){
|
||||
Config.EnableTrayHighlight = checkTrayHighlight.Checked;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
namespace TweetDuck.Core {
|
||||
namespace TweetDuck.Core.Other {
|
||||
partial class TrayIcon {
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
@@ -2,8 +2,8 @@
|
||||
using System.ComponentModel;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace TweetDuck.Core{
|
||||
partial class TrayIcon : Component{
|
||||
namespace TweetDuck.Core.Other{
|
||||
sealed partial class TrayIcon : Component{
|
||||
public enum Behavior{ // keep order
|
||||
Disabled, DisplayOnly, MinimizeToTray, CloseToTray, Combined
|
||||
}
|
||||
@@ -12,7 +12,9 @@ namespace TweetDuck.Core{
|
||||
public event EventHandler ClickClose;
|
||||
|
||||
public bool Visible{
|
||||
get => notifyIcon.Visible;
|
||||
get{
|
||||
return notifyIcon.Visible;
|
||||
}
|
||||
|
||||
set{
|
||||
if (value){
|
242
Core/TweetDeckBrowser.cs
Normal file
242
Core/TweetDeckBrowser.cs
Normal file
@@ -0,0 +1,242 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
using CefSharp;
|
||||
using CefSharp.WinForms;
|
||||
using TweetDuck.Core.Bridge;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Handling;
|
||||
using TweetDuck.Core.Handling.General;
|
||||
using TweetDuck.Core.Other.Management;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetDuck.Plugins;
|
||||
using TweetDuck.Plugins.Enums;
|
||||
using TweetDuck.Plugins.Events;
|
||||
using TweetDuck.Resources;
|
||||
using TweetDuck.Updates;
|
||||
|
||||
namespace TweetDuck.Core{
|
||||
sealed class TweetDeckBrowser : IDisposable{
|
||||
public bool Ready { get; private set; }
|
||||
|
||||
public bool Enabled{
|
||||
get => browser.Enabled;
|
||||
set => browser.Enabled = value;
|
||||
}
|
||||
|
||||
public bool IsTweetDeckWebsite{
|
||||
get{
|
||||
using(IFrame frame = browser.GetBrowser().MainFrame){
|
||||
return TwitterUtils.IsTweetDeckWebsite(frame);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public event EventHandler PageLoaded;
|
||||
|
||||
private readonly ChromiumWebBrowser browser;
|
||||
private readonly PluginManager plugins;
|
||||
private readonly MemoryUsageTracker memoryUsageTracker;
|
||||
|
||||
public TweetDeckBrowser(FormBrowser owner, PluginManager plugins, TweetDeckBridge bridge){
|
||||
this.browser = new ChromiumWebBrowser(TwitterUtils.TweetDeckURL){
|
||||
DialogHandler = new FileDialogHandler(),
|
||||
DragHandler = new DragHandlerBrowser(),
|
||||
MenuHandler = new ContextMenuBrowser(owner),
|
||||
JsDialogHandler = new JavaScriptDialogHandler(),
|
||||
KeyboardHandler = new KeyboardHandlerBrowser(owner),
|
||||
LifeSpanHandler = new LifeSpanHandler(),
|
||||
RequestHandler = new RequestHandlerBrowser()
|
||||
};
|
||||
|
||||
#if DEBUG
|
||||
this.browser.ConsoleMessage += BrowserUtils.HandleConsoleMessage;
|
||||
#endif
|
||||
|
||||
this.browser.LoadingStateChanged += browser_LoadingStateChanged;
|
||||
this.browser.FrameLoadStart += browser_FrameLoadStart;
|
||||
this.browser.FrameLoadEnd += browser_FrameLoadEnd;
|
||||
this.browser.LoadError += browser_LoadError;
|
||||
|
||||
this.browser.RegisterAsyncJsObject("$TD", bridge);
|
||||
this.browser.RegisterAsyncJsObject("$TDP", plugins.Bridge);
|
||||
|
||||
this.browser.BrowserSettings.BackgroundColor = (uint)TwitterUtils.BackgroundColor.ToArgb();
|
||||
this.browser.Dock = DockStyle.None;
|
||||
this.browser.Location = ControlExtensions.InvisibleLocation;
|
||||
|
||||
owner.Controls.Add(browser);
|
||||
|
||||
this.plugins = plugins;
|
||||
this.plugins.PluginChangedState += plugins_PluginChangedState;
|
||||
|
||||
this.memoryUsageTracker = new MemoryUsageTracker("TDGF_tryRunCleanup");
|
||||
|
||||
Program.UserConfig.MuteToggled += UserConfig_MuteToggled;
|
||||
Program.UserConfig.ZoomLevelChanged += UserConfig_ZoomLevelChanged;
|
||||
}
|
||||
|
||||
// setup and management
|
||||
|
||||
private void OnBrowserReady(){
|
||||
if (!Ready){
|
||||
browser.Location = Point.Empty;
|
||||
browser.Dock = DockStyle.Fill;
|
||||
Ready = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Focus(){
|
||||
browser.Focus();
|
||||
}
|
||||
|
||||
public void Dispose(){
|
||||
plugins.PluginChangedState -= plugins_PluginChangedState;
|
||||
|
||||
Program.UserConfig.MuteToggled -= UserConfig_MuteToggled;
|
||||
Program.UserConfig.ZoomLevelChanged -= UserConfig_ZoomLevelChanged;
|
||||
|
||||
memoryUsageTracker.Dispose();
|
||||
browser.Dispose();
|
||||
}
|
||||
|
||||
// event handlers
|
||||
|
||||
private void browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e){
|
||||
if (!e.IsLoading){
|
||||
foreach(string word in TwitterUtils.DictionaryWords){
|
||||
browser.AddWordToDictionary(word);
|
||||
}
|
||||
|
||||
browser.BeginInvoke(new Action(OnBrowserReady));
|
||||
browser.LoadingStateChanged -= browser_LoadingStateChanged;
|
||||
}
|
||||
}
|
||||
|
||||
private void browser_FrameLoadStart(object sender, FrameLoadStartEventArgs e){
|
||||
if (e.Frame.IsMain){
|
||||
memoryUsageTracker.Stop();
|
||||
|
||||
if (Program.UserConfig.ZoomLevel != 100){
|
||||
BrowserUtils.SetZoomLevel(browser.GetBrowser(), Program.UserConfig.ZoomLevel);
|
||||
}
|
||||
|
||||
if (TwitterUtils.IsTwitterWebsite(e.Frame)){
|
||||
ScriptLoader.ExecuteFile(e.Frame, "twitter.js");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
|
||||
if (e.Frame.IsMain && TwitterUtils.IsTweetDeckWebsite(e.Frame)){
|
||||
e.Frame.ExecuteJavaScriptAsync(TwitterUtils.BackgroundColorFix);
|
||||
|
||||
UpdateProperties();
|
||||
TweetDeckBridge.RestoreSessionData(e.Frame);
|
||||
ScriptLoader.ExecuteFile(e.Frame, "code.js");
|
||||
InjectBrowserCSS();
|
||||
ReinjectCustomCSS(Program.UserConfig.CustomBrowserCSS);
|
||||
plugins.ExecutePlugins(e.Frame, PluginEnvironment.Browser);
|
||||
|
||||
TweetDeckBridge.ResetStaticProperties();
|
||||
|
||||
if (Program.SystemConfig.EnableBrowserGCReload){
|
||||
memoryUsageTracker.Start(browser, Program.SystemConfig.BrowserMemoryThreshold);
|
||||
}
|
||||
|
||||
if (Program.UserConfig.FirstRun){
|
||||
ScriptLoader.ExecuteFile(e.Frame, "introduction.js");
|
||||
}
|
||||
|
||||
PageLoaded?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
private void browser_LoadError(object sender, LoadErrorEventArgs e){
|
||||
if (e.ErrorCode == CefErrorCode.Aborted){
|
||||
return;
|
||||
}
|
||||
|
||||
if (!e.FailedUrl.StartsWith("http://td/", StringComparison.Ordinal)){
|
||||
string errorPage = ScriptLoader.LoadResource("pages/error.html", true);
|
||||
|
||||
if (errorPage != null){
|
||||
browser.LoadHtml(errorPage.Replace("{err}", BrowserUtils.GetErrorName(e.ErrorCode)), "http://td/error");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void plugins_PluginChangedState(object sender, PluginChangedStateEventArgs e){
|
||||
browser.ExecuteScriptAsync("TDPF_setPluginState", e.Plugin, e.IsEnabled);
|
||||
}
|
||||
|
||||
private void UserConfig_MuteToggled(object sender, EventArgs e){
|
||||
UpdateProperties();
|
||||
}
|
||||
|
||||
private void UserConfig_ZoomLevelChanged(object sender, EventArgs e){
|
||||
BrowserUtils.SetZoomLevel(browser.GetBrowser(), Program.UserConfig.ZoomLevel);
|
||||
}
|
||||
|
||||
// external handling
|
||||
|
||||
public UpdateHandler CreateUpdateHandler(UpdaterSettings settings){
|
||||
return new UpdateHandler(browser, settings);
|
||||
}
|
||||
|
||||
public void RefreshMemoryTracker(){
|
||||
if (Program.SystemConfig.EnableBrowserGCReload){
|
||||
memoryUsageTracker.Start(browser, Program.SystemConfig.BrowserMemoryThreshold);
|
||||
}
|
||||
else{
|
||||
memoryUsageTracker.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
public void HideVideoOverlay(bool focus){
|
||||
if (focus){
|
||||
browser.GetBrowser().GetHost().SendFocusEvent(true);
|
||||
}
|
||||
|
||||
browser.ExecuteScriptAsync("$('#td-video-player-overlay').remove()");
|
||||
}
|
||||
|
||||
// javascript calls
|
||||
|
||||
public void ReloadToTweetDeck(){
|
||||
browser.ExecuteScriptAsync($"if(window.TDGF_reload)window.TDGF_reload();else window.location.href='{TwitterUtils.TweetDeckURL}'");
|
||||
}
|
||||
|
||||
public void UpdateProperties(){
|
||||
browser.ExecuteScriptAsync(PropertyBridge.GenerateScript(PropertyBridge.Environment.Browser));
|
||||
}
|
||||
|
||||
public void InjectBrowserCSS(){
|
||||
browser.ExecuteScriptAsync("TDGF_injectBrowserCSS", ScriptLoader.LoadResource("styles/browser.css").TrimEnd());
|
||||
}
|
||||
|
||||
public void ReinjectCustomCSS(string css){
|
||||
browser.ExecuteScriptAsync("TDGF_reinjectCustomCSS", css?.Replace(Environment.NewLine, " ") ?? string.Empty);
|
||||
}
|
||||
|
||||
public void OnMouseClickExtra(IntPtr param){
|
||||
browser.ExecuteScriptAsync("TDGF_onMouseClickExtra", (param.ToInt32() >> 16) & 0xFFFF);
|
||||
}
|
||||
|
||||
public void ShowTweetDetail(string columnId, string chirpId, string fallbackUrl){
|
||||
browser.ExecuteScriptAsync("TDGF_showTweetDetail", columnId, chirpId, fallbackUrl);
|
||||
}
|
||||
|
||||
public void TriggerTweetScreenshot(){
|
||||
browser.ExecuteScriptAsync("TDGF_triggerScreenshot()");
|
||||
}
|
||||
|
||||
public void ReloadColumns(){
|
||||
browser.ExecuteScriptAsync("TDGF_reloadColumns()");
|
||||
}
|
||||
|
||||
public void ApplyROT13(){
|
||||
browser.ExecuteScriptAsync("TDGF_applyROT13()");
|
||||
}
|
||||
}
|
||||
}
|
@@ -8,15 +8,10 @@ namespace TweetDuck.Core.Utils{
|
||||
static class BrowserCache{
|
||||
private static bool ClearOnExit { get; set; }
|
||||
|
||||
private static readonly string CacheFolder = Path.Combine(Program.StoragePath, "Cache");
|
||||
public static readonly string CacheFolder = Path.Combine(Program.StoragePath, "Cache");
|
||||
private static IEnumerable<string> CacheFiles => Directory.EnumerateFiles(CacheFolder);
|
||||
|
||||
private static IEnumerable<string> CacheFiles{
|
||||
get{
|
||||
return Directory.EnumerateFiles(CacheFolder);
|
||||
}
|
||||
}
|
||||
|
||||
public static void CalculateCacheSize(Action<long> callbackBytes){
|
||||
public static void CalculateCacheSize(Action<Task<long>> callbackBytes){
|
||||
Task<long> task = new Task<long>(() => {
|
||||
return CacheFiles.Select(file => {
|
||||
try{
|
||||
@@ -27,7 +22,7 @@ namespace TweetDuck.Core.Utils{
|
||||
}).Sum();
|
||||
});
|
||||
|
||||
task.ContinueWith(originalTask => callbackBytes(originalTask.Exception == null ? originalTask.Result : -1L), TaskContinuationOptions.ExecuteSynchronously);
|
||||
task.ContinueWith(callbackBytes);
|
||||
task.Start();
|
||||
}
|
||||
|
||||
|
@@ -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{
|
||||
@@ -30,7 +31,7 @@ namespace TweetDuck.Core.Utils{
|
||||
args["disable-gpu-vsync"] = "1";
|
||||
}
|
||||
|
||||
args["disable-extensions"] = "1";
|
||||
args["disable-pdf-extension"] = "1";
|
||||
args["disable-plugins-discovery"] = "1";
|
||||
args["enable-system-flash"] = "0";
|
||||
|
||||
@@ -42,28 +43,47 @@ 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:
|
||||
WindowsUtils.OpenAssociatedProgram(url);
|
||||
break;
|
||||
|
||||
public static void OpenExternalBrowserUnsafe(string url){
|
||||
using(Process.Start(url)){}
|
||||
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)){
|
||||
WindowsUtils.OpenAssociatedProgram(url);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case UrlCheckResult.Invalid:
|
||||
FormMessage.Warning("Blocked URL", "A potentially malicious URL was blocked from opening:\n"+url, FormMessage.OK);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetFileNameFromUrl(string url){
|
||||
@@ -75,9 +95,14 @@ namespace TweetDuck.Core.Utils{
|
||||
return StringUtils.ConvertPascalCaseToScreamingSnakeCase(Enum.GetName(typeof(CefErrorCode), code) ?? string.Empty);
|
||||
}
|
||||
|
||||
public static WebClient DownloadFileAsync(string url, string target, Action onSuccess, Action<Exception> onFailure){
|
||||
public static WebClient CreateWebClient(){
|
||||
WebClient client = new WebClient{ Proxy = null };
|
||||
client.Headers[HttpRequestHeader.UserAgent] = HeaderUserAgent;
|
||||
return client;
|
||||
}
|
||||
|
||||
public static WebClient DownloadFileAsync(string url, string target, Action onSuccess, Action<Exception> onFailure){
|
||||
WebClient client = CreateWebClient();
|
||||
|
||||
client.DownloadFileCompleted += (sender, args) => {
|
||||
if (args.Cancelled){
|
||||
|
@@ -17,7 +17,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;
|
||||
@@ -53,6 +52,12 @@ namespace TweetDuck.Core.Utils{
|
||||
[DllImport("user32.dll", EntryPoint = "SetWindowPos")]
|
||||
private static extern bool SetWindowPos(int hWnd, int hWndOrder, int x, int y, int width, int height, uint flags);
|
||||
|
||||
[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")]
|
||||
private static extern bool GetLastInputInfo(ref LASTINPUTINFO info);
|
||||
|
||||
@@ -65,16 +70,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);
|
||||
@@ -107,13 +103,18 @@ namespace TweetDuck.Core.Utils{
|
||||
}
|
||||
}
|
||||
|
||||
public static void BroadcastMessage(uint msg, uint wParam, int lParam){
|
||||
PostMessage(HWND_BROADCAST, msg, new UIntPtr(wParam), new IntPtr(lParam));
|
||||
}
|
||||
|
||||
public static int GetMouseHookData(IntPtr ptr){
|
||||
return Marshal.PtrToStructure<MSLLHOOKSTRUCT>(ptr).mouseData >> 16;
|
||||
}
|
||||
|
||||
public static int GetIdleSeconds(){
|
||||
LASTINPUTINFO info = new LASTINPUTINFO();
|
||||
info.cbSize = LASTINPUTINFO.Size;
|
||||
LASTINPUTINFO info = new LASTINPUTINFO{
|
||||
cbSize = LASTINPUTINFO.Size
|
||||
};
|
||||
|
||||
if (!GetLastInputInfo(ref info)){
|
||||
return 0;
|
||||
|
@@ -18,5 +18,16 @@ namespace TweetDuck.Core.Utils{
|
||||
public static string ConvertPascalCaseToScreamingSnakeCase(string str){
|
||||
return Regex.Replace(str, @"(\p{Ll})(\P{Ll})|(\P{Ll})(\P{Ll}\p{Ll})", "$1$3_$2$4").ToUpper();
|
||||
}
|
||||
|
||||
public static int CountOccurrences(string source, string substring){
|
||||
int count = 0, index = 0;
|
||||
|
||||
while((index = source.IndexOf(substring, index)) != -1){
|
||||
index += substring.Length;
|
||||
++count;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -2,6 +2,7 @@
|
||||
using CefSharp;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Core.Other;
|
||||
@@ -13,13 +14,17 @@ 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 = {
|
||||
"tweetdeck", "TweetDeck", "tweetduck", "TweetDuck", "TD"
|
||||
};
|
||||
|
||||
public static readonly string[] ValidImageExtensions = {
|
||||
".jpg", ".jpeg", ".png", ".gif"
|
||||
};
|
||||
|
||||
public enum ImageQuality{
|
||||
Default, Orig
|
||||
}
|
||||
@@ -32,16 +37,16 @@ namespace TweetDuck.Core.Utils{
|
||||
return frame.Url.Contains("//twitter.com/");
|
||||
}
|
||||
|
||||
private static string ExtractImageBaseLink(string url){
|
||||
int dot = url.LastIndexOf('.');
|
||||
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){
|
||||
if (result != url || url.Contains("//pbs.twimg.com/media/")){
|
||||
result += ":orig";
|
||||
}
|
||||
|
||||
@@ -51,24 +56,39 @@ namespace TweetDuck.Core.Utils{
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetImageFileName(string url){
|
||||
return BrowserUtils.GetFileNameFromUrl(ExtractMediaBaseLink(url));
|
||||
}
|
||||
|
||||
public static void DownloadImage(string url, ImageQuality quality){
|
||||
DownloadImages(new string[]{ url }, quality);
|
||||
public static void DownloadImage(string url, string username, ImageQuality quality){
|
||||
DownloadImages(new string[]{ url }, username, quality);
|
||||
}
|
||||
|
||||
public static void DownloadImages(string[] urls, ImageQuality quality){
|
||||
public static void DownloadImages(string[] urls, string username, ImageQuality quality){
|
||||
if (urls.Length == 0){
|
||||
return;
|
||||
}
|
||||
|
||||
string file = BrowserUtils.GetFileNameFromUrl(ExtractImageBaseLink(urls[0]));
|
||||
string firstImageLink = GetMediaLink(urls[0], quality);
|
||||
int qualityIndex = firstImageLink.IndexOf(':', firstImageLink.LastIndexOf('/'));
|
||||
|
||||
string file = GetImageFileName(firstImageLink);
|
||||
string ext = Path.GetExtension(file); // includes dot
|
||||
|
||||
string[] fileNameParts = qualityIndex == -1 ? new string[]{
|
||||
Path.ChangeExtension(file, null)
|
||||
} : new string[]{
|
||||
username,
|
||||
Path.ChangeExtension(file, null),
|
||||
firstImageLink.Substring(qualityIndex+1)
|
||||
};
|
||||
|
||||
using(SaveFileDialog dialog = new SaveFileDialog{
|
||||
AutoUpgradeEnabled = true,
|
||||
OverwritePrompt = urls.Length == 1,
|
||||
Title = "Save image",
|
||||
FileName = file,
|
||||
FileName = $"{string.Join(" ", fileNameParts.Where(part => part.Length > 0))}{ext}",
|
||||
Filter = (urls.Length == 1 ? "Image" : "Images")+(string.IsNullOrEmpty(ext) ? " (unknown)|*.*" : $" (*{ext})|*{ext}")
|
||||
}){
|
||||
if (dialog.ShowDialog() == DialogResult.OK){
|
||||
@@ -77,18 +97,37 @@ namespace TweetDuck.Core.Utils{
|
||||
}
|
||||
|
||||
if (urls.Length == 1){
|
||||
BrowserUtils.DownloadFileAsync(GetImageLink(urls[0], quality), dialog.FileName, null, OnFailure);
|
||||
BrowserUtils.DownloadFileAsync(firstImageLink, dialog.FileName, null, OnFailure);
|
||||
}
|
||||
else{
|
||||
string pathBase = Path.ChangeExtension(dialog.FileName, null);
|
||||
string pathExt = Path.GetExtension(dialog.FileName);
|
||||
|
||||
for(int index = 0; index < urls.Length; index++){
|
||||
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 username){
|
||||
string filename = BrowserUtils.GetFileNameFromUrl(url);
|
||||
string ext = Path.GetExtension(filename);
|
||||
|
||||
using(SaveFileDialog dialog = new SaveFileDialog{
|
||||
AutoUpgradeEnabled = true,
|
||||
OverwritePrompt = true,
|
||||
Title = "Save video",
|
||||
FileName = string.IsNullOrEmpty(username) ? filename : $"{username} {filename}",
|
||||
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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Management;
|
||||
@@ -49,17 +50,22 @@ namespace TweetDuck.Core.Utils{
|
||||
}
|
||||
}
|
||||
|
||||
public static Process StartProcess(string file, string arguments, bool runElevated){
|
||||
ProcessStartInfo processInfo = new ProcessStartInfo{
|
||||
FileName = file,
|
||||
Arguments = arguments
|
||||
};
|
||||
|
||||
if (runElevated){
|
||||
processInfo.Verb = "runas";
|
||||
public static bool OpenAssociatedProgram(string file, string arguments = "", bool runElevated = false){
|
||||
try{
|
||||
using(Process.Start(new ProcessStartInfo{
|
||||
FileName = file,
|
||||
Arguments = arguments,
|
||||
Verb = runElevated ? "runas" : string.Empty,
|
||||
ErrorDialog = true
|
||||
})){
|
||||
return true;
|
||||
}
|
||||
}catch(Win32Exception e) when (e.NativeErrorCode == 0x000004C7){ // operation canceled by the user
|
||||
return false;
|
||||
}catch(Exception e){
|
||||
Program.Reporter.HandleException("Error opening file", e.Message, true, e);
|
||||
return false;
|
||||
}
|
||||
|
||||
return Process.Start(processInfo);
|
||||
}
|
||||
|
||||
public static bool TrySleepUntil(Func<bool> test, int timeoutMillis, int timeStepMillis){
|
||||
@@ -104,7 +110,7 @@ namespace TweetDuck.Core.Utils{
|
||||
}
|
||||
|
||||
public static void ClipboardStripHtmlStyles(){
|
||||
if (!Clipboard.ContainsText(TextDataFormat.Html)){
|
||||
if (!Clipboard.ContainsText(TextDataFormat.Html) || !Clipboard.ContainsText(TextDataFormat.UnicodeText)){
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -115,7 +121,7 @@ namespace TweetDuck.Core.Utils{
|
||||
|
||||
int removed = originalHtml.Length-updatedHtml.Length;
|
||||
updatedHtml = RegexOffsetClipboardHtml.Value.Replace(updatedHtml, match => (int.Parse(match.Value)-removed).ToString().PadLeft(match.Value.Length, '0'));
|
||||
|
||||
|
||||
DataObject obj = new DataObject();
|
||||
obj.SetText(originalText, TextDataFormat.UnicodeText);
|
||||
obj.SetText(updatedHtml, TextDataFormat.Html);
|
||||
|
@@ -91,7 +91,7 @@ namespace TweetDuck.Data{
|
||||
stream.Dispose();
|
||||
}
|
||||
|
||||
public class Entry{
|
||||
public sealed class Entry{
|
||||
public string Identifier { get; }
|
||||
|
||||
public string KeyName{
|
||||
|
@@ -4,6 +4,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Serialization;
|
||||
using TweetDuck.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Data.Serialization{
|
||||
sealed class FileSerializer<T>{
|
||||
@@ -12,8 +13,8 @@ namespace TweetDuck.Data.Serialization{
|
||||
|
||||
private static readonly ITypeConverter BasicSerializerObj = new BasicTypeConverter();
|
||||
|
||||
public delegate bool OnReadUnknownPropertyHandler(T obj, string property, string value);
|
||||
public OnReadUnknownPropertyHandler OnReadUnknownProperty { get; set; }
|
||||
public delegate void HandleUnknownPropertiesHandler(T obj, Dictionary<string, string> data);
|
||||
public HandleUnknownPropertiesHandler HandleUnknownProperties { get; set; }
|
||||
|
||||
private readonly Dictionary<string, PropertyInfo> props;
|
||||
private readonly Dictionary<Type, ITypeConverter> converters;
|
||||
@@ -28,6 +29,8 @@ namespace TweetDuck.Data.Serialization{
|
||||
}
|
||||
|
||||
public void Write(string file, T obj){
|
||||
WindowsUtils.CreateDirectoryForFile(file);
|
||||
|
||||
using(StreamWriter writer = new StreamWriter(new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None))){
|
||||
foreach(KeyValuePair<string, PropertyInfo> prop in props){
|
||||
Type type = prop.Value.PropertyType;
|
||||
@@ -51,9 +54,15 @@ namespace TweetDuck.Data.Serialization{
|
||||
}
|
||||
|
||||
public void Read(string file, T obj){
|
||||
Dictionary<string, string> unknownProperties = new Dictionary<string, string>(4);
|
||||
|
||||
using(StreamReader reader = new StreamReader(new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read))){
|
||||
if (reader.Peek() <= 1){
|
||||
throw new FormatException("Input appears to be a binary file.");
|
||||
switch(reader.Peek()){
|
||||
case -1:
|
||||
throw new FormatException("File is empty.");
|
||||
case 0:
|
||||
case 1:
|
||||
throw new FormatException("Input appears to be a binary file.");
|
||||
}
|
||||
|
||||
foreach(string line in reader.ReadToEnd().Split(new string[]{ NewLineReal }, StringSplitOptions.RemoveEmptyEntries)){
|
||||
@@ -78,14 +87,29 @@ namespace TweetDuck.Data.Serialization{
|
||||
throw new SerializationException($"Invalid file format, cannot convert value: {value} (property: {property})");
|
||||
}
|
||||
}
|
||||
else if (!(OnReadUnknownProperty?.Invoke(obj, property, value) ?? false)){
|
||||
throw new SerializationException($"Invalid file format, unknown property: {property}+");
|
||||
else{
|
||||
unknownProperties[property] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (unknownProperties.Count > 0){
|
||||
HandleUnknownProperties?.Invoke(obj, unknownProperties);
|
||||
|
||||
if (unknownProperties.Count > 0){
|
||||
throw new SerializationException($"Invalid file format, unknown properties: {string.Join(", ", unknownProperties.Keys)}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class BasicTypeConverter : ITypeConverter{
|
||||
public void ReadIfExists(string file, T obj){
|
||||
try{
|
||||
Read(file, obj);
|
||||
}catch(FileNotFoundException){
|
||||
}catch(DirectoryNotFoundException){}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
@@ -62,9 +62,7 @@ namespace TweetDuck.Plugins.Controls{
|
||||
}
|
||||
|
||||
private void btnToggleState_Click(object sender, EventArgs e){
|
||||
bool newState = !pluginManager.Config.IsEnabled(plugin);
|
||||
pluginManager.Config.SetEnabled(plugin, newState);
|
||||
|
||||
pluginManager.Config.ToggleEnabled(plugin);
|
||||
UpdatePluginState();
|
||||
}
|
||||
|
||||
|
@@ -17,7 +17,20 @@ namespace TweetDuck.Plugins.Enums{
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetScriptFile(this PluginEnvironment environment){
|
||||
public static bool IncludesDisabledPlugins(this PluginEnvironment environment){
|
||||
return environment == PluginEnvironment.Browser;
|
||||
}
|
||||
|
||||
public static string GetScriptIdentifier(this PluginEnvironment environment){
|
||||
switch(environment){
|
||||
case PluginEnvironment.None: return "root:plugins";
|
||||
case PluginEnvironment.Browser: return "root:plugins.browser";
|
||||
case PluginEnvironment.Notification: return "root:plugins.notification";
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetPluginScriptFile(this PluginEnvironment environment){
|
||||
switch(environment){
|
||||
case PluginEnvironment.Browser: return "browser.js";
|
||||
case PluginEnvironment.Notification: return "notification.js";
|
||||
@@ -25,7 +38,7 @@ namespace TweetDuck.Plugins.Enums{
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetScriptVariables(this PluginEnvironment environment){
|
||||
public static string GetPluginScriptVariables(this PluginEnvironment environment){
|
||||
switch(environment){
|
||||
case PluginEnvironment.Browser: return "$,$TD,$TDP,TD";
|
||||
case PluginEnvironment.Notification: return "$TD,$TDP";
|
||||
|
@@ -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,9 +7,11 @@ using TweetDuck.Plugins.Enums;
|
||||
|
||||
namespace TweetDuck.Plugins{
|
||||
sealed class Plugin{
|
||||
private const string VersionWildcard = "*";
|
||||
|
||||
public string Identifier { get; }
|
||||
public PluginGroup Group { get; }
|
||||
public PluginEnvironment Environments { get; private set; }
|
||||
public PluginEnvironment Environments { get; }
|
||||
|
||||
public string Name => metadata["NAME"];
|
||||
public string Description => metadata["DESCRIPTION"];
|
||||
@@ -20,9 +22,7 @@ namespace TweetDuck.Plugins{
|
||||
public string ConfigDefault => metadata["CONFIGDEFAULT"];
|
||||
public string RequiredVersion => metadata["REQUIRES"];
|
||||
|
||||
public bool CanRun{
|
||||
get => canRun ?? (canRun = CheckRequiredVersion(RequiredVersion)).Value;
|
||||
}
|
||||
public bool CanRun { get; private set; }
|
||||
|
||||
public bool HasConfig{
|
||||
get => ConfigFile.Length > 0 && GetFullPathIfSafe(PluginFolder.Data, ConfigFile).Length > 0;
|
||||
@@ -50,24 +50,21 @@ namespace TweetDuck.Plugins{
|
||||
{ "WEBSITE", "" },
|
||||
{ "CONFIGFILE", "" },
|
||||
{ "CONFIGDEFAULT", "" },
|
||||
{ "REQUIRES", "*" }
|
||||
{ "REQUIRES", VersionWildcard }
|
||||
};
|
||||
|
||||
private bool? canRun;
|
||||
|
||||
private Plugin(string path, PluginGroup group){
|
||||
string name = Path.GetFileName(path);
|
||||
System.Diagnostics.Debug.Assert(name != null);
|
||||
|
||||
private Plugin(string path, string name, PluginGroup group, PluginEnvironment environments){
|
||||
this.pathRoot = path;
|
||||
this.pathData = Path.Combine(Program.PluginDataPath, group.GetIdentifierPrefix(), name);
|
||||
|
||||
this.Identifier = group.GetIdentifierPrefix()+name;
|
||||
this.Group = group;
|
||||
this.Environments = PluginEnvironment.None;
|
||||
this.Environments = environments;
|
||||
}
|
||||
|
||||
private void OnMetadataLoaded(){
|
||||
CanRun = CheckRequiredVersion(RequiredVersion);
|
||||
|
||||
string configPath = ConfigPath, defaultConfigPath = DefaultConfigPath;
|
||||
|
||||
if (configPath.Length > 0 && defaultConfigPath.Length > 0 && !File.Exists(configPath) && File.Exists(defaultConfigPath)){
|
||||
@@ -77,14 +74,14 @@ namespace TweetDuck.Plugins{
|
||||
Directory.CreateDirectory(dataFolder);
|
||||
File.Copy(defaultConfigPath, configPath, false);
|
||||
}catch(Exception e){
|
||||
Program.Reporter.HandleException("Plugin Loading Error", "Could not generate a configuration file for '"+Identifier+"' plugin.", true, e);
|
||||
throw new IOException("Could not generate a configuration file for '"+Identifier+"' plugin: "+e.Message, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string GetScriptPath(PluginEnvironment environment){
|
||||
if (Environments.HasFlag(environment)){
|
||||
string file = environment.GetScriptFile();
|
||||
string file = environment.GetPluginScriptFile();
|
||||
return file != null ? Path.Combine(pathRoot, file) : string.Empty;
|
||||
}
|
||||
else{
|
||||
@@ -135,93 +132,78 @@ namespace TweetDuck.Plugins{
|
||||
return obj is Plugin plugin && plugin.Identifier.Equals(Identifier);
|
||||
}
|
||||
|
||||
public static Plugin CreateFromFolder(string path, PluginGroup group, out string error){
|
||||
Plugin plugin = new Plugin(path, group);
|
||||
// Static
|
||||
|
||||
private static readonly Version AppVersion = new Version(Program.VersionTag);
|
||||
private static readonly string[] EndTag = { "[END]" };
|
||||
|
||||
if (!LoadMetadata(path, plugin, out error)){
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!LoadEnvironments(path, plugin, out error)){
|
||||
return null;
|
||||
}
|
||||
|
||||
error = string.Empty;
|
||||
public static Plugin CreateFromFolder(string path, PluginGroup group){
|
||||
Plugin plugin = new Plugin(path, Path.GetFileName(path), group, LoadEnvironments(path));
|
||||
LoadMetadata(path, plugin);
|
||||
return plugin;
|
||||
}
|
||||
|
||||
private static bool LoadEnvironments(string path, Plugin plugin, out string error){
|
||||
private static PluginEnvironment LoadEnvironments(string path){
|
||||
PluginEnvironment environments = PluginEnvironment.None;
|
||||
|
||||
foreach(string file in Directory.EnumerateFiles(path, "*.js", SearchOption.TopDirectoryOnly).Select(Path.GetFileName)){
|
||||
plugin.Environments |= PluginEnvironmentExtensions.Values.FirstOrDefault(env => file.Equals(env.GetScriptFile(), StringComparison.Ordinal));
|
||||
environments |= PluginEnvironmentExtensions.Values.FirstOrDefault(env => file.Equals(env.GetPluginScriptFile(), StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
if (plugin.Environments == PluginEnvironment.None){
|
||||
error = "Plugin has no script files.";
|
||||
return false;
|
||||
if (environments == PluginEnvironment.None){
|
||||
throw new ArgumentException("Plugin has no script files");
|
||||
}
|
||||
|
||||
error = string.Empty;
|
||||
return true;
|
||||
return environments;
|
||||
}
|
||||
|
||||
private static readonly string[] endTag = { "[END]" };
|
||||
|
||||
private static bool LoadMetadata(string path, Plugin plugin, out string error){
|
||||
private static void LoadMetadata(string path, Plugin plugin){
|
||||
string metaFile = Path.Combine(path, ".meta");
|
||||
|
||||
if (!File.Exists(metaFile)){
|
||||
error = "Missing .meta file.";
|
||||
return false;
|
||||
throw new ArgumentException("Missing .meta file");
|
||||
}
|
||||
|
||||
string currentTag = null, currentContents = string.Empty;
|
||||
|
||||
string[] lines = File.ReadAllLines(metaFile, Encoding.UTF8);
|
||||
string currentTag = null, currentContents = "";
|
||||
|
||||
foreach(string line in lines.Concat(endTag).Select(line => line.TrimEnd()).Where(line => line.Length > 0)){
|
||||
foreach(string line in File.ReadAllLines(metaFile, Encoding.UTF8).Concat(EndTag).Select(line => line.TrimEnd()).Where(line => line.Length > 0)){
|
||||
if (line[0] == '[' && line[line.Length-1] == ']'){
|
||||
if (currentTag != null){
|
||||
plugin.metadata[currentTag] = currentContents;
|
||||
}
|
||||
|
||||
currentTag = line.Substring(1, line.Length-2).ToUpper();
|
||||
currentContents = "";
|
||||
currentContents = string.Empty;
|
||||
|
||||
if (line.Equals(endTag[0])){
|
||||
if (line.Equals(EndTag[0])){
|
||||
break;
|
||||
}
|
||||
|
||||
if (!plugin.metadata.ContainsKey(currentTag)){
|
||||
error = "Invalid metadata tag: "+currentTag;
|
||||
return false;
|
||||
throw new FormatException("Invalid metadata tag: "+currentTag);
|
||||
}
|
||||
}
|
||||
else if (currentTag != null){
|
||||
currentContents = currentContents.Length == 0 ? line : currentContents+"\r\n"+line;
|
||||
currentContents = currentContents.Length == 0 ? line : currentContents+Environment.NewLine+line;
|
||||
}
|
||||
else{
|
||||
error = "Missing metadata tag before value: "+line;
|
||||
return false;
|
||||
throw new FormatException("Missing metadata tag before value: "+line);
|
||||
}
|
||||
}
|
||||
|
||||
if (plugin.Name.Length == 0){
|
||||
error = "Plugin is missing a name in the .meta file.";
|
||||
return false;
|
||||
throw new FormatException("Plugin is missing a name in the .meta file");
|
||||
}
|
||||
|
||||
if (plugin.RequiredVersion.Length == 0 || !(plugin.RequiredVersion.Equals("*") || System.Version.TryParse(plugin.RequiredVersion, out Version _))){
|
||||
error = "Plugin contains invalid version: "+plugin.RequiredVersion;
|
||||
return false;
|
||||
if (plugin.RequiredVersion.Length == 0 || !(plugin.RequiredVersion == VersionWildcard || System.Version.TryParse(plugin.RequiredVersion, out Version _))){
|
||||
throw new FormatException("Plugin contains invalid version: "+plugin.RequiredVersion);
|
||||
}
|
||||
|
||||
plugin.OnMetadataLoaded();
|
||||
|
||||
error = string.Empty;
|
||||
return true;
|
||||
}
|
||||
|
||||
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,42 @@ 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 void ToggleEnabled(Plugin plugin){
|
||||
SetEnabled(plugin, !IsEnabled(plugin));
|
||||
}
|
||||
|
||||
public bool IsEnabled(Plugin plugin){
|
||||
return !Disabled.Contains(plugin.Identifier);
|
||||
return !disabled.Contains(plugin.Identifier);
|
||||
}
|
||||
|
||||
public void Save(string file){
|
||||
try{
|
||||
using(StreamWriter writer = new StreamWriter(new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None), Encoding.UTF8)){
|
||||
writer.WriteLine("#Disabled");
|
||||
|
||||
foreach(string identifier in disabled){
|
||||
writer.WriteLine(identifier);
|
||||
}
|
||||
}
|
||||
}catch(Exception e){
|
||||
Program.Reporter.HandleException("Plugin Configuration Error", "Could not save the plugin configuration file.", true, e);
|
||||
}
|
||||
}
|
||||
|
||||
public void Load(string file){
|
||||
@@ -32,33 +52,21 @@ 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();
|
||||
disabled.UnionWith(DefaultDisabled);
|
||||
Save(file);
|
||||
}catch(DirectoryNotFoundException){
|
||||
}catch(Exception e){
|
||||
Program.Reporter.HandleException("Plugin Configuration Error", "Could not read the plugin configuration file. If you continue, the list of disabled plugins will be reset to default.", true, e);
|
||||
}
|
||||
}
|
||||
|
||||
public void Save(string file){
|
||||
try{
|
||||
using(StreamWriter writer = new StreamWriter(new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None), Encoding.UTF8)){
|
||||
writer.WriteLine("#Disabled");
|
||||
|
||||
foreach(string disabled in Disabled){
|
||||
writer.WriteLine(disabled);
|
||||
}
|
||||
}
|
||||
}catch(Exception e){
|
||||
Program.Reporter.HandleException("Plugin Configuration Error", "Could not save the plugin configuration file.", true, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -9,11 +9,11 @@ using TweetDuck.Resources;
|
||||
|
||||
namespace TweetDuck.Plugins{
|
||||
sealed class PluginManager{
|
||||
public const string PluginBrowserScriptFile = "plugins.browser.js";
|
||||
public const string PluginNotificationScriptFile = "plugins.notification.js";
|
||||
public const string PluginGlobalScriptFile = "plugins.js";
|
||||
|
||||
private const int InvalidToken = 0;
|
||||
private static readonly Dictionary<PluginEnvironment, string> PluginSetupScripts = new Dictionary<PluginEnvironment, string>(4){
|
||||
{ PluginEnvironment.None, ScriptLoader.LoadResource("plugins.js") },
|
||||
{ PluginEnvironment.Browser, ScriptLoader.LoadResource("plugins.browser.js") },
|
||||
{ PluginEnvironment.Notification, ScriptLoader.LoadResource("plugins.notification.js") }
|
||||
};
|
||||
|
||||
public string PathOfficialPlugins => Path.Combine(rootPath, "official");
|
||||
public string PathCustomPlugins => Path.Combine(rootPath, "user");
|
||||
@@ -33,8 +33,6 @@ namespace TweetDuck.Plugins{
|
||||
private readonly Dictionary<int, Plugin> tokens = new Dictionary<int, Plugin>();
|
||||
private readonly Random rand = new Random();
|
||||
|
||||
private List<string> loadErrors;
|
||||
|
||||
public PluginManager(string rootPath, string configPath){
|
||||
this.rootPath = rootPath;
|
||||
this.configPath = configPath;
|
||||
@@ -43,14 +41,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){
|
||||
@@ -73,7 +64,18 @@ namespace TweetDuck.Plugins{
|
||||
}
|
||||
}
|
||||
|
||||
return InvalidToken;
|
||||
int token, attempts = 1000;
|
||||
|
||||
do{
|
||||
token = rand.Next();
|
||||
}while(tokens.ContainsKey(token) && --attempts >= 0);
|
||||
|
||||
if (attempts < 0){
|
||||
token = -tokens.Count-1;
|
||||
}
|
||||
|
||||
tokens[token] = plugin;
|
||||
return token;
|
||||
}
|
||||
|
||||
public Plugin GetPluginFromToken(int token){
|
||||
@@ -81,23 +83,48 @@ namespace TweetDuck.Plugins{
|
||||
}
|
||||
|
||||
public void Reload(){
|
||||
Config.Load(configPath);
|
||||
|
||||
plugins.Clear();
|
||||
tokens.Clear();
|
||||
|
||||
loadErrors = new List<string>(2);
|
||||
|
||||
foreach(Plugin plugin in LoadPluginsFrom(PathOfficialPlugins, PluginGroup.Official)){
|
||||
plugins.Add(plugin);
|
||||
}
|
||||
List<string> loadErrors = new List<string>(2);
|
||||
|
||||
foreach(Plugin plugin in LoadPluginsFrom(PathCustomPlugins, PluginGroup.Custom)){
|
||||
plugins.Add(plugin);
|
||||
IEnumerable<Plugin> LoadPluginsFrom(string path, PluginGroup group){
|
||||
if (!Directory.Exists(path)){
|
||||
yield break;
|
||||
}
|
||||
|
||||
foreach(string fullDir in Directory.EnumerateDirectories(path, "*", SearchOption.TopDirectoryOnly)){
|
||||
Plugin plugin;
|
||||
|
||||
try{
|
||||
plugin = Plugin.CreateFromFolder(fullDir, group);
|
||||
}catch(Exception e){
|
||||
loadErrors.Add(group.GetIdentifierPrefix()+Path.GetFileName(fullDir)+": "+e.Message);
|
||||
continue;
|
||||
}
|
||||
|
||||
yield return plugin;
|
||||
}
|
||||
}
|
||||
|
||||
plugins.UnionWith(LoadPluginsFrom(PathOfficialPlugins, PluginGroup.Official));
|
||||
plugins.UnionWith(LoadPluginsFrom(PathCustomPlugins, PluginGroup.Custom));
|
||||
|
||||
Reloaded?.Invoke(this, new PluginErrorEventArgs(loadErrors));
|
||||
}
|
||||
|
||||
public void ExecutePlugins(IFrame frame, PluginEnvironment environment, bool includeDisabled){
|
||||
public void ExecutePlugins(IFrame frame, PluginEnvironment environment){
|
||||
if (!HasAnyPlugin(environment)){
|
||||
return;
|
||||
}
|
||||
|
||||
ScriptLoader.ExecuteScript(frame, PluginSetupScripts[environment], environment.GetScriptIdentifier());
|
||||
ScriptLoader.ExecuteScript(frame, PluginSetupScripts[PluginEnvironment.None], PluginEnvironment.None.GetScriptIdentifier());
|
||||
|
||||
bool includeDisabled = environment.IncludesDisabledPlugins();
|
||||
|
||||
if (includeDisabled){
|
||||
ScriptLoader.ExecuteScript(frame, PluginScriptGenerator.GenerateConfig(Config), "gen:pluginconfig");
|
||||
}
|
||||
@@ -106,7 +133,10 @@ namespace TweetDuck.Plugins{
|
||||
|
||||
foreach(Plugin plugin in Plugins){
|
||||
string path = plugin.GetScriptPath(environment);
|
||||
if (string.IsNullOrEmpty(path) || (!includeDisabled && !Config.IsEnabled(plugin)) || !plugin.CanRun)continue;
|
||||
|
||||
if (string.IsNullOrEmpty(path) || (!includeDisabled && !Config.IsEnabled(plugin)) || !plugin.CanRun){
|
||||
continue;
|
||||
}
|
||||
|
||||
string script;
|
||||
|
||||
@@ -116,50 +146,11 @@ namespace TweetDuck.Plugins{
|
||||
failedPlugins.Add(plugin.Identifier+" ("+Path.GetFileName(path)+"): "+e.Message);
|
||||
continue;
|
||||
}
|
||||
|
||||
int token;
|
||||
|
||||
if (tokens.ContainsValue(plugin)){
|
||||
token = GetTokenFromPlugin(plugin);
|
||||
}
|
||||
else{
|
||||
token = GenerateToken();
|
||||
tokens[token] = plugin;
|
||||
}
|
||||
|
||||
ScriptLoader.ExecuteScript(frame, PluginScriptGenerator.GeneratePlugin(plugin.Identifier, script, token, environment), "plugin:"+plugin);
|
||||
|
||||
ScriptLoader.ExecuteScript(frame, PluginScriptGenerator.GeneratePlugin(plugin.Identifier, script, GetTokenFromPlugin(plugin), environment), "plugin:"+plugin);
|
||||
}
|
||||
|
||||
Executed?.Invoke(this, new PluginErrorEventArgs(failedPlugins));
|
||||
}
|
||||
|
||||
private IEnumerable<Plugin> LoadPluginsFrom(string path, PluginGroup group){
|
||||
if (!Directory.Exists(path)){
|
||||
yield break;
|
||||
}
|
||||
|
||||
foreach(string fullDir in Directory.EnumerateDirectories(path, "*", SearchOption.TopDirectoryOnly)){
|
||||
Plugin plugin = Plugin.CreateFromFolder(fullDir, group, out string error);
|
||||
|
||||
if (plugin == null){
|
||||
loadErrors.Add(group.GetIdentifierPrefix()+Path.GetFileName(fullDir)+": "+error);
|
||||
}
|
||||
else{
|
||||
yield return plugin;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int GenerateToken(){
|
||||
for(int attempt = 0; attempt < 1000; attempt++){
|
||||
int token = rand.Next();
|
||||
|
||||
if (!tokens.ContainsKey(token) && token != InvalidToken){
|
||||
return token;
|
||||
}
|
||||
}
|
||||
|
||||
return -tokens.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -8,7 +8,7 @@ namespace TweetDuck.Plugins{
|
||||
|
||||
public static string GeneratePlugin(string pluginIdentifier, string pluginContents, int pluginToken, PluginEnvironment environment){
|
||||
return PluginGen
|
||||
.Replace("%params", environment.GetScriptVariables())
|
||||
.Replace("%params", environment.GetPluginScriptVariables())
|
||||
.Replace("%id", pluginIdentifier)
|
||||
.Replace("%token", pluginToken.ToString())
|
||||
.Replace("%contents", pluginContents);
|
||||
|
71
Program.cs
71
Program.cs
@@ -1,4 +1,4 @@
|
||||
using CefSharp;
|
||||
using CefSharp;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
@@ -13,8 +13,6 @@ using TweetDuck.Core.Other;
|
||||
using TweetDuck.Core.Other.Settings.Export;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetDuck.Data;
|
||||
using TweetDuck.Plugins;
|
||||
using TweetDuck.Plugins.Events;
|
||||
using TweetDuck.Updates;
|
||||
|
||||
namespace TweetDuck{
|
||||
@@ -22,10 +20,8 @@ namespace TweetDuck{
|
||||
public const string BrandName = "TweetDuck";
|
||||
public const string Website = "https://tweetduck.chylex.com";
|
||||
|
||||
public const string VersionTag = "1.8.4";
|
||||
public const string VersionFull = "1.8.4";
|
||||
public const string VersionTag = "1.11.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;
|
||||
@@ -40,6 +36,7 @@ namespace TweetDuck{
|
||||
public static string UserConfigFilePath => Path.Combine(StoragePath, "TD_UserConfig.cfg");
|
||||
public static string SystemConfigFilePath => Path.Combine(StoragePath, "TD_SystemConfig.cfg");
|
||||
public static string PluginConfigFilePath => Path.Combine(StoragePath, "TD_PluginConfig.cfg");
|
||||
public static string AnalyticsFilePath => Path.Combine(StoragePath, "TD_Analytics.cfg");
|
||||
|
||||
private static string ErrorLogFilePath => Path.Combine(StoragePath, "TD_Log.txt");
|
||||
private static string ConsoleLogFilePath => Path.Combine(StoragePath, "TD_Console.txt");
|
||||
@@ -49,19 +46,21 @@ namespace TweetDuck{
|
||||
|
||||
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,6 +69,7 @@ namespace TweetDuck{
|
||||
private static void Main(){
|
||||
Application.EnableVisualStyles();
|
||||
Application.SetCompatibleTextRenderingDefault(false);
|
||||
Cef.EnableHighDPISupport();
|
||||
|
||||
WindowRestoreMessage = NativeMethods.RegisterWindowMessage("TweetDuckRestore");
|
||||
SubProcessMessage = NativeMethods.RegisterWindowMessage("TweetDuckSubProcess");
|
||||
@@ -114,13 +114,16 @@ namespace TweetDuck{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ReloadConfig();
|
||||
|
||||
UserConfig = UserConfig.Load(UserConfigFilePath);
|
||||
SystemConfig = SystemConfig.Load(SystemConfigFilePath);
|
||||
|
||||
if (Arguments.HasFlag(Arguments.ArgImportCookies)){
|
||||
ExportManager.ImportCookies();
|
||||
}
|
||||
else if (Arguments.HasFlag(Arguments.ArgDeleteCookies)){
|
||||
ExportManager.DeleteCookies();
|
||||
}
|
||||
|
||||
if (Arguments.HasFlag(Arguments.ArgUpdated)){
|
||||
WindowsUtils.TryDeleteFolderWhenAble(InstallerPath, 8000);
|
||||
@@ -143,23 +146,17 @@ namespace TweetDuck{
|
||||
CommandLineArgs.ReadCefArguments(UserConfig.CustomCefArgs).ToDictionary(settings.CefCommandLineArgs);
|
||||
BrowserUtils.SetupCefArgs(settings.CefCommandLineArgs);
|
||||
|
||||
Cef.EnableHighDPISupport();
|
||||
Cef.Initialize(settings, false, new BrowserProcessHandler());
|
||||
|
||||
Application.ApplicationExit += (sender, args) => ExitCleanup();
|
||||
|
||||
PluginManager plugins = new PluginManager(PluginPath, PluginConfigFilePath);
|
||||
plugins.Reloaded += plugins_Reloaded;
|
||||
plugins.Executed += plugins_Executed;
|
||||
plugins.Reload();
|
||||
|
||||
UpdaterSettings updaterSettings = new UpdaterSettings{
|
||||
AllowPreReleases = Arguments.HasFlag(Arguments.ArgDebugUpdates),
|
||||
DismissedUpdate = UserConfig.DismissedUpdate,
|
||||
InstallerDownloadFolder = InstallerPath
|
||||
};
|
||||
|
||||
FormBrowser mainForm = new FormBrowser(plugins, updaterSettings);
|
||||
FormBrowser mainForm = new FormBrowser(updaterSettings);
|
||||
Application.Run(mainForm);
|
||||
|
||||
if (mainForm.UpdateInstallerPath != null){
|
||||
@@ -169,20 +166,12 @@ namespace TweetDuck{
|
||||
string updaterArgs = "/SP- /SILENT /CLOSEAPPLICATIONS /UPDATEPATH=\""+ProgramPath+"\" /RUNARGS=\""+Arguments.GetCurrentForInstallerCmd()+"\""+(IsPortable ? " /PORTABLE=1" : "");
|
||||
bool runElevated = !IsPortable || !WindowsUtils.CheckFolderWritePermission(ProgramPath);
|
||||
|
||||
WindowsUtils.StartProcess(mainForm.UpdateInstallerPath, updaterArgs, runElevated);
|
||||
Application.Exit();
|
||||
}
|
||||
}
|
||||
|
||||
private static void plugins_Reloaded(object sender, PluginErrorEventArgs e){
|
||||
if (e.HasErrors){
|
||||
FormMessage.Error("Error Loading Plugins", "The following plugins will not be available until the issues are resolved:\n\n"+string.Join("\n\n", e.Errors), FormMessage.OK);
|
||||
}
|
||||
}
|
||||
|
||||
private static void plugins_Executed(object sender, PluginErrorEventArgs e){
|
||||
if (e.HasErrors){
|
||||
FormMessage.Error("Error Executing Plugins", "Failed to execute the following plugins:\n\n"+string.Join("\n\n", e.Errors), FormMessage.OK);
|
||||
if (WindowsUtils.OpenAssociatedProgram(mainForm.UpdateInstallerPath, updaterArgs, runElevated)){
|
||||
Application.Exit();
|
||||
}
|
||||
else{
|
||||
RestartWithArgsInternal(Arguments.GetCurrentClean());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,11 +193,6 @@ namespace TweetDuck{
|
||||
}
|
||||
}
|
||||
|
||||
public static void ReloadConfig(){
|
||||
UserConfig = UserConfig.Load(UserConfigFilePath);
|
||||
UserConfigReplaced?.Invoke(UserConfig, new EventArgs());
|
||||
}
|
||||
|
||||
public static void ResetConfig(){
|
||||
try{
|
||||
File.Delete(UserConfigFilePath);
|
||||
@@ -217,8 +201,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){
|
||||
@@ -231,11 +215,14 @@ namespace TweetDuck{
|
||||
FormBrowser browserForm = Application.OpenForms.OfType<FormBrowser>().FirstOrDefault();
|
||||
if (browserForm == null)return;
|
||||
|
||||
args.AddFlag(Arguments.ArgRestart);
|
||||
|
||||
browserForm.ForceClose();
|
||||
ExitCleanup();
|
||||
|
||||
ExitCleanup();
|
||||
RestartWithArgsInternal(args);
|
||||
}
|
||||
|
||||
private static void RestartWithArgsInternal(CommandLineArgs args){
|
||||
args.AddFlag(Arguments.ArgRestart);
|
||||
Process.Start(Application.ExecutablePath, args.ToString());
|
||||
Application.Exit();
|
||||
}
|
||||
|
@@ -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")]
|
||||
|
||||
|
68
README.md
68
README.md
@@ -1,11 +1,73 @@
|
||||
# Support
|
||||
|
||||
[Follow TweetDuck on Twitter](https://twitter.com/TryTweetDuck) | [Support via PayPal](https://paypal.me/chylex) | [Support via Patreon](https://www.patreon.com/chylex)
|
||||
|
||||
# Build Instructions
|
||||
|
||||
The program was built using Visual Studio 2017. After opening the solution, make sure you have **CefSharp.WinForms** and **Microsoft.VC120.CRT.JetBrains** included - if not, download them using NuGet.
|
||||
### Setup
|
||||
|
||||
The program was built using Visual Studio 2017. Before opening the solution, please make sure you have the following workloads and components installed (optional components that are not listed can be deselected to save space):
|
||||
* **.NET desktop development**
|
||||
* .NET Framework 4 – 4.6 development tools
|
||||
* **Desktop development with C++**
|
||||
* VC++ 2017 v141 toolset
|
||||
|
||||
After opening the solution, download the following NuGet packages by right-clicking on the solution and selecting **Restore NuGet Packages**, or manually running these commands in the **Package Manager Console**:
|
||||
```
|
||||
PM> Install-Package CefSharp.WinForms -Version 57.0.0
|
||||
PM> Install-Package Microsoft.VC120.CRT.JetBrains
|
||||
```
|
||||
|
||||
After building, run either `_postbuild.bat` if you want to package the files yourself, or `bld/RUN BUILD.bat` to generate installer files using Inno Setup (make sure the Inno Setup binaries are on your PATH).
|
||||
### Debug
|
||||
|
||||
Built files are then available in **bin/x86**, installer files are generated in **bld/Output**. If you decide to release a custom version publicly, please make it clear that it is not the original TweetDuck.
|
||||
It is recommended to create a separate data folder for debugging, otherwise you will not be able to run TweetDuck while debugging the solution.
|
||||
|
||||
To do that, open **TweetDuck Properties**, click the **Debug** tab, make sure your **Configuration** is set to `Active (Debug)` (or just `Debug`), and insert this into the **Command line arguments** field:
|
||||
```
|
||||
-datafolder TweetDuckDebug
|
||||
```
|
||||
|
||||
### Build
|
||||
|
||||
To make a release build of TweetDuck, open **Batch Build**, tick all `Release` configurations except for the `UnitTest` project (otherwise the build will fail), and click **Rebuild**. Check the status bar to make sure it says **Rebuild All succeeded**; if not, see the [Troubleshooting](#troubleshooting) section.
|
||||
|
||||
After the build succeeds, the `bin/x86/Release` folder will contain files intended for distribution (no debug symbols or other unnecessary files). You may package these files yourself, or see the [Installers](#installers) section for automated installer generation.
|
||||
|
||||
If you decide to release a custom version publicly, please make it clear that it is not an official release of TweetDuck.
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
There are a few quirks in the build process that may catch you off guard:
|
||||
|
||||
- **Plugin files are not updated automatically**
|
||||
- Since official plugins (`Resources/Plugins`) are not included in the project, Visual Studio will not automatically detect changes in the files
|
||||
- To ensure plugins are updated when testing the app, click **Rebuild Solution** before clicking **Start**
|
||||
- **Error: The command (...) exited with code 1**
|
||||
- If the post-build event fails, open the **Output** tab and look for the cause
|
||||
- Determine if there was an IO error while copying files or modifying folders, or whether the final .ps1 script failed (`Encountered an error while running PostBuild.ps1 on line xyz`)
|
||||
- Some files are checked for invalid characters:
|
||||
- `Resources/Plugins/emoji-keyboard/emoji-ordering.txt` line endings must be LF (line feed); any CR (carriage return) in the file will cause a failed build, and you will need to ensure correct line endings in your text editor
|
||||
|
||||
### Installers
|
||||
|
||||
TweetDuck uses **Inno Setup** to automate the creation of installers. First, download and install [InnoSetup QuickStart Pack](http://www.jrsoftware.org/isdl.php) (non-unicode; editor and encryption support not required) and the [Inno Download Plugin](https://code.google.com/archive/p/inno-download-plugin).
|
||||
|
||||
Next, add the Inno Setup installation folder (usually `C:\Program Files (x86)\Inno Setup 5`) into your **PATH** environment variable. You may need to restart File Explorer for the change to take place.
|
||||
|
||||
Now you can generate installers after a build by running `bld/RUN BUILD.bat`. Note that despite the name, this will only package the files, you still need to run the [build](#build) in Visual Studio!
|
||||
|
||||
After the window closes, three installers will be generated inside the `bld/Output` folder:
|
||||
* **TweetDuck.exe**
|
||||
* This is the main installer that creates entries in the Start Menu & Programs and Features, and an optional desktop icon
|
||||
* **TweetDuck.Update.exe**
|
||||
* This is a lightweight update installer that only contains the most important files that usually change across releases
|
||||
* It will automatically download and apply the full installer if the user's current version of CEF does not match (the download link is in `gen_upd.iss` and points to this repository by default)
|
||||
* **TweetDuck.Portable.exe**
|
||||
* This is a portable installer that does not need administrator privileges
|
||||
* It automatically creates a `makeportable` file in the program folder, which forces TweetDuck to run in portable mode
|
||||
|
||||
Note: There is a small chance you will see a resource error when running `RUN BUILD.bat`. If that happens, close the console window (which will terminate all Inno Setup processes and leave corrupted installer files in the output folder), and run it again.
|
||||
|
||||
### Code Notes
|
||||
|
||||
There are many references to the official TweetDuck website and this repository in the code and installers, so if you plan to release your own version, make sure to search for `tweetduck.chylex.com` and `github.com` in the whole repository and replace them appropriately.
|
||||
|
BIN
Resources/Guide/img/app-menu.png
Normal file
BIN
Resources/Guide/img/app-menu.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.9 KiB |
BIN
Resources/Guide/img/column-clear-header.png
Normal file
BIN
Resources/Guide/img/column-clear-header.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.7 KiB |
BIN
Resources/Guide/img/column-clear-preferences.png
Normal file
BIN
Resources/Guide/img/column-clear-preferences.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.5 KiB |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user