mirror of
https://github.com/chylex/TweetDuck.git
synced 2025-09-14 19:32:10 +02:00
Compare commits
147 Commits
Author | SHA1 | Date | |
---|---|---|---|
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 | |||
e4967ea46d | |||
3f28f18fb4 | |||
1b90e0f65e | |||
756ed649e6 | |||
fbc423e2a7 | |||
f04cdb6a13 | |||
63b58b1cfe | |||
77e656d8e4 | |||
a673957bd0 | |||
c99a0c9974 | |||
0fb06d0ff2 | |||
c51eebfe22 | |||
a51b34b48f | |||
1b239bada1 | |||
50ab1a6ac3 | |||
f181f1fadc | |||
c686349922 | |||
5f44a1f4ad | |||
a968938832 | |||
8d67f3dfdc | |||
973ae8cb5d | |||
a4747b0d7b | |||
f07640cc84 | |||
c235c55b19 | |||
485ef684be | |||
7caca22e57 | |||
f1d9e32bf5 | |||
23d5fa3107 | |||
4e7d8aba1c | |||
98ba871a71 | |||
3ff23c0264 | |||
e21f89477b | |||
f177f514f5 | |||
af30f3b348 | |||
82df618429 | |||
bb3538e270 | |||
71925e1126 | |||
93c1cbd231 | |||
894b890fe5 | |||
8e9e8f7fad | |||
2a0461a76f | |||
85f923a6fc | |||
b35e4d4d01 | |||
cb24a859f4 | |||
b1ef00746f | |||
aebe82e3a7 | |||
7c87856b4d | |||
d1b1dd539f | |||
55eea88ace | |||
a70f64e1f6 | |||
fa0cb120a7 | |||
e3080d07dc |
@@ -1,29 +1,28 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Threading;
|
||||||
using TweetDuck.Core.Utils;
|
using TweetDuck.Core.Utils;
|
||||||
|
using TweetLib.Communication;
|
||||||
|
|
||||||
namespace TweetDuck.Configuration{
|
namespace TweetDuck.Configuration{
|
||||||
sealed class LockManager{
|
sealed class LockManager{
|
||||||
|
private const int RetryDelay = 250;
|
||||||
|
|
||||||
public enum Result{
|
public enum Result{
|
||||||
Success, HasProcess, Fail
|
Success, HasProcess, Fail
|
||||||
}
|
}
|
||||||
|
|
||||||
public Process LockingProcess { get; private set; }
|
|
||||||
|
|
||||||
private readonly string file;
|
private readonly string file;
|
||||||
private FileStream lockStream;
|
private FileStream lockStream;
|
||||||
|
private Process lockingProcess;
|
||||||
|
|
||||||
public LockManager(string file){
|
public LockManager(string file){
|
||||||
this.file = file;
|
this.file = file;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CreateLockFileStream(){
|
// Lock file
|
||||||
lockStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.Read);
|
|
||||||
WriteIntToStream(lockStream, WindowsUtils.CurrentProcessID);
|
|
||||||
lockStream.Flush(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool ReleaseLockFileStream(){
|
private bool ReleaseLockFileStream(){
|
||||||
if (lockStream != null){
|
if (lockStream != null){
|
||||||
@@ -37,8 +36,10 @@ namespace TweetDuck.Configuration{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Result TryCreateLockFile(){
|
private Result TryCreateLockFile(){
|
||||||
if (lockStream != null){
|
void CreateLockFileStream(){
|
||||||
throw new InvalidOperationException("Lock file already exists.");
|
lockStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.Read);
|
||||||
|
lockStream.Write(BitConverter.GetBytes(WindowsUtils.CurrentProcessID), 0, sizeof(int));
|
||||||
|
lockStream.Flush(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
try{
|
try{
|
||||||
@@ -60,6 +61,8 @@ namespace TweetDuck.Configuration{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Lock management
|
||||||
|
|
||||||
public Result Lock(){
|
public Result Lock(){
|
||||||
if (lockStream != null){
|
if (lockStream != null){
|
||||||
return Result.Success;
|
return Result.Success;
|
||||||
@@ -72,7 +75,9 @@ namespace TweetDuck.Configuration{
|
|||||||
int pid;
|
int pid;
|
||||||
|
|
||||||
using(FileStream fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)){
|
using(FileStream fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)){
|
||||||
pid = ReadIntFromStream(fileStream);
|
byte[] bytes = new byte[sizeof(int)];
|
||||||
|
fileStream.Read(bytes, 0, bytes.Length);
|
||||||
|
pid = BitConverter.ToInt32(bytes, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
try{
|
try{
|
||||||
@@ -80,7 +85,7 @@ namespace TweetDuck.Configuration{
|
|||||||
|
|
||||||
using(Process currentProcess = Process.GetCurrentProcess()){
|
using(Process currentProcess = Process.GetCurrentProcess()){
|
||||||
if (foundProcess.MainModule.FileVersionInfo.InternalName == currentProcess.MainModule.FileVersionInfo.InternalName){
|
if (foundProcess.MainModule.FileVersionInfo.InternalName == currentProcess.MainModule.FileVersionInfo.InternalName){
|
||||||
LockingProcess = foundProcess;
|
lockingProcess = foundProcess;
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
foundProcess.Close();
|
foundProcess.Close();
|
||||||
@@ -91,7 +96,7 @@ namespace TweetDuck.Configuration{
|
|||||||
// Process.MainModule can throw exceptions in some cases
|
// Process.MainModule can throw exceptions in some cases
|
||||||
}
|
}
|
||||||
|
|
||||||
return LockingProcess == null ? Result.Fail : Result.HasProcess;
|
return lockingProcess == null ? Result.Fail : Result.HasProcess;
|
||||||
}catch{
|
}catch{
|
||||||
return Result.Fail;
|
return Result.Fail;
|
||||||
}
|
}
|
||||||
@@ -100,45 +105,72 @@ namespace TweetDuck.Configuration{
|
|||||||
return initialResult;
|
return initialResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Unlock(){
|
public Result LockWait(int timeout){
|
||||||
bool result = true;
|
for(int elapsed = 0; elapsed < timeout; elapsed += RetryDelay){
|
||||||
|
Result result = Lock();
|
||||||
|
|
||||||
|
if (result == Result.HasProcess){
|
||||||
|
Thread.Sleep(RetryDelay);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Lock();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Unlock(){
|
||||||
if (ReleaseLockFileStream()){
|
if (ReleaseLockFileStream()){
|
||||||
try{
|
try{
|
||||||
File.Delete(file);
|
File.Delete(file);
|
||||||
}catch(Exception e){
|
}catch(Exception e){
|
||||||
Program.Reporter.Log(e.ToString());
|
Program.Reporter.Log(e.ToString());
|
||||||
result = false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Locking process
|
||||||
|
|
||||||
|
public bool RestoreLockingProcess(int failTimeout){
|
||||||
|
if (lockingProcess != null){
|
||||||
|
if (lockingProcess.MainWindowHandle == IntPtr.Zero){ // restore if the original process is in tray
|
||||||
|
Comms.BroadcastMessage(Program.WindowRestoreMessage, (uint)lockingProcess.Id, 0);
|
||||||
|
|
||||||
|
if (WindowsUtils.TrySleepUntil(() => CheckLockingProcessExited() || (lockingProcess.MainWindowHandle != IntPtr.Zero && lockingProcess.Responding), failTimeout, RetryDelay)){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool CloseLockingProcess(int closeTimeout, int killTimeout){
|
public bool CloseLockingProcess(int closeTimeout, int killTimeout){
|
||||||
if (LockingProcess != null){
|
if (lockingProcess != null){
|
||||||
try{
|
try{
|
||||||
if (LockingProcess.CloseMainWindow()){
|
if (lockingProcess.CloseMainWindow()){
|
||||||
WindowsUtils.TrySleepUntil(CheckLockingProcessExited, closeTimeout, 250);
|
WindowsUtils.TrySleepUntil(CheckLockingProcessExited, closeTimeout, RetryDelay);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!LockingProcess.HasExited){
|
if (!lockingProcess.HasExited){
|
||||||
LockingProcess.Kill();
|
lockingProcess.Kill();
|
||||||
WindowsUtils.TrySleepUntil(CheckLockingProcessExited, killTimeout, 250);
|
WindowsUtils.TrySleepUntil(CheckLockingProcessExited, killTimeout, RetryDelay);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (LockingProcess.HasExited){
|
if (lockingProcess.HasExited){
|
||||||
LockingProcess.Dispose();
|
lockingProcess.Dispose();
|
||||||
LockingProcess = null;
|
lockingProcess = null;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}catch(Exception ex){
|
}catch(Exception ex){
|
||||||
if (ex is InvalidOperationException || ex is Win32Exception){
|
if (ex is InvalidOperationException || ex is Win32Exception){
|
||||||
if (LockingProcess != null){
|
if (lockingProcess != null){
|
||||||
LockingProcess.Refresh();
|
bool hasExited = CheckLockingProcessExited();
|
||||||
|
lockingProcess.Dispose();
|
||||||
bool hasExited = LockingProcess.HasExited;
|
|
||||||
LockingProcess.Dispose();
|
|
||||||
return hasExited;
|
return hasExited;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -150,21 +182,8 @@ namespace TweetDuck.Configuration{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private bool CheckLockingProcessExited(){
|
private bool CheckLockingProcessExited(){
|
||||||
LockingProcess.Refresh();
|
lockingProcess.Refresh();
|
||||||
return LockingProcess.HasExited;
|
return lockingProcess.HasExited;
|
||||||
}
|
|
||||||
|
|
||||||
// Utility functions
|
|
||||||
|
|
||||||
private static void WriteIntToStream(Stream stream, int value){
|
|
||||||
byte[] id = BitConverter.GetBytes(value);
|
|
||||||
stream.Write(id, 0, id.Length);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int ReadIntFromStream(Stream stream){
|
|
||||||
byte[] bytes = new byte[4];
|
|
||||||
stream.Read(bytes, 0, 4);
|
|
||||||
return BitConverter.ToInt32(bytes, 0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,43 +1,43 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using TweetDuck.Core.Utils;
|
using TweetDuck.Core.Utils;
|
||||||
|
using TweetDuck.Data.Serialization;
|
||||||
|
|
||||||
namespace TweetDuck.Configuration{
|
namespace TweetDuck.Configuration{
|
||||||
sealed class SystemConfig{
|
sealed class SystemConfig{
|
||||||
|
private static readonly FileSerializer<SystemConfig> Serializer = new FileSerializer<SystemConfig>{
|
||||||
|
// HandleUnknownProperties = (obj, data) => {}
|
||||||
|
};
|
||||||
|
|
||||||
public static readonly bool IsHardwareAccelerationSupported = File.Exists(Path.Combine(Program.ProgramPath, "libEGL.dll")) &&
|
public static readonly bool IsHardwareAccelerationSupported = File.Exists(Path.Combine(Program.ProgramPath, "libEGL.dll")) &&
|
||||||
File.Exists(Path.Combine(Program.ProgramPath, "libGLESv2.dll"));
|
File.Exists(Path.Combine(Program.ProgramPath, "libGLESv2.dll"));
|
||||||
|
|
||||||
|
// CONFIGURATION DATA
|
||||||
|
|
||||||
|
private bool _hardwareAcceleration = true;
|
||||||
|
|
||||||
|
public bool EnableBrowserGCReload { get; set; } = true;
|
||||||
|
public int BrowserMemoryThreshold { get; set; } = 400;
|
||||||
|
|
||||||
|
// SPECIAL PROPERTIES
|
||||||
|
|
||||||
public bool HardwareAcceleration{
|
public bool HardwareAcceleration{
|
||||||
get => hardwareAcceleration && IsHardwareAccelerationSupported;
|
get => _hardwareAcceleration && IsHardwareAccelerationSupported;
|
||||||
set => hardwareAcceleration = value;
|
set => _hardwareAcceleration = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// END OF CONFIG
|
||||||
|
|
||||||
private readonly string file;
|
private readonly string file;
|
||||||
|
|
||||||
private bool hardwareAcceleration;
|
|
||||||
|
|
||||||
private SystemConfig(string file){
|
private SystemConfig(string file){
|
||||||
this.file = file;
|
this.file = file;
|
||||||
|
|
||||||
HardwareAcceleration = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void WriteToStream(Stream stream){
|
|
||||||
stream.WriteByte((byte)(HardwareAcceleration ? 1 : 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ReadFromStream(Stream stream){
|
|
||||||
HardwareAcceleration = stream.ReadByte() > 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Save(){
|
public bool Save(){
|
||||||
try{
|
try{
|
||||||
WindowsUtils.CreateDirectoryForFile(file);
|
WindowsUtils.CreateDirectoryForFile(file);
|
||||||
|
Serializer.Write(file, this);
|
||||||
using(Stream stream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None)){
|
|
||||||
WriteToStream(stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}catch(Exception e){
|
}catch(Exception e){
|
||||||
Program.Reporter.HandleException("Configuration Error", "Could not save the system configuration file.", true, e);
|
Program.Reporter.HandleException("Configuration Error", "Could not save the system configuration file.", true, e);
|
||||||
@@ -49,11 +49,20 @@ namespace TweetDuck.Configuration{
|
|||||||
SystemConfig config = new SystemConfig(file);
|
SystemConfig config = new SystemConfig(file);
|
||||||
|
|
||||||
try{
|
try{
|
||||||
using(Stream stream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read)){
|
Serializer.Read(file, config);
|
||||||
config.ReadFromStream(stream);
|
return config;
|
||||||
}
|
|
||||||
}catch(FileNotFoundException){
|
}catch(FileNotFoundException){
|
||||||
}catch(DirectoryNotFoundException){
|
}catch(DirectoryNotFoundException){
|
||||||
|
}catch(FormatException){
|
||||||
|
try{
|
||||||
|
using(Stream stream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read)){
|
||||||
|
config.HardwareAcceleration = stream.ReadByte() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
config.Save();
|
||||||
|
}catch(Exception e){
|
||||||
|
Program.Reporter.HandleException("Configuration Error", "Could not update the system configuration file.", true, e);
|
||||||
|
}
|
||||||
}catch(Exception e){
|
}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);
|
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,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using TweetDuck.Core;
|
using TweetDuck.Core;
|
||||||
@@ -9,8 +10,27 @@ using TweetDuck.Data;
|
|||||||
using TweetDuck.Data.Serialization;
|
using TweetDuck.Data.Serialization;
|
||||||
|
|
||||||
namespace TweetDuck.Configuration{
|
namespace TweetDuck.Configuration{
|
||||||
sealed class UserConfig : ISerializedObject{
|
sealed class UserConfig{
|
||||||
private static readonly FileSerializer<UserConfig> Serializer = new FileSerializer<UserConfig>();
|
private static readonly FileSerializer<UserConfig> Serializer = new FileSerializer<UserConfig>{ HandleUnknownProperties = HandleUnknownProperties };
|
||||||
|
|
||||||
|
private static void HandleUnknownProperties(UserConfig obj, Dictionary<string, string> data){
|
||||||
|
if (data.TryGetValue("EnableBrowserGCReload", out string propGCReload) && data.TryGetValue("BrowserMemoryThreshold", out string propMemThreshold)){
|
||||||
|
if (bool.TryParse(propGCReload, out bool isGCReloadEnabled) && isGCReloadEnabled && int.TryParse(propMemThreshold, out int memThreshold)){
|
||||||
|
// SystemConfig initialization was moved before UserConfig to allow for this
|
||||||
|
// TODO remove the migration soon
|
||||||
|
Program.SystemConfig.EnableBrowserGCReload = true;
|
||||||
|
Program.SystemConfig.BrowserMemoryThreshold = memThreshold;
|
||||||
|
Program.SystemConfig.Save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data.Remove("EnableBrowserGCReload");
|
||||||
|
data.Remove("BrowserMemoryThreshold");
|
||||||
|
|
||||||
|
if (data.Count == 0){
|
||||||
|
obj.Save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static UserConfig(){
|
static UserConfig(){
|
||||||
Serializer.RegisterTypeConverter(typeof(WindowState), WindowState.Converter);
|
Serializer.RegisterTypeConverter(typeof(WindowState), WindowState.Converter);
|
||||||
@@ -39,7 +59,9 @@ namespace TweetDuck.Configuration{
|
|||||||
|
|
||||||
public bool ExpandLinksOnHover { get; set; } = true;
|
public bool ExpandLinksOnHover { get; set; } = true;
|
||||||
public bool SwitchAccountSelectors { get; set; } = true;
|
public bool SwitchAccountSelectors { get; set; } = true;
|
||||||
|
public bool BestImageQuality { get; set; } = true;
|
||||||
public bool EnableSpellCheck { get; set; } = false;
|
public bool EnableSpellCheck { get; set; } = false;
|
||||||
|
public int VideoPlayerVolume { get; set; } = 50;
|
||||||
private int _zoomLevel = 100;
|
private int _zoomLevel = 100;
|
||||||
private bool _muteNotifications;
|
private bool _muteNotifications;
|
||||||
|
|
||||||
@@ -50,6 +72,7 @@ namespace TweetDuck.Configuration{
|
|||||||
public string DismissedUpdate { get; set; } = null;
|
public string DismissedUpdate { get; set; } = null;
|
||||||
|
|
||||||
public bool DisplayNotificationColumn { get; set; } = false;
|
public bool DisplayNotificationColumn { get; set; } = false;
|
||||||
|
public bool NotificationMediaPreviews { get; set; } = true;
|
||||||
public bool NotificationSkipOnLinkClick { get; set; } = false;
|
public bool NotificationSkipOnLinkClick { get; set; } = false;
|
||||||
public bool NotificationNonIntrusiveMode { get; set; } = true;
|
public bool NotificationNonIntrusiveMode { get; set; } = true;
|
||||||
public int NotificationIdlePauseSeconds { get; set; } = 0;
|
public int NotificationIdlePauseSeconds { get; set; } = 0;
|
||||||
@@ -73,14 +96,13 @@ namespace TweetDuck.Configuration{
|
|||||||
public string CustomBrowserCSS { get; set; } = null;
|
public string CustomBrowserCSS { get; set; } = null;
|
||||||
public string CustomNotificationCSS { get; set; } = null;
|
public string CustomNotificationCSS { get; set; } = null;
|
||||||
|
|
||||||
public bool EnableBrowserGCReload { get; set; } = false;
|
|
||||||
public int BrowserMemoryThreshold { get; set; } = 350;
|
|
||||||
|
|
||||||
// SPECIAL PROPERTIES
|
// SPECIAL PROPERTIES
|
||||||
|
|
||||||
public bool IsCustomNotificationPositionSet => CustomNotificationPosition != ControlExtensions.InvisibleLocation;
|
public bool IsCustomNotificationPositionSet => CustomNotificationPosition != ControlExtensions.InvisibleLocation;
|
||||||
public bool IsCustomNotificationSizeSet => CustomNotificationSize != Size.Empty;
|
public bool IsCustomNotificationSizeSet => CustomNotificationSize != Size.Empty;
|
||||||
|
|
||||||
|
public TwitterUtils.ImageQuality TwitterImageQuality => BestImageQuality ? TwitterUtils.ImageQuality.Orig : TwitterUtils.ImageQuality.Default;
|
||||||
|
|
||||||
public string NotificationSoundPath{
|
public string NotificationSoundPath{
|
||||||
get => string.IsNullOrEmpty(_notificationSoundPath) ? string.Empty : _notificationSoundPath;
|
get => string.IsNullOrEmpty(_notificationSoundPath) ? string.Empty : _notificationSoundPath;
|
||||||
set => _notificationSoundPath = value;
|
set => _notificationSoundPath = value;
|
||||||
@@ -127,16 +149,14 @@ namespace TweetDuck.Configuration{
|
|||||||
public event EventHandler ZoomLevelChanged;
|
public event EventHandler ZoomLevelChanged;
|
||||||
public event EventHandler TrayBehaviorChanged;
|
public event EventHandler TrayBehaviorChanged;
|
||||||
|
|
||||||
|
// END OF CONFIG
|
||||||
|
|
||||||
private readonly string file;
|
private readonly string file;
|
||||||
|
|
||||||
public UserConfig(string file){ // TODO make private after removing UserConfigLegacy
|
public UserConfig(string file){ // TODO make private after removing UserConfigLegacy
|
||||||
this.file = file;
|
this.file = file;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ISerializedObject.OnReadUnknownProperty(string property, string value){
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Save(){
|
public bool Save(){
|
||||||
try{
|
try{
|
||||||
WindowsUtils.CreateDirectoryForFile(file);
|
WindowsUtils.CreateDirectoryForFile(file);
|
||||||
|
@@ -1,45 +1,32 @@
|
|||||||
using System;
|
using System.Text;
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace TweetDuck.Core.Bridge{
|
namespace TweetDuck.Core.Bridge{
|
||||||
static class PropertyBridge{
|
static class PropertyBridge{
|
||||||
[Flags]
|
public enum Environment{
|
||||||
public enum Properties{
|
Browser, Notification
|
||||||
ExpandLinksOnHover = 1,
|
|
||||||
MuteNotifications = 2,
|
|
||||||
HasCustomNotificationSound = 4,
|
|
||||||
SkipOnLinkClick = 8,
|
|
||||||
SwitchAccountSelectors = 16,
|
|
||||||
AllBrowser = ExpandLinksOnHover | SwitchAccountSelectors | MuteNotifications | HasCustomNotificationSound,
|
|
||||||
AllNotification = ExpandLinksOnHover | SkipOnLinkClick
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GenerateScript(Properties properties){
|
public static string GenerateScript(Environment environment){
|
||||||
StringBuilder build = new StringBuilder();
|
string Bool(bool value){
|
||||||
build.Append("(function(c){");
|
return value ? "true;" : "false;";
|
||||||
|
|
||||||
if (properties.HasFlag(Properties.ExpandLinksOnHover)){
|
|
||||||
build.Append("c.expandLinksOnHover=").Append(Program.UserConfig.ExpandLinksOnHover ? "true;" : "false;");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (properties.HasFlag(Properties.SwitchAccountSelectors)){
|
StringBuilder build = new StringBuilder().Append("(function(x){");
|
||||||
build.Append("c.switchAccountSelectors=").Append(Program.UserConfig.SwitchAccountSelectors ? "true;" : "false;");
|
|
||||||
|
build.Append("x.expandLinksOnHover=").Append(Bool(Program.UserConfig.ExpandLinksOnHover));
|
||||||
|
|
||||||
|
if (environment == Environment.Browser){
|
||||||
|
build.Append("x.switchAccountSelectors=").Append(Bool(Program.UserConfig.SwitchAccountSelectors));
|
||||||
|
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 (properties.HasFlag(Properties.MuteNotifications)){
|
if (environment == Environment.Notification){
|
||||||
build.Append("c.muteNotifications=").Append(Program.UserConfig.MuteNotifications ? "true;" : "false;");
|
build.Append("x.skipOnLinkClick=").Append(Bool(Program.UserConfig.NotificationSkipOnLinkClick));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (properties.HasFlag(Properties.HasCustomNotificationSound)){
|
return build.Append("})(window.$TDX=window.$TDX||{})").ToString();
|
||||||
build.Append("c.hasCustomNotificationSound=").Append(Program.UserConfig.NotificationSoundPath.Length > 0 ? "true;" : "false;");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (properties.HasFlag(Properties.SkipOnLinkClick)){
|
|
||||||
build.Append("c.skipOnLinkClick=").Append(Program.UserConfig.NotificationSkipOnLinkClick ? "true;" : "false;");
|
|
||||||
}
|
|
||||||
|
|
||||||
build.Append("})(window.$TDX=window.$TDX||{})");
|
|
||||||
return build.ToString();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,17 +1,39 @@
|
|||||||
using System.Windows.Forms;
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using CefSharp;
|
||||||
using TweetDuck.Core.Controls;
|
using TweetDuck.Core.Controls;
|
||||||
using TweetDuck.Core.Notification;
|
using TweetDuck.Core.Notification;
|
||||||
using TweetDuck.Core.Other;
|
using TweetDuck.Core.Other;
|
||||||
using TweetDuck.Core.Utils;
|
using TweetDuck.Core.Utils;
|
||||||
|
using TweetDuck.Resources;
|
||||||
|
|
||||||
namespace TweetDuck.Core.Bridge{
|
namespace TweetDuck.Core.Bridge{
|
||||||
sealed class TweetDeckBridge{
|
sealed class TweetDeckBridge{
|
||||||
public static string LastRightClickedLink = string.Empty;
|
public static string LastRightClickedLink = string.Empty;
|
||||||
|
public static string LastRightClickedImage = string.Empty;
|
||||||
public static string LastHighlightedTweet = string.Empty;
|
public static string LastHighlightedTweet = string.Empty;
|
||||||
public static string LastHighlightedQuotedTweet = string.Empty;
|
public static string LastHighlightedQuotedTweet = string.Empty;
|
||||||
|
public static string LastHighlightedTweetAuthor = string.Empty;
|
||||||
|
public static string[] LastHighlightedTweetImages = StringUtils.EmptyArray;
|
||||||
|
public static Dictionary<string, string> SessionData = new Dictionary<string, string>(2);
|
||||||
|
|
||||||
public static void ResetStaticProperties(){
|
public static void ResetStaticProperties(){
|
||||||
LastRightClickedLink = LastHighlightedTweet = LastHighlightedQuotedTweet = string.Empty;
|
LastRightClickedLink = LastRightClickedImage = LastHighlightedTweet = LastHighlightedQuotedTweet = LastHighlightedTweetAuthor = string.Empty;
|
||||||
|
LastHighlightedTweetImages = StringUtils.EmptyArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RestoreSessionData(IFrame frame){
|
||||||
|
if (SessionData.Count > 0){
|
||||||
|
StringBuilder build = new StringBuilder().Append("window.TD_SESSION={");
|
||||||
|
|
||||||
|
foreach(KeyValuePair<string, string> kvp in SessionData){
|
||||||
|
build.Append(kvp.Key).Append(":'").Append(kvp.Value.Replace("'", "\\'")).Append("',");
|
||||||
|
}
|
||||||
|
|
||||||
|
ScriptLoader.ExecuteScript(frame, build.Append("}").ToString(), "gen:session");
|
||||||
|
SessionData.Clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly FormBrowser form;
|
private readonly FormBrowser form;
|
||||||
@@ -38,10 +60,16 @@ namespace TweetDuck.Core.Bridge{
|
|||||||
form.InvokeAsyncSafe(() => LastRightClickedLink = link);
|
form.InvokeAsyncSafe(() => LastRightClickedLink = link);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetLastHighlightedTweet(string link, string quotedLink){
|
public void SetLastRightClickedImage(string link){
|
||||||
|
form.InvokeAsyncSafe(() => LastRightClickedImage = link);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetLastHighlightedTweet(string link, string quotedLink, string author, string imageList){
|
||||||
form.InvokeAsyncSafe(() => {
|
form.InvokeAsyncSafe(() => {
|
||||||
LastHighlightedTweet = link;
|
LastHighlightedTweet = link;
|
||||||
LastHighlightedQuotedTweet = quotedLink;
|
LastHighlightedQuotedTweet = quotedLink;
|
||||||
|
LastHighlightedTweetAuthor = author;
|
||||||
|
LastHighlightedTweetImages = imageList.Split(';');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,6 +100,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(){
|
public void LoadNextNotification(){
|
||||||
notification.InvokeAsyncSafe(notification.FinishCurrentNotification);
|
notification.InvokeAsyncSafe(notification.FinishCurrentNotification);
|
||||||
}
|
}
|
||||||
@@ -80,6 +114,10 @@ namespace TweetDuck.Core.Bridge{
|
|||||||
form.InvokeAsyncSafe(() => form.OnTweetScreenshotReady(html, width, height));
|
form.InvokeAsyncSafe(() => form.OnTweetScreenshotReady(html, width, height));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void PlayVideo(string url){
|
||||||
|
form.InvokeAsyncSafe(() => form.PlayVideo(url));
|
||||||
|
}
|
||||||
|
|
||||||
public void FixClipboard(){
|
public void FixClipboard(){
|
||||||
form.InvokeAsyncSafe(WindowsUtils.ClipboardStripHtmlStyles);
|
form.InvokeAsyncSafe(WindowsUtils.ClipboardStripHtmlStyles);
|
||||||
}
|
}
|
||||||
|
@@ -1,8 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using TweetDuck.Core.Utils;
|
using TweetDuck.Core.Utils;
|
||||||
|
using TweetLib.Communication;
|
||||||
|
|
||||||
namespace TweetDuck.Core.Controls{
|
namespace TweetDuck.Core.Controls{
|
||||||
static class ControlExtensions{
|
static class ControlExtensions{
|
||||||
@@ -70,7 +71,7 @@ namespace TweetDuck.Core.Controls{
|
|||||||
public static void SetElevated(this Button button){
|
public static void SetElevated(this Button button){
|
||||||
button.Text = " "+button.Text;
|
button.Text = " "+button.Text;
|
||||||
button.FlatStyle = FlatStyle.System;
|
button.FlatStyle = FlatStyle.System;
|
||||||
NativeMethods.SendMessage(button.Handle, NativeMethods.BCM_SETSHIELD, new UIntPtr(0), new IntPtr(1));
|
Comms.SendMessage(button.Handle, NativeMethods.BCM_SETSHIELD, 0, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void EnableMultilineShortcuts(this TextBox textBox){
|
public static void EnableMultilineShortcuts(this TextBox textBox){
|
||||||
|
2
Core/FormBrowser.Designer.cs
generated
2
Core/FormBrowser.Designer.cs
generated
@@ -38,7 +38,7 @@
|
|||||||
//
|
//
|
||||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||||
this.BackColor = TweetDuck.Core.Utils.BrowserUtils.BackgroundColor;
|
this.BackColor = TweetDuck.Core.Utils.TwitterUtils.BackgroundColor;
|
||||||
this.ClientSize = new System.Drawing.Size(324, 386);
|
this.ClientSize = new System.Drawing.Size(324, 386);
|
||||||
this.Icon = Properties.Resources.icon;
|
this.Icon = Properties.Resources.icon;
|
||||||
this.Location = TweetDuck.Core.Controls.ControlExtensions.InvisibleLocation;
|
this.Location = TweetDuck.Core.Controls.ControlExtensions.InvisibleLocation;
|
||||||
|
@@ -2,15 +2,16 @@
|
|||||||
using CefSharp.WinForms;
|
using CefSharp.WinForms;
|
||||||
using System;
|
using System;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Linq;
|
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using TweetDuck.Configuration;
|
using TweetDuck.Configuration;
|
||||||
using TweetDuck.Core.Bridge;
|
using TweetDuck.Core.Bridge;
|
||||||
using TweetDuck.Core.Controls;
|
using TweetDuck.Core.Controls;
|
||||||
using TweetDuck.Core.Handling;
|
using TweetDuck.Core.Handling;
|
||||||
|
using TweetDuck.Core.Handling.General;
|
||||||
using TweetDuck.Core.Notification;
|
using TweetDuck.Core.Notification;
|
||||||
using TweetDuck.Core.Notification.Screenshot;
|
using TweetDuck.Core.Notification.Screenshot;
|
||||||
using TweetDuck.Core.Other;
|
using TweetDuck.Core.Other;
|
||||||
|
using TweetDuck.Core.Other.Management;
|
||||||
using TweetDuck.Core.Other.Settings;
|
using TweetDuck.Core.Other.Settings;
|
||||||
using TweetDuck.Core.Utils;
|
using TweetDuck.Core.Utils;
|
||||||
using TweetDuck.Plugins;
|
using TweetDuck.Plugins;
|
||||||
@@ -25,6 +26,23 @@ namespace TweetDuck.Core{
|
|||||||
sealed partial class FormBrowser : Form{
|
sealed partial class FormBrowser : Form{
|
||||||
private static UserConfig Config => Program.UserConfig;
|
private static UserConfig Config => Program.UserConfig;
|
||||||
|
|
||||||
|
public bool IsWaiting{
|
||||||
|
set{
|
||||||
|
if (value){
|
||||||
|
browser.Enabled = false;
|
||||||
|
Cursor = Cursors.WaitCursor;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
browser.Enabled = true;
|
||||||
|
Cursor = Cursors.Default;
|
||||||
|
|
||||||
|
if (Focused){ // re-focus browser only if the window or a child is activated
|
||||||
|
browser.Focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public string UpdateInstallerPath { get; private set; }
|
public string UpdateInstallerPath { get; private set; }
|
||||||
|
|
||||||
private readonly ChromiumWebBrowser browser;
|
private readonly ChromiumWebBrowser browser;
|
||||||
@@ -40,15 +58,18 @@ namespace TweetDuck.Core{
|
|||||||
|
|
||||||
private TweetScreenshotManager notificationScreenshotManager;
|
private TweetScreenshotManager notificationScreenshotManager;
|
||||||
private SoundNotification soundNotification;
|
private SoundNotification soundNotification;
|
||||||
|
private VideoPlayer videoPlayer;
|
||||||
|
|
||||||
public FormBrowser(PluginManager pluginManager, UpdaterSettings updaterSettings){
|
public FormBrowser(UpdaterSettings updaterSettings){
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
Text = Program.BrandName;
|
Text = Program.BrandName;
|
||||||
|
|
||||||
this.plugins = pluginManager;
|
this.plugins = new PluginManager(Program.PluginPath, Program.PluginConfigFilePath);
|
||||||
this.plugins.Reloaded += plugins_Reloaded;
|
this.plugins.Reloaded += plugins_Reloaded;
|
||||||
|
this.plugins.Executed += plugins_Executed;
|
||||||
this.plugins.PluginChangedState += plugins_PluginChangedState;
|
this.plugins.PluginChangedState += plugins_PluginChangedState;
|
||||||
|
this.plugins.Reload();
|
||||||
|
|
||||||
this.contextMenu = ContextMenuBrowser.CreateMenu(this);
|
this.contextMenu = ContextMenuBrowser.CreateMenu(this);
|
||||||
this.memoryUsageTracker = new MemoryUsageTracker("TDGF_tryRunCleanup");
|
this.memoryUsageTracker = new MemoryUsageTracker("TDGF_tryRunCleanup");
|
||||||
@@ -66,6 +87,7 @@ namespace TweetDuck.Core{
|
|||||||
this.browser = new ChromiumWebBrowser("https://tweetdeck.twitter.com/"){
|
this.browser = new ChromiumWebBrowser("https://tweetdeck.twitter.com/"){
|
||||||
MenuHandler = new ContextMenuBrowser(this),
|
MenuHandler = new ContextMenuBrowser(this),
|
||||||
JsDialogHandler = new JavaScriptDialogHandler(),
|
JsDialogHandler = new JavaScriptDialogHandler(),
|
||||||
|
KeyboardHandler = new KeyboardHandlerBrowser(this),
|
||||||
LifeSpanHandler = new LifeSpanHandler(),
|
LifeSpanHandler = new LifeSpanHandler(),
|
||||||
RequestHandler = new RequestHandlerBrowser()
|
RequestHandler = new RequestHandlerBrowser()
|
||||||
};
|
};
|
||||||
@@ -81,7 +103,7 @@ namespace TweetDuck.Core{
|
|||||||
this.browser.RegisterAsyncJsObject("$TD", new TweetDeckBridge(this, notification));
|
this.browser.RegisterAsyncJsObject("$TD", new TweetDeckBridge(this, notification));
|
||||||
this.browser.RegisterAsyncJsObject("$TDP", plugins.Bridge);
|
this.browser.RegisterAsyncJsObject("$TDP", plugins.Bridge);
|
||||||
|
|
||||||
browser.BrowserSettings.BackgroundColor = (uint)BrowserUtils.BackgroundColor.ToArgb();
|
browser.BrowserSettings.BackgroundColor = (uint)TwitterUtils.BackgroundColor.ToArgb();
|
||||||
browser.Dock = DockStyle.None;
|
browser.Dock = DockStyle.None;
|
||||||
browser.Location = ControlExtensions.InvisibleLocation;
|
browser.Location = ControlExtensions.InvisibleLocation;
|
||||||
Controls.Add(browser);
|
Controls.Add(browser);
|
||||||
@@ -96,6 +118,7 @@ namespace TweetDuck.Core{
|
|||||||
|
|
||||||
notificationScreenshotManager?.Dispose();
|
notificationScreenshotManager?.Dispose();
|
||||||
soundNotification?.Dispose();
|
soundNotification?.Dispose();
|
||||||
|
videoPlayer?.Dispose();
|
||||||
};
|
};
|
||||||
|
|
||||||
this.trayIcon.ClickRestore += trayIcon_ClickRestore;
|
this.trayIcon.ClickRestore += trayIcon_ClickRestore;
|
||||||
@@ -114,16 +137,6 @@ namespace TweetDuck.Core{
|
|||||||
RestoreWindow();
|
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){
|
private void ShowChildForm(Form form){
|
||||||
form.VisibleChanged += (sender, args) => form.MoveToCenter(this);
|
form.VisibleChanged += (sender, args) => form.MoveToCenter(this);
|
||||||
form.Show(this);
|
form.Show(this);
|
||||||
@@ -158,7 +171,7 @@ namespace TweetDuck.Core{
|
|||||||
|
|
||||||
private void browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e){
|
private void browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e){
|
||||||
if (!e.IsLoading){
|
if (!e.IsLoading){
|
||||||
foreach(string word in BrowserUtils.DictionaryWords){
|
foreach(string word in TwitterUtils.DictionaryWords){
|
||||||
browser.AddWordToDictionary(word);
|
browser.AddWordToDictionary(word);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,30 +188,26 @@ namespace TweetDuck.Core{
|
|||||||
BrowserUtils.SetZoomLevel(browser.GetBrowser(), Config.ZoomLevel);
|
BrowserUtils.SetZoomLevel(browser.GetBrowser(), Config.ZoomLevel);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (BrowserUtils.IsTwitterWebsite(e.Frame)){
|
if (TwitterUtils.IsTwitterWebsite(e.Frame)){
|
||||||
ScriptLoader.ExecuteFile(e.Frame, "twitter.js");
|
ScriptLoader.ExecuteFile(e.Frame, "twitter.js");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
|
private void browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
|
||||||
if (e.Frame.IsMain && BrowserUtils.IsTweetDeckWebsite(e.Frame)){
|
if (e.Frame.IsMain && TwitterUtils.IsTweetDeckWebsite(e.Frame)){
|
||||||
e.Frame.ExecuteJavaScriptAsync(BrowserUtils.BackgroundColorFix);
|
e.Frame.ExecuteJavaScriptAsync(TwitterUtils.BackgroundColorFix);
|
||||||
|
|
||||||
UpdateProperties(PropertyBridge.Properties.AllBrowser);
|
UpdateProperties(PropertyBridge.Environment.Browser);
|
||||||
|
TweetDeckBridge.RestoreSessionData(e.Frame);
|
||||||
ScriptLoader.ExecuteFile(e.Frame, "code.js");
|
ScriptLoader.ExecuteFile(e.Frame, "code.js");
|
||||||
ReinjectCustomCSS(Config.CustomBrowserCSS);
|
ReinjectCustomCSS(Config.CustomBrowserCSS);
|
||||||
|
plugins.ExecutePlugins(e.Frame, PluginEnvironment.Browser);
|
||||||
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();
|
TweetDeckBridge.ResetStaticProperties();
|
||||||
|
|
||||||
if (Config.EnableBrowserGCReload){
|
if (Program.SystemConfig.EnableBrowserGCReload){
|
||||||
memoryUsageTracker.Start(this, e.Browser, Config.BrowserMemoryThreshold);
|
memoryUsageTracker.Start(this, e.Browser, Program.SystemConfig.BrowserMemoryThreshold);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -225,6 +234,10 @@ namespace TweetDuck.Core{
|
|||||||
if (!isLoaded)return;
|
if (!isLoaded)return;
|
||||||
|
|
||||||
trayIcon.HasNotifications = false;
|
trayIcon.HasNotifications = false;
|
||||||
|
|
||||||
|
if (!browser.Enabled){ // when taking a screenshot, the window is unfocused and
|
||||||
|
browser.Enabled = true; // the browser is disabled; if the user clicks back into
|
||||||
|
} // the window, enable the browser again
|
||||||
}
|
}
|
||||||
|
|
||||||
private void FormBrowser_LocationChanged(object sender, EventArgs e){
|
private void FormBrowser_LocationChanged(object sender, EventArgs e){
|
||||||
@@ -282,7 +295,7 @@ namespace TweetDuck.Core{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void Config_MuteToggled(object sender, EventArgs e){
|
private void Config_MuteToggled(object sender, EventArgs e){
|
||||||
UpdateProperties(PropertyBridge.Properties.MuteNotifications);
|
UpdateProperties(PropertyBridge.Environment.Browser);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Config_ZoomLevelChanged(object sender, EventArgs e){
|
private void Config_ZoomLevelChanged(object sender, EventArgs e){
|
||||||
@@ -305,7 +318,19 @@ namespace TweetDuck.Core{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void plugins_Reloaded(object sender, PluginErrorEventArgs e){
|
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){
|
private void plugins_PluginChangedState(object sender, PluginChangedStateEventArgs e){
|
||||||
@@ -314,11 +339,7 @@ namespace TweetDuck.Core{
|
|||||||
|
|
||||||
private void updates_UpdateAccepted(object sender, UpdateAcceptedEventArgs e){
|
private void updates_UpdateAccepted(object sender, UpdateAcceptedEventArgs e){
|
||||||
this.InvokeAsyncSafe(() => {
|
this.InvokeAsyncSafe(() => {
|
||||||
foreach(Form form in Application.OpenForms.Cast<Form>().Reverse()){
|
FormManager.CloseAllDialogs();
|
||||||
if (form is FormSettings || form is FormPlugins || form is FormAbout){
|
|
||||||
form.Close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updates.BeginUpdateDownload(this, e.UpdateInfo, update => {
|
updates.BeginUpdateDownload(this, e.UpdateInfo, update => {
|
||||||
if (update.DownloadStatus == UpdateDownloadStatus.Done){
|
if (update.DownloadStatus == UpdateDownloadStatus.Done){
|
||||||
@@ -374,7 +395,13 @@ namespace TweetDuck.Core{
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isBrowserReady && m.Msg == NativeMethods.WM_PARENTNOTIFY && (m.WParam.ToInt32() & 0xFFFF) == NativeMethods.WM_XBUTTONDOWN){
|
if (isBrowserReady && m.Msg == NativeMethods.WM_PARENTNOTIFY && (m.WParam.ToInt32() & 0xFFFF) == NativeMethods.WM_XBUTTONDOWN){
|
||||||
|
if (videoPlayer != null && videoPlayer.Running){
|
||||||
|
videoPlayer.Close();
|
||||||
|
}
|
||||||
|
else{
|
||||||
browser.ExecuteScriptAsync("TDGF_onMouseClickExtra", (m.WParam.ToInt32() >> 16) & 0xFFFF);
|
browser.ExecuteScriptAsync("TDGF_onMouseClickExtra", (m.WParam.ToInt32() >> 16) & 0xFFFF);
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -401,12 +428,12 @@ namespace TweetDuck.Core{
|
|||||||
browser.ExecuteScriptAsync("TDGF_reinjectCustomCSS", css?.Replace(Environment.NewLine, " ") ?? string.Empty);
|
browser.ExecuteScriptAsync("TDGF_reinjectCustomCSS", css?.Replace(Environment.NewLine, " ") ?? string.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateProperties(PropertyBridge.Properties properties){
|
public void UpdateProperties(PropertyBridge.Environment environment){
|
||||||
browser.ExecuteScriptAsync(PropertyBridge.GenerateScript(properties));
|
browser.ExecuteScriptAsync(PropertyBridge.GenerateScript(environment));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ReloadToTweetDeck(){
|
public void ReloadToTweetDeck(){
|
||||||
browser.ExecuteScriptAsync($"gc&&gc();window.location.href='{BrowserUtils.TweetDeckURL}'");
|
browser.ExecuteScriptAsync($"if(window.TDGF_reload)window.TDGF_reload();else window.location.href='{TwitterUtils.TweetDeckURL}'");
|
||||||
}
|
}
|
||||||
|
|
||||||
// callback handlers
|
// callback handlers
|
||||||
@@ -420,7 +447,7 @@ namespace TweetDuck.Core{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void OpenSettings(Type startTab){
|
public void OpenSettings(Type startTab){
|
||||||
if (!TryBringToFront<FormSettings>()){
|
if (!FormManager.TryBringToFront<FormSettings>()){
|
||||||
bool prevEnableUpdateCheck = Config.EnableUpdateCheck;
|
bool prevEnableUpdateCheck = Config.EnableUpdateCheck;
|
||||||
|
|
||||||
FormSettings form = new FormSettings(this, plugins, updates, startTab);
|
FormSettings form = new FormSettings(this, plugins, updates, startTab);
|
||||||
@@ -435,14 +462,14 @@ namespace TweetDuck.Core{
|
|||||||
trayIcon.HasNotifications = false;
|
trayIcon.HasNotifications = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Config.EnableBrowserGCReload){
|
if (Program.SystemConfig.EnableBrowserGCReload){
|
||||||
memoryUsageTracker.Start(this, browser.GetBrowser(), Config.BrowserMemoryThreshold);
|
memoryUsageTracker.Start(this, browser.GetBrowser(), Program.SystemConfig.BrowserMemoryThreshold);
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
memoryUsageTracker.Stop();
|
memoryUsageTracker.Stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateProperties(PropertyBridge.Properties.ExpandLinksOnHover | PropertyBridge.Properties.SwitchAccountSelectors | PropertyBridge.Properties.HasCustomNotificationSound);
|
UpdateProperties(PropertyBridge.Environment.Browser);
|
||||||
|
|
||||||
notification.RequiresResize = true;
|
notification.RequiresResize = true;
|
||||||
form.Dispose();
|
form.Dispose();
|
||||||
@@ -453,13 +480,13 @@ namespace TweetDuck.Core{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void OpenAbout(){
|
public void OpenAbout(){
|
||||||
if (!TryBringToFront<FormAbout>()){
|
if (!FormManager.TryBringToFront<FormAbout>()){
|
||||||
ShowChildForm(new FormAbout());
|
ShowChildForm(new FormAbout());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OpenPlugins(){
|
public void OpenPlugins(){
|
||||||
if (!TryBringToFront<FormPlugins>()){
|
if (!FormManager.TryBringToFront<FormPlugins>()){
|
||||||
ShowChildForm(new FormPlugins(plugins));
|
ShowChildForm(new FormPlugins(plugins));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -483,6 +510,37 @@ namespace TweetDuck.Core{
|
|||||||
soundNotification.Play(Config.NotificationSoundPath);
|
soundNotification.Play(Config.NotificationSoundPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void PlayVideo(string url){
|
||||||
|
if (string.IsNullOrEmpty(url)){
|
||||||
|
videoPlayer?.Close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (videoPlayer == null){
|
||||||
|
videoPlayer = new VideoPlayer(this);
|
||||||
|
|
||||||
|
videoPlayer.ProcessExited += (sender, args) => {
|
||||||
|
browser.GetBrowser().GetHost().SendFocusEvent(true);
|
||||||
|
HideVideoOverlay();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
videoPlayer.Launch(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ToggleVideoPause(){
|
||||||
|
if (videoPlayer != null && videoPlayer.Running){
|
||||||
|
videoPlayer.TogglePause();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void HideVideoOverlay(){
|
||||||
|
browser.ExecuteScriptAsync("$('#td-video-player-overlay').remove()");
|
||||||
|
}
|
||||||
|
|
||||||
public void OnTweetScreenshotReady(string html, int width, int height){
|
public void OnTweetScreenshotReady(string html, int width, int height){
|
||||||
if (notificationScreenshotManager == null){
|
if (notificationScreenshotManager == null){
|
||||||
notificationScreenshotManager = new TweetScreenshotManager(this, plugins);
|
notificationScreenshotManager = new TweetScreenshotManager(this, plugins);
|
||||||
|
25
Core/FormManager.cs
Normal file
25
Core/FormManager.cs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using TweetDuck.Core.Other;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core{
|
||||||
|
static class FormManager{
|
||||||
|
public static bool TryBringToFront<T>() where T : Form{
|
||||||
|
T form = Application.OpenForms.OfType<T>().FirstOrDefault();
|
||||||
|
|
||||||
|
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.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,35 +1,56 @@
|
|||||||
using CefSharp;
|
using System;
|
||||||
using System;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
|
using CefSharp;
|
||||||
using TweetDuck.Core.Bridge;
|
using TweetDuck.Core.Bridge;
|
||||||
using TweetDuck.Core.Controls;
|
using TweetDuck.Core.Controls;
|
||||||
using TweetDuck.Core.Other;
|
|
||||||
using TweetDuck.Core.Utils;
|
using TweetDuck.Core.Utils;
|
||||||
|
|
||||||
namespace TweetDuck.Core.Handling{
|
namespace TweetDuck.Core.Handling{
|
||||||
abstract class ContextMenuBase : IContextMenuHandler{
|
abstract class ContextMenuBase : IContextMenuHandler{
|
||||||
private static readonly Lazy<Regex> RegexTwitterAccount = new Lazy<Regex>(() => new Regex(@"^https?://twitter\.com/([^/]+)/?$", RegexOptions.Compiled), false);
|
|
||||||
protected static readonly bool HasDevTools = File.Exists(Path.Combine(Program.ProgramPath, "devtools_resources.pak"));
|
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 MenuOpenLinkUrl = 26500;
|
||||||
private const int MenuCopyLinkUrl = 26501;
|
private const int MenuCopyLinkUrl = 26501;
|
||||||
private const int MenuCopyUsername = 26502;
|
private const int MenuCopyUsername = 26502;
|
||||||
private const int MenuOpenImage = 26503;
|
private const int MenuOpenImageUrl = 26503;
|
||||||
private const int MenuSaveImage = 26504;
|
private const int MenuCopyImageUrl = 26504;
|
||||||
private const int MenuCopyImageUrl = 26505;
|
private const int MenuSaveImage = 26505;
|
||||||
|
private const int MenuSaveAllImages = 26506;
|
||||||
private const int MenuOpenDevTools = 26599;
|
private const int MenuOpenDevTools = 26599;
|
||||||
|
|
||||||
private readonly Form form;
|
private readonly Form form;
|
||||||
|
|
||||||
|
private string lastHighlightedTweetAuthor;
|
||||||
|
private string[] lastHighlightedTweetImageList;
|
||||||
|
|
||||||
protected ContextMenuBase(Form form){
|
protected ContextMenuBase(Form form){
|
||||||
this.form = form;
|
this.form = form;
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){
|
public virtual void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){
|
||||||
if (parameters.TypeFlags.HasFlag(ContextMenuType.Link) && !parameters.UnfilteredLinkUrl.EndsWith("tweetdeck.twitter.com/#", StringComparison.Ordinal)){
|
bool hasTweetImage = !string.IsNullOrEmpty(TweetDeckBridge.LastRightClickedImage);
|
||||||
if (RegexTwitterAccount.Value.IsMatch(parameters.UnfilteredLinkUrl)){
|
lastHighlightedTweetAuthor = TweetDeckBridge.LastHighlightedTweetAuthor;
|
||||||
|
lastHighlightedTweetImageList = TweetDeckBridge.LastHighlightedTweetImages;
|
||||||
|
|
||||||
|
if (!TwitterUtils.IsTweetDeckWebsite(frame) || browser.IsLoading){
|
||||||
|
lastHighlightedTweetAuthor = string.Empty;
|
||||||
|
lastHighlightedTweetImageList = StringUtils.EmptyArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parameters.TypeFlags.HasFlag(ContextMenuType.Link) && !parameters.UnfilteredLinkUrl.EndsWith("tweetdeck.twitter.com/#", StringComparison.Ordinal) && !hasTweetImage){
|
||||||
|
if (TwitterUtils.RegexAccount.IsMatch(parameters.UnfilteredLinkUrl)){
|
||||||
model.AddItem((CefMenuCommand)MenuOpenLinkUrl, "Open account in browser");
|
model.AddItem((CefMenuCommand)MenuOpenLinkUrl, "Open account in browser");
|
||||||
model.AddItem((CefMenuCommand)MenuCopyLinkUrl, "Copy account address");
|
model.AddItem((CefMenuCommand)MenuCopyLinkUrl, "Copy account address");
|
||||||
model.AddItem((CefMenuCommand)MenuCopyUsername, "Copy account username");
|
model.AddItem((CefMenuCommand)MenuCopyUsername, "Copy account username");
|
||||||
@@ -42,10 +63,15 @@ namespace TweetDuck.Core.Handling{
|
|||||||
model.AddSeparator();
|
model.AddSeparator();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parameters.TypeFlags.HasFlag(ContextMenuType.Media) && parameters.HasImageContents){
|
if ((parameters.TypeFlags.HasFlag(ContextMenuType.Media) && parameters.HasImageContents) || hasTweetImage){
|
||||||
model.AddItem((CefMenuCommand)MenuOpenImage, "Open image in browser");
|
model.AddItem((CefMenuCommand)MenuOpenImageUrl, "Open image in browser");
|
||||||
model.AddItem((CefMenuCommand)MenuSaveImage, "Save image as...");
|
|
||||||
model.AddItem((CefMenuCommand)MenuCopyImageUrl, "Copy image address");
|
model.AddItem((CefMenuCommand)MenuCopyImageUrl, "Copy image address");
|
||||||
|
model.AddItem((CefMenuCommand)MenuSaveImage, "Save image as...");
|
||||||
|
|
||||||
|
if (lastHighlightedTweetImageList.Length > 1){
|
||||||
|
model.AddItem((CefMenuCommand)MenuSaveAllImages, "Save all images as...");
|
||||||
|
}
|
||||||
|
|
||||||
model.AddSeparator();
|
model.AddSeparator();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -57,42 +83,27 @@ namespace TweetDuck.Core.Handling{
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case MenuCopyLinkUrl:
|
case MenuCopyLinkUrl:
|
||||||
SetClipboardText(string.IsNullOrEmpty(TweetDeckBridge.LastRightClickedLink) ? parameters.UnfilteredLinkUrl : TweetDeckBridge.LastRightClickedLink);
|
SetClipboardText(GetLink(parameters));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MenuOpenImage:
|
case MenuOpenImageUrl:
|
||||||
BrowserUtils.OpenExternalBrowser(parameters.SourceUrl);
|
BrowserUtils.OpenExternalBrowser(TwitterUtils.GetImageLink(GetImage(parameters), ImageQuality));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MenuSaveImage:
|
case MenuSaveImage:
|
||||||
string fileName = GetImageFileName(parameters.SourceUrl);
|
TwitterUtils.DownloadImage(GetImage(parameters), lastHighlightedTweetAuthor, ImageQuality);
|
||||||
string extension = Path.GetExtension(fileName);
|
break;
|
||||||
string saveTarget;
|
|
||||||
|
|
||||||
using(SaveFileDialog dialog = new SaveFileDialog{
|
|
||||||
AutoUpgradeEnabled = true,
|
|
||||||
OverwritePrompt = true,
|
|
||||||
Title = "Save image",
|
|
||||||
FileName = fileName,
|
|
||||||
Filter = "Image ("+(string.IsNullOrEmpty(extension) ? "unknown" : extension)+")|*.*"
|
|
||||||
}){
|
|
||||||
saveTarget = dialog.ShowDialog() == DialogResult.OK ? dialog.FileName : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (saveTarget != null){
|
|
||||||
BrowserUtils.DownloadFileAsync(parameters.SourceUrl, saveTarget, null, ex => {
|
|
||||||
FormMessage.Error("Image Download", "An error occurred while downloading the image: "+ex.Message, FormMessage.OK);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
case MenuSaveAllImages:
|
||||||
|
TwitterUtils.DownloadImages(lastHighlightedTweetImageList, lastHighlightedTweetAuthor, ImageQuality);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MenuCopyImageUrl:
|
case MenuCopyImageUrl:
|
||||||
SetClipboardText(parameters.SourceUrl);
|
SetClipboardText(TwitterUtils.GetImageLink(GetImage(parameters), ImageQuality));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MenuCopyUsername:
|
case MenuCopyUsername:
|
||||||
Match match = RegexTwitterAccount.Value.Match(parameters.UnfilteredLinkUrl);
|
Match match = TwitterUtils.RegexAccount.Match(parameters.UnfilteredLinkUrl);
|
||||||
SetClipboardText(match.Success ? match.Groups[1].Value : parameters.UnfilteredLinkUrl);
|
SetClipboardText(match.Success ? match.Groups[1].Value : parameters.UnfilteredLinkUrl);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -104,7 +115,10 @@ namespace TweetDuck.Core.Handling{
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame){}
|
public virtual void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame){
|
||||||
|
TweetDeckBridge.LastRightClickedLink = string.Empty;
|
||||||
|
TweetDeckBridge.LastRightClickedImage = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
public virtual bool RunContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model, IRunContextMenuCallback callback){
|
public virtual bool RunContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model, IRunContextMenuCallback callback){
|
||||||
return false;
|
return false;
|
||||||
@@ -129,17 +143,5 @@ namespace TweetDuck.Core.Handling{
|
|||||||
model.AddSeparator();
|
model.AddSeparator();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GetImageFileName(string url){
|
|
||||||
// twimg adds a colon after file extension
|
|
||||||
int dot = url.LastIndexOf('.');
|
|
||||||
|
|
||||||
if (dot != -1){
|
|
||||||
url = StringUtils.ExtractBefore(url, ':', dot);
|
|
||||||
}
|
|
||||||
|
|
||||||
// return file name
|
|
||||||
return BrowserUtils.GetFileNameFromUrl(url) ?? "unknown";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -49,7 +49,7 @@ namespace TweetDuck.Core.Handling{
|
|||||||
lastHighlightedTweet = TweetDeckBridge.LastHighlightedTweet;
|
lastHighlightedTweet = TweetDeckBridge.LastHighlightedTweet;
|
||||||
lastHighlightedQuotedTweet = TweetDeckBridge.LastHighlightedQuotedTweet;
|
lastHighlightedQuotedTweet = TweetDeckBridge.LastHighlightedQuotedTweet;
|
||||||
|
|
||||||
if (!BrowserUtils.IsTweetDeckWebsite(frame) || browser.IsLoading){
|
if (!TwitterUtils.IsTweetDeckWebsite(frame) || browser.IsLoading){
|
||||||
lastHighlightedTweet = string.Empty;
|
lastHighlightedTweet = string.Empty;
|
||||||
lastHighlightedQuotedTweet = string.Empty;
|
lastHighlightedQuotedTweet = string.Empty;
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
using CefSharp;
|
using System;
|
||||||
using System;
|
using CefSharp;
|
||||||
|
|
||||||
namespace TweetDuck.Core.Handling{
|
namespace TweetDuck.Core.Handling.General{
|
||||||
class BrowserProcessHandler : IBrowserProcessHandler{
|
class BrowserProcessHandler : IBrowserProcessHandler{
|
||||||
void IBrowserProcessHandler.OnContextInitialized(){
|
void IBrowserProcessHandler.OnContextInitialized(){
|
||||||
using(IRequestContext ctx = Cef.GetGlobalRequestContext()){
|
using(IRequestContext ctx = Cef.GetGlobalRequestContext()){
|
@@ -1,11 +1,12 @@
|
|||||||
using CefSharp;
|
using System.Drawing;
|
||||||
using CefSharp.WinForms;
|
|
||||||
using System.Drawing;
|
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
|
using CefSharp;
|
||||||
|
using CefSharp.WinForms;
|
||||||
using TweetDuck.Core.Controls;
|
using TweetDuck.Core.Controls;
|
||||||
using TweetDuck.Core.Other;
|
using TweetDuck.Core.Other;
|
||||||
|
using TweetDuck.Core.Utils;
|
||||||
|
|
||||||
namespace TweetDuck.Core.Handling {
|
namespace TweetDuck.Core.Handling.General{
|
||||||
class JavaScriptDialogHandler : IJsDialogHandler{
|
class JavaScriptDialogHandler : IJsDialogHandler{
|
||||||
bool IJsDialogHandler.OnJSDialog(IWebBrowser browserControl, IBrowser browser, string originUrl, CefJsDialogType dialogType, string messageText, string defaultPromptText, IJsDialogCallback callback, ref bool suppressMessage){
|
bool IJsDialogHandler.OnJSDialog(IWebBrowser browserControl, IBrowser browser, string originUrl, CefJsDialogType dialogType, string messageText, string defaultPromptText, IJsDialogCallback callback, ref bool suppressMessage){
|
||||||
((ChromiumWebBrowser)browserControl).InvokeSafe(() => {
|
((ChromiumWebBrowser)browserControl).InvokeSafe(() => {
|
||||||
@@ -13,23 +14,25 @@ namespace TweetDuck.Core.Handling {
|
|||||||
TextBox input = null;
|
TextBox input = null;
|
||||||
|
|
||||||
if (dialogType == CefJsDialogType.Alert){
|
if (dialogType == CefJsDialogType.Alert){
|
||||||
form = new FormMessage("TweetDuck Browser Message", messageText, MessageBoxIcon.None);
|
form = new FormMessage("Browser Message", messageText, MessageBoxIcon.None);
|
||||||
form.AddButton(FormMessage.OK, ControlType.Accept | ControlType.Focused);
|
form.AddButton(FormMessage.OK, ControlType.Accept | ControlType.Focused);
|
||||||
}
|
}
|
||||||
else if (dialogType == CefJsDialogType.Confirm){
|
else if (dialogType == CefJsDialogType.Confirm){
|
||||||
form = new FormMessage("TweetDuck Browser Confirmation", messageText, MessageBoxIcon.None);
|
form = new FormMessage("Browser Confirmation", messageText, MessageBoxIcon.None);
|
||||||
form.AddButton(FormMessage.No, DialogResult.No, ControlType.Cancel);
|
form.AddButton(FormMessage.No, DialogResult.No, ControlType.Cancel);
|
||||||
form.AddButton(FormMessage.Yes, ControlType.Focused);
|
form.AddButton(FormMessage.Yes, ControlType.Focused);
|
||||||
}
|
}
|
||||||
else if (dialogType == CefJsDialogType.Prompt){
|
else if (dialogType == CefJsDialogType.Prompt){
|
||||||
form = new FormMessage("TweetDuck Browser Prompt", messageText, MessageBoxIcon.None);
|
form = new FormMessage("Browser Prompt", messageText, MessageBoxIcon.None);
|
||||||
form.AddButton(FormMessage.Cancel, DialogResult.Cancel, ControlType.Cancel);
|
form.AddButton(FormMessage.Cancel, DialogResult.Cancel, ControlType.Cancel);
|
||||||
form.AddButton(FormMessage.OK, ControlType.Accept | ControlType.Focused);
|
form.AddButton(FormMessage.OK, ControlType.Accept | ControlType.Focused);
|
||||||
|
|
||||||
|
float dpiScale = form.GetDPIScale();
|
||||||
|
|
||||||
input = new TextBox{
|
input = new TextBox{
|
||||||
Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Bottom,
|
Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Bottom,
|
||||||
Location = new Point(27, form.ActionPanelY-46),
|
Location = new Point(BrowserUtils.Scale(22, dpiScale), form.ActionPanelY-BrowserUtils.Scale(46, dpiScale)),
|
||||||
Size = new Size(form.ClientSize.Width-54, 20)
|
Size = new Size(form.ClientSize.Width-BrowserUtils.Scale(44, dpiScale), 20)
|
||||||
};
|
};
|
||||||
|
|
||||||
form.Controls.Add(input);
|
form.Controls.Add(input);
|
@@ -1,7 +1,7 @@
|
|||||||
using CefSharp;
|
using CefSharp;
|
||||||
using TweetDuck.Core.Utils;
|
using TweetDuck.Core.Utils;
|
||||||
|
|
||||||
namespace TweetDuck.Core.Handling{
|
namespace TweetDuck.Core.Handling.General{
|
||||||
class LifeSpanHandler : ILifeSpanHandler{
|
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){
|
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;
|
newBrowser = null;
|
@@ -1,8 +1,8 @@
|
|||||||
using System.Security.Cryptography.X509Certificates;
|
using System.Security.Cryptography.X509Certificates;
|
||||||
using CefSharp;
|
using CefSharp;
|
||||||
|
|
||||||
namespace TweetDuck.Core.Handling{
|
namespace TweetDuck.Core.Handling.General{
|
||||||
abstract class RequestHandler : IRequestHandler{
|
abstract class RequestHandlerBase : IRequestHandler{
|
||||||
// Browser
|
// Browser
|
||||||
|
|
||||||
public virtual void OnRenderViewReady(IWebBrowser browserControl, IBrowser browser){}
|
public virtual void OnRenderViewReady(IWebBrowser browserControl, IBrowser browser){}
|
24
Core/Handling/KeyboardHandlerBrowser.cs
Normal file
24
Core/Handling/KeyboardHandlerBrowser.cs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
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){
|
||||||
|
if (type == KeyType.RawKeyDown && (Keys)windowsKeyCode == Keys.Space){
|
||||||
|
return form.ToggleVideoPause();
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IKeyboardHandler.OnKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
38
Core/Handling/KeyboardHandlerNotification.cs
Normal file
38
Core/Handling/KeyboardHandlerNotification.cs
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
using CefSharp;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using TweetDuck.Core.Controls;
|
||||||
|
using TweetDuck.Core.Notification;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.Handling {
|
||||||
|
sealed class KeyboardHandlerNotification : IKeyboardHandler{
|
||||||
|
private readonly FormNotificationBase notification;
|
||||||
|
|
||||||
|
public KeyboardHandlerNotification(FormNotificationBase notification){
|
||||||
|
this.notification = notification;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case Keys.Escape:
|
||||||
|
notification.InvokeAsyncSafe(() => notification.HideNotification(true));
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case Keys.Space:
|
||||||
|
notification.InvokeAsyncSafe(() => notification.FreezeTimer = !notification.FreezeTimer);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IKeyboardHandler.OnKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,7 +1,8 @@
|
|||||||
using CefSharp;
|
using CefSharp;
|
||||||
|
using TweetDuck.Core.Handling.General;
|
||||||
|
|
||||||
namespace TweetDuck.Core.Handling{
|
namespace TweetDuck.Core.Handling{
|
||||||
class RequestHandlerBrowser : RequestHandler{
|
class RequestHandlerBrowser : RequestHandlerBase{
|
||||||
public override void OnRenderProcessTerminated(IWebBrowser browserControl, IBrowser browser, CefTerminationStatus status){
|
public override void OnRenderProcessTerminated(IWebBrowser browserControl, IBrowser browser, CefTerminationStatus status){
|
||||||
browser.Reload();
|
browser.Reload();
|
||||||
}
|
}
|
||||||
|
@@ -6,6 +6,8 @@ using System.Windows.Forms;
|
|||||||
using TweetDuck.Configuration;
|
using TweetDuck.Configuration;
|
||||||
using TweetDuck.Core.Controls;
|
using TweetDuck.Core.Controls;
|
||||||
using TweetDuck.Core.Handling;
|
using TweetDuck.Core.Handling;
|
||||||
|
using TweetDuck.Core.Handling.General;
|
||||||
|
using TweetDuck.Core.Other.Management;
|
||||||
using TweetDuck.Core.Utils;
|
using TweetDuck.Core.Utils;
|
||||||
|
|
||||||
namespace TweetDuck.Core.Notification{
|
namespace TweetDuck.Core.Notification{
|
||||||
@@ -97,6 +99,7 @@ namespace TweetDuck.Core.Notification{
|
|||||||
|
|
||||||
this.browser = new ChromiumWebBrowser("about:blank"){
|
this.browser = new ChromiumWebBrowser("about:blank"){
|
||||||
MenuHandler = new ContextMenuNotification(this, enableContextMenu),
|
MenuHandler = new ContextMenuNotification(this, enableContextMenu),
|
||||||
|
JsDialogHandler = new JavaScriptDialogHandler(),
|
||||||
LifeSpanHandler = new LifeSpanHandler()
|
LifeSpanHandler = new LifeSpanHandler()
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -111,7 +114,7 @@ namespace TweetDuck.Core.Notification{
|
|||||||
this.dpiScale = this.GetDPIScale();
|
this.dpiScale = this.GetDPIScale();
|
||||||
|
|
||||||
DefaultResourceHandlerFactory handlerFactory = (DefaultResourceHandlerFactory)browser.ResourceHandlerFactory;
|
DefaultResourceHandlerFactory handlerFactory = (DefaultResourceHandlerFactory)browser.ResourceHandlerFactory;
|
||||||
handlerFactory.RegisterHandler(BrowserUtils.TweetDeckURL, this.resourceHandler);
|
handlerFactory.RegisterHandler(TwitterUtils.TweetDeckURL, this.resourceHandler);
|
||||||
|
|
||||||
Controls.Add(browser);
|
Controls.Add(browser);
|
||||||
|
|
||||||
@@ -183,7 +186,7 @@ namespace TweetDuck.Core.Notification{
|
|||||||
currentColumn = tweet.Column;
|
currentColumn = tweet.Column;
|
||||||
|
|
||||||
resourceHandler.SetHTML(GetTweetHTML(tweet));
|
resourceHandler.SetHTML(GetTweetHTML(tweet));
|
||||||
browser.Load(BrowserUtils.TweetDeckURL);
|
browser.Load(TwitterUtils.TweetDeckURL);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void SetNotificationSize(int width, int height){
|
protected virtual void SetNotificationSize(int width, int height){
|
||||||
|
@@ -4,6 +4,7 @@ using System.Drawing;
|
|||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using TweetDuck.Core.Bridge;
|
using TweetDuck.Core.Bridge;
|
||||||
using TweetDuck.Core.Controls;
|
using TweetDuck.Core.Controls;
|
||||||
|
using TweetDuck.Core.Handling;
|
||||||
using TweetDuck.Core.Utils;
|
using TweetDuck.Core.Utils;
|
||||||
using TweetDuck.Data;
|
using TweetDuck.Data;
|
||||||
using TweetDuck.Plugins;
|
using TweetDuck.Plugins;
|
||||||
@@ -16,10 +17,7 @@ namespace TweetDuck.Core.Notification{
|
|||||||
private const int TimerBarHeight = 4;
|
private const int TimerBarHeight = 4;
|
||||||
|
|
||||||
private static readonly string NotificationScriptIdentifier = ScriptLoader.GetRootIdentifier(NotificationScriptFile);
|
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 NotificationJS = ScriptLoader.LoadResource(NotificationScriptFile);
|
||||||
private static readonly string PluginJS = ScriptLoader.LoadResource(PluginManager.PluginNotificationScriptFile);
|
|
||||||
|
|
||||||
private readonly PluginManager plugins;
|
private readonly PluginManager plugins;
|
||||||
|
|
||||||
@@ -66,7 +64,7 @@ namespace TweetDuck.Core.Notification{
|
|||||||
get{
|
get{
|
||||||
switch(Program.UserConfig.NotificationSize){
|
switch(Program.UserConfig.NotificationSize){
|
||||||
default:
|
default:
|
||||||
return BrowserUtils.Scale(118, SizeScale*(1.0+0.075*TweetNotification.FontSizeLevel));
|
return BrowserUtils.Scale(122, SizeScale*(1.0+0.075*TweetNotification.FontSizeLevel));
|
||||||
|
|
||||||
case TweetNotification.Size.Custom:
|
case TweetNotification.Size.Custom:
|
||||||
return Program.UserConfig.CustomNotificationSize.Height;
|
return Program.UserConfig.CustomNotificationSize.Height;
|
||||||
@@ -83,6 +81,8 @@ namespace TweetDuck.Core.Notification{
|
|||||||
|
|
||||||
this.plugins = pluginManager;
|
this.plugins = pluginManager;
|
||||||
|
|
||||||
|
browser.KeyboardHandler = new KeyboardHandlerNotification(this);
|
||||||
|
|
||||||
browser.RegisterAsyncJsObject("$TD", new TweetDeckBridge(owner, this));
|
browser.RegisterAsyncJsObject("$TD", new TweetDeckBridge(owner, this));
|
||||||
browser.RegisterAsyncJsObject("$TDP", plugins.Bridge);
|
browser.RegisterAsyncJsObject("$TDP", plugins.Bridge);
|
||||||
|
|
||||||
@@ -164,14 +164,9 @@ namespace TweetDuck.Core.Notification{
|
|||||||
|
|
||||||
private void Browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
|
private void Browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
|
||||||
if (e.Frame.IsMain && NotificationJS != null && browser.Address != "about:blank"){
|
if (e.Frame.IsMain && NotificationJS != null && browser.Address != "about:blank"){
|
||||||
e.Frame.ExecuteJavaScriptAsync(PropertyBridge.GenerateScript(PropertyBridge.Properties.AllNotification));
|
e.Frame.ExecuteJavaScriptAsync(PropertyBridge.GenerateScript(PropertyBridge.Environment.Notification));
|
||||||
ScriptLoader.ExecuteScript(e.Frame, NotificationJS, NotificationScriptIdentifier);
|
ScriptLoader.ExecuteScript(e.Frame, NotificationJS, NotificationScriptIdentifier);
|
||||||
|
plugins.ExecutePlugins(e.Frame, PluginEnvironment.Notification);
|
||||||
if (plugins.HasAnyPlugin(PluginEnvironment.Notification)){
|
|
||||||
ScriptLoader.ExecuteScript(e.Frame, PluginJS, PluginScriptIdentifier);
|
|
||||||
ScriptLoader.ExecuteFile(e.Frame, PluginManager.PluginGlobalScriptFile);
|
|
||||||
plugins.ExecutePlugins(e.Frame, PluginEnvironment.Notification, false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Drawing.Imaging;
|
using System.Drawing.Imaging;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
|
using CefSharp;
|
||||||
using TweetDuck.Core.Bridge;
|
using TweetDuck.Core.Bridge;
|
||||||
using TweetDuck.Core.Other;
|
using TweetDuck.Core.Other;
|
||||||
using TweetDuck.Core.Utils;
|
using TweetDuck.Core.Utils;
|
||||||
@@ -18,9 +19,11 @@ namespace TweetDuck.Core.Notification.Screenshot{
|
|||||||
|
|
||||||
browser.RegisterAsyncJsObject("$TD_NotificationScreenshot", new CallbackBridge(this, callback));
|
browser.RegisterAsyncJsObject("$TD_NotificationScreenshot", new CallbackBridge(this, callback));
|
||||||
|
|
||||||
browser.FrameLoadEnd += (sender, args) => {
|
browser.LoadingStateChanged += (sender, args) => {
|
||||||
if (args.Frame.IsMain && browser.Address != "about:blank"){
|
if (!args.IsLoading){
|
||||||
ScriptLoader.ExecuteScript(args.Frame, "window.setTimeout($TD_NotificationScreenshot.trigger, 67)", "gen:screenshot");
|
using(IFrame frame = args.Browser.MainFrame){
|
||||||
|
ScriptLoader.ExecuteScript(frame, "window.setTimeout($TD_NotificationScreenshot.trigger, 129)", "gen:screenshot");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@@ -8,14 +8,14 @@ using TweetDuck.Plugins;
|
|||||||
|
|
||||||
namespace TweetDuck.Core.Notification.Screenshot{
|
namespace TweetDuck.Core.Notification.Screenshot{
|
||||||
sealed class TweetScreenshotManager : IDisposable{
|
sealed class TweetScreenshotManager : IDisposable{
|
||||||
private readonly Form owner;
|
private readonly FormBrowser owner;
|
||||||
private readonly PluginManager plugins;
|
private readonly PluginManager plugins;
|
||||||
private readonly Timer timeout;
|
private readonly Timer timeout;
|
||||||
private readonly Timer disposer;
|
private readonly Timer disposer;
|
||||||
|
|
||||||
private FormNotificationScreenshotable screenshot;
|
private FormNotificationScreenshotable screenshot;
|
||||||
|
|
||||||
public TweetScreenshotManager(Form owner, PluginManager pluginManager){
|
public TweetScreenshotManager(FormBrowser owner, PluginManager pluginManager){
|
||||||
this.owner = owner;
|
this.owner = owner;
|
||||||
this.plugins = pluginManager;
|
this.plugins = pluginManager;
|
||||||
|
|
||||||
@@ -28,8 +28,7 @@ namespace TweetDuck.Core.Notification.Screenshot{
|
|||||||
|
|
||||||
private void timeout_Tick(object sender, EventArgs e){
|
private void timeout_Tick(object sender, EventArgs e){
|
||||||
timeout.Stop();
|
timeout.Stop();
|
||||||
screenshot.Location = ControlExtensions.InvisibleLocation;
|
OnFinished();
|
||||||
disposer.Start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void disposer_Tick(object sender, EventArgs e){
|
private void disposer_Tick(object sender, EventArgs e){
|
||||||
@@ -50,6 +49,10 @@ namespace TweetDuck.Core.Notification.Screenshot{
|
|||||||
screenshot.LoadNotificationForScreenshot(new TweetNotification(string.Empty, html, 0, string.Empty, string.Empty), width, height);
|
screenshot.LoadNotificationForScreenshot(new TweetNotification(string.Empty, html, 0, string.Empty, string.Empty), width, height);
|
||||||
screenshot.Show();
|
screenshot.Show();
|
||||||
timeout.Start();
|
timeout.Start();
|
||||||
|
|
||||||
|
#if !(DEBUG && NO_HIDE_SCREENSHOTS)
|
||||||
|
owner.IsWaiting = true;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Callback(){
|
private void Callback(){
|
||||||
@@ -61,14 +64,19 @@ namespace TweetDuck.Core.Notification.Screenshot{
|
|||||||
screenshot.TakeScreenshot();
|
screenshot.TakeScreenshot();
|
||||||
|
|
||||||
#if !(DEBUG && NO_HIDE_SCREENSHOTS)
|
#if !(DEBUG && NO_HIDE_SCREENSHOTS)
|
||||||
screenshot.Location = ControlExtensions.InvisibleLocation;
|
OnFinished();
|
||||||
disposer.Start();
|
|
||||||
#else
|
#else
|
||||||
screenshot.MoveToVisibleLocation();
|
screenshot.MoveToVisibleLocation();
|
||||||
screenshot.FormClosed += (sender, args) => disposer.Start();
|
screenshot.FormClosed += (sender, args) => disposer.Start();
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnFinished(){
|
||||||
|
screenshot.Location = ControlExtensions.InvisibleLocation;
|
||||||
|
owner.IsWaiting = false;
|
||||||
|
disposer.Start();
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose(){
|
public void Dispose(){
|
||||||
timeout.Dispose();
|
timeout.Dispose();
|
||||||
disposer.Dispose();
|
disposer.Dispose();
|
||||||
|
@@ -9,7 +9,7 @@ namespace TweetDuck.Core.Notification{
|
|||||||
|
|
||||||
private const string DefaultFontSizeClass = "medium";
|
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 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}";
|
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{
|
public static int FontSizeLevel{
|
||||||
get{
|
get{
|
||||||
|
@@ -87,7 +87,7 @@ namespace TweetDuck.Core.Other{
|
|||||||
|
|
||||||
this.prevLabelWidth = labelMessage.Width;
|
this.prevLabelWidth = labelMessage.Width;
|
||||||
this.prevLabelHeight = labelMessage.Height;
|
this.prevLabelHeight = labelMessage.Height;
|
||||||
this.minFormWidth = BrowserUtils.Scale(40, dpiScale);
|
this.minFormWidth = BrowserUtils.Scale(42, dpiScale);
|
||||||
|
|
||||||
switch(messageIcon){
|
switch(messageIcon){
|
||||||
case MessageBoxIcon.Information:
|
case MessageBoxIcon.Information:
|
||||||
@@ -108,7 +108,7 @@ namespace TweetDuck.Core.Other{
|
|||||||
|
|
||||||
default:
|
default:
|
||||||
icon = null;
|
icon = null;
|
||||||
labelMessage.Location = new Point(labelMessage.Location.X-38, labelMessage.Location.Y);
|
labelMessage.Location = new Point(BrowserUtils.Scale(19, dpiScale), labelMessage.Location.Y); // 19 instead of 9 due to larger height
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,7 +176,7 @@ namespace TweetDuck.Core.Other{
|
|||||||
|
|
||||||
private void RecalculateButtonLocation(){
|
private void RecalculateButtonLocation(){
|
||||||
int dist = ButtonDistance;
|
int dist = ButtonDistance;
|
||||||
int start = ClientWidth-dist-BrowserUtils.Scale(1, dpiScale);
|
int start = ClientWidth-dist;
|
||||||
|
|
||||||
for(int index = 0; index < buttonCount; index++){
|
for(int index = 0; index < buttonCount; index++){
|
||||||
Control control = panelActions.Controls[index];
|
Control control = panelActions.Controls[index];
|
||||||
@@ -201,7 +201,7 @@ namespace TweetDuck.Core.Other{
|
|||||||
prevLabelHeight -= labelOffset;
|
prevLabelHeight -= labelOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
realFormWidth = ClientWidth-(icon == null ? 50 : 0)+labelMessage.Width-prevLabelWidth;
|
realFormWidth = ClientWidth-(icon == null ? BrowserUtils.Scale(50, dpiScale) : 0)+labelMessage.Width-prevLabelWidth;
|
||||||
ClientWidth = Math.Max(realFormWidth, minFormWidth);
|
ClientWidth = Math.Max(realFormWidth, minFormWidth);
|
||||||
Height += labelMessage.Height-prevLabelHeight;
|
Height += labelMessage.Height-prevLabelHeight;
|
||||||
|
|
||||||
@@ -212,7 +212,7 @@ namespace TweetDuck.Core.Other{
|
|||||||
|
|
||||||
protected override void OnPaint(PaintEventArgs e){
|
protected override void OnPaint(PaintEventArgs e){
|
||||||
if (icon != null){
|
if (icon != null){
|
||||||
e.Graphics.DrawIcon(icon, BrowserUtils.Scale(25, dpiScale), BrowserUtils.Scale(26, dpiScale));
|
e.Graphics.DrawIcon(icon, BrowserUtils.Scale(25, dpiScale), 1+BrowserUtils.Scale(25, dpiScale));
|
||||||
}
|
}
|
||||||
|
|
||||||
base.OnPaint(e);
|
base.OnPaint(e);
|
||||||
|
@@ -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,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.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using CefSharp;
|
using CefSharp;
|
||||||
|
using TweetDuck.Core.Utils;
|
||||||
|
|
||||||
namespace TweetDuck.Core.Utils{
|
namespace TweetDuck.Core.Other.Management{
|
||||||
static class BrowserProcesses{
|
static class BrowserProcesses{
|
||||||
private static readonly Dictionary<int, int> PIDs = new Dictionary<int, int>();
|
private static readonly Dictionary<int, int> PIDs = new Dictionary<int, int>();
|
||||||
|
|
@@ -5,7 +5,7 @@ using System.Windows.Forms;
|
|||||||
using CefSharp;
|
using CefSharp;
|
||||||
using Timer = System.Timers.Timer;
|
using Timer = System.Timers.Timer;
|
||||||
|
|
||||||
namespace TweetDuck.Core.Utils{
|
namespace TweetDuck.Core.Other.Management{
|
||||||
sealed class MemoryUsageTracker : IDisposable{
|
sealed class MemoryUsageTracker : IDisposable{
|
||||||
private const int IntervalMemoryCheck = 60000*30; // 30 minutes
|
private const int IntervalMemoryCheck = 60000*30; // 30 minutes
|
||||||
private const int IntervalCleanupAttempt = 60000*5; // 5 minutes
|
private const int IntervalCleanupAttempt = 60000*5; // 5 minutes
|
175
Core/Other/Management/VideoPlayer.cs
Normal file
175
Core/Other/Management/VideoPlayer.cs
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
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{
|
||||||
|
private readonly string PlayerExe = Path.Combine(Program.ProgramPath, "TweetDuck.Video.exe");
|
||||||
|
|
||||||
|
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 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){
|
||||||
|
if (Running){
|
||||||
|
Destroy();
|
||||||
|
isClosing = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastUrl = url;
|
||||||
|
|
||||||
|
try{
|
||||||
|
currentPipe = DuplexPipe.CreateServer();
|
||||||
|
currentPipe.DataIn += currentPipe_DataIn;
|
||||||
|
|
||||||
|
if ((currentProcess = Process.Start(new ProcessStartInfo{
|
||||||
|
FileName = PlayerExe,
|
||||||
|
Arguments = $"{owner.Handle} {Program.UserConfig.VideoPlayerVolume} \"{url}\" \"{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 TogglePause(){
|
||||||
|
currentPipe?.Write("pause");
|
||||||
|
}
|
||||||
|
|
||||||
|
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 "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){
|
||||||
|
switch(currentProcess.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 a 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 a browser?", FormMessage.Yes, FormMessage.No)){
|
||||||
|
BrowserUtils.OpenExternalBrowser(lastUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentProcess.Dispose();
|
||||||
|
currentProcess = null;
|
||||||
|
|
||||||
|
currentPipe.Dispose();
|
||||||
|
currentPipe = null;
|
||||||
|
|
||||||
|
owner.InvokeAsyncSafe(TriggerProcessExitEventUnsafe);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TriggerProcessExitEventUnsafe(){
|
||||||
|
ProcessExited?.Invoke(this, new EventArgs());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -2,6 +2,7 @@
|
|||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using TweetDuck.Core.Controls;
|
using TweetDuck.Core.Controls;
|
||||||
using TweetDuck.Core.Utils;
|
using TweetDuck.Core.Utils;
|
||||||
|
using TweetDuck.Data;
|
||||||
|
|
||||||
namespace TweetDuck.Core.Other.Settings.Dialogs{
|
namespace TweetDuck.Core.Other.Settings.Dialogs{
|
||||||
sealed partial class DialogSettingsCefArgs : Form{
|
sealed partial class DialogSettingsCefArgs : Form{
|
||||||
@@ -30,7 +31,7 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int count = CommandLineArgsParser.ReadCefArguments(CefArgs).Count;
|
int count = CommandLineArgs.ReadCefArguments(CefArgs).Count;
|
||||||
string prompt = count == 0 && !string.IsNullOrWhiteSpace(prevArgs) ? "All current arguments will be removed. Continue?" : count+(count == 1 ? " argument was" : " arguments were")+" detected. Continue?";
|
string prompt = count == 0 && !string.IsNullOrWhiteSpace(prevArgs) ? "All current arguments will be removed. Continue?" : count+(count == 1 ? " argument was" : " arguments were")+" detected. Continue?";
|
||||||
|
|
||||||
if (FormMessage.Question("Confirm CEF Arguments", prompt, FormMessage.OK, FormMessage.Cancel)){
|
if (FormMessage.Question("Confirm CEF Arguments", prompt, FormMessage.OK, FormMessage.Cancel)){
|
||||||
|
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.Size = new System.Drawing.Size(134, 17);
|
||||||
this.checkHardwareAcceleration.TabIndex = 0;
|
this.checkHardwareAcceleration.TabIndex = 0;
|
||||||
this.checkHardwareAcceleration.Text = "Hardware Acceleration";
|
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;
|
this.checkHardwareAcceleration.UseVisualStyleBackColor = true;
|
||||||
//
|
//
|
||||||
// btnEditCefArgs
|
// btnEditCefArgs
|
||||||
@@ -142,26 +142,14 @@
|
|||||||
0,
|
0,
|
||||||
0});
|
0});
|
||||||
this.numMemoryThreshold.Location = new System.Drawing.Point(202, 82);
|
this.numMemoryThreshold.Location = new System.Drawing.Point(202, 82);
|
||||||
this.numMemoryThreshold.Maximum = new decimal(new int[] {
|
this.numMemoryThreshold.Maximum = 2000;
|
||||||
3000,
|
this.numMemoryThreshold.Minimum = 200;
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0});
|
|
||||||
this.numMemoryThreshold.Minimum = new decimal(new int[] {
|
|
||||||
200,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0});
|
|
||||||
this.numMemoryThreshold.Name = "numMemoryThreshold";
|
this.numMemoryThreshold.Name = "numMemoryThreshold";
|
||||||
this.numMemoryThreshold.Size = new System.Drawing.Size(97, 20);
|
this.numMemoryThreshold.Size = new System.Drawing.Size(97, 20);
|
||||||
this.numMemoryThreshold.TabIndex = 4;
|
this.numMemoryThreshold.TabIndex = 4;
|
||||||
this.numMemoryThreshold.TextSuffix = " MB";
|
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.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[] {
|
this.numMemoryThreshold.Value = 400;
|
||||||
350,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0});
|
|
||||||
//
|
//
|
||||||
// checkBrowserGCReload
|
// checkBrowserGCReload
|
||||||
//
|
//
|
||||||
@@ -172,7 +160,7 @@
|
|||||||
this.checkBrowserGCReload.Size = new System.Drawing.Size(190, 17);
|
this.checkBrowserGCReload.Size = new System.Drawing.Size(190, 17);
|
||||||
this.checkBrowserGCReload.TabIndex = 3;
|
this.checkBrowserGCReload.TabIndex = 3;
|
||||||
this.checkBrowserGCReload.Text = "Enable Browser Memory Threshold";
|
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;
|
this.checkBrowserGCReload.UseVisualStyleBackColor = true;
|
||||||
//
|
//
|
||||||
// labelApp
|
// labelApp
|
||||||
|
@@ -8,6 +8,8 @@ using TweetDuck.Core.Utils;
|
|||||||
|
|
||||||
namespace TweetDuck.Core.Other.Settings{
|
namespace TweetDuck.Core.Other.Settings{
|
||||||
partial class TabSettingsAdvanced : BaseTabSettings{
|
partial class TabSettingsAdvanced : BaseTabSettings{
|
||||||
|
private static SystemConfig SysConfig => Program.SystemConfig;
|
||||||
|
|
||||||
private readonly Action<string> reinjectBrowserCSS;
|
private readonly Action<string> reinjectBrowserCSS;
|
||||||
|
|
||||||
public TabSettingsAdvanced(Action<string> reinjectBrowserCSS){
|
public TabSettingsAdvanced(Action<string> reinjectBrowserCSS){
|
||||||
@@ -16,16 +18,16 @@ namespace TweetDuck.Core.Other.Settings{
|
|||||||
this.reinjectBrowserCSS = reinjectBrowserCSS;
|
this.reinjectBrowserCSS = reinjectBrowserCSS;
|
||||||
|
|
||||||
if (SystemConfig.IsHardwareAccelerationSupported){
|
if (SystemConfig.IsHardwareAccelerationSupported){
|
||||||
checkHardwareAcceleration.Checked = Program.SystemConfig.HardwareAcceleration;
|
checkHardwareAcceleration.Checked = SysConfig.HardwareAcceleration;
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
checkHardwareAcceleration.Enabled = false;
|
checkHardwareAcceleration.Enabled = false;
|
||||||
checkHardwareAcceleration.Checked = false;
|
checkHardwareAcceleration.Checked = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
checkBrowserGCReload.Checked = Config.EnableBrowserGCReload;
|
checkBrowserGCReload.Checked = SysConfig.EnableBrowserGCReload;
|
||||||
numMemoryThreshold.Enabled = checkBrowserGCReload.Checked;
|
numMemoryThreshold.Enabled = checkBrowserGCReload.Checked;
|
||||||
numMemoryThreshold.SetValueSafe(Config.BrowserMemoryThreshold);
|
numMemoryThreshold.SetValueSafe(SysConfig.BrowserMemoryThreshold);
|
||||||
|
|
||||||
BrowserCache.CalculateCacheSize(bytes => this.InvokeSafe(() => {
|
BrowserCache.CalculateCacheSize(bytes => this.InvokeSafe(() => {
|
||||||
if (bytes == -1L){
|
if (bytes == -1L){
|
||||||
@@ -53,6 +55,10 @@ namespace TweetDuck.Core.Other.Settings{
|
|||||||
btnRestartArgs.Click += btnRestartArgs_Click;
|
btnRestartArgs.Click += btnRestartArgs_Click;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void OnClosing(){
|
||||||
|
SysConfig.Save();
|
||||||
|
}
|
||||||
|
|
||||||
private void btnClearCache_Click(object sender, EventArgs e){
|
private void btnClearCache_Click(object sender, EventArgs e){
|
||||||
btnClearCache.Enabled = false;
|
btnClearCache.Enabled = false;
|
||||||
BrowserCache.SetClearOnExit();
|
BrowserCache.SetClearOnExit();
|
||||||
@@ -60,18 +66,17 @@ namespace TweetDuck.Core.Other.Settings{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void checkHardwareAcceleration_CheckedChanged(object sender, EventArgs e){
|
private void checkHardwareAcceleration_CheckedChanged(object sender, EventArgs e){
|
||||||
Program.SystemConfig.HardwareAcceleration = checkHardwareAcceleration.Checked;
|
SysConfig.HardwareAcceleration = checkHardwareAcceleration.Checked;
|
||||||
Program.SystemConfig.Save();
|
PromptRestart(); // calls OnClosing
|
||||||
PromptRestart();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkBrowserGCReload_CheckedChanged(object sender, EventArgs e){
|
private void checkBrowserGCReload_CheckedChanged(object sender, EventArgs e){
|
||||||
Config.EnableBrowserGCReload = checkBrowserGCReload.Checked;
|
SysConfig.EnableBrowserGCReload = checkBrowserGCReload.Checked;
|
||||||
numMemoryThreshold.Enabled = checkBrowserGCReload.Checked;
|
numMemoryThreshold.Enabled = checkBrowserGCReload.Checked;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void numMemoryThreshold_ValueChanged(object sender, EventArgs e){
|
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){
|
private void btnEditCefArgs_Click(object sender, EventArgs e){
|
||||||
@@ -82,7 +87,7 @@ namespace TweetDuck.Core.Other.Settings{
|
|||||||
};
|
};
|
||||||
|
|
||||||
form.FormClosed += (sender2, args2) => {
|
form.FormClosed += (sender2, args2) => {
|
||||||
NativeMethods.SetFormDisabled(ParentForm, false);
|
RestoreParentForm();
|
||||||
|
|
||||||
if (form.DialogResult == DialogResult.OK){
|
if (form.DialogResult == DialogResult.OK){
|
||||||
Config.CustomCefArgs = form.CefArgs;
|
Config.CustomCefArgs = form.CefArgs;
|
||||||
@@ -104,7 +109,7 @@ namespace TweetDuck.Core.Other.Settings{
|
|||||||
};
|
};
|
||||||
|
|
||||||
form.FormClosed += (sender2, args2) => {
|
form.FormClosed += (sender2, args2) => {
|
||||||
NativeMethods.SetFormDisabled(ParentForm, false);
|
RestoreParentForm();
|
||||||
|
|
||||||
if (form.DialogResult == DialogResult.OK){
|
if (form.DialogResult == DialogResult.OK){
|
||||||
Config.CustomBrowserCSS = form.BrowserCSS;
|
Config.CustomBrowserCSS = form.BrowserCSS;
|
||||||
@@ -138,5 +143,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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
43
Core/Other/Settings/TabSettingsGeneral.Designer.cs
generated
43
Core/Other/Settings/TabSettingsGeneral.Designer.cs
generated
@@ -43,6 +43,7 @@
|
|||||||
this.panelUpdates = new System.Windows.Forms.Panel();
|
this.panelUpdates = new System.Windows.Forms.Panel();
|
||||||
this.panelTray = new System.Windows.Forms.Panel();
|
this.panelTray = new System.Windows.Forms.Panel();
|
||||||
this.labelUpdates = new System.Windows.Forms.Label();
|
this.labelUpdates = new System.Windows.Forms.Label();
|
||||||
|
this.checkBestImageQuality = new System.Windows.Forms.CheckBox();
|
||||||
((System.ComponentModel.ISupportInitialize)(this.trackBarZoom)).BeginInit();
|
((System.ComponentModel.ISupportInitialize)(this.trackBarZoom)).BeginInit();
|
||||||
this.panelUI.SuspendLayout();
|
this.panelUI.SuspendLayout();
|
||||||
this.panelUpdates.SuspendLayout();
|
this.panelUpdates.SuspendLayout();
|
||||||
@@ -87,11 +88,11 @@
|
|||||||
// checkSpellCheck
|
// checkSpellCheck
|
||||||
//
|
//
|
||||||
this.checkSpellCheck.AutoSize = true;
|
this.checkSpellCheck.AutoSize = true;
|
||||||
this.checkSpellCheck.Location = new System.Drawing.Point(6, 51);
|
this.checkSpellCheck.Location = new System.Drawing.Point(6, 74);
|
||||||
this.checkSpellCheck.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
|
this.checkSpellCheck.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
|
||||||
this.checkSpellCheck.Name = "checkSpellCheck";
|
this.checkSpellCheck.Name = "checkSpellCheck";
|
||||||
this.checkSpellCheck.Size = new System.Drawing.Size(119, 17);
|
this.checkSpellCheck.Size = new System.Drawing.Size(119, 17);
|
||||||
this.checkSpellCheck.TabIndex = 2;
|
this.checkSpellCheck.TabIndex = 3;
|
||||||
this.checkSpellCheck.Text = "Enable Spell Check";
|
this.checkSpellCheck.Text = "Enable Spell Check";
|
||||||
this.toolTip.SetToolTip(this.checkSpellCheck, "Underlines words that are spelled incorrectly.");
|
this.toolTip.SetToolTip(this.checkSpellCheck, "Underlines words that are spelled incorrectly.");
|
||||||
this.checkSpellCheck.UseVisualStyleBackColor = true;
|
this.checkSpellCheck.UseVisualStyleBackColor = true;
|
||||||
@@ -122,11 +123,11 @@
|
|||||||
// labelZoomValue
|
// labelZoomValue
|
||||||
//
|
//
|
||||||
this.labelZoomValue.BackColor = System.Drawing.Color.Transparent;
|
this.labelZoomValue.BackColor = System.Drawing.Color.Transparent;
|
||||||
this.labelZoomValue.Location = new System.Drawing.Point(141, 100);
|
this.labelZoomValue.Location = new System.Drawing.Point(141, 123);
|
||||||
this.labelZoomValue.Margin = new System.Windows.Forms.Padding(0, 0, 3, 0);
|
this.labelZoomValue.Margin = new System.Windows.Forms.Padding(0, 0, 3, 0);
|
||||||
this.labelZoomValue.Name = "labelZoomValue";
|
this.labelZoomValue.Name = "labelZoomValue";
|
||||||
this.labelZoomValue.Size = new System.Drawing.Size(38, 13);
|
this.labelZoomValue.Size = new System.Drawing.Size(38, 13);
|
||||||
this.labelZoomValue.TabIndex = 5;
|
this.labelZoomValue.TabIndex = 6;
|
||||||
this.labelZoomValue.Text = "100%";
|
this.labelZoomValue.Text = "100%";
|
||||||
this.labelZoomValue.TextAlign = System.Drawing.ContentAlignment.TopRight;
|
this.labelZoomValue.TextAlign = System.Drawing.ContentAlignment.TopRight;
|
||||||
this.toolTip.SetToolTip(this.labelZoomValue, "Changes the zoom level.\r\nAlso affects notifications and screenshots.");
|
this.toolTip.SetToolTip(this.labelZoomValue, "Changes the zoom level.\r\nAlso affects notifications and screenshots.");
|
||||||
@@ -158,24 +159,24 @@
|
|||||||
this.trackBarZoom.AutoSize = false;
|
this.trackBarZoom.AutoSize = false;
|
||||||
this.trackBarZoom.BackColor = System.Drawing.SystemColors.Control;
|
this.trackBarZoom.BackColor = System.Drawing.SystemColors.Control;
|
||||||
this.trackBarZoom.LargeChange = 25;
|
this.trackBarZoom.LargeChange = 25;
|
||||||
this.trackBarZoom.Location = new System.Drawing.Point(3, 99);
|
this.trackBarZoom.Location = new System.Drawing.Point(3, 122);
|
||||||
this.trackBarZoom.Maximum = 200;
|
this.trackBarZoom.Maximum = 200;
|
||||||
this.trackBarZoom.Minimum = 50;
|
this.trackBarZoom.Minimum = 50;
|
||||||
this.trackBarZoom.Name = "trackBarZoom";
|
this.trackBarZoom.Name = "trackBarZoom";
|
||||||
this.trackBarZoom.Size = new System.Drawing.Size(148, 30);
|
this.trackBarZoom.Size = new System.Drawing.Size(148, 30);
|
||||||
this.trackBarZoom.SmallChange = 5;
|
this.trackBarZoom.SmallChange = 5;
|
||||||
this.trackBarZoom.TabIndex = 4;
|
this.trackBarZoom.TabIndex = 5;
|
||||||
this.trackBarZoom.TickFrequency = 25;
|
this.trackBarZoom.TickFrequency = 25;
|
||||||
this.trackBarZoom.Value = 100;
|
this.trackBarZoom.Value = 100;
|
||||||
//
|
//
|
||||||
// labelZoom
|
// labelZoom
|
||||||
//
|
//
|
||||||
this.labelZoom.AutoSize = true;
|
this.labelZoom.AutoSize = true;
|
||||||
this.labelZoom.Location = new System.Drawing.Point(3, 83);
|
this.labelZoom.Location = new System.Drawing.Point(3, 106);
|
||||||
this.labelZoom.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
|
this.labelZoom.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
|
||||||
this.labelZoom.Name = "labelZoom";
|
this.labelZoom.Name = "labelZoom";
|
||||||
this.labelZoom.Size = new System.Drawing.Size(34, 13);
|
this.labelZoom.Size = new System.Drawing.Size(34, 13);
|
||||||
this.labelZoom.TabIndex = 3;
|
this.labelZoom.TabIndex = 4;
|
||||||
this.labelZoom.Text = "Zoom";
|
this.labelZoom.Text = "Zoom";
|
||||||
//
|
//
|
||||||
// zoomUpdateTimer
|
// zoomUpdateTimer
|
||||||
@@ -198,6 +199,7 @@
|
|||||||
//
|
//
|
||||||
this.panelUI.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
this.panelUI.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
||||||
| System.Windows.Forms.AnchorStyles.Right)));
|
| System.Windows.Forms.AnchorStyles.Right)));
|
||||||
|
this.panelUI.Controls.Add(this.checkBestImageQuality);
|
||||||
this.panelUI.Controls.Add(this.checkExpandLinks);
|
this.panelUI.Controls.Add(this.checkExpandLinks);
|
||||||
this.panelUI.Controls.Add(this.checkSwitchAccountSelectors);
|
this.panelUI.Controls.Add(this.checkSwitchAccountSelectors);
|
||||||
this.panelUI.Controls.Add(this.checkSpellCheck);
|
this.panelUI.Controls.Add(this.checkSpellCheck);
|
||||||
@@ -206,14 +208,14 @@
|
|||||||
this.panelUI.Controls.Add(this.trackBarZoom);
|
this.panelUI.Controls.Add(this.trackBarZoom);
|
||||||
this.panelUI.Location = new System.Drawing.Point(9, 31);
|
this.panelUI.Location = new System.Drawing.Point(9, 31);
|
||||||
this.panelUI.Name = "panelUI";
|
this.panelUI.Name = "panelUI";
|
||||||
this.panelUI.Size = new System.Drawing.Size(322, 134);
|
this.panelUI.Size = new System.Drawing.Size(322, 157);
|
||||||
this.panelUI.TabIndex = 1;
|
this.panelUI.TabIndex = 1;
|
||||||
//
|
//
|
||||||
// labelTray
|
// labelTray
|
||||||
//
|
//
|
||||||
this.labelTray.AutoSize = true;
|
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.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(5, 189);
|
this.labelTray.Location = new System.Drawing.Point(6, 212);
|
||||||
this.labelTray.Margin = new System.Windows.Forms.Padding(0, 21, 0, 0);
|
this.labelTray.Margin = new System.Windows.Forms.Padding(0, 21, 0, 0);
|
||||||
this.labelTray.Name = "labelTray";
|
this.labelTray.Name = "labelTray";
|
||||||
this.labelTray.Size = new System.Drawing.Size(96, 20);
|
this.labelTray.Size = new System.Drawing.Size(96, 20);
|
||||||
@@ -226,7 +228,7 @@
|
|||||||
| System.Windows.Forms.AnchorStyles.Right)));
|
| System.Windows.Forms.AnchorStyles.Right)));
|
||||||
this.panelUpdates.Controls.Add(this.checkUpdateNotifications);
|
this.panelUpdates.Controls.Add(this.checkUpdateNotifications);
|
||||||
this.panelUpdates.Controls.Add(this.btnCheckUpdates);
|
this.panelUpdates.Controls.Add(this.btnCheckUpdates);
|
||||||
this.panelUpdates.Location = new System.Drawing.Point(9, 335);
|
this.panelUpdates.Location = new System.Drawing.Point(9, 358);
|
||||||
this.panelUpdates.Name = "panelUpdates";
|
this.panelUpdates.Name = "panelUpdates";
|
||||||
this.panelUpdates.Size = new System.Drawing.Size(322, 55);
|
this.panelUpdates.Size = new System.Drawing.Size(322, 55);
|
||||||
this.panelUpdates.TabIndex = 5;
|
this.panelUpdates.TabIndex = 5;
|
||||||
@@ -238,7 +240,7 @@
|
|||||||
this.panelTray.Controls.Add(this.checkTrayHighlight);
|
this.panelTray.Controls.Add(this.checkTrayHighlight);
|
||||||
this.panelTray.Controls.Add(this.comboBoxTrayType);
|
this.panelTray.Controls.Add(this.comboBoxTrayType);
|
||||||
this.panelTray.Controls.Add(this.labelTrayIcon);
|
this.panelTray.Controls.Add(this.labelTrayIcon);
|
||||||
this.panelTray.Location = new System.Drawing.Point(9, 212);
|
this.panelTray.Location = new System.Drawing.Point(9, 235);
|
||||||
this.panelTray.Name = "panelTray";
|
this.panelTray.Name = "panelTray";
|
||||||
this.panelTray.Size = new System.Drawing.Size(322, 76);
|
this.panelTray.Size = new System.Drawing.Size(322, 76);
|
||||||
this.panelTray.TabIndex = 3;
|
this.panelTray.TabIndex = 3;
|
||||||
@@ -247,13 +249,25 @@
|
|||||||
//
|
//
|
||||||
this.labelUpdates.AutoSize = true;
|
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.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, 312);
|
this.labelUpdates.Location = new System.Drawing.Point(6, 335);
|
||||||
this.labelUpdates.Margin = new System.Windows.Forms.Padding(0, 21, 0, 0);
|
this.labelUpdates.Margin = new System.Windows.Forms.Padding(0, 21, 0, 0);
|
||||||
this.labelUpdates.Name = "labelUpdates";
|
this.labelUpdates.Name = "labelUpdates";
|
||||||
this.labelUpdates.Size = new System.Drawing.Size(70, 20);
|
this.labelUpdates.Size = new System.Drawing.Size(70, 20);
|
||||||
this.labelUpdates.TabIndex = 4;
|
this.labelUpdates.TabIndex = 4;
|
||||||
this.labelUpdates.Text = "Updates";
|
this.labelUpdates.Text = "Updates";
|
||||||
//
|
//
|
||||||
|
// checkBestImageQuality
|
||||||
|
//
|
||||||
|
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;
|
||||||
|
//
|
||||||
// TabSettingsGeneral
|
// TabSettingsGeneral
|
||||||
//
|
//
|
||||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||||
@@ -265,7 +279,7 @@
|
|||||||
this.Controls.Add(this.panelUI);
|
this.Controls.Add(this.panelUI);
|
||||||
this.Controls.Add(this.labelUI);
|
this.Controls.Add(this.labelUI);
|
||||||
this.Name = "TabSettingsGeneral";
|
this.Name = "TabSettingsGeneral";
|
||||||
this.Size = new System.Drawing.Size(340, 400);
|
this.Size = new System.Drawing.Size(340, 422);
|
||||||
((System.ComponentModel.ISupportInitialize)(this.trackBarZoom)).EndInit();
|
((System.ComponentModel.ISupportInitialize)(this.trackBarZoom)).EndInit();
|
||||||
this.panelUI.ResumeLayout(false);
|
this.panelUI.ResumeLayout(false);
|
||||||
this.panelUI.PerformLayout();
|
this.panelUI.PerformLayout();
|
||||||
@@ -299,5 +313,6 @@
|
|||||||
private System.Windows.Forms.Panel panelUpdates;
|
private System.Windows.Forms.Panel panelUpdates;
|
||||||
private System.Windows.Forms.Panel panelTray;
|
private System.Windows.Forms.Panel panelTray;
|
||||||
private System.Windows.Forms.Label labelUpdates;
|
private System.Windows.Forms.Label labelUpdates;
|
||||||
|
private System.Windows.Forms.CheckBox checkBestImageQuality;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -28,6 +28,7 @@ namespace TweetDuck.Core.Other.Settings{
|
|||||||
|
|
||||||
checkExpandLinks.Checked = Config.ExpandLinksOnHover;
|
checkExpandLinks.Checked = Config.ExpandLinksOnHover;
|
||||||
checkSwitchAccountSelectors.Checked = Config.SwitchAccountSelectors;
|
checkSwitchAccountSelectors.Checked = Config.SwitchAccountSelectors;
|
||||||
|
checkBestImageQuality.Checked = Config.BestImageQuality;
|
||||||
checkSpellCheck.Checked = Config.EnableSpellCheck;
|
checkSpellCheck.Checked = Config.EnableSpellCheck;
|
||||||
checkTrayHighlight.Checked = Config.EnableTrayHighlight;
|
checkTrayHighlight.Checked = Config.EnableTrayHighlight;
|
||||||
|
|
||||||
@@ -37,6 +38,7 @@ namespace TweetDuck.Core.Other.Settings{
|
|||||||
public override void OnReady(){
|
public override void OnReady(){
|
||||||
checkExpandLinks.CheckedChanged += checkExpandLinks_CheckedChanged;
|
checkExpandLinks.CheckedChanged += checkExpandLinks_CheckedChanged;
|
||||||
checkSwitchAccountSelectors.CheckedChanged += checkSwitchAccountSelectors_CheckedChanged;
|
checkSwitchAccountSelectors.CheckedChanged += checkSwitchAccountSelectors_CheckedChanged;
|
||||||
|
checkBestImageQuality.CheckedChanged += checkBestImageQuality_CheckedChanged;
|
||||||
checkSpellCheck.CheckedChanged += checkSpellCheck_CheckedChanged;
|
checkSpellCheck.CheckedChanged += checkSpellCheck_CheckedChanged;
|
||||||
trackBarZoom.ValueChanged += trackBarZoom_ValueChanged;
|
trackBarZoom.ValueChanged += trackBarZoom_ValueChanged;
|
||||||
|
|
||||||
@@ -59,6 +61,10 @@ namespace TweetDuck.Core.Other.Settings{
|
|||||||
Config.SwitchAccountSelectors = checkSwitchAccountSelectors.Checked;
|
Config.SwitchAccountSelectors = checkSwitchAccountSelectors.Checked;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void checkBestImageQuality_CheckedChanged(object sender, EventArgs e){
|
||||||
|
Config.BestImageQuality = checkBestImageQuality.Checked;
|
||||||
|
}
|
||||||
|
|
||||||
private void checkSpellCheck_CheckedChanged(object sender, EventArgs e){
|
private void checkSpellCheck_CheckedChanged(object sender, EventArgs e){
|
||||||
Config.EnableSpellCheck = checkSpellCheck.Checked;
|
Config.EnableSpellCheck = checkSpellCheck.Checked;
|
||||||
PromptRestart();
|
PromptRestart();
|
||||||
|
@@ -63,6 +63,7 @@
|
|||||||
this.labelSize = new System.Windows.Forms.Label();
|
this.labelSize = new System.Windows.Forms.Label();
|
||||||
this.panelMiscellaneous = new System.Windows.Forms.Panel();
|
this.panelMiscellaneous = new System.Windows.Forms.Panel();
|
||||||
this.durationUpdateTimer = new System.Windows.Forms.Timer(this.components);
|
this.durationUpdateTimer = new System.Windows.Forms.Timer(this.components);
|
||||||
|
this.checkMediaPreviews = new System.Windows.Forms.CheckBox();
|
||||||
((System.ComponentModel.ISupportInitialize)(this.trackBarEdgeDistance)).BeginInit();
|
((System.ComponentModel.ISupportInitialize)(this.trackBarEdgeDistance)).BeginInit();
|
||||||
this.tableLayoutDurationButtons.SuspendLayout();
|
this.tableLayoutDurationButtons.SuspendLayout();
|
||||||
((System.ComponentModel.ISupportInitialize)(this.trackBarDuration)).BeginInit();
|
((System.ComponentModel.ISupportInitialize)(this.trackBarDuration)).BeginInit();
|
||||||
@@ -273,11 +274,11 @@
|
|||||||
// checkSkipOnLinkClick
|
// checkSkipOnLinkClick
|
||||||
//
|
//
|
||||||
this.checkSkipOnLinkClick.AutoSize = true;
|
this.checkSkipOnLinkClick.AutoSize = true;
|
||||||
this.checkSkipOnLinkClick.Location = new System.Drawing.Point(6, 28);
|
this.checkSkipOnLinkClick.Location = new System.Drawing.Point(6, 51);
|
||||||
this.checkSkipOnLinkClick.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
|
this.checkSkipOnLinkClick.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
|
||||||
this.checkSkipOnLinkClick.Name = "checkSkipOnLinkClick";
|
this.checkSkipOnLinkClick.Name = "checkSkipOnLinkClick";
|
||||||
this.checkSkipOnLinkClick.Size = new System.Drawing.Size(113, 17);
|
this.checkSkipOnLinkClick.Size = new System.Drawing.Size(113, 17);
|
||||||
this.checkSkipOnLinkClick.TabIndex = 1;
|
this.checkSkipOnLinkClick.TabIndex = 2;
|
||||||
this.checkSkipOnLinkClick.Text = "Skip On Link Click";
|
this.checkSkipOnLinkClick.Text = "Skip On Link Click";
|
||||||
this.toolTip.SetToolTip(this.checkSkipOnLinkClick, "Skips current notification when a link\r\ninside the notification is clicked.");
|
this.toolTip.SetToolTip(this.checkSkipOnLinkClick, "Skips current notification when a link\r\ninside the notification is clicked.");
|
||||||
this.checkSkipOnLinkClick.UseVisualStyleBackColor = true;
|
this.checkSkipOnLinkClick.UseVisualStyleBackColor = true;
|
||||||
@@ -297,32 +298,32 @@
|
|||||||
// labelIdlePause
|
// labelIdlePause
|
||||||
//
|
//
|
||||||
this.labelIdlePause.AutoSize = true;
|
this.labelIdlePause.AutoSize = true;
|
||||||
this.labelIdlePause.Location = new System.Drawing.Point(3, 83);
|
this.labelIdlePause.Location = new System.Drawing.Point(3, 106);
|
||||||
this.labelIdlePause.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
|
this.labelIdlePause.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
|
||||||
this.labelIdlePause.Name = "labelIdlePause";
|
this.labelIdlePause.Name = "labelIdlePause";
|
||||||
this.labelIdlePause.Size = new System.Drawing.Size(89, 13);
|
this.labelIdlePause.Size = new System.Drawing.Size(89, 13);
|
||||||
this.labelIdlePause.TabIndex = 3;
|
this.labelIdlePause.TabIndex = 4;
|
||||||
this.labelIdlePause.Text = "Pause When Idle";
|
this.labelIdlePause.Text = "Pause When Idle";
|
||||||
//
|
//
|
||||||
// comboBoxIdlePause
|
// comboBoxIdlePause
|
||||||
//
|
//
|
||||||
this.comboBoxIdlePause.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
|
this.comboBoxIdlePause.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
|
||||||
this.comboBoxIdlePause.FormattingEnabled = true;
|
this.comboBoxIdlePause.FormattingEnabled = true;
|
||||||
this.comboBoxIdlePause.Location = new System.Drawing.Point(5, 99);
|
this.comboBoxIdlePause.Location = new System.Drawing.Point(5, 122);
|
||||||
this.comboBoxIdlePause.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3);
|
this.comboBoxIdlePause.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3);
|
||||||
this.comboBoxIdlePause.Name = "comboBoxIdlePause";
|
this.comboBoxIdlePause.Name = "comboBoxIdlePause";
|
||||||
this.comboBoxIdlePause.Size = new System.Drawing.Size(144, 21);
|
this.comboBoxIdlePause.Size = new System.Drawing.Size(144, 21);
|
||||||
this.comboBoxIdlePause.TabIndex = 4;
|
this.comboBoxIdlePause.TabIndex = 5;
|
||||||
this.toolTip.SetToolTip(this.comboBoxIdlePause, "Pauses new notifications after going idle for a set amount of time.");
|
this.toolTip.SetToolTip(this.comboBoxIdlePause, "Pauses new notifications after going idle for a set amount of time.");
|
||||||
//
|
//
|
||||||
// checkNonIntrusive
|
// checkNonIntrusive
|
||||||
//
|
//
|
||||||
this.checkNonIntrusive.AutoSize = true;
|
this.checkNonIntrusive.AutoSize = true;
|
||||||
this.checkNonIntrusive.Location = new System.Drawing.Point(6, 51);
|
this.checkNonIntrusive.Location = new System.Drawing.Point(6, 74);
|
||||||
this.checkNonIntrusive.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
|
this.checkNonIntrusive.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
|
||||||
this.checkNonIntrusive.Name = "checkNonIntrusive";
|
this.checkNonIntrusive.Name = "checkNonIntrusive";
|
||||||
this.checkNonIntrusive.Size = new System.Drawing.Size(128, 17);
|
this.checkNonIntrusive.Size = new System.Drawing.Size(128, 17);
|
||||||
this.checkNonIntrusive.TabIndex = 2;
|
this.checkNonIntrusive.TabIndex = 3;
|
||||||
this.checkNonIntrusive.Text = "Non-Intrusive Popups";
|
this.checkNonIntrusive.Text = "Non-Intrusive Popups";
|
||||||
this.toolTip.SetToolTip(this.checkNonIntrusive, "When not idle and the cursor is within the notification window area,\r\nit will be delayed until the cursor moves away to prevent accidental clicks.");
|
this.toolTip.SetToolTip(this.checkNonIntrusive, "When not idle and the cursor is within the notification window area,\r\nit will be delayed until the cursor moves away to prevent accidental clicks.");
|
||||||
this.checkNonIntrusive.UseVisualStyleBackColor = true;
|
this.checkNonIntrusive.UseVisualStyleBackColor = true;
|
||||||
@@ -389,6 +390,7 @@
|
|||||||
//
|
//
|
||||||
this.panelGeneral.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
this.panelGeneral.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
||||||
| System.Windows.Forms.AnchorStyles.Right)));
|
| System.Windows.Forms.AnchorStyles.Right)));
|
||||||
|
this.panelGeneral.Controls.Add(this.checkMediaPreviews);
|
||||||
this.panelGeneral.Controls.Add(this.checkColumnName);
|
this.panelGeneral.Controls.Add(this.checkColumnName);
|
||||||
this.panelGeneral.Controls.Add(this.checkSkipOnLinkClick);
|
this.panelGeneral.Controls.Add(this.checkSkipOnLinkClick);
|
||||||
this.panelGeneral.Controls.Add(this.checkNonIntrusive);
|
this.panelGeneral.Controls.Add(this.checkNonIntrusive);
|
||||||
@@ -396,7 +398,7 @@
|
|||||||
this.panelGeneral.Controls.Add(this.comboBoxIdlePause);
|
this.panelGeneral.Controls.Add(this.comboBoxIdlePause);
|
||||||
this.panelGeneral.Location = new System.Drawing.Point(9, 31);
|
this.panelGeneral.Location = new System.Drawing.Point(9, 31);
|
||||||
this.panelGeneral.Name = "panelGeneral";
|
this.panelGeneral.Name = "panelGeneral";
|
||||||
this.panelGeneral.Size = new System.Drawing.Size(322, 126);
|
this.panelGeneral.Size = new System.Drawing.Size(322, 149);
|
||||||
this.panelGeneral.TabIndex = 1;
|
this.panelGeneral.TabIndex = 1;
|
||||||
//
|
//
|
||||||
// labelScrollSpeedValue
|
// labelScrollSpeedValue
|
||||||
@@ -437,7 +439,7 @@
|
|||||||
//
|
//
|
||||||
this.labelLocation.AutoSize = true;
|
this.labelLocation.AutoSize = true;
|
||||||
this.labelLocation.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
|
this.labelLocation.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
|
||||||
this.labelLocation.Location = new System.Drawing.Point(6, 372);
|
this.labelLocation.Location = new System.Drawing.Point(6, 395);
|
||||||
this.labelLocation.Margin = new System.Windows.Forms.Padding(0, 21, 0, 0);
|
this.labelLocation.Margin = new System.Windows.Forms.Padding(0, 21, 0, 0);
|
||||||
this.labelLocation.Name = "labelLocation";
|
this.labelLocation.Name = "labelLocation";
|
||||||
this.labelLocation.Size = new System.Drawing.Size(70, 20);
|
this.labelLocation.Size = new System.Drawing.Size(70, 20);
|
||||||
@@ -458,7 +460,7 @@
|
|||||||
this.panelLocation.Controls.Add(this.radioLocBL);
|
this.panelLocation.Controls.Add(this.radioLocBL);
|
||||||
this.panelLocation.Controls.Add(this.radioLocCustom);
|
this.panelLocation.Controls.Add(this.radioLocCustom);
|
||||||
this.panelLocation.Controls.Add(this.radioLocBR);
|
this.panelLocation.Controls.Add(this.radioLocBR);
|
||||||
this.panelLocation.Location = new System.Drawing.Point(9, 395);
|
this.panelLocation.Location = new System.Drawing.Point(9, 418);
|
||||||
this.panelLocation.Name = "panelLocation";
|
this.panelLocation.Name = "panelLocation";
|
||||||
this.panelLocation.Size = new System.Drawing.Size(322, 165);
|
this.panelLocation.Size = new System.Drawing.Size(322, 165);
|
||||||
this.panelLocation.TabIndex = 5;
|
this.panelLocation.TabIndex = 5;
|
||||||
@@ -473,7 +475,7 @@
|
|||||||
this.panelTimer.Controls.Add(this.checkTimerCountDown);
|
this.panelTimer.Controls.Add(this.checkTimerCountDown);
|
||||||
this.panelTimer.Controls.Add(this.labelDurationValue);
|
this.panelTimer.Controls.Add(this.labelDurationValue);
|
||||||
this.panelTimer.Controls.Add(this.trackBarDuration);
|
this.panelTimer.Controls.Add(this.trackBarDuration);
|
||||||
this.panelTimer.Location = new System.Drawing.Point(9, 204);
|
this.panelTimer.Location = new System.Drawing.Point(9, 227);
|
||||||
this.panelTimer.Name = "panelTimer";
|
this.panelTimer.Name = "panelTimer";
|
||||||
this.panelTimer.Size = new System.Drawing.Size(322, 144);
|
this.panelTimer.Size = new System.Drawing.Size(322, 144);
|
||||||
this.panelTimer.TabIndex = 3;
|
this.panelTimer.TabIndex = 3;
|
||||||
@@ -492,7 +494,7 @@
|
|||||||
//
|
//
|
||||||
this.labelTimer.AutoSize = true;
|
this.labelTimer.AutoSize = true;
|
||||||
this.labelTimer.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
|
this.labelTimer.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
|
||||||
this.labelTimer.Location = new System.Drawing.Point(6, 181);
|
this.labelTimer.Location = new System.Drawing.Point(6, 204);
|
||||||
this.labelTimer.Margin = new System.Windows.Forms.Padding(0, 21, 0, 0);
|
this.labelTimer.Margin = new System.Windows.Forms.Padding(0, 21, 0, 0);
|
||||||
this.labelTimer.Name = "labelTimer";
|
this.labelTimer.Name = "labelTimer";
|
||||||
this.labelTimer.Size = new System.Drawing.Size(48, 20);
|
this.labelTimer.Size = new System.Drawing.Size(48, 20);
|
||||||
@@ -503,7 +505,7 @@
|
|||||||
//
|
//
|
||||||
this.labelSize.AutoSize = true;
|
this.labelSize.AutoSize = true;
|
||||||
this.labelSize.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
|
this.labelSize.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
|
||||||
this.labelSize.Location = new System.Drawing.Point(6, 584);
|
this.labelSize.Location = new System.Drawing.Point(6, 607);
|
||||||
this.labelSize.Margin = new System.Windows.Forms.Padding(0, 21, 0, 0);
|
this.labelSize.Margin = new System.Windows.Forms.Padding(0, 21, 0, 0);
|
||||||
this.labelSize.Name = "labelSize";
|
this.labelSize.Name = "labelSize";
|
||||||
this.labelSize.Size = new System.Drawing.Size(40, 20);
|
this.labelSize.Size = new System.Drawing.Size(40, 20);
|
||||||
@@ -519,7 +521,7 @@
|
|||||||
this.panelMiscellaneous.Controls.Add(this.labelScrollSpeedValue);
|
this.panelMiscellaneous.Controls.Add(this.labelScrollSpeedValue);
|
||||||
this.panelMiscellaneous.Controls.Add(this.trackBarScrollSpeed);
|
this.panelMiscellaneous.Controls.Add(this.trackBarScrollSpeed);
|
||||||
this.panelMiscellaneous.Controls.Add(this.labelScrollSpeed);
|
this.panelMiscellaneous.Controls.Add(this.labelScrollSpeed);
|
||||||
this.panelMiscellaneous.Location = new System.Drawing.Point(9, 607);
|
this.panelMiscellaneous.Location = new System.Drawing.Point(9, 630);
|
||||||
this.panelMiscellaneous.Name = "panelMiscellaneous";
|
this.panelMiscellaneous.Name = "panelMiscellaneous";
|
||||||
this.panelMiscellaneous.Size = new System.Drawing.Size(322, 92);
|
this.panelMiscellaneous.Size = new System.Drawing.Size(322, 92);
|
||||||
this.panelMiscellaneous.TabIndex = 7;
|
this.panelMiscellaneous.TabIndex = 7;
|
||||||
@@ -529,6 +531,18 @@
|
|||||||
this.durationUpdateTimer.Interval = 200;
|
this.durationUpdateTimer.Interval = 200;
|
||||||
this.durationUpdateTimer.Tick += new System.EventHandler(this.durationUpdateTimer_Tick);
|
this.durationUpdateTimer.Tick += new System.EventHandler(this.durationUpdateTimer_Tick);
|
||||||
//
|
//
|
||||||
|
// checkMediaPreviews
|
||||||
|
//
|
||||||
|
this.checkMediaPreviews.AutoSize = true;
|
||||||
|
this.checkMediaPreviews.Location = new System.Drawing.Point(6, 28);
|
||||||
|
this.checkMediaPreviews.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
|
||||||
|
this.checkMediaPreviews.Name = "checkMediaPreviews";
|
||||||
|
this.checkMediaPreviews.Size = new System.Drawing.Size(131, 17);
|
||||||
|
this.checkMediaPreviews.TabIndex = 1;
|
||||||
|
this.checkMediaPreviews.Text = "Show Media Previews";
|
||||||
|
this.toolTip.SetToolTip(this.checkMediaPreviews, "Shows image and video thumbnails in the notification window.");
|
||||||
|
this.checkMediaPreviews.UseVisualStyleBackColor = true;
|
||||||
|
//
|
||||||
// TabSettingsNotifications
|
// TabSettingsNotifications
|
||||||
//
|
//
|
||||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||||
@@ -542,7 +556,7 @@
|
|||||||
this.Controls.Add(this.labelGeneral);
|
this.Controls.Add(this.labelGeneral);
|
||||||
this.Controls.Add(this.panelTimer);
|
this.Controls.Add(this.panelTimer);
|
||||||
this.Name = "TabSettingsNotifications";
|
this.Name = "TabSettingsNotifications";
|
||||||
this.Size = new System.Drawing.Size(340, 708);
|
this.Size = new System.Drawing.Size(340, 731);
|
||||||
this.ParentChanged += new System.EventHandler(this.TabSettingsNotifications_ParentChanged);
|
this.ParentChanged += new System.EventHandler(this.TabSettingsNotifications_ParentChanged);
|
||||||
((System.ComponentModel.ISupportInitialize)(this.trackBarEdgeDistance)).EndInit();
|
((System.ComponentModel.ISupportInitialize)(this.trackBarEdgeDistance)).EndInit();
|
||||||
this.tableLayoutDurationButtons.ResumeLayout(false);
|
this.tableLayoutDurationButtons.ResumeLayout(false);
|
||||||
@@ -601,5 +615,6 @@
|
|||||||
private System.Windows.Forms.Timer durationUpdateTimer;
|
private System.Windows.Forms.Timer durationUpdateTimer;
|
||||||
private System.Windows.Forms.RadioButton radioSizeCustom;
|
private System.Windows.Forms.RadioButton radioSizeCustom;
|
||||||
private System.Windows.Forms.RadioButton radioSizeAuto;
|
private System.Windows.Forms.RadioButton radioSizeAuto;
|
||||||
|
private System.Windows.Forms.CheckBox checkMediaPreviews;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -63,6 +63,7 @@ namespace TweetDuck.Core.Other.Settings{
|
|||||||
checkNotificationTimer.Checked = Config.DisplayNotificationTimer;
|
checkNotificationTimer.Checked = Config.DisplayNotificationTimer;
|
||||||
checkTimerCountDown.Enabled = checkNotificationTimer.Checked;
|
checkTimerCountDown.Enabled = checkNotificationTimer.Checked;
|
||||||
checkTimerCountDown.Checked = Config.NotificationTimerCountDown;
|
checkTimerCountDown.Checked = Config.NotificationTimerCountDown;
|
||||||
|
checkMediaPreviews.Checked = Config.NotificationMediaPreviews;
|
||||||
checkSkipOnLinkClick.Checked = Config.NotificationSkipOnLinkClick;
|
checkSkipOnLinkClick.Checked = Config.NotificationSkipOnLinkClick;
|
||||||
checkNonIntrusive.Checked = Config.NotificationNonIntrusiveMode;
|
checkNonIntrusive.Checked = Config.NotificationNonIntrusiveMode;
|
||||||
|
|
||||||
@@ -96,6 +97,7 @@ namespace TweetDuck.Core.Other.Settings{
|
|||||||
checkColumnName.CheckedChanged += checkColumnName_CheckedChanged;
|
checkColumnName.CheckedChanged += checkColumnName_CheckedChanged;
|
||||||
checkNotificationTimer.CheckedChanged += checkNotificationTimer_CheckedChanged;
|
checkNotificationTimer.CheckedChanged += checkNotificationTimer_CheckedChanged;
|
||||||
checkTimerCountDown.CheckedChanged += checkTimerCountDown_CheckedChanged;
|
checkTimerCountDown.CheckedChanged += checkTimerCountDown_CheckedChanged;
|
||||||
|
checkMediaPreviews.CheckedChanged += checkMediaPreviews_CheckedChanged;
|
||||||
checkSkipOnLinkClick.CheckedChanged += checkSkipOnLinkClick_CheckedChanged;
|
checkSkipOnLinkClick.CheckedChanged += checkSkipOnLinkClick_CheckedChanged;
|
||||||
checkNonIntrusive.CheckedChanged += checkNonIntrusive_CheckedChanged;
|
checkNonIntrusive.CheckedChanged += checkNonIntrusive_CheckedChanged;
|
||||||
|
|
||||||
@@ -218,6 +220,10 @@ namespace TweetDuck.Core.Other.Settings{
|
|||||||
notification.ShowNotificationForSettings(true);
|
notification.ShowNotificationForSettings(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void checkMediaPreviews_CheckedChanged(object sender, EventArgs e){
|
||||||
|
Config.NotificationMediaPreviews = checkMediaPreviews.Checked;
|
||||||
|
}
|
||||||
|
|
||||||
private void checkSkipOnLinkClick_CheckedChanged(object sender, EventArgs e){
|
private void checkSkipOnLinkClick_CheckedChanged(object sender, EventArgs e){
|
||||||
Config.NotificationSkipOnLinkClick = checkSkipOnLinkClick.Checked;
|
Config.NotificationSkipOnLinkClick = checkSkipOnLinkClick.Checked;
|
||||||
}
|
}
|
||||||
|
@@ -34,7 +34,7 @@ namespace TweetDuck.Core.Other.Settings{
|
|||||||
|
|
||||||
private void tbCustomSound_TextChanged(object sender, EventArgs e){
|
private void tbCustomSound_TextChanged(object sender, EventArgs e){
|
||||||
bool isEmpty = string.IsNullOrEmpty(tbCustomSound.Text);
|
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;
|
btnPlaySound.Enabled = !isEmpty;
|
||||||
btnResetSound.Enabled = !isEmpty;
|
btnResetSound.Enabled = !isEmpty;
|
||||||
}
|
}
|
||||||
|
@@ -2,7 +2,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Drawing;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
@@ -10,8 +9,6 @@ using TweetDuck.Core.Other;
|
|||||||
|
|
||||||
namespace TweetDuck.Core.Utils{
|
namespace TweetDuck.Core.Utils{
|
||||||
static class BrowserUtils{
|
static class BrowserUtils{
|
||||||
public const string TweetDeckURL = "https://tweetdeck.twitter.com";
|
|
||||||
|
|
||||||
public static string HeaderAcceptLanguage{
|
public static string HeaderAcceptLanguage{
|
||||||
get{
|
get{
|
||||||
string culture = Program.Culture.Name;
|
string culture = Program.Culture.Name;
|
||||||
@@ -27,13 +24,6 @@ namespace TweetDuck.Core.Utils{
|
|||||||
|
|
||||||
public static string HeaderUserAgent => Program.BrandName+" "+Application.ProductVersion;
|
public static string HeaderUserAgent => Program.BrandName+" "+Application.ProductVersion;
|
||||||
|
|
||||||
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}'";
|
|
||||||
|
|
||||||
public static readonly string[] DictionaryWords = {
|
|
||||||
"tweetdeck", "TweetDeck", "tweetduck", "TweetDuck", "TD"
|
|
||||||
};
|
|
||||||
|
|
||||||
public static void SetupCefArgs(IDictionary<string, string> args){
|
public static void SetupCefArgs(IDictionary<string, string> args){
|
||||||
if (!Program.SystemConfig.HardwareAcceleration){
|
if (!Program.SystemConfig.HardwareAcceleration){
|
||||||
args["disable-gpu"] = "1";
|
args["disable-gpu"] = "1";
|
||||||
@@ -117,14 +107,6 @@ namespace TweetDuck.Core.Utils{
|
|||||||
browser.GetHost().SetZoomLevel(Math.Log(percentage/100.0, 1.2));
|
browser.GetHost().SetZoomLevel(Math.Log(percentage/100.0, 1.2));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool IsTweetDeckWebsite(IFrame frame){
|
|
||||||
return frame.Url.Contains("//tweetdeck.twitter.com/");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsTwitterWebsite(IFrame frame){
|
|
||||||
return frame.Url.Contains("//twitter.com/");
|
|
||||||
}
|
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
public static void HandleConsoleMessage(object sender, ConsoleMessageEventArgs e){
|
public static void HandleConsoleMessage(object sender, ConsoleMessageEventArgs e){
|
||||||
Debug.WriteLine("[Console] {0} ({1}:{2})", e.Message, e.Source, e.Line);
|
Debug.WriteLine("[Console] {0} ({1}:{2})", e.Message, e.Source, e.Line);
|
||||||
|
@@ -1,39 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using TweetDuck.Data;
|
|
||||||
|
|
||||||
namespace TweetDuck.Core.Utils{
|
|
||||||
static class CommandLineArgsParser{
|
|
||||||
private static readonly Lazy<Regex> SplitRegex = new Lazy<Regex>(() => new Regex(@"([^=\s]+(?:=(?:\S*""[^""]*?""\S*|\S*))?)", RegexOptions.Compiled), false);
|
|
||||||
|
|
||||||
public static CommandLineArgs ReadCefArguments(string argumentString){
|
|
||||||
CommandLineArgs args = new CommandLineArgs();
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(argumentString)){
|
|
||||||
return args;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach(Match match in SplitRegex.Value.Matches(argumentString)){
|
|
||||||
string matchValue = match.Value;
|
|
||||||
|
|
||||||
int indexEquals = matchValue.IndexOf('=');
|
|
||||||
string key, value;
|
|
||||||
|
|
||||||
if (indexEquals == -1){
|
|
||||||
key = matchValue.TrimStart('-');
|
|
||||||
value = "1";
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
key = matchValue.Substring(0, indexEquals).TrimStart('-');
|
|
||||||
value = matchValue.Substring(indexEquals+1).Trim('"');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key.Length != 0){
|
|
||||||
args.SetValue(key, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return args;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -8,7 +8,6 @@ namespace TweetDuck.Core.Utils{
|
|||||||
[SuppressMessage("ReSharper", "FieldCanBeMadeReadOnly.Local")]
|
[SuppressMessage("ReSharper", "FieldCanBeMadeReadOnly.Local")]
|
||||||
[SuppressMessage("ReSharper", "MemberCanBePrivate.Local")]
|
[SuppressMessage("ReSharper", "MemberCanBePrivate.Local")]
|
||||||
static class NativeMethods{
|
static class NativeMethods{
|
||||||
public static readonly IntPtr HWND_BROADCAST = new IntPtr(0xFFFF);
|
|
||||||
public static readonly IntPtr HOOK_HANDLED = new IntPtr(-1);
|
public static readonly IntPtr HOOK_HANDLED = new IntPtr(-1);
|
||||||
|
|
||||||
public const int HWND_TOPMOST = -1;
|
public const int HWND_TOPMOST = -1;
|
||||||
@@ -66,15 +65,6 @@ namespace TweetDuck.Core.Utils{
|
|||||||
[return: MarshalAs(UnmanagedType.Bool)]
|
[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);
|
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")]
|
[DllImport("user32.dll")]
|
||||||
[return: MarshalAs(UnmanagedType.Bool)]
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
public static extern bool ShowScrollBar(IntPtr hWnd, int wBar, bool bShow);
|
public static extern bool ShowScrollBar(IntPtr hWnd, int wBar, bool bShow);
|
||||||
|
@@ -4,6 +4,8 @@ using System.Text.RegularExpressions;
|
|||||||
|
|
||||||
namespace TweetDuck.Core.Utils{
|
namespace TweetDuck.Core.Utils{
|
||||||
static class StringUtils{
|
static class StringUtils{
|
||||||
|
public static readonly string[] EmptyArray = new string[0];
|
||||||
|
|
||||||
public static string ExtractBefore(string str, char search, int startIndex = 0){
|
public static string ExtractBefore(string str, char search, int startIndex = 0){
|
||||||
int index = str.IndexOf(search, startIndex);
|
int index = str.IndexOf(search, startIndex);
|
||||||
return index == -1 ? str : str.Substring(0, index);
|
return index == -1 ? str : str.Substring(0, index);
|
||||||
|
106
Core/Utils/TwitterUtils.cs
Normal file
106
Core/Utils/TwitterUtils.cs
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
using System;
|
||||||
|
using CefSharp;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using TweetDuck.Core.Other;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.Utils{
|
||||||
|
static class TwitterUtils{
|
||||||
|
public const string TweetDeckURL = "https://tweetdeck.twitter.com";
|
||||||
|
|
||||||
|
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);
|
||||||
|
public static Regex RegexAccount => RegexAccountLazy.Value;
|
||||||
|
|
||||||
|
public static readonly string[] DictionaryWords = {
|
||||||
|
"tweetdeck", "TweetDeck", "tweetduck", "TweetDuck", "TD"
|
||||||
|
};
|
||||||
|
|
||||||
|
public enum ImageQuality{
|
||||||
|
Default, Orig
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsTweetDeckWebsite(IFrame frame){
|
||||||
|
return frame.Url.Contains("//tweetdeck.twitter.com/");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsTwitterWebsite(IFrame frame){
|
||||||
|
return frame.Url.Contains("//twitter.com/");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ExtractImageBaseLink(string url){
|
||||||
|
int dot = url.LastIndexOf('/');
|
||||||
|
return dot == -1 ? url : StringUtils.ExtractBefore(url, ':', dot);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetImageLink(string url, ImageQuality quality){
|
||||||
|
if (quality == ImageQuality.Orig){
|
||||||
|
string result = ExtractImageBaseLink(url);
|
||||||
|
|
||||||
|
if (result != url || url.Contains("//pbs.twimg.com/media/")){
|
||||||
|
result += ":orig";
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DownloadImage(string url, string username, ImageQuality quality){
|
||||||
|
DownloadImages(new string[]{ url }, username, quality);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DownloadImages(string[] urls, string username, ImageQuality quality){
|
||||||
|
if (urls.Length == 0){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string firstImageLink = GetImageLink(urls[0], quality);
|
||||||
|
int qualityIndex = firstImageLink.IndexOf(':', firstImageLink.LastIndexOf('/'));
|
||||||
|
|
||||||
|
string file = BrowserUtils.GetFileNameFromUrl(ExtractImageBaseLink(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 = $"{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){
|
||||||
|
void OnFailure(Exception ex){
|
||||||
|
FormMessage.Error("Image Download", "An error occurred while downloading the image: "+ex.Message, FormMessage.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (urls.Length == 1){
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -103,7 +103,7 @@ namespace TweetDuck.Data{
|
|||||||
public string[] KeyValue{
|
public string[] KeyValue{
|
||||||
get{
|
get{
|
||||||
int index = Identifier.IndexOf(KeySeparator);
|
int index = Identifier.IndexOf(KeySeparator);
|
||||||
return index == -1 ? new string[0] : Identifier.Substring(index+1).Split(KeySeparator);
|
return index == -1 ? StringUtils.EmptyArray : Identifier.Substring(index+1).Split(KeySeparator);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace TweetDuck.Data{
|
namespace TweetDuck.Data{
|
||||||
sealed class CommandLineArgs{
|
sealed class CommandLineArgs{
|
||||||
@@ -32,6 +33,36 @@ namespace TweetDuck.Data{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static CommandLineArgs ReadCefArguments(string argumentString){
|
||||||
|
CommandLineArgs args = new CommandLineArgs();
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(argumentString)){
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach(Match match in Regex.Matches(argumentString, @"([^=\s]+(?:=(?:\S*""[^""]*?""\S*|\S*))?)")){
|
||||||
|
string matchValue = match.Value;
|
||||||
|
|
||||||
|
int indexEquals = matchValue.IndexOf('=');
|
||||||
|
string key, value;
|
||||||
|
|
||||||
|
if (indexEquals == -1){
|
||||||
|
key = matchValue.TrimStart('-');
|
||||||
|
value = "1";
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
key = matchValue.Substring(0, indexEquals).TrimStart('-');
|
||||||
|
value = matchValue.Substring(indexEquals+1).Trim('"');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key.Length != 0){
|
||||||
|
args.SetValue(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
private readonly HashSet<string> flags = new HashSet<string>();
|
private readonly HashSet<string> flags = new HashSet<string>();
|
||||||
private readonly Dictionary<string, string> values = new Dictionary<string, string>();
|
private readonly Dictionary<string, string> values = new Dictionary<string, string>();
|
||||||
|
|
||||||
|
@@ -6,12 +6,15 @@ using System.Reflection;
|
|||||||
using System.Runtime.Serialization;
|
using System.Runtime.Serialization;
|
||||||
|
|
||||||
namespace TweetDuck.Data.Serialization{
|
namespace TweetDuck.Data.Serialization{
|
||||||
sealed class FileSerializer<T> where T : ISerializedObject{
|
sealed class FileSerializer<T>{
|
||||||
private const string NewLineReal = "\r\n";
|
private const string NewLineReal = "\r\n";
|
||||||
private const string NewLineCustom = "\r~\n";
|
private const string NewLineCustom = "\r~\n";
|
||||||
|
|
||||||
private static readonly ITypeConverter BasicSerializerObj = new BasicTypeConverter();
|
private static readonly ITypeConverter BasicSerializerObj = new BasicTypeConverter();
|
||||||
|
|
||||||
|
public delegate void HandleUnknownPropertiesHandler(T obj, Dictionary<string, string> data);
|
||||||
|
public HandleUnknownPropertiesHandler HandleUnknownProperties { get; set; }
|
||||||
|
|
||||||
private readonly Dictionary<string, PropertyInfo> props;
|
private readonly Dictionary<string, PropertyInfo> props;
|
||||||
private readonly Dictionary<Type, ITypeConverter> converters;
|
private readonly Dictionary<Type, ITypeConverter> converters;
|
||||||
|
|
||||||
@@ -48,8 +51,10 @@ namespace TweetDuck.Data.Serialization{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void Read(string file, T obj){
|
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))){
|
using(StreamReader reader = new StreamReader(new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read))){
|
||||||
if (reader.Peek() == 0){
|
if (reader.Peek() <= 1){
|
||||||
throw new FormatException("Input appears to be a binary file.");
|
throw new FormatException("Input appears to be a binary file.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,11 +80,19 @@ namespace TweetDuck.Data.Serialization{
|
|||||||
throw new SerializationException($"Invalid file format, cannot convert value: {value} (property: {property})");
|
throw new SerializationException($"Invalid file format, cannot convert value: {value} (property: {property})");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (!obj.OnReadUnknownProperty(property, value)){
|
else{
|
||||||
throw new SerializationException($"Invalid file format, unknown property: {property}+");
|
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{
|
private class BasicTypeConverter : ITypeConverter{
|
||||||
|
@@ -1,5 +0,0 @@
|
|||||||
namespace TweetDuck.Data.Serialization{
|
|
||||||
interface ISerializedObject{
|
|
||||||
bool OnReadUnknownProperty(string property, string value);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -2,11 +2,8 @@
|
|||||||
|
|
||||||
namespace TweetDuck.Data.Serialization{
|
namespace TweetDuck.Data.Serialization{
|
||||||
sealed class SingleTypeConverter<T> : ITypeConverter{
|
sealed class SingleTypeConverter<T> : ITypeConverter{
|
||||||
public delegate string FuncConvertToString<U>(U value);
|
public Func<T, string> ConvertToString { get; set; }
|
||||||
public delegate U FuncConvertToObject<U>(string value);
|
public Func<string, T> ConvertToObject { get; set; }
|
||||||
|
|
||||||
public FuncConvertToString<T> ConvertToString { get; set; }
|
|
||||||
public FuncConvertToObject<T> ConvertToObject { get; set; }
|
|
||||||
|
|
||||||
bool ITypeConverter.TryWriteType(Type type, object value, out string converted){
|
bool ITypeConverter.TryWriteType(Type type, object value, out string converted){
|
||||||
try{
|
try{
|
||||||
|
@@ -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){
|
switch(environment){
|
||||||
case PluginEnvironment.Browser: return "browser.js";
|
case PluginEnvironment.Browser: return "browser.js";
|
||||||
case PluginEnvironment.Notification: return "notification.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){
|
switch(environment){
|
||||||
case PluginEnvironment.Browser: return "$,$TD,$TDP,TD";
|
case PluginEnvironment.Browser: return "$,$TD,$TDP,TD";
|
||||||
case PluginEnvironment.Notification: return "$TD,$TDP";
|
case PluginEnvironment.Notification: return "$TD,$TDP";
|
||||||
|
@@ -42,7 +42,7 @@ namespace TweetDuck.Plugins{
|
|||||||
|
|
||||||
private readonly string pathRoot;
|
private readonly string pathRoot;
|
||||||
private readonly string pathData;
|
private readonly string pathData;
|
||||||
private readonly Dictionary<string, string> metadata = new Dictionary<string, string>(4){
|
private readonly Dictionary<string, string> metadata = new Dictionary<string, string>(8){
|
||||||
{ "NAME", "" },
|
{ "NAME", "" },
|
||||||
{ "DESCRIPTION", "" },
|
{ "DESCRIPTION", "" },
|
||||||
{ "AUTHOR", "(anonymous)" },
|
{ "AUTHOR", "(anonymous)" },
|
||||||
@@ -84,7 +84,7 @@ namespace TweetDuck.Plugins{
|
|||||||
|
|
||||||
public string GetScriptPath(PluginEnvironment environment){
|
public string GetScriptPath(PluginEnvironment environment){
|
||||||
if (Environments.HasFlag(environment)){
|
if (Environments.HasFlag(environment)){
|
||||||
string file = environment.GetScriptFile();
|
string file = environment.GetPluginScriptFile();
|
||||||
return file != null ? Path.Combine(pathRoot, file) : string.Empty;
|
return file != null ? Path.Combine(pathRoot, file) : string.Empty;
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
@@ -152,7 +152,7 @@ namespace TweetDuck.Plugins{
|
|||||||
|
|
||||||
private static bool LoadEnvironments(string path, Plugin plugin, out string error){
|
private static bool LoadEnvironments(string path, Plugin plugin, out string error){
|
||||||
foreach(string file in Directory.EnumerateFiles(path, "*.js", SearchOption.TopDirectoryOnly).Select(Path.GetFileName)){
|
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));
|
plugin.Environments |= PluginEnvironmentExtensions.Values.FirstOrDefault(env => file.Equals(env.GetPluginScriptFile(), StringComparison.Ordinal));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (plugin.Environments == PluginEnvironment.None){
|
if (plugin.Environments == PluginEnvironment.None){
|
||||||
|
@@ -40,6 +40,7 @@ namespace TweetDuck.Plugins{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}catch(FileNotFoundException){
|
}catch(FileNotFoundException){
|
||||||
|
Save(file);
|
||||||
}catch(DirectoryNotFoundException){
|
}catch(DirectoryNotFoundException){
|
||||||
}catch(Exception e){
|
}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);
|
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);
|
||||||
|
@@ -9,12 +9,14 @@ using TweetDuck.Resources;
|
|||||||
|
|
||||||
namespace TweetDuck.Plugins{
|
namespace TweetDuck.Plugins{
|
||||||
sealed class PluginManager{
|
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 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 PathOfficialPlugins => Path.Combine(rootPath, "official");
|
||||||
public string PathCustomPlugins => Path.Combine(rootPath, "user");
|
public string PathCustomPlugins => Path.Combine(rootPath, "user");
|
||||||
|
|
||||||
@@ -97,7 +99,17 @@ namespace TweetDuck.Plugins{
|
|||||||
Reloaded?.Invoke(this, new PluginErrorEventArgs(loadErrors));
|
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)){
|
||||||
|
ScriptLoader.ExecuteScript(frame, PluginSetupScripts[environment], environment.GetScriptIdentifier());
|
||||||
|
ScriptLoader.ExecuteScript(frame, PluginSetupScripts[PluginEnvironment.None], PluginEnvironment.None.GetScriptIdentifier());
|
||||||
|
ExecutePluginScripts(frame, environment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ExecutePluginScripts(IFrame frame, PluginEnvironment environment){
|
||||||
|
bool includeDisabled = environment.IncludesDisabledPlugins();
|
||||||
|
|
||||||
if (includeDisabled){
|
if (includeDisabled){
|
||||||
ScriptLoader.ExecuteScript(frame, PluginScriptGenerator.GenerateConfig(Config), "gen:pluginconfig");
|
ScriptLoader.ExecuteScript(frame, PluginScriptGenerator.GenerateConfig(Config), "gen:pluginconfig");
|
||||||
}
|
}
|
||||||
|
@@ -8,7 +8,7 @@ namespace TweetDuck.Plugins{
|
|||||||
|
|
||||||
public static string GeneratePlugin(string pluginIdentifier, string pluginContents, int pluginToken, PluginEnvironment environment){
|
public static string GeneratePlugin(string pluginIdentifier, string pluginContents, int pluginToken, PluginEnvironment environment){
|
||||||
return PluginGen
|
return PluginGen
|
||||||
.Replace("%params", environment.GetScriptVariables())
|
.Replace("%params", environment.GetPluginScriptVariables())
|
||||||
.Replace("%id", pluginIdentifier)
|
.Replace("%id", pluginIdentifier)
|
||||||
.Replace("%token", pluginToken.ToString())
|
.Replace("%token", pluginToken.ToString())
|
||||||
.Replace("%contents", pluginContents);
|
.Replace("%contents", pluginContents);
|
||||||
|
87
Program.cs
87
Program.cs
@@ -1,4 +1,4 @@
|
|||||||
using CefSharp;
|
using CefSharp;
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
@@ -8,22 +8,21 @@ using System.Threading;
|
|||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using TweetDuck.Configuration;
|
using TweetDuck.Configuration;
|
||||||
using TweetDuck.Core;
|
using TweetDuck.Core;
|
||||||
using TweetDuck.Core.Handling;
|
using TweetDuck.Core.Handling.General;
|
||||||
using TweetDuck.Core.Other;
|
using TweetDuck.Core.Other;
|
||||||
using TweetDuck.Core.Other.Settings.Export;
|
using TweetDuck.Core.Other.Settings.Export;
|
||||||
using TweetDuck.Core.Utils;
|
using TweetDuck.Core.Utils;
|
||||||
using TweetDuck.Data;
|
using TweetDuck.Data;
|
||||||
using TweetDuck.Plugins;
|
|
||||||
using TweetDuck.Plugins.Events;
|
|
||||||
using TweetDuck.Updates;
|
using TweetDuck.Updates;
|
||||||
|
using TweetLib.Communication;
|
||||||
|
|
||||||
namespace TweetDuck{
|
namespace TweetDuck{
|
||||||
static class Program{
|
static class Program{
|
||||||
public const string BrandName = "TweetDuck";
|
public const string BrandName = "TweetDuck";
|
||||||
public const string Website = "https://tweetduck.chylex.com";
|
public const string Website = "https://tweetduck.chylex.com";
|
||||||
|
|
||||||
public const string VersionTag = "1.8.3";
|
public const string VersionTag = "1.8.6";
|
||||||
public const string VersionFull = "1.8.3.0";
|
public const string VersionFull = "1.8.6";
|
||||||
|
|
||||||
public static readonly Version Version = new Version(VersionTag);
|
public static readonly Version Version = new Version(VersionTag);
|
||||||
public static readonly bool IsPortable = File.Exists("makeportable");
|
public static readonly bool IsPortable = File.Exists("makeportable");
|
||||||
@@ -34,13 +33,13 @@ namespace TweetDuck{
|
|||||||
public static readonly string ScriptPath = Path.Combine(ProgramPath, "scripts");
|
public static readonly string ScriptPath = Path.Combine(ProgramPath, "scripts");
|
||||||
public static readonly string PluginPath = Path.Combine(ProgramPath, "plugins");
|
public static readonly string PluginPath = Path.Combine(ProgramPath, "plugins");
|
||||||
|
|
||||||
public static readonly string UserConfigFilePath = Path.Combine(StoragePath, "TD_UserConfig.cfg");
|
|
||||||
public static readonly string SystemConfigFilePath = Path.Combine(StoragePath, "TD_SystemConfig.cfg");
|
|
||||||
public static readonly string PluginConfigFilePath = Path.Combine(StoragePath, "TD_PluginConfig.cfg");
|
|
||||||
|
|
||||||
public static readonly string PluginDataPath = Path.Combine(StoragePath, "TD_Plugins");
|
public static readonly string PluginDataPath = Path.Combine(StoragePath, "TD_Plugins");
|
||||||
private static readonly string InstallerPath = Path.Combine(StoragePath, "TD_Updates");
|
private static readonly string InstallerPath = Path.Combine(StoragePath, "TD_Updates");
|
||||||
|
|
||||||
|
public static string UserConfigFilePath => Path.Combine(StoragePath, "TD_UserConfig.cfg");
|
||||||
|
public static string SystemConfigFilePath => Path.Combine(StoragePath, "TD_SystemConfig.cfg");
|
||||||
|
public static string PluginConfigFilePath => Path.Combine(StoragePath, "TD_PluginConfig.cfg");
|
||||||
|
|
||||||
private static string ErrorLogFilePath => Path.Combine(StoragePath, "TD_Log.txt");
|
private static string ErrorLogFilePath => Path.Combine(StoragePath, "TD_Log.txt");
|
||||||
private static string ConsoleLogFilePath => Path.Combine(StoragePath, "TD_Console.txt");
|
private static string ConsoleLogFilePath => Path.Combine(StoragePath, "TD_Console.txt");
|
||||||
|
|
||||||
@@ -62,6 +61,10 @@ namespace TweetDuck{
|
|||||||
Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
|
Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
|
||||||
CultureInfo.DefaultThreadCurrentCulture = CultureInfo.InvariantCulture;
|
CultureInfo.DefaultThreadCurrentCulture = CultureInfo.InvariantCulture;
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
CultureInfo.DefaultThreadCurrentUICulture = Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-us"); // force english exceptions
|
||||||
|
#endif
|
||||||
|
|
||||||
Reporter = new Reporter(ErrorLogFilePath);
|
Reporter = new Reporter(ErrorLogFilePath);
|
||||||
Reporter.SetupUnhandledExceptionHandler("TweetDuck Has Failed :(");
|
Reporter.SetupUnhandledExceptionHandler("TweetDuck Has Failed :(");
|
||||||
}
|
}
|
||||||
@@ -71,8 +74,8 @@ namespace TweetDuck{
|
|||||||
Application.EnableVisualStyles();
|
Application.EnableVisualStyles();
|
||||||
Application.SetCompatibleTextRenderingDefault(false);
|
Application.SetCompatibleTextRenderingDefault(false);
|
||||||
|
|
||||||
WindowRestoreMessage = NativeMethods.RegisterWindowMessage("TweetDuckRestore");
|
WindowRestoreMessage = Comms.RegisterMessage("TweetDuckRestore");
|
||||||
SubProcessMessage = NativeMethods.RegisterWindowMessage("TweetDuckSubProcess");
|
SubProcessMessage = Comms.RegisterMessage("TweetDuckSubProcess");
|
||||||
|
|
||||||
if (!WindowsUtils.CheckFolderWritePermission(StoragePath)){
|
if (!WindowsUtils.CheckFolderWritePermission(StoragePath)){
|
||||||
FormMessage.Warning("Permission Error", "TweetDuck does not have write permissions to the storage folder: "+StoragePath, FormMessage.OK);
|
FormMessage.Warning("Permission Error", "TweetDuck does not have write permissions to the storage folder: "+StoragePath, FormMessage.OK);
|
||||||
@@ -80,60 +83,43 @@ namespace TweetDuck{
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (Arguments.HasFlag(Arguments.ArgRestart)){
|
if (Arguments.HasFlag(Arguments.ArgRestart)){
|
||||||
for(int attempt = 0; attempt < 21; attempt++){
|
LockManager.Result lockResult = LockManager.LockWait(10000);
|
||||||
LockManager.Result lockResult = LockManager.Lock();
|
|
||||||
|
|
||||||
if (lockResult == LockManager.Result.Success){
|
while(lockResult != LockManager.Result.Success){
|
||||||
break;
|
if (lockResult == LockManager.Result.Fail){
|
||||||
}
|
|
||||||
else if (lockResult == LockManager.Result.Fail){
|
|
||||||
FormMessage.Error("TweetDuck Has Failed :(", "An unknown error occurred accessing the data folder. Please, make sure TweetDuck is not already running. If the problem persists, try restarting your system.", FormMessage.OK);
|
FormMessage.Error("TweetDuck Has Failed :(", "An unknown error occurred accessing the data folder. Please, make sure TweetDuck is not already running. If the problem persists, try restarting your system.", FormMessage.OK);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else if (attempt == 20){
|
else if (!FormMessage.Warning("TweetDuck Cannot Restart", "TweetDuck is taking too long to close.", FormMessage.Retry, FormMessage.Exit)){
|
||||||
if (FormMessage.Warning("TweetDuck Cannot Restart", "TweetDuck is taking too long to close.", FormMessage.Retry, FormMessage.Exit)){
|
|
||||||
attempt /= 2;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else Thread.Sleep(500);
|
|
||||||
|
lockResult = LockManager.LockWait(5000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
LockManager.Result lockResult = LockManager.Lock();
|
LockManager.Result lockResult = LockManager.Lock();
|
||||||
|
|
||||||
if (lockResult == LockManager.Result.HasProcess){
|
if (lockResult == LockManager.Result.HasProcess){
|
||||||
if (LockManager.LockingProcess.MainWindowHandle == IntPtr.Zero){ // restore if the original process is in tray
|
if (!LockManager.RestoreLockingProcess(2000) && FormMessage.Error("TweetDuck is Already Running", "Another instance of TweetDuck is already running.\nDo you want to close it?", FormMessage.Yes, FormMessage.No)){
|
||||||
NativeMethods.PostMessage(NativeMethods.HWND_BROADCAST, WindowRestoreMessage, new UIntPtr((uint)LockManager.LockingProcess.Id), IntPtr.Zero);
|
|
||||||
|
|
||||||
if (WindowsUtils.TrySleepUntil(() => {
|
|
||||||
LockManager.LockingProcess.Refresh();
|
|
||||||
return LockManager.LockingProcess.HasExited || (LockManager.LockingProcess.MainWindowHandle != IntPtr.Zero && LockManager.LockingProcess.Responding);
|
|
||||||
}, 2000, 250)){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (FormMessage.Error("TweetDuck is Already Running", "Another instance of TweetDuck is already running.\nDo you want to close it?", FormMessage.Yes, FormMessage.No)){
|
|
||||||
if (!LockManager.CloseLockingProcess(10000, 5000)){
|
if (!LockManager.CloseLockingProcess(10000, 5000)){
|
||||||
FormMessage.Error("TweetDuck Has Failed :(", "Could not close the other process.", FormMessage.OK);
|
FormMessage.Error("TweetDuck Has Failed :(", "Could not close the other process.", FormMessage.OK);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
LockManager.Lock();
|
lockResult = LockManager.Lock();
|
||||||
}
|
}
|
||||||
else return;
|
else return;
|
||||||
}
|
}
|
||||||
else if (lockResult != LockManager.Result.Success){
|
|
||||||
|
if (lockResult != LockManager.Result.Success){
|
||||||
FormMessage.Error("TweetDuck Has Failed :(", "An unknown error occurred accessing the data folder. Please, make sure TweetDuck is not already running. If the problem persists, try restarting your system.", FormMessage.OK);
|
FormMessage.Error("TweetDuck Has Failed :(", "An unknown error occurred accessing the data folder. Please, make sure TweetDuck is not already running. If the problem persists, try restarting your system.", FormMessage.OK);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ReloadConfig();
|
|
||||||
SystemConfig = SystemConfig.Load(SystemConfigFilePath);
|
SystemConfig = SystemConfig.Load(SystemConfigFilePath);
|
||||||
|
ReloadConfig();
|
||||||
|
|
||||||
if (Arguments.HasFlag(Arguments.ArgImportCookies)){
|
if (Arguments.HasFlag(Arguments.ArgImportCookies)){
|
||||||
ExportManager.ImportCookies();
|
ExportManager.ImportCookies();
|
||||||
@@ -157,7 +143,7 @@ namespace TweetDuck{
|
|||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
CommandLineArgsParser.ReadCefArguments(UserConfig.CustomCefArgs).ToDictionary(settings.CefCommandLineArgs);
|
CommandLineArgs.ReadCefArguments(UserConfig.CustomCefArgs).ToDictionary(settings.CefCommandLineArgs);
|
||||||
BrowserUtils.SetupCefArgs(settings.CefCommandLineArgs);
|
BrowserUtils.SetupCefArgs(settings.CefCommandLineArgs);
|
||||||
|
|
||||||
Cef.EnableHighDPISupport();
|
Cef.EnableHighDPISupport();
|
||||||
@@ -165,18 +151,13 @@ namespace TweetDuck{
|
|||||||
|
|
||||||
Application.ApplicationExit += (sender, args) => ExitCleanup();
|
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{
|
UpdaterSettings updaterSettings = new UpdaterSettings{
|
||||||
AllowPreReleases = Arguments.HasFlag(Arguments.ArgDebugUpdates),
|
AllowPreReleases = Arguments.HasFlag(Arguments.ArgDebugUpdates),
|
||||||
DismissedUpdate = UserConfig.DismissedUpdate,
|
DismissedUpdate = UserConfig.DismissedUpdate,
|
||||||
InstallerDownloadFolder = InstallerPath
|
InstallerDownloadFolder = InstallerPath
|
||||||
};
|
};
|
||||||
|
|
||||||
FormBrowser mainForm = new FormBrowser(plugins, updaterSettings);
|
FormBrowser mainForm = new FormBrowser(updaterSettings);
|
||||||
Application.Run(mainForm);
|
Application.Run(mainForm);
|
||||||
|
|
||||||
if (mainForm.UpdateInstallerPath != null){
|
if (mainForm.UpdateInstallerPath != null){
|
||||||
@@ -191,18 +172,6 @@ namespace TweetDuck{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string GetDataStoragePath(){
|
private static string GetDataStoragePath(){
|
||||||
string custom = Arguments.GetValue(Arguments.ArgDataFolder, null);
|
string custom = Arguments.GetValue(Arguments.ArgDataFolder, null);
|
||||||
|
|
||||||
|
@@ -6,6 +6,6 @@ PM> Install-Package CefSharp.WinForms -Version 57.0.0
|
|||||||
PM> Install-Package Microsoft.VC120.CRT.JetBrains
|
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).
|
When building the `Release` configuration, the folder will only contain files intended for distribution (no debug symbols or other unnecessary files). You may package the files yourself, or use `bld/RUN BUILD.bat` to generate installer files using Inno Setup (make sure the Inno Setup binaries are on your PATH).
|
||||||
|
|
||||||
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.
|
Built files are 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.
|
||||||
|
@@ -8,7 +8,7 @@ Edit layout & design
|
|||||||
chylex
|
chylex
|
||||||
|
|
||||||
[version]
|
[version]
|
||||||
1.1.1
|
1.1.2
|
||||||
|
|
||||||
[website]
|
[website]
|
||||||
https://tweetduck.chylex.com
|
https://tweetduck.chylex.com
|
||||||
|
@@ -1,9 +1,3 @@
|
|||||||
constructor(){
|
|
||||||
super({
|
|
||||||
requiresPageReload: true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
enabled(){
|
enabled(){
|
||||||
// elements & data
|
// elements & data
|
||||||
this.css = null;
|
this.css = null;
|
||||||
@@ -16,13 +10,11 @@ enabled(){
|
|||||||
fontSize: "12px",
|
fontSize: "12px",
|
||||||
hideTweetActions: true,
|
hideTweetActions: true,
|
||||||
moveTweetActionsToRight: true,
|
moveTweetActionsToRight: true,
|
||||||
revertReplies: false,
|
|
||||||
themeColorTweaks: true,
|
themeColorTweaks: true,
|
||||||
roundedScrollBars: false,
|
|
||||||
revertIcons: true,
|
revertIcons: true,
|
||||||
smallComposeTextSize: false,
|
smallComposeTextSize: false,
|
||||||
optimizeAnimations: true,
|
optimizeAnimations: true,
|
||||||
avatarRadius: 10
|
avatarRadius: 2
|
||||||
};
|
};
|
||||||
|
|
||||||
this.firstTimeLoad = null;
|
this.firstTimeLoad = null;
|
||||||
@@ -60,7 +52,6 @@ enabled(){
|
|||||||
this.firstTimeLoad = obj === null;
|
this.firstTimeLoad = obj === null;
|
||||||
|
|
||||||
this.onStageReady();
|
this.onStageReady();
|
||||||
this.injectDeciderReplyHook(obj && obj.revertReplies);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.$$wasLoadedBefore){
|
if (this.$$wasLoadedBefore){
|
||||||
@@ -129,9 +120,7 @@ enabled(){
|
|||||||
let itemEditDesign = $('<li class="is-selectable"><a href="#" data-action>Edit layout & design</a></li>');
|
let itemEditDesign = $('<li class="is-selectable"><a href="#" data-action>Edit layout & design</a></li>');
|
||||||
itemTD.after(itemEditDesign);
|
itemTD.after(itemEditDesign);
|
||||||
|
|
||||||
itemEditDesign.on("click", "a", function(){
|
itemEditDesign.on("click", "a", this.openEditDesignDialog);
|
||||||
new customDesignModal();
|
|
||||||
});
|
|
||||||
|
|
||||||
itemEditDesign.hover(function(){
|
itemEditDesign.hover(function(){
|
||||||
$(this).addClass("is-selected");
|
$(this).addClass("is-selected");
|
||||||
@@ -249,18 +238,7 @@ enabled(){
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// decider injections
|
this.openEditDesignDialog = () => new customDesignModal();
|
||||||
this.injectDeciderReplyHook = enable => {
|
|
||||||
let prevFunc = TD.decider.updateFromBackend;
|
|
||||||
|
|
||||||
TD.decider.updateFromBackend = function(data){
|
|
||||||
data["simplified_replies"] = !enable;
|
|
||||||
return prevFunc.apply(this, arguments);
|
|
||||||
};
|
|
||||||
|
|
||||||
TD.decider.updateForGuestId();
|
|
||||||
this.$requiresReload = enable;
|
|
||||||
};
|
|
||||||
|
|
||||||
// animation optimization
|
// animation optimization
|
||||||
this.optimizations = null;
|
this.optimizations = null;
|
||||||
@@ -353,7 +331,7 @@ enabled(){
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.css.insert("#general_settings .cf { display: none !important }");
|
this.css.insert("#general_settings .cf { display: none !important }");
|
||||||
this.css.insert("#general_settings .divider-bar::after { display: inline-block; padding-top: 10px; line-height: 17px; content: 'Use the new | Edit layout & design | option in the Settings to modify TweetDeck theme, column width, font size, and other features.' }");
|
this.css.insert("#settings-modal .js-setting-list li:nth-child(3) { border-bottom: 1px solid #ccd6dd }");
|
||||||
|
|
||||||
this.css.insert(".txt-base-smallest:not(.icon), .txt-base-largest:not(.icon) { font-size: "+this.config.fontSize+" !important }");
|
this.css.insert(".txt-base-smallest:not(.icon), .txt-base-largest:not(.icon) { font-size: "+this.config.fontSize+" !important }");
|
||||||
this.css.insert(".avatar { border-radius: "+this.config.avatarRadius+"% !important }");
|
this.css.insert(".avatar { border-radius: "+this.config.avatarRadius+"% !important }");
|
||||||
@@ -389,22 +367,6 @@ enabled(){
|
|||||||
this.css.insert(".compose-text { font-size: 12px !important; height: 120px !important }");
|
this.css.insert(".compose-text { font-size: 12px !important; height: 120px !important }");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.config.roundedScrollBars){
|
|
||||||
this.css.insert(".scroll-styled-v:not(.antiscroll-inner)::-webkit-scrollbar { width: 8px }");
|
|
||||||
this.css.insert(".scroll-styled-h:not(.antiscroll-inner)::-webkit-scrollbar { height: 8px }");
|
|
||||||
this.css.insert(".scroll-styled-v::-webkit-scrollbar-thumb { border-radius: 0 }");
|
|
||||||
this.css.insert(".scroll-styled-h::-webkit-scrollbar-thumb { border-radius: 0 }");
|
|
||||||
this.css.insert(".antiscroll-scrollbar { border-radius: 0 }");
|
|
||||||
this.css.insert(".antiscroll-scrollbar-vertical { margin-top: 0 }");
|
|
||||||
this.css.insert(".antiscroll-scrollbar-horizontal { margin-left: 0 }");
|
|
||||||
this.css.insert(".app-columns-container::-webkit-scrollbar { height: 9px !important }");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.config.revertReplies){
|
|
||||||
this.css.insert(".activity-header + .tweet .tweet-context { margin-left: -35px }");
|
|
||||||
this.css.insert(".activity-header + .tweet .tweet-context .obj-left { margin-right: 5px }");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.config.revertIcons){
|
if (this.config.revertIcons){
|
||||||
this.icons = document.createElement("style");
|
this.icons = document.createElement("style");
|
||||||
this.icons.innerHTML = `
|
this.icons.innerHTML = `
|
||||||
@@ -497,7 +459,9 @@ enabled(){
|
|||||||
.icon-list-filled:before{content:"\\f014";font-family:tweetdeckold}
|
.icon-list-filled:before{content:"\\f014";font-family:tweetdeckold}
|
||||||
.icon-user-filled:before{content:"\\f035";font-family:tweetdeckold}
|
.icon-user-filled:before{content:"\\f035";font-family:tweetdeckold}
|
||||||
|
|
||||||
|
.drawer .btn .icon, .app-header .btn .icon { line-height: 1em !important }
|
||||||
.column-header .column-type-icon { bottom: 26px !important }
|
.column-header .column-type-icon { bottom: 26px !important }
|
||||||
|
.is-options-open .column-type-icon { bottom: 25px !important }
|
||||||
.tweet-footer { margin-top: 6px !important }`;
|
.tweet-footer { margin-top: 6px !important }`;
|
||||||
|
|
||||||
document.head.appendChild(this.icons);
|
document.head.appendChild(this.icons);
|
||||||
@@ -540,11 +504,6 @@ enabled(){
|
|||||||
.txt-base-smallest:not(.icon), .txt-base-largest:not(.icon) { font-size: ${this.config.fontSize} !important }
|
.txt-base-smallest:not(.icon), .txt-base-largest:not(.icon) { font-size: ${this.config.fontSize} !important }
|
||||||
.avatar { border-radius: ${this.config.avatarRadius}% !important }
|
.avatar { border-radius: ${this.config.avatarRadius}% !important }
|
||||||
|
|
||||||
${this.config.revertReplies ? `
|
|
||||||
.activity-header + .tweet .tweet-context { margin-left: -35px }
|
|
||||||
.activity-header + .tweet .tweet-context .obj-left { margin-right: 5px }
|
|
||||||
` : ``}
|
|
||||||
|
|
||||||
${this.config.revertIcons ? `
|
${this.config.revertIcons ? `
|
||||||
@font-face { font-family: 'tweetdeckold'; src: url(\"https://ton.twimg.com/tweetdeck-web/web/assets/fonts/tweetdeck-regular-webfont.5f4ea87976.woff\") format(\"woff\"); font-weight: normal; font-style: normal }
|
@font-face { font-family: 'tweetdeckold'; src: url(\"https://ton.twimg.com/tweetdeck-web/web/assets/fonts/tweetdeck-regular-webfont.5f4ea87976.woff\") format(\"woff\"); font-weight: normal; font-style: normal }
|
||||||
.icon-reply:before{content:"\\f006";font-family:tweetdeckold}
|
.icon-reply:before{content:"\\f006";font-family:tweetdeckold}
|
||||||
@@ -575,6 +534,32 @@ ready(){
|
|||||||
// modal
|
// modal
|
||||||
$("[data-action='settings-menu']").on("click", this.onSettingsMenuClickedEvent);
|
$("[data-action='settings-menu']").on("click", this.onSettingsMenuClickedEvent);
|
||||||
$(".js-app").append('<div id="td-design-plugin-modal" class="js-modal settings-modal ovl scroll-v scroll-styled-v"></div>');
|
$(".js-app").append('<div id="td-design-plugin-modal" class="js-modal settings-modal ovl scroll-v scroll-styled-v"></div>');
|
||||||
|
|
||||||
|
// global settings override
|
||||||
|
var me = this;
|
||||||
|
|
||||||
|
this.prevFuncSettingsGetInfo = TD.components.GlobalSettings.prototype.getInfo;
|
||||||
|
this.prevFuncSettingsSwitchTab = TD.components.GlobalSettings.prototype.switchTab;
|
||||||
|
|
||||||
|
TD.components.GlobalSettings.prototype.getInfo = function(){
|
||||||
|
let data = me.prevFuncSettingsGetInfo.apply(this, arguments);
|
||||||
|
|
||||||
|
data.tabs.push({
|
||||||
|
title: "Layout & Design",
|
||||||
|
action: "tdp-edit-design"
|
||||||
|
});
|
||||||
|
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
TD.components.GlobalSettings.prototype.switchTab = function(tab){
|
||||||
|
if (tab === "tdp-edit-design"){
|
||||||
|
me.openEditDesignDialog();
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return me.prevFuncSettingsSwitchTab.apply(this, arguments);
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
disabled(){
|
disabled(){
|
||||||
@@ -598,6 +583,9 @@ disabled(){
|
|||||||
$(window).off("focus", this.onWindowFocusEvent);
|
$(window).off("focus", this.onWindowFocusEvent);
|
||||||
$(window).off("blur", this.onWindowBlurEvent);
|
$(window).off("blur", this.onWindowBlurEvent);
|
||||||
|
|
||||||
|
TD.components.GlobalSettings.prototype.getInfo = this.prevFuncSettingsGetInfo;
|
||||||
|
TD.components.GlobalSettings.prototype.switchTab = this.prevFuncSettingsSwitchTab;
|
||||||
|
|
||||||
$("[data-action='settings-menu']").off("click", this.onSettingsMenuClickedEvent);
|
$("[data-action='settings-menu']").off("click", this.onSettingsMenuClickedEvent);
|
||||||
$("#td-design-plugin-modal").remove();
|
$("#td-design-plugin-modal").remove();
|
||||||
}
|
}
|
||||||
|
@@ -88,10 +88,6 @@
|
|||||||
<input data-td-key="moveTweetActionsToRight" class="js-theme-checkbox touch-larger-label" type="checkbox">
|
<input data-td-key="moveTweetActionsToRight" class="js-theme-checkbox touch-larger-label" type="checkbox">
|
||||||
Tweet actions on the right side
|
Tweet actions on the right side
|
||||||
</label>
|
</label>
|
||||||
<label class="checkbox">
|
|
||||||
<input data-td-key="revertReplies" data-td-reload class="js-theme-checkbox touch-larger-label" type="checkbox">
|
|
||||||
Revert reply style (reloads page)
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<!-- DESIGN -->
|
<!-- DESIGN -->
|
||||||
|
|
||||||
@@ -102,10 +98,6 @@
|
|||||||
<input data-td-key="themeColorTweaks" class="js-theme-checkbox touch-larger-label" type="checkbox">
|
<input data-td-key="themeColorTweaks" class="js-theme-checkbox touch-larger-label" type="checkbox">
|
||||||
Theme color tweaks
|
Theme color tweaks
|
||||||
</label>
|
</label>
|
||||||
<label class="checkbox">
|
|
||||||
<input data-td-key="roundedScrollBars" class="js-theme-checkbox touch-larger-label" type="checkbox">
|
|
||||||
Rounded scroll bars
|
|
||||||
</label>
|
|
||||||
<label class="checkbox">
|
<label class="checkbox">
|
||||||
<input data-td-key="revertIcons" class="js-theme-checkbox touch-larger-label" type="checkbox">
|
<input data-td-key="revertIcons" class="js-theme-checkbox touch-larger-label" type="checkbox">
|
||||||
Revert icon design
|
Revert icon design
|
||||||
@@ -130,7 +122,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="td-avatar-shape-item-outer" data-td-key="avatarRadius" data-td-value="10">
|
<div class="td-avatar-shape-item-outer" data-td-key="avatarRadius" data-td-value="10">
|
||||||
<div class="td-avatar-shape" style="border-radius:10%"></div>
|
<div class="td-avatar-shape" style="border-radius:10%"></div>
|
||||||
<label>Default</label>
|
<label>Legacy</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="td-avatar-shape-item-outer" data-td-key="avatarRadius" data-td-value="30">
|
<div class="td-avatar-shape-item-outer" data-td-key="avatarRadius" data-td-value="30">
|
||||||
<div class="td-avatar-shape" style="border-radius:30%"></div>
|
<div class="td-avatar-shape" style="border-radius:30%"></div>
|
||||||
|
@@ -9,7 +9,7 @@ Emoji keyboard
|
|||||||
chylex
|
chylex
|
||||||
|
|
||||||
[version]
|
[version]
|
||||||
1.2
|
1.3
|
||||||
|
|
||||||
[website]
|
[website]
|
||||||
https://tweetduck.chylex.com
|
https://tweetduck.chylex.com
|
||||||
|
@@ -1,4 +1,6 @@
|
|||||||
enabled(){
|
enabled(){
|
||||||
|
this.ENABLE_CUSTOM_KEYBOARD = false;
|
||||||
|
|
||||||
this.selectedSkinTone = "";
|
this.selectedSkinTone = "";
|
||||||
this.currentKeywords = [];
|
this.currentKeywords = [];
|
||||||
|
|
||||||
@@ -29,17 +31,29 @@ enabled(){
|
|||||||
// styles
|
// styles
|
||||||
|
|
||||||
this.css = window.TDPF_createCustomStyle(this);
|
this.css = window.TDPF_createCustomStyle(this);
|
||||||
this.css.insert(".emoji-keyboard { position: absolute; width: 15.35em; background-color: white; border-radius: 2px 2px 3px 3px; font-size: 24px; z-index: 9999 }");
|
this.css.insert(".emoji-keyboard { position: absolute; width: 15.35em; background-color: white; border-radius: 1px; font-size: 24px; z-index: 9999 }");
|
||||||
|
|
||||||
this.css.insert(".emoji-keyboard-list { height: 10.14em; padding: 0.1em; box-sizing: border-box; overflow-y: auto }");
|
this.css.insert(".emoji-keyboard-list { height: 10.14em; padding: 0.1em; box-sizing: border-box; overflow-y: auto }");
|
||||||
this.css.insert(".emoji-keyboard-list .separator { height: 26px }");
|
this.css.insert(".emoji-keyboard-list .separator { height: 26px }");
|
||||||
this.css.insert(".emoji-keyboard-list img { padding: 0.1em !important; width: 1em; height: 1em; vertical-align: -0.1em; cursor: pointer }");
|
this.css.insert(".emoji-keyboard-list img { padding: 0.1em !important; width: 1em; height: 1em; vertical-align: -0.1em; cursor: pointer }");
|
||||||
this.css.insert(".emoji-keyboard-search { height: auto; padding: 4px 10px 8px; background-color: #292f33; border-radius: 2px 2px 0 0 }");
|
|
||||||
|
this.css.insert(".emoji-keyboard-search { height: auto; padding: 4px 10px 8px; background-color: #292f33; border-radius: 1px 1px 0 0 }");
|
||||||
this.css.insert(".emoji-keyboard-search input { width: 100%; border-radius: 1px; }");
|
this.css.insert(".emoji-keyboard-search input { width: 100%; border-radius: 1px; }");
|
||||||
this.css.insert(".emoji-keyboard-skintones { height: 1.3em; text-align: center; background-color: #292f33; border-radius: 0 0 2px 2px }");
|
|
||||||
|
this.css.insert(".emoji-keyboard-skintones { height: 1.3em; text-align: center; background-color: #292f33; border-radius: 0 0 1px 1px }");
|
||||||
this.css.insert(".emoji-keyboard-skintones div { width: 0.8em; height: 0.8em; margin: 0.25em 0.1em; border-radius: 50%; display: inline-block; box-sizing: border-box; cursor: pointer }");
|
this.css.insert(".emoji-keyboard-skintones div { width: 0.8em; height: 0.8em; margin: 0.25em 0.1em; border-radius: 50%; display: inline-block; box-sizing: border-box; cursor: pointer }");
|
||||||
this.css.insert(".emoji-keyboard-skintones .sel { border: 2px solid rgba(0, 0, 0, 0.35); box-shadow: 0 0 2px 0 rgba(255, 255, 255, 0.65), 0 0 1px 0 rgba(255, 255, 255, 0.4) inset }");
|
this.css.insert(".emoji-keyboard-skintones .sel { border: 2px solid rgba(0, 0, 0, 0.35); box-shadow: 0 0 2px 0 rgba(255, 255, 255, 0.65), 0 0 1px 0 rgba(255, 255, 255, 0.4) inset }");
|
||||||
this.css.insert(".emoji-keyboard-skintones :hover { border: 2px solid rgba(0, 0, 0, 0.25); box-shadow: 0 0 1px 0 rgba(255, 255, 255, 0.65), 0 0 1px 0 rgba(255, 255, 255, 0.25) inset }");
|
this.css.insert(".emoji-keyboard-skintones :hover { border: 2px solid rgba(0, 0, 0, 0.25); box-shadow: 0 0 1px 0 rgba(255, 255, 255, 0.65), 0 0 1px 0 rgba(255, 255, 255, 0.25) inset }");
|
||||||
|
|
||||||
|
this.css.insert("#emoji-keyboard-tweet-input { padding: 0 !important; line-height: 18px }");
|
||||||
|
this.css.insert("#emoji-keyboard-tweet-input img { padding: 0.1em !important; width: 1em; height: 1em; vertical-align: -0.25em }");
|
||||||
|
this.css.insert("#emoji-keyboard-tweet-input:empty::before { content: \"What's happening?\"; display: inline-block; color: #ced8de }");
|
||||||
|
|
||||||
|
this.css.insert(".js-docked-compose .compose-text-container.td-emoji-keyboard-swap .js-compose-text { position: absolute; z-index: -9999; left: 0; opacity: 0 }");
|
||||||
|
this.css.insert(".compose-text-container:not(.td-emoji-keyboard-swap) #emoji-keyboard-tweet-input { display: none; }");
|
||||||
|
|
||||||
|
this.css.insert(".js-compose-text { font-family: \"Twitter Color Emoji\", Helvetica, Arial, Verdana, sans-serif; }");
|
||||||
|
|
||||||
// layout
|
// layout
|
||||||
|
|
||||||
var buttonHTML = '<button class="needsclick btn btn-on-blue txt-left padding-v--9 emoji-keyboard-popup-btn"><i class="icon icon-heart"></i></button>';
|
var buttonHTML = '<button class="needsclick btn btn-on-blue txt-left padding-v--9 emoji-keyboard-popup-btn"><i class="icon icon-heart"></i></button>';
|
||||||
@@ -47,10 +61,10 @@ enabled(){
|
|||||||
this.prevComposeMustache = TD.mustaches["compose/docked_compose.mustache"];
|
this.prevComposeMustache = TD.mustaches["compose/docked_compose.mustache"];
|
||||||
TD.mustaches["compose/docked_compose.mustache"] = TD.mustaches["compose/docked_compose.mustache"].replace('<div class="cf margin-t--12 margin-b--30">', '<div class="cf margin-t--12 margin-b--30">'+buttonHTML);
|
TD.mustaches["compose/docked_compose.mustache"] = TD.mustaches["compose/docked_compose.mustache"].replace('<div class="cf margin-t--12 margin-b--30">', '<div class="cf margin-t--12 margin-b--30">'+buttonHTML);
|
||||||
|
|
||||||
var dockedComposePanel = $(".js-docked-compose");
|
var maybeDockedComposePanel = $(".js-docked-compose");
|
||||||
|
|
||||||
if (dockedComposePanel.length){
|
if (maybeDockedComposePanel.length){
|
||||||
dockedComposePanel.find(".cf.margin-t--12.margin-b--30").first().append(buttonHTML);
|
maybeDockedComposePanel.find(".cf.margin-t--12.margin-b--30").first().append(buttonHTML);
|
||||||
}
|
}
|
||||||
|
|
||||||
// keyboard generation
|
// keyboard generation
|
||||||
@@ -58,7 +72,7 @@ enabled(){
|
|||||||
this.currentKeyboard = null;
|
this.currentKeyboard = null;
|
||||||
this.currentSpanner = null;
|
this.currentSpanner = null;
|
||||||
|
|
||||||
var hideKeyboard = () => {
|
var hideKeyboard = (refocus) => {
|
||||||
$(this.currentKeyboard).remove();
|
$(this.currentKeyboard).remove();
|
||||||
this.currentKeyboard = null;
|
this.currentKeyboard = null;
|
||||||
|
|
||||||
@@ -70,7 +84,15 @@ enabled(){
|
|||||||
this.composePanelScroller.trigger("scroll");
|
this.composePanelScroller.trigger("scroll");
|
||||||
|
|
||||||
$(".emoji-keyboard-popup-btn").removeClass("is-selected");
|
$(".emoji-keyboard-popup-btn").removeClass("is-selected");
|
||||||
$(".js-compose-text").first().focus();
|
|
||||||
|
if (refocus){
|
||||||
|
if ($(".compose-text-container", ".js-docked-compose").hasClass("td-emoji-keyboard-swap")){
|
||||||
|
$("#emoji-keyboard-tweet-input").focus();
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
$(".js-compose-text", ".js-docked-compose").focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var generateEmojiHTML = skinTone => {
|
var generateEmojiHTML = skinTone => {
|
||||||
@@ -124,7 +146,7 @@ enabled(){
|
|||||||
updateFilters();
|
updateFilters();
|
||||||
};
|
};
|
||||||
|
|
||||||
this.generateKeyboard = (input, left, top) => {
|
this.generateKeyboard = (left, top) => {
|
||||||
var outer = document.createElement("div");
|
var outer = document.createElement("div");
|
||||||
outer.classList.add("emoji-keyboard");
|
outer.classList.add("emoji-keyboard");
|
||||||
outer.style.left = left+"px";
|
outer.style.left = left+"px";
|
||||||
@@ -134,18 +156,10 @@ enabled(){
|
|||||||
keyboard.classList.add("emoji-keyboard-list");
|
keyboard.classList.add("emoji-keyboard-list");
|
||||||
|
|
||||||
keyboard.addEventListener("click", function(e){
|
keyboard.addEventListener("click", function(e){
|
||||||
if (e.target.tagName === "IMG"){
|
let ele = e.target;
|
||||||
var val = input.val();
|
|
||||||
var inserted = e.target.getAttribute("alt");
|
|
||||||
var posStart = input[0].selectionStart;
|
|
||||||
var posEnd = input[0].selectionEnd;
|
|
||||||
|
|
||||||
input.val(val.slice(0, posStart)+inserted+val.slice(posStart));
|
if (ele.tagName === "IMG"){
|
||||||
input.trigger("change");
|
insertEmoji(ele.getAttribute("src"), ele.getAttribute("alt"));
|
||||||
input.focus();
|
|
||||||
|
|
||||||
input[0].selectionStart = posStart+inserted.length;
|
|
||||||
input[0].selectionEnd = posEnd+inserted.length;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@@ -204,7 +218,43 @@ enabled(){
|
|||||||
return button.offset().top+button.outerHeight()+me.composePanelScroller.scrollTop()+8;
|
return button.offset().top+button.outerHeight()+me.composePanelScroller.scrollTop()+8;
|
||||||
};
|
};
|
||||||
|
|
||||||
// event handlers
|
var focusWithCaretAtEnd = () => {
|
||||||
|
let range = document.createRange();
|
||||||
|
range.selectNodeContents(me.composeInputNew[0]);
|
||||||
|
range.collapse(false);
|
||||||
|
|
||||||
|
let sel = window.getSelection();
|
||||||
|
sel.removeAllRanges();
|
||||||
|
sel.addRange(range);
|
||||||
|
};
|
||||||
|
|
||||||
|
var insertEmoji = (src, alt) => {
|
||||||
|
if (this.ENABLE_CUSTOM_KEYBOARD){
|
||||||
|
replaceEditor(true);
|
||||||
|
|
||||||
|
if (!hasSelectionInEditor()){
|
||||||
|
focusWithCaretAtEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
document.execCommand("insertHTML", false, `<img src="${src}" alt="${alt}">`);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
let input = $(".js-compose-text", ".js-docked-compose");
|
||||||
|
|
||||||
|
let val = input.val();
|
||||||
|
let posStart = input[0].selectionStart;
|
||||||
|
let posEnd = input[0].selectionEnd;
|
||||||
|
|
||||||
|
input.val(val.slice(0, posStart)+alt+val.slice(posEnd));
|
||||||
|
input.trigger("change");
|
||||||
|
input.focus();
|
||||||
|
|
||||||
|
input[0].selectionStart = posStart+alt.length;
|
||||||
|
input[0].selectionEnd = posStart+alt.length;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// general event handlers
|
||||||
|
|
||||||
this.emojiKeyboardButtonClickEvent = function(e){
|
this.emojiKeyboardButtonClickEvent = function(e){
|
||||||
if (me.currentKeyboard){
|
if (me.currentKeyboard){
|
||||||
@@ -212,7 +262,7 @@ enabled(){
|
|||||||
$(this).blur();
|
$(this).blur();
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
me.generateKeyboard($(".js-compose-text").first(), $(this).offset().left, getKeyboardTop());
|
me.generateKeyboard($(this).offset().left, getKeyboardTop());
|
||||||
$(this).addClass("is-selected");
|
$(this).addClass("is-selected");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,15 +275,19 @@ enabled(){
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.composerSendingEvent = function(e){
|
||||||
|
hideKeyboard();
|
||||||
|
};
|
||||||
|
|
||||||
this.documentClickEvent = function(e){
|
this.documentClickEvent = function(e){
|
||||||
if (me.currentKeyboard && !e.target.classList.contains("js-compose-text")){
|
if (me.currentKeyboard && $(e.target).closest(".compose-text-container").length === 0){
|
||||||
hideKeyboard();
|
hideKeyboard();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.documentKeyEvent = function(e){
|
this.documentKeyEvent = function(e){
|
||||||
if (me.currentKeyboard && e.keyCode === 27){ // escape
|
if (me.currentKeyboard && e.keyCode === 27){ // escape
|
||||||
hideKeyboard();
|
hideKeyboard(true);
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -242,10 +296,154 @@ enabled(){
|
|||||||
if (me.currentKeyboard){
|
if (me.currentKeyboard){
|
||||||
me.currentKeyboard.style.top = getKeyboardTop()+"px";
|
me.currentKeyboard.style.top = getKeyboardTop()+"px";
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// new editor event handlers
|
||||||
|
|
||||||
|
var prevOldInputVal = "";
|
||||||
|
var isEditorActive = false;
|
||||||
|
|
||||||
|
var hasSelectionInEditor = function(){
|
||||||
|
let sel = window.getSelection();
|
||||||
|
return sel.anchorNode && $(sel.anchorNode).closest("#emoji-keyboard-tweet-input").length;
|
||||||
|
};
|
||||||
|
|
||||||
|
var migrateEditorText = function(){
|
||||||
|
let selStart = me.composeInputOrig[0].selectionStart;
|
||||||
|
let selEnd = me.composeInputOrig[0].selectionEnd;
|
||||||
|
|
||||||
|
let val = me.composeInputOrig.val();
|
||||||
|
let splitStart = val.substring(0, selStart).split("\n");
|
||||||
|
let splitEnd = val.substring(0, selEnd).split("\n");
|
||||||
|
|
||||||
|
me.composeInputNew.text(val);
|
||||||
|
me.composeInputNew.html(me.composeInputNew.text().replace(/^(.*?)$/gm, "<div>$1</div>").replace(/<div><\/div>/g, "<div><br></div>"));
|
||||||
|
|
||||||
|
let sel = window.getSelection();
|
||||||
|
let range = document.createRange();
|
||||||
|
|
||||||
|
if (me.composeInputNew[0].children.length === 0){
|
||||||
|
focusWithCaretAtEnd();
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
me.composeInputNew.focus();
|
||||||
|
range.setStart(me.composeInputNew[0].children[splitStart.length-1].firstChild, splitStart.length > 1 ? selStart-val.lastIndexOf("\n", selStart)-1 : selStart);
|
||||||
|
range.setEnd(me.composeInputNew[0].children[splitEnd.length-1].firstChild, splitEnd.length > 1 ? selEnd-val.lastIndexOf("\n", selEnd)-1 : selEnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
sel.removeAllRanges();
|
||||||
|
sel.addRange(range);
|
||||||
|
};
|
||||||
|
|
||||||
|
var replaceEditor = function(useCustom){
|
||||||
|
if (useCustom && !isEditorActive){
|
||||||
|
isEditorActive = true;
|
||||||
|
}
|
||||||
|
else if (!useCustom && isEditorActive){
|
||||||
|
isEditorActive = false;
|
||||||
|
}
|
||||||
|
else return;
|
||||||
|
|
||||||
|
$(".compose-text-container", ".js-docked-compose").toggleClass("td-emoji-keyboard-swap", isEditorActive);
|
||||||
|
|
||||||
|
if (isEditorActive){
|
||||||
|
migrateEditorText();
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
me.composeInputOrig.focus();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.composeOldInputFocusEvent = function(){
|
||||||
|
if (!isEditorActive){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let val = $(this).val();
|
||||||
|
|
||||||
|
if (val.length === 0){
|
||||||
|
replaceEditor(false);
|
||||||
|
}
|
||||||
|
else if (val != prevOldInputVal){
|
||||||
|
setTimeout(migrateEditorText, 1);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
focusWithCaretAtEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
prevOldInputVal = val;
|
||||||
|
};
|
||||||
|
|
||||||
|
var allowedShortcuts = [ 65 /* A */, 67 /* C */, 86 /* V */, 89 /* Y */, 90 /* Z */ ];
|
||||||
|
|
||||||
|
this.composeInputKeyEvent = function(e){
|
||||||
|
if (e.type === "keydown" && (e.ctrlKey || e.metaKey)){
|
||||||
|
if (e.keyCode === 13){ // enter
|
||||||
|
$(".js-send-button", ".js-docked-compose").click();
|
||||||
|
}
|
||||||
|
else if (e.keyCode >= 48 && !allowedShortcuts.includes(e.keyCode)){
|
||||||
|
e.preventDefault();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (e.keyCode !== 27){ // escape
|
||||||
|
e.stopPropagation();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.composeInputUpdateEvent = function(){
|
||||||
|
let clone = $(this).clone();
|
||||||
|
|
||||||
|
clone.children("div").each(function(){
|
||||||
|
let ele = $(this)[0];
|
||||||
|
ele.outerHTML = "\n"+ele.innerHTML;
|
||||||
|
});
|
||||||
|
|
||||||
|
clone.children("img").each(function(){
|
||||||
|
let ele = $(this)[0];
|
||||||
|
ele.outerHTML = ele.getAttribute("alt");
|
||||||
|
});
|
||||||
|
|
||||||
|
me.composeInputOrig.val(prevOldInputVal = clone.text());
|
||||||
|
me.composeInputOrig.trigger("change");
|
||||||
|
|
||||||
|
if (prevOldInputVal.length === 0){
|
||||||
|
replaceEditor(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TODO if (!emoji.length){
|
||||||
|
let sel = window.getSelection();
|
||||||
|
let selStart = -1, selEnd = -1;
|
||||||
|
|
||||||
|
if ($(sel.anchorNode).closest("#emoji-keyboard-tweet-input").length && sel.rangeCount === 1){
|
||||||
|
let range = sel.getRangeAt(0);
|
||||||
|
// TODO figure out offset
|
||||||
|
}
|
||||||
|
|
||||||
|
replaceEditor(false);
|
||||||
|
me.composeInputOrig.focus();
|
||||||
|
|
||||||
|
if (selStart !== -1){
|
||||||
|
me.composeInputOrig[0].selectionStart = selStart;
|
||||||
|
me.composeInputOrig[0].selectionEnd = selEnd;
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
};
|
||||||
|
|
||||||
|
this.composeInputPasteEvent = function(e){ // contenteditable with <img alt> handles copying just fine
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
let text = e.originalEvent.clipboardData.getData("text/plain");
|
||||||
|
text && document.execCommand("insertText", false, text);
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO handle @ and hashtags
|
||||||
|
}
|
||||||
|
|
||||||
ready(){
|
ready(){
|
||||||
|
this.composeDrawer = $("[data-drawer='compose']");
|
||||||
|
|
||||||
this.composePanelScroller = $(".js-compose-scroller", ".js-docked-compose").first().children().first();
|
this.composePanelScroller = $(".js-compose-scroller", ".js-docked-compose").first().children().first();
|
||||||
this.composePanelScroller.on("scroll", this.composerScrollEvent);
|
this.composePanelScroller.on("scroll", this.composerScrollEvent);
|
||||||
|
|
||||||
@@ -253,6 +451,20 @@ ready(){
|
|||||||
$(document).on("click", this.documentClickEvent);
|
$(document).on("click", this.documentClickEvent);
|
||||||
$(document).on("keydown", this.documentKeyEvent);
|
$(document).on("keydown", this.documentKeyEvent);
|
||||||
$(document).on("uiComposeImageAdded", this.uploadFilesEvent);
|
$(document).on("uiComposeImageAdded", this.uploadFilesEvent);
|
||||||
|
this.composeDrawer.on("uiComposeTweetSending", this.composerSendingEvent);
|
||||||
|
|
||||||
|
// Editor
|
||||||
|
|
||||||
|
this.composeInputOrig = $(".js-compose-text", ".js-docked-compose").first();
|
||||||
|
this.composeInputNew = $('<div id="emoji-keyboard-tweet-input" contenteditable="true" class="compose-text txt-size--14 scroll-v scroll-styled-v scroll-styled-h scroll-alt td-detect-image-paste"></div>').insertAfter(this.composeInputOrig);
|
||||||
|
|
||||||
|
if (this.ENABLE_CUSTOM_KEYBOARD){
|
||||||
|
this.composeInputOrig.on("focus", this.composeOldInputFocusEvent);
|
||||||
|
|
||||||
|
this.composeInputNew.on("keydown keypress keyup", this.composeInputKeyEvent);
|
||||||
|
this.composeInputNew.on("input", this.composeInputUpdateEvent);
|
||||||
|
this.composeInputNew.on("paste", this.composeInputPasteEvent);
|
||||||
|
}
|
||||||
|
|
||||||
// HTML generation
|
// HTML generation
|
||||||
|
|
||||||
@@ -273,12 +485,14 @@ ready(){
|
|||||||
|
|
||||||
// declaration inserters
|
// declaration inserters
|
||||||
|
|
||||||
|
let mapUnicode = pt => convUnicode(parseInt(pt, 16));
|
||||||
|
|
||||||
let addDeclaration1 = decl => {
|
let addDeclaration1 = decl => {
|
||||||
this.emojiData1.push(decl.split(" ").map(pt => convUnicode(parseInt(pt, 16))).join(""));
|
this.emojiData1.push(decl.split(" ").map(mapUnicode).join(""));
|
||||||
};
|
};
|
||||||
|
|
||||||
let addDeclaration2 = (tone, decl) => {
|
let addDeclaration2 = (tone, decl) => {
|
||||||
let gen = decl.split(" ").map(pt => convUnicode(parseInt(pt, 16))).join("");
|
let gen = decl.split(" ").map(mapUnicode).join("");
|
||||||
|
|
||||||
if (tone === null){
|
if (tone === null){
|
||||||
for(let skinTone of this.skinToneList){
|
for(let skinTone of this.skinToneList){
|
||||||
@@ -291,7 +505,7 @@ ready(){
|
|||||||
};
|
};
|
||||||
|
|
||||||
let addDeclaration3 = decl => {
|
let addDeclaration3 = decl => {
|
||||||
this.emojiData3.push(decl.split(" ").map(pt => convUnicode(parseInt(pt, 16))).join(""));
|
this.emojiData3.push(decl.split(" ").map(mapUnicode).join(""));
|
||||||
};
|
};
|
||||||
|
|
||||||
// line reading
|
// line reading
|
||||||
@@ -365,6 +579,9 @@ disabled(){
|
|||||||
$(this.currentSpanner).remove();
|
$(this.currentSpanner).remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.composeInputNew.remove();
|
||||||
|
|
||||||
|
this.composeInputOrig.off("focus", this.composeOldInputFocusEvent);
|
||||||
this.composePanelScroller.off("scroll", this.composerScrollEvent);
|
this.composePanelScroller.off("scroll", this.composerScrollEvent);
|
||||||
|
|
||||||
$(".emoji-keyboard-popup-btn").off("click", this.emojiKeyboardButtonClickEvent);
|
$(".emoji-keyboard-popup-btn").off("click", this.emojiKeyboardButtonClickEvent);
|
||||||
@@ -373,5 +590,7 @@ disabled(){
|
|||||||
$(document).off("click", this.documentClickEvent);
|
$(document).off("click", this.documentClickEvent);
|
||||||
$(document).off("keydown", this.documentKeyEvent);
|
$(document).off("keydown", this.documentKeyEvent);
|
||||||
$(document).off("uiComposeImageAdded", this.uploadFilesEvent);
|
$(document).off("uiComposeImageAdded", this.uploadFilesEvent);
|
||||||
|
this.composeDrawer.off("uiComposeTweetSending", this.composerSendingEvent);
|
||||||
|
|
||||||
TD.mustaches["compose/docked_compose.mustache"] = this.prevComposeMustache;
|
TD.mustaches["compose/docked_compose.mustache"] = this.prevComposeMustache;
|
||||||
}
|
}
|
||||||
|
@@ -8,7 +8,7 @@ Templates
|
|||||||
chylex
|
chylex
|
||||||
|
|
||||||
[version]
|
[version]
|
||||||
1.0
|
1.0.2
|
||||||
|
|
||||||
[website]
|
[website]
|
||||||
https://tweetduck.chylex.com
|
https://tweetduck.chylex.com
|
||||||
|
@@ -53,8 +53,10 @@ enabled(){
|
|||||||
this.css.insert(".templates-modal-wrap { width: 100%; height: 100%; padding: 49px; position: absolute; z-index: 999; box-sizing: border-box; background-color: rgba(0, 0, 0, 0.5); }");
|
this.css.insert(".templates-modal-wrap { width: 100%; height: 100%; padding: 49px; position: absolute; z-index: 999; box-sizing: border-box; background-color: rgba(0, 0, 0, 0.5); }");
|
||||||
this.css.insert(".templates-modal { width: 100%; height: 100%; background-color: #fff; display: flex; }");
|
this.css.insert(".templates-modal { width: 100%; height: 100%; background-color: #fff; display: flex; }");
|
||||||
this.css.insert(".templates-modal > div { display: flex; flex-direction: column; }");
|
this.css.insert(".templates-modal > div { display: flex; flex-direction: column; }");
|
||||||
this.css.insert(".templates-modal-bottom { flex: 0 0 auto; padding: 16px; text-align: right; }");
|
|
||||||
this.css.insert(".templates-modal-bottom button { margin-left: 4px; }");
|
this.css.insert(".templates-modal-bottom { flex: 0 0 auto; padding: 16px; }");
|
||||||
|
this.css.insert(".template-list .templates-modal-bottom { display: flex; justify-content: space-between; }");
|
||||||
|
this.css.insert(".template-editor .templates-modal-bottom { text-align: right; }");
|
||||||
|
|
||||||
this.css.insert(".template-list { height: 100%; flex: 1 1 auto; }");
|
this.css.insert(".template-list { height: 100%; flex: 1 1 auto; }");
|
||||||
this.css.insert(".template-list ul { list-style-type: none; font-size: 24px; color: #222; flex: 1 1 auto; padding: 12px; overflow-y: auto; }");
|
this.css.insert(".template-list ul { list-style-type: none; font-size: 24px; color: #222; flex: 1 1 auto; padding: 12px; overflow-y: auto; }");
|
||||||
@@ -265,6 +267,7 @@ enabled(){
|
|||||||
<ul></ul>
|
<ul></ul>
|
||||||
|
|
||||||
<div class="templates-modal-bottom">
|
<div class="templates-modal-bottom">
|
||||||
|
<button data-action="close" class="btn"><i class="icon icon-close icon-small padding-rs"></i><span class="label">Close</span></button>
|
||||||
<button data-action="new-template" class="btn btn-positive"><i class="icon icon-plus icon-small padding-rs"></i><span class="label">New Template</span></button>
|
<button data-action="new-template" class="btn btn-positive"><i class="icon icon-plus icon-small padding-rs"></i><span class="label">New Template</span></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -298,7 +301,7 @@ enabled(){
|
|||||||
|
|
||||||
<div class="templates-modal-bottom">
|
<div class="templates-modal-bottom">
|
||||||
<button data-action="editor-cancel" class="btn"><i class="icon icon-close icon-small padding-rs"></i><span class="label">Cancel</span></button>
|
<button data-action="editor-cancel" class="btn"><i class="icon icon-close icon-small padding-rs"></i><span class="label">Cancel</span></button>
|
||||||
<button data-action="editor-confirm" class="btn btn-positive"><i class="icon icon-check icon-small padding-rs"></i><span class="label">Confirm</span></button>
|
<button data-action="editor-confirm" class="btn btn-positive" style="margin-left:4px"><i class="icon icon-check icon-small padding-rs"></i><span class="label">Confirm</span></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -392,6 +395,10 @@ enabled(){
|
|||||||
toggleEditor();
|
toggleEditor();
|
||||||
onTemplatesUpdated(true);
|
onTemplatesUpdated(true);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case "close":
|
||||||
|
hideTemplateModal();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
$(this).blur();
|
$(this).blur();
|
||||||
|
12
Resources/PostBuild.ps1
Normal file
12
Resources/PostBuild.ps1
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
Param([Parameter(Mandatory = $True, Position = 1)][string] $dir)
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
Set-Location $dir
|
||||||
|
|
||||||
|
ForEach($file in Get-ChildItem -Include *.js, *.html -Recurse){
|
||||||
|
$lines = Get-Content -Path $file.FullName
|
||||||
|
$lines = ($lines | % { $_.TrimStart() }) -Replace '^(.*?)((?<=^|[;{}()] )//\s.*)?$', '$1'
|
||||||
|
$lines | Where { $_ -ne '' } | Set-Content -Path $file.FullName
|
||||||
|
|
||||||
|
Write-Host "Processed" $file.FullName.Substring($dir.Length)
|
||||||
|
}
|
@@ -56,7 +56,7 @@
|
|||||||
//
|
//
|
||||||
var appendToFunction = function(func, extension){
|
var appendToFunction = function(func, extension){
|
||||||
return function(){
|
return function(){
|
||||||
var res = func.apply(this, arguments);
|
let res = func.apply(this, arguments);
|
||||||
extension.apply(this, arguments);
|
extension.apply(this, arguments);
|
||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
@@ -66,7 +66,7 @@
|
|||||||
// Function: Returns true if an object has a specified property, otherwise returns false without throwing an error.
|
// Function: Returns true if an object has a specified property, otherwise returns false without throwing an error.
|
||||||
//
|
//
|
||||||
var ensurePropertyExists = function(obj, ...chain){
|
var ensurePropertyExists = function(obj, ...chain){
|
||||||
for(var index = 0; index < chain.length; index++){
|
for(let index = 0; index < chain.length; index++){
|
||||||
if (!obj.hasOwnProperty(chain[index])){
|
if (!obj.hasOwnProperty(chain[index])){
|
||||||
$TD.crashDebug("Missing property "+chain[index]+" in chain [obj]."+chain.join("."));
|
$TD.crashDebug("Missing property "+chain[index]+" in chain [obj]."+chain.join("."));
|
||||||
return false;
|
return false;
|
||||||
@@ -97,52 +97,85 @@
|
|||||||
// Function: Event callback for a new tweet.
|
// Function: Event callback for a new tweet.
|
||||||
//
|
//
|
||||||
var onNewTweet = (function(){
|
var onNewTweet = (function(){
|
||||||
|
let recentMessages = new Set();
|
||||||
let recentTweets = new Set();
|
let recentTweets = new Set();
|
||||||
let recentTweetTimer = null;
|
let recentTweetTimer = null;
|
||||||
|
|
||||||
let startRecentTweetTimer = () => {
|
let resetRecentTweets = () => {
|
||||||
if (recentTweetTimer){
|
|
||||||
window.clearTimeout(recentTweetTimer);
|
|
||||||
}
|
|
||||||
|
|
||||||
recentTweetTimer = window.setTimeout(() => {
|
|
||||||
recentTweetTimer = null;
|
recentTweetTimer = null;
|
||||||
recentTweets.clear();
|
recentTweets.clear();
|
||||||
}, 10000);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let checkRecentTweet = id => {
|
let startRecentTweetTimer = () => {
|
||||||
if (recentTweets.size > 50){
|
recentTweetTimer && window.clearTimeout(recentTweetTimer);
|
||||||
recentTweets.clear();
|
recentTweetTimer = window.setTimeout(resetRecentTweets, 20000);
|
||||||
}
|
};
|
||||||
else if (recentTweets.has(id)){
|
|
||||||
|
let checkTweetCache = (set, id) => {
|
||||||
|
if (set.has(id)){
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
recentTweets.add(id);
|
if (set.size > 50){
|
||||||
startRecentTweetTimer();
|
set.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
set.add(id);
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let fixMedia = (html, media) => {
|
||||||
|
return html.find(".js-media a[data-media-entity-id='"+media.mediaId+"']").css("background-image", 'url("'+media.thumb()+'")').removeClass("is-zoomable");
|
||||||
|
};
|
||||||
|
|
||||||
return function(column, tweet){
|
return function(column, tweet){
|
||||||
if (checkRecentTweet(tweet.id)){
|
if (tweet instanceof TD.services.TwitterConversation || tweet instanceof TD.services.TwitterConversationMessageEvent){
|
||||||
|
if (checkTweetCache(recentMessages, tweet.id)){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (checkTweetCache(recentTweets, tweet.id)){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
startRecentTweetTimer();
|
||||||
|
|
||||||
if (column.model.getHasNotification()){
|
if (column.model.getHasNotification()){
|
||||||
|
let sensitive = (tweet.getRelatedTweet() && tweet.getRelatedTweet().possiblySensitive || (tweet.quotedTweet && tweet.quotedTweet.possiblySensitive));
|
||||||
|
let previews = $TDX.notificationMediaPreviews && !sensitive;
|
||||||
|
|
||||||
let html = $(tweet.render({
|
let html = $(tweet.render({
|
||||||
withFooter: false,
|
withFooter: false,
|
||||||
withTweetActions: false,
|
withTweetActions: false,
|
||||||
withMediaPreview: true,
|
withMediaPreview: true,
|
||||||
isMediaPreviewOff: true,
|
isMediaPreviewOff: !previews,
|
||||||
isMediaPreviewSmall: false,
|
isMediaPreviewSmall: previews,
|
||||||
isMediaPreviewLarge: false
|
isMediaPreviewLarge: false,
|
||||||
|
isMediaPreviewCompact: false,
|
||||||
|
isMediaPreviewInQuoted: previews,
|
||||||
|
thumbSizeClass: "media-size-medium"
|
||||||
}));
|
}));
|
||||||
|
|
||||||
html.css("border", "0");
|
html.css("border", "0");
|
||||||
html.find("footer").last().remove(); // apparently withTweetActions breaks for certain tweets, nice
|
html.find("footer").last().remove(); // apparently withTweetActions breaks for certain tweets, nice
|
||||||
html.find(".js-media").last().remove(); // and quoted tweets still show media previews, nice nice
|
html.find(".js-quote-detail").removeClass("is-actionable margin-b--8"); // prevent quoted tweets from changing the cursor and reduce bottom margin
|
||||||
html.find(".js-quote-detail").removeClass("is-actionable"); // prevent quoted tweets from changing the cursor
|
|
||||||
|
if (previews){
|
||||||
|
html.find(".reverse-image-search").remove();
|
||||||
|
|
||||||
|
for(let media of tweet.getMedia()){
|
||||||
|
fixMedia(html, media);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tweet.quotedTweet){
|
||||||
|
for(let media of tweet.quotedTweet.getMedia()){
|
||||||
|
fixMedia(html, media).addClass("media-size-medium");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (tweet instanceof TD.services.TwitterActionOnTweet){
|
||||||
|
html.find(".js-media").remove();
|
||||||
|
}
|
||||||
|
|
||||||
html.find("a[href='#']").each(function(){ // remove <a> tags around links that don't lead anywhere (such as account names the tweet replied to)
|
html.find("a[href='#']").each(function(){ // remove <a> tags around links that don't lead anywhere (such as account names the tweet replied to)
|
||||||
this.outerHTML = this.innerHTML;
|
this.outerHTML = this.innerHTML;
|
||||||
@@ -152,12 +185,19 @@
|
|||||||
|
|
||||||
if (type === "follow"){
|
if (type === "follow"){
|
||||||
html.find(".js-user-actions-menu").parent().remove();
|
html.find(".js-user-actions-menu").parent().remove();
|
||||||
|
html.find(".account-bio").removeClass("padding-t--5").css("padding-top", "2px");
|
||||||
}
|
}
|
||||||
else if (type.includes("list_member")){
|
else if (type.includes("list_member")){
|
||||||
html.find(".activity-header").first().css("margin-top", "2px");
|
html.find(".activity-header").css("margin-top", "2px");
|
||||||
html.find(".avatar").first().css("margin-bottom", "0");
|
html.find(".avatar").first().css("margin-bottom", "0");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sensitive){
|
||||||
|
html.find(".media-badge").each(function(){
|
||||||
|
$(this)[0].lastChild.textContent += " (possibly sensitive)";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let source = tweet.getRelatedTweet();
|
let source = tweet.getRelatedTweet();
|
||||||
let duration = source ? source.text.length+(source.quotedTweet ? source.quotedTweet.text.length : 0) : tweet.text.length;
|
let duration = source ? source.text.length+(source.quotedTweet ? source.quotedTweet.text.length : 0) : tweet.text.length;
|
||||||
let tweetUrl = source ? source.getChirpURL() : "";
|
let tweetUrl = source ? source.getChirpURL() : "";
|
||||||
@@ -210,6 +250,9 @@
|
|||||||
|
|
||||||
onAppReady.push(function(){
|
onAppReady.push(function(){
|
||||||
document.documentElement.setAttribute("data-td-theme", TD.settings.getTheme());
|
document.documentElement.setAttribute("data-td-theme", TD.settings.getTheme());
|
||||||
|
|
||||||
|
$TD.loadFontSizeClass(TD.settings.getFontSize());
|
||||||
|
$TD.loadNotificationHeadContents(getNotificationHeadContents());
|
||||||
});
|
});
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -237,12 +280,12 @@
|
|||||||
onAppReady.push(function(){
|
onAppReady.push(function(){
|
||||||
$("[data-action='settings-menu']").click(function(){
|
$("[data-action='settings-menu']").click(function(){
|
||||||
setTimeout(function(){
|
setTimeout(function(){
|
||||||
var menu = $(".js-dropdown-content").children("ul").first();
|
let menu = $(".js-dropdown-content").children("ul").first();
|
||||||
if (menu.length === 0)return;
|
if (menu.length === 0)return;
|
||||||
|
|
||||||
menu.children(".drp-h-divider").last().before('<li class="is-selectable" data-std><a href="#" data-action="tweetduck">TweetDuck</a></li>');
|
menu.children(".drp-h-divider").last().before('<li class="is-selectable" data-std><a href="#" data-action="tweetduck">TweetDuck</a></li>');
|
||||||
|
|
||||||
var button = menu.children("[data-std]");
|
let button = menu.children("[data-std]");
|
||||||
|
|
||||||
button.on("click", "a", function(){
|
button.on("click", "a", function(){
|
||||||
$TD.openContextMenu();
|
$TD.openContextMenu();
|
||||||
@@ -272,7 +315,7 @@
|
|||||||
var me = $(this);
|
var me = $(this);
|
||||||
|
|
||||||
if (e.type === "mouseenter"){
|
if (e.type === "mouseenter"){
|
||||||
var text = me.text();
|
let text = me.text();
|
||||||
|
|
||||||
if (text.charCodeAt(text.length-1) !== 8230){ // horizontal ellipsis
|
if (text.charCodeAt(text.length-1) !== 8230){ // horizontal ellipsis
|
||||||
return;
|
return;
|
||||||
@@ -280,7 +323,7 @@
|
|||||||
|
|
||||||
if ($TDX.expandLinksOnHover){
|
if ($TDX.expandLinksOnHover){
|
||||||
tooltipTimer = window.setTimeout(function(){
|
tooltipTimer = window.setTimeout(function(){
|
||||||
var expanded = me.attr("data-full-url");
|
let expanded = me.attr("data-full-url");
|
||||||
expanded = cutStart(expanded, "https://");
|
expanded = cutStart(expanded, "https://");
|
||||||
expanded = cutStart(expanded, "http://");
|
expanded = cutStart(expanded, "http://");
|
||||||
expanded = cutStart(expanded, "www.");
|
expanded = cutStart(expanded, "www.");
|
||||||
@@ -298,7 +341,7 @@
|
|||||||
}
|
}
|
||||||
else if (e.type === "mouseleave"){
|
else if (e.type === "mouseleave"){
|
||||||
if ($TDX.expandLinksOnHover){
|
if ($TDX.expandLinksOnHover){
|
||||||
var prevText = me.attr("td-prev-text");
|
let prevText = me.attr("td-prev-text");
|
||||||
|
|
||||||
if (prevText){
|
if (prevText){
|
||||||
me.text(prevText);
|
me.text(prevText);
|
||||||
@@ -323,17 +366,28 @@
|
|||||||
})();
|
})();
|
||||||
|
|
||||||
//
|
//
|
||||||
// Block: Allow bypassing of t.co in context menus.
|
// Block: Allow bypassing of t.co and include media previews in context menus.
|
||||||
//
|
//
|
||||||
$(document.body).delegate("a", "contextmenu", function(){
|
$(document.body).delegate("a", "contextmenu", function(){
|
||||||
$TD.setLastRightClickedLink($(this).attr("data-full-url") || "");
|
$TD.setLastRightClickedLink($(this).attr("data-full-url") || "");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$(document.body).delegate("a.js-media-image-link", "contextmenu", function(){
|
||||||
|
let me = $(this)[0];
|
||||||
|
|
||||||
|
if (me.firstElementChild){
|
||||||
|
$TD.setLastRightClickedImage(me.firstElementChild.getAttribute("src"));
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
$TD.setLastRightClickedImage(me.style.backgroundImage.replace(/url\(['"]?(.*?)['"]?\)/, "$1"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
//
|
//
|
||||||
// Block: Hook into the notification sound effect.
|
// Block: Hook into the notification sound effect.
|
||||||
//
|
//
|
||||||
(function(){
|
(function(){
|
||||||
var soundEle = document.getElementById("update-sound");
|
let soundEle = document.getElementById("update-sound");
|
||||||
|
|
||||||
soundEle.play = prependToFunction(soundEle.play, function(){
|
soundEle.play = prependToFunction(soundEle.play, function(){
|
||||||
return $TDX.muteNotifications || $TDX.hasCustomNotificationSound;
|
return $TDX.muteNotifications || $TDX.hasCustomNotificationSound;
|
||||||
@@ -352,12 +406,12 @@
|
|||||||
return !!highlightedColumnObj;
|
return !!highlightedColumnObj;
|
||||||
};
|
};
|
||||||
|
|
||||||
var updateHighlightedTweet = function(ele, obj, link, embeddedLink){
|
var updateHighlightedTweet = function(ele, obj, link, embeddedLink, author, imageList){
|
||||||
highlightedTweetEle = ele;
|
highlightedTweetEle = ele;
|
||||||
highlightedTweetObj = obj;
|
highlightedTweetObj = obj;
|
||||||
|
|
||||||
if (lastTweet !== link){
|
if (lastTweet !== link){
|
||||||
$TD.setLastHighlightedTweet(link, embeddedLink);
|
$TD.setLastHighlightedTweet(link, embeddedLink, author, imageList);
|
||||||
lastTweet = link;
|
lastTweet = link;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -375,28 +429,31 @@
|
|||||||
|
|
||||||
app.delegate("article.js-stream-item", "mouseenter mouseleave", function(e){
|
app.delegate("article.js-stream-item", "mouseenter mouseleave", function(e){
|
||||||
if (e.type === "mouseenter"){
|
if (e.type === "mouseenter"){
|
||||||
var me = $(this);
|
let me = $(this);
|
||||||
|
|
||||||
if (!me[0].hasAttribute("data-account-key") || (!highlightedColumnObj && !updateHighlightedColumn(me.closest("section.js-column")))){
|
if (!me[0].hasAttribute("data-account-key") || (!highlightedColumnObj && !updateHighlightedColumn(me.closest("section.js-column")))){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var tweet = highlightedColumnObj.findChirp(me.attr("data-tweet-id")) || highlightedColumnObj.findChirp(me.attr("data-key"));
|
let tweet = highlightedColumnObj.findChirp(me.attr("data-tweet-id")) || highlightedColumnObj.findChirp(me.attr("data-key"));
|
||||||
|
|
||||||
if (tweet){
|
if (tweet){
|
||||||
if (tweet.chirpType === TD.services.ChirpBase.TWEET){
|
if (tweet.chirpType === TD.services.ChirpBase.TWEET){
|
||||||
var link = tweet.getChirpURL();
|
let link = tweet.getChirpURL();
|
||||||
var embedded = tweet.quotedTweet ? tweet.quotedTweet.getChirpURL() : "";
|
let embedded = tweet.quotedTweet ? tweet.quotedTweet.getChirpURL() : "";
|
||||||
|
let username = tweet.getMainUser().screenName;
|
||||||
|
let images = tweet.hasImage() ? tweet.getMedia().filter(item => !item.isAnimatedGif).map(item => item.entity.media_url_https+":small").join(";") : "";
|
||||||
|
// TODO maybe handle embedded images too?
|
||||||
|
|
||||||
updateHighlightedTweet(me, tweet, link || "", embedded || "");
|
updateHighlightedTweet(me, tweet, link || "", embedded || "", username, images);
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
updateHighlightedTweet(me, tweet, "", "");
|
updateHighlightedTweet(me, tweet, "", "", "", "");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (e.type === "mouseleave"){
|
else if (e.type === "mouseleave"){
|
||||||
updateHighlightedTweet(null, null, "", "");
|
updateHighlightedTweet(null, null, "", "", "", "");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
@@ -419,11 +476,11 @@
|
|||||||
|
|
||||||
window.TDGF_triggerScreenshot = function(){
|
window.TDGF_triggerScreenshot = function(){
|
||||||
if (selectedTweet){
|
if (selectedTweet){
|
||||||
var tweetWidth = Math.floor(selectedTweet.width());
|
let tweetWidth = Math.floor(selectedTweet.width());
|
||||||
var parent = selectedTweet.parent();
|
let parent = selectedTweet.parent();
|
||||||
|
|
||||||
var isDetail = parent.hasClass("js-tweet-detail");
|
let isDetail = parent.hasClass("js-tweet-detail");
|
||||||
var isReply = !isDetail && (parent.hasClass("js-replies-to") || parent.hasClass("js-replies-before"));
|
let isReply = !isDetail && (parent.hasClass("js-replies-to") || parent.hasClass("js-replies-before"));
|
||||||
|
|
||||||
selectedTweet = selectedTweet.clone();
|
selectedTweet = selectedTweet.clone();
|
||||||
selectedTweet.children().first().addClass($(document.documentElement).attr("class")).css("padding-bottom", "0");
|
selectedTweet.children().first().addClass($(document.documentElement).attr("class")).css("padding-bottom", "0");
|
||||||
@@ -457,13 +514,13 @@
|
|||||||
selectedTweet.find(".js-poll-link").remove();
|
selectedTweet.find(".js-poll-link").remove();
|
||||||
selectedTweet.find(".td-screenshot-remove").remove();
|
selectedTweet.find(".td-screenshot-remove").remove();
|
||||||
|
|
||||||
var testTweet = selectedTweet.clone().css({
|
let testTweet = selectedTweet.clone().css({
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
left: "-999px",
|
left: "-999px",
|
||||||
width: tweetWidth+"px"
|
width: tweetWidth+"px"
|
||||||
}).appendTo(document.body);
|
}).appendTo(document.body);
|
||||||
|
|
||||||
var realHeight = Math.floor(testTweet.height());
|
let realHeight = Math.floor(testTweet.height());
|
||||||
testTweet.remove();
|
testTweet.remove();
|
||||||
|
|
||||||
$TD.screenshotTweet(selectedTweet.html(), tweetWidth, realHeight);
|
$TD.screenshotTweet(selectedTweet.html(), tweetWidth, realHeight);
|
||||||
@@ -477,10 +534,15 @@
|
|||||||
onAppReady.push(function(){
|
onAppReady.push(function(){
|
||||||
var uploader = $._data(document, "events")["uiComposeAddImageClick"][0].handler.context;
|
var uploader = $._data(document, "events")["uiComposeAddImageClick"][0].handler.context;
|
||||||
|
|
||||||
app.delegate(".js-compose-text,.js-reply-tweetbox", "paste", function(e){
|
app.delegate(".js-compose-text,.js-reply-tweetbox,.td-detect-image-paste", "paste", function(e){
|
||||||
for(let item of e.originalEvent.clipboardData.items){
|
for(let item of e.originalEvent.clipboardData.items){
|
||||||
if (item.type.startsWith("image/")){
|
if (item.type.startsWith("image/")){
|
||||||
$(this).closest(".rpl").find(".js-reply-popout").click(); // popout direct messages
|
if (!$(this).closest(".rpl").find(".js-reply-popout").click().length){ // popout direct messages
|
||||||
|
if ($(".js-add-image-button").is(".is-disabled")){ // tweetdeck does not check upload count properly
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
uploader.addFilesToUpload([ item.getAsFile() ]);
|
uploader.addFilesToUpload([ item.getAsFile() ]);
|
||||||
|
|
||||||
$(".js-compose-text", ".js-docked-compose").focus();
|
$(".js-compose-text", ".js-docked-compose").focus();
|
||||||
@@ -499,31 +561,33 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
var tryCloseModal1 = function(){
|
var tryCloseModal1 = function(){
|
||||||
var modal = $("#open-modal");
|
let modal = $("#open-modal");
|
||||||
return modal.is(":visible") && tryClickSelector("a.mdl-dismiss", modal);
|
return modal.is(":visible") && tryClickSelector("a.mdl-dismiss", modal);
|
||||||
};
|
};
|
||||||
|
|
||||||
var tryCloseModal2 = function(){
|
var tryCloseModal2 = function(){
|
||||||
var modal = $(".js-modals-container");
|
let modal = $(".js-modals-container");
|
||||||
return modal.length && tryClickSelector("a.mdl-dismiss", modal);
|
return modal.length && tryClickSelector("a.mdl-dismiss", modal);
|
||||||
};
|
};
|
||||||
|
|
||||||
var tryCloseHighlightedColumn = function(){
|
var tryCloseHighlightedColumn = function(){
|
||||||
if (highlightedColumnEle){
|
if (highlightedColumnEle){
|
||||||
var column = highlightedColumnEle.closest(".js-column");
|
let column = highlightedColumnEle.closest(".js-column");
|
||||||
return (column.is(".is-shifted-2") && tryClickSelector(".js-tweet-social-proof-back", column)) || (column.is(".is-shifted-1") && tryClickSelector(".js-column-back", column));
|
return (column.is(".is-shifted-2") && tryClickSelector(".js-tweet-social-proof-back", column)) || (column.is(".is-shifted-1") && tryClickSelector(".js-column-back", column));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
window.TDGF_onMouseClickExtra = function(button){
|
window.TDGF_onMouseClickExtra = function(button){
|
||||||
if (button === 1){ // back button
|
if (button === 1){ // back button
|
||||||
|
tryClickSelector(".is-shifted-2 .js-tweet-social-proof-back", ".js-modal-panel") ||
|
||||||
|
tryClickSelector(".is-shifted-1 .js-column-back", ".js-modal-panel") ||
|
||||||
tryCloseModal1() ||
|
tryCloseModal1() ||
|
||||||
tryCloseModal2() ||
|
tryCloseModal2() ||
|
||||||
tryClickSelector(".js-inline-compose-close") ||
|
tryClickSelector(".js-inline-compose-close") ||
|
||||||
tryCloseHighlightedColumn() ||
|
tryCloseHighlightedColumn() ||
|
||||||
tryClickSelector(".js-app-content.is-open .js-drawer-close:visible") ||
|
tryClickSelector(".js-app-content.is-open .js-drawer-close:visible") ||
|
||||||
tryClickSelector(".is-shifted-2 .js-tweet-social-proof-back, .is-shifted-2 .js-dm-participants-back") ||
|
tryClickSelector(".is-shifted-2 .js-tweet-social-proof-back, .is-shifted-2 .js-dm-participants-back") ||
|
||||||
$(".js-column-back").click();
|
$(".is-shifted-1 .js-column-back").click();
|
||||||
}
|
}
|
||||||
else if (button === 2){ // forward button
|
else if (button === 2){ // forward button
|
||||||
if (highlightedTweetEle){
|
if (highlightedTweetEle){
|
||||||
@@ -538,7 +602,7 @@
|
|||||||
//
|
//
|
||||||
$(document).on("dataTweetSent", function(e, data){
|
$(document).on("dataTweetSent", function(e, data){
|
||||||
if (data.response.state && data.response.state === "scheduled"){
|
if (data.response.state && data.response.state === "scheduled"){
|
||||||
var column = Object.values(TD.controller.columnManager.getAll()).find(column => column.model.state.type === "scheduled");
|
let column = Object.values(TD.controller.columnManager.getAll()).find(column => column.model.state.type === "scheduled");
|
||||||
|
|
||||||
if (column){
|
if (column){
|
||||||
setTimeout(function(){
|
setTimeout(function(){
|
||||||
@@ -591,16 +655,18 @@
|
|||||||
})();
|
})();
|
||||||
|
|
||||||
//
|
//
|
||||||
// Block: Swap shift key functionality for selecting accounts.
|
// Block: Swap shift key functionality for selecting accounts, and refocus the textbox afterwards.
|
||||||
//
|
//
|
||||||
onAppReady.push(function(){
|
onAppReady.push(function(){
|
||||||
var toggleEventShiftKey = function(e){
|
var onAccountClick = function(e){
|
||||||
if ($TDX.switchAccountSelectors){
|
if ($TDX.switchAccountSelectors){
|
||||||
e.shiftKey = !e.shiftKey;
|
e.shiftKey = !e.shiftKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$(".js-compose-text", ".js-docked-compose").focus();
|
||||||
};
|
};
|
||||||
|
|
||||||
$(".js-drawer[data-drawer='compose']").delegate(".js-account-list > .js-account-item", "click", toggleEventShiftKey);
|
$(".js-drawer[data-drawer='compose']").delegate(".js-account-list > .js-account-item", "click", onAccountClick);
|
||||||
|
|
||||||
if (!ensurePropertyExists(TD, "components", "AccountSelector", "prototype", "refreshPostingAccounts")){
|
if (!ensurePropertyExists(TD, "components", "AccountSelector", "prototype", "refreshPostingAccounts")){
|
||||||
return;
|
return;
|
||||||
@@ -609,7 +675,7 @@
|
|||||||
TD.components.AccountSelector.prototype.refreshPostingAccounts = appendToFunction(TD.components.AccountSelector.prototype.refreshPostingAccounts, function(){
|
TD.components.AccountSelector.prototype.refreshPostingAccounts = appendToFunction(TD.components.AccountSelector.prototype.refreshPostingAccounts, function(){
|
||||||
if (!this.$node.attr("td-account-selector-hook")){
|
if (!this.$node.attr("td-account-selector-hook")){
|
||||||
this.$node.attr("td-account-selector-hook", "1");
|
this.$node.attr("td-account-selector-hook", "1");
|
||||||
this.$node.delegate(".js-account-item", "click", toggleEventShiftKey);
|
this.$node.delegate(".js-account-item", "click", onAccountClick);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -645,41 +711,67 @@
|
|||||||
// Block: Inject custom CSS and layout into the page.
|
// Block: Inject custom CSS and layout into the page.
|
||||||
//
|
//
|
||||||
(function(){
|
(function(){
|
||||||
var styleOfficial = document.createElement("style");
|
let styleOfficial = document.createElement("style");
|
||||||
document.head.appendChild(styleOfficial);
|
document.head.appendChild(styleOfficial);
|
||||||
|
|
||||||
styleOfficial.sheet.insertRule("a[data-full-url] { word-break: break-all; }", 0); // break long urls
|
let addRule = (rule) => {
|
||||||
styleOfficial.sheet.insertRule(".column-nav-link .attribution { position: absolute; }", 0); // fix cut off account names
|
styleOfficial.sheet.insertRule(rule, 0);
|
||||||
styleOfficial.sheet.insertRule(".txt-base-smallest .sprite-verified-mini { width: 13px !important; height: 13px !important; background-position: -223px -99px !important; }", 0); // fix cut off badge icon when zoomed in
|
};
|
||||||
styleOfficial.sheet.insertRule(".keyboard-shortcut-list { vertical-align: top; }", 0); // fix keyboard navigation alignment
|
|
||||||
styleOfficial.sheet.insertRule(".sprite-logo { background-position: -5px -46px !important; }", 0); // fix TweetDeck logo on certain zoom levels
|
|
||||||
styleOfficial.sheet.insertRule(".app-navigator .tooltip { display: none !important; }", 0); // hide broken tooltips in the menu
|
|
||||||
styleOfficial.sheet.insertRule(".account-inline .username { vertical-align: 10%; }", 0); // move usernames a bit higher
|
|
||||||
|
|
||||||
styleOfficial.sheet.insertRule(".btn-compose, .app-search-fake, .app-search-input { border-radius: 1px; }", 0); // use consistent menu button radius
|
addRule("a[data-full-url] { word-break: break-all; }"); // break long urls
|
||||||
styleOfficial.sheet.insertRule(".is-condensed .app-header-inner { padding-top: 10px !important; }", 0); // add extra padding to menu buttons when condensed
|
addRule(".keyboard-shortcut-list { vertical-align: top; }"); // fix keyboard navigation alignment
|
||||||
styleOfficial.sheet.insertRule(".is-condensed .btn-compose { padding: 8px !important; }", 0); // fix compose button icon when condensed
|
addRule(".account-inline .username { vertical-align: 10%; }"); // move usernames a bit higher
|
||||||
styleOfficial.sheet.insertRule(".app-header:not(.is-condensed) .nav-user-info { padding: 0 5px; }", 0); // add padding to user info
|
addRule(".character-count-compose { width: 40px !important; }"); // fix strangely wide character count element
|
||||||
|
addRule(".is-video a:not([href*='youtu']) .icon-bg-dot, .is-gif .icon-bg-dot { color: #9f51cf; }"); // change play icon on mp4s
|
||||||
|
|
||||||
styleOfficial.sheet.insertRule(".app-title { display: none; }", 0); // hide TweetDeck logo
|
addRule(".column-nav-link .attribution { position: absolute; }"); // fix cut off account names
|
||||||
styleOfficial.sheet.insertRule(".nav-user-info { bottom: 10px !important; }", 0); // move user info
|
addRule(".txt-base-smallest .sprite-verified-mini { width: 13px !important; height: 13px !important; background-position: -223px -99px !important; }"); // fix cut off badge icon when zoomed in
|
||||||
styleOfficial.sheet.insertRule(".app-navigator { bottom: 50px !important; }", 0); // move navigation
|
|
||||||
styleOfficial.sheet.insertRule(".column-navigator-overflow { bottom: 192px !important; }", 0); // move column list
|
|
||||||
|
|
||||||
styleOfficial.sheet.insertRule(".column .column-header { height: 49px !important; }", 0); // fix one pixel space below column header
|
addRule(".btn, .mdl, .mdl-content, .app-search-fake, .app-search-input, .popover, .lst-modal, .media-item, .media-preview, .tooltip-inner { border-radius: 1px !important; }"); // square-ify buttons, inputs, dialogs, menus, media previews
|
||||||
styleOfficial.sheet.insertRule(".column:not(.is-options-open) .column-header { border-bottom: none; }", 0); // fix one pixel space below column header
|
addRule(".compose-text-container, .dropdown-menu, .list-item-last, .quoted-tweet { border-radius: 0 !important; }"); // square-ify dropdowns, quoted tweets, and account selectors
|
||||||
|
addRule(".prf-header { border-radius: 0; }"); // fix user account header border
|
||||||
|
|
||||||
styleOfficial.sheet.insertRule(".activity-header { align-items: center !important; margin-bottom: 4px; }", 0); // tweak alignment of avatar and text in notifications
|
addRule(".accs li, .accs img { border-radius: 0 !important; }"); // square-ify retweet account selector
|
||||||
styleOfficial.sheet.insertRule(".activity-header .tweet-timestamp { line-height: unset }", 0); // fix timestamp position in notifications
|
addRule(".accs-header { padding-left: 0 !important; }"); // fix retweet account selector heading
|
||||||
|
|
||||||
styleOfficial.sheet.insertRule(".app-columns-container::-webkit-scrollbar-track { border-left: 0; }", 0); // remove weird border in the column container scrollbar
|
addRule(".scroll-styled-v::-webkit-scrollbar-thumb, .scroll-styled-h::-webkit-scrollbar-thumb, .antiscroll-scrollbar { border-radius: 0; }"); // square-ify scroll bars
|
||||||
styleOfficial.sheet.insertRule(".app-columns-container { bottom: 0 !important; }", 0); // move column container scrollbar to bottom to fit updated style
|
addRule(".antiscroll-scrollbar-vertical { margin-top: 0; }"); // square-ify scroll bars
|
||||||
|
addRule(".antiscroll-scrollbar-horizontal { margin-left: 0; }"); // square-ify scroll bars
|
||||||
|
addRule(".scroll-styled-v:not(.antiscroll-inner)::-webkit-scrollbar { width: 8px; }"); // square-ify scroll bars
|
||||||
|
addRule(".scroll-styled-h:not(.antiscroll-inner)::-webkit-scrollbar { height: 8px; }"); // square-ify scroll bars
|
||||||
|
addRule(".app-columns-container::-webkit-scrollbar { height: 9px !important; }"); // square-ify scroll bars
|
||||||
|
|
||||||
styleOfficial.sheet.insertRule(".js-column-header .column-header-link { padding: 0; }", 0); // fix column header tooltip hover box
|
addRule(".is-condensed .app-header-inner { padding-top: 10px !important; }"); // add extra padding to menu buttons when condensed
|
||||||
styleOfficial.sheet.insertRule(".js-column-header .column-header-link .icon { padding: 9px 4px; width: calc(1em + 8px); height: 100%; box-sizing: border-box; }", 0); // fix column header tooltip hover box
|
addRule(".is-condensed .btn-compose { padding: 8px !important; }"); // fix compose button icon when condensed
|
||||||
|
addRule(".app-header:not(.is-condensed) .nav-user-info { padding: 0 5px; }"); // add padding to user info
|
||||||
|
|
||||||
styleOfficial.sheet.insertRule(".is-video a:not([href*='youtu']), .is-gif .js-media-gif-container { cursor: alias; }", 0); // change cursor on unsupported videos
|
addRule(".app-title { display: none; }"); // hide TweetDeck logo
|
||||||
styleOfficial.sheet.insertRule(".is-video a:not([href*='youtu']) .icon-bg-dot, .is-gif .icon-bg-dot { color: #bd3d37; }", 0); // change play icon color on unsupported videos
|
addRule(".nav-user-info { bottom: 10px !important; }"); // move user info
|
||||||
|
addRule(".app-navigator { bottom: 50px !important; }"); // move navigation
|
||||||
|
addRule(".column-navigator-overflow { bottom: 192px !important; }"); // move column list
|
||||||
|
addRule(".app-navigator .tooltip { display: none !important; }"); // hide broken tooltips in the menu
|
||||||
|
|
||||||
|
addRule(".column .column-header { height: 49px !important; }"); // fix one pixel space below column header
|
||||||
|
addRule(".column:not(.is-options-open) .column-header { border-bottom: none; }"); // fix one pixel space below column header
|
||||||
|
addRule(".is-options-open .column-type-icon { bottom: 27px; }"); // fix one pixel space below column header
|
||||||
|
|
||||||
|
addRule(".activity-header { align-items: center !important; margin-bottom: 4px; }"); // tweak alignment of avatar and text in notifications
|
||||||
|
addRule(".activity-header .tweet-timestamp { line-height: unset; }"); // fix timestamp position in notifications
|
||||||
|
addRule(".account-bio.padding-t--5 { padding-top: 2px !important; }"); // decrease padding on follow notifications
|
||||||
|
|
||||||
|
addRule("html[data-td-theme='light'] .stream-item:not(:hover) .js-user-actions-menu { color: #000; border-color: #000; opacity: 0.25; }"); // make follow notification button nicer
|
||||||
|
addRule("html[data-td-theme='dark'] .stream-item:not(:hover) .js-user-actions-menu { color: #fff; border-color: #fff; opacity: 0.25; }"); // make follow notification button nicer
|
||||||
|
|
||||||
|
addRule(".app-columns-container::-webkit-scrollbar-track { border-left: 0; }"); // remove weird border in the column container scrollbar
|
||||||
|
addRule(".app-columns-container { bottom: 0 !important; }"); // move column container scrollbar to bottom to fit updated style
|
||||||
|
|
||||||
|
addRule(".js-column-header .column-header-link { padding: 0; }"); // fix column header tooltip hover box
|
||||||
|
addRule(".js-column-header .column-header-link .icon { padding: 9px 4px; width: calc(1em + 8px); height: 100%; box-sizing: border-box; }"); // fix column header tooltip hover box
|
||||||
|
|
||||||
|
addRule("#td-compose-drawer-pin { margin: 17px 4px 0 0; transition: transform 0.1s ease; fill: #fff; float: right; cursor: pointer; }"); // replace 'stay open' checkbox with a pin icon
|
||||||
|
addRule(".js-docked-compose footer { display: none; }"); // replace 'stay open' checkbox with a pin icon
|
||||||
|
addRule(".compose-content { bottom: 0 !important; }"); // replace 'stay open' checkbox with a pin icon
|
||||||
|
addRule(".js-docked-compose .js-drawer-close { margin: 20px 0 0 !important; }"); // fix close drawer button because twitter is fucking incompetent
|
||||||
|
|
||||||
window.TDGF_reinjectCustomCSS = function(styles){
|
window.TDGF_reinjectCustomCSS = function(styles){
|
||||||
$("#tweetduck-custom-css").remove();
|
$("#tweetduck-custom-css").remove();
|
||||||
@@ -709,20 +801,46 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Block: Setup unsupported video element hook.
|
// Block: Setup video player hooks.
|
||||||
//
|
//
|
||||||
(function(){
|
(function(){
|
||||||
var cancelModal = false;
|
var playVideo = function(url){
|
||||||
|
$('<div id="td-video-player-overlay" class="ovl" style="display:block"></div>').on("click contextmenu", function(){
|
||||||
|
$TD.playVideo(null);
|
||||||
|
}).appendTo(app);
|
||||||
|
|
||||||
|
$TD.playVideo(url);
|
||||||
|
};
|
||||||
|
|
||||||
|
app.delegate(".js-gif-play", "click", function(e){
|
||||||
|
let src = !e.ctrlKey && $(this).closest(".js-media-gif-container").find("video").attr("src");
|
||||||
|
|
||||||
|
if (src){
|
||||||
|
playVideo(src);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
let parent = $(e.target).closest(".js-tweet").first();
|
||||||
|
let link = (parent.hasClass("tweet-detail") ? parent.find("a[rel='url']") : parent.find("time").first().children("a")).first();
|
||||||
|
$TD.openBrowser(link.attr("href"));
|
||||||
|
}
|
||||||
|
|
||||||
|
e.stopPropagation();
|
||||||
|
});
|
||||||
|
|
||||||
|
TD.mustaches["status/media_thumb.mustache"] = TD.mustaches["status/media_thumb.mustache"].replace("is-gif", "is-gif is-paused");
|
||||||
|
TD.mustaches["media/native_video.mustache"] = '<div class="js-media-gif-container media-item nbfc is-video" style="background-image:url({{imageSrc}})"><video class="js-media-gif media-item-gif full-width block {{#isPossiblySensitive}}is-invisible{{/isPossiblySensitive}}" loop src="{{videoUrl}}"></video><a class="js-gif-play pin-all is-actionable">{{> media/video_overlay}}</a></div>';
|
||||||
|
|
||||||
if (!ensurePropertyExists(TD, "components", "MediaGallery", "prototype", "_loadTweet") ||
|
if (!ensurePropertyExists(TD, "components", "MediaGallery", "prototype", "_loadTweet") ||
|
||||||
!ensurePropertyExists(TD, "components", "BaseModal", "prototype", "setAndShowContainer") ||
|
!ensurePropertyExists(TD, "components", "BaseModal", "prototype", "setAndShowContainer") ||
|
||||||
!ensurePropertyExists(TD, "ui", "Column", "prototype", "playGifIfNotManuallyPaused"))return;
|
!ensurePropertyExists(TD, "ui", "Column", "prototype", "playGifIfNotManuallyPaused"))return;
|
||||||
|
|
||||||
|
var cancelModal = false;
|
||||||
|
|
||||||
TD.components.MediaGallery.prototype._loadTweet = appendToFunction(TD.components.MediaGallery.prototype._loadTweet, function(){
|
TD.components.MediaGallery.prototype._loadTweet = appendToFunction(TD.components.MediaGallery.prototype._loadTweet, function(){
|
||||||
var media = this.chirp.getMedia().find(media => media.mediaId === this.clickedMediaEntityId);
|
let media = this.chirp.getMedia().find(media => media.mediaId === this.clickedMediaEntityId);
|
||||||
|
|
||||||
if (media && media.isVideo && media.service !== "youtube"){
|
if (media && media.isVideo && media.service !== "youtube"){
|
||||||
$TD.openBrowser(this.clickedLink);
|
playVideo(media.chooseVideoVariant().url);
|
||||||
cancelModal = true;
|
cancelModal = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -735,22 +853,13 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
TD.ui.Column.prototype.playGifIfNotManuallyPaused = function(){};
|
TD.ui.Column.prototype.playGifIfNotManuallyPaused = function(){};
|
||||||
TD.mustaches["status/media_thumb.mustache"] = TD.mustaches["status/media_thumb.mustache"].replace("is-gif", "is-gif is-paused");
|
|
||||||
|
|
||||||
app.delegate(".js-gif-play", "click", function(e){
|
|
||||||
var parent = $(e.target).closest(".js-tweet").first();
|
|
||||||
var link = (parent.hasClass("tweet-detail") ? parent.find("a[rel='url']") : parent.find("time").first().children("a")).first();
|
|
||||||
|
|
||||||
$TD.openBrowser(link.attr("href"));
|
|
||||||
e.stopPropagation();
|
|
||||||
});
|
|
||||||
})();
|
})();
|
||||||
|
|
||||||
//
|
//
|
||||||
// Block: Fix youtu.be previews not showing up for https links.
|
// Block: Fix youtu.be previews not showing up for https links.
|
||||||
//
|
//
|
||||||
if (ensurePropertyExists(TD, "services", "TwitterMedia")){
|
if (ensurePropertyExists(TD, "services", "TwitterMedia")){
|
||||||
var media = TD.services.TwitterMedia;
|
let media = TD.services.TwitterMedia;
|
||||||
|
|
||||||
if (!ensurePropertyExists(media, "YOUTUBE_TINY_RE") ||
|
if (!ensurePropertyExists(media, "YOUTUBE_TINY_RE") ||
|
||||||
!ensurePropertyExists(media, "YOUTUBE_LONG_RE") ||
|
!ensurePropertyExists(media, "YOUTUBE_LONG_RE") ||
|
||||||
@@ -762,6 +871,34 @@
|
|||||||
media.SERVICES["youtube"] = media.YOUTUBE_RE;
|
media.SERVICES["youtube"] = media.YOUTUBE_RE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Block: Add a pin icon to make tweet compose drawer stay open.
|
||||||
|
//
|
||||||
|
onAppReady.push(function(){
|
||||||
|
let ele = $(`<svg id="td-compose-drawer-pin" viewBox="0 0 24 24" class="icon js-show-tip" data-original-title="Stay open" data-tooltip-position="left">
|
||||||
|
<path d="M9.884,16.959l3.272,0.001l-0.82,4.568l-1.635,0l-0.817,-4.569Z"/>
|
||||||
|
<rect x="8.694" y="7.208" width="5.652" height="7.445"/>
|
||||||
|
<path d="M16.877,17.448c0,-1.908 -1.549,-3.456 -3.456,-3.456l-3.802,0c-1.907,0 -3.456,1.548 -3.456,3.456l10.714,0Z"/>
|
||||||
|
<path d="M6.572,5.676l2.182,2.183l5.532,0l2.182,-2.183l0,-1.455l-9.896,0l0,1.455Z"/>
|
||||||
|
</svg>`
|
||||||
|
).appendTo(".js-docked-compose .js-compose-header");
|
||||||
|
|
||||||
|
ele.click(function(){
|
||||||
|
if (TD.settings.getComposeStayOpen()){
|
||||||
|
ele.css("transform", "rotate(0deg)");
|
||||||
|
TD.settings.setComposeStayOpen(false);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
ele.css("transform", "rotate(90deg)");
|
||||||
|
TD.settings.setComposeStayOpen(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (TD.settings.getComposeStayOpen()){
|
||||||
|
ele.css("transform", "rotate(90deg)");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
//
|
//
|
||||||
// Block: Fix DM reply input box not getting focused after opening a conversation.
|
// Block: Fix DM reply input box not getting focused after opening a conversation.
|
||||||
//
|
//
|
||||||
@@ -787,8 +924,19 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Block: Memory cleanup check and execution.
|
// Block: Custom reload function with memory cleanup.
|
||||||
//
|
//
|
||||||
|
window.TDGF_reload = function(){
|
||||||
|
let session = TD.storage.feedController.getAll()
|
||||||
|
.filter(feed => !!feed.getTopSortIndex())
|
||||||
|
.reduce((obj, feed) => (obj[feed.privateState.key] = feed.getTopSortIndex(), obj), {});
|
||||||
|
|
||||||
|
$TD.setSessionData("gc", JSON.stringify(session)).then(() => {
|
||||||
|
window.gc && window.gc();
|
||||||
|
window.location.reload();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
window.TDGF_tryRunCleanup = function(){
|
window.TDGF_tryRunCleanup = function(){
|
||||||
// all textareas are empty
|
// all textareas are empty
|
||||||
if ($("textarea").is(function(){
|
if ($("textarea").is(function(){
|
||||||
@@ -815,11 +963,62 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// cleanup
|
// cleanup
|
||||||
window.gc && window.gc();
|
window.TDGF_reload();
|
||||||
window.location.reload();
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (window.TD_SESSION && window.TD_SESSION.gc){
|
||||||
|
var state;
|
||||||
|
|
||||||
|
try{
|
||||||
|
state = JSON.parse(window.TD_SESSION.gc);
|
||||||
|
}catch(err){
|
||||||
|
$TD.crashDebug("Invalid session gc data: "+window.TD_SESSION.gc);
|
||||||
|
state = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
var showMissedNotifications = function(){
|
||||||
|
let tweets = [];
|
||||||
|
let columns = {};
|
||||||
|
|
||||||
|
let tmp = new TD.services.ChirpBase;
|
||||||
|
|
||||||
|
for(let column of Object.values(TD.controller.columnManager.getAll())){
|
||||||
|
for(let feed of column.getFeeds()){
|
||||||
|
if (feed.privateState.key in state){
|
||||||
|
tmp.sortIndex = state[feed.privateState.key];
|
||||||
|
|
||||||
|
for(let tweet of [].concat.apply([], column.updateArray.map(function(chirp){
|
||||||
|
return chirp.getUnreadChirps(tmp);
|
||||||
|
}))){
|
||||||
|
tweets.push(tweet);
|
||||||
|
columns[tweet.id] = column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tweets.sort(TD.util.chirpReverseColumnSort);
|
||||||
|
|
||||||
|
for(let tweet of tweets){
|
||||||
|
onNewTweet(columns[tweet.id], tweet);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$(document).one("dataColumnsLoaded", function(){
|
||||||
|
let columns = Object.values(TD.controller.columnManager.getAll());
|
||||||
|
let remaining = columns.length;
|
||||||
|
|
||||||
|
for(let column of columns){
|
||||||
|
column.ui.getChirpContainer().one("dataColumnFeedUpdated", () => {
|
||||||
|
if (--remaining === 0){
|
||||||
|
setTimeout(showMissedNotifications, 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Block: Disable TweetDeck metrics.
|
// Block: Disable TweetDeck metrics.
|
||||||
//
|
//
|
||||||
@@ -842,9 +1041,9 @@
|
|||||||
//
|
//
|
||||||
$(document).one("TD.ready", function(){
|
$(document).one("TD.ready", function(){
|
||||||
onAppReady.forEach(func => func());
|
onAppReady.forEach(func => func());
|
||||||
|
onAppReady = null;
|
||||||
|
|
||||||
$TD.loadFontSizeClass(TD.settings.getFontSize());
|
delete window.TD_SESSION;
|
||||||
$TD.loadNotificationHeadContents(getNotificationHeadContents());
|
|
||||||
|
|
||||||
if (window.TD_PLUGINS){
|
if (window.TD_PLUGINS){
|
||||||
window.TD_PLUGINS.onReady();
|
window.TD_PLUGINS.onReady();
|
||||||
|
@@ -98,7 +98,7 @@
|
|||||||
//
|
//
|
||||||
window.TDPF_requestReload = function(){
|
window.TDPF_requestReload = function(){
|
||||||
if (!isReloading){
|
if (!isReloading){
|
||||||
window.setTimeout(() => location.reload(), 1);
|
window.setTimeout(window.TDGF_reload, 1);
|
||||||
isReloading = true;
|
isReloading = true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@@ -11,22 +11,30 @@
|
|||||||
var style = document.createElement("style");
|
var style = document.createElement("style");
|
||||||
document.head.appendChild(style);
|
document.head.appendChild(style);
|
||||||
|
|
||||||
style.sheet.insertRule("body { overflow: hidden !important; }", 0); // remove scrollbar
|
let addRule = (rule) => {
|
||||||
style.sheet.insertRule(".topbar { display: none !important; }", 0); // hide top bar
|
style.sheet.insertRule(rule, 0);
|
||||||
style.sheet.insertRule(".page-canvas, .buttons, .btn, input { border-radius: 0 !important; }", 0); // sharpen borders
|
};
|
||||||
style.sheet.insertRule("input { padding: 5px 8px 4px !important; }", 0); // tweak input padding
|
|
||||||
|
|
||||||
style.sheet.insertRule("#doc { width: 100%; height: 100%; margin: 0; position: absolute; display: table; }", 0); // center everything
|
addRule("body { overflow: hidden !important; background-color: #1c6399 !important; }"); // remove scrollbar and change background
|
||||||
style.sheet.insertRule("#page-outer { display: table-cell; vertical-align: middle; }", 0); // center everything
|
addRule(".page-canvas { box-shadow: 0 0 150px rgba(255, 255, 255, 0.3) !important; }"); // change page box shadow
|
||||||
style.sheet.insertRule("#page-container { padding: 0 20px !important; width: 100% !important; box-sizing: border-box !important; }", 0); // center everything
|
addRule(".topbar { display: none !important; }"); // hide top bar
|
||||||
style.sheet.insertRule(".page-canvas { margin: 0 auto !important; }", 0); // center everything
|
|
||||||
|
addRule(".page-canvas, .buttons, .btn, input { border-radius: 0 !important; }"); // sharpen borders
|
||||||
|
addRule("input { padding: 5px 8px 4px !important; }"); // tweak input padding
|
||||||
|
addRule("button[type='submit'] { border: 1px solid rgba(0, 0, 0, 0.3) !important; border-radius: 0 !important; }"); // style buttons
|
||||||
|
|
||||||
|
addRule("#doc { width: 100%; height: 100%; margin: 0; position: absolute; display: table; }"); // center everything
|
||||||
|
addRule("#page-outer { display: table-cell; vertical-align: middle; }"); // center everything
|
||||||
|
addRule("#page-container { padding: 0 20px !important; width: 100% !important; box-sizing: border-box !important; }"); // center everything
|
||||||
|
addRule(".page-canvas { margin: 0 auto !important; }"); // center everything
|
||||||
|
|
||||||
if (location.pathname === "/logout"){
|
if (location.pathname === "/logout"){
|
||||||
style.sheet.insertRule(".page-canvas { width: auto !important; max-width: 888px; }", 0); // fix min width
|
addRule(".page-canvas { width: auto !important; max-width: 888px; }"); // fix min width
|
||||||
style.sheet.insertRule(".signout-wrapper { width: auto !important; }", 0); // fix min width
|
addRule(".signout-wrapper { width: auto !important; margin: 0 auto !important; }"); // fix min width and margins
|
||||||
style.sheet.insertRule(".btn { margin: 0 4px !important; }", 0); // add margin around buttons
|
addRule(".signout { margin: 60px 0 54px !important; }"); // fix dialog margins
|
||||||
style.sheet.insertRule(".btn.cancel { border: 1px solid #bbc1c5 !important; }", 0); // add border to cancel button
|
addRule(".buttons { padding-bottom: 0 !important; }"); // fix dialog margins
|
||||||
style.sheet.insertRule(".aside p { display: none; }", 0); // hide text below the logout dialog
|
addRule(".aside { display: none; }"); // hide elements around logout dialog
|
||||||
|
addRule(".buttons button, .buttons a { display: inline-block; margin: 0 4px !important; border: 1px solid rgba(0, 0, 0, 0.3) !important; border-radius: 0 !important; }"); // style buttons
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -125,18 +125,26 @@
|
|||||||
font-size: 23px;
|
font-size: 23px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#tweetduck-changelog h2 + br {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
#tweetduck-changelog h3 {
|
#tweetduck-changelog h3 {
|
||||||
margin: 0 0 5px 7px;
|
margin: 0 0 5px 7px;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#tweetduck-changelog p {
|
#tweetduck-changelog p {
|
||||||
margin: 0 0 2px 30px;
|
margin: 8px 8px 0 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tweetduck-changelog p.li {
|
||||||
|
margin: 0 8px 2px 30px;
|
||||||
display: list-item;
|
display: list-item;
|
||||||
}
|
}
|
||||||
|
|
||||||
#tweetduck-changelog p.l2 {
|
#tweetduck-changelog p.l2 {
|
||||||
margin-left: 50px;
|
margin-left: 50px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
#tweetduck-changelog a {
|
#tweetduck-changelog a {
|
||||||
@@ -268,14 +276,16 @@
|
|||||||
return md.replace(/&/g, "&")
|
return md.replace(/&/g, "&")
|
||||||
.replace(/</g, "<")
|
.replace(/</g, "<")
|
||||||
.replace(/>/g, ">")
|
.replace(/>/g, ">")
|
||||||
|
.replace(/^##? (.*?)$/gm, "<h2>$1</h2>")
|
||||||
.replace(/^### (.*?)$/gm, "<h3>$1</h3>")
|
.replace(/^### (.*?)$/gm, "<h3>$1</h3>")
|
||||||
.replace(/^- (.*?)$/gm, "<p>$1</p>")
|
.replace(/^- (.*?)$/gm, "<p class='li'>$1</p>")
|
||||||
.replace(/^ - (.*?)$/gm, "<p class='l2'>$1</p>")
|
.replace(/^ - (.*?)$/gm, "<p class='li l2'>$1</p>")
|
||||||
.replace(/\*\*(.*?)\*\*/g, "<strong>$1</strong>")
|
.replace(/\*\*(.*?)\*\*/g, "<strong>$1</strong>")
|
||||||
.replace(/\*(.*?)\*/g, "<em>$1</em>")
|
.replace(/\*(.*?)\*/g, "<em>$1</em>")
|
||||||
.replace(/`(.*?)`/g, "<code>$1</code>")
|
.replace(/`(.*?)`/g, "<code>$1</code>")
|
||||||
.replace(/\[(.*?)\]\((.*?)\)/g, "<a href='$2'>$1</a>")
|
.replace(/\[(.*?)\]\((.*?)\)/g, "<a href='$2' target='_blank'>$1</a>")
|
||||||
.replace(/\n\r?\n/g, "<br>");
|
.replace(/^([a-z0-9].*?)$/gmi, "<p>$1</p>")
|
||||||
|
.replace(/\n\r?\n\r?/g, "<br>");
|
||||||
};
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@@ -88,7 +88,8 @@
|
|||||||
<Compile Include="Core\Controls\NumericUpDownEx.cs">
|
<Compile Include="Core\Controls\NumericUpDownEx.cs">
|
||||||
<SubType>Component</SubType>
|
<SubType>Component</SubType>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="Core\Handling\BrowserProcessHandler.cs" />
|
<Compile Include="Core\FormManager.cs" />
|
||||||
|
<Compile Include="Core\Handling\General\BrowserProcessHandler.cs" />
|
||||||
<Compile Include="Core\Handling\ContextMenuBase.cs" />
|
<Compile Include="Core\Handling\ContextMenuBase.cs" />
|
||||||
<Compile Include="Core\Handling\ContextMenuBrowser.cs" />
|
<Compile Include="Core\Handling\ContextMenuBrowser.cs" />
|
||||||
<Compile Include="Core\FormBrowser.cs">
|
<Compile Include="Core\FormBrowser.cs">
|
||||||
@@ -97,8 +98,10 @@
|
|||||||
<Compile Include="Core\FormBrowser.Designer.cs">
|
<Compile Include="Core\FormBrowser.Designer.cs">
|
||||||
<DependentUpon>FormBrowser.cs</DependentUpon>
|
<DependentUpon>FormBrowser.cs</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="Core\Handling\KeyboardHandlerBrowser.cs" />
|
||||||
|
<Compile Include="Core\Handling\KeyboardHandlerNotification.cs" />
|
||||||
<Compile Include="Core\Handling\RequestHandlerBrowser.cs" />
|
<Compile Include="Core\Handling\RequestHandlerBrowser.cs" />
|
||||||
<Compile Include="Core\Handling\RequestHandler.cs" />
|
<Compile Include="Core\Handling\General\RequestHandlerBase.cs" />
|
||||||
<Compile Include="Core\Handling\ResourceHandlerNotification.cs" />
|
<Compile Include="Core\Handling\ResourceHandlerNotification.cs" />
|
||||||
<Compile Include="Core\Notification\FormNotificationMain.cs">
|
<Compile Include="Core\Notification\FormNotificationMain.cs">
|
||||||
<SubType>Form</SubType>
|
<SubType>Form</SubType>
|
||||||
@@ -113,8 +116,8 @@
|
|||||||
<DependentUpon>FormNotificationBase.cs</DependentUpon>
|
<DependentUpon>FormNotificationBase.cs</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="Core\Handling\ContextMenuNotification.cs" />
|
<Compile Include="Core\Handling\ContextMenuNotification.cs" />
|
||||||
<Compile Include="Core\Handling\JavaScriptDialogHandler.cs" />
|
<Compile Include="Core\Handling\General\JavaScriptDialogHandler.cs" />
|
||||||
<Compile Include="Core\Handling\LifeSpanHandler.cs" />
|
<Compile Include="Core\Handling\General\LifeSpanHandler.cs" />
|
||||||
<Compile Include="Core\Notification\FormNotificationTweet.cs">
|
<Compile Include="Core\Notification\FormNotificationTweet.cs">
|
||||||
<SubType>Form</SubType>
|
<SubType>Form</SubType>
|
||||||
</Compile>
|
</Compile>
|
||||||
@@ -141,6 +144,7 @@
|
|||||||
<Compile Include="Core\Other\FormPlugins.Designer.cs">
|
<Compile Include="Core\Other\FormPlugins.Designer.cs">
|
||||||
<DependentUpon>FormPlugins.cs</DependentUpon>
|
<DependentUpon>FormPlugins.cs</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="Core\Other\Management\VideoPlayer.cs" />
|
||||||
<Compile Include="Core\Other\Settings\Dialogs\DialogSettingsCSS.cs">
|
<Compile Include="Core\Other\Settings\Dialogs\DialogSettingsCSS.cs">
|
||||||
<SubType>Form</SubType>
|
<SubType>Form</SubType>
|
||||||
</Compile>
|
</Compile>
|
||||||
@@ -165,8 +169,9 @@
|
|||||||
<Compile Include="Core\Other\Settings\Dialogs\DialogSettingsRestart.Designer.cs">
|
<Compile Include="Core\Other\Settings\Dialogs\DialogSettingsRestart.Designer.cs">
|
||||||
<DependentUpon>DialogSettingsRestart.cs</DependentUpon>
|
<DependentUpon>DialogSettingsRestart.cs</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="Core\Utils\BrowserProcesses.cs" />
|
<Compile Include="Core\Other\Management\BrowserProcesses.cs" />
|
||||||
<Compile Include="Core\Utils\StringUtils.cs" />
|
<Compile Include="Core\Utils\StringUtils.cs" />
|
||||||
|
<Compile Include="Core\Utils\TwitterUtils.cs" />
|
||||||
<Compile Include="Data\CombinedFileStream.cs" />
|
<Compile Include="Data\CombinedFileStream.cs" />
|
||||||
<Compile Include="Core\Other\Settings\Export\ExportFileFlags.cs" />
|
<Compile Include="Core\Other\Settings\Export\ExportFileFlags.cs" />
|
||||||
<Compile Include="Core\Other\Settings\Export\ExportManager.cs" />
|
<Compile Include="Core\Other\Settings\Export\ExportManager.cs" />
|
||||||
@@ -199,19 +204,17 @@
|
|||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="Core\Bridge\CallbackBridge.cs" />
|
<Compile Include="Core\Bridge\CallbackBridge.cs" />
|
||||||
<Compile Include="Data\CommandLineArgs.cs" />
|
<Compile Include="Data\CommandLineArgs.cs" />
|
||||||
<Compile Include="Core\Utils\CommandLineArgsParser.cs" />
|
|
||||||
<Compile Include="Core\Notification\Screenshot\FormNotificationScreenshotable.cs">
|
<Compile Include="Core\Notification\Screenshot\FormNotificationScreenshotable.cs">
|
||||||
<SubType>Form</SubType>
|
<SubType>Form</SubType>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="Core\Notification\Screenshot\TweetScreenshotManager.cs" />
|
<Compile Include="Core\Notification\Screenshot\TweetScreenshotManager.cs" />
|
||||||
<Compile Include="Data\Serialization\FileSerializer.cs" />
|
<Compile Include="Data\Serialization\FileSerializer.cs" />
|
||||||
<Compile Include="Data\InjectedHTML.cs" />
|
<Compile Include="Data\InjectedHTML.cs" />
|
||||||
<Compile Include="Data\Serialization\ISerializedObject.cs" />
|
|
||||||
<Compile Include="Data\Serialization\ITypeConverter.cs" />
|
<Compile Include="Data\Serialization\ITypeConverter.cs" />
|
||||||
<Compile Include="Data\Serialization\SingleTypeConverter.cs" />
|
<Compile Include="Data\Serialization\SingleTypeConverter.cs" />
|
||||||
<Compile Include="Data\TwoKeyDictionary.cs" />
|
<Compile Include="Data\TwoKeyDictionary.cs" />
|
||||||
<Compile Include="Data\WindowState.cs" />
|
<Compile Include="Data\WindowState.cs" />
|
||||||
<Compile Include="Core\Utils\MemoryUsageTracker.cs" />
|
<Compile Include="Core\Other\Management\MemoryUsageTracker.cs" />
|
||||||
<Compile Include="Core\Utils\WindowsUtils.cs" />
|
<Compile Include="Core\Utils\WindowsUtils.cs" />
|
||||||
<Compile Include="Core\Bridge\TweetDeckBridge.cs" />
|
<Compile Include="Core\Bridge\TweetDeckBridge.cs" />
|
||||||
<Compile Include="Core\Other\FormSettings.cs">
|
<Compile Include="Core\Other\FormSettings.cs">
|
||||||
@@ -296,15 +299,11 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<EmbeddedResource Include="Core\FormBrowser.resx">
|
<EmbeddedResource Include="Core\FormBrowser.resx">
|
||||||
<DependentUpon>FormBrowser.cs</DependentUpon>
|
<DependentUpon>FormBrowser.cs</DependentUpon>
|
||||||
|
<SubType>Designer</SubType>
|
||||||
</EmbeddedResource>
|
</EmbeddedResource>
|
||||||
<EmbeddedResource Include="Core\Other\FormAbout.resx">
|
<EmbeddedResource Include="Core\Other\FormAbout.resx">
|
||||||
<DependentUpon>FormAbout.cs</DependentUpon>
|
<DependentUpon>FormAbout.cs</DependentUpon>
|
||||||
</EmbeddedResource>
|
<SubType>Designer</SubType>
|
||||||
<EmbeddedResource Include="Core\Other\FormPlugins.resx">
|
|
||||||
<DependentUpon>FormPlugins.cs</DependentUpon>
|
|
||||||
</EmbeddedResource>
|
|
||||||
<EmbeddedResource Include="Core\Other\FormSettings.resx">
|
|
||||||
<DependentUpon>FormSettings.cs</DependentUpon>
|
|
||||||
</EmbeddedResource>
|
</EmbeddedResource>
|
||||||
<EmbeddedResource Include="Updates\FormUpdateDownload.resx">
|
<EmbeddedResource Include="Updates\FormUpdateDownload.resx">
|
||||||
<DependentUpon>FormUpdateDownload.cs</DependentUpon>
|
<DependentUpon>FormUpdateDownload.cs</DependentUpon>
|
||||||
@@ -325,6 +324,7 @@
|
|||||||
<None Include="Resources\icon-small.ico" />
|
<None Include="Resources\icon-small.ico" />
|
||||||
<None Include="Resources\icon-tray-new.ico" />
|
<None Include="Resources\icon-tray-new.ico" />
|
||||||
<None Include="Resources\icon-tray.ico" />
|
<None Include="Resources\icon-tray.ico" />
|
||||||
|
<None Include="Resources\PostBuild.ps1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="Resources\Plugins\" />
|
<Folder Include="Resources\Plugins\" />
|
||||||
@@ -349,11 +349,18 @@
|
|||||||
<Project>{b10b0017-819e-4f71-870f-8256b36a26aa}</Project>
|
<Project>{b10b0017-819e-4f71-870f-8256b36a26aa}</Project>
|
||||||
<Name>TweetDuck.Browser</Name>
|
<Name>TweetDuck.Browser</Name>
|
||||||
</ProjectReference>
|
</ProjectReference>
|
||||||
|
<ProjectReference Include="video\TweetDuck.Video\TweetDuck.Video.csproj">
|
||||||
|
<Project>{278b2d11-402d-44b6-b6a1-8fa67db65565}</Project>
|
||||||
|
<Name>TweetDuck.Video</Name>
|
||||||
|
</ProjectReference>
|
||||||
|
<ProjectReference Include="lib\TweetLib.Communication\TweetLib.Communication.csproj">
|
||||||
|
<Project>{72473763-4b9d-4fb6-a923-9364b2680f06}</Project>
|
||||||
|
<Name>TweetLib.Communication</Name>
|
||||||
|
</ProjectReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<PostBuildEvent>del "$(TargetPath).config"
|
<PostBuildEvent>xcopy "$(ProjectDir)LICENSE.md" "$(TargetDir)" /Y
|
||||||
xcopy "$(ProjectDir)LICENSE.md" "$(TargetDir)" /Y
|
|
||||||
del "$(TargetDir)LICENSE.txt"
|
del "$(TargetDir)LICENSE.txt"
|
||||||
ren "$(TargetDir)LICENSE.md" "LICENSE.txt"
|
ren "$(TargetDir)LICENSE.md" "LICENSE.txt"
|
||||||
xcopy "$(ProjectDir)bld\Resources\CEFSHARP-LICENSE.txt" "$(TargetDir)" /Y
|
xcopy "$(ProjectDir)bld\Resources\CEFSHARP-LICENSE.txt" "$(TargetDir)" /Y
|
||||||
@@ -371,14 +378,16 @@ rmdir "$(ProjectDir)bin\Debug"
|
|||||||
rmdir "$(ProjectDir)bin\Release"
|
rmdir "$(ProjectDir)bin\Release"
|
||||||
|
|
||||||
rmdir "$(TargetDir)plugins\official\.debug" /S /Q
|
rmdir "$(TargetDir)plugins\official\.debug" /S /Q
|
||||||
|
del "$(TargetDir)plugins\official\emoji-keyboard\emoji-instructions.txt"
|
||||||
|
|
||||||
if $(ConfigurationName) == Debug (
|
if $(ConfigurationName) == Debug (
|
||||||
rmdir "$(TargetDir)plugins\official\.debug" /S /Q
|
rmdir "$(TargetDir)plugins\official\.debug" /S /Q
|
||||||
mkdir "$(TargetDir)plugins\user\.debug"
|
mkdir "$(TargetDir)plugins\user\.debug"
|
||||||
xcopy "$(ProjectDir)Resources\Plugins\.debug\*" "$(TargetDir)plugins\user\.debug\" /E /Y
|
xcopy "$(ProjectDir)Resources\Plugins\.debug\*" "$(TargetDir)plugins\user\.debug\" /E /Y
|
||||||
)</PostBuildEvent>
|
)
|
||||||
|
|
||||||
|
powershell -ExecutionPolicy Unrestricted -File "$(ProjectDir)Resources\PostBuild.ps1" "$(TargetDir)\"</PostBuildEvent>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<Import Project="packages\cef.redist.x64.3.2987.1601\build\cef.redist.x64.targets" Condition="Exists('packages\cef.redist.x64.3.2987.1601\build\cef.redist.x64.targets')" />
|
|
||||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||||
@@ -390,14 +399,18 @@ if $(ConfigurationName) == Debug (
|
|||||||
<Error Condition="!Exists('packages\CefSharp.WinForms.57.0.0\build\CefSharp.WinForms.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.WinForms.57.0.0\build\CefSharp.WinForms.props'))" />
|
<Error Condition="!Exists('packages\CefSharp.WinForms.57.0.0\build\CefSharp.WinForms.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.WinForms.57.0.0\build\CefSharp.WinForms.props'))" />
|
||||||
<Error Condition="!Exists('packages\CefSharp.WinForms.57.0.0\build\CefSharp.WinForms.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.WinForms.57.0.0\build\CefSharp.WinForms.targets'))" />
|
<Error Condition="!Exists('packages\CefSharp.WinForms.57.0.0\build\CefSharp.WinForms.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.WinForms.57.0.0\build\CefSharp.WinForms.targets'))" />
|
||||||
</Target>
|
</Target>
|
||||||
|
<Import Project="packages\cef.redist.x64.3.2987.1601\build\cef.redist.x64.targets" Condition="Exists('packages\cef.redist.x64.3.2987.1601\build\cef.redist.x64.targets')" />
|
||||||
<Import Project="packages\cef.redist.x86.3.2987.1601\build\cef.redist.x86.targets" Condition="Exists('packages\cef.redist.x86.3.2987.1601\build\cef.redist.x86.targets')" />
|
<Import Project="packages\cef.redist.x86.3.2987.1601\build\cef.redist.x86.targets" Condition="Exists('packages\cef.redist.x86.3.2987.1601\build\cef.redist.x86.targets')" />
|
||||||
<Import Project="packages\CefSharp.Common.57.0.0\build\CefSharp.Common.targets" Condition="Exists('packages\CefSharp.Common.57.0.0\build\CefSharp.Common.targets')" />
|
<Import Project="packages\CefSharp.Common.57.0.0\build\CefSharp.Common.targets" Condition="Exists('packages\CefSharp.Common.57.0.0\build\CefSharp.Common.targets')" />
|
||||||
<Import Project="packages\CefSharp.WinForms.57.0.0\build\CefSharp.WinForms.targets" Condition="Exists('packages\CefSharp.WinForms.57.0.0\build\CefSharp.WinForms.targets')" />
|
<Import Project="packages\CefSharp.WinForms.57.0.0\build\CefSharp.WinForms.targets" Condition="Exists('packages\CefSharp.WinForms.57.0.0\build\CefSharp.WinForms.targets')" />
|
||||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
<Target Name="AfterBuild" Condition="$(ConfigurationName) == Release">
|
||||||
Other similar extension points exist, see Microsoft.Common.targets.
|
<Exec Command="del "$(TargetDir)*.pdb"" />
|
||||||
<Target Name="BeforeBuild">
|
<Exec Command="del "$(TargetDir)*.xml"" />
|
||||||
|
<Delete Files="$(TargetDir)CefSharp.BrowserSubprocess.exe" />
|
||||||
|
<Delete Files="$(TargetDir)devtools_resources.pak" />
|
||||||
|
<Delete Files="$(TargetDir)widevinecdmadapter.dll" />
|
||||||
</Target>
|
</Target>
|
||||||
<Target Name="AfterBuild">
|
<PropertyGroup>
|
||||||
</Target>
|
<PreBuildEvent>powershell Get-Process TweetDuck.Browser -ErrorAction SilentlyContinue ^| Where-Object {$_.Path -eq '$(TargetDir)TweetDuck.Browser.exe'} ^| Stop-Process; Exit 0</PreBuildEvent>
|
||||||
-->
|
</PropertyGroup>
|
||||||
</Project>
|
</Project>
|
@@ -1,6 +1,6 @@
|
|||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio 15
|
# Visual Studio 15
|
||||||
VisualStudioVersion = 15.0.26430.12
|
VisualStudioVersion = 15.0.26430.16
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetDuck", "TweetDuck.csproj", "{2389A7CD-E0D3-4706-8294-092929A33A2D}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetDuck", "TweetDuck.csproj", "{2389A7CD-E0D3-4706-8294-092929A33A2D}"
|
||||||
EndProject
|
EndProject
|
||||||
@@ -10,6 +10,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests", "tests\UnitTest
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetLib.Audio", "lib\TweetLib.Audio\TweetLib.Audio.csproj", "{E9E1FD1B-F480-45B7-9970-BE2ECFD309AC}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetLib.Audio", "lib\TweetLib.Audio\TweetLib.Audio.csproj", "{E9E1FD1B-F480-45B7-9970-BE2ECFD309AC}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetDuck.Video", "video\TweetDuck.Video\TweetDuck.Video.csproj", "{278B2D11-402D-44B6-B6A1-8FA67DB65565}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetLib.Communication", "lib\TweetLib.Communication\TweetLib.Communication.csproj", "{72473763-4B9D-4FB6-A923-9364B2680F06}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|x86 = Debug|x86
|
Debug|x86 = Debug|x86
|
||||||
@@ -32,6 +36,14 @@ Global
|
|||||||
{E9E1FD1B-F480-45B7-9970-BE2ECFD309AC}.Debug|x86.Build.0 = Debug|x86
|
{E9E1FD1B-F480-45B7-9970-BE2ECFD309AC}.Debug|x86.Build.0 = Debug|x86
|
||||||
{E9E1FD1B-F480-45B7-9970-BE2ECFD309AC}.Release|x86.ActiveCfg = Release|x86
|
{E9E1FD1B-F480-45B7-9970-BE2ECFD309AC}.Release|x86.ActiveCfg = Release|x86
|
||||||
{E9E1FD1B-F480-45B7-9970-BE2ECFD309AC}.Release|x86.Build.0 = Release|x86
|
{E9E1FD1B-F480-45B7-9970-BE2ECFD309AC}.Release|x86.Build.0 = Release|x86
|
||||||
|
{278B2D11-402D-44B6-B6A1-8FA67DB65565}.Debug|x86.ActiveCfg = Debug|x86
|
||||||
|
{278B2D11-402D-44B6-B6A1-8FA67DB65565}.Debug|x86.Build.0 = Debug|x86
|
||||||
|
{278B2D11-402D-44B6-B6A1-8FA67DB65565}.Release|x86.ActiveCfg = Release|x86
|
||||||
|
{278B2D11-402D-44B6-B6A1-8FA67DB65565}.Release|x86.Build.0 = Release|x86
|
||||||
|
{72473763-4B9D-4FB6-A923-9364B2680F06}.Debug|x86.ActiveCfg = Debug|x86
|
||||||
|
{72473763-4B9D-4FB6-A923-9364B2680F06}.Debug|x86.Build.0 = Debug|x86
|
||||||
|
{72473763-4B9D-4FB6-A923-9364B2680F06}.Release|x86.ActiveCfg = Release|x86
|
||||||
|
{72473763-4B9D-4FB6-A923-9364B2680F06}.Release|x86.Build.0 = Release|x86
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
@@ -30,7 +30,7 @@ namespace TweetDuck.Updates{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
|
private void browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
|
||||||
if (e.Frame.IsMain && BrowserUtils.IsTweetDeckWebsite(e.Frame)){
|
if (e.Frame.IsMain && TwitterUtils.IsTweetDeckWebsite(e.Frame)){
|
||||||
ScriptLoader.ExecuteFile(e.Frame, "update.js");
|
ScriptLoader.ExecuteFile(e.Frame, "update.js");
|
||||||
Check(false);
|
Check(false);
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +0,0 @@
|
|||||||
del "bin\x86\Release\*.xml"
|
|
||||||
del "bin\x86\Release\*.pdb"
|
|
||||||
del "bin\x86\Release\CefSharp.BrowserSubprocess.exe"
|
|
||||||
del "bin\x86\Release\devtools_resources.pak"
|
|
||||||
del "bin\x86\Release\d3dcompiler_43.dll"
|
|
||||||
del "bin\x86\Release\widevinecdmadapter.dll"
|
|
@@ -21,7 +21,6 @@ DefaultDirName={pf}\{#MyAppName}
|
|||||||
DefaultGroupName={#MyAppName}
|
DefaultGroupName={#MyAppName}
|
||||||
OutputBaseFilename={#MyAppName}
|
OutputBaseFilename={#MyAppName}
|
||||||
VersionInfoVersion={#MyAppVersion}
|
VersionInfoVersion={#MyAppVersion}
|
||||||
LicenseFile=.\Resources\LICENSE
|
|
||||||
SetupIconFile=.\Resources\icon.ico
|
SetupIconFile=.\Resources\icon.ico
|
||||||
Uninstallable=TDIsUninstallable
|
Uninstallable=TDIsUninstallable
|
||||||
UninstallDisplayName={#MyAppName}
|
UninstallDisplayName={#MyAppName}
|
||||||
|
@@ -21,7 +21,6 @@ DefaultDirName={sd}\{#MyAppName}
|
|||||||
DefaultGroupName={#MyAppName}
|
DefaultGroupName={#MyAppName}
|
||||||
OutputBaseFilename={#MyAppName}.Portable
|
OutputBaseFilename={#MyAppName}.Portable
|
||||||
VersionInfoVersion={#MyAppVersion}
|
VersionInfoVersion={#MyAppVersion}
|
||||||
LicenseFile=.\Resources\LICENSE
|
|
||||||
SetupIconFile=.\Resources\icon.ico
|
SetupIconFile=.\Resources\icon.ico
|
||||||
Uninstallable=no
|
Uninstallable=no
|
||||||
UsePreviousAppDir=no
|
UsePreviousAppDir=no
|
||||||
|
@@ -23,7 +23,6 @@ DefaultDirName={pf}\{#MyAppName}
|
|||||||
DefaultGroupName={#MyAppName}
|
DefaultGroupName={#MyAppName}
|
||||||
OutputBaseFilename={#MyAppName}.Update
|
OutputBaseFilename={#MyAppName}.Update
|
||||||
VersionInfoVersion={#MyAppVersion}
|
VersionInfoVersion={#MyAppVersion}
|
||||||
LicenseFile=.\Resources\LICENSE
|
|
||||||
SetupIconFile=.\Resources\icon.ico
|
SetupIconFile=.\Resources\icon.ico
|
||||||
Uninstallable=TDIsUninstallable
|
Uninstallable=TDIsUninstallable
|
||||||
UninstallDisplayName={#MyAppName}
|
UninstallDisplayName={#MyAppName}
|
||||||
@@ -59,6 +58,9 @@ Type: filesandordirs; Name: "{app}\scripts"
|
|||||||
Type: filesandordirs; Name: "{localappdata}\{#MyAppName}\Cache"
|
Type: filesandordirs; Name: "{localappdata}\{#MyAppName}\Cache"
|
||||||
Type: filesandordirs; Name: "{localappdata}\{#MyAppName}\GPUCache"
|
Type: filesandordirs; Name: "{localappdata}\{#MyAppName}\GPUCache"
|
||||||
|
|
||||||
|
[InstallDelete]
|
||||||
|
Type: files; Name: "{app}\plugins\official\emoji-keyboard\emoji-instructions.txt"
|
||||||
|
|
||||||
[Code]
|
[Code]
|
||||||
function TDIsUninstallable: Boolean; forward;
|
function TDIsUninstallable: Boolean; forward;
|
||||||
function TDGetRunArgs(Param: String): String; forward;
|
function TDGetRunArgs(Param: String): String; forward;
|
||||||
|
34
lib/TweetLib.Communication/Comms.cs
Normal file
34
lib/TweetLib.Communication/Comms.cs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
using System;
|
||||||
|
using TweetLib.Communication.Utils;
|
||||||
|
|
||||||
|
namespace TweetLib.Communication{
|
||||||
|
public static class Comms{
|
||||||
|
public static void SendMessage(IntPtr hWnd, uint msg, uint wParam, int lParam){
|
||||||
|
NativeMethods.SendMessage(hWnd, msg, new UIntPtr(wParam), new IntPtr(lParam));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SendMessage(IntPtr hWnd, uint msg, UIntPtr wParam, IntPtr lParam){
|
||||||
|
NativeMethods.SendMessage(hWnd, msg, wParam, lParam);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void PostMessage(IntPtr hWnd, uint msg, uint wParam, int lParam){
|
||||||
|
NativeMethods.PostMessage(hWnd, msg, new UIntPtr(wParam), new IntPtr(lParam));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void PostMessage(IntPtr hWnd, uint msg, UIntPtr wParam, IntPtr lParam){
|
||||||
|
NativeMethods.PostMessage(hWnd, msg, wParam, lParam);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void BroadcastMessage(uint msg, uint wParam, int lParam){
|
||||||
|
NativeMethods.PostMessage(NativeMethods.HWND_BROADCAST, msg, new UIntPtr(wParam), new IntPtr(lParam));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void BroadcastMessage(uint msg, UIntPtr wParam, IntPtr lParam){
|
||||||
|
NativeMethods.PostMessage(NativeMethods.HWND_BROADCAST, msg, wParam, lParam);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static uint RegisterMessage(string name){
|
||||||
|
return NativeMethods.RegisterWindowMessage(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
108
lib/TweetLib.Communication/DuplexPipe.cs
Normal file
108
lib/TweetLib.Communication/DuplexPipe.cs
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.IO.Pipes;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
namespace TweetLib.Communication{
|
||||||
|
public abstract class DuplexPipe : IDisposable{
|
||||||
|
private const string Separator = "\x1F";
|
||||||
|
|
||||||
|
public static Server CreateServer(){
|
||||||
|
return new Server();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Client CreateClient(string token){
|
||||||
|
int space = token.IndexOf(' ');
|
||||||
|
return new Client(token.Substring(0, space), token.Substring(space+1));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected readonly PipeStream pipeIn;
|
||||||
|
protected readonly PipeStream pipeOut;
|
||||||
|
|
||||||
|
private readonly Thread readerThread;
|
||||||
|
private readonly StreamWriter writerStream;
|
||||||
|
|
||||||
|
public event EventHandler<PipeReadEventArgs> DataIn;
|
||||||
|
|
||||||
|
protected DuplexPipe(PipeStream pipeIn, PipeStream pipeOut){
|
||||||
|
this.pipeIn = pipeIn;
|
||||||
|
this.pipeOut = pipeOut;
|
||||||
|
|
||||||
|
this.readerThread = new Thread(ReaderThread){
|
||||||
|
IsBackground = true
|
||||||
|
};
|
||||||
|
|
||||||
|
this.readerThread.Start();
|
||||||
|
this.writerStream = new StreamWriter(this.pipeOut);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReaderThread(){
|
||||||
|
using(StreamReader read = new StreamReader(pipeIn)){
|
||||||
|
string data;
|
||||||
|
|
||||||
|
while((data = read.ReadLine()) != null){
|
||||||
|
DataIn?.Invoke(this, new PipeReadEventArgs(data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Write(string key){
|
||||||
|
writerStream.WriteLine(key);
|
||||||
|
writerStream.Flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Write(string key, string data){
|
||||||
|
writerStream.WriteLine(string.Concat(key, Separator, data));
|
||||||
|
writerStream.Flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose(){
|
||||||
|
try{
|
||||||
|
readerThread.Abort();
|
||||||
|
}catch{
|
||||||
|
// /shrug
|
||||||
|
}
|
||||||
|
|
||||||
|
pipeIn.Dispose();
|
||||||
|
writerStream.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class Server : DuplexPipe{
|
||||||
|
private AnonymousPipeServerStream ServerPipeIn => (AnonymousPipeServerStream)pipeIn;
|
||||||
|
private AnonymousPipeServerStream ServerPipeOut => (AnonymousPipeServerStream)pipeOut;
|
||||||
|
|
||||||
|
internal Server() : base(new AnonymousPipeServerStream(PipeDirection.In, HandleInheritability.Inheritable), new AnonymousPipeServerStream(PipeDirection.Out, HandleInheritability.Inheritable)){}
|
||||||
|
|
||||||
|
public string GenerateToken(){
|
||||||
|
return ServerPipeIn.GetClientHandleAsString()+" "+ServerPipeOut.GetClientHandleAsString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DisposeToken(){
|
||||||
|
ServerPipeIn.DisposeLocalCopyOfClientHandle();
|
||||||
|
ServerPipeOut.DisposeLocalCopyOfClientHandle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class Client : DuplexPipe{
|
||||||
|
internal Client(string handleOut, string handleIn) : base(new AnonymousPipeClientStream(PipeDirection.In, handleIn), new AnonymousPipeClientStream(PipeDirection.Out, handleOut)){}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class PipeReadEventArgs : EventArgs{
|
||||||
|
public string Key { get; }
|
||||||
|
public string Data { get; }
|
||||||
|
|
||||||
|
internal PipeReadEventArgs(string line){
|
||||||
|
int separatorIndex = line.IndexOf(Separator, StringComparison.Ordinal);
|
||||||
|
|
||||||
|
if (separatorIndex == -1){
|
||||||
|
Key = line;
|
||||||
|
Data = string.Empty;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
Key = line.Substring(0, separatorIndex);
|
||||||
|
Data = line.Substring(separatorIndex+1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
35
lib/TweetLib.Communication/Properties/AssemblyInfo.cs
Normal file
35
lib/TweetLib.Communication/Properties/AssemblyInfo.cs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
// General Information about an assembly is controlled through the following
|
||||||
|
// set of attributes. Change these attribute values to modify the information
|
||||||
|
// associated with an assembly.
|
||||||
|
[assembly: AssemblyTitle("TweetDuck Communication Library")]
|
||||||
|
[assembly: AssemblyDescription("")]
|
||||||
|
[assembly: AssemblyConfiguration("")]
|
||||||
|
[assembly: AssemblyCompany("")]
|
||||||
|
[assembly: AssemblyProduct("TweetDuck Communication Library")]
|
||||||
|
[assembly: AssemblyCopyright("")]
|
||||||
|
[assembly: AssemblyTrademark("")]
|
||||||
|
[assembly: AssemblyCulture("")]
|
||||||
|
|
||||||
|
// Setting ComVisible to false makes the types in this assembly not visible
|
||||||
|
// to COM components. If you need to access a type in this assembly from
|
||||||
|
// COM, set the ComVisible attribute to true on that type.
|
||||||
|
[assembly: ComVisible(false)]
|
||||||
|
|
||||||
|
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||||
|
[assembly: Guid("72473763-4b9d-4fb6-a923-9364b2680f06")]
|
||||||
|
|
||||||
|
// Version information for an assembly consists of the following four values:
|
||||||
|
//
|
||||||
|
// Major Version
|
||||||
|
// Minor Version
|
||||||
|
// Build Number
|
||||||
|
// Revision
|
||||||
|
//
|
||||||
|
// You can specify all the values or you can default the Build and Revision Numbers
|
||||||
|
// by using the '*' as shown below:
|
||||||
|
// [assembly: AssemblyVersion("1.0.*")]
|
||||||
|
[assembly: AssemblyVersion("1.0.0.0")]
|
||||||
|
[assembly: AssemblyFileVersion("1.0.0.0")]
|
44
lib/TweetLib.Communication/TweetLib.Communication.csproj
Normal file
44
lib/TweetLib.Communication/TweetLib.Communication.csproj
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||||
|
<PropertyGroup>
|
||||||
|
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||||
|
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||||
|
<ProjectGuid>{72473763-4B9D-4FB6-A923-9364B2680F06}</ProjectGuid>
|
||||||
|
<OutputType>Library</OutputType>
|
||||||
|
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||||
|
<RootNamespace>TweetLib.Communication</RootNamespace>
|
||||||
|
<AssemblyName>TweetLib.Communication</AssemblyName>
|
||||||
|
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
|
||||||
|
<FileAlignment>512</FileAlignment>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
|
||||||
|
<DebugSymbols>true</DebugSymbols>
|
||||||
|
<OutputPath>bin\x86\Debug\</OutputPath>
|
||||||
|
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||||
|
<DebugType>full</DebugType>
|
||||||
|
<PlatformTarget>x86</PlatformTarget>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
|
||||||
|
<OutputPath>bin\x86\Release\</OutputPath>
|
||||||
|
<DefineConstants>TRACE</DefineConstants>
|
||||||
|
<Optimize>true</Optimize>
|
||||||
|
<DebugType>none</DebugType>
|
||||||
|
<PlatformTarget>x86</PlatformTarget>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="System" />
|
||||||
|
<Reference Include="System.Core" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="DuplexPipe.cs" />
|
||||||
|
<Compile Include="Utils\NativeMethods.cs" />
|
||||||
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
|
<Compile Include="Comms.cs" />
|
||||||
|
</ItemGroup>
|
||||||
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
|
</Project>
|
@@ -1,10 +1,13 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace TweetDuck.Browser{
|
namespace TweetLib.Communication.Utils{
|
||||||
static class NativeMethods{
|
static class NativeMethods{
|
||||||
public static readonly IntPtr HWND_BROADCAST = new IntPtr(0xFFFF);
|
public static readonly IntPtr HWND_BROADCAST = new IntPtr(0xFFFF);
|
||||||
|
|
||||||
|
[DllImport("user32.dll")]
|
||||||
|
public static extern IntPtr SendMessage(IntPtr hWnd, uint msg, UIntPtr wParam, IntPtr lParam);
|
||||||
|
|
||||||
[DllImport("user32.dll")]
|
[DllImport("user32.dll")]
|
||||||
public static extern bool PostMessage(IntPtr hWnd, uint msg, UIntPtr wParam, IntPtr lParam);
|
public static extern bool PostMessage(IntPtr hWnd, uint msg, UIntPtr wParam, IntPtr lParam);
|
||||||
|
|
@@ -1,7 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using CefSharp;
|
using CefSharp;
|
||||||
using CefSharp.BrowserSubprocess;
|
using CefSharp.BrowserSubprocess;
|
||||||
|
using TweetLib.Communication;
|
||||||
|
|
||||||
namespace TweetDuck.Browser{
|
namespace TweetDuck.Browser{
|
||||||
static class Program{
|
static class Program{
|
||||||
@@ -26,7 +27,7 @@ namespace TweetDuck.Browser{
|
|||||||
base.OnBrowserCreated(wrapper);
|
base.OnBrowserCreated(wrapper);
|
||||||
|
|
||||||
using(Process me = Process.GetCurrentProcess()){
|
using(Process me = Process.GetCurrentProcess()){
|
||||||
NativeMethods.PostMessage(NativeMethods.HWND_BROADCAST, NativeMethods.RegisterWindowMessage("TweetDuckSubProcess"), new UIntPtr((uint)me.Id), new IntPtr(wrapper.BrowserId));
|
Comms.BroadcastMessage(Comms.RegisterMessage("TweetDuckSubProcess"), (uint)me.Id, wrapper.BrowserId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -31,5 +31,5 @@ using System.Runtime.InteropServices;
|
|||||||
// You can specify all the values or you can default the Build and Revision Numbers
|
// You can specify all the values or you can default the Build and Revision Numbers
|
||||||
// by using the '*' as shown below:
|
// by using the '*' as shown below:
|
||||||
// [assembly: AssemblyVersion("1.0.*")]
|
// [assembly: AssemblyVersion("1.0.*")]
|
||||||
[assembly: AssemblyVersion("1.0.1.0")]
|
[assembly: AssemblyVersion("1.1.0.0")]
|
||||||
[assembly: AssemblyFileVersion("1.0.1.0")]
|
[assembly: AssemblyFileVersion("1.1.0.0")]
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
@@ -31,10 +31,15 @@
|
|||||||
<Reference Include="System" />
|
<Reference Include="System" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="NativeMethods.cs" />
|
|
||||||
<Compile Include="Program.cs" />
|
<Compile Include="Program.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\lib\TweetLib.Communication\TweetLib.Communication.csproj">
|
||||||
|
<Project>{72473763-4b9d-4fb6-a923-9364b2680f06}</Project>
|
||||||
|
<Name>TweetLib.Communication</Name>
|
||||||
|
</ProjectReference>
|
||||||
|
</ItemGroup>
|
||||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<PostBuildEvent>call "$(DevEnvDir)..\..\VC\Auxiliary\Build\vcvars32.bat"
|
<PostBuildEvent>call "$(DevEnvDir)..\..\VC\Auxiliary\Build\vcvars32.bat"
|
||||||
|
@@ -1,33 +0,0 @@
|
|||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
|
||||||
using TweetDuck.Core.Utils;
|
|
||||||
using TweetDuck.Data;
|
|
||||||
|
|
||||||
namespace UnitTests.Core{
|
|
||||||
[TestClass]
|
|
||||||
public class TestCommandLineArgsParser{
|
|
||||||
[TestMethod]
|
|
||||||
public void TestEmptyString(){
|
|
||||||
Assert.AreEqual(0, CommandLineArgsParser.ReadCefArguments("").Count);
|
|
||||||
Assert.AreEqual(0, CommandLineArgsParser.ReadCefArguments(" ").Count);
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void TestValidString(){
|
|
||||||
CommandLineArgs args = CommandLineArgsParser.ReadCefArguments("--aaa --bbb --first-value=123 --SECOND-VALUE=\"a b c d e\"\r\n--ccc");
|
|
||||||
// cef has no flags, flag arguments have a value of 1
|
|
||||||
// the processing removes all dashes in front of each key
|
|
||||||
|
|
||||||
Assert.AreEqual(5, args.Count);
|
|
||||||
Assert.IsTrue(args.HasValue("aaa"));
|
|
||||||
Assert.IsTrue(args.HasValue("bbb"));
|
|
||||||
Assert.IsTrue(args.HasValue("ccc"));
|
|
||||||
Assert.IsTrue(args.HasValue("first-value"));
|
|
||||||
Assert.IsTrue(args.HasValue("second-value"));
|
|
||||||
Assert.AreEqual("1", args.GetValue("aaa", string.Empty));
|
|
||||||
Assert.AreEqual("1", args.GetValue("bbb", string.Empty));
|
|
||||||
Assert.AreEqual("1", args.GetValue("ccc", string.Empty));
|
|
||||||
Assert.AreEqual("123", args.GetValue("first-value", string.Empty));
|
|
||||||
Assert.AreEqual("a b c d e", args.GetValue("second-value", string.Empty));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
45
tests/Core/TestTwitterUtils.cs
Normal file
45
tests/Core/TestTwitterUtils.cs
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using TweetDuck.Core.Utils;
|
||||||
|
|
||||||
|
namespace UnitTests.Core{
|
||||||
|
[TestClass]
|
||||||
|
public class TestTwitterUtils{
|
||||||
|
[TestMethod]
|
||||||
|
public void TestAccountRegex(){
|
||||||
|
Assert.IsTrue(TwitterUtils.RegexAccount.IsMatch("http://twitter.com/chylexmc"));
|
||||||
|
Assert.IsTrue(TwitterUtils.RegexAccount.IsMatch("https://twitter.com/chylexmc"));
|
||||||
|
Assert.IsTrue(TwitterUtils.RegexAccount.IsMatch("http://twitter.com/chylexmc/"));
|
||||||
|
Assert.IsTrue(TwitterUtils.RegexAccount.IsMatch("https://twitter.com/chylexmc/"));
|
||||||
|
|
||||||
|
Assert.AreEqual("chylexmc", TwitterUtils.RegexAccount.Match("http://twitter.com/chylexmc").Groups[1].Value);
|
||||||
|
Assert.AreEqual("123", TwitterUtils.RegexAccount.Match("http://twitter.com/123").Groups[1].Value);
|
||||||
|
Assert.AreEqual("_", TwitterUtils.RegexAccount.Match("http://twitter.com/_").Groups[1].Value);
|
||||||
|
|
||||||
|
Assert.AreEqual("Abc_123", TwitterUtils.RegexAccount.Match("http://twitter.com/Abc_123").Groups[1].Value);
|
||||||
|
Assert.AreEqual("Abc_123", TwitterUtils.RegexAccount.Match("https://twitter.com/Abc_123/").Groups[1].Value);
|
||||||
|
|
||||||
|
Assert.IsFalse(TwitterUtils.RegexAccount.IsMatch("http://twitter.com/"));
|
||||||
|
Assert.IsFalse(TwitterUtils.RegexAccount.IsMatch("http://twitter.com/chylexmc/status"));
|
||||||
|
Assert.IsFalse(TwitterUtils.RegexAccount.IsMatch("http://nottwitter.com/chylexmc"));
|
||||||
|
Assert.IsFalse(TwitterUtils.RegexAccount.IsMatch("www.twitter.com/chylexmc"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestImageQualityLink(){
|
||||||
|
Assert.AreEqual("https://pbs.twimg.com/profile_images/123", TwitterUtils.GetImageLink("https://pbs.twimg.com/profile_images/123", TwitterUtils.ImageQuality.Default));
|
||||||
|
Assert.AreEqual("https://pbs.twimg.com/profile_images/123", TwitterUtils.GetImageLink("https://pbs.twimg.com/profile_images/123", TwitterUtils.ImageQuality.Orig));
|
||||||
|
|
||||||
|
Assert.AreEqual("https://pbs.twimg.com/profile_images/123.jpg", TwitterUtils.GetImageLink("https://pbs.twimg.com/profile_images/123.jpg", TwitterUtils.ImageQuality.Default));
|
||||||
|
Assert.AreEqual("https://pbs.twimg.com/profile_images/123.jpg", TwitterUtils.GetImageLink("https://pbs.twimg.com/profile_images/123.jpg", TwitterUtils.ImageQuality.Orig));
|
||||||
|
|
||||||
|
Assert.AreEqual("https://pbs.twimg.com/media/123.jpg", TwitterUtils.GetImageLink("https://pbs.twimg.com/media/123.jpg", TwitterUtils.ImageQuality.Default));
|
||||||
|
Assert.AreEqual("https://pbs.twimg.com/media/123.jpg:orig", TwitterUtils.GetImageLink("https://pbs.twimg.com/media/123.jpg", TwitterUtils.ImageQuality.Orig));
|
||||||
|
|
||||||
|
Assert.AreEqual("https://pbs.twimg.com/media/123.jpg:small", TwitterUtils.GetImageLink("https://pbs.twimg.com/media/123.jpg:small", TwitterUtils.ImageQuality.Default));
|
||||||
|
Assert.AreEqual("https://pbs.twimg.com/media/123.jpg:orig", TwitterUtils.GetImageLink("https://pbs.twimg.com/media/123.jpg:small", TwitterUtils.ImageQuality.Orig));
|
||||||
|
|
||||||
|
Assert.AreEqual("https://pbs.twimg.com/media/123.jpg:large", TwitterUtils.GetImageLink("https://pbs.twimg.com/media/123.jpg:large", TwitterUtils.ImageQuality.Default));
|
||||||
|
Assert.AreEqual("https://pbs.twimg.com/media/123.jpg:orig", TwitterUtils.GetImageLink("https://pbs.twimg.com/media/123.jpg:large", TwitterUtils.ImageQuality.Orig));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
using TweetDuck.Data;
|
using TweetDuck.Data;
|
||||||
|
|
||||||
@@ -139,5 +140,31 @@ namespace UnitTests.Data{
|
|||||||
|
|
||||||
Directory.Delete("cfs_directory", true);
|
Directory.Delete("cfs_directory", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestLongIdentifierSuccess(){
|
||||||
|
TestUtils.WriteText("cfs_long_identifier_fail_in", "test");
|
||||||
|
|
||||||
|
string identifier = string.Join("", Enumerable.Repeat("x", 255));
|
||||||
|
|
||||||
|
using(CombinedFileStream cfs = new CombinedFileStream(TestUtils.WriteFile("cfs_long_identifier_success"))){
|
||||||
|
cfs.WriteFile(identifier, "cfs_long_identifier_fail_in");
|
||||||
|
cfs.Flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
using(CombinedFileStream cfs = new CombinedFileStream(TestUtils.ReadFile("cfs_long_identifier_success"))){
|
||||||
|
Assert.AreEqual(identifier, cfs.ReadFile().Identifier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||||
|
public void TestLongIdentifierFail(){
|
||||||
|
TestUtils.WriteText("cfs_long_identifier_fail_in", "test");
|
||||||
|
|
||||||
|
using(CombinedFileStream cfs = new CombinedFileStream(TestUtils.WriteFile("cfs_long_identifier_fail"))){
|
||||||
|
cfs.WriteFile(string.Join("", Enumerable.Repeat("x", 256)), "cfs_long_identifier_fail_in");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -153,5 +153,30 @@ namespace UnitTests.Data{
|
|||||||
Assert.IsTrue(args.HasValue("-value"));
|
Assert.IsTrue(args.HasValue("-value"));
|
||||||
Assert.AreEqual("Here is some text!", args.GetValue("-value", string.Empty));
|
Assert.AreEqual("Here is some text!", args.GetValue("-value", string.Empty));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestCefEmptyString(){
|
||||||
|
Assert.AreEqual(0, CommandLineArgs.ReadCefArguments("").Count);
|
||||||
|
Assert.AreEqual(0, CommandLineArgs.ReadCefArguments(" ").Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestCefValidString(){
|
||||||
|
CommandLineArgs args = CommandLineArgs.ReadCefArguments("--aaa --bbb --first-value=123 --SECOND-VALUE=\"a b c d e\"\r\n--ccc");
|
||||||
|
// cef has no flags, flag arguments have a value of 1
|
||||||
|
// the processing removes all dashes in front of each key
|
||||||
|
|
||||||
|
Assert.AreEqual(5, args.Count);
|
||||||
|
Assert.IsTrue(args.HasValue("aaa"));
|
||||||
|
Assert.IsTrue(args.HasValue("bbb"));
|
||||||
|
Assert.IsTrue(args.HasValue("ccc"));
|
||||||
|
Assert.IsTrue(args.HasValue("first-value"));
|
||||||
|
Assert.IsTrue(args.HasValue("second-value"));
|
||||||
|
Assert.AreEqual("1", args.GetValue("aaa", string.Empty));
|
||||||
|
Assert.AreEqual("1", args.GetValue("bbb", string.Empty));
|
||||||
|
Assert.AreEqual("1", args.GetValue("ccc", string.Empty));
|
||||||
|
Assert.AreEqual("123", args.GetValue("first-value", string.Empty));
|
||||||
|
Assert.AreEqual("a b c d e", args.GetValue("second-value", string.Empty));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -10,16 +10,12 @@ namespace UnitTests.Data{
|
|||||||
A, B, C, D, E
|
A, B, C, D, E
|
||||||
}
|
}
|
||||||
|
|
||||||
private class SerializationTestBasic : ISerializedObject{
|
private class SerializationTestBasic{
|
||||||
public bool TestBool { get; set; }
|
public bool TestBool { get; set; }
|
||||||
public int TestInt { get; set; }
|
public int TestInt { get; set; }
|
||||||
public string TestString { get; set; }
|
public string TestString { get; set; }
|
||||||
public string TestStringNull { get; set; }
|
public string TestStringNull { get; set; }
|
||||||
public TestEnum TestEnum { get; set; }
|
public TestEnum TestEnum { get; set; }
|
||||||
|
|
||||||
bool ISerializedObject.OnReadUnknownProperty(string property, string value){
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
|
@@ -48,10 +48,10 @@
|
|||||||
</Choose>
|
</Choose>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="Core\TestStringUtils.cs" />
|
<Compile Include="Core\TestStringUtils.cs" />
|
||||||
|
<Compile Include="Core\TestTwitterUtils.cs" />
|
||||||
<Compile Include="Data\TestCombinedFileStream.cs" />
|
<Compile Include="Data\TestCombinedFileStream.cs" />
|
||||||
<Compile Include="Core\TestBrowserUtils.cs" />
|
<Compile Include="Core\TestBrowserUtils.cs" />
|
||||||
<Compile Include="Data\TestCommandLineArgs.cs" />
|
<Compile Include="Data\TestCommandLineArgs.cs" />
|
||||||
<Compile Include="Core\TestCommandLineArgsParser.cs" />
|
|
||||||
<Compile Include="Data\TestFileSerializer.cs" />
|
<Compile Include="Data\TestFileSerializer.cs" />
|
||||||
<Compile Include="Data\TestInjectedHTML.cs" />
|
<Compile Include="Data\TestInjectedHTML.cs" />
|
||||||
<Compile Include="Data\TestTwoKeyDictionary.cs" />
|
<Compile Include="Data\TestTwoKeyDictionary.cs" />
|
||||||
|
17
video/TweetDuck.Video/Controls/ControlWMP.cs
Normal file
17
video/TweetDuck.Video/Controls/ControlWMP.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
using System.ComponentModel;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using WMPLib;
|
||||||
|
|
||||||
|
namespace TweetDuck.Video.Controls{
|
||||||
|
[DesignTimeVisible(true)]
|
||||||
|
[Clsid("{6bf52a52-394a-11d3-b153-00c04f79faa6}")]
|
||||||
|
class ControlWMP : AxHost{
|
||||||
|
public WindowsMediaPlayer Ocx { get; private set; }
|
||||||
|
|
||||||
|
public ControlWMP() : base("6bf52a52-394a-11d3-b153-00c04f79faa6"){}
|
||||||
|
|
||||||
|
protected override void AttachInterfaces(){
|
||||||
|
Ocx = (WindowsMediaPlayer)GetOcx();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
57
video/TweetDuck.Video/Controls/SeekBar.cs
Normal file
57
video/TweetDuck.Video/Controls/SeekBar.cs
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
using System.Drawing;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
|
||||||
|
namespace TweetDuck.Video.Controls{
|
||||||
|
sealed class SeekBar : ProgressBar{
|
||||||
|
private readonly SolidBrush brushFore;
|
||||||
|
private readonly SolidBrush brushHover;
|
||||||
|
private readonly SolidBrush brushOverlap;
|
||||||
|
|
||||||
|
public SeekBar(){
|
||||||
|
brushFore = new SolidBrush(Color.White);
|
||||||
|
brushHover = new SolidBrush(Color.White);
|
||||||
|
brushOverlap = new SolidBrush(Color.White);
|
||||||
|
|
||||||
|
SetStyle(ControlStyles.UserPaint, true);
|
||||||
|
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnPaint(PaintEventArgs e){
|
||||||
|
if (brushFore.Color != ForeColor){
|
||||||
|
brushFore.Color = ForeColor;
|
||||||
|
brushHover.Color = Color.FromArgb(128, ForeColor);
|
||||||
|
brushOverlap.Color = Color.FromArgb(80+ForeColor.R*11/16, 80+ForeColor.G*11/16, 80+ForeColor.B*11/16);
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle rect = e.ClipRectangle;
|
||||||
|
Point cursor = PointToClient(Cursor.Position);
|
||||||
|
int width = rect.Width;
|
||||||
|
int progress = (int)(width*((double)Value/Maximum));
|
||||||
|
|
||||||
|
rect.Width = progress;
|
||||||
|
e.Graphics.FillRectangle(brushFore, rect);
|
||||||
|
|
||||||
|
if (cursor.X >= 0 && cursor.Y >= 0 && cursor.X <= width && cursor.Y <= rect.Height){
|
||||||
|
if (progress >= cursor.X){
|
||||||
|
rect.Width = cursor.X;
|
||||||
|
e.Graphics.FillRectangle(brushOverlap, rect);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
rect.X = progress;
|
||||||
|
rect.Width = cursor.X-rect.X;
|
||||||
|
e.Graphics.FillRectangle(brushHover, rect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing){
|
||||||
|
base.Dispose(disposing);
|
||||||
|
|
||||||
|
if (disposing){
|
||||||
|
brushFore.Dispose();
|
||||||
|
brushHover.Dispose();
|
||||||
|
brushOverlap.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
142
video/TweetDuck.Video/FormPlayer.Designer.cs
generated
Normal file
142
video/TweetDuck.Video/FormPlayer.Designer.cs
generated
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
namespace TweetDuck.Video {
|
||||||
|
partial class FormPlayer {
|
||||||
|
/// <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.components = new System.ComponentModel.Container();
|
||||||
|
this.timerSync = new System.Windows.Forms.Timer(this.components);
|
||||||
|
this.trackBarVolume = new System.Windows.Forms.TrackBar();
|
||||||
|
this.tablePanel = new System.Windows.Forms.TableLayoutPanel();
|
||||||
|
this.progressSeek = new TweetDuck.Video.Controls.SeekBar();
|
||||||
|
this.labelTime = new System.Windows.Forms.Label();
|
||||||
|
this.timerData = new System.Windows.Forms.Timer(this.components);
|
||||||
|
((System.ComponentModel.ISupportInitialize)(this.trackBarVolume)).BeginInit();
|
||||||
|
this.tablePanel.SuspendLayout();
|
||||||
|
this.SuspendLayout();
|
||||||
|
//
|
||||||
|
// timerSync
|
||||||
|
//
|
||||||
|
this.timerSync.Interval = 15;
|
||||||
|
this.timerSync.Tick += new System.EventHandler(this.timerSync_Tick);
|
||||||
|
//
|
||||||
|
// trackBarVolume
|
||||||
|
//
|
||||||
|
this.trackBarVolume.AutoSize = false;
|
||||||
|
this.trackBarVolume.BackColor = System.Drawing.SystemColors.Control;
|
||||||
|
this.trackBarVolume.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||||
|
this.trackBarVolume.Location = new System.Drawing.Point(158, 5);
|
||||||
|
this.trackBarVolume.Margin = new System.Windows.Forms.Padding(3, 5, 3, 3);
|
||||||
|
this.trackBarVolume.Maximum = 100;
|
||||||
|
this.trackBarVolume.Name = "trackBarVolume";
|
||||||
|
this.trackBarVolume.Size = new System.Drawing.Size(94, 26);
|
||||||
|
this.trackBarVolume.SmallChange = 5;
|
||||||
|
this.trackBarVolume.TabIndex = 2;
|
||||||
|
this.trackBarVolume.TickFrequency = 10;
|
||||||
|
this.trackBarVolume.TickStyle = System.Windows.Forms.TickStyle.None;
|
||||||
|
this.trackBarVolume.Value = 50;
|
||||||
|
this.trackBarVolume.ValueChanged += new System.EventHandler(this.trackBarVolume_ValueChanged);
|
||||||
|
this.trackBarVolume.MouseDown += new System.Windows.Forms.MouseEventHandler(this.trackBarVolume_MouseDown);
|
||||||
|
this.trackBarVolume.MouseUp += new System.Windows.Forms.MouseEventHandler(this.trackBarVolume_MouseUp);
|
||||||
|
//
|
||||||
|
// tablePanel
|
||||||
|
//
|
||||||
|
this.tablePanel.BackColor = System.Drawing.SystemColors.Control;
|
||||||
|
this.tablePanel.ColumnCount = 3;
|
||||||
|
this.tablePanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));
|
||||||
|
this.tablePanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 75F));
|
||||||
|
this.tablePanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 100F));
|
||||||
|
this.tablePanel.Controls.Add(this.trackBarVolume, 2, 0);
|
||||||
|
this.tablePanel.Controls.Add(this.progressSeek, 0, 0);
|
||||||
|
this.tablePanel.Controls.Add(this.labelTime, 1, 0);
|
||||||
|
this.tablePanel.Dock = System.Windows.Forms.DockStyle.Bottom;
|
||||||
|
this.tablePanel.Location = new System.Drawing.Point(0, 86);
|
||||||
|
this.tablePanel.Name = "tablePanel";
|
||||||
|
this.tablePanel.RowCount = 1;
|
||||||
|
this.tablePanel.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F));
|
||||||
|
this.tablePanel.Size = new System.Drawing.Size(255, 34);
|
||||||
|
this.tablePanel.TabIndex = 1;
|
||||||
|
//
|
||||||
|
// progressSeek
|
||||||
|
//
|
||||||
|
this.progressSeek.BackColor = System.Drawing.Color.White;
|
||||||
|
this.progressSeek.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||||
|
this.progressSeek.ForeColor = System.Drawing.Color.LimeGreen;
|
||||||
|
this.progressSeek.Location = new System.Drawing.Point(9, 10);
|
||||||
|
this.progressSeek.Margin = new System.Windows.Forms.Padding(9, 10, 9, 11);
|
||||||
|
this.progressSeek.Maximum = 5000;
|
||||||
|
this.progressSeek.Name = "progressSeek";
|
||||||
|
this.progressSeek.Size = new System.Drawing.Size(62, 13);
|
||||||
|
this.progressSeek.Style = System.Windows.Forms.ProgressBarStyle.Continuous;
|
||||||
|
this.progressSeek.TabIndex = 0;
|
||||||
|
this.progressSeek.MouseDown += new System.Windows.Forms.MouseEventHandler(this.progressSeek_MouseDown);
|
||||||
|
//
|
||||||
|
// labelTime
|
||||||
|
//
|
||||||
|
this.labelTime.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||||
|
this.labelTime.Location = new System.Drawing.Point(80, 2);
|
||||||
|
this.labelTime.Margin = new System.Windows.Forms.Padding(0, 2, 0, 5);
|
||||||
|
this.labelTime.Name = "labelTime";
|
||||||
|
this.labelTime.Size = new System.Drawing.Size(75, 27);
|
||||||
|
this.labelTime.TabIndex = 1;
|
||||||
|
this.labelTime.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
|
||||||
|
//
|
||||||
|
// timerData
|
||||||
|
//
|
||||||
|
this.timerData.Interval = 500;
|
||||||
|
this.timerData.Tick += new System.EventHandler(this.timerData_Tick);
|
||||||
|
//
|
||||||
|
// FormPlayer
|
||||||
|
//
|
||||||
|
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||||
|
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||||
|
this.BackColor = System.Drawing.Color.Black;
|
||||||
|
this.ClientSize = new System.Drawing.Size(255, 120);
|
||||||
|
this.ControlBox = false;
|
||||||
|
this.Controls.Add(this.tablePanel);
|
||||||
|
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
|
||||||
|
this.Location = new System.Drawing.Point(-32000, -32000);
|
||||||
|
this.MaximizeBox = false;
|
||||||
|
this.MinimizeBox = false;
|
||||||
|
this.Name = "FormPlayer";
|
||||||
|
this.ShowIcon = false;
|
||||||
|
this.ShowInTaskbar = false;
|
||||||
|
this.StartPosition = System.Windows.Forms.FormStartPosition.Manual;
|
||||||
|
this.Text = "TweetDuck Video";
|
||||||
|
this.Load += new System.EventHandler(this.FormPlayer_Load);
|
||||||
|
((System.ComponentModel.ISupportInitialize)(this.trackBarVolume)).EndInit();
|
||||||
|
this.tablePanel.ResumeLayout(false);
|
||||||
|
this.ResumeLayout(false);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
private System.Windows.Forms.Timer timerSync;
|
||||||
|
private System.Windows.Forms.TrackBar trackBarVolume;
|
||||||
|
private System.Windows.Forms.TableLayoutPanel tablePanel;
|
||||||
|
private Controls.SeekBar progressSeek;
|
||||||
|
private System.Windows.Forms.Label labelTime;
|
||||||
|
private System.Windows.Forms.Timer timerData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
249
video/TweetDuck.Video/FormPlayer.cs
Normal file
249
video/TweetDuck.Video/FormPlayer.cs
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
using System;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using TweetDuck.Video.Controls;
|
||||||
|
using TweetLib.Communication;
|
||||||
|
using WMPLib;
|
||||||
|
|
||||||
|
namespace TweetDuck.Video{
|
||||||
|
partial class FormPlayer : Form{
|
||||||
|
protected override bool ShowWithoutActivation => true;
|
||||||
|
|
||||||
|
private readonly IntPtr ownerHandle;
|
||||||
|
private readonly string videoUrl;
|
||||||
|
private readonly DuplexPipe pipe;
|
||||||
|
|
||||||
|
private readonly ControlWMP player;
|
||||||
|
private bool wasCursorInside;
|
||||||
|
private bool isPaused;
|
||||||
|
private bool isDragging;
|
||||||
|
|
||||||
|
private WindowsMediaPlayer Player => player.Ocx;
|
||||||
|
|
||||||
|
public FormPlayer(IntPtr handle, int volume, string url, string token){
|
||||||
|
InitializeComponent();
|
||||||
|
|
||||||
|
this.ownerHandle = handle;
|
||||||
|
this.videoUrl = url;
|
||||||
|
this.pipe = DuplexPipe.CreateClient(token);
|
||||||
|
this.pipe.DataIn += pipe_DataIn;
|
||||||
|
|
||||||
|
player = new ControlWMP{
|
||||||
|
Dock = DockStyle.Fill
|
||||||
|
};
|
||||||
|
|
||||||
|
player.BeginInit();
|
||||||
|
Controls.Add(player);
|
||||||
|
player.EndInit();
|
||||||
|
|
||||||
|
Player.enableContextMenu = false;
|
||||||
|
Player.uiMode = "none";
|
||||||
|
Player.settings.autoStart = false;
|
||||||
|
Player.settings.enableErrorDialogs = false;
|
||||||
|
Player.settings.setMode("loop", true);
|
||||||
|
|
||||||
|
Player.PlayStateChange += player_PlayStateChange;
|
||||||
|
Player.MediaError += player_MediaError;
|
||||||
|
|
||||||
|
trackBarVolume.Value = volume; // changes player volume too if non-default
|
||||||
|
|
||||||
|
Application.AddMessageFilter(new MessageFilter(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Events
|
||||||
|
|
||||||
|
private void FormPlayer_Load(object sender, EventArgs e){
|
||||||
|
Player.URL = videoUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void pipe_DataIn(object sender, DuplexPipe.PipeReadEventArgs e){
|
||||||
|
switch(e.Key){
|
||||||
|
case "pause":
|
||||||
|
TogglePause();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "die":
|
||||||
|
timerSync.Stop();
|
||||||
|
Visible = false;
|
||||||
|
pipe.Write("rip");
|
||||||
|
|
||||||
|
Close();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void player_PlayStateChange(int newState){
|
||||||
|
WMPPlayState state = (WMPPlayState)newState;
|
||||||
|
|
||||||
|
if (state == WMPPlayState.wmppsReady){
|
||||||
|
Player.controls.play();
|
||||||
|
}
|
||||||
|
else if (state == WMPPlayState.wmppsPlaying){
|
||||||
|
Player.PlayStateChange -= player_PlayStateChange;
|
||||||
|
|
||||||
|
timerSync.Start();
|
||||||
|
NativeMethods.SetWindowOwner(Handle, ownerHandle);
|
||||||
|
Cursor.Current = Cursors.Default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void player_MediaError(object pMediaObject){
|
||||||
|
Console.Out.WriteLine(((IWMPMedia2)pMediaObject).Error.errorDescription);
|
||||||
|
|
||||||
|
Marshal.ReleaseComObject(pMediaObject);
|
||||||
|
Environment.Exit(Program.CODE_MEDIA_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void timerSync_Tick(object sender, EventArgs e){
|
||||||
|
if (NativeMethods.GetWindowRect(ownerHandle, out NativeMethods.RECT rect)){
|
||||||
|
int width = rect.Right-rect.Left+1;
|
||||||
|
int height = rect.Bottom-rect.Top+1;
|
||||||
|
|
||||||
|
IWMPMedia media = Player.currentMedia;
|
||||||
|
IWMPControls controls = Player.controls;
|
||||||
|
|
||||||
|
bool isCursorInside = ClientRectangle.Contains(PointToClient(Cursor.Position));
|
||||||
|
|
||||||
|
ClientSize = new Size(Math.Min(media.imageSourceWidth, width*3/4), Math.Min(media.imageSourceHeight, height*3/4));
|
||||||
|
Location = new Point(rect.Left+(width-ClientSize.Width)/2, rect.Top+(height-ClientSize.Height+SystemInformation.CaptionHeight)/2);
|
||||||
|
|
||||||
|
tablePanel.Visible = isCursorInside || isDragging;
|
||||||
|
|
||||||
|
if (tablePanel.Visible){
|
||||||
|
labelTime.Text = $"{controls.currentPositionString} / {media.durationString}";
|
||||||
|
|
||||||
|
int value = (int)Math.Round(progressSeek.Maximum*controls.currentPosition/media.duration);
|
||||||
|
|
||||||
|
if (value >= progressSeek.Maximum){
|
||||||
|
progressSeek.Value = progressSeek.Maximum;
|
||||||
|
progressSeek.Value = progressSeek.Maximum-1;
|
||||||
|
progressSeek.Value = progressSeek.Maximum;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
progressSeek.Value = value+1;
|
||||||
|
progressSeek.Value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (controls.currentPosition > media.duration){ // pausing near the end of the video causes WMP to play beyond the end of the video wtf
|
||||||
|
controls.stop();
|
||||||
|
controls.currentPosition = 0;
|
||||||
|
controls.play();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isCursorInside && !wasCursorInside){
|
||||||
|
wasCursorInside = true;
|
||||||
|
}
|
||||||
|
else if (!isCursorInside && wasCursorInside){
|
||||||
|
wasCursorInside = false;
|
||||||
|
|
||||||
|
if (!Player.fullScreen){
|
||||||
|
NativeMethods.SetForegroundWindow(ownerHandle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Marshal.ReleaseComObject(media);
|
||||||
|
Marshal.ReleaseComObject(controls);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
Environment.Exit(Program.CODE_OWNER_GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void timerData_Tick(object sender, EventArgs e){
|
||||||
|
timerData.Stop();
|
||||||
|
pipe.Write("vol", trackBarVolume.Value.ToString(CultureInfo.InvariantCulture));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void progressSeek_MouseDown(object sender, MouseEventArgs e){
|
||||||
|
if (e.Button == MouseButtons.Left){
|
||||||
|
IWMPMedia media = Player.currentMedia;
|
||||||
|
IWMPControls controls = Player.controls;
|
||||||
|
|
||||||
|
controls.currentPosition = media.duration*progressSeek.PointToClient(Cursor.Position).X/progressSeek.Width;
|
||||||
|
|
||||||
|
Marshal.ReleaseComObject(media);
|
||||||
|
Marshal.ReleaseComObject(controls);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void trackBarVolume_ValueChanged(object sender, EventArgs e){
|
||||||
|
IWMPSettings settings = Player.settings;
|
||||||
|
settings.volume = trackBarVolume.Value;
|
||||||
|
|
||||||
|
Marshal.ReleaseComObject(settings);
|
||||||
|
|
||||||
|
if (timerSync.Enabled){
|
||||||
|
timerData.Stop();
|
||||||
|
timerData.Start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void trackBarVolume_MouseDown(object sender, MouseEventArgs e){
|
||||||
|
isDragging = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void trackBarVolume_MouseUp(object sender, MouseEventArgs e){
|
||||||
|
isDragging = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Controls & messages
|
||||||
|
|
||||||
|
private void TogglePause(){
|
||||||
|
IWMPControls controls = Player.controls;
|
||||||
|
|
||||||
|
if (isPaused){
|
||||||
|
controls.play();
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
controls.pause();
|
||||||
|
}
|
||||||
|
|
||||||
|
isPaused = !isPaused;
|
||||||
|
Marshal.ReleaseComObject(controls);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class MessageFilter : IMessageFilter{
|
||||||
|
private readonly FormPlayer form;
|
||||||
|
|
||||||
|
private bool IsCursorOverVideo{
|
||||||
|
get{
|
||||||
|
Point cursor = form.PointToClient(Cursor.Position);
|
||||||
|
return cursor.Y < form.tablePanel.Location.Y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public MessageFilter(FormPlayer form){
|
||||||
|
this.form = form;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IMessageFilter.PreFilterMessage(ref Message m){
|
||||||
|
if (m.Msg == 0x0201){ // WM_LBUTTONDOWN
|
||||||
|
if (IsCursorOverVideo){
|
||||||
|
form.TogglePause();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (m.Msg == 0x0203){ // WM_LBUTTONDBLCLK
|
||||||
|
if (IsCursorOverVideo){
|
||||||
|
form.TogglePause();
|
||||||
|
form.Player.fullScreen = !form.Player.fullScreen;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (m.Msg == 0x0100 && m.WParam.ToInt32() == 0x20){ // WM_KEYDOWN, VK_SPACE
|
||||||
|
form.TogglePause();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (m.Msg == 0x020B && ((m.WParam.ToInt32() >> 16) & 0xFFFF) == 1){ // WM_XBUTTONDOWN
|
||||||
|
NativeMethods.SetForegroundWindow(form.ownerHandle);
|
||||||
|
Environment.Exit(Program.CODE_USER_REQUESTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
38
video/TweetDuck.Video/NativeMethods.cs
Normal file
38
video/TweetDuck.Video/NativeMethods.cs
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace TweetDuck.Video{
|
||||||
|
static class NativeMethods{
|
||||||
|
private const int GWL_HWNDPARENT = -8;
|
||||||
|
|
||||||
|
[DllImport("user32.dll")]
|
||||||
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
|
public static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
|
||||||
|
|
||||||
|
[DllImport("user32.dll")]
|
||||||
|
private static extern int SetWindowLong(IntPtr hWnd, int nIndex, IntPtr dwNewLong);
|
||||||
|
|
||||||
|
[DllImport("user32.dll")]
|
||||||
|
public static extern bool SetForegroundWindow(IntPtr hWnd);
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct RECT{
|
||||||
|
public int Left;
|
||||||
|
public int Top;
|
||||||
|
public int Right;
|
||||||
|
public int Bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SetWindowOwner(IntPtr child, IntPtr owner){
|
||||||
|
SetWindowLong(child, GWL_HWNDPARENT, owner);
|
||||||
|
/*
|
||||||
|
* "You must not call SetWindowLong with the GWL_HWNDPARENT index to change the parent of a child window"
|
||||||
|
*
|
||||||
|
* ...which I'm not sure they're saying because this is completely unsupported and causes demons to come out of sewers
|
||||||
|
* ...or because GWL_HWNDPARENT actually changes the OWNER and is therefore NOT changing the parent of a child window
|
||||||
|
*
|
||||||
|
* ...so technically, this is following the documentation to the word.
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
45
video/TweetDuck.Video/Program.cs
Normal file
45
video/TweetDuck.Video/Program.cs
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using TweetLib.Communication;
|
||||||
|
|
||||||
|
namespace TweetDuck.Video{
|
||||||
|
static class Program{
|
||||||
|
// referenced in VideoPlayer
|
||||||
|
// set by task manager -- public const int CODE_PROCESS_KILLED = 1;
|
||||||
|
public const int CODE_INVALID_ARGS = 2;
|
||||||
|
public const int CODE_LAUNCH_FAIL = 3;
|
||||||
|
public const int CODE_MEDIA_ERROR = 4;
|
||||||
|
public const int CODE_OWNER_GONE = 5;
|
||||||
|
public const int CODE_USER_REQUESTED = 6;
|
||||||
|
|
||||||
|
[STAThread]
|
||||||
|
private static int Main(string[] args){
|
||||||
|
Application.EnableVisualStyles();
|
||||||
|
Application.SetCompatibleTextRenderingDefault(false);
|
||||||
|
|
||||||
|
IntPtr ownerHandle;
|
||||||
|
int defaultVolume;
|
||||||
|
string videoUrl;
|
||||||
|
string pipeToken;
|
||||||
|
|
||||||
|
try{
|
||||||
|
ownerHandle = new IntPtr(int.Parse(args[0], NumberStyles.Integer, CultureInfo.InvariantCulture));
|
||||||
|
defaultVolume = int.Parse(args[1], NumberStyles.Integer, CultureInfo.InvariantCulture);
|
||||||
|
videoUrl = new Uri(args[2], UriKind.Absolute).AbsoluteUri;
|
||||||
|
pipeToken = args[3];
|
||||||
|
}catch{
|
||||||
|
return CODE_INVALID_ARGS;
|
||||||
|
}
|
||||||
|
|
||||||
|
try{
|
||||||
|
Application.Run(new FormPlayer(ownerHandle, defaultVolume, videoUrl, pipeToken));
|
||||||
|
}catch(Exception e){
|
||||||
|
Console.Out.WriteLine(e.Message);
|
||||||
|
return CODE_LAUNCH_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
35
video/TweetDuck.Video/Properties/AssemblyInfo.cs
Normal file
35
video/TweetDuck.Video/Properties/AssemblyInfo.cs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
// General Information about an assembly is controlled through the following
|
||||||
|
// set of attributes. Change these attribute values to modify the information
|
||||||
|
// associated with an assembly.
|
||||||
|
[assembly: AssemblyTitle("TweetDuck Video")]
|
||||||
|
[assembly: AssemblyDescription("TweetDuck Video")]
|
||||||
|
[assembly: AssemblyConfiguration("")]
|
||||||
|
[assembly: AssemblyCompany("")]
|
||||||
|
[assembly: AssemblyProduct("TweetDuck.Video")]
|
||||||
|
[assembly: AssemblyCopyright("")]
|
||||||
|
[assembly: AssemblyTrademark("")]
|
||||||
|
[assembly: AssemblyCulture("")]
|
||||||
|
|
||||||
|
// Setting ComVisible to false makes the types in this assembly not visible
|
||||||
|
// to COM components. If you need to access a type in this assembly from
|
||||||
|
// COM, set the ComVisible attribute to true on that type.
|
||||||
|
[assembly: ComVisible(false)]
|
||||||
|
|
||||||
|
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||||
|
[assembly: Guid("278b2d11-402d-44b6-b6a1-8fa67db65565")]
|
||||||
|
|
||||||
|
// Version information for an assembly consists of the following four values:
|
||||||
|
//
|
||||||
|
// Major Version
|
||||||
|
// Minor Version
|
||||||
|
// Build Number
|
||||||
|
// Revision
|
||||||
|
//
|
||||||
|
// You can specify all the values or you can default the Build and Revision Numbers
|
||||||
|
// by using the '*' as shown below:
|
||||||
|
// [assembly: AssemblyVersion("1.0.*")]
|
||||||
|
[assembly: AssemblyVersion("1.0.0.0")]
|
||||||
|
[assembly: AssemblyFileVersion("1.0.0.0")]
|
BIN
video/TweetDuck.Video/Resources/icon.ico
Normal file
BIN
video/TweetDuck.Video/Resources/icon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
84
video/TweetDuck.Video/TweetDuck.Video.csproj
Normal file
84
video/TweetDuck.Video/TweetDuck.Video.csproj
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||||
|
<PropertyGroup>
|
||||||
|
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||||
|
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||||
|
<ProjectGuid>{278B2D11-402D-44B6-B6A1-8FA67DB65565}</ProjectGuid>
|
||||||
|
<OutputType>WinExe</OutputType>
|
||||||
|
<RootNamespace>TweetDuck.Video</RootNamespace>
|
||||||
|
<AssemblyName>TweetDuck.Video</AssemblyName>
|
||||||
|
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
|
||||||
|
<FileAlignment>512</FileAlignment>
|
||||||
|
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||||
|
<ResolveComReferenceSilent>True</ResolveComReferenceSilent>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
|
||||||
|
<DebugSymbols>true</DebugSymbols>
|
||||||
|
<OutputPath>bin\x86\Debug\</OutputPath>
|
||||||
|
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||||
|
<DebugType>full</DebugType>
|
||||||
|
<PlatformTarget>x86</PlatformTarget>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||||
|
<Prefer32Bit>true</Prefer32Bit>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
|
||||||
|
<OutputPath>bin\x86\Release\</OutputPath>
|
||||||
|
<DefineConstants>TRACE</DefineConstants>
|
||||||
|
<Optimize>true</Optimize>
|
||||||
|
<DebugType>none</DebugType>
|
||||||
|
<PlatformTarget>x86</PlatformTarget>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||||
|
<Prefer32Bit>true</Prefer32Bit>
|
||||||
|
<GenerateSerializationAssemblies>Auto</GenerateSerializationAssemblies>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup>
|
||||||
|
<ApplicationIcon>Resources\icon.ico</ApplicationIcon>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="System" />
|
||||||
|
<Reference Include="System.Core" />
|
||||||
|
<Reference Include="System.Drawing" />
|
||||||
|
<Reference Include="System.Windows.Forms" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="Controls\ControlWMP.cs">
|
||||||
|
<SubType>Component</SubType>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="Controls\SeekBar.cs">
|
||||||
|
<SubType>Component</SubType>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="FormPlayer.cs">
|
||||||
|
<SubType>Form</SubType>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="FormPlayer.Designer.cs">
|
||||||
|
<DependentUpon>FormPlayer.cs</DependentUpon>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="NativeMethods.cs" />
|
||||||
|
<Compile Include="Program.cs" />
|
||||||
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<COMReference Include="WMPLib">
|
||||||
|
<Guid>{6BF52A50-394A-11D3-B153-00C04F79FAA6}</Guid>
|
||||||
|
<VersionMajor>1</VersionMajor>
|
||||||
|
<VersionMinor>0</VersionMinor>
|
||||||
|
<Lcid>0</Lcid>
|
||||||
|
<WrapperTool>tlbimp</WrapperTool>
|
||||||
|
<Isolated>False</Isolated>
|
||||||
|
<EmbedInteropTypes>True</EmbedInteropTypes>
|
||||||
|
</COMReference>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Include="Resources\icon.ico" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\lib\TweetLib.Communication\TweetLib.Communication.csproj">
|
||||||
|
<Project>{72473763-4b9d-4fb6-a923-9364b2680f06}</Project>
|
||||||
|
<Name>TweetLib.Windows</Name>
|
||||||
|
</ProjectReference>
|
||||||
|
</ItemGroup>
|
||||||
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
|
</Project>
|
Reference in New Issue
Block a user