mirror of
https://github.com/chylex/TweetDuck.git
synced 2025-09-14 10:32:10 +02:00
Compare commits
1 Commits
1.18.2
...
taskbar-ov
Author | SHA1 | Date | |
---|---|---|---|
9238410756 |
4
.github/FUNDING.yml
vendored
4
.github/FUNDING.yml
vendored
@@ -1,4 +0,0 @@
|
|||||||
# These are supported funding model platforms
|
|
||||||
|
|
||||||
patreon: chylex
|
|
||||||
ko_fi: chylex
|
|
@@ -1,5 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using TweetLib.Core.Collections;
|
using TweetDuck.Data;
|
||||||
|
|
||||||
namespace TweetDuck.Configuration{
|
namespace TweetDuck.Configuration{
|
||||||
static class Arguments{
|
static class Arguments{
|
||||||
@@ -22,8 +22,8 @@ namespace TweetDuck.Configuration{
|
|||||||
return Current.HasFlag(flag);
|
return Current.HasFlag(flag);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GetValue(string key){
|
public static string GetValue(string key, string defaultValue){
|
||||||
return Current.GetValue(key);
|
return Current.GetValue(key, defaultValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static CommandLineArgs GetCurrentClean(){
|
public static CommandLineArgs GetCurrentClean(){
|
||||||
|
@@ -1,13 +1,13 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
|
using TweetDuck.Configuration.Instance;
|
||||||
|
using TweetDuck.Core.Utils;
|
||||||
using TweetDuck.Data;
|
using TweetDuck.Data;
|
||||||
using TweetLib.Core.Features.Configuration;
|
using TweetDuck.Data.Serialization;
|
||||||
using TweetLib.Core.Features.Plugins.Config;
|
|
||||||
using TweetLib.Core.Serialization.Converters;
|
|
||||||
using TweetLib.Core.Utils;
|
|
||||||
|
|
||||||
namespace TweetDuck.Configuration{
|
namespace TweetDuck.Configuration{
|
||||||
sealed class ConfigManager : IConfigManager{
|
sealed class ConfigManager{
|
||||||
public UserConfig User { get; }
|
public UserConfig User { get; }
|
||||||
public SystemConfig System { get; }
|
public SystemConfig System { get; }
|
||||||
public PluginConfig Plugins { get; }
|
public PluginConfig Plugins { get; }
|
||||||
@@ -16,7 +16,7 @@ namespace TweetDuck.Configuration{
|
|||||||
|
|
||||||
private readonly FileConfigInstance<UserConfig> infoUser;
|
private readonly FileConfigInstance<UserConfig> infoUser;
|
||||||
private readonly FileConfigInstance<SystemConfig> infoSystem;
|
private readonly FileConfigInstance<SystemConfig> infoSystem;
|
||||||
private readonly PluginConfigInstance<PluginConfig> infoPlugins;
|
private readonly PluginConfigInstance infoPlugins;
|
||||||
|
|
||||||
private readonly IConfigInstance<BaseConfig>[] infoList;
|
private readonly IConfigInstance<BaseConfig>[] infoList;
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ namespace TweetDuck.Configuration{
|
|||||||
infoList = new IConfigInstance<BaseConfig>[]{
|
infoList = new IConfigInstance<BaseConfig>[]{
|
||||||
infoUser = new FileConfigInstance<UserConfig>(Program.UserConfigFilePath, User, "program options"),
|
infoUser = new FileConfigInstance<UserConfig>(Program.UserConfigFilePath, User, "program options"),
|
||||||
infoSystem = new FileConfigInstance<SystemConfig>(Program.SystemConfigFilePath, System, "system options"),
|
infoSystem = new FileConfigInstance<SystemConfig>(Program.SystemConfigFilePath, System, "system options"),
|
||||||
infoPlugins = new PluginConfigInstance<PluginConfig>(Program.PluginConfigFilePath, Plugins)
|
infoPlugins = new PluginConfigInstance(Program.PluginConfigFilePath, Plugins)
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO refactor further
|
// TODO refactor further
|
||||||
@@ -70,13 +70,59 @@ namespace TweetDuck.Configuration{
|
|||||||
infoPlugins.Reload();
|
infoPlugins.Reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
void IConfigManager.TriggerProgramRestartRequested(){
|
private void TriggerProgramRestartRequested(){
|
||||||
ProgramRestartRequested?.Invoke(this, EventArgs.Empty);
|
ProgramRestartRequested?.Invoke(this, EventArgs.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
IConfigInstance<BaseConfig> IConfigManager.GetInstanceInfo(BaseConfig instance){
|
private IConfigInstance<BaseConfig> GetInstanceInfo(BaseConfig instance){
|
||||||
Type instanceType = instance.GetType();
|
Type instanceType = instance.GetType();
|
||||||
return Array.Find(infoList, info => info.Instance.GetType() == instanceType); // TODO handle null
|
return Array.Find(infoList, info => info.Instance.GetType() == instanceType); // TODO handle null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public abstract class BaseConfig{
|
||||||
|
private readonly ConfigManager configManager;
|
||||||
|
|
||||||
|
protected BaseConfig(ConfigManager configManager){
|
||||||
|
this.configManager = configManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Management
|
||||||
|
|
||||||
|
public void Save(){
|
||||||
|
configManager.GetInstanceInfo(this).Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reload(){
|
||||||
|
configManager.GetInstanceInfo(this).Reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reset(){
|
||||||
|
configManager.GetInstanceInfo(this).Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construction methods
|
||||||
|
|
||||||
|
public T ConstructWithDefaults<T>() where T : BaseConfig{
|
||||||
|
return ConstructWithDefaults(configManager) as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract BaseConfig ConstructWithDefaults(ConfigManager configManager);
|
||||||
|
|
||||||
|
// Utility methods
|
||||||
|
|
||||||
|
protected void UpdatePropertyWithEvent<T>(ref T field, T value, EventHandler eventHandler){
|
||||||
|
if (!EqualityComparer<T>.Default.Equals(field, value)){
|
||||||
|
field = value;
|
||||||
|
eventHandler?.Invoke(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void UpdatePropertyWithRestartRequest<T>(ref T field, T value){
|
||||||
|
if (!EqualityComparer<T>.Default.Equals(field, value)){
|
||||||
|
field = value;
|
||||||
|
configManager.TriggerProgramRestartRequested();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,20 +1,22 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using TweetLib.Core.Serialization;
|
using TweetDuck.Data.Serialization;
|
||||||
|
|
||||||
|
namespace TweetDuck.Configuration.Instance{
|
||||||
|
sealed class FileConfigInstance<T> : IConfigInstance<T> where T : ConfigManager.BaseConfig{
|
||||||
|
private const string ErrorTitle = "Configuration Error";
|
||||||
|
|
||||||
namespace TweetLib.Core.Features.Configuration{
|
|
||||||
public sealed class FileConfigInstance<T> : IConfigInstance<T> where T : BaseConfig{
|
|
||||||
public T Instance { get; }
|
public T Instance { get; }
|
||||||
public FileSerializer<T> Serializer { get; }
|
public FileSerializer<T> Serializer { get; }
|
||||||
|
|
||||||
private readonly string filenameMain;
|
private readonly string filenameMain;
|
||||||
private readonly string filenameBackup;
|
private readonly string filenameBackup;
|
||||||
private readonly string identifier;
|
private readonly string errorIdentifier;
|
||||||
|
|
||||||
public FileConfigInstance(string filename, T instance, string identifier){
|
public FileConfigInstance(string filename, T instance, string errorIdentifier){
|
||||||
this.filenameMain = filename;
|
this.filenameMain = filename;
|
||||||
this.filenameBackup = filename + ".bak";
|
this.filenameBackup = filename+".bak";
|
||||||
this.identifier = identifier;
|
this.errorIdentifier = errorIdentifier;
|
||||||
|
|
||||||
this.Instance = instance;
|
this.Instance = instance;
|
||||||
this.Serializer = new FileSerializer<T>();
|
this.Serializer = new FileSerializer<T>();
|
||||||
@@ -25,14 +27,14 @@ namespace TweetLib.Core.Features.Configuration{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void Load(){
|
public void Load(){
|
||||||
Exception? firstException = null;
|
Exception firstException = null;
|
||||||
|
|
||||||
for(int attempt = 0; attempt < 2; attempt++){
|
for(int attempt = 0; attempt < 2; attempt++){
|
||||||
try{
|
try{
|
||||||
LoadInternal(attempt > 0);
|
LoadInternal(attempt > 0);
|
||||||
|
|
||||||
if (firstException != null){ // silently log exception that caused a backup restore
|
if (firstException != null){ // silently log exception that caused a backup restore
|
||||||
App.ErrorHandler.Log(firstException.ToString());
|
Program.Reporter.LogImportant(firstException.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@@ -47,13 +49,13 @@ namespace TweetLib.Core.Features.Configuration{
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (firstException is FormatException){
|
if (firstException is FormatException){
|
||||||
OnException($"The configuration file for {identifier} is outdated or corrupted. If you continue, your {identifier} will be reset.", firstException);
|
Program.Reporter.HandleException(ErrorTitle, "The configuration file for "+errorIdentifier+" is outdated or corrupted. If you continue, your "+errorIdentifier+" will be reset.", true, firstException);
|
||||||
}
|
}
|
||||||
else if (firstException is SerializationSoftException sse){
|
else if (firstException is SerializationSoftException sse){
|
||||||
OnException($"{sse.Errors.Count} error{(sse.Errors.Count == 1 ? " was" : "s were")} encountered while loading the configuration file for {identifier}. If you continue, some of your {identifier} will be reset.", firstException);
|
Program.Reporter.HandleException(ErrorTitle, $"{sse.Errors.Count} error{(sse.Errors.Count == 1 ? " was" : "s were")} encountered while loading the configuration file for "+errorIdentifier+". If you continue, some of your "+errorIdentifier+" will be reset.", true, firstException);
|
||||||
}
|
}
|
||||||
else if (firstException != null){
|
else if (firstException != null){
|
||||||
OnException($"Could not open the configuration file for {identifier}. If you continue, your {identifier} will be reset.", firstException);
|
Program.Reporter.HandleException(ErrorTitle, "Could not open the configuration file for "+errorIdentifier+". If you continue, your "+errorIdentifier+" will be reset.", true, firstException);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,9 +68,9 @@ namespace TweetLib.Core.Features.Configuration{
|
|||||||
|
|
||||||
Serializer.Write(filenameMain, Instance);
|
Serializer.Write(filenameMain, Instance);
|
||||||
}catch(SerializationSoftException e){
|
}catch(SerializationSoftException e){
|
||||||
OnException($"{e.Errors.Count} error{(e.Errors.Count == 1 ? " was" : "s were")} encountered while saving the configuration file for {identifier}.", e);
|
Program.Reporter.HandleException(ErrorTitle, $"{e.Errors.Count} error{(e.Errors.Count == 1 ? " was" : "s were")} encountered while saving the configuration file for "+errorIdentifier+".", true, e);
|
||||||
}catch(Exception e){
|
}catch(Exception e){
|
||||||
OnException($"Could not save the configuration file for {identifier}.", e);
|
Program.Reporter.HandleException(ErrorTitle, "Could not save the configuration file for "+errorIdentifier+".", true, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,10 +82,10 @@ namespace TweetLib.Core.Features.Configuration{
|
|||||||
Serializer.Write(filenameMain, Instance.ConstructWithDefaults<T>());
|
Serializer.Write(filenameMain, Instance.ConstructWithDefaults<T>());
|
||||||
LoadInternal(false);
|
LoadInternal(false);
|
||||||
}catch(Exception e){
|
}catch(Exception e){
|
||||||
OnException($"Could not regenerate the configuration file for {identifier}.", e);
|
Program.Reporter.HandleException(ErrorTitle, "Could not regenerate the configuration file for "+errorIdentifier+".", true, e);
|
||||||
}
|
}
|
||||||
}catch(Exception e){
|
}catch(Exception e){
|
||||||
OnException($"Could not reload the configuration file for {identifier}.", e);
|
Program.Reporter.HandleException(ErrorTitle, "Could not reload the configuration file for "+errorIdentifier+".", true, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,15 +94,11 @@ namespace TweetLib.Core.Features.Configuration{
|
|||||||
File.Delete(filenameMain);
|
File.Delete(filenameMain);
|
||||||
File.Delete(filenameBackup);
|
File.Delete(filenameBackup);
|
||||||
}catch(Exception e){
|
}catch(Exception e){
|
||||||
OnException($"Could not delete configuration files to reset {identifier}.", e);
|
Program.Reporter.HandleException(ErrorTitle, "Could not delete configuration files to reset "+errorIdentifier+".", true, e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Reload();
|
Reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void OnException(string message, Exception e){
|
|
||||||
App.ErrorHandler.HandleException("Configuration Error", message, true, e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,5 +1,5 @@
|
|||||||
namespace TweetLib.Core.Features.Configuration{
|
namespace TweetDuck.Configuration.Instance{
|
||||||
public interface IConfigInstance<out T>{
|
interface IConfigInstance<out T>{
|
||||||
T Instance { get; }
|
T Instance { get; }
|
||||||
|
|
||||||
void Save();
|
void Save();
|
69
Configuration/Instance/PluginConfigInstance.cs
Normal file
69
Configuration/Instance/PluginConfigInstance.cs
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace TweetDuck.Configuration.Instance{
|
||||||
|
class PluginConfigInstance : IConfigInstance<PluginConfig>{
|
||||||
|
public PluginConfig Instance { get; }
|
||||||
|
|
||||||
|
private readonly string filename;
|
||||||
|
|
||||||
|
public PluginConfigInstance(string filename, PluginConfig instance){
|
||||||
|
this.filename = filename;
|
||||||
|
this.Instance = instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Load(){
|
||||||
|
try{
|
||||||
|
using(StreamReader reader = new StreamReader(new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read), Encoding.UTF8)){
|
||||||
|
string line = reader.ReadLine();
|
||||||
|
|
||||||
|
if (line == "#Disabled"){
|
||||||
|
HashSet<string> newDisabled = new HashSet<string>();
|
||||||
|
|
||||||
|
while((line = reader.ReadLine()) != null){
|
||||||
|
newDisabled.Add(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
Instance.ReloadSilently(newDisabled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}catch(FileNotFoundException){
|
||||||
|
}catch(DirectoryNotFoundException){
|
||||||
|
}catch(Exception e){
|
||||||
|
Program.Reporter.HandleException("Plugin Configuration Error", "Could not read the plugin configuration file. If you continue, the list of disabled plugins will be reset to default.", true, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Save(){
|
||||||
|
try{
|
||||||
|
using(StreamWriter writer = new StreamWriter(new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None), Encoding.UTF8)){
|
||||||
|
writer.WriteLine("#Disabled");
|
||||||
|
|
||||||
|
foreach(string identifier in Instance.DisabledPlugins){
|
||||||
|
writer.WriteLine(identifier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}catch(Exception e){
|
||||||
|
Program.Reporter.HandleException("Plugin Configuration Error", "Could not save the plugin configuration file.", true, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reload(){
|
||||||
|
Load();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reset(){
|
||||||
|
try{
|
||||||
|
File.Delete(filename);
|
||||||
|
Instance.ReloadSilently(Instance.ConstructWithDefaults<PluginConfig>().DisabledPlugins);
|
||||||
|
}catch(Exception e){
|
||||||
|
Program.Reporter.HandleException("Plugin Configuration Error", "Could not delete the plugin configuration file.", true, e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,11 +1,12 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.ComponentModel;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using TweetDuck.Core.Utils;
|
||||||
|
|
||||||
namespace TweetLib.Core.Application.Helpers{
|
namespace TweetDuck.Configuration{
|
||||||
public sealed class LockManager{
|
sealed class LockManager{
|
||||||
private const int RetryDelay = 250;
|
private const int RetryDelay = 250;
|
||||||
|
|
||||||
public enum Result{
|
public enum Result{
|
||||||
@@ -13,8 +14,8 @@ namespace TweetLib.Core.Application.Helpers{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private readonly string file;
|
private readonly string file;
|
||||||
private FileStream? lockStream;
|
private FileStream lockStream;
|
||||||
private Process? lockingProcess;
|
private Process lockingProcess;
|
||||||
|
|
||||||
public LockManager(string file){
|
public LockManager(string file){
|
||||||
this.file = file;
|
this.file = file;
|
||||||
@@ -36,7 +37,7 @@ namespace TweetLib.Core.Application.Helpers{
|
|||||||
private Result TryCreateLockFile(){
|
private Result TryCreateLockFile(){
|
||||||
void CreateLockFileStream(){
|
void CreateLockFileStream(){
|
||||||
lockStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.Read);
|
lockStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.Read);
|
||||||
lockStream.Write(BitConverter.GetBytes(CurrentProcessID), 0, sizeof(int));
|
lockStream.Write(BitConverter.GetBytes(WindowsUtils.CurrentProcessID), 0, sizeof(int));
|
||||||
lockStream.Flush(true);
|
lockStream.Flush(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,11 +82,13 @@ namespace TweetLib.Core.Application.Helpers{
|
|||||||
try{
|
try{
|
||||||
Process foundProcess = Process.GetProcessById(pid);
|
Process foundProcess = Process.GetProcessById(pid);
|
||||||
|
|
||||||
if (MatchesCurrentProcess(foundProcess)){
|
using(Process currentProcess = Process.GetCurrentProcess()){
|
||||||
lockingProcess = foundProcess;
|
if (foundProcess.MainModule.FileVersionInfo.InternalName == currentProcess.MainModule.FileVersionInfo.InternalName){
|
||||||
}
|
lockingProcess = foundProcess;
|
||||||
else{
|
}
|
||||||
foundProcess.Close();
|
else{
|
||||||
|
foundProcess.Close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}catch{
|
}catch{
|
||||||
// GetProcessById throws ArgumentException if the process is missing
|
// GetProcessById throws ArgumentException if the process is missing
|
||||||
@@ -121,7 +124,7 @@ namespace TweetLib.Core.Application.Helpers{
|
|||||||
try{
|
try{
|
||||||
File.Delete(file);
|
File.Delete(file);
|
||||||
}catch(Exception e){
|
}catch(Exception e){
|
||||||
App.ErrorHandler.Log(e.ToString());
|
Program.Reporter.LogImportant(e.ToString());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -130,33 +133,51 @@ namespace TweetLib.Core.Application.Helpers{
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Locking process
|
// Locking process
|
||||||
|
|
||||||
public bool RestoreLockingProcess(){
|
|
||||||
return lockingProcess != null && App.LockHandler.RestoreProcess(lockingProcess);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool CloseLockingProcess(){
|
public bool RestoreLockingProcess(int failTimeout){
|
||||||
if (lockingProcess != null && App.LockHandler.CloseProcess(lockingProcess)){
|
if (lockingProcess != null && lockingProcess.MainWindowHandle == IntPtr.Zero){ // restore if the original process is in tray
|
||||||
lockingProcess = null;
|
NativeMethods.BroadcastMessage(Program.WindowRestoreMessage, (uint)lockingProcess.Id, 0);
|
||||||
return true;
|
|
||||||
|
if (WindowsUtils.TrySleepUntil(() => CheckLockingProcessExited() || (lockingProcess.MainWindowHandle != IntPtr.Zero && lockingProcess.Responding), failTimeout, RetryDelay)){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Utilities
|
public bool CloseLockingProcess(int closeTimeout, int killTimeout){
|
||||||
|
if (lockingProcess != null){
|
||||||
|
try{
|
||||||
|
if (lockingProcess.CloseMainWindow()){
|
||||||
|
WindowsUtils.TrySleepUntil(CheckLockingProcessExited, closeTimeout, RetryDelay);
|
||||||
|
}
|
||||||
|
|
||||||
private static int CurrentProcessID{
|
if (!lockingProcess.HasExited){
|
||||||
get{
|
lockingProcess.Kill();
|
||||||
using Process me = Process.GetCurrentProcess();
|
WindowsUtils.TrySleepUntil(CheckLockingProcessExited, killTimeout, RetryDelay);
|
||||||
return me.Id;
|
}
|
||||||
|
|
||||||
|
if (lockingProcess.HasExited){
|
||||||
|
lockingProcess.Dispose();
|
||||||
|
lockingProcess = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}catch(Exception ex) when (ex is InvalidOperationException || ex is Win32Exception){
|
||||||
|
if (lockingProcess != null){
|
||||||
|
bool hasExited = CheckLockingProcessExited();
|
||||||
|
lockingProcess.Dispose();
|
||||||
|
return hasExited;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
[SuppressMessage("ReSharper", "PossibleNullReferenceException")]
|
private bool CheckLockingProcessExited(){
|
||||||
private static bool MatchesCurrentProcess(Process process){
|
lockingProcess.Refresh();
|
||||||
using Process current = Process.GetCurrentProcess();
|
return lockingProcess.HasExited;
|
||||||
return current.MainModule.FileVersionInfo.InternalName == process.MainModule.FileVersionInfo.InternalName;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,41 +1,20 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using TweetLib.Core.Features.Configuration;
|
using TweetDuck.Plugins;
|
||||||
using TweetLib.Core.Features.Plugins;
|
using TweetDuck.Plugins.Events;
|
||||||
using TweetLib.Core.Features.Plugins.Config;
|
|
||||||
using TweetLib.Core.Features.Plugins.Events;
|
|
||||||
|
|
||||||
namespace TweetDuck.Configuration{
|
namespace TweetDuck.Configuration{
|
||||||
sealed class PluginConfig : BaseConfig, IPluginConfig{
|
sealed class PluginConfig : ConfigManager.BaseConfig, IPluginConfig{
|
||||||
private static readonly string[] DefaultDisabled = {
|
private static readonly string[] DefaultDisabled = {
|
||||||
"official/clear-columns",
|
"official/clear-columns",
|
||||||
"official/reply-account"
|
"official/reply-account"
|
||||||
};
|
};
|
||||||
|
|
||||||
// CONFIGURATION DATA
|
// CONFIGURATION
|
||||||
|
|
||||||
private readonly HashSet<string> disabled = new HashSet<string>(DefaultDisabled);
|
public IEnumerable<string> DisabledPlugins => disabled;
|
||||||
|
|
||||||
// EVENTS
|
|
||||||
|
|
||||||
public event EventHandler<PluginChangedStateEventArgs> PluginChangedState;
|
public event EventHandler<PluginChangedStateEventArgs> PluginChangedState;
|
||||||
|
|
||||||
// END OF CONFIG
|
|
||||||
|
|
||||||
public PluginConfig(IConfigManager configManager) : base(configManager){}
|
|
||||||
|
|
||||||
protected override BaseConfig ConstructWithDefaults(IConfigManager configManager){
|
|
||||||
return new PluginConfig(configManager);
|
|
||||||
}
|
|
||||||
|
|
||||||
// INTERFACE IMPLEMENTATION
|
|
||||||
|
|
||||||
IEnumerable<string> IPluginConfig.DisabledPlugins => disabled;
|
|
||||||
|
|
||||||
void IPluginConfig.Reset(IEnumerable<string> newDisabledPlugins){
|
|
||||||
disabled.Clear();
|
|
||||||
disabled.UnionWith(newDisabledPlugins);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetEnabled(Plugin plugin, bool enabled){
|
public void SetEnabled(Plugin plugin, bool enabled){
|
||||||
if ((enabled && disabled.Remove(plugin.Identifier)) || (!enabled && disabled.Add(plugin.Identifier))){
|
if ((enabled && disabled.Remove(plugin.Identifier)) || (!enabled && disabled.Add(plugin.Identifier))){
|
||||||
@@ -47,5 +26,20 @@ namespace TweetDuck.Configuration{
|
|||||||
public bool IsEnabled(Plugin plugin){
|
public bool IsEnabled(Plugin plugin){
|
||||||
return !disabled.Contains(plugin.Identifier);
|
return !disabled.Contains(plugin.Identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ReloadSilently(IEnumerable<string> newDisabled){
|
||||||
|
disabled.Clear();
|
||||||
|
disabled.UnionWith(newDisabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly HashSet<string> disabled = new HashSet<string>(DefaultDisabled);
|
||||||
|
|
||||||
|
// END OF CONFIG
|
||||||
|
|
||||||
|
public PluginConfig(ConfigManager configManager) : base(configManager){}
|
||||||
|
|
||||||
|
protected override ConfigManager.BaseConfig ConstructWithDefaults(ConfigManager configManager){
|
||||||
|
return new PluginConfig(configManager);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,5 @@
|
|||||||
using TweetLib.Core.Features.Configuration;
|
namespace TweetDuck.Configuration{
|
||||||
|
sealed class SystemConfig : ConfigManager.BaseConfig{
|
||||||
namespace TweetDuck.Configuration{
|
|
||||||
sealed class SystemConfig : BaseConfig{
|
|
||||||
|
|
||||||
// CONFIGURATION DATA
|
// CONFIGURATION DATA
|
||||||
|
|
||||||
@@ -19,9 +17,9 @@ namespace TweetDuck.Configuration{
|
|||||||
|
|
||||||
// END OF CONFIG
|
// END OF CONFIG
|
||||||
|
|
||||||
public SystemConfig(IConfigManager configManager) : base(configManager){}
|
public SystemConfig(ConfigManager configManager) : base(configManager){}
|
||||||
|
|
||||||
protected override BaseConfig ConstructWithDefaults(IConfigManager configManager){
|
protected override ConfigManager.BaseConfig ConstructWithDefaults(ConfigManager configManager){
|
||||||
return new SystemConfig(configManager);
|
return new SystemConfig(configManager);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,14 +1,13 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using TweetDuck.Core.Controls;
|
using TweetDuck.Core.Controls;
|
||||||
|
using TweetDuck.Core.Notification;
|
||||||
using TweetDuck.Core.Other;
|
using TweetDuck.Core.Other;
|
||||||
|
using TweetDuck.Core.Utils;
|
||||||
using TweetDuck.Data;
|
using TweetDuck.Data;
|
||||||
using TweetLib.Core.Features.Configuration;
|
|
||||||
using TweetLib.Core.Features.Notifications;
|
|
||||||
using TweetLib.Core.Features.Twitter;
|
|
||||||
|
|
||||||
namespace TweetDuck.Configuration{
|
namespace TweetDuck.Configuration{
|
||||||
sealed class UserConfig : BaseConfig{
|
sealed class UserConfig : ConfigManager.BaseConfig{
|
||||||
|
|
||||||
// CONFIGURATION DATA
|
// CONFIGURATION DATA
|
||||||
|
|
||||||
@@ -57,14 +56,14 @@ namespace TweetDuck.Configuration{
|
|||||||
public bool NotificationTimerCountDown { get; set; } = false;
|
public bool NotificationTimerCountDown { get; set; } = false;
|
||||||
public int NotificationDurationValue { get; set; } = 25;
|
public int NotificationDurationValue { get; set; } = 25;
|
||||||
|
|
||||||
public DesktopNotification.Position NotificationPosition { get; set; } = DesktopNotification.Position.TopRight;
|
public TweetNotification.Position NotificationPosition { get; set; } = TweetNotification.Position.TopRight;
|
||||||
public Point CustomNotificationPosition { get; set; } = ControlExtensions.InvisibleLocation;
|
public Point CustomNotificationPosition { get; set; } = ControlExtensions.InvisibleLocation;
|
||||||
public int NotificationDisplay { get; set; } = 0;
|
public int NotificationDisplay { get; set; } = 0;
|
||||||
public int NotificationEdgeDistance { get; set; } = 8;
|
public int NotificationEdgeDistance { get; set; } = 8;
|
||||||
|
|
||||||
public DesktopNotification.Size NotificationSize { get; set; } = DesktopNotification.Size.Auto;
|
public TweetNotification.Size NotificationSize { get; set; } = TweetNotification.Size.Auto;
|
||||||
public Size CustomNotificationSize { get; set; } = Size.Empty;
|
public Size CustomNotificationSize { get; set; } = Size.Empty;
|
||||||
public int NotificationScrollSpeed { get; set; } = 100;
|
public int NotificationScrollSpeed { get; set; } = 100;
|
||||||
|
|
||||||
private string _notificationSoundPath;
|
private string _notificationSoundPath;
|
||||||
private int _notificationSoundVolume = 100;
|
private int _notificationSoundVolume = 100;
|
||||||
@@ -80,7 +79,7 @@ namespace TweetDuck.Configuration{
|
|||||||
public bool IsCustomNotificationSizeSet => CustomNotificationSize != Size.Empty;
|
public bool IsCustomNotificationSizeSet => CustomNotificationSize != Size.Empty;
|
||||||
public bool IsCustomSoundNotificationSet => NotificationSoundPath != string.Empty;
|
public bool IsCustomSoundNotificationSet => NotificationSoundPath != string.Empty;
|
||||||
|
|
||||||
public ImageQuality TwitterImageQuality => BestImageQuality ? ImageQuality.Best : ImageQuality.Default;
|
public TwitterUtils.ImageQuality TwitterImageQuality => BestImageQuality ? TwitterUtils.ImageQuality.Orig : TwitterUtils.ImageQuality.Default;
|
||||||
|
|
||||||
public string NotificationSoundPath{
|
public string NotificationSoundPath{
|
||||||
get => _notificationSoundPath ?? string.Empty;
|
get => _notificationSoundPath ?? string.Empty;
|
||||||
@@ -136,9 +135,9 @@ namespace TweetDuck.Configuration{
|
|||||||
|
|
||||||
// END OF CONFIG
|
// END OF CONFIG
|
||||||
|
|
||||||
public UserConfig(IConfigManager configManager) : base(configManager){}
|
public UserConfig(ConfigManager configManager) : base(configManager){}
|
||||||
|
|
||||||
protected override BaseConfig ConstructWithDefaults(IConfigManager configManager){
|
protected override ConfigManager.BaseConfig ConstructWithDefaults(ConfigManager configManager){
|
||||||
return new UserConfig(configManager);
|
return new UserConfig(configManager);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,41 +0,0 @@
|
|||||||
using System.IO;
|
|
||||||
using CefSharp;
|
|
||||||
using TweetLib.Core.Browser;
|
|
||||||
|
|
||||||
namespace TweetDuck.Core.Adapters{
|
|
||||||
sealed class CefScriptExecutor : IScriptExecutor{
|
|
||||||
private readonly IWebBrowser browser;
|
|
||||||
|
|
||||||
public CefScriptExecutor(IWebBrowser browser){
|
|
||||||
this.browser = browser;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RunFunction(string name, params object[] args){
|
|
||||||
browser.ExecuteScriptAsync(name, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RunScript(string identifier, string script){
|
|
||||||
using IFrame frame = browser.GetMainFrame();
|
|
||||||
RunScript(frame, script, identifier);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool RunFile(string file){
|
|
||||||
using IFrame frame = browser.GetMainFrame();
|
|
||||||
return RunFile(frame, file);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helpers
|
|
||||||
|
|
||||||
public static void RunScript(IFrame frame, string script, string identifier){
|
|
||||||
if (script != null){
|
|
||||||
frame.ExecuteJavaScriptAsync(script, identifier, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool RunFile(IFrame frame, string file){
|
|
||||||
string script = Program.Resources.Load(file);
|
|
||||||
RunScript(frame, script, "root:" + Path.GetFileNameWithoutExtension(file));
|
|
||||||
return script != null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -8,8 +8,8 @@ namespace TweetDuck.Core.Bridge{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static string GenerateScript(Environment environment){
|
public static string GenerateScript(Environment environment){
|
||||||
static string Bool(bool value) => value ? "true;" : "false;";
|
string Bool(bool value) => value ? "true;" : "false;";
|
||||||
static string Str(string value) => $"\"{value}\";";
|
string Str(string value) => '"'+value+"\";";
|
||||||
|
|
||||||
UserConfig config = Program.Config.User;
|
UserConfig config = Program.Config.User;
|
||||||
StringBuilder build = new StringBuilder(128).Append("(function(x){");
|
StringBuilder build = new StringBuilder(128).Append("(function(x){");
|
||||||
|
@@ -1,18 +1,18 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Windows.Forms;
|
||||||
using System.Windows.Forms;
|
|
||||||
using TweetDuck.Core.Controls;
|
using TweetDuck.Core.Controls;
|
||||||
using TweetDuck.Core.Handling;
|
using TweetDuck.Core.Management;
|
||||||
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 TweetLib.Core.Features.Notifications;
|
|
||||||
|
|
||||||
namespace TweetDuck.Core.Bridge{
|
namespace TweetDuck.Core.Bridge{
|
||||||
[SuppressMessage("ReSharper", "UnusedMember.Global")]
|
|
||||||
class TweetDeckBridge{
|
class TweetDeckBridge{
|
||||||
|
public static string FontSize { get; private set; }
|
||||||
|
public static string NotificationHeadLayout { get; private set; }
|
||||||
|
public static readonly ContextInfo ContextInfo = new ContextInfo();
|
||||||
|
|
||||||
public static void ResetStaticProperties(){
|
public static void ResetStaticProperties(){
|
||||||
FormNotificationBase.FontSize = null;
|
FontSize = NotificationHeadLayout = null;
|
||||||
FormNotificationBase.HeadLayout = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly FormBrowser form;
|
private readonly FormBrowser form;
|
||||||
@@ -44,17 +44,17 @@ namespace TweetDuck.Core.Bridge{
|
|||||||
|
|
||||||
public void LoadNotificationLayout(string fontSize, string headLayout){
|
public void LoadNotificationLayout(string fontSize, string headLayout){
|
||||||
form.InvokeAsyncSafe(() => {
|
form.InvokeAsyncSafe(() => {
|
||||||
FormNotificationBase.FontSize = fontSize;
|
FontSize = fontSize;
|
||||||
FormNotificationBase.HeadLayout = headLayout;
|
NotificationHeadLayout = headLayout;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetRightClickedLink(string type, string url){
|
public void SetRightClickedLink(string type, string url){
|
||||||
ContextMenuBase.CurrentInfo.SetLink(type, url);
|
ContextInfo.SetLink(type, url);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetRightClickedChirp(string tweetUrl, string quoteUrl, string chirpAuthors, string chirpImages){
|
public void SetRightClickedChirp(string tweetUrl, string quoteUrl, string chirpAuthors, string chirpImages){
|
||||||
ContextMenuBase.CurrentInfo.SetChirp(tweetUrl, quoteUrl, chirpAuthors, chirpImages);
|
ContextInfo.SetChirp(tweetUrl, quoteUrl, chirpAuthors, chirpImages);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DisplayTooltip(string text){
|
public void DisplayTooltip(string text){
|
||||||
@@ -63,7 +63,7 @@ namespace TweetDuck.Core.Bridge{
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Notification only
|
// Notification only
|
||||||
|
|
||||||
public sealed class Notification : TweetDeckBridge{
|
public sealed class Notification : TweetDeckBridge{
|
||||||
public Notification(FormBrowser form, FormNotificationMain notification) : base(form, notification){}
|
public Notification(FormBrowser form, FormNotificationMain notification) : base(form, notification){}
|
||||||
|
|
||||||
@@ -85,7 +85,7 @@ namespace TweetDuck.Core.Bridge{
|
|||||||
public void OnTweetPopup(string columnId, string chirpId, string columnName, string tweetHtml, int tweetCharacters, string tweetUrl, string quoteUrl){
|
public void OnTweetPopup(string columnId, string chirpId, string columnName, string tweetHtml, int tweetCharacters, string tweetUrl, string quoteUrl){
|
||||||
notification.InvokeAsyncSafe(() => {
|
notification.InvokeAsyncSafe(() => {
|
||||||
form.OnTweetNotification();
|
form.OnTweetNotification();
|
||||||
notification.ShowNotification(new DesktopNotification(columnId, chirpId, columnName, tweetHtml, tweetCharacters, tweetUrl, quoteUrl));
|
notification.ShowNotification(new TweetNotification(columnId, chirpId, columnName, tweetHtml, tweetCharacters, tweetUrl, quoteUrl));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,12 +117,14 @@ namespace TweetDuck.Core.Bridge{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void Alert(string type, string contents){
|
public void Alert(string type, string contents){
|
||||||
MessageBoxIcon icon = type switch{
|
MessageBoxIcon icon;
|
||||||
"error" => MessageBoxIcon.Error,
|
|
||||||
"warning" => MessageBoxIcon.Warning,
|
switch(type){
|
||||||
"info" => MessageBoxIcon.Information,
|
case "error": icon = MessageBoxIcon.Error; break;
|
||||||
_ => MessageBoxIcon.None
|
case "warning": icon = MessageBoxIcon.Warning; break;
|
||||||
};
|
case "info": icon = MessageBoxIcon.Information; break;
|
||||||
|
default: icon = MessageBoxIcon.None; break;
|
||||||
|
}
|
||||||
|
|
||||||
FormMessage.Show("TweetDuck Browser Message", contents, icon, FormMessage.OK);
|
FormMessage.Show("TweetDuck Browser Message", contents, icon, FormMessage.OK);
|
||||||
}
|
}
|
||||||
|
@@ -1,11 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using TweetDuck.Core.Controls;
|
using TweetDuck.Core.Controls;
|
||||||
using TweetLib.Core.Features.Updates;
|
using TweetDuck.Updates;
|
||||||
|
|
||||||
namespace TweetDuck.Core.Bridge{
|
namespace TweetDuck.Core.Bridge{
|
||||||
[SuppressMessage("ReSharper", "UnusedMember.Global")]
|
|
||||||
class UpdateBridge{
|
class UpdateBridge{
|
||||||
private readonly UpdateHandler updates;
|
private readonly UpdateHandler updates;
|
||||||
private readonly Control sync;
|
private readonly Control sync;
|
||||||
@@ -13,6 +11,7 @@ namespace TweetDuck.Core.Bridge{
|
|||||||
private UpdateInfo nextUpdate = null;
|
private UpdateInfo nextUpdate = null;
|
||||||
|
|
||||||
public event EventHandler<UpdateInfo> UpdateAccepted;
|
public event EventHandler<UpdateInfo> UpdateAccepted;
|
||||||
|
public event EventHandler<UpdateInfo> UpdateDelayed;
|
||||||
public event EventHandler<UpdateInfo> UpdateDismissed;
|
public event EventHandler<UpdateInfo> UpdateDismissed;
|
||||||
|
|
||||||
public UpdateBridge(UpdateHandler updates, Control sync){
|
public UpdateBridge(UpdateHandler updates, Control sync){
|
||||||
@@ -55,6 +54,10 @@ namespace TweetDuck.Core.Bridge{
|
|||||||
HandleInteractionEvent(UpdateAccepted);
|
HandleInteractionEvent(UpdateAccepted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void OnUpdateDelayed(){
|
||||||
|
HandleInteractionEvent(UpdateDelayed);
|
||||||
|
}
|
||||||
|
|
||||||
public void OnUpdateDismissed(){
|
public void OnUpdateDismissed(){
|
||||||
HandleInteractionEvent(UpdateDismissed);
|
HandleInteractionEvent(UpdateDismissed);
|
||||||
|
|
||||||
|
@@ -21,8 +21,9 @@ namespace TweetDuck.Core.Controls{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static float GetDPIScale(this Control control){
|
public static float GetDPIScale(this Control control){
|
||||||
using Graphics graphics = control.CreateGraphics();
|
using(Graphics graphics = control.CreateGraphics()){
|
||||||
return graphics.DpiY / 96F;
|
return graphics.DpiY/96F;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool IsFullyOutsideView(this Form form){
|
public static bool IsFullyOutsideView(this Form form){
|
||||||
@@ -30,17 +31,17 @@ namespace TweetDuck.Core.Controls{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void MoveToCenter(this Form targetForm, Form parentForm){
|
public static void MoveToCenter(this Form targetForm, Form parentForm){
|
||||||
targetForm.Location = new Point(parentForm.Location.X + (parentForm.Width / 2) - (targetForm.Width / 2), parentForm.Location.Y + (parentForm.Height / 2) - (targetForm.Height / 2));
|
targetForm.Location = new Point(parentForm.Location.X+parentForm.Width/2-targetForm.Width/2, parentForm.Location.Y+parentForm.Height/2-targetForm.Height/2);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void SetValueInstant(this ProgressBar bar, int value){
|
public static void SetValueInstant(this ProgressBar bar, int value){
|
||||||
if (value == bar.Maximum){
|
if (value == bar.Maximum){
|
||||||
bar.Value = value;
|
bar.Value = value;
|
||||||
bar.Value = value - 1;
|
bar.Value = value-1;
|
||||||
bar.Value = value;
|
bar.Value = value;
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
bar.Value = value + 1;
|
bar.Value = value+1;
|
||||||
bar.Value = value;
|
bar.Value = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -59,11 +60,10 @@ namespace TweetDuck.Core.Controls{
|
|||||||
|
|
||||||
public static bool AlignValueToTick(this TrackBar trackBar){
|
public static bool AlignValueToTick(this TrackBar trackBar){
|
||||||
if (trackBar.Value % trackBar.SmallChange != 0){
|
if (trackBar.Value % trackBar.SmallChange != 0){
|
||||||
trackBar.Value = trackBar.SmallChange * (int)Math.Floor(((double)trackBar.Value / trackBar.SmallChange) + 0.5);
|
trackBar.Value = trackBar.SmallChange*(int)Math.Floor(((double)trackBar.Value/trackBar.SmallChange)+0.5);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
else return true;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void EnableMultilineShortcuts(this TextBox textBox){
|
public static void EnableMultilineShortcuts(this TextBox textBox){
|
||||||
|
@@ -23,7 +23,7 @@ namespace TweetDuck.Core.Controls{
|
|||||||
}
|
}
|
||||||
|
|
||||||
Rectangle rect = e.ClipRectangle;
|
Rectangle rect = e.ClipRectangle;
|
||||||
rect.Width = (int)(rect.Width * ((double)Value / Maximum));
|
rect.Width = (int)(rect.Width*((double)Value/Maximum));
|
||||||
e.Graphics.FillRectangle(brush, rect);
|
e.Graphics.FillRectangle(brush, rect);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -7,15 +7,16 @@ namespace TweetDuck.Core.Controls{
|
|||||||
public int LineHeight { get; set; }
|
public int LineHeight { get; set; }
|
||||||
|
|
||||||
protected override void OnPaint(PaintEventArgs e){
|
protected override void OnPaint(PaintEventArgs e){
|
||||||
int y = (int)Math.Floor((ClientRectangle.Height - Text.Length * LineHeight) / 2F) - 1;
|
int y = (int)Math.Floor((ClientRectangle.Height-Text.Length*LineHeight)/2F)-1;
|
||||||
using Brush brush = new SolidBrush(ForeColor);
|
|
||||||
|
|
||||||
foreach(char chr in Text){
|
using(Brush brush = new SolidBrush(ForeColor)){
|
||||||
string str = chr.ToString();
|
foreach(char chr in Text){
|
||||||
float x = (ClientRectangle.Width - e.Graphics.MeasureString(str, Font).Width) / 2F;
|
string str = chr.ToString();
|
||||||
|
float x = (ClientRectangle.Width-e.Graphics.MeasureString(str, Font).Width)/2F;
|
||||||
|
|
||||||
e.Graphics.DrawString(str, Font, brush, x, y);
|
e.Graphics.DrawString(str, Font, brush, x, y);
|
||||||
y += LineHeight;
|
y += LineHeight;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,8 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
|
using Microsoft.WindowsAPICodePack.Taskbar;
|
||||||
using TweetDuck.Configuration;
|
using TweetDuck.Configuration;
|
||||||
using TweetDuck.Core.Bridge;
|
using TweetDuck.Core.Bridge;
|
||||||
using TweetDuck.Core.Controls;
|
using TweetDuck.Core.Controls;
|
||||||
@@ -15,10 +14,10 @@ using TweetDuck.Core.Other;
|
|||||||
using TweetDuck.Core.Other.Analytics;
|
using TweetDuck.Core.Other.Analytics;
|
||||||
using TweetDuck.Core.Other.Settings.Dialogs;
|
using TweetDuck.Core.Other.Settings.Dialogs;
|
||||||
using TweetDuck.Core.Utils;
|
using TweetDuck.Core.Utils;
|
||||||
|
using TweetDuck.Plugins;
|
||||||
|
using TweetDuck.Plugins.Events;
|
||||||
|
using TweetDuck.Resources;
|
||||||
using TweetDuck.Updates;
|
using TweetDuck.Updates;
|
||||||
using TweetLib.Core.Features.Plugins;
|
|
||||||
using TweetLib.Core.Features.Plugins.Events;
|
|
||||||
using TweetLib.Core.Features.Updates;
|
|
||||||
|
|
||||||
namespace TweetDuck.Core{
|
namespace TweetDuck.Core{
|
||||||
sealed partial class FormBrowser : Form, AnalyticsFile.IProvider{
|
sealed partial class FormBrowser : Form, AnalyticsFile.IProvider{
|
||||||
@@ -52,6 +51,7 @@ namespace TweetDuck.Core{
|
|||||||
private readonly FormNotificationTweet notification;
|
private readonly FormNotificationTweet notification;
|
||||||
private readonly ContextMenu contextMenu;
|
private readonly ContextMenu contextMenu;
|
||||||
private readonly UpdateBridge updateBridge;
|
private readonly UpdateBridge updateBridge;
|
||||||
|
private readonly TaskbarIcon taskbarIcon;
|
||||||
|
|
||||||
private bool isLoaded;
|
private bool isLoaded;
|
||||||
private FormWindowState prevState;
|
private FormWindowState prevState;
|
||||||
@@ -65,7 +65,7 @@ namespace TweetDuck.Core{
|
|||||||
|
|
||||||
Text = Program.BrandName;
|
Text = Program.BrandName;
|
||||||
|
|
||||||
this.plugins = new PluginManager(Program.Config.Plugins, Program.PluginPath, Program.PluginDataPath);
|
this.plugins = new PluginManager(Program.Config.Plugins, Program.PluginPath);
|
||||||
this.plugins.Reloaded += plugins_Reloaded;
|
this.plugins.Reloaded += plugins_Reloaded;
|
||||||
this.plugins.Executed += plugins_Executed;
|
this.plugins.Executed += plugins_Executed;
|
||||||
this.plugins.Reload();
|
this.plugins.Reload();
|
||||||
@@ -73,11 +73,12 @@ namespace TweetDuck.Core{
|
|||||||
this.notification = new FormNotificationTweet(this, plugins);
|
this.notification = new FormNotificationTweet(this, plugins);
|
||||||
this.notification.Show();
|
this.notification.Show();
|
||||||
|
|
||||||
this.updates = new UpdateHandler(new UpdateCheckClient(Program.InstallerPath), TaskScheduler.FromCurrentSynchronizationContext());
|
this.updates = new UpdateHandler(Program.InstallerPath);
|
||||||
this.updates.CheckFinished += updates_CheckFinished;
|
this.updates.CheckFinished += updates_CheckFinished;
|
||||||
|
|
||||||
this.updateBridge = new UpdateBridge(updates, this);
|
this.updateBridge = new UpdateBridge(updates, this);
|
||||||
this.updateBridge.UpdateAccepted += updateBridge_UpdateAccepted;
|
this.updateBridge.UpdateAccepted += updateBridge_UpdateAccepted;
|
||||||
|
this.updateBridge.UpdateDelayed += updateBridge_UpdateDelayed;
|
||||||
this.updateBridge.UpdateDismissed += updateBridge_UpdateDismissed;
|
this.updateBridge.UpdateDismissed += updateBridge_UpdateDismissed;
|
||||||
|
|
||||||
this.browser = new TweetDeckBrowser(this, plugins, new TweetDeckBridge.Browser(this, notification), updateBridge);
|
this.browser = new TweetDeckBrowser(this, plugins, new TweetDeckBridge.Browser(this, notification), updateBridge);
|
||||||
@@ -85,6 +86,9 @@ namespace TweetDuck.Core{
|
|||||||
|
|
||||||
Controls.Add(new MenuStrip{ Visible = false }); // fixes Alt freezing the program in Win 10 Anniversary Update
|
Controls.Add(new MenuStrip{ Visible = false }); // fixes Alt freezing the program in Win 10 Anniversary Update
|
||||||
|
|
||||||
|
this.taskbarIcon = new TaskbarIcon();
|
||||||
|
Shown += (sender, args) => taskbarIcon.UpdateIcon();
|
||||||
|
|
||||||
Disposed += (sender, args) => {
|
Disposed += (sender, args) => {
|
||||||
Config.MuteToggled -= Config_MuteToggled;
|
Config.MuteToggled -= Config_MuteToggled;
|
||||||
Config.TrayBehaviorChanged -= Config_TrayBehaviorChanged;
|
Config.TrayBehaviorChanged -= Config_TrayBehaviorChanged;
|
||||||
@@ -92,6 +96,7 @@ namespace TweetDuck.Core{
|
|||||||
browser.Dispose();
|
browser.Dispose();
|
||||||
updates.Dispose();
|
updates.Dispose();
|
||||||
contextMenu.Dispose();
|
contextMenu.Dispose();
|
||||||
|
taskbarIcon.Dispose();
|
||||||
|
|
||||||
notificationScreenshotManager?.Dispose();
|
notificationScreenshotManager?.Dispose();
|
||||||
videoPlayer?.Dispose();
|
videoPlayer?.Dispose();
|
||||||
@@ -106,10 +111,6 @@ namespace TweetDuck.Core{
|
|||||||
|
|
||||||
UpdateTray();
|
UpdateTray();
|
||||||
|
|
||||||
if (Config.MuteNotifications){
|
|
||||||
UpdateFormIcon();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Config.AllowDataCollection){
|
if (Config.AllowDataCollection){
|
||||||
analytics = new AnalyticsManager(this, plugins, Program.AnalyticsFilePath);
|
analytics = new AnalyticsManager(this, plugins, Program.AnalyticsFilePath);
|
||||||
}
|
}
|
||||||
@@ -135,10 +136,6 @@ namespace TweetDuck.Core{
|
|||||||
isLoaded = true;
|
isLoaded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateFormIcon(){ // TODO fix to show icon in taskbar too
|
|
||||||
Icon = Config.MuteNotifications ? Properties.Resources.icon_muted : Properties.Resources.icon;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateTray(){
|
private void UpdateTray(){
|
||||||
trayIcon.Visible = Config.TrayBehavior.ShouldDisplayIcon();
|
trayIcon.Visible = Config.TrayBehavior.ShouldDisplayIcon();
|
||||||
}
|
}
|
||||||
@@ -153,6 +150,7 @@ namespace TweetDuck.Core{
|
|||||||
if (!isLoaded)return;
|
if (!isLoaded)return;
|
||||||
|
|
||||||
trayIcon.HasNotifications = false;
|
trayIcon.HasNotifications = false;
|
||||||
|
taskbarIcon.HasNotifications = false;
|
||||||
|
|
||||||
if (!browser.Enabled){ // when taking a screenshot, the window is unfocused and
|
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
|
browser.Enabled = true; // the browser is disabled; if the user clicks back into
|
||||||
@@ -214,7 +212,6 @@ namespace TweetDuck.Core{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void Config_MuteToggled(object sender, EventArgs e){
|
private void Config_MuteToggled(object sender, EventArgs e){
|
||||||
UpdateFormIcon();
|
|
||||||
AnalyticsFile.NotificationMutes.Trigger();
|
AnalyticsFile.NotificationMutes.Trigger();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,9 +232,7 @@ namespace TweetDuck.Core{
|
|||||||
|
|
||||||
private void plugins_Reloaded(object sender, PluginErrorEventArgs e){
|
private void plugins_Reloaded(object sender, PluginErrorEventArgs e){
|
||||||
if (e.HasErrors){
|
if (e.HasErrors){
|
||||||
this.InvokeAsyncSafe(() => { // TODO not needed but makes code consistent...
|
FormMessage.Error("Error Loading Plugins", "The following plugins will not be available until the issues are resolved:\n\n"+string.Join("\n\n", e.Errors), FormMessage.OK);
|
||||||
FormMessage.Error("Error Loading Plugins", "The following plugins will not be available until the issues are resolved:\n\n" + string.Join("\n\n", e.Errors), FormMessage.OK);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isLoaded){
|
if (isLoaded){
|
||||||
@@ -245,11 +240,9 @@ namespace TweetDuck.Core{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void plugins_Executed(object sender, PluginErrorEventArgs e){
|
private static void plugins_Executed(object sender, PluginErrorEventArgs e){
|
||||||
if (e.HasErrors){
|
if (e.HasErrors){
|
||||||
this.InvokeAsyncSafe(() => {
|
FormMessage.Error("Error Executing Plugins", "Failed to execute the following plugins:\n\n"+string.Join("\n\n", e.Errors), FormMessage.OK);
|
||||||
FormMessage.Error("Error Executing Plugins", "Failed to execute the following plugins:\n\n" + string.Join("\n\n", e.Errors), FormMessage.OK);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -289,7 +282,7 @@ namespace TweetDuck.Core{
|
|||||||
UpdateInstallerPath = update.InstallerPath;
|
UpdateInstallerPath = update.InstallerPath;
|
||||||
ForceClose();
|
ForceClose();
|
||||||
}
|
}
|
||||||
else if (status != UpdateDownloadStatus.Canceled && FormMessage.Error("Update Has Failed", "Could not automatically download the update: " + (update.DownloadError?.Message ?? "unknown error") + "\n\nWould you like to open the website and try downloading the update manually?", FormMessage.Yes, FormMessage.No)){
|
else if (status != UpdateDownloadStatus.Canceled && FormMessage.Error("Update Has Failed", "Could not automatically download the update: "+(update.DownloadError?.Message ?? "unknown error")+"\n\nWould you like to open the website and try downloading the update manually?", FormMessage.Yes, FormMessage.No)){
|
||||||
BrowserUtils.OpenExternalBrowser(Program.Website);
|
BrowserUtils.OpenExternalBrowser(Program.Website);
|
||||||
ForceClose();
|
ForceClose();
|
||||||
}
|
}
|
||||||
@@ -322,6 +315,10 @@ namespace TweetDuck.Core{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateBridge_UpdateDelayed(object sender, UpdateInfo update){
|
||||||
|
// stops the timer
|
||||||
|
}
|
||||||
|
|
||||||
private void updateBridge_UpdateDismissed(object sender, UpdateInfo update){
|
private void updateBridge_UpdateDismissed(object sender, UpdateInfo update){
|
||||||
Config.DismissedUpdate = update.VersionTag;
|
Config.DismissedUpdate = update.VersionTag;
|
||||||
Config.Save();
|
Config.Save();
|
||||||
@@ -329,9 +326,7 @@ namespace TweetDuck.Core{
|
|||||||
|
|
||||||
protected override void WndProc(ref Message m){
|
protected override void WndProc(ref Message m){
|
||||||
if (isLoaded && m.Msg == Program.WindowRestoreMessage){
|
if (isLoaded && m.Msg == Program.WindowRestoreMessage){
|
||||||
using Process me = Process.GetCurrentProcess();
|
if (WindowsUtils.CurrentProcessID == m.WParam.ToInt32()){
|
||||||
|
|
||||||
if (me.Id == m.WParam.ToInt32()){
|
|
||||||
trayIcon_ClickRestore(trayIcon, EventArgs.Empty);
|
trayIcon_ClickRestore(trayIcon, EventArgs.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -368,7 +363,14 @@ namespace TweetDuck.Core{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void ReloadToTweetDeck(){
|
public void ReloadToTweetDeck(){
|
||||||
Program.Resources.OnReloadTriggered();
|
#if DEBUG
|
||||||
|
ScriptLoader.HotSwap();
|
||||||
|
#else
|
||||||
|
if (ModifierKeys.HasFlag(Keys.Shift)){
|
||||||
|
ScriptLoader.ClearCache();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
ignoreUpdateCheckError = false;
|
ignoreUpdateCheckError = false;
|
||||||
browser.ReloadToTweetDeck();
|
browser.ReloadToTweetDeck();
|
||||||
AnalyticsFile.BrowserReloads.Trigger();
|
AnalyticsFile.BrowserReloads.Trigger();
|
||||||
@@ -499,8 +501,12 @@ namespace TweetDuck.Core{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void OnTweetNotification(){ // may be called multiple times, once for each type of notification
|
public void OnTweetNotification(){ // may be called multiple times, once for each type of notification
|
||||||
if (Config.EnableTrayHighlight && !ContainsFocus){
|
if (!ContainsFocus){
|
||||||
trayIcon.HasNotifications = true;
|
if (Config.EnableTrayHighlight){
|
||||||
|
trayIcon.HasNotifications = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
taskbarIcon.HasNotifications = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -17,6 +17,8 @@ namespace TweetDuck.Core{
|
|||||||
else return false;
|
else return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool HasAnyDialogs => Application.OpenForms.OfType<IAppDialog>().Any();
|
||||||
|
|
||||||
public static void CloseAllDialogs(){
|
public static void CloseAllDialogs(){
|
||||||
foreach(IAppDialog dialog in Application.OpenForms.OfType<IAppDialog>().Reverse()){
|
foreach(IAppDialog dialog in Application.OpenForms.OfType<IAppDialog>().Reverse()){
|
||||||
((Form)dialog).Close();
|
((Form)dialog).Close();
|
||||||
|
@@ -6,20 +6,18 @@ using TweetDuck.Core.Controls;
|
|||||||
using TweetDuck.Core.Utils;
|
using TweetDuck.Core.Utils;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using TweetDuck.Configuration;
|
using TweetDuck.Configuration;
|
||||||
using TweetDuck.Core.Adapters;
|
using TweetDuck.Core.Bridge;
|
||||||
using TweetDuck.Core.Management;
|
using TweetDuck.Core.Management;
|
||||||
using TweetDuck.Core.Notification;
|
using TweetDuck.Core.Notification;
|
||||||
using TweetDuck.Core.Other;
|
using TweetDuck.Core.Other;
|
||||||
using TweetDuck.Core.Other.Analytics;
|
using TweetDuck.Core.Other.Analytics;
|
||||||
using TweetLib.Core.Features.Twitter;
|
using TweetDuck.Resources;
|
||||||
using TweetLib.Core.Utils;
|
|
||||||
|
|
||||||
namespace TweetDuck.Core.Handling{
|
namespace TweetDuck.Core.Handling{
|
||||||
abstract class ContextMenuBase : IContextMenuHandler{
|
abstract class ContextMenuBase : IContextMenuHandler{
|
||||||
public static ContextInfo CurrentInfo { get; } = new ContextInfo();
|
|
||||||
|
|
||||||
protected static UserConfig Config => Program.Config.User;
|
protected static UserConfig Config => Program.Config.User;
|
||||||
private static ImageQuality ImageQuality => Config.TwitterImageQuality;
|
|
||||||
|
private static TwitterUtils.ImageQuality ImageQuality => Config.TwitterImageQuality;
|
||||||
|
|
||||||
private const CefMenuCommand MenuOpenLinkUrl = (CefMenuCommand)26500;
|
private const CefMenuCommand MenuOpenLinkUrl = (CefMenuCommand)26500;
|
||||||
private const CefMenuCommand MenuCopyLinkUrl = (CefMenuCommand)26501;
|
private const CefMenuCommand MenuCopyLinkUrl = (CefMenuCommand)26501;
|
||||||
@@ -42,11 +40,11 @@ namespace TweetDuck.Core.Handling{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public virtual void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){
|
public virtual void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){
|
||||||
if (!TwitterUrls.IsTweetDeck(frame.Url) || browser.IsLoading){
|
if (!TwitterUtils.IsTweetDeckWebsite(frame) || browser.IsLoading){
|
||||||
Context = CurrentInfo.Reset();
|
Context = TweetDeckBridge.ContextInfo.Reset();
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
Context = CurrentInfo.Create(parameters);
|
Context = TweetDeckBridge.ContextInfo.Create(parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parameters.TypeFlags.HasFlag(ContextMenuType.Selection) && !parameters.TypeFlags.HasFlag(ContextMenuType.Editable)){
|
if (parameters.TypeFlags.HasFlag(ContextMenuType.Selection) && !parameters.TypeFlags.HasFlag(ContextMenuType.Editable)){
|
||||||
@@ -55,13 +53,13 @@ namespace TweetDuck.Core.Handling{
|
|||||||
model.AddItem(MenuReadApplyROT13, "Apply ROT13");
|
model.AddItem(MenuReadApplyROT13, "Apply ROT13");
|
||||||
model.AddSeparator();
|
model.AddSeparator();
|
||||||
}
|
}
|
||||||
|
|
||||||
static string TextOpen(string name) => "Open " + name + " in browser";
|
string TextOpen(string name) => "Open "+name+" in browser";
|
||||||
static string TextCopy(string name) => "Copy " + name + " address";
|
string TextCopy(string name) => "Copy "+name+" address";
|
||||||
static string TextSave(string name) => "Save " + name + " as...";
|
string TextSave(string name) => "Save "+name+" as...";
|
||||||
|
|
||||||
if (Context.Types.HasFlag(ContextInfo.ContextType.Link) && !Context.UnsafeLinkUrl.EndsWith("tweetdeck.twitter.com/#", StringComparison.Ordinal)){
|
if (Context.Types.HasFlag(ContextInfo.ContextType.Link) && !Context.UnsafeLinkUrl.EndsWith("tweetdeck.twitter.com/#", StringComparison.Ordinal)){
|
||||||
if (TwitterUrls.RegexAccount.IsMatch(Context.UnsafeLinkUrl)){
|
if (TwitterUtils.RegexAccount.IsMatch(Context.UnsafeLinkUrl)){
|
||||||
model.AddItem(MenuOpenLinkUrl, TextOpen("account"));
|
model.AddItem(MenuOpenLinkUrl, TextOpen("account"));
|
||||||
model.AddItem(MenuCopyLinkUrl, TextCopy("account"));
|
model.AddItem(MenuCopyLinkUrl, TextCopy("account"));
|
||||||
model.AddItem(MenuCopyUsername, "Copy account username");
|
model.AddItem(MenuCopyUsername, "Copy account username");
|
||||||
@@ -80,7 +78,7 @@ namespace TweetDuck.Core.Handling{
|
|||||||
model.AddItem(MenuSaveMedia, TextSave("video"));
|
model.AddItem(MenuSaveMedia, TextSave("video"));
|
||||||
model.AddSeparator();
|
model.AddSeparator();
|
||||||
}
|
}
|
||||||
else if (Context.Types.HasFlag(ContextInfo.ContextType.Image) && Context.MediaUrl != FormNotificationBase.AppLogo.Url){
|
else if (Context.Types.HasFlag(ContextInfo.ContextType.Image) && Context.MediaUrl != TweetNotification.AppLogo.Url){
|
||||||
model.AddItem(MenuViewImage, "View image in photo viewer");
|
model.AddItem(MenuViewImage, "View image in photo viewer");
|
||||||
model.AddItem(MenuOpenMediaUrl, TextOpen("image"));
|
model.AddItem(MenuOpenMediaUrl, TextOpen("image"));
|
||||||
model.AddItem(MenuCopyMediaUrl, TextCopy("image"));
|
model.AddItem(MenuCopyMediaUrl, TextCopy("image"));
|
||||||
@@ -108,7 +106,7 @@ namespace TweetDuck.Core.Handling{
|
|||||||
|
|
||||||
case MenuCopyUsername: {
|
case MenuCopyUsername: {
|
||||||
string url = Context.UnsafeLinkUrl;
|
string url = Context.UnsafeLinkUrl;
|
||||||
Match match = TwitterUrls.RegexAccount.Match(url);
|
Match match = TwitterUtils.RegexAccount.Match(url);
|
||||||
|
|
||||||
SetClipboardText(control, match.Success ? match.Groups[1].Value : url);
|
SetClipboardText(control, match.Success ? match.Groups[1].Value : url);
|
||||||
control.InvokeAsyncSafe(analytics.AnalyticsFile.CopiedUsernames.Trigger);
|
control.InvokeAsyncSafe(analytics.AnalyticsFile.CopiedUsernames.Trigger);
|
||||||
@@ -116,11 +114,11 @@ namespace TweetDuck.Core.Handling{
|
|||||||
}
|
}
|
||||||
|
|
||||||
case MenuOpenMediaUrl:
|
case MenuOpenMediaUrl:
|
||||||
OpenBrowser(control, TwitterUrls.GetMediaLink(Context.MediaUrl, ImageQuality));
|
OpenBrowser(control, TwitterUtils.GetMediaLink(Context.MediaUrl, ImageQuality));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MenuCopyMediaUrl:
|
case MenuCopyMediaUrl:
|
||||||
SetClipboardText(control, TwitterUrls.GetMediaLink(Context.MediaUrl, ImageQuality));
|
SetClipboardText(control, TwitterUtils.GetMediaLink(Context.MediaUrl, ImageQuality));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MenuViewImage: {
|
case MenuViewImage: {
|
||||||
@@ -186,7 +184,7 @@ namespace TweetDuck.Core.Handling{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public virtual void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame){
|
public virtual void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame){
|
||||||
Context = CurrentInfo.Reset();
|
Context = TweetDeckBridge.ContextInfo.Reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual bool RunContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model, IRunContextMenuCallback callback){
|
public virtual bool RunContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model, IRunContextMenuCallback callback){
|
||||||
@@ -194,7 +192,7 @@ namespace TweetDuck.Core.Handling{
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected static void DeselectAll(IFrame frame){
|
protected static void DeselectAll(IFrame frame){
|
||||||
CefScriptExecutor.RunScript(frame, "window.getSelection().removeAllRanges()", "gen:deselect");
|
ScriptLoader.ExecuteScript(frame, "window.getSelection().removeAllRanges()", "gen:deselect");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static void OpenBrowser(Control control, string url){
|
protected static void OpenBrowser(Control control, string url){
|
||||||
@@ -206,7 +204,7 @@ namespace TweetDuck.Core.Handling{
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected static void InsertSelectionSearchItem(IMenuModel model, CefMenuCommand insertCommand, string insertLabel){
|
protected static void InsertSelectionSearchItem(IMenuModel model, CefMenuCommand insertCommand, string insertLabel){
|
||||||
model.InsertItemAt(model.GetIndexOf(MenuSearchInBrowser) + 1, insertCommand, insertLabel);
|
model.InsertItemAt(model.GetIndexOf(MenuSearchInBrowser)+1, insertCommand, insertLabel);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static void AddDebugMenuItems(IMenuModel model){
|
protected static void AddDebugMenuItems(IMenuModel model){
|
||||||
@@ -217,13 +215,13 @@ namespace TweetDuck.Core.Handling{
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected static void RemoveSeparatorIfLast(IMenuModel model){
|
protected static void RemoveSeparatorIfLast(IMenuModel model){
|
||||||
if (model.Count > 0 && model.GetTypeAt(model.Count - 1) == MenuItemType.Separator){
|
if (model.Count > 0 && model.GetTypeAt(model.Count-1) == MenuItemType.Separator){
|
||||||
model.RemoveAt(model.Count - 1);
|
model.RemoveAt(model.Count-1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static void AddSeparator(IMenuModel model){
|
protected static void AddSeparator(IMenuModel model){
|
||||||
if (model.Count > 0 && model.GetTypeAt(model.Count - 1) != MenuItemType.Separator){ // do not add separators if there is nothing to separate
|
if (model.Count > 0 && model.GetTypeAt(model.Count-1) != MenuItemType.Separator){ // do not add separators if there is nothing to separate
|
||||||
model.AddSeparator();
|
model.AddSeparator();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using TweetDuck.Core.Controls;
|
using TweetDuck.Core.Controls;
|
||||||
using TweetDuck.Core.Management;
|
using TweetDuck.Core.Management;
|
||||||
using TweetLib.Core.Features.Twitter;
|
using TweetDuck.Core.Utils;
|
||||||
|
|
||||||
namespace TweetDuck.Core.Handling{
|
namespace TweetDuck.Core.Handling{
|
||||||
sealed class ContextMenuBrowser : ContextMenuBase{
|
sealed class ContextMenuBrowser : ContextMenuBase{
|
||||||
@@ -24,7 +24,7 @@ namespace TweetDuck.Core.Handling{
|
|||||||
private const string TitleMuteNotifications = "Mute notifications";
|
private const string TitleMuteNotifications = "Mute notifications";
|
||||||
private const string TitleSettings = "Options";
|
private const string TitleSettings = "Options";
|
||||||
private const string TitlePlugins = "Plugins";
|
private const string TitlePlugins = "Plugins";
|
||||||
private const string TitleAboutProgram = "About " + Program.BrandName;
|
private const string TitleAboutProgram = "About "+Program.BrandName;
|
||||||
|
|
||||||
private readonly FormBrowser form;
|
private readonly FormBrowser form;
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ namespace TweetDuck.Core.Handling{
|
|||||||
|
|
||||||
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
|
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
|
||||||
|
|
||||||
if (isSelecting && !isEditing && TwitterUrls.IsTweetDeck(frame.Url)){
|
if (isSelecting && !isEditing && TwitterUtils.IsTweetDeckWebsite(frame)){
|
||||||
InsertSelectionSearchItem(model, MenuSearchInColumn, "Search in a column");
|
InsertSelectionSearchItem(model, MenuSearchInColumn, "Search in a column");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -25,7 +25,7 @@ namespace TweetDuck.Core.Handling.Filters{
|
|||||||
int responseLength = responseData.Length;
|
int responseLength = responseData.Length;
|
||||||
|
|
||||||
if (state == State.Reading){
|
if (state == State.Reading){
|
||||||
int bytesToRead = Math.Min(responseLength - offset, (int)Math.Min(dataIn?.Length ?? 0, int.MaxValue));
|
int bytesToRead = Math.Min(responseLength-offset, (int)Math.Min(dataIn?.Length ?? 0, int.MaxValue));
|
||||||
|
|
||||||
dataIn?.Read(responseData, offset, bytesToRead);
|
dataIn?.Read(responseData, offset, bytesToRead);
|
||||||
offset += bytesToRead;
|
offset += bytesToRead;
|
||||||
@@ -42,7 +42,7 @@ namespace TweetDuck.Core.Handling.Filters{
|
|||||||
return FilterStatus.NeedMoreData;
|
return FilterStatus.NeedMoreData;
|
||||||
}
|
}
|
||||||
else if (state == State.Writing){
|
else if (state == State.Writing){
|
||||||
int bytesToWrite = Math.Min(responseLength - offset, (int)Math.Min(dataOut.Length, int.MaxValue));
|
int bytesToWrite = Math.Min(responseLength-offset, (int)Math.Min(dataOut.Length, int.MaxValue));
|
||||||
|
|
||||||
if (bytesToWrite > 0){
|
if (bytesToWrite > 0){
|
||||||
dataOut.Write(responseData, offset, bytesToWrite);
|
dataOut.Write(responseData, offset, bytesToWrite);
|
||||||
|
@@ -11,11 +11,12 @@ namespace TweetDuck.Core.Handling.General{
|
|||||||
|
|
||||||
private static void UpdatePrefsInternal(){
|
private static void UpdatePrefsInternal(){
|
||||||
UserConfig config = Program.Config.User;
|
UserConfig config = Program.Config.User;
|
||||||
using IRequestContext ctx = Cef.GetGlobalRequestContext();
|
|
||||||
|
|
||||||
ctx.SetPreference("browser.enable_spellchecking", config.EnableSpellCheck, out string _);
|
using(IRequestContext ctx = Cef.GetGlobalRequestContext()){
|
||||||
ctx.SetPreference("spellcheck.dictionary", config.SpellCheckLanguage, out string _);
|
ctx.SetPreference("browser.enable_spellchecking", config.EnableSpellCheck, out string _);
|
||||||
ctx.SetPreference("settings.a11y.animation_policy", config.EnableAnimatedImages ? "allowed" : "none", out string _);
|
ctx.SetPreference("spellcheck.dictionary", config.SpellCheckLanguage, out string _);
|
||||||
|
ctx.SetPreference("settings.a11y.animation_policy", config.EnableAnimatedImages ? "allowed" : "none", out string _);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void IBrowserProcessHandler.OnContextInitialized(){
|
void IBrowserProcessHandler.OnContextInitialized(){
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
@@ -8,7 +9,7 @@ namespace TweetDuck.Core.Handling.General{
|
|||||||
sealed class FileDialogHandler : IDialogHandler{
|
sealed class FileDialogHandler : IDialogHandler{
|
||||||
public bool OnFileDialog(IWebBrowser browserControl, IBrowser browser, CefFileDialogMode mode, CefFileDialogFlags flags, string title, string defaultFilePath, List<string> acceptFilters, int selectedAcceptFilter, IFileDialogCallback callback){
|
public bool OnFileDialog(IWebBrowser browserControl, IBrowser browser, CefFileDialogMode mode, CefFileDialogFlags flags, string title, string defaultFilePath, List<string> acceptFilters, int selectedAcceptFilter, IFileDialogCallback callback){
|
||||||
if (mode == CefFileDialogMode.Open || mode == CefFileDialogMode.OpenMultiple){
|
if (mode == CefFileDialogMode.Open || mode == CefFileDialogMode.OpenMultiple){
|
||||||
string allFilters = string.Join(";", acceptFilters.SelectMany(ParseFileType).Where(filter => !string.IsNullOrEmpty(filter)).Select(filter => "*" + filter));
|
string allFilters = string.Join(";", acceptFilters.Select(filter => "*"+filter));
|
||||||
|
|
||||||
using(OpenFileDialog dialog = new OpenFileDialog{
|
using(OpenFileDialog dialog = new OpenFileDialog{
|
||||||
AutoUpgradeEnabled = true,
|
AutoUpgradeEnabled = true,
|
||||||
@@ -18,8 +19,8 @@ namespace TweetDuck.Core.Handling.General{
|
|||||||
Filter = $"All Supported Formats ({allFilters})|{allFilters}|All Files (*.*)|*.*"
|
Filter = $"All Supported Formats ({allFilters})|{allFilters}|All Files (*.*)|*.*"
|
||||||
}){
|
}){
|
||||||
if (dialog.ShowDialog() == DialogResult.OK){
|
if (dialog.ShowDialog() == DialogResult.OK){
|
||||||
string ext = Path.GetExtension(dialog.FileName)?.ToLower();
|
string ext = Path.GetExtension(dialog.FileName);
|
||||||
callback.Continue(acceptFilters.FindIndex(filter => ParseFileType(filter).Contains(ext)), dialog.FileNames.ToList());
|
callback.Continue(acceptFilters.FindIndex(filter => filter.Equals(ext, StringComparison.OrdinalIgnoreCase)), dialog.FileNames.ToList());
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
callback.Cancel();
|
callback.Cancel();
|
||||||
@@ -35,27 +36,5 @@ namespace TweetDuck.Core.Handling.General{
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IEnumerable<string> ParseFileType(string type){
|
|
||||||
if (string.IsNullOrEmpty(type)){
|
|
||||||
return new string[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type[0] == '.'){
|
|
||||||
return new string[]{ type };
|
|
||||||
}
|
|
||||||
|
|
||||||
switch(type){
|
|
||||||
case "image/jpeg": return new string[]{ ".jpg", ".jpeg" };
|
|
||||||
case "image/png": return new string[]{ ".png" };
|
|
||||||
case "image/gif": return new string[]{ ".gif" };
|
|
||||||
case "image/webp": return new string[]{ ".webp" };
|
|
||||||
case "video/mp4": return new string[]{ ".mp4" };
|
|
||||||
case "video/quicktime": return new string[]{ ".mov", ".qt" };
|
|
||||||
}
|
|
||||||
|
|
||||||
System.Diagnostics.Debugger.Break();
|
|
||||||
return new string[0];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -12,17 +12,15 @@ namespace TweetDuck.Core.Handling.General{
|
|||||||
int pipe = text.IndexOf('|');
|
int pipe = text.IndexOf('|');
|
||||||
|
|
||||||
if (pipe != -1){
|
if (pipe != -1){
|
||||||
icon = text.Substring(0, pipe) switch{
|
switch(text.Substring(0, pipe)){
|
||||||
"error" => MessageBoxIcon.Error,
|
case "error": icon = MessageBoxIcon.Error; break;
|
||||||
"warning" => MessageBoxIcon.Warning,
|
case "warning": icon = MessageBoxIcon.Warning; break;
|
||||||
"info" => MessageBoxIcon.Information,
|
case "info": icon = MessageBoxIcon.Information; break;
|
||||||
"question" => MessageBoxIcon.Question,
|
case "question": icon = MessageBoxIcon.Question; break;
|
||||||
_ => MessageBoxIcon.None
|
default: return new FormMessage(caption, text, icon);
|
||||||
};
|
|
||||||
|
|
||||||
if (icon != MessageBoxIcon.None){
|
|
||||||
text = text.Substring(pipe + 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
text = text.Substring(pipe+1);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new FormMessage(caption, text, icon);
|
return new FormMessage(caption, text, icon);
|
||||||
@@ -53,13 +51,13 @@ namespace TweetDuck.Core.Handling.General{
|
|||||||
input = new TextBox{
|
input = new TextBox{
|
||||||
Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Bottom,
|
Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Bottom,
|
||||||
Font = SystemFonts.MessageBoxFont,
|
Font = SystemFonts.MessageBoxFont,
|
||||||
Location = new Point(BrowserUtils.Scale(22 + inputPad, dpiScale), form.ActionPanelY - BrowserUtils.Scale(46, dpiScale)),
|
Location = new Point(BrowserUtils.Scale(22+inputPad, dpiScale), form.ActionPanelY-BrowserUtils.Scale(46, dpiScale)),
|
||||||
Size = new Size(form.ClientSize.Width - BrowserUtils.Scale(44 + inputPad, dpiScale), BrowserUtils.Scale(23, dpiScale))
|
Size = new Size(form.ClientSize.Width-BrowserUtils.Scale(44+inputPad, dpiScale), BrowserUtils.Scale(23, dpiScale))
|
||||||
};
|
};
|
||||||
|
|
||||||
form.Controls.Add(input);
|
form.Controls.Add(input);
|
||||||
form.ActiveControl = input;
|
form.ActiveControl = input;
|
||||||
form.Height += input.Size.Height + input.Margin.Vertical;
|
form.Height += input.Size.Height+input.Margin.Vertical;
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
callback.Continue(false);
|
callback.Continue(false);
|
||||||
|
@@ -4,15 +4,11 @@ using TweetDuck.Core.Utils;
|
|||||||
|
|
||||||
namespace TweetDuck.Core.Handling.General{
|
namespace TweetDuck.Core.Handling.General{
|
||||||
sealed class LifeSpanHandler : ILifeSpanHandler{
|
sealed class LifeSpanHandler : ILifeSpanHandler{
|
||||||
private static bool IsPopupAllowed(string url){
|
|
||||||
return url.StartsWith("https://twitter.com/teams/authorize?");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool HandleLinkClick(IWebBrowser browserControl, WindowOpenDisposition targetDisposition, string targetUrl){
|
public static bool HandleLinkClick(IWebBrowser browserControl, WindowOpenDisposition targetDisposition, string targetUrl){
|
||||||
switch(targetDisposition){
|
switch(targetDisposition){
|
||||||
case WindowOpenDisposition.NewBackgroundTab:
|
case WindowOpenDisposition.NewBackgroundTab:
|
||||||
case WindowOpenDisposition.NewForegroundTab:
|
case WindowOpenDisposition.NewForegroundTab:
|
||||||
case WindowOpenDisposition.NewPopup when !IsPopupAllowed(targetUrl):
|
case WindowOpenDisposition.NewPopup:
|
||||||
case WindowOpenDisposition.NewWindow:
|
case WindowOpenDisposition.NewWindow:
|
||||||
browserControl.AsControl().InvokeAsyncSafe(() => BrowserUtils.OpenExternalBrowser(targetUrl));
|
browserControl.AsControl().InvokeAsyncSafe(() => BrowserUtils.OpenExternalBrowser(targetUrl));
|
||||||
return true;
|
return true;
|
||||||
|
@@ -22,7 +22,7 @@ namespace TweetDuck.Core.Handling{
|
|||||||
extraMessage = "Please download the installer, and tick 'Install dev tools' during the installation process. The installer will automatically find and update your current installation of TweetDuck.";
|
extraMessage = "Please download the installer, and tick 'Install dev tools' during the installation process. The installer will automatically find and update your current installation of TweetDuck.";
|
||||||
}
|
}
|
||||||
|
|
||||||
FormMessage.Information("Dev Tools", "You do not have dev tools installed. " + extraMessage, FormMessage.OK);
|
FormMessage.Information("Dev Tools", "You do not have dev tools installed. "+extraMessage, FormMessage.OK);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -7,11 +7,10 @@ using CefSharp;
|
|||||||
using CefSharp.Handler;
|
using CefSharp.Handler;
|
||||||
using TweetDuck.Core.Handling.General;
|
using TweetDuck.Core.Handling.General;
|
||||||
using TweetDuck.Core.Utils;
|
using TweetDuck.Core.Utils;
|
||||||
using TweetLib.Core.Utils;
|
|
||||||
|
|
||||||
namespace TweetDuck.Core.Handling{
|
namespace TweetDuck.Core.Handling{
|
||||||
class RequestHandlerBase : DefaultRequestHandler{
|
class RequestHandlerBase : DefaultRequestHandler{
|
||||||
private static readonly Regex TweetDeckResourceUrl = new Regex(@"/dist/(.*?)\.(.*?)\.(css|js)$");
|
private static readonly Regex TweetDeckResourceUrl = new Regex(@"/dist/(.*?)\.(.*?)\.(css|js)$", RegexOptions.Compiled);
|
||||||
private static readonly SortedList<string, string> TweetDeckHashes = new SortedList<string, string>(4);
|
private static readonly SortedList<string, string> TweetDeckHashes = new SortedList<string, string>(4);
|
||||||
|
|
||||||
public static void LoadResourceRewriteRules(string rules){
|
public static void LoadResourceRewriteRules(string rules){
|
||||||
@@ -22,13 +21,21 @@ namespace TweetDuck.Core.Handling{
|
|||||||
TweetDeckHashes.Clear();
|
TweetDeckHashes.Clear();
|
||||||
|
|
||||||
foreach(string rule in rules.Replace(" ", "").ToLower().Split(',')){
|
foreach(string rule in rules.Replace(" ", "").ToLower().Split(',')){
|
||||||
var (key, hash) = StringUtils.SplitInTwo(rule, '=') ?? throw new ArgumentException("A rule must have one '=' character: " + rule);
|
string[] split = rule.Split('=');
|
||||||
|
|
||||||
if (hash.All(chr => char.IsDigit(chr) || (chr >= 'a' && chr <= 'f'))){
|
if (split.Length == 2){
|
||||||
TweetDeckHashes.Add(key, hash);
|
string key = split[0];
|
||||||
|
string hash = split[1];
|
||||||
|
|
||||||
|
if (hash.All(chr => char.IsDigit(chr) || (chr >= 'a' && chr <= 'f'))){
|
||||||
|
TweetDeckHashes.Add(key, hash);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
throw new ArgumentException("Invalid hash characters: "+rule);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
throw new ArgumentException("Invalid hash characters: " + rule);
|
throw new ArgumentException("A rule must have exactly one '=' character: "+rule);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,7 +2,6 @@
|
|||||||
using CefSharp;
|
using CefSharp;
|
||||||
using TweetDuck.Core.Handling.Filters;
|
using TweetDuck.Core.Handling.Filters;
|
||||||
using TweetDuck.Core.Utils;
|
using TweetDuck.Core.Utils;
|
||||||
using TweetLib.Core.Features.Twitter;
|
|
||||||
|
|
||||||
namespace TweetDuck.Core.Handling{
|
namespace TweetDuck.Core.Handling{
|
||||||
sealed class RequestHandlerBrowser : RequestHandlerBase{
|
sealed class RequestHandlerBrowser : RequestHandlerBase{
|
||||||
@@ -16,7 +15,7 @@ namespace TweetDuck.Core.Handling{
|
|||||||
public override CefReturnValue OnBeforeResourceLoad(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IRequestCallback callback){
|
public override CefReturnValue OnBeforeResourceLoad(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IRequestCallback callback){
|
||||||
if (request.ResourceType == ResourceType.MainFrame){
|
if (request.ResourceType == ResourceType.MainFrame){
|
||||||
if (request.Url.EndsWith("//twitter.com/")){
|
if (request.Url.EndsWith("//twitter.com/")){
|
||||||
request.Url = TwitterUrls.TweetDeck; // redirect plain twitter.com requests, fixes bugs with login 2FA
|
request.Url = TwitterUtils.TweetDeckURL; // redirect plain twitter.com requests, fixes bugs with login 2FA
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (request.ResourceType == ResourceType.Script){
|
else if (request.ResourceType == ResourceType.Script){
|
||||||
@@ -42,9 +41,6 @@ namespace TweetDuck.Core.Handling{
|
|||||||
BlockNextUserNavUrl = string.Empty;
|
BlockNextUserNavUrl = string.Empty;
|
||||||
return block;
|
return block;
|
||||||
}
|
}
|
||||||
else if (request.TransitionType.HasFlag(TransitionType.ForwardBack) && TwitterUrls.IsTweetDeck(frame.Url)){
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return base.OnBeforeBrowse(browserControl, browser, frame, request, userGesture, isRedirect);
|
return base.OnBeforeBrowse(browserControl, browser, frame, request, userGesture, isRedirect);
|
||||||
}
|
}
|
||||||
|
@@ -38,7 +38,7 @@ namespace TweetDuck.Core.Management{
|
|||||||
AutoClearTimer = new Timer(state => {
|
AutoClearTimer = new Timer(state => {
|
||||||
if (AutoClearTimer != null){
|
if (AutoClearTimer != null){
|
||||||
try{
|
try{
|
||||||
if (CalculateCacheSize() >= Program.Config.System.ClearCacheThreshold * 1024L * 1024L){
|
if (CalculateCacheSize() >= Program.Config.System.ClearCacheThreshold*1024L*1024L){
|
||||||
SetClearOnExit();
|
SetClearOnExit();
|
||||||
}
|
}
|
||||||
}catch(Exception){
|
}catch(Exception){
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using CefSharp;
|
using CefSharp;
|
||||||
using TweetLib.Core.Utils;
|
using TweetDuck.Core.Utils;
|
||||||
|
|
||||||
namespace TweetDuck.Core.Management{
|
namespace TweetDuck.Core.Management{
|
||||||
sealed class ContextInfo{
|
sealed class ContextInfo{
|
||||||
@@ -107,7 +107,7 @@ namespace TweetDuck.Core.Management{
|
|||||||
private string unsafeLinkUrl = string.Empty;
|
private string unsafeLinkUrl = string.Empty;
|
||||||
private string mediaUrl = string.Empty;
|
private string mediaUrl = string.Empty;
|
||||||
|
|
||||||
private ChirpInfo chirp = default;
|
private ChirpInfo chirp = default(ChirpInfo);
|
||||||
|
|
||||||
public void AddContext(IContextMenuParams parameters){
|
public void AddContext(IContextMenuParams parameters){
|
||||||
ContextMenuType flags = parameters.TypeFlags;
|
ContextMenuType flags = parameters.TypeFlags;
|
||||||
|
@@ -3,9 +3,9 @@ using System.Collections.Generic;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using TweetDuck.Core.Other;
|
using TweetDuck.Core.Other;
|
||||||
using TweetLib.Core.Data;
|
using TweetDuck.Data;
|
||||||
using TweetLib.Core.Features.Plugins;
|
using TweetDuck.Plugins;
|
||||||
using TweetLib.Core.Features.Plugins.Enums;
|
using TweetDuck.Plugins.Enums;
|
||||||
|
|
||||||
namespace TweetDuck.Core.Management{
|
namespace TweetDuck.Core.Management{
|
||||||
sealed class ProfileManager{
|
sealed class ProfileManager{
|
||||||
@@ -49,7 +49,7 @@ namespace TweetDuck.Core.Management{
|
|||||||
try{
|
try{
|
||||||
stream.WriteFile(new string[]{ "plugin.data", plugin.Identifier, path.Relative }, path.Full);
|
stream.WriteFile(new string[]{ "plugin.data", plugin.Identifier, path.Relative }, path.Full);
|
||||||
}catch(ArgumentOutOfRangeException e){
|
}catch(ArgumentOutOfRangeException e){
|
||||||
FormMessage.Warning("Export Profile", "Could not include a plugin file in the export. " + e.Message, FormMessage.OK);
|
FormMessage.Warning("Export Profile", "Could not include a plugin file in the export. "+e.Message, FormMessage.OK);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -73,27 +73,28 @@ namespace TweetDuck.Core.Management{
|
|||||||
Items items = Items.None;
|
Items items = Items.None;
|
||||||
|
|
||||||
try{
|
try{
|
||||||
using CombinedFileStream stream = new CombinedFileStream(new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.None));
|
using(CombinedFileStream stream = new CombinedFileStream(new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.None))){
|
||||||
string key;
|
string key;
|
||||||
|
|
||||||
while((key = stream.SkipFile()) != null){
|
while((key = stream.SkipFile()) != null){
|
||||||
switch(key){
|
switch(key){
|
||||||
case "config":
|
case "config":
|
||||||
items |= Items.UserConfig;
|
items |= Items.UserConfig;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "system":
|
case "system":
|
||||||
items |= Items.SystemConfig;
|
items |= Items.SystemConfig;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "plugin.config":
|
case "plugin.config":
|
||||||
case "plugin.data":
|
case "plugin.data":
|
||||||
items |= Items.PluginData;
|
items |= Items.PluginData;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "cookies":
|
case "cookies":
|
||||||
items |= Items.Session;
|
items |= Items.Session;
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}catch(Exception){
|
}catch(Exception){
|
||||||
@@ -139,7 +140,7 @@ namespace TweetDuck.Core.Management{
|
|||||||
|
|
||||||
entry.WriteToFile(Path.Combine(Program.PluginDataPath, value[0], value[1]), true);
|
entry.WriteToFile(Path.Combine(Program.PluginDataPath, value[0], value[1]), true);
|
||||||
|
|
||||||
if (!plugins.Plugins.Any(plugin => plugin.Identifier.Equals(value[0]))){
|
if (!plugins.IsPluginInstalled(value[0])){
|
||||||
missingPlugins.Add(value[0]);
|
missingPlugins.Add(value[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -157,7 +158,7 @@ namespace TweetDuck.Core.Management{
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (missingPlugins.Count > 0){
|
if (missingPlugins.Count > 0){
|
||||||
FormMessage.Information("Profile Import", "Detected missing plugins when importing plugin data:\n" + string.Join("\n", missingPlugins), FormMessage.OK);
|
FormMessage.Information("Profile Import", "Detected missing plugins when importing plugin data:\n"+string.Join("\n", missingPlugins), FormMessage.OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@@ -40,7 +40,7 @@ namespace TweetDuck.Core.Management{
|
|||||||
|
|
||||||
if ((process = Process.Start(new ProcessStartInfo{
|
if ((process = Process.Start(new ProcessStartInfo{
|
||||||
FileName = Path.Combine(Program.ProgramPath, "TweetDuck.Video.exe"),
|
FileName = Path.Combine(Program.ProgramPath, "TweetDuck.Video.exe"),
|
||||||
Arguments = $"{owner.Handle} {(int)Math.Floor(100F * owner.GetDPIScale())} {Config.VideoPlayerVolume} \"{url}\" \"{pipe.GenerateToken()}\"",
|
Arguments = $"{owner.Handle} {(int)Math.Floor(100F*owner.GetDPIScale())} {Config.VideoPlayerVolume} \"{url}\" \"{pipe.GenerateToken()}\"",
|
||||||
UseShellExecute = false,
|
UseShellExecute = false,
|
||||||
RedirectStandardOutput = true
|
RedirectStandardOutput = true
|
||||||
})) != null){
|
})) != null){
|
||||||
@@ -135,7 +135,7 @@ namespace TweetDuck.Core.Management{
|
|||||||
|
|
||||||
private void process_OutputDataReceived(object sender, DataReceivedEventArgs e){
|
private void process_OutputDataReceived(object sender, DataReceivedEventArgs e){
|
||||||
if (!string.IsNullOrEmpty(e.Data)){
|
if (!string.IsNullOrEmpty(e.Data)){
|
||||||
Program.Reporter.LogVerbose("[VideoPlayer] " + e.Data);
|
Program.Reporter.LogVerbose("[VideoPlayer] "+e.Data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -2,17 +2,17 @@
|
|||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using CefSharp;
|
using CefSharp;
|
||||||
using TweetDuck.Core.Controls;
|
using TweetDuck.Core.Controls;
|
||||||
using TweetLib.Core.Features.Notifications;
|
using TweetDuck.Plugins;
|
||||||
using TweetLib.Core.Features.Plugins;
|
using TweetDuck.Resources;
|
||||||
|
|
||||||
namespace TweetDuck.Core.Notification.Example{
|
namespace TweetDuck.Core.Notification.Example{
|
||||||
sealed class FormNotificationExample : FormNotificationMain{
|
sealed class FormNotificationExample : FormNotificationMain{
|
||||||
public override bool RequiresResize => true;
|
public override bool RequiresResize => true;
|
||||||
protected override bool CanDragWindow => Config.NotificationPosition == DesktopNotification.Position.Custom;
|
protected override bool CanDragWindow => Config.NotificationPosition == TweetNotification.Position.Custom;
|
||||||
|
|
||||||
protected override FormBorderStyle NotificationBorderStyle{
|
protected override FormBorderStyle NotificationBorderStyle{
|
||||||
get{
|
get{
|
||||||
if (Config.NotificationSize == DesktopNotification.Size.Custom){
|
if (Config.NotificationSize == TweetNotification.Size.Custom){
|
||||||
switch(base.NotificationBorderStyle){
|
switch(base.NotificationBorderStyle){
|
||||||
case FormBorderStyle.FixedSingle: return FormBorderStyle.Sizable;
|
case FormBorderStyle.FixedSingle: return FormBorderStyle.Sizable;
|
||||||
case FormBorderStyle.FixedToolWindow: return FormBorderStyle.SizableToolWindow;
|
case FormBorderStyle.FixedToolWindow: return FormBorderStyle.SizableToolWindow;
|
||||||
@@ -23,22 +23,22 @@ namespace TweetDuck.Core.Notification.Example{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string BodyClasses => base.BodyClasses + " td-example";
|
protected override string BodyClasses => base.BodyClasses+" td-example";
|
||||||
|
|
||||||
public event EventHandler Ready;
|
public event EventHandler Ready;
|
||||||
|
|
||||||
private readonly DesktopNotification exampleNotification;
|
private readonly TweetNotification exampleNotification;
|
||||||
|
|
||||||
public FormNotificationExample(FormBrowser owner, PluginManager pluginManager) : base(owner, pluginManager, false){
|
public FormNotificationExample(FormBrowser owner, PluginManager pluginManager) : base(owner, pluginManager, false){
|
||||||
browser.LoadingStateChanged += browser_LoadingStateChanged;
|
browser.LoadingStateChanged += browser_LoadingStateChanged;
|
||||||
|
|
||||||
string exampleTweetHTML = Program.Resources.LoadSilent("pages/example.html")?.Replace("{avatar}", AppLogo.Url) ?? string.Empty;
|
string exampleTweetHTML = ScriptLoader.LoadResourceSilent("pages/example.html")?.Replace("{avatar}", TweetNotification.AppLogo.Url) ?? string.Empty;
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
exampleTweetHTML = exampleTweetHTML.Replace("</p>", @"</p><div style='margin-top:256px'>Scrollbar test padding...</div>");
|
exampleTweetHTML = exampleTweetHTML.Replace("</p>", @"</p><div style='margin-top:256px'>Scrollbar test padding...</div>");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
exampleNotification = new DesktopNotification(string.Empty, string.Empty, "Home", exampleTweetHTML, 176, string.Empty, string.Empty);
|
exampleNotification = new TweetNotification(string.Empty, string.Empty, "Home", exampleTweetHTML, 176, string.Empty, string.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e){
|
private void browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e){
|
||||||
|
@@ -1,34 +1,28 @@
|
|||||||
using CefSharp.WinForms;
|
using CefSharp.WinForms;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using CefSharp;
|
|
||||||
using TweetDuck.Configuration;
|
using TweetDuck.Configuration;
|
||||||
|
using TweetDuck.Core.Bridge;
|
||||||
using TweetDuck.Core.Controls;
|
using TweetDuck.Core.Controls;
|
||||||
using TweetDuck.Core.Handling;
|
using TweetDuck.Core.Handling;
|
||||||
using TweetDuck.Core.Handling.General;
|
using TweetDuck.Core.Handling.General;
|
||||||
using TweetDuck.Core.Other.Analytics;
|
using TweetDuck.Core.Other.Analytics;
|
||||||
using TweetDuck.Core.Utils;
|
using TweetDuck.Core.Utils;
|
||||||
using TweetDuck.Data;
|
|
||||||
using TweetLib.Core.Features.Notifications;
|
|
||||||
using TweetLib.Core.Features.Twitter;
|
|
||||||
|
|
||||||
namespace TweetDuck.Core.Notification{
|
namespace TweetDuck.Core.Notification{
|
||||||
abstract partial class FormNotificationBase : Form, AnalyticsFile.IProvider{
|
abstract partial class FormNotificationBase : Form, AnalyticsFile.IProvider{
|
||||||
public static readonly ResourceLink AppLogo = new ResourceLink("https://ton.twimg.com/tduck/avatar", ResourceHandler.FromByteArray(Properties.Resources.avatar, "image/png"));
|
|
||||||
|
|
||||||
public static string FontSize = null;
|
|
||||||
public static string HeadLayout = null;
|
|
||||||
|
|
||||||
protected static UserConfig Config => Program.Config.User;
|
protected static UserConfig Config => Program.Config.User;
|
||||||
|
|
||||||
protected static int FontSizeLevel{
|
protected static int FontSizeLevel{
|
||||||
get => FontSize switch{
|
get{
|
||||||
"largest" => 4,
|
switch(TweetDeckBridge.FontSize){
|
||||||
"large" => 3,
|
case "largest": return 4;
|
||||||
"small" => 1,
|
case "large": return 3;
|
||||||
"smallest" => 0,
|
case "small": return 1;
|
||||||
_ => 2
|
case "smallest": return 0;
|
||||||
};
|
default: return 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual Point PrimaryLocation{
|
protected virtual Point PrimaryLocation{
|
||||||
@@ -36,7 +30,7 @@ namespace TweetDuck.Core.Notification{
|
|||||||
Screen screen;
|
Screen screen;
|
||||||
|
|
||||||
if (Config.NotificationDisplay > 0 && Config.NotificationDisplay <= Screen.AllScreens.Length){
|
if (Config.NotificationDisplay > 0 && Config.NotificationDisplay <= Screen.AllScreens.Length){
|
||||||
screen = Screen.AllScreens[Config.NotificationDisplay - 1];
|
screen = Screen.AllScreens[Config.NotificationDisplay-1];
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
screen = Screen.FromControl(owner);
|
screen = Screen.FromControl(owner);
|
||||||
@@ -45,21 +39,21 @@ namespace TweetDuck.Core.Notification{
|
|||||||
int edgeDist = Config.NotificationEdgeDistance;
|
int edgeDist = Config.NotificationEdgeDistance;
|
||||||
|
|
||||||
switch(Config.NotificationPosition){
|
switch(Config.NotificationPosition){
|
||||||
case DesktopNotification.Position.TopLeft:
|
case TweetNotification.Position.TopLeft:
|
||||||
return new Point(screen.WorkingArea.X + edgeDist, screen.WorkingArea.Y + edgeDist);
|
return new Point(screen.WorkingArea.X+edgeDist, screen.WorkingArea.Y+edgeDist);
|
||||||
|
|
||||||
case DesktopNotification.Position.TopRight:
|
case TweetNotification.Position.TopRight:
|
||||||
return new Point(screen.WorkingArea.X + screen.WorkingArea.Width - edgeDist - Width, screen.WorkingArea.Y + edgeDist);
|
return new Point(screen.WorkingArea.X+screen.WorkingArea.Width-edgeDist-Width, screen.WorkingArea.Y+edgeDist);
|
||||||
|
|
||||||
case DesktopNotification.Position.BottomLeft:
|
case TweetNotification.Position.BottomLeft:
|
||||||
return new Point(screen.WorkingArea.X + edgeDist, screen.WorkingArea.Y + screen.WorkingArea.Height - edgeDist - Height);
|
return new Point(screen.WorkingArea.X+edgeDist, screen.WorkingArea.Y+screen.WorkingArea.Height-edgeDist-Height);
|
||||||
|
|
||||||
case DesktopNotification.Position.BottomRight:
|
case TweetNotification.Position.BottomRight:
|
||||||
return new Point(screen.WorkingArea.X + screen.WorkingArea.Width - edgeDist - Width, screen.WorkingArea.Y + screen.WorkingArea.Height - edgeDist - Height);
|
return new Point(screen.WorkingArea.X+screen.WorkingArea.Width-edgeDist-Width, screen.WorkingArea.Y+screen.WorkingArea.Height-edgeDist-Height);
|
||||||
|
|
||||||
case DesktopNotification.Position.Custom:
|
case TweetNotification.Position.Custom:
|
||||||
if (!Config.IsCustomNotificationPositionSet){
|
if (!Config.IsCustomNotificationPositionSet){
|
||||||
Config.CustomNotificationPosition = new Point(screen.WorkingArea.X + screen.WorkingArea.Width - edgeDist - Width, screen.WorkingArea.Y + edgeDist);
|
Config.CustomNotificationPosition = new Point(screen.WorkingArea.X+screen.WorkingArea.Width-edgeDist-Width, screen.WorkingArea.Y+edgeDist);
|
||||||
Config.Save();
|
Config.Save();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,14 +94,14 @@ namespace TweetDuck.Core.Notification{
|
|||||||
protected override bool ShowWithoutActivation => true;
|
protected override bool ShowWithoutActivation => true;
|
||||||
|
|
||||||
protected float DpiScale { get; }
|
protected float DpiScale { get; }
|
||||||
protected double SizeScale => DpiScale * Config.ZoomLevel / 100.0;
|
protected double SizeScale => DpiScale*Config.ZoomLevel/100.0;
|
||||||
|
|
||||||
protected readonly FormBrowser owner;
|
protected readonly FormBrowser owner;
|
||||||
protected readonly ChromiumWebBrowser browser;
|
protected readonly ChromiumWebBrowser browser;
|
||||||
|
|
||||||
private readonly ResourceHandlerNotification resourceHandler = new ResourceHandlerNotification();
|
private readonly ResourceHandlerNotification resourceHandler = new ResourceHandlerNotification();
|
||||||
|
|
||||||
private DesktopNotification currentNotification;
|
private TweetNotification currentNotification;
|
||||||
private int pauseCounter;
|
private int pauseCounter;
|
||||||
|
|
||||||
public string CurrentTweetUrl => currentNotification?.TweetUrl;
|
public string CurrentTweetUrl => currentNotification?.TweetUrl;
|
||||||
@@ -128,8 +122,8 @@ namespace TweetDuck.Core.Notification{
|
|||||||
this.owner.FormClosed += owner_FormClosed;
|
this.owner.FormClosed += owner_FormClosed;
|
||||||
|
|
||||||
ResourceHandlerFactory resourceHandlerFactory = new ResourceHandlerFactory();
|
ResourceHandlerFactory resourceHandlerFactory = new ResourceHandlerFactory();
|
||||||
resourceHandlerFactory.RegisterHandler(TwitterUrls.TweetDeck, this.resourceHandler);
|
resourceHandlerFactory.RegisterHandler(TwitterUtils.TweetDeckURL, this.resourceHandler);
|
||||||
resourceHandlerFactory.RegisterHandler(AppLogo);
|
resourceHandlerFactory.RegisterHandler(TweetNotification.AppLogo);
|
||||||
|
|
||||||
this.browser = new ChromiumWebBrowser("about:blank"){
|
this.browser = new ChromiumWebBrowser("about:blank"){
|
||||||
MenuHandler = new ContextMenuNotification(this, enableContextMenu),
|
MenuHandler = new ContextMenuNotification(this, enableContextMenu),
|
||||||
@@ -194,13 +188,13 @@ namespace TweetDuck.Core.Notification{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract string GetTweetHTML(DesktopNotification tweet);
|
protected abstract string GetTweetHTML(TweetNotification tweet);
|
||||||
|
|
||||||
protected virtual void LoadTweet(DesktopNotification tweet){
|
protected virtual void LoadTweet(TweetNotification tweet){
|
||||||
currentNotification = tweet;
|
currentNotification = tweet;
|
||||||
resourceHandler.SetHTML(GetTweetHTML(tweet));
|
resourceHandler.SetHTML(GetTweetHTML(tweet));
|
||||||
|
|
||||||
browser.Load(TwitterUrls.TweetDeck);
|
browser.Load(TwitterUtils.TweetDeckURL);
|
||||||
DisplayTooltip(null);
|
DisplayTooltip(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -2,16 +2,14 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using TweetDuck.Core.Adapters;
|
|
||||||
using TweetDuck.Core.Bridge;
|
using TweetDuck.Core.Bridge;
|
||||||
using TweetDuck.Core.Controls;
|
using TweetDuck.Core.Controls;
|
||||||
using TweetDuck.Core.Handling;
|
using TweetDuck.Core.Handling;
|
||||||
using TweetDuck.Core.Utils;
|
using TweetDuck.Core.Utils;
|
||||||
|
using TweetDuck.Data;
|
||||||
using TweetDuck.Plugins;
|
using TweetDuck.Plugins;
|
||||||
using TweetLib.Core.Data;
|
using TweetDuck.Plugins.Enums;
|
||||||
using TweetLib.Core.Features.Notifications;
|
using TweetDuck.Resources;
|
||||||
using TweetLib.Core.Features.Plugins;
|
|
||||||
using TweetLib.Core.Features.Plugins.Enums;
|
|
||||||
|
|
||||||
namespace TweetDuck.Core.Notification{
|
namespace TweetDuck.Core.Notification{
|
||||||
abstract partial class FormNotificationMain : FormNotificationBase{
|
abstract partial class FormNotificationMain : FormNotificationBase{
|
||||||
@@ -46,22 +44,32 @@ namespace TweetDuck.Core.Notification{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private int BaseClientWidth{
|
private int BaseClientWidth{
|
||||||
get => Config.NotificationSize switch{
|
get{
|
||||||
DesktopNotification.Size.Custom => Config.CustomNotificationSize.Width,
|
switch(Config.NotificationSize){
|
||||||
_ => BrowserUtils.Scale(284, SizeScale * (1.0 + 0.05 * FontSizeLevel))
|
default:
|
||||||
};
|
return BrowserUtils.Scale(284, SizeScale*(1.0+0.05*FontSizeLevel));
|
||||||
|
|
||||||
|
case TweetNotification.Size.Custom:
|
||||||
|
return Config.CustomNotificationSize.Width;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private int BaseClientHeight{
|
private int BaseClientHeight{
|
||||||
get => Config.NotificationSize switch{
|
get{
|
||||||
DesktopNotification.Size.Custom => Config.CustomNotificationSize.Height,
|
switch(Config.NotificationSize){
|
||||||
_ => BrowserUtils.Scale(122, SizeScale * (1.0 + 0.08 * FontSizeLevel))
|
default:
|
||||||
};
|
return BrowserUtils.Scale(122, SizeScale*(1.0+0.08*FontSizeLevel));
|
||||||
|
|
||||||
|
case TweetNotification.Size.Custom:
|
||||||
|
return Config.CustomNotificationSize.Height;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual string BodyClasses => IsCursorOverBrowser ? "td-notification td-hover" : "td-notification";
|
protected virtual string BodyClasses => IsCursorOverBrowser ? "td-notification td-hover" : "td-notification";
|
||||||
|
|
||||||
public Size BrowserSize => Config.DisplayNotificationTimer ? new Size(ClientSize.Width, ClientSize.Height - timerBarHeight) : ClientSize;
|
public Size BrowserSize => Config.DisplayNotificationTimer ? new Size(ClientSize.Width, ClientSize.Height-timerBarHeight) : ClientSize;
|
||||||
|
|
||||||
protected FormNotificationMain(FormBrowser owner, PluginManager pluginManager, bool enableContextMenu) : base(owner, enableContextMenu){
|
protected FormNotificationMain(FormBrowser owner, PluginManager pluginManager, bool enableContextMenu) : base(owner, enableContextMenu){
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
@@ -75,7 +83,7 @@ namespace TweetDuck.Core.Notification{
|
|||||||
browser.LoadingStateChanged += Browser_LoadingStateChanged;
|
browser.LoadingStateChanged += Browser_LoadingStateChanged;
|
||||||
browser.FrameLoadEnd += Browser_FrameLoadEnd;
|
browser.FrameLoadEnd += Browser_FrameLoadEnd;
|
||||||
|
|
||||||
plugins.Register(PluginEnvironment.Notification, new PluginDispatcher(browser));
|
plugins.Register(browser, PluginEnvironment.Notification, this);
|
||||||
|
|
||||||
mouseHookDelegate = MouseHookProc;
|
mouseHookDelegate = MouseHookProc;
|
||||||
Disposed += (sender, args) => StopMouseHook(true);
|
Disposed += (sender, args) => StopMouseHook(true);
|
||||||
@@ -102,7 +110,7 @@ namespace TweetDuck.Core.Notification{
|
|||||||
int eventType = wParam.ToInt32();
|
int eventType = wParam.ToInt32();
|
||||||
|
|
||||||
if (eventType == NativeMethods.WM_MOUSEWHEEL && IsCursorOverBrowser){
|
if (eventType == NativeMethods.WM_MOUSEWHEEL && IsCursorOverBrowser){
|
||||||
browser.SendMouseWheelEvent(0, 0, 0, BrowserUtils.Scale(NativeMethods.GetMouseHookData(lParam), Config.NotificationScrollSpeed * 0.01), CefEventFlags.None);
|
browser.SendMouseWheelEvent(0, 0, 0, BrowserUtils.Scale(NativeMethods.GetMouseHookData(lParam), Config.NotificationScrollSpeed*0.01), CefEventFlags.None);
|
||||||
return NativeMethods.HOOK_HANDLED;
|
return NativeMethods.HOOK_HANDLED;
|
||||||
}
|
}
|
||||||
else if (eventType == NativeMethods.WM_XBUTTONDOWN && DesktopBounds.Contains(Cursor.Position)){
|
else if (eventType == NativeMethods.WM_XBUTTONDOWN && DesktopBounds.Contains(Cursor.Position)){
|
||||||
@@ -156,7 +164,7 @@ namespace TweetDuck.Core.Notification{
|
|||||||
|
|
||||||
if (frame.IsMain && browser.Address != "about:blank"){
|
if (frame.IsMain && browser.Address != "about:blank"){
|
||||||
frame.ExecuteJavaScriptAsync(PropertyBridge.GenerateScript(PropertyBridge.Environment.Notification));
|
frame.ExecuteJavaScriptAsync(PropertyBridge.GenerateScript(PropertyBridge.Environment.Notification));
|
||||||
CefScriptExecutor.RunFile(frame, "notification.js");
|
ScriptLoader.ExecuteFile(frame, "notification.js", this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,23 +174,14 @@ namespace TweetDuck.Core.Notification{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void timerHideProgress_Tick(object sender, EventArgs e){
|
private void timerHideProgress_Tick(object sender, EventArgs e){
|
||||||
bool isCursorInside = Bounds.Contains(Cursor.Position);
|
if (Bounds.Contains(Cursor.Position) || FreezeTimer || ContextMenuOpen){
|
||||||
|
|
||||||
if (isCursorInside){
|
|
||||||
StartMouseHook();
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
StopMouseHook(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isCursorInside || FreezeTimer || ContextMenuOpen){
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
timeLeft -= timerProgress.Interval;
|
timeLeft -= timerProgress.Interval;
|
||||||
|
|
||||||
int value = BrowserUtils.Scale(progressBarTimer.Maximum + 25, (totalTime - timeLeft) / (double)totalTime);
|
int value = BrowserUtils.Scale(progressBarTimer.Maximum+25, (totalTime-timeLeft)/(double)totalTime);
|
||||||
progressBarTimer.SetValueInstant(Config.NotificationTimerCountDown ? progressBarTimer.Maximum - value : value);
|
progressBarTimer.SetValueInstant(Config.NotificationTimerCountDown ? progressBarTimer.Maximum-value : value);
|
||||||
|
|
||||||
if (timeLeft <= 0){
|
if (timeLeft <= 0){
|
||||||
FinishCurrentNotification();
|
FinishCurrentNotification();
|
||||||
@@ -191,7 +190,7 @@ namespace TweetDuck.Core.Notification{
|
|||||||
|
|
||||||
// notification methods
|
// notification methods
|
||||||
|
|
||||||
public virtual void ShowNotification(DesktopNotification notification){
|
public virtual void ShowNotification(TweetNotification notification){
|
||||||
LoadTweet(notification);
|
LoadTweet(notification);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,8 +227,8 @@ namespace TweetDuck.Core.Notification{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string GetTweetHTML(DesktopNotification tweet){
|
protected override string GetTweetHTML(TweetNotification tweet){
|
||||||
string html = tweet.GenerateHtml(BodyClasses, HeadLayout, Config.CustomNotificationCSS);
|
string html = tweet.GenerateHtml(BodyClasses, this);
|
||||||
|
|
||||||
foreach(InjectedHTML injection in plugins.NotificationInjections){
|
foreach(InjectedHTML injection in plugins.NotificationInjections){
|
||||||
html = injection.InjectInto(html);
|
html = injection.InjectInto(html);
|
||||||
@@ -238,7 +237,7 @@ namespace TweetDuck.Core.Notification{
|
|||||||
return html;
|
return html;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadTweet(DesktopNotification tweet){
|
protected override void LoadTweet(TweetNotification tweet){
|
||||||
timerProgress.Stop();
|
timerProgress.Stop();
|
||||||
totalTime = timeLeft = tweet.GetDisplayDuration(Config.NotificationDurationValue);
|
totalTime = timeLeft = tweet.GetDisplayDuration(Config.NotificationDurationValue);
|
||||||
progressBarTimer.Value = Config.NotificationTimerCountDown ? progressBarTimer.Maximum : progressBarTimer.Minimum;
|
progressBarTimer.Value = Config.NotificationTimerCountDown ? progressBarTimer.Maximum : progressBarTimer.Minimum;
|
||||||
@@ -248,7 +247,7 @@ namespace TweetDuck.Core.Notification{
|
|||||||
|
|
||||||
protected override void SetNotificationSize(int width, int height){
|
protected override void SetNotificationSize(int width, int height){
|
||||||
if (Config.DisplayNotificationTimer){
|
if (Config.DisplayNotificationTimer){
|
||||||
ClientSize = new Size(width, height + timerBarHeight);
|
ClientSize = new Size(width, height+timerBarHeight);
|
||||||
progressBarTimer.Visible = true;
|
progressBarTimer.Visible = true;
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
@@ -266,6 +265,7 @@ namespace TweetDuck.Core.Notification{
|
|||||||
}
|
}
|
||||||
|
|
||||||
MoveToVisibleLocation();
|
MoveToVisibleLocation();
|
||||||
|
StartMouseHook();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void OnNotificationReady(){
|
protected virtual void OnNotificationReady(){
|
||||||
|
@@ -1,10 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
|
using TweetDuck.Plugins;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using TweetDuck.Core.Utils;
|
using TweetDuck.Core.Utils;
|
||||||
using TweetLib.Core.Features.Notifications;
|
|
||||||
using TweetLib.Core.Features.Plugins;
|
|
||||||
|
|
||||||
namespace TweetDuck.Core.Notification{
|
namespace TweetDuck.Core.Notification{
|
||||||
sealed partial class FormNotificationTweet : FormNotificationMain{
|
sealed partial class FormNotificationTweet : FormNotificationMain{
|
||||||
@@ -26,7 +25,7 @@ namespace TweetDuck.Core.Notification{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly Queue<DesktopNotification> tweetQueue = new Queue<DesktopNotification>(4);
|
private readonly Queue<TweetNotification> tweetQueue = new Queue<TweetNotification>(4);
|
||||||
private bool needsTrim;
|
private bool needsTrim;
|
||||||
private bool hasTemporarilyMoved;
|
private bool hasTemporarilyMoved;
|
||||||
|
|
||||||
@@ -82,7 +81,7 @@ namespace TweetDuck.Core.Notification{
|
|||||||
|
|
||||||
// notification methods
|
// notification methods
|
||||||
|
|
||||||
public override void ShowNotification(DesktopNotification notification){
|
public override void ShowNotification(TweetNotification notification){
|
||||||
tweetQueue.Enqueue(notification);
|
tweetQueue.Enqueue(notification);
|
||||||
|
|
||||||
if (!IsPaused){
|
if (!IsPaused){
|
||||||
@@ -154,7 +153,7 @@ namespace TweetDuck.Core.Notification{
|
|||||||
base.UpdateTitle();
|
base.UpdateTitle();
|
||||||
|
|
||||||
if (tweetQueue.Count > 0){
|
if (tweetQueue.Count > 0){
|
||||||
Text = Text + " (" + tweetQueue.Count + " more left)";
|
Text = Text+" ("+tweetQueue.Count+" more left)";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -3,13 +3,12 @@ using System.Drawing;
|
|||||||
using System.Drawing.Imaging;
|
using System.Drawing.Imaging;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using CefSharp;
|
using CefSharp;
|
||||||
using TweetDuck.Core.Adapters;
|
|
||||||
using TweetDuck.Core.Controls;
|
using TweetDuck.Core.Controls;
|
||||||
using TweetDuck.Core.Other;
|
using TweetDuck.Core.Other;
|
||||||
using TweetDuck.Core.Utils;
|
using TweetDuck.Core.Utils;
|
||||||
using TweetLib.Core.Data;
|
using TweetDuck.Data;
|
||||||
using TweetLib.Core.Features.Notifications;
|
using TweetDuck.Plugins;
|
||||||
using TweetLib.Core.Features.Plugins;
|
using TweetDuck.Resources;
|
||||||
|
|
||||||
namespace TweetDuck.Core.Notification.Screenshot{
|
namespace TweetDuck.Core.Notification.Screenshot{
|
||||||
sealed class FormNotificationScreenshotable : FormNotificationBase{
|
sealed class FormNotificationScreenshotable : FormNotificationBase{
|
||||||
@@ -30,23 +29,24 @@ namespace TweetDuck.Core.Notification.Screenshot{
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
string script = Program.Resources.LoadSilent("screenshot.js");
|
string script = ScriptLoader.LoadResourceSilent("screenshot.js");
|
||||||
|
|
||||||
if (script == null){
|
if (script == null){
|
||||||
this.InvokeAsyncSafe(callback);
|
this.InvokeAsyncSafe(callback);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
using IFrame frame = args.Browser.MainFrame;
|
using(IFrame frame = args.Browser.MainFrame){
|
||||||
CefScriptExecutor.RunScript(frame, script.Replace("{width}", realWidth.ToString()).Replace("{frames}", TweetScreenshotManager.WaitFrames.ToString()), "gen:screenshot");
|
ScriptLoader.ExecuteScript(frame, script.Replace("{width}", realWidth.ToString()).Replace("{frames}", TweetScreenshotManager.WaitFrames.ToString()), "gen:screenshot");
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
SetNotificationSize(realWidth, 1024);
|
SetNotificationSize(realWidth, 1024);
|
||||||
LoadTweet(new DesktopNotification(string.Empty, string.Empty, string.Empty, html, 0, string.Empty, string.Empty));
|
LoadTweet(new TweetNotification(string.Empty, string.Empty, string.Empty, html, 0, string.Empty, string.Empty));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string GetTweetHTML(DesktopNotification tweet){
|
protected override string GetTweetHTML(TweetNotification tweet){
|
||||||
string html = tweet.GenerateHtml("td-screenshot", HeadLayout, Config.CustomNotificationCSS);
|
string html = tweet.GenerateHtml("td-screenshot", this);
|
||||||
|
|
||||||
foreach(InjectedHTML injection in plugins.NotificationInjections){
|
foreach(InjectedHTML injection in plugins.NotificationInjections){
|
||||||
html = injection.InjectInto(html);
|
html = injection.InjectInto(html);
|
||||||
|
@@ -1,10 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using TweetDuck.Core.Controls;
|
using TweetDuck.Core.Controls;
|
||||||
|
|
||||||
namespace TweetDuck.Core.Notification.Screenshot{
|
namespace TweetDuck.Core.Notification.Screenshot{
|
||||||
[SuppressMessage("ReSharper", "UnusedMember.Global")]
|
|
||||||
sealed class ScreenshotBridge{
|
sealed class ScreenshotBridge{
|
||||||
private readonly Control owner;
|
private readonly Control owner;
|
||||||
|
|
||||||
|
@@ -9,7 +9,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using TweetDuck.Core.Controls;
|
using TweetDuck.Core.Controls;
|
||||||
using TweetLib.Core.Features.Plugins;
|
using TweetDuck.Plugins;
|
||||||
|
|
||||||
#if GEN_SCREENSHOT_FRAMES
|
#if GEN_SCREENSHOT_FRAMES
|
||||||
using System.Drawing.Imaging;
|
using System.Drawing.Imaging;
|
||||||
@@ -134,9 +134,9 @@ namespace TweetDuck.Core.Notification.Screenshot{
|
|||||||
private void debugger_Tick(object sender, EventArgs e){
|
private void debugger_Tick(object sender, EventArgs e){
|
||||||
if (frameCounter < 63 && screenshot.TakeScreenshot(true)){
|
if (frameCounter < 63 && screenshot.TakeScreenshot(true)){
|
||||||
try{
|
try{
|
||||||
Clipboard.GetImage()?.Save(Path.Combine(DebugScreenshotPath, "frame_" + (++frameCounter) + ".png"), ImageFormat.Png);
|
Clipboard.GetImage()?.Save(Path.Combine(DebugScreenshotPath, "frame_"+(++frameCounter)+".png"), ImageFormat.Png);
|
||||||
}catch{
|
}catch{
|
||||||
System.Diagnostics.Debug.WriteLine("Failed generating frame " + frameCounter);
|
System.Diagnostics.Debug.WriteLine("Failed generating frame "+frameCounter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
|
@@ -11,16 +11,18 @@ namespace TweetDuck.Core.Notification{
|
|||||||
public const string SupportedFormats = "*.wav;*.ogg;*.mp3;*.flac;*.opus;*.weba;*.webm";
|
public const string SupportedFormats = "*.wav;*.ogg;*.mp3;*.flac;*.opus;*.weba;*.webm";
|
||||||
|
|
||||||
public static IResourceHandler CreateFileHandler(string path){
|
public static IResourceHandler CreateFileHandler(string path){
|
||||||
string mimeType = Path.GetExtension(path) switch{
|
string mimeType;
|
||||||
".weba" => "audio/webm",
|
|
||||||
".webm" => "audio/webm",
|
switch(Path.GetExtension(path)){
|
||||||
".wav" => "audio/wav",
|
case ".weba":
|
||||||
".ogg" => "audio/ogg",
|
case ".webm": mimeType = "audio/webm"; break;
|
||||||
".mp3" => "audio/mp3",
|
case ".wav": mimeType = "audio/wav"; break;
|
||||||
".flac" => "audio/flac",
|
case ".ogg": mimeType = "audio/ogg"; break;
|
||||||
".opus" => "audio/ogg; codecs=opus",
|
case ".mp3": mimeType = "audio/mp3"; break;
|
||||||
_ => null
|
case ".flac": mimeType = "audio/flac"; break;
|
||||||
};
|
case ".opus": mimeType = "audio/ogg; codecs=opus"; break;
|
||||||
|
default: mimeType = null; break;
|
||||||
|
}
|
||||||
|
|
||||||
try{
|
try{
|
||||||
return ResourceHandler.FromFilePath(path, mimeType);
|
return ResourceHandler.FromFilePath(path, mimeType);
|
||||||
@@ -28,12 +30,12 @@ namespace TweetDuck.Core.Notification{
|
|||||||
FormBrowser browser = FormManager.TryFind<FormBrowser>();
|
FormBrowser browser = FormManager.TryFind<FormBrowser>();
|
||||||
|
|
||||||
browser?.InvokeAsyncSafe(() => {
|
browser?.InvokeAsyncSafe(() => {
|
||||||
using(FormMessage form = new FormMessage("Sound Notification Error", "Could not find custom notification sound file:\n" + path, MessageBoxIcon.Error)){
|
using(FormMessage form = new FormMessage("Sound Notification Error", "Could not find custom notification sound file:\n"+path, MessageBoxIcon.Error)){
|
||||||
form.AddButton(FormMessage.Ignore, ControlType.Cancel | ControlType.Focused);
|
form.AddButton(FormMessage.Ignore, ControlType.Cancel | ControlType.Focused);
|
||||||
|
|
||||||
Button btnViewOptions = form.AddButton("View Options");
|
Button btnViewOptions = form.AddButton("View Options");
|
||||||
btnViewOptions.Width += 16;
|
btnViewOptions.Width += 16;
|
||||||
btnViewOptions.Location = new Point(btnViewOptions.Location.X - 16, btnViewOptions.Location.Y);
|
btnViewOptions.Location = new Point(btnViewOptions.Location.X-16, btnViewOptions.Location.Y);
|
||||||
|
|
||||||
if (form.ShowDialog() == DialogResult.OK && form.ClickedButton == btnViewOptions){
|
if (form.ShowDialog() == DialogResult.OK && form.ClickedButton == btnViewOptions){
|
||||||
browser.OpenSettings(typeof(TabSettingsSounds));
|
browser.OpenSettings(typeof(TabSettingsSounds));
|
||||||
|
@@ -1,10 +1,16 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using CefSharp;
|
||||||
|
using TweetDuck.Core.Bridge;
|
||||||
|
using TweetDuck.Data;
|
||||||
|
using TweetDuck.Resources;
|
||||||
|
|
||||||
namespace TweetLib.Core.Features.Notifications{
|
namespace TweetDuck.Core.Notification{
|
||||||
public sealed class DesktopNotification{
|
sealed class TweetNotification{
|
||||||
private const string DefaultHeadLayout = @"<html class=""scroll-v os-windows dark txt-size--14"" lang=""en-US"" id=""tduck"" data-td-font=""medium"" data-td-theme=""dark""><head><meta charset=""utf-8""><link href=""https://ton.twimg.com/tweetdeck-web/web/dist/bundle.4b1f87e09d.css"" rel=""stylesheet""><style type='text/css'>body { background: rgb(34, 36, 38) !important }</style>";
|
private const string DefaultHeadLayout = @"<html class=""scroll-v os-windows dark txt-size--14"" lang=""en-US"" id=""tduck"" data-td-font=""medium"" data-td-theme=""dark""><head><meta charset=""utf-8""><link href=""https://ton.twimg.com/tweetdeck-web/web/dist/bundle.4b1f87e09d.css"" rel=""stylesheet""><style type='text/css'>body { background: rgb(34, 36, 38) !important }</style>";
|
||||||
|
public static readonly ResourceLink AppLogo = new ResourceLink("https://ton.twimg.com/tduck/avatar", ResourceHandler.FromByteArray(Properties.Resources.avatar, "image/png"));
|
||||||
|
|
||||||
public enum Position{
|
public enum Position{
|
||||||
TopLeft, TopRight, BottomLeft, BottomRight, Custom
|
TopLeft, TopRight, BottomLeft, BottomRight, Custom
|
||||||
}
|
}
|
||||||
@@ -23,7 +29,7 @@ namespace TweetLib.Core.Features.Notifications{
|
|||||||
private readonly string html;
|
private readonly string html;
|
||||||
private readonly int characters;
|
private readonly int characters;
|
||||||
|
|
||||||
public DesktopNotification(string columnId, string chirpId, string title, string html, int characters, string tweetUrl, string quoteUrl){
|
public TweetNotification(string columnId, string chirpId, string title, string html, int characters, string tweetUrl, string quoteUrl){
|
||||||
this.ColumnId = columnId;
|
this.ColumnId = columnId;
|
||||||
this.ChirpId = chirpId;
|
this.ChirpId = chirpId;
|
||||||
|
|
||||||
@@ -36,22 +42,21 @@ namespace TweetLib.Core.Features.Notifications{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public int GetDisplayDuration(int value){
|
public int GetDisplayDuration(int value){
|
||||||
return 2000 + Math.Max(1000, value * characters);
|
return 2000+Math.Max(1000, value*characters);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GenerateHtml(string bodyClasses, string? headLayout, string? customStyles){ // TODO
|
public string GenerateHtml(string bodyClasses, Control sync){
|
||||||
headLayout ??= DefaultHeadLayout;
|
string headLayout = TweetDeckBridge.NotificationHeadLayout ?? DefaultHeadLayout;
|
||||||
customStyles ??= string.Empty;
|
string mainCSS = ScriptLoader.LoadResource("styles/notification.css", sync) ?? string.Empty;
|
||||||
|
string customCSS = Program.Config.User.CustomNotificationCSS ?? string.Empty;
|
||||||
string mainCSS = App.ResourceHandler.Load("styles/notification.css") ?? string.Empty;
|
|
||||||
|
|
||||||
StringBuilder build = new StringBuilder(320 + headLayout.Length + mainCSS.Length + customStyles.Length + html.Length);
|
StringBuilder build = new StringBuilder(320 + headLayout.Length + mainCSS.Length + customCSS.Length + html.Length);
|
||||||
build.Append("<!DOCTYPE html>");
|
build.Append("<!DOCTYPE html>");
|
||||||
build.Append(headLayout);
|
build.Append(headLayout);
|
||||||
build.Append("<style type='text/css'>").Append(mainCSS).Append("</style>");
|
build.Append("<style type='text/css'>").Append(mainCSS).Append("</style>");
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(customStyles)){
|
if (!string.IsNullOrWhiteSpace(customCSS)){
|
||||||
build.Append("<style type='text/css'>").Append(customStyles).Append("</style>");
|
build.Append("<style type='text/css'>").Append(customCSS).Append("</style>");
|
||||||
}
|
}
|
||||||
|
|
||||||
build.Append("</head><body class='scroll-styled-v");
|
build.Append("</head><body class='scroll-styled-v");
|
@@ -2,8 +2,7 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using TweetLib.Core.Serialization;
|
using TweetDuck.Data.Serialization;
|
||||||
using TweetLib.Core.Serialization.Converters;
|
|
||||||
|
|
||||||
namespace TweetDuck.Core.Other.Analytics{
|
namespace TweetDuck.Core.Other.Analytics{
|
||||||
[SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Local")]
|
[SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Local")]
|
||||||
|
@@ -8,9 +8,7 @@ using System.Threading.Tasks;
|
|||||||
using System.Timers;
|
using System.Timers;
|
||||||
using TweetDuck.Core.Controls;
|
using TweetDuck.Core.Controls;
|
||||||
using TweetDuck.Core.Utils;
|
using TweetDuck.Core.Utils;
|
||||||
using TweetLib.Core;
|
using TweetDuck.Plugins;
|
||||||
using TweetLib.Core.Features.Plugins;
|
|
||||||
using TweetLib.Core.Utils;
|
|
||||||
|
|
||||||
namespace TweetDuck.Core.Other.Analytics{
|
namespace TweetDuck.Core.Other.Analytics{
|
||||||
sealed class AnalyticsManager : IDisposable{
|
sealed class AnalyticsManager : IDisposable{
|
||||||
@@ -22,7 +20,7 @@ namespace TweetDuck.Core.Other.Analytics{
|
|||||||
#else
|
#else
|
||||||
"https://tweetduck.chylex.com/breadcrumb/report"
|
"https://tweetduck.chylex.com/breadcrumb/report"
|
||||||
#endif
|
#endif
|
||||||
);
|
);
|
||||||
|
|
||||||
public AnalyticsFile File { get; }
|
public AnalyticsFile File { get; }
|
||||||
|
|
||||||
@@ -82,7 +80,7 @@ namespace TweetDuck.Core.Other.Analytics{
|
|||||||
private void SetLastDataCollectionTime(DateTime dt, string message = null){
|
private void SetLastDataCollectionTime(DateTime dt, string message = null){
|
||||||
File.LastDataCollection = new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, 0, dt.Kind);
|
File.LastDataCollection = new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, 0, dt.Kind);
|
||||||
File.LastCollectionVersion = Program.VersionTag;
|
File.LastCollectionVersion = Program.VersionTag;
|
||||||
File.LastCollectionMessage = message ?? dt.ToString("g", Lib.Culture);
|
File.LastCollectionMessage = message ?? dt.ToString("g", Program.Culture);
|
||||||
|
|
||||||
File.Save();
|
File.Save();
|
||||||
RestartTimer();
|
RestartTimer();
|
||||||
@@ -90,9 +88,9 @@ namespace TweetDuck.Core.Other.Analytics{
|
|||||||
|
|
||||||
private void RestartTimer(){
|
private void RestartTimer(){
|
||||||
TimeSpan diff = DateTime.Now.Subtract(File.LastDataCollection);
|
TimeSpan diff = DateTime.Now.Subtract(File.LastDataCollection);
|
||||||
int minutesTillNext = (int)(CollectionInterval.TotalMinutes - Math.Floor(diff.TotalMinutes));
|
int minutesTillNext = (int)(CollectionInterval.TotalMinutes-Math.Floor(diff.TotalMinutes));
|
||||||
|
|
||||||
currentTimer.Interval = Math.Max(minutesTillNext, 2) * 60000;
|
currentTimer.Interval = Math.Max(minutesTillNext, 2)*60000;
|
||||||
currentTimer.Start();
|
currentTimer.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,7 +117,7 @@ namespace TweetDuck.Core.Other.Analytics{
|
|||||||
System.Diagnostics.Debugger.Break();
|
System.Diagnostics.Debugger.Break();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
WebUtils.NewClient(BrowserUtils.UserAgentVanilla).UploadValues(CollectionUrl, "POST", report.ToNameValueCollection());
|
BrowserUtils.CreateWebClient().UploadValues(CollectionUrl, "POST", report.ToNameValueCollection());
|
||||||
}).ContinueWith(task => browser.InvokeAsyncSafe(() => {
|
}).ContinueWith(task => browser.InvokeAsyncSafe(() => {
|
||||||
if (task.Status == TaskStatus.RanToCompletion){
|
if (task.Status == TaskStatus.RanToCompletion){
|
||||||
SetLastDataCollectionTime(DateTime.Now);
|
SetLastDataCollectionTime(DateTime.Now);
|
||||||
@@ -139,7 +137,7 @@ namespace TweetDuck.Core.Other.Analytics{
|
|||||||
|
|
||||||
case WebExceptionStatus.ProtocolError:
|
case WebExceptionStatus.ProtocolError:
|
||||||
HttpWebResponse response = e.Response as HttpWebResponse;
|
HttpWebResponse response = e.Response as HttpWebResponse;
|
||||||
message = "HTTP Error " + (response != null ? $"{(int)response.StatusCode} ({response.StatusDescription})" : "(unknown code)");
|
message = "HTTP Error "+(response != null ? $"{(int)response.StatusCode} ({response.StatusDescription})" : "(unknown code)");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,7 +150,7 @@ namespace TweetDuck.Core.Other.Analytics{
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
ScheduleReportIn(TimeSpan.FromHours(4), message ?? "Error: " + (task.Exception.InnerException?.Message ?? task.Exception.Message));
|
ScheduleReportIn(TimeSpan.FromHours(4), message ?? "Error: "+(task.Exception.InnerException?.Message ?? task.Exception.Message));
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
@@ -47,7 +47,7 @@ namespace TweetDuck.Core.Other.Analytics{
|
|||||||
build.AppendLine();
|
build.AppendLine();
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
build.AppendLine(entry.Key + ": " + entry.Value);
|
build.AppendLine(entry.Key+": "+entry.Value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -8,12 +8,10 @@ using TweetDuck.Configuration;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Management;
|
using System.Management;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
using TweetDuck.Core.Notification;
|
||||||
using TweetDuck.Core.Utils;
|
using TweetDuck.Core.Utils;
|
||||||
using TweetLib.Core;
|
using TweetDuck.Plugins;
|
||||||
using TweetLib.Core.Features.Notifications;
|
using TweetDuck.Plugins.Enums;
|
||||||
using TweetLib.Core.Features.Plugins;
|
|
||||||
using TweetLib.Core.Features.Plugins.Enums;
|
|
||||||
using TweetLib.Core.Utils;
|
|
||||||
|
|
||||||
namespace TweetDuck.Core.Other.Analytics{
|
namespace TweetDuck.Core.Other.Analytics{
|
||||||
static class AnalyticsReportGenerator{
|
static class AnalyticsReportGenerator{
|
||||||
@@ -29,7 +27,7 @@ namespace TweetDuck.Core.Other.Analytics{
|
|||||||
{ "System Edition" , SystemEdition },
|
{ "System Edition" , SystemEdition },
|
||||||
{ "System Environment" , Environment.Is64BitOperatingSystem ? "64-bit" : "32-bit" },
|
{ "System Environment" , Environment.Is64BitOperatingSystem ? "64-bit" : "32-bit" },
|
||||||
{ "System Build" , SystemBuild },
|
{ "System Build" , SystemBuild },
|
||||||
{ "System Locale" , Lib.Culture.Name.ToLower() },
|
{ "System Locale" , Program.Culture.Name.ToLower() },
|
||||||
0,
|
0,
|
||||||
{ "RAM" , Exact(RamSize) },
|
{ "RAM" , Exact(RamSize) },
|
||||||
{ "GPU" , GpuVendor },
|
{ "GPU" , GpuVendor },
|
||||||
@@ -81,7 +79,7 @@ namespace TweetDuck.Core.Other.Analytics{
|
|||||||
{ "Custom Notification CSS" , RoundUp((UserConfig.CustomNotificationCSS ?? string.Empty).Length, 50) },
|
{ "Custom Notification CSS" , RoundUp((UserConfig.CustomNotificationCSS ?? string.Empty).Length, 50) },
|
||||||
0,
|
0,
|
||||||
{ "Plugins All" , List(plugins.Plugins.Select(Plugin)) },
|
{ "Plugins All" , List(plugins.Plugins.Select(Plugin)) },
|
||||||
{ "Plugins Enabled" , List(plugins.Plugins.Where(plugins.Config.IsEnabled).Select(Plugin)) },
|
{ "Plugins Enabled" , List(plugins.Plugins.Where(plugin => plugins.Config.IsEnabled(plugin)).Select(Plugin)) },
|
||||||
0,
|
0,
|
||||||
{ "Theme" , Dict(editLayoutDesign, "_theme", "light/def") },
|
{ "Theme" , Dict(editLayoutDesign, "_theme", "light/def") },
|
||||||
{ "Column Width" , Dict(editLayoutDesign, "columnWidth", "310px/def") },
|
{ "Column Width" , Dict(editLayoutDesign, "columnWidth", "310px/def") },
|
||||||
@@ -126,9 +124,9 @@ namespace TweetDuck.Core.Other.Analytics{
|
|||||||
|
|
||||||
private static string Bool(bool value) => value ? "on" : "off";
|
private static string Bool(bool value) => value ? "on" : "off";
|
||||||
private static string Exact(int value) => value.ToString();
|
private static string Exact(int value) => value.ToString();
|
||||||
private static string RoundUp(int value, int multiple) => (multiple * (int)Math.Ceiling((double)value / multiple)).ToString();
|
private static string RoundUp(int value, int multiple) => (multiple*(int)Math.Ceiling((double)value/multiple)).ToString();
|
||||||
private static string LogRound(int value, int logBase) => (value <= 0 ? 0 : (int)Math.Pow(logBase, Math.Floor(Math.Log(value, logBase)))).ToString();
|
private static string LogRound(int value, int logBase) => (value <= 0 ? 0 : (int)Math.Pow(logBase, Math.Floor(Math.Log(value, logBase)))).ToString();
|
||||||
private static string Plugin(Plugin plugin) => plugin.Group.GetIdentifierPrefixShort() + plugin.Identifier.Substring(plugin.Group.GetIdentifierPrefix().Length);
|
private static string Plugin(Plugin plugin) => plugin.Group.GetIdentifierPrefixShort()+plugin.Identifier.Substring(plugin.Group.GetIdentifierPrefix().Length);
|
||||||
private static string Dict(Dictionary<string, string> dict, string key, string def = "(unknown)") => dict.TryGetValue(key, out string value) ? value : def;
|
private static string Dict(Dictionary<string, string> dict, string key, string def = "(unknown)") => dict.TryGetValue(key, out string value) ? value : def;
|
||||||
private static string List(IEnumerable<string> list) => string.Join("|", list.DefaultIfEmpty("(none)"));
|
private static string List(IEnumerable<string> list) => string.Join("|", list.DefaultIfEmpty("(none)"));
|
||||||
|
|
||||||
@@ -143,19 +141,19 @@ namespace TweetDuck.Core.Other.Analytics{
|
|||||||
string osName, osEdition, osBuild;
|
string osName, osEdition, osBuild;
|
||||||
|
|
||||||
try{
|
try{
|
||||||
using RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion", false);
|
using(RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion", false)){
|
||||||
|
// ReSharper disable once PossibleNullReferenceException
|
||||||
// ReSharper disable once PossibleNullReferenceException
|
osName = key.GetValue("ProductName") as string;
|
||||||
osName = key.GetValue("ProductName") as string;
|
osBuild = key.GetValue("CurrentBuild") as string;
|
||||||
osBuild = key.GetValue("CurrentBuild") as string;
|
osEdition = null;
|
||||||
osEdition = null;
|
|
||||||
|
|
||||||
if (osName != null){
|
if (osName != null){
|
||||||
Match match = Regex.Match(osName, @"^(.*?\d+(?:\.\d+)?) (.*)$");
|
Match match = Regex.Match(osName, @"^(.*?\d+(?:\.\d+)?) (.*)$");
|
||||||
|
|
||||||
if (match.Success){
|
if (match.Success){
|
||||||
osName = match.Groups[1].Value;
|
osName = match.Groups[1].Value;
|
||||||
osEdition = match.Groups[2].Value;
|
osEdition = match.Groups[2].Value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}catch{
|
}catch{
|
||||||
@@ -167,10 +165,10 @@ namespace TweetDuck.Core.Other.Analytics{
|
|||||||
SystemBuild = osBuild ?? "(unknown)";
|
SystemBuild = osBuild ?? "(unknown)";
|
||||||
|
|
||||||
try{
|
try{
|
||||||
using ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT Capacity FROM Win32_PhysicalMemory");
|
using(ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT Capacity FROM Win32_PhysicalMemory")){
|
||||||
|
foreach(ManagementBaseObject obj in searcher.Get()){
|
||||||
foreach(ManagementBaseObject obj in searcher.Get()){
|
RamSize += (int)((ulong)obj["Capacity"]/(1024L*1024L));
|
||||||
RamSize += (int)((ulong)obj["Capacity"] / (1024L * 1024L));
|
}
|
||||||
}
|
}
|
||||||
}catch{
|
}catch{
|
||||||
RamSize = 0;
|
RamSize = 0;
|
||||||
@@ -179,13 +177,13 @@ namespace TweetDuck.Core.Other.Analytics{
|
|||||||
string gpu = null;
|
string gpu = null;
|
||||||
|
|
||||||
try{
|
try{
|
||||||
using ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT Caption FROM Win32_VideoController");
|
using(ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT Caption FROM Win32_VideoController")){
|
||||||
|
foreach(ManagementBaseObject obj in searcher.Get()){
|
||||||
|
string vendor = obj["Caption"] as string;
|
||||||
|
|
||||||
foreach(ManagementBaseObject obj in searcher.Get()){
|
if (!string.IsNullOrEmpty(vendor)){
|
||||||
string vendor = obj["Caption"] as string;
|
gpu = vendor;
|
||||||
|
}
|
||||||
if (!string.IsNullOrEmpty(vendor)){
|
|
||||||
gpu = vendor;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}catch{
|
}catch{
|
||||||
@@ -206,30 +204,36 @@ namespace TweetDuck.Core.Other.Analytics{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static string TrayMode{
|
private static string TrayMode{
|
||||||
get => UserConfig.TrayBehavior switch{
|
get{
|
||||||
TrayIcon.Behavior.DisplayOnly => "icon",
|
switch(UserConfig.TrayBehavior){
|
||||||
TrayIcon.Behavior.MinimizeToTray => "minimize",
|
case TrayIcon.Behavior.DisplayOnly: return "icon";
|
||||||
TrayIcon.Behavior.CloseToTray => "close",
|
case TrayIcon.Behavior.MinimizeToTray: return "minimize";
|
||||||
TrayIcon.Behavior.Combined => "combined",
|
case TrayIcon.Behavior.CloseToTray: return "close";
|
||||||
_ => "off"
|
case TrayIcon.Behavior.Combined: return "combined";
|
||||||
};
|
default: return "off";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string NotificationPosition{
|
private static string NotificationPosition{
|
||||||
get => UserConfig.NotificationPosition switch{
|
get{
|
||||||
DesktopNotification.Position.TopLeft => "top left",
|
switch(UserConfig.NotificationPosition){
|
||||||
DesktopNotification.Position.TopRight => "top right",
|
case TweetNotification.Position.TopLeft: return "top left";
|
||||||
DesktopNotification.Position.BottomLeft => "bottom left",
|
case TweetNotification.Position.TopRight: return "top right";
|
||||||
DesktopNotification.Position.BottomRight => "bottom right",
|
case TweetNotification.Position.BottomLeft: return "bottom left";
|
||||||
_ => "custom"
|
case TweetNotification.Position.BottomRight: return "bottom right";
|
||||||
};
|
default: return "custom";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string NotificationSize{
|
private static string NotificationSize{
|
||||||
get => UserConfig.NotificationSize switch{
|
get{
|
||||||
DesktopNotification.Size.Auto => "auto",
|
switch(UserConfig.NotificationSize){
|
||||||
_ => RoundUp(UserConfig.CustomNotificationSize.Width, 20) + "x" + RoundUp(UserConfig.CustomNotificationSize.Height, 20)
|
case TweetNotification.Size.Auto: return "auto";
|
||||||
};
|
default: return RoundUp(UserConfig.CustomNotificationSize.Width, 20)+"x"+RoundUp(UserConfig.CustomNotificationSize.Height, 20);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string NotificationTimer{
|
private static string NotificationTimer{
|
||||||
@@ -285,7 +289,7 @@ namespace TweetDuck.Core.Other.Analytics{
|
|||||||
}
|
}
|
||||||
|
|
||||||
string accType = matchType.Groups[1].Value == "#" ? matchType.Groups[2].Value : "account";
|
string accType = matchType.Groups[1].Value == "#" ? matchType.Groups[2].Value : "account";
|
||||||
return matchAdvanced.Success && !matchAdvanced.Value.Contains("false") ? "advanced/" + accType : accType;
|
return matchAdvanced.Success && !matchAdvanced.Value.Contains("false") ? "advanced/"+accType : accType;
|
||||||
}catch{
|
}catch{
|
||||||
return "(unknown)";
|
return "(unknown)";
|
||||||
}
|
}
|
||||||
@@ -306,7 +310,7 @@ namespace TweetDuck.Core.Other.Analytics{
|
|||||||
}
|
}
|
||||||
|
|
||||||
return new ExternalInfo{
|
return new ExternalInfo{
|
||||||
Resolution = screen.Bounds.Width + "x" + screen.Bounds.Height,
|
Resolution = screen.Bounds.Width+"x"+screen.Bounds.Height,
|
||||||
DPI = dpi
|
DPI = dpi
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@@ -12,7 +12,7 @@ namespace TweetDuck.Core.Other{
|
|||||||
public FormAbout(){
|
public FormAbout(){
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
Text = "About " + Program.BrandName + " " + Program.VersionTag;
|
Text = "About "+Program.BrandName+" "+Program.VersionTag;
|
||||||
|
|
||||||
labelDescription.Text = "TweetDuck was created by chylex as a replacement to the discontinued official TweetDeck client for Windows.\n\nThe program is available for free under the open source MIT license.";
|
labelDescription.Text = "TweetDuck was created by chylex as a replacement to the discontinued official TweetDeck client for Windows.\n\nThe program is available for free under the open source MIT license.";
|
||||||
|
|
||||||
|
@@ -7,8 +7,8 @@ using TweetDuck.Core.Handling;
|
|||||||
using TweetDuck.Core.Handling.General;
|
using TweetDuck.Core.Handling.General;
|
||||||
using TweetDuck.Core.Utils;
|
using TweetDuck.Core.Utils;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using TweetDuck.Core.Adapters;
|
|
||||||
using TweetDuck.Data;
|
using TweetDuck.Data;
|
||||||
|
using TweetDuck.Resources;
|
||||||
|
|
||||||
namespace TweetDuck.Core.Other{
|
namespace TweetDuck.Core.Other{
|
||||||
sealed partial class FormGuide : Form, FormManager.IAppDialog{
|
sealed partial class FormGuide : Form, FormManager.IAppDialog{
|
||||||
@@ -37,7 +37,7 @@ namespace TweetDuck.Core.Other{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void Show(string hash = null){
|
public static void Show(string hash = null){
|
||||||
string url = GuideUrl + (hash ?? string.Empty);
|
string url = GuideUrl+(hash ?? string.Empty);
|
||||||
FormGuide guide = FormManager.TryFind<FormGuide>();
|
FormGuide guide = FormManager.TryFind<FormGuide>();
|
||||||
|
|
||||||
if (guide == null){
|
if (guide == null){
|
||||||
@@ -60,8 +60,8 @@ namespace TweetDuck.Core.Other{
|
|||||||
private FormGuide(string url, FormBrowser owner){
|
private FormGuide(string url, FormBrowser owner){
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
Text = Program.BrandName + " Guide";
|
Text = Program.BrandName+" Guide";
|
||||||
Size = new Size(owner.Size.Width * 3 / 4, owner.Size.Height * 3 / 4);
|
Size = new Size(owner.Size.Width*3/4, owner.Size.Height*3/4);
|
||||||
VisibleChanged += (sender, args) => this.MoveToCenter(owner);
|
VisibleChanged += (sender, args) => this.MoveToCenter(owner);
|
||||||
|
|
||||||
ResourceHandlerFactory resourceHandlerFactory = new ResourceHandlerFactory();
|
ResourceHandlerFactory resourceHandlerFactory = new ResourceHandlerFactory();
|
||||||
@@ -116,7 +116,7 @@ namespace TweetDuck.Core.Other{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
|
private void browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
|
||||||
CefScriptExecutor.RunScript(e.Frame, "Array.prototype.forEach.call(document.getElementsByTagName('A'), ele => ele.addEventListener('click', e => { e.preventDefault(); window.open(ele.getAttribute('href')); }))", "gen:links");
|
ScriptLoader.ExecuteScript(e.Frame, "Array.prototype.forEach.call(document.getElementsByTagName('A'), ele => ele.addEventListener('click', e => { e.preventDefault(); window.open(ele.getAttribute('href')); }))", "gen:links");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -133,7 +133,7 @@ namespace TweetDuck.Core.Other{
|
|||||||
Font = SystemFonts.MessageBoxFont,
|
Font = SystemFonts.MessageBoxFont,
|
||||||
Location = new Point(0, 12),
|
Location = new Point(0, 12),
|
||||||
Size = new Size(BrowserUtils.Scale(88, dpiScale), BrowserUtils.Scale(26, dpiScale)),
|
Size = new Size(BrowserUtils.Scale(88, dpiScale), BrowserUtils.Scale(26, dpiScale)),
|
||||||
TabIndex = 256 - buttonCount,
|
TabIndex = 256-buttonCount,
|
||||||
Text = title,
|
Text = title,
|
||||||
UseVisualStyleBackColor = true
|
UseVisualStyleBackColor = true
|
||||||
};
|
};
|
||||||
@@ -171,17 +171,17 @@ namespace TweetDuck.Core.Other{
|
|||||||
|
|
||||||
control.Size = new Size(BrowserUtils.Scale(control.Width, dpiScale), BrowserUtils.Scale(control.Height, dpiScale));
|
control.Size = new Size(BrowserUtils.Scale(control.Width, dpiScale), BrowserUtils.Scale(control.Height, dpiScale));
|
||||||
|
|
||||||
minFormWidth += control.Width + control.Margin.Horizontal;
|
minFormWidth += control.Width+control.Margin.Horizontal;
|
||||||
ClientWidth = Math.Max(realFormWidth, minFormWidth);
|
ClientWidth = Math.Max(realFormWidth, minFormWidth);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RecalculateButtonLocation(){
|
private void RecalculateButtonLocation(){
|
||||||
int dist = ButtonDistance;
|
int dist = ButtonDistance;
|
||||||
int start = ClientWidth - dist;
|
int start = ClientWidth-dist;
|
||||||
|
|
||||||
for(int index = 0; index < buttonCount; index++){
|
for(int index = 0; index < buttonCount; index++){
|
||||||
Control control = panelActions.Controls[index];
|
Control control = panelActions.Controls[index];
|
||||||
control.Location = new Point(start - index * dist, control.Location.Y);
|
control.Location = new Point(start-index*dist, control.Location.Y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,17 +194,17 @@ namespace TweetDuck.Core.Other{
|
|||||||
int labelOffset = BrowserUtils.Scale(8, dpiScale);
|
int labelOffset = BrowserUtils.Scale(8, dpiScale);
|
||||||
|
|
||||||
if (isMultiline && !wasLabelMultiline){
|
if (isMultiline && !wasLabelMultiline){
|
||||||
labelMessage.Location = new Point(labelMessage.Location.X, labelMessage.Location.Y - labelOffset);
|
labelMessage.Location = new Point(labelMessage.Location.X, labelMessage.Location.Y-labelOffset);
|
||||||
prevLabelHeight += labelOffset;
|
prevLabelHeight += labelOffset;
|
||||||
}
|
}
|
||||||
else if (!isMultiline && wasLabelMultiline){
|
else if (!isMultiline && wasLabelMultiline){
|
||||||
labelMessage.Location = new Point(labelMessage.Location.X, labelMessage.Location.Y + labelOffset);
|
labelMessage.Location = new Point(labelMessage.Location.X, labelMessage.Location.Y+labelOffset);
|
||||||
prevLabelHeight -= labelOffset;
|
prevLabelHeight -= labelOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
realFormWidth = ClientWidth - (icon == null ? BrowserUtils.Scale(50, dpiScale) : 0) + labelMessage.Width - prevLabelWidth;
|
realFormWidth = ClientWidth-(icon == null ? BrowserUtils.Scale(50, dpiScale) : 0)+labelMessage.Width-prevLabelWidth;
|
||||||
ClientWidth = Math.Max(realFormWidth, minFormWidth);
|
ClientWidth = Math.Max(realFormWidth, minFormWidth);
|
||||||
Height += labelMessage.Height - prevLabelHeight;
|
Height += labelMessage.Height-prevLabelHeight;
|
||||||
|
|
||||||
prevLabelWidth = labelMessage.Width;
|
prevLabelWidth = labelMessage.Width;
|
||||||
prevLabelHeight = labelMessage.Height;
|
prevLabelHeight = labelMessage.Height;
|
||||||
@@ -213,7 +213,7 @@ namespace TweetDuck.Core.Other{
|
|||||||
|
|
||||||
protected override void OnPaint(PaintEventArgs e){
|
protected override void OnPaint(PaintEventArgs e){
|
||||||
if (icon != null){
|
if (icon != null){
|
||||||
e.Graphics.DrawIcon(icon, BrowserUtils.Scale(25, dpiScale), 1 + BrowserUtils.Scale(25, dpiScale));
|
e.Graphics.DrawIcon(icon, BrowserUtils.Scale(25, dpiScale), 1+BrowserUtils.Scale(25, dpiScale));
|
||||||
}
|
}
|
||||||
|
|
||||||
base.OnPaint(e);
|
base.OnPaint(e);
|
||||||
|
8
Core/Other/FormPlugins.Designer.cs
generated
8
Core/Other/FormPlugins.Designer.cs
generated
@@ -1,6 +1,4 @@
|
|||||||
using TweetDuck.Core.Controls;
|
namespace TweetDuck.Core.Other {
|
||||||
|
|
||||||
namespace TweetDuck.Core.Other {
|
|
||||||
partial class FormPlugins {
|
partial class FormPlugins {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Required designer variable.
|
/// Required designer variable.
|
||||||
@@ -29,7 +27,7 @@ namespace TweetDuck.Core.Other {
|
|||||||
this.btnClose = new System.Windows.Forms.Button();
|
this.btnClose = new System.Windows.Forms.Button();
|
||||||
this.btnReload = new System.Windows.Forms.Button();
|
this.btnReload = new System.Windows.Forms.Button();
|
||||||
this.btnOpenFolder = new System.Windows.Forms.Button();
|
this.btnOpenFolder = new System.Windows.Forms.Button();
|
||||||
this.flowLayoutPlugins = new FlowLayoutPanelNoHScroll();
|
this.flowLayoutPlugins = new TweetDuck.Plugins.Controls.PluginListFlowLayout();
|
||||||
this.timerLayout = new System.Windows.Forms.Timer(this.components);
|
this.timerLayout = new System.Windows.Forms.Timer(this.components);
|
||||||
this.SuspendLayout();
|
this.SuspendLayout();
|
||||||
//
|
//
|
||||||
@@ -119,7 +117,7 @@ namespace TweetDuck.Core.Other {
|
|||||||
private System.Windows.Forms.Button btnClose;
|
private System.Windows.Forms.Button btnClose;
|
||||||
private System.Windows.Forms.Button btnReload;
|
private System.Windows.Forms.Button btnReload;
|
||||||
private System.Windows.Forms.Button btnOpenFolder;
|
private System.Windows.Forms.Button btnOpenFolder;
|
||||||
private FlowLayoutPanelNoHScroll flowLayoutPlugins;
|
private Plugins.Controls.PluginListFlowLayout flowLayoutPlugins;
|
||||||
private System.Windows.Forms.Timer timerLayout;
|
private System.Windows.Forms.Timer timerLayout;
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,11 +1,11 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using TweetDuck.Configuration;
|
using TweetDuck.Configuration;
|
||||||
using TweetDuck.Plugins;
|
using TweetDuck.Plugins;
|
||||||
using TweetLib.Core;
|
using TweetDuck.Plugins.Controls;
|
||||||
using TweetLib.Core.Features.Plugins;
|
|
||||||
|
|
||||||
namespace TweetDuck.Core.Other{
|
namespace TweetDuck.Core.Other{
|
||||||
sealed partial class FormPlugins : Form, FormManager.IAppDialog{
|
sealed partial class FormPlugins : Form, FormManager.IAppDialog{
|
||||||
@@ -16,7 +16,7 @@ namespace TweetDuck.Core.Other{
|
|||||||
public FormPlugins(){
|
public FormPlugins(){
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
Text = Program.BrandName + " Plugins";
|
Text = Program.BrandName+" Plugins";
|
||||||
}
|
}
|
||||||
|
|
||||||
public FormPlugins(PluginManager pluginManager) : this(){
|
public FormPlugins(PluginManager pluginManager) : this(){
|
||||||
@@ -69,8 +69,8 @@ namespace TweetDuck.Core.Other{
|
|||||||
timerLayout.Stop();
|
timerLayout.Stop();
|
||||||
|
|
||||||
// stupid WinForms scrollbars and panels
|
// stupid WinForms scrollbars and panels
|
||||||
Padding = new Padding(Padding.Left, Padding.Top, Padding.Right + 1, Padding.Bottom + 1);
|
Padding = new Padding(Padding.Left, Padding.Top, Padding.Right+1, Padding.Bottom+1);
|
||||||
Padding = new Padding(Padding.Left, Padding.Top, Padding.Right - 1, Padding.Bottom - 1);
|
Padding = new Padding(Padding.Left, Padding.Top, Padding.Right-1, Padding.Bottom-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void flowLayoutPlugins_Resize(object sender, EventArgs e){
|
public void flowLayoutPlugins_Resize(object sender, EventArgs e){
|
||||||
@@ -80,22 +80,22 @@ namespace TweetDuck.Core.Other{
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool showScrollBar = lastPlugin.Location.Y + lastPlugin.Height + 1 >= flowLayoutPlugins.Height;
|
bool showScrollBar = lastPlugin.Location.Y+lastPlugin.Height+1 >= flowLayoutPlugins.Height;
|
||||||
int horizontalOffset = showScrollBar ? SystemInformation.VerticalScrollBarWidth : 0;
|
int horizontalOffset = showScrollBar ? SystemInformation.VerticalScrollBarWidth : 0;
|
||||||
|
|
||||||
flowLayoutPlugins.AutoScroll = showScrollBar;
|
flowLayoutPlugins.AutoScroll = showScrollBar;
|
||||||
flowLayoutPlugins.VerticalScroll.Visible = showScrollBar;
|
flowLayoutPlugins.VerticalScroll.Visible = showScrollBar;
|
||||||
|
|
||||||
foreach(Control control in flowLayoutPlugins.Controls){
|
foreach(Control control in flowLayoutPlugins.Controls){
|
||||||
control.Width = flowLayoutPlugins.Width - control.Margin.Horizontal - horizontalOffset;
|
control.Width = flowLayoutPlugins.Width-control.Margin.Horizontal-horizontalOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
flowLayoutPlugins.Controls[flowLayoutPlugins.Controls.Count - 1].Visible = !showScrollBar;
|
flowLayoutPlugins.Controls[flowLayoutPlugins.Controls.Count-1].Visible = !showScrollBar;
|
||||||
flowLayoutPlugins.Focus();
|
flowLayoutPlugins.Focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void btnOpenFolder_Click(object sender, EventArgs e){
|
private void btnOpenFolder_Click(object sender, EventArgs e){
|
||||||
App.SystemHandler.OpenFileExplorer(pluginManager.PathCustomPlugins);
|
using(Process.Start("explorer.exe", '"'+pluginManager.PathCustomPlugins+'"')){}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void btnReload_Click(object sender, EventArgs e){
|
private void btnReload_Click(object sender, EventArgs e){
|
||||||
|
@@ -9,8 +9,8 @@ using TweetDuck.Core.Other.Analytics;
|
|||||||
using TweetDuck.Core.Other.Settings;
|
using TweetDuck.Core.Other.Settings;
|
||||||
using TweetDuck.Core.Other.Settings.Dialogs;
|
using TweetDuck.Core.Other.Settings.Dialogs;
|
||||||
using TweetDuck.Core.Utils;
|
using TweetDuck.Core.Utils;
|
||||||
using TweetLib.Core.Features.Plugins;
|
using TweetDuck.Plugins;
|
||||||
using TweetLib.Core.Features.Updates;
|
using TweetDuck.Updates;
|
||||||
|
|
||||||
namespace TweetDuck.Core.Other{
|
namespace TweetDuck.Core.Other{
|
||||||
sealed partial class FormSettings : Form, FormManager.IAppDialog{
|
sealed partial class FormSettings : Form, FormManager.IAppDialog{
|
||||||
@@ -27,7 +27,7 @@ namespace TweetDuck.Core.Other{
|
|||||||
public FormSettings(FormBrowser browser, PluginManager plugins, UpdateHandler updates, AnalyticsManager analytics, Type startTab){
|
public FormSettings(FormBrowser browser, PluginManager plugins, UpdateHandler updates, AnalyticsManager analytics, Type startTab){
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
Text = Program.BrandName + " Options";
|
Text = Program.BrandName+" Options";
|
||||||
|
|
||||||
this.browser = browser;
|
this.browser = browser;
|
||||||
this.browser.PauseNotification();
|
this.browser.PauseNotification();
|
||||||
@@ -110,7 +110,7 @@ namespace TweetDuck.Core.Other{
|
|||||||
BackColor = SystemColors.Control,
|
BackColor = SystemColors.Control,
|
||||||
FlatStyle = FlatStyle.Flat,
|
FlatStyle = FlatStyle.Flat,
|
||||||
Font = SystemFonts.MessageBoxFont,
|
Font = SystemFonts.MessageBoxFont,
|
||||||
Location = new Point(0, (buttonHeight + 1) * (panelButtons.Controls.Count / 2)),
|
Location = new Point(0, (buttonHeight+1)*(panelButtons.Controls.Count/2)),
|
||||||
Margin = new Padding(0),
|
Margin = new Padding(0),
|
||||||
Size = new Size(panelButtons.Width, buttonHeight),
|
Size = new Size(panelButtons.Width, buttonHeight),
|
||||||
Text = title,
|
Text = title,
|
||||||
@@ -125,7 +125,7 @@ namespace TweetDuck.Core.Other{
|
|||||||
|
|
||||||
panelButtons.Controls.Add(new Panel{
|
panelButtons.Controls.Add(new Panel{
|
||||||
BackColor = Color.DimGray,
|
BackColor = Color.DimGray,
|
||||||
Location = new Point(0, panelButtons.Controls[panelButtons.Controls.Count - 1].Location.Y + buttonHeight),
|
Location = new Point(0, panelButtons.Controls[panelButtons.Controls.Count-1].Location.Y+buttonHeight),
|
||||||
Margin = new Padding(0),
|
Margin = new Padding(0),
|
||||||
Size = new Size(panelButtons.Width, 1)
|
Size = new Size(panelButtons.Width, 1)
|
||||||
});
|
});
|
||||||
@@ -157,8 +157,8 @@ namespace TweetDuck.Core.Other{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tab.Control.Height < panelContents.Height - 2){
|
if (tab.Control.Height < panelContents.Height-2){
|
||||||
tab.Control.Height = panelContents.Height - 2; // fixes off-by-pixel error on high DPI
|
tab.Control.Height = panelContents.Height-2; // fixes off-by-pixel error on high DPI
|
||||||
}
|
}
|
||||||
|
|
||||||
tab.Control.OnReady();
|
tab.Control.OnReady();
|
||||||
@@ -195,7 +195,7 @@ namespace TweetDuck.Core.Other{
|
|||||||
private sealed class SettingsTab{
|
private sealed class SettingsTab{
|
||||||
public Button Button { get; }
|
public Button Button { get; }
|
||||||
|
|
||||||
public BaseTabSettings Control => control ??= constructor();
|
public BaseTabSettings Control => control ?? (control = constructor());
|
||||||
public bool IsInitialized => control != null;
|
public bool IsInitialized => control != null;
|
||||||
|
|
||||||
private readonly Func<BaseTabSettings> constructor;
|
private readonly Func<BaseTabSettings> constructor;
|
||||||
|
@@ -9,7 +9,7 @@ namespace TweetDuck.Core.Other.Settings{
|
|||||||
|
|
||||||
public IEnumerable<Control> InteractiveControls{
|
public IEnumerable<Control> InteractiveControls{
|
||||||
get{
|
get{
|
||||||
static IEnumerable<Control> FindInteractiveControls(Control parent){
|
IEnumerable<Control> FindInteractiveControls(Control parent){
|
||||||
foreach(Control control in parent.Controls){
|
foreach(Control control in parent.Controls){
|
||||||
if (control is Panel subPanel){
|
if (control is Panel subPanel){
|
||||||
foreach(Control subControl in FindInteractiveControls(subPanel)){
|
foreach(Control subControl in FindInteractiveControls(subPanel)){
|
||||||
|
@@ -5,10 +5,12 @@ using TweetDuck.Core.Other.Analytics;
|
|||||||
|
|
||||||
namespace TweetDuck.Core.Other.Settings.Dialogs{
|
namespace TweetDuck.Core.Other.Settings.Dialogs{
|
||||||
sealed partial class DialogSettingsAnalytics : Form{
|
sealed partial class DialogSettingsAnalytics : Form{
|
||||||
|
public string CefArgs => textBoxReport.Text;
|
||||||
|
|
||||||
public DialogSettingsAnalytics(AnalyticsReport report){
|
public DialogSettingsAnalytics(AnalyticsReport report){
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
Text = Program.BrandName + " Options - Analytics Report";
|
Text = Program.BrandName+" Options - Analytics Report";
|
||||||
|
|
||||||
textBoxReport.EnableMultilineShortcuts();
|
textBoxReport.EnableMultilineShortcuts();
|
||||||
textBoxReport.Text = report.ToString().TrimEnd();
|
textBoxReport.Text = report.ToString().TrimEnd();
|
||||||
|
@@ -16,7 +16,7 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
|
|||||||
public DialogSettingsCSS(string browserCSS, string notificationCSS, Action<string> reinjectBrowserCSS, Action openDevTools){
|
public DialogSettingsCSS(string browserCSS, string notificationCSS, Action<string> reinjectBrowserCSS, Action openDevTools){
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
Text = Program.BrandName + " Options - CSS";
|
Text = Program.BrandName+" Options - CSS";
|
||||||
|
|
||||||
this.reinjectBrowserCSS = reinjectBrowserCSS;
|
this.reinjectBrowserCSS = reinjectBrowserCSS;
|
||||||
this.openDevTools = openDevTools;
|
this.openDevTools = openDevTools;
|
||||||
@@ -64,21 +64,21 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(deleteTo < text.Length - 1 && text[deleteTo] == '\r' && text[deleteTo + 1] == '\n')){
|
if (!(deleteTo < text.Length-1 && text[deleteTo] == '\r' && text[deleteTo+1] == '\n')){
|
||||||
++deleteTo;
|
++deleteTo;
|
||||||
}
|
}
|
||||||
|
|
||||||
tb.Select(deleteTo, tb.SelectionLength + tb.SelectionStart - deleteTo);
|
tb.Select(deleteTo, tb.SelectionLength+tb.SelectionStart-deleteTo);
|
||||||
tb.SelectedText = string.Empty;
|
tb.SelectedText = string.Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (e.KeyCode == Keys.Back && e.Modifiers == Keys.None){
|
else if (e.KeyCode == Keys.Back && e.Modifiers == Keys.None){
|
||||||
int deleteTo = tb.SelectionStart;
|
int deleteTo = tb.SelectionStart;
|
||||||
|
|
||||||
if (deleteTo > 1 && text[deleteTo - 1] == ' ' && text[deleteTo - 2] == ' '){
|
if (deleteTo > 1 && text[deleteTo-1] == ' ' && text[deleteTo-2] == ' '){
|
||||||
e.SuppressKeyPress = true;
|
e.SuppressKeyPress = true;
|
||||||
|
|
||||||
tb.Select(deleteTo - 2, 2);
|
tb.Select(deleteTo-2, 2);
|
||||||
tb.SelectedText = string.Empty;
|
tb.SelectedText = string.Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -89,28 +89,28 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
|
|||||||
if (insertAt == 0){
|
if (insertAt == 0){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else if (text[insertAt - 1] == '{'){
|
else if (text[insertAt-1] == '{'){
|
||||||
insertText = Environment.NewLine + " ";
|
insertText = Environment.NewLine+" ";
|
||||||
|
|
||||||
int nextBracket = insertAt < text.Length ? text.IndexOfAny(new char[]{ '{', '}' }, insertAt + 1) : -1;
|
int nextBracket = insertAt < text.Length ? text.IndexOfAny(new char[]{ '{', '}' }, insertAt+1) : -1;
|
||||||
|
|
||||||
if (nextBracket == -1 || text[nextBracket] == '{'){
|
if (nextBracket == -1 || text[nextBracket] == '{'){
|
||||||
string insertExtra = Environment.NewLine + "}";
|
string insertExtra = Environment.NewLine+"}";
|
||||||
insertText += insertExtra;
|
insertText += insertExtra;
|
||||||
cursorOffset -= insertExtra.Length;
|
cursorOffset -= insertExtra.Length;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
int lineStart = text.LastIndexOf('\n', tb.SelectionStart - 1);
|
int lineStart = text.LastIndexOf('\n', tb.SelectionStart-1);
|
||||||
|
|
||||||
Match match = Regex.Match(text.Substring(lineStart == -1 ? 0 : lineStart + 1), "^([ \t]+)");
|
Match match = Regex.Match(text.Substring(lineStart == -1 ? 0 : lineStart+1), "^([ \t]+)");
|
||||||
insertText = match.Success ? Environment.NewLine + match.Groups[1].Value : null;
|
insertText = match.Success ? Environment.NewLine+match.Groups[1].Value : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(insertText)){
|
if (!string.IsNullOrEmpty(insertText)){
|
||||||
e.SuppressKeyPress = true;
|
e.SuppressKeyPress = true;
|
||||||
tb.Text = text.Insert(insertAt, insertText);
|
tb.Text = text.Insert(insertAt, insertText);
|
||||||
tb.SelectionStart = insertAt + cursorOffset + insertText.Length;
|
tb.SelectionStart = insertAt+cursorOffset+insertText.Length;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,7 +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 TweetLib.Core.Collections;
|
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{
|
||||||
@@ -13,7 +13,7 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
|
|||||||
public DialogSettingsCefArgs(string args){
|
public DialogSettingsCefArgs(string args){
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
Text = Program.BrandName + " Options - CEF Arguments";
|
Text = Program.BrandName+" Options - CEF Arguments";
|
||||||
|
|
||||||
textBoxArgs.EnableMultilineShortcuts();
|
textBoxArgs.EnableMultilineShortcuts();
|
||||||
textBoxArgs.Text = initialArgs = args ?? "";
|
textBoxArgs.Text = initialArgs = args ?? "";
|
||||||
@@ -32,7 +32,7 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
|
|||||||
}
|
}
|
||||||
|
|
||||||
int count = CommandLineArgs.ReadCefArguments(CefArgs).Count;
|
int count = CommandLineArgs.ReadCefArguments(CefArgs).Count;
|
||||||
string prompt = count == 0 && !string.IsNullOrWhiteSpace(initialArgs) ? "All current arguments will be removed. Continue?" : count + (count == 1 ? " argument was" : " arguments were") + " detected. Continue?";
|
string prompt = count == 0 && !string.IsNullOrWhiteSpace(initialArgs) ? "All current arguments will be removed. Continue?" : count+(count == 1 ? " argument was" : " arguments were")+" detected. Continue?";
|
||||||
|
|
||||||
if (FormMessage.Question("Confirm CEF Arguments", prompt, FormMessage.OK, FormMessage.Cancel)){
|
if (FormMessage.Question("Confirm CEF Arguments", prompt, FormMessage.OK, FormMessage.Cancel)){
|
||||||
DialogResult = DialogResult.OK;
|
DialogResult = DialogResult.OK;
|
||||||
|
@@ -4,8 +4,8 @@ using System.IO;
|
|||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using TweetDuck.Configuration;
|
using TweetDuck.Configuration;
|
||||||
using TweetDuck.Core.Management;
|
using TweetDuck.Core.Management;
|
||||||
using TweetLib.Core.Features.Plugins;
|
using TweetDuck.Core.Utils;
|
||||||
using TweetLib.Core.Utils;
|
using TweetDuck.Plugins;
|
||||||
|
|
||||||
namespace TweetDuck.Core.Other.Settings.Dialogs{
|
namespace TweetDuck.Core.Other.Settings.Dialogs{
|
||||||
sealed partial class DialogSettingsManage : Form{
|
sealed partial class DialogSettingsManage : Form{
|
||||||
@@ -124,7 +124,7 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
|
|||||||
// Continue...
|
// Continue...
|
||||||
panelDecision.Visible = false;
|
panelDecision.Visible = false;
|
||||||
panelSelection.Visible = true;
|
panelSelection.Visible = true;
|
||||||
Height += panelSelection.Height - panelDecision.Height;
|
Height += panelSelection.Height-panelDecision.Height;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case State.Reset:
|
case State.Reset:
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using TweetDuck.Configuration;
|
using TweetDuck.Configuration;
|
||||||
using TweetLib.Core.Collections;
|
using TweetDuck.Data;
|
||||||
|
|
||||||
namespace TweetDuck.Core.Other.Settings.Dialogs{
|
namespace TweetDuck.Core.Other.Settings.Dialogs{
|
||||||
sealed partial class DialogSettingsRestart : Form{
|
sealed partial class DialogSettingsRestart : Form{
|
||||||
@@ -18,13 +18,13 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
|
|||||||
tbDataFolder.Enabled = false;
|
tbDataFolder.Enabled = false;
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
tbDataFolder.Text = currentArgs.GetValue(Arguments.ArgDataFolder) ?? string.Empty;
|
tbDataFolder.Text = currentArgs.GetValue(Arguments.ArgDataFolder, string.Empty);
|
||||||
tbDataFolder.TextChanged += control_Change;
|
tbDataFolder.TextChanged += control_Change;
|
||||||
}
|
}
|
||||||
|
|
||||||
control_Change(this, EventArgs.Empty);
|
control_Change(this, EventArgs.Empty);
|
||||||
|
|
||||||
Text = Program.BrandName + " Arguments";
|
Text = Program.BrandName+" Arguments";
|
||||||
}
|
}
|
||||||
|
|
||||||
private void control_Change(object sender, EventArgs e){
|
private void control_Change(object sender, EventArgs e){
|
||||||
|
@@ -8,7 +8,7 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
|
|||||||
public DialogSettingsSearchEngine(){
|
public DialogSettingsSearchEngine(){
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
Text = Program.BrandName + " Options - Custom Search Engine";
|
Text = Program.BrandName+" Options - Custom Search Engine";
|
||||||
|
|
||||||
textBoxUrl.Text = Program.Config.User.SearchEngineUrl ?? "";
|
textBoxUrl.Text = Program.Config.User.SearchEngineUrl ?? "";
|
||||||
textBoxUrl.Select(textBoxUrl.Text.Length, 0);
|
textBoxUrl.Select(textBoxUrl.Text.Length, 0);
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using TweetDuck.Configuration;
|
using TweetDuck.Configuration;
|
||||||
@@ -6,7 +7,6 @@ using TweetDuck.Core.Controls;
|
|||||||
using TweetDuck.Core.Management;
|
using TweetDuck.Core.Management;
|
||||||
using TweetDuck.Core.Other.Settings.Dialogs;
|
using TweetDuck.Core.Other.Settings.Dialogs;
|
||||||
using TweetDuck.Core.Utils;
|
using TweetDuck.Core.Utils;
|
||||||
using TweetLib.Core;
|
|
||||||
|
|
||||||
namespace TweetDuck.Core.Other.Settings{
|
namespace TweetDuck.Core.Other.Settings{
|
||||||
sealed partial class TabSettingsAdvanced : BaseTabSettings{
|
sealed partial class TabSettingsAdvanced : BaseTabSettings{
|
||||||
@@ -36,7 +36,7 @@ namespace TweetDuck.Core.Other.Settings{
|
|||||||
numClearCacheThreshold.SetValueSafe(SysConfig.ClearCacheThreshold);
|
numClearCacheThreshold.SetValueSafe(SysConfig.ClearCacheThreshold);
|
||||||
|
|
||||||
BrowserCache.GetCacheSize(task => {
|
BrowserCache.GetCacheSize(task => {
|
||||||
string text = task.Status == TaskStatus.RanToCompletion ? (int)Math.Ceiling(task.Result / (1024.0 * 1024.0)) + " MB" : "unknown";
|
string text = task.Status == TaskStatus.RanToCompletion ? (int)Math.Ceiling(task.Result/(1024.0*1024.0))+" MB" : "unknown";
|
||||||
this.InvokeSafe(() => btnClearCache.Text = $"Clear Cache ({text})");
|
this.InvokeSafe(() => btnClearCache.Text = $"Clear Cache ({text})");
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -67,11 +67,11 @@ namespace TweetDuck.Core.Other.Settings{
|
|||||||
#region Application
|
#region Application
|
||||||
|
|
||||||
private void btnOpenAppFolder_Click(object sender, EventArgs e){
|
private void btnOpenAppFolder_Click(object sender, EventArgs e){
|
||||||
App.SystemHandler.OpenFileExplorer(Program.ProgramPath);
|
using(Process.Start("explorer.exe", "\""+Program.ProgramPath+"\"")){}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void btnOpenDataFolder_Click(object sender, EventArgs e){
|
private void btnOpenDataFolder_Click(object sender, EventArgs e){
|
||||||
App.SystemHandler.OpenFileExplorer(Program.StoragePath);
|
using(Process.Start("explorer.exe", "\""+Program.StoragePath+"\"")){}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void btnRestart_Click(object sender, EventArgs e){
|
private void btnRestart_Click(object sender, EventArgs e){
|
||||||
|
@@ -3,7 +3,7 @@ using System.Windows.Forms;
|
|||||||
using TweetDuck.Core.Other.Analytics;
|
using TweetDuck.Core.Other.Analytics;
|
||||||
using TweetDuck.Core.Other.Settings.Dialogs;
|
using TweetDuck.Core.Other.Settings.Dialogs;
|
||||||
using TweetDuck.Core.Utils;
|
using TweetDuck.Core.Utils;
|
||||||
using TweetLib.Core.Features.Plugins;
|
using TweetDuck.Plugins;
|
||||||
|
|
||||||
namespace TweetDuck.Core.Other.Settings{
|
namespace TweetDuck.Core.Other.Settings{
|
||||||
sealed partial class TabSettingsFeedback : BaseTabSettings{
|
sealed partial class TabSettingsFeedback : BaseTabSettings{
|
||||||
@@ -24,7 +24,7 @@ namespace TweetDuck.Core.Other.Settings{
|
|||||||
|
|
||||||
if (analytics != null){
|
if (analytics != null){
|
||||||
string collectionTime = analyticsFile.LastCollectionMessage;
|
string collectionTime = analyticsFile.LastCollectionMessage;
|
||||||
labelDataCollectionMessage.Text = string.IsNullOrEmpty(collectionTime) ? "No collection yet" : "Last collection: " + collectionTime;
|
labelDataCollectionMessage.Text = string.IsNullOrEmpty(collectionTime) ? "No collection yet" : "Last collection: "+collectionTime;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -6,8 +6,7 @@ using TweetDuck.Core.Controls;
|
|||||||
using TweetDuck.Core.Handling.General;
|
using TweetDuck.Core.Handling.General;
|
||||||
using TweetDuck.Core.Other.Settings.Dialogs;
|
using TweetDuck.Core.Other.Settings.Dialogs;
|
||||||
using TweetDuck.Core.Utils;
|
using TweetDuck.Core.Utils;
|
||||||
using TweetLib.Core.Features.Updates;
|
using TweetDuck.Updates;
|
||||||
using TweetLib.Core.Utils;
|
|
||||||
|
|
||||||
namespace TweetDuck.Core.Other.Settings{
|
namespace TweetDuck.Core.Other.Settings{
|
||||||
sealed partial class TabSettingsGeneral : BaseTabSettings{
|
sealed partial class TabSettingsGeneral : BaseTabSettings{
|
||||||
@@ -51,7 +50,7 @@ namespace TweetDuck.Core.Other.Settings{
|
|||||||
checkAnimatedAvatars.Checked = Config.EnableAnimatedImages;
|
checkAnimatedAvatars.Checked = Config.EnableAnimatedImages;
|
||||||
|
|
||||||
trackBarZoom.SetValueSafe(Config.ZoomLevel);
|
trackBarZoom.SetValueSafe(Config.ZoomLevel);
|
||||||
labelZoomValue.Text = trackBarZoom.Value + "%";
|
labelZoomValue.Text = trackBarZoom.Value+"%";
|
||||||
|
|
||||||
// system tray
|
// system tray
|
||||||
|
|
||||||
@@ -63,7 +62,7 @@ namespace TweetDuck.Core.Other.Settings{
|
|||||||
comboBoxTrayType.Items.Add("Minimize to Tray");
|
comboBoxTrayType.Items.Add("Minimize to Tray");
|
||||||
comboBoxTrayType.Items.Add("Close to Tray");
|
comboBoxTrayType.Items.Add("Close to Tray");
|
||||||
comboBoxTrayType.Items.Add("Combined");
|
comboBoxTrayType.Items.Add("Combined");
|
||||||
comboBoxTrayType.SelectedIndex = Math.Min(Math.Max((int)Config.TrayBehavior, 0), comboBoxTrayType.Items.Count - 1);
|
comboBoxTrayType.SelectedIndex = Math.Min(Math.Max((int)Config.TrayBehavior, 0), comboBoxTrayType.Items.Count-1);
|
||||||
|
|
||||||
checkTrayHighlight.Enabled = Config.TrayBehavior.ShouldDisplayIcon();
|
checkTrayHighlight.Enabled = Config.TrayBehavior.ShouldDisplayIcon();
|
||||||
checkTrayHighlight.Checked = Config.EnableTrayHighlight;
|
checkTrayHighlight.Checked = Config.EnableTrayHighlight;
|
||||||
@@ -189,7 +188,7 @@ namespace TweetDuck.Core.Other.Settings{
|
|||||||
if (trackBarZoom.AlignValueToTick()){
|
if (trackBarZoom.AlignValueToTick()){
|
||||||
zoomUpdateTimer.Stop();
|
zoomUpdateTimer.Stop();
|
||||||
zoomUpdateTimer.Start();
|
zoomUpdateTimer.Start();
|
||||||
labelZoomValue.Text = trackBarZoom.Value + "%";
|
labelZoomValue.Text = trackBarZoom.Value+"%";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using TweetDuck.Core.Controls;
|
using TweetDuck.Core.Controls;
|
||||||
|
using TweetDuck.Core.Notification;
|
||||||
using TweetDuck.Core.Notification.Example;
|
using TweetDuck.Core.Notification.Example;
|
||||||
using TweetLib.Core.Features.Notifications;
|
|
||||||
|
|
||||||
namespace TweetDuck.Core.Other.Settings{
|
namespace TweetDuck.Core.Other.Settings{
|
||||||
sealed partial class TabSettingsNotifications : BaseTabSettings{
|
sealed partial class TabSettingsNotifications : BaseTabSettings{
|
||||||
@@ -59,31 +59,31 @@ namespace TweetDuck.Core.Other.Settings{
|
|||||||
checkTimerCountDown.Checked = Config.NotificationTimerCountDown;
|
checkTimerCountDown.Checked = Config.NotificationTimerCountDown;
|
||||||
|
|
||||||
trackBarDuration.SetValueSafe(Config.NotificationDurationValue);
|
trackBarDuration.SetValueSafe(Config.NotificationDurationValue);
|
||||||
labelDurationValue.Text = Config.NotificationDurationValue + " ms/c";
|
labelDurationValue.Text = Config.NotificationDurationValue+" ms/c";
|
||||||
|
|
||||||
// location
|
// location
|
||||||
|
|
||||||
toolTip.SetToolTip(radioLocCustom, "Drag the example notification window to the desired location.");
|
toolTip.SetToolTip(radioLocCustom, "Drag the example notification window to the desired location.");
|
||||||
|
|
||||||
switch(Config.NotificationPosition){
|
switch(Config.NotificationPosition){
|
||||||
case DesktopNotification.Position.TopLeft: radioLocTL.Checked = true; break;
|
case TweetNotification.Position.TopLeft: radioLocTL.Checked = true; break;
|
||||||
case DesktopNotification.Position.TopRight: radioLocTR.Checked = true; break;
|
case TweetNotification.Position.TopRight: radioLocTR.Checked = true; break;
|
||||||
case DesktopNotification.Position.BottomLeft: radioLocBL.Checked = true; break;
|
case TweetNotification.Position.BottomLeft: radioLocBL.Checked = true; break;
|
||||||
case DesktopNotification.Position.BottomRight: radioLocBR.Checked = true; break;
|
case TweetNotification.Position.BottomRight: radioLocBR.Checked = true; break;
|
||||||
case DesktopNotification.Position.Custom: radioLocCustom.Checked = true; break;
|
case TweetNotification.Position.Custom: radioLocCustom.Checked = true; break;
|
||||||
}
|
}
|
||||||
|
|
||||||
comboBoxDisplay.Enabled = trackBarEdgeDistance.Enabled = !radioLocCustom.Checked;
|
comboBoxDisplay.Enabled = trackBarEdgeDistance.Enabled = !radioLocCustom.Checked;
|
||||||
comboBoxDisplay.Items.Add("(Same as TweetDuck)");
|
comboBoxDisplay.Items.Add("(Same as TweetDuck)");
|
||||||
|
|
||||||
foreach(Screen screen in Screen.AllScreens){
|
foreach(Screen screen in Screen.AllScreens){
|
||||||
comboBoxDisplay.Items.Add($"{screen.DeviceName.TrimStart('\\', '.')} ({screen.Bounds.Width}x{screen.Bounds.Height})");
|
comboBoxDisplay.Items.Add(screen.DeviceName.TrimStart('\\', '.')+" ("+screen.Bounds.Width+"x"+screen.Bounds.Height+")");
|
||||||
}
|
}
|
||||||
|
|
||||||
comboBoxDisplay.SelectedIndex = Math.Min(comboBoxDisplay.Items.Count - 1, Config.NotificationDisplay);
|
comboBoxDisplay.SelectedIndex = Math.Min(comboBoxDisplay.Items.Count-1, Config.NotificationDisplay);
|
||||||
|
|
||||||
trackBarEdgeDistance.SetValueSafe(Config.NotificationEdgeDistance);
|
trackBarEdgeDistance.SetValueSafe(Config.NotificationEdgeDistance);
|
||||||
labelEdgeDistanceValue.Text = trackBarEdgeDistance.Value + " px";
|
labelEdgeDistanceValue.Text = trackBarEdgeDistance.Value+" px";
|
||||||
|
|
||||||
// size
|
// size
|
||||||
|
|
||||||
@@ -91,12 +91,12 @@ namespace TweetDuck.Core.Other.Settings{
|
|||||||
toolTip.SetToolTip(radioSizeCustom, "Resize the example notification window to the desired size.");
|
toolTip.SetToolTip(radioSizeCustom, "Resize the example notification window to the desired size.");
|
||||||
|
|
||||||
switch(Config.NotificationSize){
|
switch(Config.NotificationSize){
|
||||||
case DesktopNotification.Size.Auto: radioSizeAuto.Checked = true; break;
|
case TweetNotification.Size.Auto: radioSizeAuto.Checked = true; break;
|
||||||
case DesktopNotification.Size.Custom: radioSizeCustom.Checked = true; break;
|
case TweetNotification.Size.Custom: radioSizeCustom.Checked = true; break;
|
||||||
}
|
}
|
||||||
|
|
||||||
trackBarScrollSpeed.SetValueSafe(Config.NotificationScrollSpeed);
|
trackBarScrollSpeed.SetValueSafe(Config.NotificationScrollSpeed);
|
||||||
labelScrollSpeedValue.Text = trackBarScrollSpeed.Value + "%";
|
labelScrollSpeedValue.Text = trackBarScrollSpeed.Value+"%";
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnReady(){
|
public override void OnReady(){
|
||||||
@@ -195,7 +195,7 @@ namespace TweetDuck.Core.Other.Settings{
|
|||||||
durationUpdateTimer.Start();
|
durationUpdateTimer.Start();
|
||||||
|
|
||||||
Config.NotificationDurationValue = trackBarDuration.Value;
|
Config.NotificationDurationValue = trackBarDuration.Value;
|
||||||
labelDurationValue.Text = Config.NotificationDurationValue + " ms/c";
|
labelDurationValue.Text = Config.NotificationDurationValue+" ms/c";
|
||||||
}
|
}
|
||||||
|
|
||||||
private void btnDurationShort_Click(object sender, EventArgs e){
|
private void btnDurationShort_Click(object sender, EventArgs e){
|
||||||
@@ -219,10 +219,10 @@ namespace TweetDuck.Core.Other.Settings{
|
|||||||
#region Location
|
#region Location
|
||||||
|
|
||||||
private void radioLoc_CheckedChanged(object sender, EventArgs e){
|
private void radioLoc_CheckedChanged(object sender, EventArgs e){
|
||||||
if (radioLocTL.Checked)Config.NotificationPosition = DesktopNotification.Position.TopLeft;
|
if (radioLocTL.Checked)Config.NotificationPosition = TweetNotification.Position.TopLeft;
|
||||||
else if (radioLocTR.Checked)Config.NotificationPosition = DesktopNotification.Position.TopRight;
|
else if (radioLocTR.Checked)Config.NotificationPosition = TweetNotification.Position.TopRight;
|
||||||
else if (radioLocBL.Checked)Config.NotificationPosition = DesktopNotification.Position.BottomLeft;
|
else if (radioLocBL.Checked)Config.NotificationPosition = TweetNotification.Position.BottomLeft;
|
||||||
else if (radioLocBR.Checked)Config.NotificationPosition = DesktopNotification.Position.BottomRight;
|
else if (radioLocBR.Checked)Config.NotificationPosition = TweetNotification.Position.BottomRight;
|
||||||
|
|
||||||
comboBoxDisplay.Enabled = trackBarEdgeDistance.Enabled = true;
|
comboBoxDisplay.Enabled = trackBarEdgeDistance.Enabled = true;
|
||||||
notification.ShowExampleNotification(false);
|
notification.ShowExampleNotification(false);
|
||||||
@@ -233,18 +233,18 @@ namespace TweetDuck.Core.Other.Settings{
|
|||||||
Config.CustomNotificationPosition = notification.Location;
|
Config.CustomNotificationPosition = notification.Location;
|
||||||
}
|
}
|
||||||
|
|
||||||
Config.NotificationPosition = DesktopNotification.Position.Custom;
|
Config.NotificationPosition = TweetNotification.Position.Custom;
|
||||||
|
|
||||||
comboBoxDisplay.Enabled = trackBarEdgeDistance.Enabled = false;
|
comboBoxDisplay.Enabled = trackBarEdgeDistance.Enabled = false;
|
||||||
notification.ShowExampleNotification(false);
|
notification.ShowExampleNotification(false);
|
||||||
|
|
||||||
if (notification.IsFullyOutsideView() && FormMessage.Question("Notification is Outside View", "The notification seems to be outside of view, would you like to reset its position?", FormMessage.Yes, FormMessage.No)){
|
if (notification.IsFullyOutsideView() && FormMessage.Question("Notification is Outside View", "The notification seems to be outside of view, would you like to reset its position?", FormMessage.Yes, FormMessage.No)){
|
||||||
Config.NotificationPosition = DesktopNotification.Position.TopRight;
|
Config.NotificationPosition = TweetNotification.Position.TopRight;
|
||||||
notification.MoveToVisibleLocation();
|
notification.MoveToVisibleLocation();
|
||||||
|
|
||||||
Config.CustomNotificationPosition = notification.Location;
|
Config.CustomNotificationPosition = notification.Location;
|
||||||
|
|
||||||
Config.NotificationPosition = DesktopNotification.Position.Custom;
|
Config.NotificationPosition = TweetNotification.Position.Custom;
|
||||||
notification.MoveToVisibleLocation();
|
notification.MoveToVisibleLocation();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -255,7 +255,7 @@ namespace TweetDuck.Core.Other.Settings{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void trackBarEdgeDistance_ValueChanged(object sender, EventArgs e){
|
private void trackBarEdgeDistance_ValueChanged(object sender, EventArgs e){
|
||||||
labelEdgeDistanceValue.Text = trackBarEdgeDistance.Value + " px";
|
labelEdgeDistanceValue.Text = trackBarEdgeDistance.Value+" px";
|
||||||
Config.NotificationEdgeDistance = trackBarEdgeDistance.Value;
|
Config.NotificationEdgeDistance = trackBarEdgeDistance.Value;
|
||||||
notification.ShowExampleNotification(false);
|
notification.ShowExampleNotification(false);
|
||||||
}
|
}
|
||||||
@@ -265,7 +265,7 @@ namespace TweetDuck.Core.Other.Settings{
|
|||||||
|
|
||||||
private void radioSize_CheckedChanged(object sender, EventArgs e){
|
private void radioSize_CheckedChanged(object sender, EventArgs e){
|
||||||
if (radioSizeAuto.Checked){
|
if (radioSizeAuto.Checked){
|
||||||
Config.NotificationSize = DesktopNotification.Size.Auto;
|
Config.NotificationSize = TweetNotification.Size.Auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
notification.ShowExampleNotification(false);
|
notification.ShowExampleNotification(false);
|
||||||
@@ -276,13 +276,13 @@ namespace TweetDuck.Core.Other.Settings{
|
|||||||
Config.CustomNotificationSize = notification.BrowserSize;
|
Config.CustomNotificationSize = notification.BrowserSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
Config.NotificationSize = DesktopNotification.Size.Custom;
|
Config.NotificationSize = TweetNotification.Size.Custom;
|
||||||
notification.ShowExampleNotification(false);
|
notification.ShowExampleNotification(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void trackBarScrollSpeed_ValueChanged(object sender, EventArgs e){
|
private void trackBarScrollSpeed_ValueChanged(object sender, EventArgs e){
|
||||||
if (trackBarScrollSpeed.AlignValueToTick()){
|
if (trackBarScrollSpeed.AlignValueToTick()){
|
||||||
labelScrollSpeedValue.Text = trackBarScrollSpeed.Value + "%";
|
labelScrollSpeedValue.Text = trackBarScrollSpeed.Value+"%";
|
||||||
Config.NotificationScrollSpeed = trackBarScrollSpeed.Value;
|
Config.NotificationScrollSpeed = trackBarScrollSpeed.Value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -20,7 +20,7 @@ namespace TweetDuck.Core.Other.Settings{
|
|||||||
toolTip.SetToolTip(tbCustomSound, "When empty, the default TweetDeck sound notification is used.");
|
toolTip.SetToolTip(tbCustomSound, "When empty, the default TweetDeck sound notification is used.");
|
||||||
|
|
||||||
trackBarVolume.SetValueSafe(Config.NotificationSoundVolume);
|
trackBarVolume.SetValueSafe(Config.NotificationSoundVolume);
|
||||||
labelVolumeValue.Text = trackBarVolume.Value + "%";
|
labelVolumeValue.Text = trackBarVolume.Value+"%";
|
||||||
|
|
||||||
tbCustomSound.Text = Config.NotificationSoundPath;
|
tbCustomSound.Text = Config.NotificationSoundPath;
|
||||||
tbCustomSound_TextChanged(tbCustomSound, EventArgs.Empty);
|
tbCustomSound_TextChanged(tbCustomSound, EventArgs.Empty);
|
||||||
@@ -83,7 +83,7 @@ namespace TweetDuck.Core.Other.Settings{
|
|||||||
private void trackBarVolume_ValueChanged(object sender, EventArgs e){
|
private void trackBarVolume_ValueChanged(object sender, EventArgs e){
|
||||||
volumeUpdateTimer.Stop();
|
volumeUpdateTimer.Stop();
|
||||||
volumeUpdateTimer.Start();
|
volumeUpdateTimer.Start();
|
||||||
labelVolumeValue.Text = trackBarVolume.Value + "%";
|
labelVolumeValue.Text = trackBarVolume.Value+"%";
|
||||||
}
|
}
|
||||||
|
|
||||||
private void volumeUpdateTimer_Tick(object sender, EventArgs e){
|
private void volumeUpdateTimer_Tick(object sender, EventArgs e){
|
||||||
|
49
Core/Other/TaskbarIcon.cs
Normal file
49
Core/Other/TaskbarIcon.cs
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.WindowsAPICodePack.Taskbar;
|
||||||
|
using TweetDuck.Configuration;
|
||||||
|
using Res = TweetDuck.Properties.Resources;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.Other{
|
||||||
|
sealed class TaskbarIcon : IDisposable{
|
||||||
|
private static UserConfig Config => Program.Config.User;
|
||||||
|
|
||||||
|
public bool HasNotifications{
|
||||||
|
get{
|
||||||
|
return hasNotifications;
|
||||||
|
}
|
||||||
|
|
||||||
|
set{
|
||||||
|
if (hasNotifications != value){
|
||||||
|
hasNotifications = value;
|
||||||
|
UpdateIcon();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool hasNotifications;
|
||||||
|
|
||||||
|
public TaskbarIcon(){
|
||||||
|
Config.MuteToggled += Config_MuteToggled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose(){
|
||||||
|
Config.MuteToggled -= Config_MuteToggled;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Config_MuteToggled(object sender, EventArgs e){
|
||||||
|
UpdateIcon();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateIcon(){
|
||||||
|
if (hasNotifications){
|
||||||
|
TaskbarManager.Instance.SetOverlayIcon(Res.overlay_notification, "Unread Notifications");
|
||||||
|
}
|
||||||
|
else if (Config.MuteNotifications){
|
||||||
|
TaskbarManager.Instance.SetOverlayIcon(Res.overlay_muted, "Notifications Muted");
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
TaskbarManager.Instance.SetOverlayIcon(null, string.Empty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -51,7 +51,7 @@ namespace TweetDuck.Core.Other{
|
|||||||
this.contextMenu.MenuItems.Add("Mute notifications", menuItemMuteNotifications_Click);
|
this.contextMenu.MenuItems.Add("Mute notifications", menuItemMuteNotifications_Click);
|
||||||
this.contextMenu.MenuItems.Add("Close", menuItemClose_Click);
|
this.contextMenu.MenuItems.Add("Close", menuItemClose_Click);
|
||||||
this.contextMenu.Popup += contextMenu_Popup;
|
this.contextMenu.Popup += contextMenu_Popup;
|
||||||
|
|
||||||
this.notifyIcon.ContextMenu = contextMenu;
|
this.notifyIcon.ContextMenu = contextMenu;
|
||||||
this.notifyIcon.Text = Program.BrandName;
|
this.notifyIcon.Text = Program.BrandName;
|
||||||
|
|
||||||
|
@@ -5,7 +5,6 @@ using System.Windows.Forms;
|
|||||||
using CefSharp;
|
using CefSharp;
|
||||||
using CefSharp.WinForms;
|
using CefSharp.WinForms;
|
||||||
using TweetDuck.Configuration;
|
using TweetDuck.Configuration;
|
||||||
using TweetDuck.Core.Adapters;
|
|
||||||
using TweetDuck.Core.Bridge;
|
using TweetDuck.Core.Bridge;
|
||||||
using TweetDuck.Core.Controls;
|
using TweetDuck.Core.Controls;
|
||||||
using TweetDuck.Core.Handling;
|
using TweetDuck.Core.Handling;
|
||||||
@@ -13,10 +12,8 @@ using TweetDuck.Core.Handling.General;
|
|||||||
using TweetDuck.Core.Notification;
|
using TweetDuck.Core.Notification;
|
||||||
using TweetDuck.Core.Utils;
|
using TweetDuck.Core.Utils;
|
||||||
using TweetDuck.Plugins;
|
using TweetDuck.Plugins;
|
||||||
using TweetLib.Core.Features.Plugins;
|
using TweetDuck.Plugins.Enums;
|
||||||
using TweetLib.Core.Features.Plugins.Enums;
|
using TweetDuck.Resources;
|
||||||
using TweetLib.Core.Features.Twitter;
|
|
||||||
using TweetLib.Core.Utils;
|
|
||||||
|
|
||||||
namespace TweetDuck.Core{
|
namespace TweetDuck.Core{
|
||||||
sealed class TweetDeckBrowser : IDisposable{
|
sealed class TweetDeckBrowser : IDisposable{
|
||||||
@@ -38,8 +35,9 @@ namespace TweetDuck.Core{
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
using IFrame frame = browser.GetBrowser().MainFrame;
|
using(IFrame frame = browser.GetBrowser().MainFrame){
|
||||||
return TwitterUrls.IsTweetDeck(frame.Url);
|
return TwitterUtils.IsTweetDeckWebsite(frame);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,12 +47,12 @@ namespace TweetDuck.Core{
|
|||||||
private string prevSoundNotificationPath = null;
|
private string prevSoundNotificationPath = null;
|
||||||
|
|
||||||
public TweetDeckBrowser(FormBrowser owner, PluginManager plugins, TweetDeckBridge tdBridge, UpdateBridge updateBridge){
|
public TweetDeckBrowser(FormBrowser owner, PluginManager plugins, TweetDeckBridge tdBridge, UpdateBridge updateBridge){
|
||||||
resourceHandlerFactory.RegisterHandler(FormNotificationBase.AppLogo);
|
resourceHandlerFactory.RegisterHandler(TweetNotification.AppLogo);
|
||||||
resourceHandlerFactory.RegisterHandler(TwitterUtils.LoadingSpinner);
|
resourceHandlerFactory.RegisterHandler(TwitterUtils.LoadingSpinner);
|
||||||
|
|
||||||
RequestHandlerBrowser requestHandler = new RequestHandlerBrowser();
|
RequestHandlerBrowser requestHandler = new RequestHandlerBrowser();
|
||||||
|
|
||||||
this.browser = new ChromiumWebBrowser(TwitterUrls.TweetDeck){
|
this.browser = new ChromiumWebBrowser(TwitterUtils.TweetDeckURL){
|
||||||
DialogHandler = new FileDialogHandler(),
|
DialogHandler = new FileDialogHandler(),
|
||||||
DragHandler = new DragHandlerBrowser(requestHandler),
|
DragHandler = new DragHandlerBrowser(requestHandler),
|
||||||
MenuHandler = new ContextMenuBrowser(owner),
|
MenuHandler = new ContextMenuBrowser(owner),
|
||||||
@@ -79,7 +77,7 @@ namespace TweetDuck.Core{
|
|||||||
this.browser.SetupZoomEvents();
|
this.browser.SetupZoomEvents();
|
||||||
|
|
||||||
owner.Controls.Add(browser);
|
owner.Controls.Add(browser);
|
||||||
plugins.Register(PluginEnvironment.Browser, new PluginDispatcher(browser));
|
plugins.Register(browser, PluginEnvironment.Browser, owner, true);
|
||||||
|
|
||||||
Config.MuteToggled += Config_MuteToggled;
|
Config.MuteToggled += Config_MuteToggled;
|
||||||
Config.SoundNotificationChanged += Config_SoundNotificationInfoChanged;
|
Config.SoundNotificationChanged += Config_SoundNotificationInfoChanged;
|
||||||
@@ -123,16 +121,14 @@ namespace TweetDuck.Core{
|
|||||||
IFrame frame = e.Frame;
|
IFrame frame = e.Frame;
|
||||||
|
|
||||||
if (frame.IsMain){
|
if (frame.IsMain){
|
||||||
string url = frame.Url;
|
if (TwitterUtils.IsTwitterWebsite(frame)){
|
||||||
|
string css = ScriptLoader.LoadResource("styles/twitter.css", browser);
|
||||||
if (TwitterUrls.IsTwitter(url)){
|
|
||||||
string css = Program.Resources.Load("styles/twitter.css");
|
|
||||||
resourceHandlerFactory.RegisterHandler(TwitterStyleUrl, ResourceHandler.FromString(css, mimeType: "text/css"));
|
resourceHandlerFactory.RegisterHandler(TwitterStyleUrl, ResourceHandler.FromString(css, mimeType: "text/css"));
|
||||||
|
|
||||||
CefScriptExecutor.RunFile(frame, "twitter.js");
|
ScriptLoader.ExecuteFile(frame, "twitter.js", browser);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!TwitterUrls.IsTwitterLogin2Factor(url)){
|
if (!TwitterUtils.IsTwitterLogin2FactorWebsite(frame)){
|
||||||
frame.ExecuteJavaScriptAsync(TwitterUtils.BackgroundColorOverride);
|
frame.ExecuteJavaScriptAsync(TwitterUtils.BackgroundColorOverride);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -140,12 +136,11 @@ namespace TweetDuck.Core{
|
|||||||
|
|
||||||
private void browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
|
private void browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
|
||||||
IFrame frame = e.Frame;
|
IFrame frame = e.Frame;
|
||||||
string url = frame.Url;
|
|
||||||
|
|
||||||
if (frame.IsMain){
|
if (frame.IsMain){
|
||||||
if (TwitterUrls.IsTweetDeck(url)){
|
if (TwitterUtils.IsTweetDeckWebsite(frame)){
|
||||||
UpdateProperties();
|
UpdateProperties();
|
||||||
CefScriptExecutor.RunFile(frame, "code.js");
|
ScriptLoader.ExecuteFile(frame, "code.js", browser);
|
||||||
|
|
||||||
InjectBrowserCSS();
|
InjectBrowserCSS();
|
||||||
ReinjectCustomCSS(Config.CustomBrowserCSS);
|
ReinjectCustomCSS(Config.CustomBrowserCSS);
|
||||||
@@ -154,18 +149,18 @@ namespace TweetDuck.Core{
|
|||||||
TweetDeckBridge.ResetStaticProperties();
|
TweetDeckBridge.ResetStaticProperties();
|
||||||
|
|
||||||
if (Arguments.HasFlag(Arguments.ArgIgnoreGDPR)){
|
if (Arguments.HasFlag(Arguments.ArgIgnoreGDPR)){
|
||||||
CefScriptExecutor.RunScript(frame, "TD.storage.Account.prototype.requiresConsent = function(){ return false; }", "gen:gdpr");
|
ScriptLoader.ExecuteScript(frame, "TD.storage.Account.prototype.requiresConsent = function(){ return false; }", "gen:gdpr");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Config.FirstRun){
|
if (Config.FirstRun){
|
||||||
CefScriptExecutor.RunFile(frame, "introduction.js");
|
ScriptLoader.ExecuteFile(frame, "introduction.js", browser);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CefScriptExecutor.RunFile(frame, "update.js");
|
ScriptLoader.ExecuteFile(frame, "update.js", browser);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (url == ErrorUrl){
|
if (frame.Url == ErrorUrl){
|
||||||
resourceHandlerFactory.UnregisterHandler(ErrorUrl);
|
resourceHandlerFactory.UnregisterHandler(ErrorUrl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -176,13 +171,10 @@ namespace TweetDuck.Core{
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!e.FailedUrl.StartsWith("http://td/", StringComparison.Ordinal)){
|
if (!e.FailedUrl.StartsWith("http://td/", StringComparison.Ordinal)){
|
||||||
string errorPage = Program.Resources.LoadSilent("pages/error.html");
|
string errorPage = ScriptLoader.LoadResourceSilent("pages/error.html");
|
||||||
|
|
||||||
if (errorPage != null){
|
if (errorPage != null){
|
||||||
string errorName = Enum.GetName(typeof(CefErrorCode), e.ErrorCode);
|
resourceHandlerFactory.RegisterHandler(ErrorUrl, ResourceHandler.FromString(errorPage.Replace("{err}", BrowserUtils.GetErrorName(e.ErrorCode))));
|
||||||
string errorTitle = StringUtils.ConvertPascalCaseToScreamingSnakeCase(errorName ?? string.Empty);
|
|
||||||
|
|
||||||
resourceHandlerFactory.RegisterHandler(ErrorUrl, ResourceHandler.FromString(errorPage.Replace("{err}", errorTitle)));
|
|
||||||
browser.Load(ErrorUrl);
|
browser.Load(ErrorUrl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -225,7 +217,7 @@ namespace TweetDuck.Core{
|
|||||||
// javascript calls
|
// javascript calls
|
||||||
|
|
||||||
public void ReloadToTweetDeck(){
|
public void ReloadToTweetDeck(){
|
||||||
browser.ExecuteScriptAsync($"if(window.TDGF_reload)window.TDGF_reload();else window.location.href='{TwitterUrls.TweetDeck}'");
|
browser.ExecuteScriptAsync($"if(window.TDGF_reload)window.TDGF_reload();else window.location.href='{TwitterUtils.TweetDeckURL}'");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateProperties(){
|
public void UpdateProperties(){
|
||||||
@@ -233,7 +225,7 @@ namespace TweetDuck.Core{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void InjectBrowserCSS(){
|
public void InjectBrowserCSS(){
|
||||||
browser.ExecuteScriptAsync("TDGF_injectBrowserCSS", Program.Resources.Load("styles/browser.css")?.TrimEnd() ?? string.Empty);
|
browser.ExecuteScriptAsync("TDGF_injectBrowserCSS", ScriptLoader.LoadResource("styles/browser.css", browser)?.TrimEnd() ?? string.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ReinjectCustomCSS(string css){
|
public void ReinjectCustomCSS(string css){
|
||||||
|
@@ -3,16 +3,16 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Net;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using CefSharp.WinForms;
|
using CefSharp.WinForms;
|
||||||
using TweetDuck.Configuration;
|
using TweetDuck.Configuration;
|
||||||
using TweetDuck.Core.Other;
|
using TweetDuck.Core.Other;
|
||||||
using TweetLib.Core.Features.Twitter;
|
|
||||||
|
|
||||||
namespace TweetDuck.Core.Utils{
|
namespace TweetDuck.Core.Utils{
|
||||||
static class BrowserUtils{
|
static class BrowserUtils{
|
||||||
public static string UserAgentVanilla => Program.BrandName + " " + Application.ProductVersion;
|
public static string UserAgentVanilla => Program.BrandName+" "+Application.ProductVersion;
|
||||||
public static string UserAgentChrome => "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/" + Cef.ChromiumVersion + " Safari/537.36";
|
public static string UserAgentChrome => "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/"+Cef.ChromiumVersion+" Safari/537.36";
|
||||||
|
|
||||||
public static readonly bool HasDevTools = File.Exists(Path.Combine(Program.ProgramPath, "devtools_resources.pak"));
|
public static readonly bool HasDevTools = File.Exists(Path.Combine(Program.ProgramPath, "devtools_resources.pak"));
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ namespace TweetDuck.Core.Utils{
|
|||||||
args["disable-threaded-scrolling"] = "1";
|
args["disable-threaded-scrolling"] = "1";
|
||||||
|
|
||||||
if (args.TryGetValue("disable-features", out string disabledFeatures)){
|
if (args.TryGetValue("disable-features", out string disabledFeatures)){
|
||||||
args["disable-features"] = "TouchpadAndWheelScrollLatching," + disabledFeatures;
|
args["disable-features"] = "TouchpadAndWheelScrollLatching,"+disabledFeatures;
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
args["disable-features"] = "TouchpadAndWheelScrollLatching";
|
args["disable-features"] = "TouchpadAndWheelScrollLatching";
|
||||||
@@ -48,7 +48,7 @@ namespace TweetDuck.Core.Utils{
|
|||||||
args["enable-system-flash"] = "0";
|
args["enable-system-flash"] = "0";
|
||||||
|
|
||||||
if (args.TryGetValue("js-flags", out string jsFlags)){
|
if (args.TryGetValue("js-flags", out string jsFlags)){
|
||||||
args["js-flags"] = "--expose-gc " + jsFlags;
|
args["js-flags"] = "--expose-gc "+jsFlags;
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
args["js-flags"] = "--expose-gc";
|
args["js-flags"] = "--expose-gc";
|
||||||
@@ -60,12 +60,8 @@ namespace TweetDuck.Core.Utils{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void SetupZoomEvents(this ChromiumWebBrowser browser){
|
public static void SetupZoomEvents(this ChromiumWebBrowser browser){
|
||||||
static void SetZoomLevel(IBrowserHost host, int percentage){
|
|
||||||
host.SetZoomLevel(Math.Log(percentage / 100.0, 1.2));
|
|
||||||
}
|
|
||||||
|
|
||||||
void UpdateZoomLevel(object sender, EventArgs args){
|
void UpdateZoomLevel(object sender, EventArgs args){
|
||||||
SetZoomLevel(browser.GetBrowserHost(), Config.ZoomLevel);
|
SetZoomLevel(browser.GetBrowser(), Config.ZoomLevel);
|
||||||
}
|
}
|
||||||
|
|
||||||
Config.ZoomLevelChanged += UpdateZoomLevel;
|
Config.ZoomLevelChanged += UpdateZoomLevel;
|
||||||
@@ -73,16 +69,34 @@ namespace TweetDuck.Core.Utils{
|
|||||||
|
|
||||||
browser.FrameLoadStart += (sender, args) => {
|
browser.FrameLoadStart += (sender, args) => {
|
||||||
if (args.Frame.IsMain && Config.ZoomLevel != 100){
|
if (args.Frame.IsMain && Config.ZoomLevel != 100){
|
||||||
SetZoomLevel(args.Browser.GetHost(), Config.ZoomLevel);
|
SetZoomLevel(args.Browser, Config.ZoomLevel);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private const string TwitterTrackingUrl = "t.co";
|
||||||
|
|
||||||
|
public enum UrlCheckResult{
|
||||||
|
Invalid, Tracking, Fine
|
||||||
|
}
|
||||||
|
|
||||||
|
public static UrlCheckResult CheckUrl(string url){
|
||||||
|
if (Uri.TryCreate(url, UriKind.Absolute, out Uri uri)){
|
||||||
|
string scheme = uri.Scheme;
|
||||||
|
|
||||||
|
if (scheme == Uri.UriSchemeHttps || scheme == Uri.UriSchemeHttp || scheme == Uri.UriSchemeFtp || scheme == Uri.UriSchemeMailto){
|
||||||
|
return uri.Host == TwitterTrackingUrl ? UrlCheckResult.Tracking : UrlCheckResult.Fine;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return UrlCheckResult.Invalid;
|
||||||
|
}
|
||||||
|
|
||||||
public static void OpenExternalBrowser(string url){
|
public static void OpenExternalBrowser(string url){
|
||||||
if (string.IsNullOrWhiteSpace(url))return;
|
if (string.IsNullOrWhiteSpace(url))return;
|
||||||
|
|
||||||
switch(TwitterUrls.Check(url)){
|
switch(CheckUrl(url)){
|
||||||
case TwitterUrls.UrlType.Fine:
|
case UrlCheckResult.Fine:
|
||||||
if (FormGuide.CheckGuideUrl(url, out string hash)){
|
if (FormGuide.CheckGuideUrl(url, out string hash)){
|
||||||
FormGuide.Show(hash);
|
FormGuide.Show(hash);
|
||||||
}
|
}
|
||||||
@@ -103,12 +117,12 @@ namespace TweetDuck.Core.Utils{
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case TwitterUrls.UrlType.Tracking:
|
case UrlCheckResult.Tracking:
|
||||||
if (Config.IgnoreTrackingUrlWarning){
|
if (Config.IgnoreTrackingUrlWarning){
|
||||||
goto case TwitterUrls.UrlType.Fine;
|
goto case UrlCheckResult.Fine;
|
||||||
}
|
}
|
||||||
|
|
||||||
using(FormMessage form = new FormMessage("Blocked URL", "TweetDuck has blocked a tracking url due to privacy concerns. Do you want to visit it anyway?\n" + url, MessageBoxIcon.Warning)){
|
using(FormMessage form = new FormMessage("Blocked URL", "TweetDuck has blocked a tracking url due to privacy concerns. Do you want to visit it anyway?\n"+url, MessageBoxIcon.Warning)){
|
||||||
form.AddButton(FormMessage.No, DialogResult.No, ControlType.Cancel | ControlType.Focused);
|
form.AddButton(FormMessage.No, DialogResult.No, ControlType.Cancel | ControlType.Focused);
|
||||||
form.AddButton(FormMessage.Yes, DialogResult.Yes, ControlType.Accept);
|
form.AddButton(FormMessage.Yes, DialogResult.Yes, ControlType.Accept);
|
||||||
form.AddButton("Always Visit", DialogResult.Ignore);
|
form.AddButton("Always Visit", DialogResult.Ignore);
|
||||||
@@ -121,22 +135,20 @@ namespace TweetDuck.Core.Utils{
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (result == DialogResult.Ignore || result == DialogResult.Yes){
|
if (result == DialogResult.Ignore || result == DialogResult.Yes){
|
||||||
goto case TwitterUrls.UrlType.Fine;
|
goto case UrlCheckResult.Fine;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case TwitterUrls.UrlType.Invalid:
|
case UrlCheckResult.Invalid:
|
||||||
FormMessage.Warning("Blocked URL", "A potentially malicious URL was blocked from opening:\n" + url, FormMessage.OK);
|
FormMessage.Warning("Blocked URL", "A potentially malicious URL was blocked from opening:\n"+url, FormMessage.OK);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void OpenExternalSearch(string query){
|
public static void OpenExternalSearch(string query){
|
||||||
if (string.IsNullOrWhiteSpace(query)){
|
if (string.IsNullOrWhiteSpace(query))return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string searchUrl = Config.SearchEngineUrl;
|
string searchUrl = Config.SearchEngineUrl;
|
||||||
|
|
||||||
@@ -158,12 +170,60 @@ namespace TweetDuck.Core.Utils{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
OpenExternalBrowser(searchUrl + Uri.EscapeUriString(query));
|
OpenExternalBrowser(searchUrl+Uri.EscapeUriString(query));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string GetFileNameFromUrl(string url){
|
||||||
|
string file = Path.GetFileName(new Uri(url).AbsolutePath);
|
||||||
|
return string.IsNullOrEmpty(file) ? null : file;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetErrorName(CefErrorCode code){
|
||||||
|
return StringUtils.ConvertPascalCaseToScreamingSnakeCase(Enum.GetName(typeof(CefErrorCode), code) ?? string.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static WebClient CreateWebClient(){
|
||||||
|
WindowsUtils.EnsureTLS12();
|
||||||
|
|
||||||
|
WebClient client = new WebClient{ Proxy = null };
|
||||||
|
client.Headers[HttpRequestHeader.UserAgent] = UserAgentVanilla;
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static WebClient DownloadFileAsync(string url, string target, string cookie, Action onSuccess, Action<Exception> onFailure){
|
||||||
|
WebClient client = CreateWebClient();
|
||||||
|
|
||||||
|
if (cookie != null){
|
||||||
|
client.Headers[HttpRequestHeader.Cookie] = cookie;
|
||||||
|
}
|
||||||
|
|
||||||
|
client.DownloadFileCompleted += (sender, args) => {
|
||||||
|
if (args.Cancelled){
|
||||||
|
try{
|
||||||
|
File.Delete(target);
|
||||||
|
}catch{
|
||||||
|
// didn't want it deleted anyways
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (args.Error != null){
|
||||||
|
onFailure?.Invoke(args.Error);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
onSuccess?.Invoke();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
client.DownloadFileAsync(new Uri(url), target);
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
public static int Scale(int baseValue, double scaleFactor){
|
public static int Scale(int baseValue, double scaleFactor){
|
||||||
return (int)Math.Round(baseValue * scaleFactor);
|
return (int)Math.Round(baseValue*scaleFactor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SetZoomLevel(IBrowser browser, int percentage){
|
||||||
|
browser.GetHost().SetZoomLevel(Math.Log(percentage/100.0, 1.2));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -3,8 +3,8 @@ using System.Collections.Generic;
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
namespace TweetLib.Core.Utils{
|
namespace TweetDuck.Core.Utils{
|
||||||
public static class LocaleUtils{
|
static class LocaleUtils{
|
||||||
// https://cs.chromium.org/chromium/src/third_party/hunspell_dictionaries/
|
// https://cs.chromium.org/chromium/src/third_party/hunspell_dictionaries/
|
||||||
public static IEnumerable<Item> SpellCheckLanguages { get; } = new List<string>{
|
public static IEnumerable<Item> SpellCheckLanguages { get; } = new List<string>{
|
||||||
"af-ZA", "bg-BG", "ca-ES", "cs-CZ", "da-DK", "de-DE",
|
"af-ZA", "bg-BG", "ca-ES", "cs-CZ", "da-DK", "de-DE",
|
||||||
@@ -33,9 +33,9 @@ namespace TweetLib.Core.Utils{
|
|||||||
|
|
||||||
private string Name => info?.NativeName ?? Code;
|
private string Name => info?.NativeName ?? Code;
|
||||||
|
|
||||||
private readonly CultureInfo? info;
|
private readonly CultureInfo info;
|
||||||
|
|
||||||
public Item(string code, string? alt = null){
|
public Item(string code, string alt = null){
|
||||||
this.Code = code;
|
this.Code = code;
|
||||||
|
|
||||||
try{
|
try{
|
@@ -133,7 +133,7 @@ namespace TweetDuck.Core.Utils{
|
|||||||
ticks = (uint)Environment.TickCount;
|
ticks = (uint)Environment.TickCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
int seconds = (int)Math.Floor(TimeSpan.FromMilliseconds(ticks - info.dwTime).TotalSeconds);
|
int seconds = (int)Math.Floor(TimeSpan.FromMilliseconds(ticks-info.dwTime).TotalSeconds);
|
||||||
return Math.Max(0, seconds); // ignore rollover after several weeks of uptime
|
return Math.Max(0, seconds); // ignore rollover after several weeks of uptime
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -2,20 +2,10 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace TweetLib.Core.Utils{
|
namespace TweetDuck.Core.Utils{
|
||||||
public static class StringUtils{
|
static class StringUtils{
|
||||||
public static readonly string[] EmptyArray = new string[0];
|
public static readonly string[] EmptyArray = new string[0];
|
||||||
|
|
||||||
public static (string before, string after)? SplitInTwo(string str, char search, int startIndex = 0){
|
|
||||||
int index = str.IndexOf(search, startIndex);
|
|
||||||
|
|
||||||
if (index == -1){
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (str.Substring(0, index), str.Substring(index + 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
||||||
@@ -33,7 +23,7 @@ namespace TweetLib.Core.Utils{
|
|||||||
return Regex.Replace(str, @"[a-zA-Z]", match => {
|
return Regex.Replace(str, @"[a-zA-Z]", match => {
|
||||||
int code = match.Value[0];
|
int code = match.Value[0];
|
||||||
int start = code <= 90 ? 65 : 97;
|
int start = code <= 90 ? 65 : 97;
|
||||||
return ((char)(start + (code - start + 13) % 26)).ToString();
|
return ((char)(start+(code-start+13)%26)).ToString();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@@ -2,50 +2,100 @@
|
|||||||
using CefSharp;
|
using CefSharp;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using TweetDuck.Core.Management;
|
using TweetDuck.Core.Management;
|
||||||
using TweetDuck.Core.Other;
|
using TweetDuck.Core.Other;
|
||||||
using TweetDuck.Data;
|
using TweetDuck.Data;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using TweetLib.Core.Features.Twitter;
|
|
||||||
using TweetLib.Core.Utils;
|
|
||||||
using Cookie = CefSharp.Cookie;
|
using Cookie = CefSharp.Cookie;
|
||||||
|
|
||||||
namespace TweetDuck.Core.Utils{
|
namespace TweetDuck.Core.Utils{
|
||||||
static class TwitterUtils{
|
static class TwitterUtils{
|
||||||
|
public const string TweetDeckURL = "https://tweetdeck.twitter.com";
|
||||||
|
|
||||||
public static readonly Color BackgroundColor = Color.FromArgb(28, 99, 153);
|
public static readonly Color BackgroundColor = Color.FromArgb(28, 99, 153);
|
||||||
public const string BackgroundColorOverride = "setTimeout(function f(){let h=document.head;if(!h){setTimeout(f,5);return;}let e=document.createElement('style');e.innerHTML='body,body::before{background:#1c6399!important;margin:0}';h.appendChild(e);},1)";
|
public const string BackgroundColorOverride = "setTimeout(function f(){let h=document.head;if(!h){setTimeout(f,5);return;}let e=document.createElement('style');e.innerHTML='body,body::before{background:#1c6399!important;margin:0}';h.appendChild(e);},1)";
|
||||||
|
|
||||||
public static readonly ResourceLink LoadingSpinner = new ResourceLink("https://ton.twimg.com/tduck/spinner", ResourceHandler.FromByteArray(Properties.Resources.spinner, "image/apng"));
|
public static readonly ResourceLink LoadingSpinner = new ResourceLink("https://ton.twimg.com/tduck/spinner", ResourceHandler.FromByteArray(Properties.Resources.spinner, "image/apng"));
|
||||||
|
|
||||||
|
private static readonly Lazy<Regex> RegexAccountLazy = new Lazy<Regex>(() => new Regex(@"^https?://twitter\.com/(?!signup$|tos$|privacy$|search$|search-)([^/?]+)/?$", RegexOptions.Compiled), false);
|
||||||
|
public static Regex RegexAccount => RegexAccountLazy.Value;
|
||||||
|
|
||||||
public static readonly string[] DictionaryWords = {
|
public static readonly string[] DictionaryWords = {
|
||||||
"tweetdeck", "TweetDeck", "tweetduck", "TweetDuck", "TD"
|
"tweetdeck", "TweetDeck", "tweetduck", "TweetDuck", "TD"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public static readonly string[] ValidImageExtensions = {
|
||||||
|
".jpg", ".jpeg", ".png", ".gif"
|
||||||
|
};
|
||||||
|
|
||||||
|
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/");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsTwitterLogin2FactorWebsite(IFrame frame){
|
||||||
|
return frame.Url.Contains("//twitter.com/account/login_verification");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ExtractMediaBaseLink(string url){
|
||||||
|
int slash = url.LastIndexOf('/');
|
||||||
|
return slash == -1 ? url : StringUtils.ExtractBefore(url, ':', slash);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetMediaLink(string url, ImageQuality quality){
|
||||||
|
if (quality == ImageQuality.Orig){
|
||||||
|
string result = ExtractMediaBaseLink(url);
|
||||||
|
|
||||||
|
if (url.Contains("//ton.twitter.com/") && url.Contains("/ton/data/dm/")){
|
||||||
|
result += ":large";
|
||||||
|
}
|
||||||
|
else if (result != url || url.Contains("//pbs.twimg.com/media/")){
|
||||||
|
result += ":orig";
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetImageFileName(string url){
|
||||||
|
return BrowserUtils.GetFileNameFromUrl(ExtractMediaBaseLink(url));
|
||||||
|
}
|
||||||
|
|
||||||
public static void ViewImage(string url, ImageQuality quality){
|
public static void ViewImage(string url, ImageQuality quality){
|
||||||
static void ViewImageInternal(string path){
|
void ViewImageInternal(string path){
|
||||||
string ext = Path.GetExtension(path);
|
string ext = Path.GetExtension(path);
|
||||||
|
|
||||||
if (ImageUrl.ValidExtensions.Contains(ext)){
|
if (ValidImageExtensions.Contains(ext)){
|
||||||
WindowsUtils.OpenAssociatedProgram(path);
|
WindowsUtils.OpenAssociatedProgram(path);
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
FormMessage.Error("Image Download", "Invalid file extension " + ext, FormMessage.OK);
|
FormMessage.Error("Image Download", "Invalid file extension "+ext, FormMessage.OK);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
string file = Path.Combine(BrowserCache.CacheFolder, TwitterUrls.GetImageFileName(url) ?? Path.GetRandomFileName());
|
string file = Path.Combine(BrowserCache.CacheFolder, GetImageFileName(url) ?? Path.GetRandomFileName());
|
||||||
|
|
||||||
if (FileUtils.FileExistsAndNotEmpty(file)){
|
if (WindowsUtils.FileExistsAndNotEmpty(file)){
|
||||||
ViewImageInternal(file);
|
ViewImageInternal(file);
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
DownloadFileAuth(TwitterUrls.GetMediaLink(url, quality), file, () => {
|
DownloadFileAuth(GetMediaLink(url, quality), file, () => {
|
||||||
ViewImageInternal(file);
|
ViewImageInternal(file);
|
||||||
}, ex => {
|
}, ex => {
|
||||||
FormMessage.Error("Image Download", "An error occurred while downloading the image: " + ex.Message, FormMessage.OK);
|
FormMessage.Error("Image Download", "An error occurred while downloading the image: "+ex.Message, FormMessage.OK);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -59,22 +109,22 @@ namespace TweetDuck.Core.Utils{
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
string firstImageLink = TwitterUrls.GetMediaLink(urls[0], quality);
|
string firstImageLink = GetMediaLink(urls[0], quality);
|
||||||
int qualityIndex = firstImageLink.IndexOf(':', firstImageLink.LastIndexOf('/'));
|
int qualityIndex = firstImageLink.IndexOf(':', firstImageLink.LastIndexOf('/'));
|
||||||
|
|
||||||
string filename = TwitterUrls.GetImageFileName(firstImageLink);
|
string filename = GetImageFileName(firstImageLink);
|
||||||
string ext = Path.GetExtension(filename); // includes dot
|
string ext = Path.GetExtension(filename); // includes dot
|
||||||
|
|
||||||
using(SaveFileDialog dialog = new SaveFileDialog{
|
using(SaveFileDialog dialog = new SaveFileDialog{
|
||||||
AutoUpgradeEnabled = true,
|
AutoUpgradeEnabled = true,
|
||||||
OverwritePrompt = urls.Length == 1,
|
OverwritePrompt = urls.Length == 1,
|
||||||
Title = "Save Image",
|
Title = "Save Image",
|
||||||
FileName = qualityIndex == -1 ? filename : $"{username} {Path.ChangeExtension(filename, null)} {firstImageLink.Substring(qualityIndex + 1)}".Trim() + ext,
|
FileName = qualityIndex == -1 ? filename : $"{username} {Path.ChangeExtension(filename, null)} {firstImageLink.Substring(qualityIndex+1)}".Trim()+ext,
|
||||||
Filter = (urls.Length == 1 ? "Image" : "Images") + (string.IsNullOrEmpty(ext) ? " (unknown)|*.*" : $" (*{ext})|*{ext}")
|
Filter = (urls.Length == 1 ? "Image" : "Images")+(string.IsNullOrEmpty(ext) ? " (unknown)|*.*" : $" (*{ext})|*{ext}")
|
||||||
}){
|
}){
|
||||||
if (dialog.ShowDialog() == DialogResult.OK){
|
if (dialog.ShowDialog() == DialogResult.OK){
|
||||||
static void OnFailure(Exception ex){
|
void OnFailure(Exception ex){
|
||||||
FormMessage.Error("Image Download", "An error occurred while downloading the image: " + ex.Message, FormMessage.OK);
|
FormMessage.Error("Image Download", "An error occurred while downloading the image: "+ex.Message, FormMessage.OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (urls.Length == 1){
|
if (urls.Length == 1){
|
||||||
@@ -85,7 +135,7 @@ namespace TweetDuck.Core.Utils{
|
|||||||
string pathExt = Path.GetExtension(dialog.FileName);
|
string pathExt = Path.GetExtension(dialog.FileName);
|
||||||
|
|
||||||
for(int index = 0; index < urls.Length; index++){
|
for(int index = 0; index < urls.Length; index++){
|
||||||
DownloadFileAuth(TwitterUrls.GetMediaLink(urls[index], quality), $"{pathBase} {index + 1}{pathExt}", null, OnFailure);
|
DownloadFileAuth(GetMediaLink(urls[index], quality), $"{pathBase} {index+1}{pathExt}", null, OnFailure);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -93,7 +143,7 @@ namespace TweetDuck.Core.Utils{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void DownloadVideo(string url, string username){
|
public static void DownloadVideo(string url, string username){
|
||||||
string filename = TwitterUrls.GetFileNameFromUrl(url);
|
string filename = BrowserUtils.GetFileNameFromUrl(url);
|
||||||
string ext = Path.GetExtension(filename);
|
string ext = Path.GetExtension(filename);
|
||||||
|
|
||||||
using(SaveFileDialog dialog = new SaveFileDialog{
|
using(SaveFileDialog dialog = new SaveFileDialog{
|
||||||
@@ -101,11 +151,11 @@ namespace TweetDuck.Core.Utils{
|
|||||||
OverwritePrompt = true,
|
OverwritePrompt = true,
|
||||||
Title = "Save Video",
|
Title = "Save Video",
|
||||||
FileName = string.IsNullOrEmpty(username) ? filename : $"{username} {filename}".TrimStart(),
|
FileName = string.IsNullOrEmpty(username) ? filename : $"{username} {filename}".TrimStart(),
|
||||||
Filter = "Video" + (string.IsNullOrEmpty(ext) ? " (unknown)|*.*" : $" (*{ext})|*{ext}")
|
Filter = "Video"+(string.IsNullOrEmpty(ext) ? " (unknown)|*.*" : $" (*{ext})|*{ext}")
|
||||||
}){
|
}){
|
||||||
if (dialog.ShowDialog() == DialogResult.OK){
|
if (dialog.ShowDialog() == DialogResult.OK){
|
||||||
DownloadFileAuth(url, dialog.FileName, null, ex => {
|
DownloadFileAuth(url, dialog.FileName, null, ex => {
|
||||||
FormMessage.Error("Video Download", "An error occurred while downloading the video: " + ex.Message, FormMessage.OK);
|
FormMessage.Error("Video Download", "An error occurred while downloading the video: "+ex.Message, FormMessage.OK);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -128,10 +178,7 @@ namespace TweetDuck.Core.Utils{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
WebClient client = WebUtils.NewClient(BrowserUtils.UserAgentChrome);
|
BrowserUtils.DownloadFileAsync(url, target, cookieStr, onSuccess, onFailure);
|
||||||
client.Headers[HttpRequestHeader.Cookie] = cookieStr;
|
|
||||||
client.DownloadFileCompleted += WebUtils.FileDownloadCallback(target, onSuccess, onFailure);
|
|
||||||
client.DownloadFileAsync(new Uri(url), target);
|
|
||||||
}, scheduler);
|
}, scheduler);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Net;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
@@ -11,17 +12,66 @@ using Microsoft.Win32;
|
|||||||
|
|
||||||
namespace TweetDuck.Core.Utils{
|
namespace TweetDuck.Core.Utils{
|
||||||
static class WindowsUtils{
|
static class WindowsUtils{
|
||||||
private static readonly bool IsWindows8OrNewer = OSVersionEquals(major: 6, minor: 2); // windows 8/10
|
|
||||||
|
|
||||||
public static bool ShouldAvoidToolWindow { get; } = IsWindows8OrNewer;
|
|
||||||
public static bool IsAeroEnabled => IsWindows8OrNewer || (NativeMethods.DwmIsCompositionEnabled(out bool isCompositionEnabled) == 0 && isCompositionEnabled);
|
|
||||||
|
|
||||||
private static readonly Lazy<Regex> RegexStripHtmlStyles = new Lazy<Regex>(() => new Regex(@"\s?(?:style|class)="".*?"""), false);
|
private static readonly Lazy<Regex> RegexStripHtmlStyles = new Lazy<Regex>(() => new Regex(@"\s?(?:style|class)="".*?"""), false);
|
||||||
private static readonly Lazy<Regex> RegexOffsetClipboardHtml = new Lazy<Regex>(() => new Regex(@"(?<=EndHTML:|EndFragment:)(\d+)"), false);
|
private static readonly Lazy<Regex> RegexOffsetClipboardHtml = new Lazy<Regex>(() => new Regex(@"(?<=EndHTML:|EndFragment:)(\d+)"), false);
|
||||||
|
|
||||||
private static bool OSVersionEquals(int major, int minor){
|
private static readonly bool IsWindows8OrNewer;
|
||||||
|
private static bool HasMicrosoftBeenBroughtTo2008Yet;
|
||||||
|
|
||||||
|
public static int CurrentProcessID { get; }
|
||||||
|
public static bool ShouldAvoidToolWindow { get; }
|
||||||
|
public static bool IsAeroEnabled => IsWindows8OrNewer || (NativeMethods.DwmIsCompositionEnabled(out bool isCompositionEnabled) == 0 && isCompositionEnabled);
|
||||||
|
|
||||||
|
static WindowsUtils(){
|
||||||
|
using(Process me = Process.GetCurrentProcess()){
|
||||||
|
CurrentProcessID = me.Id;
|
||||||
|
}
|
||||||
|
|
||||||
Version ver = Environment.OSVersion.Version;
|
Version ver = Environment.OSVersion.Version;
|
||||||
return ver.Major == major && ver.Minor == minor;
|
IsWindows8OrNewer = ver.Major == 6 && ver.Minor == 2; // windows 8/10
|
||||||
|
|
||||||
|
ShouldAvoidToolWindow = IsWindows8OrNewer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void EnsureTLS12(){
|
||||||
|
if (!HasMicrosoftBeenBroughtTo2008Yet){
|
||||||
|
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;
|
||||||
|
ServicePointManager.SecurityProtocol &= ~(SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls | SecurityProtocolType.Tls11);
|
||||||
|
HasMicrosoftBeenBroughtTo2008Yet = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void CreateDirectoryForFile(string file){
|
||||||
|
string dir = Path.GetDirectoryName(file);
|
||||||
|
|
||||||
|
if (dir == null){
|
||||||
|
throw new ArgumentException("Invalid file path: "+file);
|
||||||
|
}
|
||||||
|
else if (dir.Length > 0){
|
||||||
|
Directory.CreateDirectory(dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool CheckFolderWritePermission(string path){
|
||||||
|
string testFile = Path.Combine(path, ".test");
|
||||||
|
|
||||||
|
try{
|
||||||
|
Directory.CreateDirectory(path);
|
||||||
|
|
||||||
|
using(File.Create(testFile)){}
|
||||||
|
File.Delete(testFile);
|
||||||
|
return true;
|
||||||
|
}catch{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool FileExistsAndNotEmpty(string path){
|
||||||
|
try{
|
||||||
|
return new FileInfo(path).Length > 0;
|
||||||
|
}catch{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool OpenAssociatedProgram(string file, string arguments = "", bool runElevated = false){
|
public static bool OpenAssociatedProgram(string file, string arguments = "", bool runElevated = false){
|
||||||
@@ -37,7 +87,7 @@ namespace TweetDuck.Core.Utils{
|
|||||||
}catch(Win32Exception e) when (e.NativeErrorCode == 0x000004C7){ // operation canceled by the user
|
}catch(Win32Exception e) when (e.NativeErrorCode == 0x000004C7){ // operation canceled by the user
|
||||||
return false;
|
return false;
|
||||||
}catch(Exception e){
|
}catch(Exception e){
|
||||||
Program.Reporter.HandleException("Error Opening Program", "Could not open the associated program for " + file, true, e);
|
Program.Reporter.HandleException("Error Opening Program", "Could not open the associated program for "+file, true, e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -79,8 +129,8 @@ namespace TweetDuck.Core.Utils{
|
|||||||
|
|
||||||
string updatedHtml = RegexStripHtmlStyles.Value.Replace(originalHtml, string.Empty);
|
string updatedHtml = RegexStripHtmlStyles.Value.Replace(originalHtml, string.Empty);
|
||||||
|
|
||||||
int removed = originalHtml.Length - updatedHtml.Length;
|
int removed = originalHtml.Length-updatedHtml.Length;
|
||||||
updatedHtml = RegexOffsetClipboardHtml.Value.Replace(updatedHtml, match => (int.Parse(match.Value) - removed).ToString().PadLeft(match.Value.Length, '0'));
|
updatedHtml = RegexOffsetClipboardHtml.Value.Replace(updatedHtml, match => (int.Parse(match.Value)-removed).ToString().PadLeft(match.Value.Length, '0'));
|
||||||
|
|
||||||
DataObject obj = new DataObject();
|
DataObject obj = new DataObject();
|
||||||
obj.SetText(originalText, TextDataFormat.UnicodeText);
|
obj.SetText(originalText, TextDataFormat.UnicodeText);
|
||||||
@@ -107,34 +157,34 @@ namespace TweetDuck.Core.Utils{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static IEnumerable<Browser> FindInstalledBrowsers(){
|
public static IEnumerable<Browser> FindInstalledBrowsers(){
|
||||||
static IEnumerable<Browser> ReadBrowsersFromKey(RegistryHive hive){
|
IEnumerable<Browser> ReadBrowsersFromKey(RegistryHive hive){
|
||||||
using RegistryKey root = RegistryKey.OpenBaseKey(hive, RegistryView.Default);
|
using(RegistryKey root = RegistryKey.OpenBaseKey(hive, RegistryView.Default))
|
||||||
using RegistryKey browserList = root.OpenSubKey(@"SOFTWARE\Clients\StartMenuInternet", false);
|
using(RegistryKey browserList = root.OpenSubKey(@"SOFTWARE\Clients\StartMenuInternet", false)){
|
||||||
|
if (browserList == null){
|
||||||
if (browserList == null){
|
yield break;
|
||||||
yield break;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach(string sub in browserList.GetSubKeyNames()){
|
|
||||||
using RegistryKey browserKey = browserList.OpenSubKey(sub, false);
|
|
||||||
using RegistryKey shellKey = browserKey?.OpenSubKey(@"shell\open\command");
|
|
||||||
|
|
||||||
if (shellKey == null){
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
string browserName = browserKey.GetValue(null) as string;
|
foreach(string sub in browserList.GetSubKeyNames()){
|
||||||
string browserPath = shellKey.GetValue(null) as string;
|
using(RegistryKey browserKey = browserList.OpenSubKey(sub, false))
|
||||||
|
using(RegistryKey shellKey = browserKey?.OpenSubKey(@"shell\open\command")){
|
||||||
|
if (shellKey == null){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(browserName) || string.IsNullOrEmpty(browserPath)){
|
string browserName = browserKey.GetValue(null) as string;
|
||||||
continue;
|
string browserPath = shellKey.GetValue(null) as string;
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(browserName) || string.IsNullOrEmpty(browserPath)){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (browserPath[0] == '"' && browserPath[browserPath.Length-1] == '"'){
|
||||||
|
browserPath = browserPath.Substring(1, browserPath.Length-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
yield return new Browser(browserName, browserPath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (browserPath[0] == '"' && browserPath[browserPath.Length - 1] == '"'){
|
|
||||||
browserPath = browserPath.Substring(1, browserPath.Length - 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
yield return new Browser(browserName, browserPath);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using TweetLib.Core.Utils;
|
using TweetDuck.Core.Utils;
|
||||||
|
|
||||||
namespace TweetLib.Core.Data{
|
namespace TweetDuck.Data{
|
||||||
public sealed class CombinedFileStream : IDisposable{
|
sealed class CombinedFileStream : IDisposable{
|
||||||
private const char KeySeparator = '|';
|
public const char KeySeparator = '|';
|
||||||
|
|
||||||
private readonly Stream stream;
|
private readonly Stream stream;
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ namespace TweetLib.Core.Data{
|
|||||||
byte[] name = Encoding.UTF8.GetBytes(identifier);
|
byte[] name = Encoding.UTF8.GetBytes(identifier);
|
||||||
|
|
||||||
if (name.Length > 255){
|
if (name.Length > 255){
|
||||||
throw new ArgumentOutOfRangeException("Identifier cannot be 256 or more characters long: " + identifier);
|
throw new ArgumentOutOfRangeException("Identifier cannot be 256 or more characters long: "+identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] contents;
|
byte[] contents;
|
||||||
@@ -45,7 +45,7 @@ namespace TweetLib.Core.Data{
|
|||||||
stream.Write(contents, 0, contents.Length);
|
stream.Write(contents, 0, contents.Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Entry? ReadFile(){
|
public Entry ReadFile(){
|
||||||
int nameLength = stream.ReadByte();
|
int nameLength = stream.ReadByte();
|
||||||
|
|
||||||
if (nameLength == -1){
|
if (nameLength == -1){
|
||||||
@@ -64,7 +64,7 @@ namespace TweetLib.Core.Data{
|
|||||||
return new Entry(Encoding.UTF8.GetString(name), contents);
|
return new Entry(Encoding.UTF8.GetString(name), contents);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string? SkipFile(){
|
public string SkipFile(){
|
||||||
int nameLength = stream.ReadByte();
|
int nameLength = stream.ReadByte();
|
||||||
|
|
||||||
if (nameLength == -1){
|
if (nameLength == -1){
|
||||||
@@ -103,7 +103,7 @@ namespace TweetLib.Core.Data{
|
|||||||
public string[] KeyValue{
|
public string[] KeyValue{
|
||||||
get{
|
get{
|
||||||
int index = Identifier.IndexOf(KeySeparator);
|
int index = Identifier.IndexOf(KeySeparator);
|
||||||
return index == -1 ? StringUtils.EmptyArray : Identifier.Substring(index + 1).Split(KeySeparator);
|
return index == -1 ? StringUtils.EmptyArray : Identifier.Substring(index+1).Split(KeySeparator);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,7 +120,7 @@ namespace TweetLib.Core.Data{
|
|||||||
|
|
||||||
public void WriteToFile(string path, bool createDirectory){
|
public void WriteToFile(string path, bool createDirectory){
|
||||||
if (createDirectory){
|
if (createDirectory){
|
||||||
FileUtils.CreateDirectoryForFile(path);
|
WindowsUtils.CreateDirectoryForFile(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
File.WriteAllBytes(path, contents);
|
File.WriteAllBytes(path, contents);
|
@@ -2,8 +2,8 @@
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace TweetLib.Core.Collections{
|
namespace TweetDuck.Data{
|
||||||
public sealed class CommandLineArgs{
|
sealed class CommandLineArgs{
|
||||||
public static CommandLineArgs FromStringArray(char entryChar, string[] array){
|
public static CommandLineArgs FromStringArray(char entryChar, string[] array){
|
||||||
CommandLineArgs args = new CommandLineArgs();
|
CommandLineArgs args = new CommandLineArgs();
|
||||||
ReadStringArray(entryChar, array, args);
|
ReadStringArray(entryChar, array, args);
|
||||||
@@ -15,8 +15,8 @@ namespace TweetLib.Core.Collections{
|
|||||||
string entry = array[index];
|
string entry = array[index];
|
||||||
|
|
||||||
if (entry.Length > 0 && entry[0] == entryChar){
|
if (entry.Length > 0 && entry[0] == entryChar){
|
||||||
if (index < array.Length - 1){
|
if (index < array.Length-1){
|
||||||
string potentialValue = array[index + 1];
|
string potentialValue = array[index+1];
|
||||||
|
|
||||||
if (potentialValue.Length > 0 && potentialValue[0] == entryChar){
|
if (potentialValue.Length > 0 && potentialValue[0] == entryChar){
|
||||||
targetArgs.AddFlag(entry);
|
targetArgs.AddFlag(entry);
|
||||||
@@ -52,7 +52,7 @@ namespace TweetLib.Core.Collections{
|
|||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
key = matchValue.Substring(0, indexEquals).TrimStart('-');
|
key = matchValue.Substring(0, indexEquals).TrimStart('-');
|
||||||
value = matchValue.Substring(indexEquals + 1).Trim('"');
|
value = matchValue.Substring(indexEquals+1).Trim('"');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key.Length != 0){
|
if (key.Length != 0){
|
||||||
@@ -66,7 +66,7 @@ namespace TweetLib.Core.Collections{
|
|||||||
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>();
|
||||||
|
|
||||||
public int Count => flags.Count + values.Count;
|
public int Count => flags.Count+values.Count;
|
||||||
|
|
||||||
public void AddFlag(string flag){
|
public void AddFlag(string flag){
|
||||||
flags.Add(flag.ToLower());
|
flags.Add(flag.ToLower());
|
||||||
@@ -84,8 +84,12 @@ namespace TweetLib.Core.Collections{
|
|||||||
values[key.ToLower()] = value;
|
values[key.ToLower()] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string? GetValue(string key){
|
public bool HasValue(string key){
|
||||||
return values.TryGetValue(key.ToLower(), out string val) ? val : null;
|
return values.ContainsKey(key.ToLower());
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetValue(string key, string defaultValue){
|
||||||
|
return values.TryGetValue(key.ToLower(), out string val) ? val : defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RemoveValue(string key){
|
public void RemoveValue(string key){
|
||||||
@@ -99,7 +103,7 @@ namespace TweetLib.Core.Collections{
|
|||||||
copy.AddFlag(flag);
|
copy.AddFlag(flag);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach(var kvp in values){
|
foreach(KeyValuePair<string, string> kvp in values){
|
||||||
copy.SetValue(kvp.Key, kvp.Value);
|
copy.SetValue(kvp.Key, kvp.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,7 +115,7 @@ namespace TweetLib.Core.Collections{
|
|||||||
target[flag] = "1";
|
target[flag] = "1";
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach(var kvp in values){
|
foreach(KeyValuePair<string, string> kvp in values){
|
||||||
target[kvp.Key] = kvp.Value;
|
target[kvp.Key] = kvp.Value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -123,11 +127,11 @@ namespace TweetLib.Core.Collections{
|
|||||||
build.Append(flag).Append(' ');
|
build.Append(flag).Append(' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach(var kvp in values){
|
foreach(KeyValuePair<string, string> kvp in values){
|
||||||
build.Append(kvp.Key).Append(" \"").Append(kvp.Value).Append("\" ");
|
build.Append(kvp.Key).Append(" \"").Append(kvp.Value).Append("\" ");
|
||||||
}
|
}
|
||||||
|
|
||||||
return build.Length == 0 ? string.Empty : build.Remove(build.Length - 1, 1).ToString();
|
return build.Length == 0 ? string.Empty : build.Remove(build.Length-1, 1).ToString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,7 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace TweetLib.Core.Data{
|
namespace TweetDuck.Data{
|
||||||
public sealed class InjectedHTML{
|
sealed class InjectedHTML{
|
||||||
public enum Position{
|
public enum Position{
|
||||||
Before, After
|
Before, After
|
||||||
}
|
}
|
||||||
@@ -27,7 +27,7 @@ namespace TweetLib.Core.Data{
|
|||||||
|
|
||||||
switch(position){
|
switch(position){
|
||||||
case Position.Before: cutIndex = index; break;
|
case Position.Before: cutIndex = index; break;
|
||||||
case Position.After: cutIndex = index + search.Length; break;
|
case Position.After: cutIndex = index+search.Length; break;
|
||||||
default: return targetHTML;
|
default: return targetHTML;
|
||||||
}
|
}
|
||||||
|
|
@@ -1,14 +1,14 @@
|
|||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace TweetLib.Core.Data{
|
namespace TweetDuck.Data{
|
||||||
public sealed class Result<T>{
|
sealed class Result<T>{
|
||||||
public bool HasValue => exception == null;
|
public bool HasValue => exception == null;
|
||||||
|
|
||||||
public T Value => HasValue ? value : throw new InvalidOperationException("Requested value from a failed result.");
|
public T Value => HasValue ? value : throw new InvalidOperationException("Requested value from a failed result.");
|
||||||
public Exception Exception => exception ?? throw new InvalidOperationException("Requested exception from a successful result.");
|
public Exception Exception => exception ?? throw new InvalidOperationException("Requested exception from a successful result.");
|
||||||
|
|
||||||
private readonly T value;
|
private readonly T value;
|
||||||
private readonly Exception? exception;
|
private readonly Exception exception;
|
||||||
|
|
||||||
public Result(T value){
|
public Result(T value){
|
||||||
this.value = value;
|
this.value = value;
|
||||||
@@ -16,7 +16,7 @@ namespace TweetLib.Core.Data{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Result(Exception exception){
|
public Result(Exception exception){
|
||||||
this.value = default!;
|
this.value = default(T);
|
||||||
this.exception = exception ?? throw new ArgumentNullException(nameof(exception));
|
this.exception = exception ?? throw new ArgumentNullException(nameof(exception));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,12 +25,12 @@ namespace TweetLib.Core.Data{
|
|||||||
onSuccess(value);
|
onSuccess(value);
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
onException(exception!);
|
onException(exception);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Result<R> Select<R>(Func<T, R> map){
|
public Result<R> Select<R>(Func<T, R> map){
|
||||||
return HasValue ? new Result<R>(map(value)) : new Result<R>(exception!);
|
return HasValue ? new Result<R>(map(value)) : new Result<R>(exception);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -4,11 +4,10 @@ using System.IO;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using TweetLib.Core.Serialization.Converters;
|
using TweetDuck.Core.Utils;
|
||||||
using TweetLib.Core.Utils;
|
|
||||||
|
|
||||||
namespace TweetLib.Core.Serialization{
|
namespace TweetDuck.Data.Serialization{
|
||||||
public sealed class FileSerializer<T>{
|
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";
|
||||||
|
|
||||||
@@ -24,25 +23,25 @@ namespace TweetLib.Core.Serialization{
|
|||||||
while(true){
|
while(true){
|
||||||
int nextIndex = data.IndexOf('\\', index);
|
int nextIndex = data.IndexOf('\\', index);
|
||||||
|
|
||||||
if (nextIndex == -1 || nextIndex + 1 >= data.Length){
|
if (nextIndex == -1 || nextIndex+1 >= data.Length){
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
build.Append(data.Substring(index, nextIndex - index));
|
build.Append(data.Substring(index, nextIndex-index));
|
||||||
|
|
||||||
char next = data[nextIndex + 1];
|
char next = data[nextIndex+1];
|
||||||
|
|
||||||
if (next == '\\'){ // convert double backslash to single backslash
|
if (next == '\\'){ // convert double backslash to single backslash
|
||||||
build.Append('\\');
|
build.Append('\\');
|
||||||
index = nextIndex + 2;
|
index = nextIndex+2;
|
||||||
}
|
}
|
||||||
else if (next == '\r' && nextIndex + 2 < data.Length && data[nextIndex + 2] == '\n'){ // convert backslash followed by CRLF to custom new line
|
else if (next == '\r' && nextIndex+2 < data.Length && data[nextIndex+2] == '\n'){ // convert backslash followed by CRLF to custom new line
|
||||||
build.Append(NewLineCustom);
|
build.Append(NewLineCustom);
|
||||||
index = nextIndex + 3;
|
index = nextIndex+3;
|
||||||
}
|
}
|
||||||
else{ // single backslash
|
else{ // single backslash
|
||||||
build.Append('\\');
|
build.Append('\\');
|
||||||
index = nextIndex + 1;
|
index = nextIndex+1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -50,6 +49,8 @@ namespace TweetLib.Core.Serialization{
|
|||||||
return build.Append(data.Substring(index)).ToString();
|
return build.Append(data.Substring(index)).ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static readonly ITypeConverter BasicSerializerObj = new BasicTypeConverter();
|
||||||
|
|
||||||
private readonly Dictionary<string, PropertyInfo> props;
|
private readonly Dictionary<string, PropertyInfo> props;
|
||||||
private readonly Dictionary<Type, ITypeConverter> converters;
|
private readonly Dictionary<Type, ITypeConverter> converters;
|
||||||
|
|
||||||
@@ -65,7 +66,7 @@ namespace TweetLib.Core.Serialization{
|
|||||||
public void Write(string file, T obj){
|
public void Write(string file, T obj){
|
||||||
LinkedList<string> errors = new LinkedList<string>();
|
LinkedList<string> errors = new LinkedList<string>();
|
||||||
|
|
||||||
FileUtils.CreateDirectoryForFile(file);
|
WindowsUtils.CreateDirectoryForFile(file);
|
||||||
|
|
||||||
using(StreamWriter writer = new StreamWriter(new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None))){
|
using(StreamWriter writer = new StreamWriter(new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None))){
|
||||||
foreach(KeyValuePair<string, PropertyInfo> prop in props){
|
foreach(KeyValuePair<string, PropertyInfo> prop in props){
|
||||||
@@ -73,10 +74,10 @@ namespace TweetLib.Core.Serialization{
|
|||||||
object value = prop.Value.GetValue(obj);
|
object value = prop.Value.GetValue(obj);
|
||||||
|
|
||||||
if (!converters.TryGetValue(type, out ITypeConverter serializer)){
|
if (!converters.TryGetValue(type, out ITypeConverter serializer)){
|
||||||
serializer = ClrTypeConverter.Instance;
|
serializer = BasicSerializerObj;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (serializer.TryWriteType(type, value, out string? converted)){
|
if (serializer.TryWriteType(type, value, out string converted)){
|
||||||
if (converted != null){
|
if (converted != null){
|
||||||
writer.Write(prop.Key);
|
writer.Write(prop.Key);
|
||||||
writer.Write(' ');
|
writer.Write(' ');
|
||||||
@@ -125,8 +126,8 @@ namespace TweetLib.Core.Serialization{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
line = contents.Substring(currentPos, nextPos - currentPos);
|
line = contents.Substring(currentPos, nextPos-currentPos);
|
||||||
currentPos = nextPos + NewLineReal.Length;
|
currentPos = nextPos+NewLineReal.Length;
|
||||||
}
|
}
|
||||||
|
|
||||||
int space = line.IndexOf(' ');
|
int space = line.IndexOf(' ');
|
||||||
@@ -137,14 +138,14 @@ namespace TweetLib.Core.Serialization{
|
|||||||
}
|
}
|
||||||
|
|
||||||
string property = line.Substring(0, space);
|
string property = line.Substring(0, space);
|
||||||
string value = UnescapeLine(line.Substring(space + 1));
|
string value = UnescapeLine(line.Substring(space+1));
|
||||||
|
|
||||||
if (props.TryGetValue(property, out PropertyInfo info)){
|
if (props.TryGetValue(property, out PropertyInfo info)){
|
||||||
if (!converters.TryGetValue(info.PropertyType, out ITypeConverter serializer)){
|
if (!converters.TryGetValue(info.PropertyType, out ITypeConverter serializer)){
|
||||||
serializer = ClrTypeConverter.Instance;
|
serializer = BasicSerializerObj;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (serializer.TryReadType(info.PropertyType, value, out object? converted)){
|
if (serializer.TryReadType(info.PropertyType, value, out object converted)){
|
||||||
info.SetValue(obj, converted);
|
info.SetValue(obj, converted);
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
@@ -164,5 +165,53 @@ namespace TweetLib.Core.Serialization{
|
|||||||
}catch(FileNotFoundException){
|
}catch(FileNotFoundException){
|
||||||
}catch(DirectoryNotFoundException){}
|
}catch(DirectoryNotFoundException){}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private sealed class BasicTypeConverter : ITypeConverter{
|
||||||
|
bool ITypeConverter.TryWriteType(Type type, object value, out string converted){
|
||||||
|
switch(Type.GetTypeCode(type)){
|
||||||
|
case TypeCode.Boolean:
|
||||||
|
converted = value.ToString();
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case TypeCode.Int32:
|
||||||
|
converted = ((int)value).ToString(); // cast required for enums
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case TypeCode.String:
|
||||||
|
converted = value?.ToString();
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
converted = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ITypeConverter.TryReadType(Type type, string value, out object converted){
|
||||||
|
switch(Type.GetTypeCode(type)){
|
||||||
|
case TypeCode.Boolean:
|
||||||
|
if (bool.TryParse(value, out bool b)){
|
||||||
|
converted = b;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else goto default;
|
||||||
|
|
||||||
|
case TypeCode.Int32:
|
||||||
|
if (int.TryParse(value, out int i)){
|
||||||
|
converted = i;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else goto default;
|
||||||
|
|
||||||
|
case TypeCode.String:
|
||||||
|
converted = value;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
converted = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
8
Data/Serialization/ITypeConverter.cs
Normal file
8
Data/Serialization/ITypeConverter.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace TweetDuck.Data.Serialization{
|
||||||
|
interface ITypeConverter{
|
||||||
|
bool TryWriteType(Type type, object value, out string converted);
|
||||||
|
bool TryReadType(Type type, string value, out object converted);
|
||||||
|
}
|
||||||
|
}
|
@@ -1,8 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace TweetLib.Core.Serialization{
|
namespace TweetDuck.Data.Serialization{
|
||||||
public sealed class SerializationSoftException : Exception{
|
sealed class SerializationSoftException : Exception{
|
||||||
public IList<string> Errors { get; }
|
public IList<string> Errors { get; }
|
||||||
|
|
||||||
public SerializationSoftException(IList<string> errors) : base(string.Join(Environment.NewLine, errors)){
|
public SerializationSoftException(IList<string> errors) : base(string.Join(Environment.NewLine, errors)){
|
@@ -1,11 +1,11 @@
|
|||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace TweetLib.Core.Serialization.Converters{
|
namespace TweetDuck.Data.Serialization{
|
||||||
public sealed class SingleTypeConverter<T> : ITypeConverter{
|
sealed class SingleTypeConverter<T> : ITypeConverter{
|
||||||
public Func<T, string> ConvertToString { get; set; }
|
public Func<T, string> ConvertToString { get; set; }
|
||||||
public Func<string, T> ConvertToObject { get; set; }
|
public Func<string, 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{
|
||||||
converted = ConvertToString((T)value);
|
converted = ConvertToString((T)value);
|
||||||
return true;
|
return true;
|
||||||
@@ -15,7 +15,7 @@ namespace TweetLib.Core.Serialization.Converters{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ITypeConverter.TryReadType(Type type, string value, out object? converted){
|
bool ITypeConverter.TryReadType(Type type, string value, out object converted){
|
||||||
try{
|
try{
|
||||||
converted = ConvertToObject(value);
|
converted = ConvertToObject(value);
|
||||||
return true;
|
return true;
|
@@ -1,8 +1,8 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
namespace TweetLib.Core.Collections{
|
namespace TweetDuck.Data{
|
||||||
public sealed class TwoKeyDictionary<K1, K2, V>{
|
sealed class TwoKeyDictionary<K1, K2, V>{
|
||||||
private readonly Dictionary<K1, Dictionary<K2, V>> dict;
|
private readonly Dictionary<K1, Dictionary<K2, V>> dict;
|
||||||
private readonly int innerCapacity;
|
private readonly int innerCapacity;
|
||||||
|
|
||||||
@@ -85,8 +85,7 @@ namespace TweetLib.Core.Collections{
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
else return false;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TryGetValue(K1 outerKey, K2 innerKey, out V value){
|
public bool TryGetValue(K1 outerKey, K2 innerKey, out V value){
|
||||||
@@ -94,7 +93,7 @@ namespace TweetLib.Core.Collections{
|
|||||||
return innerDict.TryGetValue(innerKey, out value);
|
return innerDict.TryGetValue(innerKey, out value);
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
value = default!;
|
value = default(V);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,8 +1,8 @@
|
|||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using TweetDuck.Core.Controls;
|
using TweetDuck.Core.Controls;
|
||||||
using TweetLib.Core.Serialization.Converters;
|
using TweetDuck.Core.Utils;
|
||||||
using TweetLib.Core.Utils;
|
using TweetDuck.Data.Serialization;
|
||||||
|
|
||||||
namespace TweetDuck.Data{
|
namespace TweetDuck.Data{
|
||||||
sealed class WindowState{
|
sealed class WindowState{
|
||||||
|
@@ -1,58 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using TweetDuck.Core.Utils;
|
|
||||||
using TweetLib.Core.Application;
|
|
||||||
|
|
||||||
namespace TweetDuck.Impl{
|
|
||||||
class LockHandler : IAppLockHandler{
|
|
||||||
private const int WaitRetryDelay = 250;
|
|
||||||
private const int RestoreFailTimeout = 2000;
|
|
||||||
private const int CloseNaturallyTimeout = 10000;
|
|
||||||
private const int CloseKillTimeout = 5000;
|
|
||||||
|
|
||||||
bool IAppLockHandler.RestoreProcess(Process process){
|
|
||||||
if (process.MainWindowHandle == IntPtr.Zero){ // restore if the original process is in tray
|
|
||||||
NativeMethods.BroadcastMessage(Program.WindowRestoreMessage, (uint)process.Id, 0);
|
|
||||||
|
|
||||||
if (WindowsUtils.TrySleepUntil(() => CheckProcessExited(process) || (process.MainWindowHandle != IntPtr.Zero && process.Responding), RestoreFailTimeout, WaitRetryDelay)){
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IAppLockHandler.CloseProcess(Process process){
|
|
||||||
try{
|
|
||||||
if (process.CloseMainWindow()){
|
|
||||||
// ReSharper disable once AccessToDisposedClosure
|
|
||||||
WindowsUtils.TrySleepUntil(() => CheckProcessExited(process), CloseNaturallyTimeout, WaitRetryDelay);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!process.HasExited){
|
|
||||||
process.Kill();
|
|
||||||
// ReSharper disable once AccessToDisposedClosure
|
|
||||||
WindowsUtils.TrySleepUntil(() => CheckProcessExited(process), CloseKillTimeout, WaitRetryDelay);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (process.HasExited){
|
|
||||||
process.Dispose();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}catch(Exception ex) when (ex is InvalidOperationException || ex is Win32Exception){
|
|
||||||
bool hasExited = CheckProcessExited(process);
|
|
||||||
process.Dispose();
|
|
||||||
return hasExited;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool CheckProcessExited(Process process){
|
|
||||||
process.Refresh();
|
|
||||||
return process.HasExited;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,16 +0,0 @@
|
|||||||
using System.Diagnostics;
|
|
||||||
using System.IO;
|
|
||||||
using TweetLib.Core.Application;
|
|
||||||
|
|
||||||
namespace TweetDuck.Impl{
|
|
||||||
class SystemHandler : IAppSystemHandler{
|
|
||||||
void IAppSystemHandler.OpenFileExplorer(string path){
|
|
||||||
if (File.Exists(path)){
|
|
||||||
using(Process.Start("explorer.exe", "/select,\"" + path.Replace('/', '\\') + "\"")){}
|
|
||||||
}
|
|
||||||
else if (Directory.Exists(path)){
|
|
||||||
using(Process.Start("explorer.exe", '"' + path.Replace('/', '\\') + '"')){}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,4 +1,4 @@
|
|||||||
namespace TweetDuck.Plugins {
|
namespace TweetDuck.Plugins.Controls {
|
||||||
partial class PluginControl {
|
partial class PluginControl {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Required designer variable.
|
/// Required designer variable.
|
@@ -3,10 +3,9 @@ using System.Drawing;
|
|||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using TweetDuck.Core.Controls;
|
using TweetDuck.Core.Controls;
|
||||||
using TweetDuck.Core.Utils;
|
using TweetDuck.Core.Utils;
|
||||||
using TweetLib.Core.Features.Plugins;
|
using TweetDuck.Plugins.Enums;
|
||||||
using TweetLib.Core.Features.Plugins.Enums;
|
|
||||||
|
|
||||||
namespace TweetDuck.Plugins{
|
namespace TweetDuck.Plugins.Controls{
|
||||||
sealed partial class PluginControl : UserControl{
|
sealed partial class PluginControl : UserControl{
|
||||||
private readonly PluginManager pluginManager;
|
private readonly PluginManager pluginManager;
|
||||||
private readonly Plugin plugin;
|
private readonly Plugin plugin;
|
||||||
@@ -27,7 +26,7 @@ namespace TweetDuck.Plugins{
|
|||||||
float dpiScale = this.GetDPIScale();
|
float dpiScale = this.GetDPIScale();
|
||||||
|
|
||||||
if (dpiScale > 1F){
|
if (dpiScale > 1F){
|
||||||
Size = MaximumSize = new Size(MaximumSize.Width, MaximumSize.Height + 3);
|
Size = MaximumSize = new Size(MaximumSize.Width, MaximumSize.Height+3);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.labelName.Text = plugin.Name;
|
this.labelName.Text = plugin.Name;
|
||||||
@@ -56,19 +55,19 @@ namespace TweetDuck.Plugins{
|
|||||||
private void panelDescription_Resize(object sender, EventArgs e){
|
private void panelDescription_Resize(object sender, EventArgs e){
|
||||||
SuspendLayout();
|
SuspendLayout();
|
||||||
|
|
||||||
int maxWidth = panelDescription.Width - (panelDescription.VerticalScroll.Visible ? SystemInformation.VerticalScrollBarWidth : 0);
|
int maxWidth = panelDescription.Width-(panelDescription.VerticalScroll.Visible ? SystemInformation.VerticalScrollBarWidth : 0);
|
||||||
labelDescription.MaximumSize = new Size(maxWidth, int.MaxValue);
|
labelDescription.MaximumSize = new Size(maxWidth, int.MaxValue);
|
||||||
|
|
||||||
Font font = labelDescription.Font;
|
Font font = labelDescription.Font;
|
||||||
int descriptionLines = TextRenderer.MeasureText(labelDescription.Text, font, new Size(maxWidth, int.MaxValue), TextFormatFlags.WordBreak).Height / (font.Height - 1);
|
int descriptionLines = TextRenderer.MeasureText(labelDescription.Text, font, new Size(maxWidth, int.MaxValue), TextFormatFlags.WordBreak).Height/(font.Height-1);
|
||||||
|
|
||||||
int requiredLines = Math.Max(descriptionLines, 1 + (string.IsNullOrEmpty(labelVersion.Text) ? 0 : 1) + (isConfigurable ? 1 : 0));
|
int requiredLines = Math.Max(descriptionLines, 1+(string.IsNullOrEmpty(labelVersion.Text) ? 0 : 1)+(isConfigurable ? 1 : 0));
|
||||||
|
|
||||||
nextHeight = requiredLines switch{
|
switch(requiredLines){
|
||||||
1 => MaximumSize.Height - 2 * (font.Height - 1),
|
case 1: nextHeight = MaximumSize.Height-2*(font.Height-1); break;
|
||||||
2 => MaximumSize.Height - 1 * (font.Height - 1),
|
case 2: nextHeight = MaximumSize.Height-(font.Height-1); break;
|
||||||
_ => MaximumSize.Height
|
default: nextHeight = MaximumSize.Height; break;
|
||||||
};
|
}
|
||||||
|
|
||||||
if (nextHeight != Height){
|
if (nextHeight != Height){
|
||||||
timerLayout.Start();
|
timerLayout.Start();
|
@@ -1,8 +1,13 @@
|
|||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using TweetDuck.Core.Utils;
|
using TweetDuck.Core.Utils;
|
||||||
|
|
||||||
namespace TweetDuck.Core.Controls{
|
namespace TweetDuck.Plugins.Controls{
|
||||||
sealed class FlowLayoutPanelNoHScroll : FlowLayoutPanel{
|
sealed class PluginListFlowLayout : FlowLayoutPanel{
|
||||||
|
public PluginListFlowLayout(){
|
||||||
|
FlowDirection = FlowDirection.TopDown;
|
||||||
|
WrapContents = false;
|
||||||
|
}
|
||||||
|
|
||||||
protected override void WndProc(ref Message m){
|
protected override void WndProc(ref Message m){
|
||||||
if (m.Msg == 0x85){ // WM_NCPAINT
|
if (m.Msg == 0x85){ // WM_NCPAINT
|
||||||
NativeMethods.ShowScrollBar(Handle, NativeMethods.SB_HORZ, false); // basically fuck the horizontal scrollbar very much
|
NativeMethods.ShowScrollBar(Handle, NativeMethods.SB_HORZ, false); // basically fuck the horizontal scrollbar very much
|
88
Plugins/Enums/PluginEnvironment.cs
Normal file
88
Plugins/Enums/PluginEnvironment.cs
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace TweetDuck.Plugins.Enums{
|
||||||
|
[Flags]
|
||||||
|
enum PluginEnvironment{
|
||||||
|
None = 0,
|
||||||
|
Browser = 1,
|
||||||
|
Notification = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
static class PluginEnvironmentExtensions{
|
||||||
|
public static IEnumerable<PluginEnvironment> Values{
|
||||||
|
get{
|
||||||
|
yield return PluginEnvironment.Browser;
|
||||||
|
yield return PluginEnvironment.Notification;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IncludesDisabledPlugins(this PluginEnvironment environment){
|
||||||
|
return environment == PluginEnvironment.Browser;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetPluginScriptFile(this PluginEnvironment environment){
|
||||||
|
switch(environment){
|
||||||
|
case PluginEnvironment.Browser: return "browser.js";
|
||||||
|
case PluginEnvironment.Notification: return "notification.js";
|
||||||
|
default: return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetPluginScriptVariables(this PluginEnvironment environment){
|
||||||
|
switch(environment){
|
||||||
|
case PluginEnvironment.Browser: return "$,$TD,$TDP,TD";
|
||||||
|
case PluginEnvironment.Notification: return "$TD,$TDP";
|
||||||
|
default: return string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IReadOnlyDictionary<PluginEnvironment, T> Map<T>(T forNone, T forBrowser, T forNotification){
|
||||||
|
return new PluginEnvironmentDictionary<T>(forNone, forBrowser, forNotification);
|
||||||
|
}
|
||||||
|
|
||||||
|
[SuppressMessage("ReSharper", "MemberHidesStaticFromOuterClass")]
|
||||||
|
private sealed class PluginEnvironmentDictionary<T> : IReadOnlyDictionary<PluginEnvironment, T>{
|
||||||
|
private const int TotalKeys = 3;
|
||||||
|
|
||||||
|
public IEnumerable<PluginEnvironment> Keys => Enum.GetValues(typeof(PluginEnvironment)).Cast<PluginEnvironment>();
|
||||||
|
public IEnumerable<T> Values => data;
|
||||||
|
public int Count => TotalKeys;
|
||||||
|
|
||||||
|
public T this[PluginEnvironment key] => data[(int)key];
|
||||||
|
|
||||||
|
private readonly T[] data;
|
||||||
|
|
||||||
|
public PluginEnvironmentDictionary(T forNone, T forBrowser, T forNotification){
|
||||||
|
this.data = new T[TotalKeys];
|
||||||
|
this.data[(int)PluginEnvironment.None] = forNone;
|
||||||
|
this.data[(int)PluginEnvironment.Browser] = forBrowser;
|
||||||
|
this.data[(int)PluginEnvironment.Notification] = forNotification;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ContainsKey(PluginEnvironment key){
|
||||||
|
return key >= 0 && (int)key < TotalKeys;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetValue(PluginEnvironment key, out T value){
|
||||||
|
if (ContainsKey(key)){
|
||||||
|
value = this[key];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
value = default(T);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerator<KeyValuePair<PluginEnvironment, T>> GetEnumerator(){
|
||||||
|
return Keys.Select(key => new KeyValuePair<PluginEnvironment, T>(key, this[key])).GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
5
Plugins/Enums/PluginFolder.cs
Normal file
5
Plugins/Enums/PluginFolder.cs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
namespace TweetDuck.Plugins.Enums{
|
||||||
|
enum PluginFolder{
|
||||||
|
Root, Data
|
||||||
|
}
|
||||||
|
}
|
23
Plugins/Enums/PluginGroup.cs
Normal file
23
Plugins/Enums/PluginGroup.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
namespace TweetDuck.Plugins.Enums{
|
||||||
|
enum PluginGroup{
|
||||||
|
Official, Custom
|
||||||
|
}
|
||||||
|
|
||||||
|
static class PluginGroupExtensions{
|
||||||
|
public static string GetIdentifierPrefix(this PluginGroup group){
|
||||||
|
switch(group){
|
||||||
|
case PluginGroup.Official: return "official/";
|
||||||
|
case PluginGroup.Custom: return "custom/";
|
||||||
|
default: return "unknown/";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetIdentifierPrefixShort(this PluginGroup group){
|
||||||
|
switch(group){
|
||||||
|
case PluginGroup.Official: return "o/";
|
||||||
|
case PluginGroup.Custom: return "c/";
|
||||||
|
default: return "?/";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,7 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace TweetLib.Core.Features.Plugins.Events{
|
namespace TweetDuck.Plugins.Events{
|
||||||
public sealed class PluginChangedStateEventArgs : EventArgs{
|
sealed class PluginChangedStateEventArgs : EventArgs{
|
||||||
public Plugin Plugin { get; }
|
public Plugin Plugin { get; }
|
||||||
public bool IsEnabled { get; }
|
public bool IsEnabled { get; }
|
||||||
|
|
@@ -1,8 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace TweetLib.Core.Features.Plugins.Events{
|
namespace TweetDuck.Plugins.Events{
|
||||||
public sealed class PluginErrorEventArgs : EventArgs{
|
sealed class PluginErrorEventArgs : EventArgs{
|
||||||
public bool HasErrors => Errors.Count > 0;
|
public bool HasErrors => Errors.Count > 0;
|
||||||
|
|
||||||
public IList<string> Errors { get; }
|
public IList<string> Errors { get; }
|
@@ -1,13 +1,12 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using TweetLib.Core.Features.Plugins.Events;
|
using TweetDuck.Plugins.Events;
|
||||||
|
|
||||||
namespace TweetLib.Core.Features.Plugins.Config{
|
|
||||||
public interface IPluginConfig{
|
|
||||||
event EventHandler<PluginChangedStateEventArgs> PluginChangedState;
|
|
||||||
|
|
||||||
|
namespace TweetDuck.Plugins{
|
||||||
|
interface IPluginConfig{
|
||||||
IEnumerable<string> DisabledPlugins { get; }
|
IEnumerable<string> DisabledPlugins { get; }
|
||||||
void Reset(IEnumerable<string> newDisabledPlugins);
|
|
||||||
|
event EventHandler<PluginChangedStateEventArgs> PluginChangedState;
|
||||||
|
|
||||||
void SetEnabled(Plugin plugin, bool enabled);
|
void SetEnabled(Plugin plugin, bool enabled);
|
||||||
bool IsEnabled(Plugin plugin);
|
bool IsEnabled(Plugin plugin);
|
@@ -1,15 +1,14 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using TweetDuck.Plugins.Enums;
|
||||||
using TweetLib.Core.Features.Plugins.Enums;
|
|
||||||
|
|
||||||
namespace TweetLib.Core.Features.Plugins{
|
namespace TweetDuck.Plugins{
|
||||||
public sealed class Plugin{
|
sealed class Plugin{
|
||||||
private static readonly Version AppVersion = new Version(Lib.VersionTag);
|
private static readonly Version AppVersion = new Version(Program.VersionTag);
|
||||||
|
|
||||||
public string Identifier { get; }
|
public string Identifier { get; }
|
||||||
public PluginGroup Group { get; }
|
public PluginGroup Group { get; }
|
||||||
|
public PluginEnvironment Environments { get; }
|
||||||
|
|
||||||
public string Name { get; }
|
public string Name { get; }
|
||||||
public string Description { get; }
|
public string Description { get; }
|
||||||
@@ -40,15 +39,14 @@ namespace TweetLib.Core.Features.Plugins{
|
|||||||
|
|
||||||
private readonly string pathRoot;
|
private readonly string pathRoot;
|
||||||
private readonly string pathData;
|
private readonly string pathData;
|
||||||
private readonly ISet<PluginEnvironment> environments;
|
|
||||||
|
|
||||||
private Plugin(PluginGroup group, string identifier, string pathRoot, string pathData, Builder builder){
|
private Plugin(PluginGroup group, string identifier, string pathRoot, string pathData, Builder builder){
|
||||||
this.pathRoot = pathRoot;
|
this.pathRoot = pathRoot;
|
||||||
this.pathData = pathData;
|
this.pathData = pathData;
|
||||||
this.environments = builder.Environments;
|
|
||||||
|
|
||||||
this.Group = group;
|
this.Group = group;
|
||||||
this.Identifier = identifier;
|
this.Identifier = identifier;
|
||||||
|
this.Environments = builder.Environments;
|
||||||
|
|
||||||
this.Name = builder.Name;
|
this.Name = builder.Name;
|
||||||
this.Description = builder.Description;
|
this.Description = builder.Description;
|
||||||
@@ -62,13 +60,9 @@ namespace TweetLib.Core.Features.Plugins{
|
|||||||
this.CanRun = AppVersion >= RequiredVersion;
|
this.CanRun = AppVersion >= RequiredVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool HasEnvironment(PluginEnvironment environment){
|
|
||||||
return environments.Contains(environment);
|
|
||||||
}
|
|
||||||
|
|
||||||
public string GetScriptPath(PluginEnvironment environment){
|
public string GetScriptPath(PluginEnvironment environment){
|
||||||
if (environments.Contains(environment)){
|
if (Environments.HasFlag(environment)){
|
||||||
string? file = environment.GetPluginScriptFile();
|
string file = environment.GetPluginScriptFile();
|
||||||
return file != null ? Path.Combine(pathRoot, file) : string.Empty;
|
return file != null ? Path.Combine(pathRoot, file) : string.Empty;
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
@@ -77,11 +71,11 @@ namespace TweetLib.Core.Features.Plugins{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public string GetPluginFolder(PluginFolder folder){
|
public string GetPluginFolder(PluginFolder folder){
|
||||||
return folder switch{
|
switch(folder){
|
||||||
PluginFolder.Root => pathRoot,
|
case PluginFolder.Root: return pathRoot;
|
||||||
PluginFolder.Data => pathData,
|
case PluginFolder.Data: return pathData;
|
||||||
_ => string.Empty
|
default: return string.Empty;
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetFullPathIfSafe(PluginFolder folder, string relativePath){
|
public string GetFullPathIfSafe(PluginFolder folder, string relativePath){
|
||||||
@@ -130,7 +124,7 @@ namespace TweetLib.Core.Features.Plugins{
|
|||||||
public sealed class Builder{
|
public sealed class Builder{
|
||||||
private static readonly Version DefaultRequiredVersion = new Version(0, 0, 0, 0);
|
private static readonly Version DefaultRequiredVersion = new Version(0, 0, 0, 0);
|
||||||
|
|
||||||
public string Name { get; set; } = string.Empty;
|
public string Name { get; set; }
|
||||||
public string Description { get; set; } = string.Empty;
|
public string Description { get; set; } = string.Empty;
|
||||||
public string Author { get; set; } = "(anonymous)";
|
public string Author { get; set; } = "(anonymous)";
|
||||||
public string Version { get; set; } = string.Empty;
|
public string Version { get; set; } = string.Empty;
|
||||||
@@ -139,7 +133,7 @@ namespace TweetLib.Core.Features.Plugins{
|
|||||||
public string ConfigDefault { get; set; } = string.Empty;
|
public string ConfigDefault { get; set; } = string.Empty;
|
||||||
public Version RequiredVersion { get; set; } = DefaultRequiredVersion;
|
public Version RequiredVersion { get; set; } = DefaultRequiredVersion;
|
||||||
|
|
||||||
public ISet<PluginEnvironment> Environments { get; } = new HashSet<PluginEnvironment>();
|
public PluginEnvironment Environments { get; private set; } = PluginEnvironment.None;
|
||||||
|
|
||||||
private readonly PluginGroup group;
|
private readonly PluginGroup group;
|
||||||
private readonly string pathRoot;
|
private readonly string pathRoot;
|
||||||
@@ -150,11 +144,11 @@ namespace TweetLib.Core.Features.Plugins{
|
|||||||
this.group = group;
|
this.group = group;
|
||||||
this.pathRoot = pathRoot;
|
this.pathRoot = pathRoot;
|
||||||
this.pathData = pathData;
|
this.pathData = pathData;
|
||||||
this.identifier = group.GetIdentifierPrefix() + name;
|
this.identifier = group.GetIdentifierPrefix()+name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddEnvironment(PluginEnvironment environment){
|
public void AddEnvironment(PluginEnvironment environment){
|
||||||
Environments.Add(environment);
|
this.Environments |= environment;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Plugin BuildAndSetup(){
|
public Plugin BuildAndSetup(){
|
||||||
@@ -164,7 +158,7 @@ namespace TweetLib.Core.Features.Plugins{
|
|||||||
throw new InvalidOperationException("Plugin is missing a name in the .meta file");
|
throw new InvalidOperationException("Plugin is missing a name in the .meta file");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!PluginEnvironments.All.Any(plugin.HasEnvironment)){
|
if (plugin.Environments == PluginEnvironment.None){
|
||||||
throw new InvalidOperationException("Plugin has no script files");
|
throw new InvalidOperationException("Plugin has no script files");
|
||||||
}
|
}
|
||||||
|
|
127
Plugins/PluginBridge.cs
Normal file
127
Plugins/PluginBridge.cs
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
using TweetDuck.Core.Utils;
|
||||||
|
using TweetDuck.Data;
|
||||||
|
using TweetDuck.Plugins.Enums;
|
||||||
|
using TweetDuck.Plugins.Events;
|
||||||
|
|
||||||
|
namespace TweetDuck.Plugins{
|
||||||
|
sealed class PluginBridge{
|
||||||
|
private static string SanitizeCacheKey(string key){
|
||||||
|
return key.Replace('\\', '/').Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly PluginManager manager;
|
||||||
|
private readonly TwoKeyDictionary<int, string, string> fileCache = new TwoKeyDictionary<int, string, string>(4, 2);
|
||||||
|
private readonly TwoKeyDictionary<int, string, InjectedHTML> notificationInjections = new TwoKeyDictionary<int, string, InjectedHTML>(4, 1);
|
||||||
|
|
||||||
|
public IEnumerable<InjectedHTML> NotificationInjections => notificationInjections.InnerValues;
|
||||||
|
public HashSet<Plugin> WithConfigureFunction { get; } = new HashSet<Plugin>();
|
||||||
|
|
||||||
|
public PluginBridge(PluginManager manager){
|
||||||
|
this.manager = manager;
|
||||||
|
this.manager.Reloaded += manager_Reloaded;
|
||||||
|
this.manager.Config.PluginChangedState += Config_PluginChangedState;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event handlers
|
||||||
|
|
||||||
|
private void manager_Reloaded(object sender, PluginErrorEventArgs e){
|
||||||
|
fileCache.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Config_PluginChangedState(object sender, PluginChangedStateEventArgs e){
|
||||||
|
if (!e.IsEnabled){
|
||||||
|
int token = manager.GetTokenFromPlugin(e.Plugin);
|
||||||
|
|
||||||
|
fileCache.Remove(token);
|
||||||
|
notificationInjections.Remove(token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Utility methods
|
||||||
|
|
||||||
|
private string GetFullPathOrThrow(int token, PluginFolder folder, string path){
|
||||||
|
Plugin plugin = manager.GetPluginFromToken(token);
|
||||||
|
string fullPath = plugin == null ? string.Empty : plugin.GetFullPathIfSafe(folder, path);
|
||||||
|
|
||||||
|
if (fullPath.Length == 0){
|
||||||
|
switch(folder){
|
||||||
|
case PluginFolder.Data: throw new ArgumentException("File path has to be relative to the plugin data folder.");
|
||||||
|
case PluginFolder.Root: throw new ArgumentException("File path has to be relative to the plugin root folder.");
|
||||||
|
default: throw new ArgumentException("Invalid folder type "+folder+", this is a TweetDuck error.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return fullPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string ReadFileUnsafe(int token, string cacheKey, string fullPath, bool readCached){
|
||||||
|
cacheKey = SanitizeCacheKey(cacheKey);
|
||||||
|
|
||||||
|
if (readCached && fileCache.TryGetValue(token, cacheKey, out string cachedContents)){
|
||||||
|
return cachedContents;
|
||||||
|
}
|
||||||
|
|
||||||
|
try{
|
||||||
|
return fileCache[token, cacheKey] = File.ReadAllText(fullPath, Encoding.UTF8);
|
||||||
|
}catch(FileNotFoundException){
|
||||||
|
throw new FileNotFoundException("File not found.");
|
||||||
|
}catch(DirectoryNotFoundException){
|
||||||
|
throw new DirectoryNotFoundException("Directory not found.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Public methods
|
||||||
|
|
||||||
|
public void WriteFile(int token, string path, string contents){
|
||||||
|
string fullPath = GetFullPathOrThrow(token, PluginFolder.Data, path);
|
||||||
|
|
||||||
|
WindowsUtils.CreateDirectoryForFile(fullPath);
|
||||||
|
File.WriteAllText(fullPath, contents, Encoding.UTF8);
|
||||||
|
fileCache[token, SanitizeCacheKey(path)] = contents;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ReadFile(int token, string path, bool cache){
|
||||||
|
return ReadFileUnsafe(token, path, GetFullPathOrThrow(token, PluginFolder.Data, path), cache);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DeleteFile(int token, string path){
|
||||||
|
string fullPath = GetFullPathOrThrow(token, PluginFolder.Data, path);
|
||||||
|
|
||||||
|
fileCache.Remove(token, SanitizeCacheKey(path));
|
||||||
|
File.Delete(fullPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CheckFileExists(int token, string path){
|
||||||
|
return File.Exists(GetFullPathOrThrow(token, PluginFolder.Data, path));
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ReadFileRoot(int token, string path){
|
||||||
|
return ReadFileUnsafe(token, "root*"+path, GetFullPathOrThrow(token, PluginFolder.Root, path), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CheckFileExistsRoot(int token, string path){
|
||||||
|
return File.Exists(GetFullPathOrThrow(token, PluginFolder.Root, path));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InjectIntoNotificationsBefore(int token, string key, string search, string html){
|
||||||
|
notificationInjections[token, key] = new InjectedHTML(InjectedHTML.Position.Before, search, html);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InjectIntoNotificationsAfter(int token, string key, string search, string html){
|
||||||
|
notificationInjections[token, key] = new InjectedHTML(InjectedHTML.Position.After, search, html);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetConfigurable(int token){
|
||||||
|
Plugin plugin = manager.GetPluginFromToken(token);
|
||||||
|
|
||||||
|
if (plugin != null){
|
||||||
|
WithConfigureFunction.Add(plugin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,34 +0,0 @@
|
|||||||
using System;
|
|
||||||
using CefSharp;
|
|
||||||
using TweetDuck.Core.Adapters;
|
|
||||||
using TweetLib.Core.Browser;
|
|
||||||
using TweetLib.Core.Features.Plugins;
|
|
||||||
using TweetLib.Core.Features.Plugins.Events;
|
|
||||||
using TweetLib.Core.Features.Twitter;
|
|
||||||
|
|
||||||
namespace TweetDuck.Plugins{
|
|
||||||
sealed class PluginDispatcher : IPluginDispatcher{
|
|
||||||
public event EventHandler<PluginDispatchEventArgs> Ready;
|
|
||||||
|
|
||||||
private readonly IWebBrowser browser;
|
|
||||||
private readonly IScriptExecutor executor;
|
|
||||||
|
|
||||||
public PluginDispatcher(IWebBrowser browser){
|
|
||||||
this.browser = browser;
|
|
||||||
this.browser.FrameLoadEnd += browser_FrameLoadEnd;
|
|
||||||
this.executor = new CefScriptExecutor(browser);
|
|
||||||
}
|
|
||||||
|
|
||||||
void IPluginDispatcher.AttachBridge(string name, object bridge){
|
|
||||||
browser.RegisterAsyncJsObject(name, bridge);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
|
|
||||||
IFrame frame = e.Frame;
|
|
||||||
|
|
||||||
if (frame.IsMain && TwitterUrls.IsTweetDeck(frame.Url)){
|
|
||||||
Ready?.Invoke(this, new PluginDispatchEventArgs(executor));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
70
Plugins/PluginLoader.cs
Normal file
70
Plugins/PluginLoader.cs
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using TweetDuck.Plugins.Enums;
|
||||||
|
|
||||||
|
namespace TweetDuck.Plugins{
|
||||||
|
static class PluginLoader{
|
||||||
|
private static readonly string[] EndTag = { "[END]" };
|
||||||
|
|
||||||
|
public static Plugin FromFolder(string path, PluginGroup group){
|
||||||
|
string name = Path.GetFileName(path);
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(name)){
|
||||||
|
throw new ArgumentException("Could not extract directory name from path: "+path);
|
||||||
|
}
|
||||||
|
|
||||||
|
Plugin.Builder builder = new Plugin.Builder(group, name, path, Path.Combine(Program.PluginDataPath, group.GetIdentifierPrefix(), name));
|
||||||
|
|
||||||
|
foreach(string file in Directory.EnumerateFiles(path, "*.js", SearchOption.TopDirectoryOnly).Select(Path.GetFileName)){
|
||||||
|
builder.AddEnvironment(PluginEnvironmentExtensions.Values.FirstOrDefault(env => file.Equals(env.GetPluginScriptFile(), StringComparison.Ordinal)));
|
||||||
|
}
|
||||||
|
|
||||||
|
string metaFile = Path.Combine(path, ".meta");
|
||||||
|
|
||||||
|
if (!File.Exists(metaFile)){
|
||||||
|
throw new ArgumentException("Plugin is missing a .meta file");
|
||||||
|
}
|
||||||
|
|
||||||
|
string currentTag = null, currentContents = string.Empty;
|
||||||
|
|
||||||
|
foreach(string line in File.ReadAllLines(metaFile, Encoding.UTF8).Concat(EndTag).Select(line => line.TrimEnd()).Where(line => line.Length > 0)){
|
||||||
|
if (line[0] == '[' && line[line.Length-1] == ']'){
|
||||||
|
if (currentTag != null){
|
||||||
|
SetProperty(builder, currentTag, currentContents);
|
||||||
|
}
|
||||||
|
|
||||||
|
currentTag = line.Substring(1, line.Length-2).ToUpper();
|
||||||
|
currentContents = string.Empty;
|
||||||
|
|
||||||
|
if (line.Equals(EndTag[0])){
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (currentTag != null){
|
||||||
|
currentContents = currentContents.Length == 0 ? line : currentContents+Environment.NewLine+line;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
throw new FormatException("Missing metadata tag before value: "+line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.BuildAndSetup();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SetProperty(Plugin.Builder builder, string tag, string value){
|
||||||
|
switch(tag){
|
||||||
|
case "NAME": builder.Name = value; break;
|
||||||
|
case "DESCRIPTION": builder.Description = value; break;
|
||||||
|
case "AUTHOR": builder.Author = value; break;
|
||||||
|
case "VERSION": builder.Version = value; break;
|
||||||
|
case "WEBSITE": builder.Website = value; break;
|
||||||
|
case "CONFIGFILE": builder.ConfigFile = value; break;
|
||||||
|
case "CONFIGDEFAULT": builder.ConfigDefault = value; break;
|
||||||
|
case "REQUIRES": builder.RequiredVersion = Version.TryParse(value, out Version version) ? version : throw new FormatException("Invalid required minimum version: "+value); break;
|
||||||
|
default: throw new FormatException("Invalid metadata tag: "+tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
183
Plugins/PluginManager.cs
Normal file
183
Plugins/PluginManager.cs
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
using CefSharp;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using TweetDuck.Core.Utils;
|
||||||
|
using TweetDuck.Data;
|
||||||
|
using TweetDuck.Plugins.Enums;
|
||||||
|
using TweetDuck.Plugins.Events;
|
||||||
|
using TweetDuck.Resources;
|
||||||
|
|
||||||
|
namespace TweetDuck.Plugins{
|
||||||
|
sealed class PluginManager{
|
||||||
|
private static readonly IReadOnlyDictionary<PluginEnvironment, string> PluginSetupScriptNames = PluginEnvironmentExtensions.Map(null, "plugins.browser.js", "plugins.notification.js");
|
||||||
|
|
||||||
|
public string PathOfficialPlugins => Path.Combine(rootPath, "official");
|
||||||
|
public string PathCustomPlugins => Path.Combine(rootPath, "user");
|
||||||
|
|
||||||
|
public IEnumerable<Plugin> Plugins => plugins;
|
||||||
|
public IEnumerable<InjectedHTML> NotificationInjections => bridge.NotificationInjections;
|
||||||
|
|
||||||
|
public IPluginConfig Config { get; }
|
||||||
|
|
||||||
|
public event EventHandler<PluginErrorEventArgs> Reloaded;
|
||||||
|
public event EventHandler<PluginErrorEventArgs> Executed;
|
||||||
|
|
||||||
|
private readonly string rootPath;
|
||||||
|
private readonly PluginBridge bridge;
|
||||||
|
|
||||||
|
private readonly HashSet<Plugin> plugins = new HashSet<Plugin>();
|
||||||
|
private readonly Dictionary<int, Plugin> tokens = new Dictionary<int, Plugin>();
|
||||||
|
private readonly Random rand = new Random();
|
||||||
|
|
||||||
|
private IWebBrowser mainBrowser;
|
||||||
|
|
||||||
|
public PluginManager(IPluginConfig config, string rootPath){
|
||||||
|
this.Config = config;
|
||||||
|
this.Config.PluginChangedState += Config_PluginChangedState;
|
||||||
|
|
||||||
|
this.rootPath = rootPath;
|
||||||
|
this.bridge = new PluginBridge(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Register(IWebBrowser browser, PluginEnvironment environment, Control sync, bool asMainBrowser = false){
|
||||||
|
browser.FrameLoadEnd += (sender, args) => {
|
||||||
|
IFrame frame = args.Frame;
|
||||||
|
|
||||||
|
if (frame.IsMain && TwitterUtils.IsTweetDeckWebsite(frame)){
|
||||||
|
ExecutePlugins(frame, environment, sync);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
browser.RegisterAsyncJsObject("$TDP", bridge);
|
||||||
|
|
||||||
|
if (asMainBrowser){
|
||||||
|
mainBrowser = browser;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Config_PluginChangedState(object sender, PluginChangedStateEventArgs e){
|
||||||
|
mainBrowser?.ExecuteScriptAsync("TDPF_setPluginState", e.Plugin, e.IsEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsPluginInstalled(string identifier){
|
||||||
|
return plugins.Any(plugin => plugin.Identifier.Equals(identifier));
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool HasAnyPlugin(PluginEnvironment environment){
|
||||||
|
return plugins.Any(plugin => plugin.Environments.HasFlag(environment));
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsPluginConfigurable(Plugin plugin){
|
||||||
|
return plugin.HasConfig || bridge.WithConfigureFunction.Contains(plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ConfigurePlugin(Plugin plugin){
|
||||||
|
if (bridge.WithConfigureFunction.Contains(plugin)){
|
||||||
|
mainBrowser?.ExecuteScriptAsync("TDPF_configurePlugin", plugin);
|
||||||
|
}
|
||||||
|
else if (plugin.HasConfig){
|
||||||
|
if (File.Exists(plugin.ConfigPath)){
|
||||||
|
using(Process.Start("explorer.exe", "/select,\""+plugin.ConfigPath.Replace('/', '\\')+"\"")){}
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
using(Process.Start("explorer.exe", '"'+plugin.GetPluginFolder(PluginFolder.Data).Replace('/', '\\')+'"')){}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetTokenFromPlugin(Plugin plugin){
|
||||||
|
foreach(KeyValuePair<int, Plugin> kvp in tokens){
|
||||||
|
if (kvp.Value.Equals(plugin)){
|
||||||
|
return kvp.Key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int token, attempts = 1000;
|
||||||
|
|
||||||
|
do{
|
||||||
|
token = rand.Next();
|
||||||
|
}while(tokens.ContainsKey(token) && --attempts >= 0);
|
||||||
|
|
||||||
|
if (attempts < 0){
|
||||||
|
token = -tokens.Count-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
tokens[token] = plugin;
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Plugin GetPluginFromToken(int token){
|
||||||
|
return tokens.TryGetValue(token, out Plugin plugin) ? plugin : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reload(){
|
||||||
|
plugins.Clear();
|
||||||
|
tokens.Clear();
|
||||||
|
|
||||||
|
List<string> loadErrors = new List<string>(2);
|
||||||
|
|
||||||
|
IEnumerable<Plugin> LoadPluginsFrom(string path, PluginGroup group){
|
||||||
|
if (!Directory.Exists(path)){
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach(string fullDir in Directory.EnumerateDirectories(path, "*", SearchOption.TopDirectoryOnly)){
|
||||||
|
Plugin plugin;
|
||||||
|
|
||||||
|
try{
|
||||||
|
plugin = PluginLoader.FromFolder(fullDir, group);
|
||||||
|
}catch(Exception e){
|
||||||
|
loadErrors.Add(group.GetIdentifierPrefix()+Path.GetFileName(fullDir)+": "+e.Message);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
yield return plugin;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
plugins.UnionWith(LoadPluginsFrom(PathOfficialPlugins, PluginGroup.Official));
|
||||||
|
plugins.UnionWith(LoadPluginsFrom(PathCustomPlugins, PluginGroup.Custom));
|
||||||
|
|
||||||
|
Reloaded?.Invoke(this, new PluginErrorEventArgs(loadErrors));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ExecutePlugins(IFrame frame, PluginEnvironment environment, Control sync){
|
||||||
|
if (!HasAnyPlugin(environment) || !ScriptLoader.ExecuteFile(frame, PluginSetupScriptNames[environment], sync)){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool includeDisabled = environment.IncludesDisabledPlugins();
|
||||||
|
|
||||||
|
if (includeDisabled){
|
||||||
|
ScriptLoader.ExecuteScript(frame, PluginScriptGenerator.GenerateConfig(Config), "gen:pluginconfig");
|
||||||
|
}
|
||||||
|
|
||||||
|
List<string> failedPlugins = new List<string>(1);
|
||||||
|
|
||||||
|
foreach(Plugin plugin in Plugins){
|
||||||
|
string path = plugin.GetScriptPath(environment);
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(path) || (!includeDisabled && !Config.IsEnabled(plugin)) || !plugin.CanRun){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
string script;
|
||||||
|
|
||||||
|
try{
|
||||||
|
script = File.ReadAllText(path);
|
||||||
|
}catch(Exception e){
|
||||||
|
failedPlugins.Add(plugin.Identifier+" ("+Path.GetFileName(path)+"): "+e.Message);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ScriptLoader.ExecuteScript(frame, PluginScriptGenerator.GeneratePlugin(plugin.Identifier, script, GetTokenFromPlugin(plugin), environment), "plugin:"+plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
Executed?.Invoke(this, new PluginErrorEventArgs(failedPlugins));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,11 +1,10 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using TweetLib.Core.Features.Plugins.Config;
|
using TweetDuck.Plugins.Enums;
|
||||||
using TweetLib.Core.Features.Plugins.Enums;
|
|
||||||
|
|
||||||
namespace TweetLib.Core.Features.Plugins{
|
namespace TweetDuck.Plugins{
|
||||||
public static class PluginScriptGenerator{
|
static class PluginScriptGenerator{
|
||||||
public static string GenerateConfig(IPluginConfig config){
|
public static string GenerateConfig(IPluginConfig config){
|
||||||
return "window.TD_PLUGINS.disabled = [" + string.Join(",", config.DisabledPlugins.Select(id => '"' + id + '"')) + "]";
|
return "window.TD_PLUGINS.disabled = ["+string.Join(",", config.DisabledPlugins.Select(id => $"\"{id}\""))+"]";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GeneratePlugin(string pluginIdentifier, string pluginContents, int pluginToken, PluginEnvironment environment){
|
public static string GeneratePlugin(string pluginIdentifier, string pluginContents, int pluginToken, PluginEnvironment environment){
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user