mirror of
https://github.com/chylex/TweetDuck.git
synced 2025-09-14 19:32:10 +02:00
Compare commits
81 Commits
Author | SHA1 | Date | |
---|---|---|---|
04cd662d78 | |||
da597f076f | |||
fab3efdcf5 | |||
a55509a34d | |||
84fb1c5b2b | |||
391a90e1df | |||
e0fe39195d | |||
385fead81a | |||
648d1b9aa9 | |||
3f0028913d | |||
45e6ec8b0f | |||
a3fbaa0b34 | |||
7102cbfb3b | |||
cb61dc742f | |||
cd53f6e757 | |||
c64f7daa8d | |||
e70d792654 | |||
9ae533f907 | |||
cfe92f18e3 | |||
e2a34ea28e | |||
ec8000360e | |||
57b0821e19 | |||
09d39df15a | |||
1f9db3bda6 | |||
b7104c8828 | |||
5da02b4092 | |||
802f1e3042 | |||
66db0df45a | |||
650c2e2eb7 | |||
6ab3754129 | |||
7dca0a8cab | |||
7cd0b4ad54 | |||
97acb41eee | |||
b916b9726e | |||
32d3990ace | |||
cb1fd109cc | |||
0e68dd6185 | |||
fb6502bc65 | |||
c7e7403781 | |||
bf224408a3 | |||
84b7078873 | |||
89e5943d8f | |||
b78c4cb8f0 | |||
976ba074a8 | |||
5205d59b96 | |||
e8394b9c08 | |||
9cd00239f9 | |||
b6b26142f8 | |||
4ee99376fd | |||
b0261342ff | |||
87fd2a521e | |||
334793c6f6 | |||
3e70d991bb | |||
feec96fc5c | |||
765984709e | |||
c7c9931f68 | |||
ae64573510 | |||
d675af5aa4 | |||
9480d17cfc | |||
5ac1df2283 | |||
20119db883 | |||
a4006deb8c | |||
25fa3cefab | |||
bb5161eb34 | |||
1bfc403a98 | |||
720d10e543 | |||
30c117672e | |||
6d1b5c77d1 | |||
d1cbf608e0 | |||
7e3014c52d | |||
82beb1f5a7 | |||
657dc81300 | |||
8e22192dd3 | |||
dc0b7d58e3 | |||
6919e5bdb0 | |||
9728a62efa | |||
276e070759 | |||
fadea54f8d | |||
523d340ade | |||
96fa7efb66 | |||
03591f8317 |
6
.gitignore
vendored
6
.gitignore
vendored
@@ -17,9 +17,13 @@
|
|||||||
[Rr]eleases/
|
[Rr]eleases/
|
||||||
x64/
|
x64/
|
||||||
x86/
|
x86/
|
||||||
bld/
|
|
||||||
[Bb]in/
|
[Bb]in/
|
||||||
[Oo]bj/
|
[Oo]bj/
|
||||||
|
bld/*
|
||||||
|
!bld/gen_full.iss
|
||||||
|
!bld/gen_port.iss
|
||||||
|
!bld/gen_upd.iss
|
||||||
|
!bld/Resources
|
||||||
|
|
||||||
# Visual Studio 2015 cache/options directory
|
# Visual Studio 2015 cache/options directory
|
||||||
.vs/
|
.vs/
|
||||||
|
@@ -5,6 +5,10 @@ using System.Threading;
|
|||||||
|
|
||||||
namespace TweetDck.Configuration{
|
namespace TweetDck.Configuration{
|
||||||
sealed class LockManager{
|
sealed class LockManager{
|
||||||
|
public enum Result{
|
||||||
|
Success, HasProcess, Fail
|
||||||
|
}
|
||||||
|
|
||||||
public Process LockingProcess { get; private set; }
|
public Process LockingProcess { get; private set; }
|
||||||
|
|
||||||
private readonly string file;
|
private readonly string file;
|
||||||
@@ -14,87 +18,97 @@ namespace TweetDck.Configuration{
|
|||||||
this.file = file;
|
this.file = file;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool CreateLockFile(){
|
private void CreateLockFileStream(){
|
||||||
|
lockStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.Read);
|
||||||
|
WriteIntToStream(lockStream, GetCurrentProcessId());
|
||||||
|
lockStream.Flush(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ReleaseLockFileStream(){
|
||||||
|
if (lockStream != null){
|
||||||
|
lockStream.Dispose();
|
||||||
|
lockStream = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Result TryCreateLockFile(){
|
||||||
if (lockStream != null){
|
if (lockStream != null){
|
||||||
throw new InvalidOperationException("Lock file already exists.");
|
throw new InvalidOperationException("Lock file already exists.");
|
||||||
}
|
}
|
||||||
|
|
||||||
try{
|
try{
|
||||||
lockStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.Read);
|
CreateLockFileStream();
|
||||||
|
return Result.Success;
|
||||||
byte[] id = BitConverter.GetBytes(Process.GetCurrentProcess().Id);
|
}catch(DirectoryNotFoundException){
|
||||||
lockStream.Write(id, 0, id.Length);
|
try{
|
||||||
lockStream.Flush();
|
CreateLockFileStream();
|
||||||
|
return Result.Success;
|
||||||
if (LockingProcess != null){
|
}catch{
|
||||||
LockingProcess.Close();
|
ReleaseLockFileStream();
|
||||||
LockingProcess = null;
|
return Result.Fail;
|
||||||
}
|
}
|
||||||
|
}catch(IOException){
|
||||||
return true;
|
return Result.HasProcess;
|
||||||
}catch(Exception){
|
}catch{
|
||||||
if (lockStream != null){
|
ReleaseLockFileStream();
|
||||||
lockStream.Close();
|
return Result.Fail;
|
||||||
lockStream.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Lock(){
|
public Result Lock(){
|
||||||
if (lockStream != null)return true;
|
if (lockStream != null){
|
||||||
|
return Result.Success;
|
||||||
try{
|
|
||||||
byte[] bytes = new byte[4];
|
|
||||||
|
|
||||||
using(FileStream fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)){
|
|
||||||
fileStream.Read(bytes, 0, 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
int pid = BitConverter.ToInt32(bytes, 0);
|
|
||||||
|
|
||||||
try{
|
|
||||||
Process foundProcess = Process.GetProcessById(pid);
|
|
||||||
|
|
||||||
using(Process currentProcess = Process.GetCurrentProcess()){
|
|
||||||
if (foundProcess.ProcessName == currentProcess.ProcessName){
|
|
||||||
LockingProcess = foundProcess;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}catch(ArgumentException){}
|
|
||||||
|
|
||||||
return LockingProcess == null && CreateLockFile();
|
|
||||||
}catch(DirectoryNotFoundException){
|
|
||||||
string dir = Path.GetDirectoryName(file);
|
|
||||||
|
|
||||||
if (dir != null){
|
|
||||||
Directory.CreateDirectory(dir);
|
|
||||||
return CreateLockFile();
|
|
||||||
}
|
|
||||||
}catch(FileNotFoundException){
|
|
||||||
return CreateLockFile();
|
|
||||||
}catch(Exception){
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
Result initialResult = TryCreateLockFile();
|
||||||
|
|
||||||
|
if (initialResult == Result.HasProcess){
|
||||||
|
try{
|
||||||
|
int pid;
|
||||||
|
|
||||||
|
using(FileStream fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)){
|
||||||
|
pid = ReadIntFromStream(fileStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
try{
|
||||||
|
Process foundProcess = Process.GetProcessById(pid);
|
||||||
|
|
||||||
|
using(Process currentProcess = Process.GetCurrentProcess()){
|
||||||
|
if (foundProcess.MainModule.FileVersionInfo.InternalName == currentProcess.MainModule.FileVersionInfo.InternalName){
|
||||||
|
LockingProcess = foundProcess;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
foundProcess.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}catch{
|
||||||
|
// GetProcessById throws ArgumentException if the process is missing
|
||||||
|
// Process.MainModule can throw exceptions in some cases
|
||||||
|
}
|
||||||
|
|
||||||
|
return LockingProcess == null ? Result.Fail : Result.HasProcess;
|
||||||
|
}catch{
|
||||||
|
return Result.Fail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return initialResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Unlock(){
|
public bool Unlock(){
|
||||||
bool result = true;
|
bool result = true;
|
||||||
|
|
||||||
if (lockStream != null){
|
if (ReleaseLockFileStream()){
|
||||||
lockStream.Dispose();
|
|
||||||
|
|
||||||
try{
|
try{
|
||||||
File.Delete(file);
|
File.Delete(file);
|
||||||
}catch(Exception e){
|
}catch(Exception e){
|
||||||
Program.Reporter.Log(e.ToString());
|
Program.Reporter.Log(e.ToString());
|
||||||
result = false;
|
result = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
lockStream = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@@ -104,11 +118,9 @@ namespace TweetDck.Configuration{
|
|||||||
if (LockingProcess != null){
|
if (LockingProcess != null){
|
||||||
LockingProcess.CloseMainWindow();
|
LockingProcess.CloseMainWindow();
|
||||||
|
|
||||||
for(int waited = 0; waited < timeout && !LockingProcess.HasExited;){
|
for(int waited = 0; waited < timeout && !LockingProcess.HasExited; waited += 250){
|
||||||
LockingProcess.Refresh();
|
LockingProcess.Refresh();
|
||||||
|
Thread.Sleep(250);
|
||||||
Thread.Sleep(100);
|
|
||||||
waited += 100;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (LockingProcess.HasExited){
|
if (LockingProcess.HasExited){
|
||||||
@@ -120,5 +132,24 @@ namespace TweetDck.Configuration{
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Utility functions
|
||||||
|
|
||||||
|
private static void WriteIntToStream(Stream stream, int value){
|
||||||
|
byte[] id = BitConverter.GetBytes(value);
|
||||||
|
stream.Write(id, 0, id.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int ReadIntFromStream(Stream stream){
|
||||||
|
byte[] bytes = new byte[4];
|
||||||
|
stream.Read(bytes, 0, 4);
|
||||||
|
return BitConverter.ToInt32(bytes, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int GetCurrentProcessId(){
|
||||||
|
using(Process process = Process.GetCurrentProcess()){
|
||||||
|
return process.Id;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Globalization;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Runtime.Serialization;
|
using System.Runtime.Serialization;
|
||||||
using System.Runtime.Serialization.Formatters.Binary;
|
using System.Runtime.Serialization.Formatters.Binary;
|
||||||
@@ -12,17 +11,12 @@ using TweetDck.Plugins;
|
|||||||
namespace TweetDck.Configuration{
|
namespace TweetDck.Configuration{
|
||||||
[Serializable]
|
[Serializable]
|
||||||
sealed class UserConfig{
|
sealed class UserConfig{
|
||||||
private static readonly IFormatter Formatter = new BinaryFormatter{
|
private static readonly IFormatter Formatter = new BinaryFormatter();
|
||||||
Binder = new SerializationCompatibilityHandler()
|
|
||||||
};
|
|
||||||
|
|
||||||
private const int CurrentFileVersion = 5;
|
private const int CurrentFileVersion = 5;
|
||||||
|
|
||||||
// START OF CONFIGURATION
|
// START OF CONFIGURATION
|
||||||
|
|
||||||
public bool IgnoreMigration { get; set; }
|
|
||||||
public bool IgnoreUninstallCheck { get; set; }
|
|
||||||
|
|
||||||
public WindowState BrowserWindow { get; set; }
|
public WindowState BrowserWindow { get; set; }
|
||||||
public bool DisplayNotificationTimer { get; set; }
|
public bool DisplayNotificationTimer { get; set; }
|
||||||
public bool NotificationTimerCountDown { get; set; }
|
public bool NotificationTimerCountDown { get; set; }
|
||||||
@@ -235,13 +229,5 @@ namespace TweetDck.Configuration{
|
|||||||
public static string GetBackupFile(string file){
|
public static string GetBackupFile(string file){
|
||||||
return file+".bak";
|
return file+".bak";
|
||||||
}
|
}
|
||||||
|
|
||||||
private class SerializationCompatibilityHandler : SerializationBinder{
|
|
||||||
public override Type BindToType(string assemblyName, string typeName){
|
|
||||||
assemblyName = assemblyName.Replace("TweetDick", "TweetDuck");
|
|
||||||
typeName = typeName.Replace("TweetDick", "TweetDck");
|
|
||||||
return Type.GetType(string.Format(CultureInfo.CurrentCulture, "{0}, {1}", typeName, assemblyName));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
|
using TweetDck.Core.Utils;
|
||||||
|
|
||||||
namespace TweetDck.Core.Controls{
|
namespace TweetDck.Core.Controls{
|
||||||
static class ControlExtensions{
|
static class ControlExtensions{
|
||||||
@@ -35,6 +36,12 @@ namespace TweetDck.Core.Controls{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void SetElevated(this Button button){
|
||||||
|
button.Text = " "+button.Text;
|
||||||
|
button.FlatStyle = FlatStyle.System;
|
||||||
|
NativeMethods.SendMessage(button.Handle, NativeMethods.BCM_SETSHIELD, 0, new IntPtr(1));
|
||||||
|
}
|
||||||
|
|
||||||
public static void EnableMultilineShortcuts(this TextBox textBox){
|
public static void EnableMultilineShortcuts(this TextBox textBox){
|
||||||
textBox.KeyDown += (sender, args) => {
|
textBox.KeyDown += (sender, args) => {
|
||||||
if (args.Control && args.KeyCode == Keys.A){
|
if (args.Control && args.KeyCode == Keys.A){
|
||||||
|
@@ -8,8 +8,10 @@ using TweetDck.Core.Other;
|
|||||||
using TweetDck.Resources;
|
using TweetDck.Resources;
|
||||||
using TweetDck.Core.Controls;
|
using TweetDck.Core.Controls;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
|
using TweetDck.Core.Utils;
|
||||||
using TweetDck.Updates;
|
using TweetDck.Updates;
|
||||||
using TweetDck.Plugins;
|
using TweetDck.Plugins;
|
||||||
|
using TweetDck.Plugins.Enums;
|
||||||
using TweetDck.Plugins.Events;
|
using TweetDck.Plugins.Events;
|
||||||
|
|
||||||
namespace TweetDck.Core{
|
namespace TweetDck.Core{
|
||||||
@@ -33,14 +35,14 @@ namespace TweetDck.Core{
|
|||||||
|
|
||||||
private FormWindowState prevState;
|
private FormWindowState prevState;
|
||||||
|
|
||||||
public FormBrowser(PluginManager pluginManager){
|
public FormBrowser(PluginManager pluginManager, UpdaterSettings updaterSettings){
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
Text = Program.BrandName;
|
Text = Program.BrandName;
|
||||||
|
|
||||||
this.plugins = pluginManager;
|
this.plugins = pluginManager;
|
||||||
this.plugins.Reloaded += plugins_Reloaded;
|
this.plugins.Reloaded += plugins_Reloaded;
|
||||||
this.plugins.Config.PluginChangedState += plugins_PluginChangedState;
|
this.plugins.PluginChangedState += plugins_PluginChangedState;
|
||||||
|
|
||||||
FormNotification notification = CreateNotificationForm(true);
|
FormNotification notification = CreateNotificationForm(true);
|
||||||
notification.CanMoveWindow = () => false;
|
notification.CanMoveWindow = () => false;
|
||||||
@@ -52,6 +54,10 @@ namespace TweetDck.Core{
|
|||||||
LifeSpanHandler = new LifeSpanHandler()
|
LifeSpanHandler = new LifeSpanHandler()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
this.browser.ConsoleMessage += BrowserUtils.HandleConsoleMessage;
|
||||||
|
#endif
|
||||||
|
|
||||||
this.browser.LoadingStateChanged += Browser_LoadingStateChanged;
|
this.browser.LoadingStateChanged += Browser_LoadingStateChanged;
|
||||||
this.browser.FrameLoadEnd += Browser_FrameLoadEnd;
|
this.browser.FrameLoadEnd += Browser_FrameLoadEnd;
|
||||||
this.browser.RegisterJsObject("$TD", new TweetDeckBridge(this, notification));
|
this.browser.RegisterJsObject("$TD", new TweetDeckBridge(this, notification));
|
||||||
@@ -67,7 +73,7 @@ namespace TweetDck.Core{
|
|||||||
|
|
||||||
UpdateTrayIcon();
|
UpdateTrayIcon();
|
||||||
|
|
||||||
this.updates = new UpdateHandler(browser, this);
|
this.updates = new UpdateHandler(browser, this, updaterSettings);
|
||||||
this.updates.UpdateAccepted += updates_UpdateAccepted;
|
this.updates.UpdateAccepted += updates_UpdateAccepted;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,7 +82,7 @@ namespace TweetDck.Core{
|
|||||||
form.Shown += (sender, args) => form.MoveToCenter(this);
|
form.Shown += (sender, args) => form.MoveToCenter(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ForceClose(){
|
public void ForceClose(){
|
||||||
trayIcon.Visible = false; // checked in FormClosing event
|
trayIcon.Visible = false; // checked in FormClosing event
|
||||||
Close();
|
Close();
|
||||||
}
|
}
|
||||||
@@ -113,7 +119,7 @@ namespace TweetDck.Core{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void Browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
|
private void Browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
|
||||||
if (e.Frame.IsMain){
|
if (e.Frame.IsMain && BrowserUtils.IsTweetDeckWebsite(e.Frame)){
|
||||||
ScriptLoader.ExecuteFile(e.Frame, "code.js");
|
ScriptLoader.ExecuteFile(e.Frame, "code.js");
|
||||||
|
|
||||||
if (plugins.HasAnyPlugin(PluginEnvironment.Browser)){
|
if (plugins.HasAnyPlugin(PluginEnvironment.Browser)){
|
||||||
@@ -297,11 +303,11 @@ namespace TweetDck.Core{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void OnImagePasted(){
|
public void OnImagePasted(){
|
||||||
browser.ExecuteScriptAsync("TDGF_tryPasteImage", new object[0]);
|
browser.ExecuteScriptAsync("TDGF_tryPasteImage()");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnImagePastedFinish(){
|
public void OnImagePastedFinish(){
|
||||||
browser.ExecuteScriptAsync("TDGF_tryPasteImageFinish", new object[0]);
|
browser.ExecuteScriptAsync("TDGF_tryPasteImageFinish()");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ReloadBrowser(){
|
public void ReloadBrowser(){
|
||||||
|
@@ -9,6 +9,7 @@ using TweetDck.Core.Handling;
|
|||||||
using TweetDck.Resources;
|
using TweetDck.Resources;
|
||||||
using TweetDck.Core.Utils;
|
using TweetDck.Core.Utils;
|
||||||
using TweetDck.Plugins;
|
using TweetDck.Plugins;
|
||||||
|
using TweetDck.Plugins.Enums;
|
||||||
|
|
||||||
namespace TweetDck.Core{
|
namespace TweetDck.Core{
|
||||||
sealed partial class FormNotification : Form{
|
sealed partial class FormNotification : Form{
|
||||||
@@ -62,6 +63,7 @@ namespace TweetDck.Core{
|
|||||||
public bool FreezeTimer { get; set; }
|
public bool FreezeTimer { get; set; }
|
||||||
public bool ContextMenuOpen { get; set; }
|
public bool ContextMenuOpen { get; set; }
|
||||||
public string CurrentUrl { get; private set; }
|
public string CurrentUrl { get; private set; }
|
||||||
|
public string CurrentQuotedTweetUrl { get; set; }
|
||||||
|
|
||||||
public EventHandler Initialized;
|
public EventHandler Initialized;
|
||||||
private bool isInitialized;
|
private bool isInitialized;
|
||||||
@@ -99,6 +101,10 @@ namespace TweetDck.Core{
|
|||||||
LifeSpanHandler = new LifeSpanHandler()
|
LifeSpanHandler = new LifeSpanHandler()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
browser.ConsoleMessage += BrowserUtils.HandleConsoleMessage;
|
||||||
|
#endif
|
||||||
|
|
||||||
browser.IsBrowserInitializedChanged += Browser_IsBrowserInitializedChanged;
|
browser.IsBrowserInitializedChanged += Browser_IsBrowserInitializedChanged;
|
||||||
browser.FrameLoadEnd += Browser_FrameLoadEnd;
|
browser.FrameLoadEnd += Browser_FrameLoadEnd;
|
||||||
browser.RegisterJsObject("$TD", new TweetDeckBridge(owner, this));
|
browser.RegisterJsObject("$TD", new TweetDeckBridge(owner, this));
|
||||||
@@ -225,7 +231,7 @@ namespace TweetDck.Core{
|
|||||||
tweetQueue.Enqueue(notification);
|
tweetQueue.Enqueue(notification);
|
||||||
UpdateTitle();
|
UpdateTitle();
|
||||||
|
|
||||||
if (!timerProgress.Enabled){
|
if (totalTime == 0){
|
||||||
LoadNextNotification();
|
LoadNextNotification();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -248,6 +254,7 @@ namespace TweetDck.Core{
|
|||||||
Location = new Point(-32000, -32000);
|
Location = new Point(-32000, -32000);
|
||||||
progressBarTimer.Value = Program.UserConfig.NotificationTimerCountDown ? 1000 : 0;
|
progressBarTimer.Value = Program.UserConfig.NotificationTimerCountDown ? 1000 : 0;
|
||||||
timerProgress.Stop();
|
timerProgress.Stop();
|
||||||
|
totalTime = 0;
|
||||||
|
|
||||||
StopMouseHook();
|
StopMouseHook();
|
||||||
}
|
}
|
||||||
@@ -276,6 +283,7 @@ namespace TweetDck.Core{
|
|||||||
|
|
||||||
private void LoadTweet(TweetNotification tweet){
|
private void LoadTweet(TweetNotification tweet){
|
||||||
CurrentUrl = tweet.Url;
|
CurrentUrl = tweet.Url;
|
||||||
|
CurrentQuotedTweetUrl = string.Empty; // load from JS
|
||||||
|
|
||||||
timerProgress.Stop();
|
timerProgress.Stop();
|
||||||
totalTime = timeLeft = tweet.GetDisplayDuration(Program.UserConfig.NotificationDurationValue);
|
totalTime = timeLeft = tweet.GetDisplayDuration(Program.UserConfig.NotificationDurationValue);
|
||||||
|
@@ -18,6 +18,9 @@ namespace TweetDck.Core.Handling{
|
|||||||
|
|
||||||
private readonly FormBrowser form;
|
private readonly FormBrowser form;
|
||||||
|
|
||||||
|
private string lastHighlightedTweet;
|
||||||
|
private string lastHighlightedQuotedTweet;
|
||||||
|
|
||||||
public ContextMenuBrowser(FormBrowser form){
|
public ContextMenuBrowser(FormBrowser form){
|
||||||
this.form = form;
|
this.form = form;
|
||||||
}
|
}
|
||||||
@@ -31,11 +34,19 @@ namespace TweetDck.Core.Handling{
|
|||||||
|
|
||||||
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
|
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(TweetDeckBridge.LastHighlightedTweet) && (parameters.TypeFlags & (ContextMenuType.Editable | ContextMenuType.Selection)) == 0){
|
lastHighlightedTweet = TweetDeckBridge.LastHighlightedTweet;
|
||||||
|
lastHighlightedQuotedTweet = TweetDeckBridge.LastHighlightedQuotedTweet;
|
||||||
|
|
||||||
|
if (!BrowserUtils.IsTweetDeckWebsite(frame)){
|
||||||
|
lastHighlightedTweet = string.Empty;
|
||||||
|
lastHighlightedQuotedTweet = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(lastHighlightedTweet) && (parameters.TypeFlags & (ContextMenuType.Editable | ContextMenuType.Selection)) == 0){
|
||||||
model.AddItem((CefMenuCommand)MenuOpenTweetUrl, "Open tweet in browser");
|
model.AddItem((CefMenuCommand)MenuOpenTweetUrl, "Open tweet in browser");
|
||||||
model.AddItem((CefMenuCommand)MenuCopyTweetUrl, "Copy tweet address");
|
model.AddItem((CefMenuCommand)MenuCopyTweetUrl, "Copy tweet address");
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(TweetDeckBridge.LastHighlightedQuotedTweet)){
|
if (!string.IsNullOrEmpty(lastHighlightedQuotedTweet)){
|
||||||
model.AddSeparator();
|
model.AddSeparator();
|
||||||
model.AddItem((CefMenuCommand)MenuOpenQuotedTweetUrl, "Open quoted tweet in browser");
|
model.AddItem((CefMenuCommand)MenuOpenQuotedTweetUrl, "Open quoted tweet in browser");
|
||||||
model.AddItem((CefMenuCommand)MenuCopyQuotedTweetUrl, "Copy quoted tweet address");
|
model.AddItem((CefMenuCommand)MenuCopyQuotedTweetUrl, "Copy quoted tweet address");
|
||||||
@@ -91,19 +102,19 @@ namespace TweetDck.Core.Handling{
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
case MenuOpenTweetUrl:
|
case MenuOpenTweetUrl:
|
||||||
BrowserUtils.OpenExternalBrowser(TweetDeckBridge.LastHighlightedTweet);
|
BrowserUtils.OpenExternalBrowser(lastHighlightedTweet);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case MenuCopyTweetUrl:
|
case MenuCopyTweetUrl:
|
||||||
Clipboard.SetText(TweetDeckBridge.LastHighlightedTweet, TextDataFormat.UnicodeText);
|
Clipboard.SetText(lastHighlightedTweet, TextDataFormat.UnicodeText);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case MenuOpenQuotedTweetUrl:
|
case MenuOpenQuotedTweetUrl:
|
||||||
BrowserUtils.OpenExternalBrowser(TweetDeckBridge.LastHighlightedQuotedTweet);
|
BrowserUtils.OpenExternalBrowser(lastHighlightedQuotedTweet);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case MenuCopyQuotedTweetUrl:
|
case MenuCopyQuotedTweetUrl:
|
||||||
Clipboard.SetText(TweetDeckBridge.LastHighlightedQuotedTweet, TextDataFormat.UnicodeText);
|
Clipboard.SetText(lastHighlightedQuotedTweet, TextDataFormat.UnicodeText);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -7,7 +7,7 @@ namespace TweetDck.Core.Handling{
|
|||||||
private const int MenuSkipTweet = 26600;
|
private const int MenuSkipTweet = 26600;
|
||||||
private const int MenuFreeze = 26601;
|
private const int MenuFreeze = 26601;
|
||||||
private const int MenuCopyTweetUrl = 26602;
|
private const int MenuCopyTweetUrl = 26602;
|
||||||
private const int MenuCopyTweetEmbeddedUrl = 26603;
|
private const int MenuCopyQuotedTweetUrl = 26603;
|
||||||
|
|
||||||
private readonly FormNotification form;
|
private readonly FormNotification form;
|
||||||
private readonly bool enableCustomMenu;
|
private readonly bool enableCustomMenu;
|
||||||
@@ -29,8 +29,8 @@ namespace TweetDck.Core.Handling{
|
|||||||
if (!string.IsNullOrEmpty(form.CurrentUrl)){
|
if (!string.IsNullOrEmpty(form.CurrentUrl)){
|
||||||
model.AddItem((CefMenuCommand)MenuCopyTweetUrl, "Copy tweet address");
|
model.AddItem((CefMenuCommand)MenuCopyTweetUrl, "Copy tweet address");
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(TweetDeckBridge.NotificationTweetEmbedded)){
|
if (!string.IsNullOrEmpty(form.CurrentQuotedTweetUrl)){
|
||||||
model.AddItem((CefMenuCommand)MenuCopyTweetEmbeddedUrl, "Copy quoted tweet address");
|
model.AddItem((CefMenuCommand)MenuCopyQuotedTweetUrl, "Copy quoted tweet address");
|
||||||
}
|
}
|
||||||
|
|
||||||
model.AddSeparator();
|
model.AddSeparator();
|
||||||
@@ -61,8 +61,8 @@ namespace TweetDck.Core.Handling{
|
|||||||
Clipboard.SetText(form.CurrentUrl, TextDataFormat.UnicodeText);
|
Clipboard.SetText(form.CurrentUrl, TextDataFormat.UnicodeText);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case MenuCopyTweetEmbeddedUrl:
|
case MenuCopyQuotedTweetUrl:
|
||||||
Clipboard.SetText(TweetDeckBridge.NotificationTweetEmbedded, TextDataFormat.UnicodeText);
|
Clipboard.SetText(form.CurrentQuotedTweetUrl, TextDataFormat.UnicodeText);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -11,7 +11,6 @@ namespace TweetDck.Core.Handling{
|
|||||||
public static string LastRightClickedLink = string.Empty;
|
public static string LastRightClickedLink = string.Empty;
|
||||||
public static string LastHighlightedTweet = string.Empty;
|
public static string LastHighlightedTweet = string.Empty;
|
||||||
public static string LastHighlightedQuotedTweet = string.Empty;
|
public static string LastHighlightedQuotedTweet = string.Empty;
|
||||||
public static string NotificationTweetEmbedded = string.Empty;
|
|
||||||
public static string ClipboardImagePath = string.Empty;
|
public static string ClipboardImagePath = string.Empty;
|
||||||
|
|
||||||
private readonly FormBrowser form;
|
private readonly FormBrowser form;
|
||||||
@@ -81,8 +80,8 @@ namespace TweetDck.Core.Handling{
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetNotificationTweetEmbedded(string link){
|
public void SetNotificationQuotedTweet(string link){
|
||||||
form.InvokeSafe(() => NotificationTweetEmbedded = link);
|
notification.InvokeSafe(() => notification.CurrentQuotedTweetUrl = link);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OpenSettingsMenu(){
|
public void OpenSettingsMenu(){
|
||||||
|
4
Core/Other/FormMessage.Designer.cs
generated
4
Core/Other/FormMessage.Designer.cs
generated
@@ -42,8 +42,8 @@
|
|||||||
| System.Windows.Forms.AnchorStyles.Left)
|
| System.Windows.Forms.AnchorStyles.Left)
|
||||||
| System.Windows.Forms.AnchorStyles.Right)));
|
| System.Windows.Forms.AnchorStyles.Right)));
|
||||||
this.labelMessage.AutoSize = true;
|
this.labelMessage.AutoSize = true;
|
||||||
this.labelMessage.Font = new System.Drawing.Font("Microsoft Sans Serif", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
|
this.labelMessage.Font = System.Drawing.SystemFonts.MessageBoxFont;
|
||||||
this.labelMessage.Location = new System.Drawing.Point(62, 33);
|
this.labelMessage.Location = new System.Drawing.Point(62, 34);
|
||||||
this.labelMessage.Margin = new System.Windows.Forms.Padding(53, 24, 27, 24);
|
this.labelMessage.Margin = new System.Windows.Forms.Padding(53, 24, 27, 24);
|
||||||
this.labelMessage.MaximumSize = new System.Drawing.Size(600, 0);
|
this.labelMessage.MaximumSize = new System.Drawing.Size(600, 0);
|
||||||
this.labelMessage.MinimumSize = new System.Drawing.Size(0, 24);
|
this.labelMessage.MinimumSize = new System.Drawing.Size(0, 24);
|
||||||
|
@@ -53,6 +53,7 @@ namespace TweetDck.Core.Other{
|
|||||||
public Button AddButton(string title){
|
public Button AddButton(string title){
|
||||||
Button button = new Button{
|
Button button = new Button{
|
||||||
Anchor = AnchorStyles.Bottom | AnchorStyles.Right,
|
Anchor = AnchorStyles.Bottom | AnchorStyles.Right,
|
||||||
|
Font = SystemFonts.MessageBoxFont,
|
||||||
Location = new Point(Width-112-buttonCount*96, 12),
|
Location = new Point(Width-112-buttonCount*96, 12),
|
||||||
Size = new Size(88, 26),
|
Size = new Size(88, 26),
|
||||||
TabIndex = buttonCount,
|
TabIndex = buttonCount,
|
||||||
|
@@ -6,6 +6,7 @@ using System.Windows.Forms;
|
|||||||
using TweetDck.Core.Controls;
|
using TweetDck.Core.Controls;
|
||||||
using TweetDck.Plugins;
|
using TweetDck.Plugins;
|
||||||
using TweetDck.Plugins.Controls;
|
using TweetDck.Plugins.Controls;
|
||||||
|
using TweetDck.Plugins.Enums;
|
||||||
using TweetDck.Plugins.Events;
|
using TweetDck.Plugins.Events;
|
||||||
|
|
||||||
namespace TweetDck.Core.Other{
|
namespace TweetDck.Core.Other{
|
||||||
|
@@ -4,7 +4,7 @@ using System.Text;
|
|||||||
|
|
||||||
namespace TweetDck.Core.Other.Settings.Export{
|
namespace TweetDck.Core.Other.Settings.Export{
|
||||||
class CombinedFileStream : IDisposable{
|
class CombinedFileStream : IDisposable{
|
||||||
public const char KeySeparator = '/';
|
public const char KeySeparator = '|';
|
||||||
|
|
||||||
private readonly Stream stream;
|
private readonly Stream stream;
|
||||||
|
|
||||||
@@ -12,6 +12,10 @@ namespace TweetDck.Core.Other.Settings.Export{
|
|||||||
this.stream = stream;
|
this.stream = stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void WriteFile(string[] identifier, string path){
|
||||||
|
WriteFile(string.Join(KeySeparator.ToString(), identifier), path);
|
||||||
|
}
|
||||||
|
|
||||||
public void WriteFile(string identifier, string path){
|
public void WriteFile(string identifier, string path){
|
||||||
byte[] name = Encoding.UTF8.GetBytes(identifier);
|
byte[] name = Encoding.UTF8.GetBytes(identifier);
|
||||||
|
|
||||||
@@ -77,6 +81,13 @@ namespace TweetDck.Core.Other.Settings.Export{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string[] KeyValue{
|
||||||
|
get{
|
||||||
|
int index = Identifier.IndexOf(KeySeparator);
|
||||||
|
return index == -1 ? new string[0] : Identifier.Substring(index+1).Split(KeySeparator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private readonly byte[] contents;
|
private readonly byte[] contents;
|
||||||
|
|
||||||
public Entry(string identifier, byte[] contents){
|
public Entry(string identifier, byte[] contents){
|
||||||
|
@@ -4,6 +4,7 @@ using System.IO;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using TweetDck.Plugins;
|
using TweetDck.Plugins;
|
||||||
|
using TweetDck.Plugins.Enums;
|
||||||
|
|
||||||
namespace TweetDck.Core.Other.Settings.Export{
|
namespace TweetDck.Core.Other.Settings.Export{
|
||||||
sealed class ExportManager{
|
sealed class ExportManager{
|
||||||
@@ -26,33 +27,14 @@ namespace TweetDck.Core.Other.Settings.Export{
|
|||||||
using(CombinedFileStream stream = new CombinedFileStream(new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None))){
|
using(CombinedFileStream stream = new CombinedFileStream(new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None))){
|
||||||
stream.WriteFile("config", Program.ConfigFilePath);
|
stream.WriteFile("config", Program.ConfigFilePath);
|
||||||
|
|
||||||
foreach(PathInfo path in EnumerateFilesRelative(plugins.PathOfficialPlugins)){
|
foreach(Plugin plugin in plugins.Plugins){
|
||||||
string[] split = path.Relative.Split(CombinedFileStream.KeySeparator);
|
foreach(PathInfo path in EnumerateFilesRelative(plugin.GetPluginFolder(PluginFolder.Data))){
|
||||||
|
try{
|
||||||
if (split.Length < 3){
|
stream.WriteFile(new string[]{ "plugin.data", plugin.Identifier, path.Relative }, path.Full);
|
||||||
continue;
|
}catch(ArgumentOutOfRangeException e){
|
||||||
}
|
MessageBox.Show("Could not include a plugin file in the export. "+e.Message, "Export Profile", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||||
else if (split.Length == 3){
|
|
||||||
if (split[2].Equals(".meta", StringComparison.OrdinalIgnoreCase) ||
|
|
||||||
split[2].Equals("browser.js", StringComparison.OrdinalIgnoreCase) ||
|
|
||||||
split[2].Equals("notification.js", StringComparison.OrdinalIgnoreCase)){
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try{
|
|
||||||
stream.WriteFile("plugin.off"+path.Relative, path.Full);
|
|
||||||
}catch(ArgumentOutOfRangeException e){
|
|
||||||
MessageBox.Show("Could not include a file in the export. "+e.Message, "Export Profile", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach(PathInfo path in EnumerateFilesRelative(plugins.PathCustomPlugins)){
|
|
||||||
try{
|
|
||||||
stream.WriteFile("plugin.usr"+path.Relative, path.Full);
|
|
||||||
}catch(ArgumentOutOfRangeException e){
|
|
||||||
MessageBox.Show("Could not include a file in the export. "+e.Message, "Export Profile", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (includeSession){
|
if (includeSession){
|
||||||
@@ -71,7 +53,7 @@ namespace TweetDck.Core.Other.Settings.Export{
|
|||||||
|
|
||||||
public bool Import(){
|
public bool Import(){
|
||||||
try{
|
try{
|
||||||
bool updatedPlugins = false;
|
HashSet<string> missingPlugins = new HashSet<string>();
|
||||||
|
|
||||||
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))){
|
||||||
CombinedFileStream.Entry entry;
|
CombinedFileStream.Entry entry;
|
||||||
@@ -83,28 +65,21 @@ namespace TweetDck.Core.Other.Settings.Export{
|
|||||||
Program.ReloadConfig();
|
Program.ReloadConfig();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "plugin.off":
|
case "plugin.data":
|
||||||
string root = Path.Combine(plugins.PathOfficialPlugins, entry.Identifier.Split(CombinedFileStream.KeySeparator)[1]);
|
string[] value = entry.KeyValue;
|
||||||
|
|
||||||
if (Directory.Exists(root)){
|
entry.WriteToFile(Path.Combine(Program.PluginDataPath, value[0], value[1]), true);
|
||||||
entry.WriteToFile(Path.Combine(plugins.PathOfficialPlugins, entry.Identifier.Substring(entry.KeyName.Length+1)), true);
|
|
||||||
updatedPlugins = true;
|
if (!plugins.IsPluginInstalled(value[0])){
|
||||||
|
missingPlugins.Add(value[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "plugin.usr":
|
|
||||||
entry.WriteToFile(Path.Combine(plugins.PathCustomPlugins, entry.Identifier.Substring(entry.KeyName.Length+1)), true);
|
|
||||||
updatedPlugins = true;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "cookies":
|
case "cookies":
|
||||||
if (MessageBox.Show("Do you want to import the login session? This will restart "+Program.BrandName+".", "Importing "+Program.BrandName+" Settings", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2) == DialogResult.Yes){
|
if (MessageBox.Show("Do you want to import the login session? This will restart "+Program.BrandName+".", "Importing "+Program.BrandName+" Profile", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2) == DialogResult.Yes){
|
||||||
entry.WriteToFile(Path.Combine(Program.StoragePath, TempCookiesPath));
|
entry.WriteToFile(Path.Combine(Program.StoragePath, TempCookiesPath));
|
||||||
|
|
||||||
// okay to and restart, 'cookies' is always the last entry
|
|
||||||
IsRestarting = true;
|
IsRestarting = true;
|
||||||
Program.Restart(new string[]{ "-importcookies" });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@@ -112,7 +87,14 @@ namespace TweetDck.Core.Other.Settings.Export{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (updatedPlugins){
|
if (missingPlugins.Count > 0){
|
||||||
|
MessageBox.Show("Detected missing plugins when importing plugin data:"+Environment.NewLine+string.Join(Environment.NewLine, missingPlugins), "Importing "+Program.BrandName+" Profile", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsRestarting){
|
||||||
|
Program.Restart(new string[]{ "-importcookies" });
|
||||||
|
}
|
||||||
|
else{
|
||||||
plugins.Reload();
|
plugins.Reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,10 +120,10 @@ namespace TweetDck.Core.Other.Settings.Export{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static IEnumerable<PathInfo> EnumerateFilesRelative(string root){
|
private static IEnumerable<PathInfo> EnumerateFilesRelative(string root){
|
||||||
return Directory.EnumerateFiles(root, "*.*", SearchOption.AllDirectories).Select(fullPath => new PathInfo{
|
return Directory.Exists(root) ? Directory.EnumerateFiles(root, "*.*", SearchOption.AllDirectories).Select(fullPath => new PathInfo{
|
||||||
Full = fullPath,
|
Full = fullPath,
|
||||||
Relative = fullPath.Substring(root.Length).Replace(Path.DirectorySeparatorChar, CombinedFileStream.KeySeparator) // includes leading separator character
|
Relative = fullPath.Substring(root.Length).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) // strip leading separator character
|
||||||
});
|
}) : Enumerable.Empty<PathInfo>();
|
||||||
}
|
}
|
||||||
|
|
||||||
private class PathInfo{
|
private class PathInfo{
|
||||||
|
64
Core/Other/Settings/TabSettingsAdvanced.Designer.cs
generated
64
Core/Other/Settings/TabSettingsAdvanced.Designer.cs
generated
@@ -29,14 +29,15 @@
|
|||||||
this.toolTip = new System.Windows.Forms.ToolTip(this.components);
|
this.toolTip = new System.Windows.Forms.ToolTip(this.components);
|
||||||
this.btnEditCefArgs = new System.Windows.Forms.Button();
|
this.btnEditCefArgs = new System.Windows.Forms.Button();
|
||||||
this.btnEditCSS = new System.Windows.Forms.Button();
|
this.btnEditCSS = new System.Windows.Forms.Button();
|
||||||
|
this.btnRestartLog = new System.Windows.Forms.Button();
|
||||||
|
this.btnRestart = new System.Windows.Forms.Button();
|
||||||
this.btnReset = new System.Windows.Forms.Button();
|
this.btnReset = new System.Windows.Forms.Button();
|
||||||
this.btnImport = new System.Windows.Forms.Button();
|
this.btnImport = new System.Windows.Forms.Button();
|
||||||
this.btnExport = new System.Windows.Forms.Button();
|
this.btnExport = new System.Windows.Forms.Button();
|
||||||
this.groupPerformance = new System.Windows.Forms.GroupBox();
|
this.groupPerformance = new System.Windows.Forms.GroupBox();
|
||||||
this.groupConfiguration = new System.Windows.Forms.GroupBox();
|
this.groupConfiguration = new System.Windows.Forms.GroupBox();
|
||||||
this.groupApp = new System.Windows.Forms.GroupBox();
|
this.groupApp = new System.Windows.Forms.GroupBox();
|
||||||
this.btnRestartLog = new System.Windows.Forms.Button();
|
this.btnOpenAppFolder = new System.Windows.Forms.Button();
|
||||||
this.btnRestart = new System.Windows.Forms.Button();
|
|
||||||
this.groupPerformance.SuspendLayout();
|
this.groupPerformance.SuspendLayout();
|
||||||
this.groupConfiguration.SuspendLayout();
|
this.groupConfiguration.SuspendLayout();
|
||||||
this.groupApp.SuspendLayout();
|
this.groupApp.SuspendLayout();
|
||||||
@@ -90,6 +91,30 @@
|
|||||||
this.btnEditCSS.UseVisualStyleBackColor = true;
|
this.btnEditCSS.UseVisualStyleBackColor = true;
|
||||||
this.btnEditCSS.Click += new System.EventHandler(this.btnEditCSS_Click);
|
this.btnEditCSS.Click += new System.EventHandler(this.btnEditCSS_Click);
|
||||||
//
|
//
|
||||||
|
// btnRestartLog
|
||||||
|
//
|
||||||
|
this.btnRestartLog.Location = new System.Drawing.Point(6, 77);
|
||||||
|
this.btnRestartLog.Name = "btnRestartLog";
|
||||||
|
this.btnRestartLog.Size = new System.Drawing.Size(171, 23);
|
||||||
|
this.btnRestartLog.TabIndex = 18;
|
||||||
|
this.btnRestartLog.Text = "Restart with Logging";
|
||||||
|
this.toolTip.SetToolTip(this.btnRestartLog, "Restarts the program and enables logging\r\ninto a debug.txt file in the installati" +
|
||||||
|
"on folder.");
|
||||||
|
this.btnRestartLog.UseVisualStyleBackColor = true;
|
||||||
|
this.btnRestartLog.Click += new System.EventHandler(this.btnRestartLog_Click);
|
||||||
|
//
|
||||||
|
// btnRestart
|
||||||
|
//
|
||||||
|
this.btnRestart.Location = new System.Drawing.Point(6, 48);
|
||||||
|
this.btnRestart.Name = "btnRestart";
|
||||||
|
this.btnRestart.Size = new System.Drawing.Size(171, 23);
|
||||||
|
this.btnRestart.TabIndex = 17;
|
||||||
|
this.btnRestart.Text = "Restart the Program";
|
||||||
|
this.toolTip.SetToolTip(this.btnRestart, "Restarts the program using the same command\r\nline arguments that were used at lau" +
|
||||||
|
"nch.");
|
||||||
|
this.btnRestart.UseVisualStyleBackColor = true;
|
||||||
|
this.btnRestart.Click += new System.EventHandler(this.btnRestart_Click);
|
||||||
|
//
|
||||||
// btnReset
|
// btnReset
|
||||||
//
|
//
|
||||||
this.btnReset.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
|
this.btnReset.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
|
||||||
@@ -154,38 +179,26 @@
|
|||||||
//
|
//
|
||||||
// groupApp
|
// groupApp
|
||||||
//
|
//
|
||||||
|
this.groupApp.Controls.Add(this.btnOpenAppFolder);
|
||||||
this.groupApp.Controls.Add(this.btnRestartLog);
|
this.groupApp.Controls.Add(this.btnRestartLog);
|
||||||
this.groupApp.Controls.Add(this.btnRestart);
|
this.groupApp.Controls.Add(this.btnRestart);
|
||||||
this.groupApp.Location = new System.Drawing.Point(198, 9);
|
this.groupApp.Location = new System.Drawing.Point(198, 9);
|
||||||
this.groupApp.Name = "groupApp";
|
this.groupApp.Name = "groupApp";
|
||||||
this.groupApp.Size = new System.Drawing.Size(183, 77);
|
this.groupApp.Size = new System.Drawing.Size(183, 106);
|
||||||
this.groupApp.TabIndex = 20;
|
this.groupApp.TabIndex = 20;
|
||||||
this.groupApp.TabStop = false;
|
this.groupApp.TabStop = false;
|
||||||
this.groupApp.Text = "App";
|
this.groupApp.Text = "App";
|
||||||
//
|
//
|
||||||
// btnRestartLog
|
// btnOpenAppFolder
|
||||||
//
|
//
|
||||||
this.btnRestartLog.Location = new System.Drawing.Point(6, 48);
|
this.btnOpenAppFolder.Location = new System.Drawing.Point(6, 19);
|
||||||
this.btnRestartLog.Name = "btnRestartLog";
|
this.btnOpenAppFolder.Name = "btnOpenAppFolder";
|
||||||
this.btnRestartLog.Size = new System.Drawing.Size(171, 23);
|
this.btnOpenAppFolder.Size = new System.Drawing.Size(171, 23);
|
||||||
this.btnRestartLog.TabIndex = 17;
|
this.btnOpenAppFolder.TabIndex = 16;
|
||||||
this.btnRestartLog.Text = "Restart with Logging";
|
this.btnOpenAppFolder.Text = "Open Program Folder";
|
||||||
this.toolTip.SetToolTip(this.btnRestartLog, "Restarts the program and enables logging\r\ninto a debug.txt file in the installati" +
|
this.toolTip.SetToolTip(this.btnOpenAppFolder, "Opens the folder where the app is located.");
|
||||||
"on folder.");
|
this.btnOpenAppFolder.UseVisualStyleBackColor = true;
|
||||||
this.btnRestartLog.UseVisualStyleBackColor = true;
|
this.btnOpenAppFolder.Click += new System.EventHandler(this.btnOpenAppFolder_Click);
|
||||||
this.btnRestartLog.Click += new System.EventHandler(this.btnRestartLog_Click);
|
|
||||||
//
|
|
||||||
// btnRestart
|
|
||||||
//
|
|
||||||
this.btnRestart.Location = new System.Drawing.Point(6, 19);
|
|
||||||
this.btnRestart.Name = "btnRestart";
|
|
||||||
this.btnRestart.Size = new System.Drawing.Size(171, 23);
|
|
||||||
this.btnRestart.TabIndex = 16;
|
|
||||||
this.btnRestart.Text = "Restart the Program";
|
|
||||||
this.toolTip.SetToolTip(this.btnRestart, "Restarts the program using the same command\r\nline arguments that were used at lau" +
|
|
||||||
"nch.");
|
|
||||||
this.btnRestart.UseVisualStyleBackColor = true;
|
|
||||||
this.btnRestart.Click += new System.EventHandler(this.btnRestart_Click);
|
|
||||||
//
|
//
|
||||||
// TabSettingsAdvanced
|
// TabSettingsAdvanced
|
||||||
//
|
//
|
||||||
@@ -223,5 +236,6 @@
|
|||||||
private System.Windows.Forms.GroupBox groupApp;
|
private System.Windows.Forms.GroupBox groupApp;
|
||||||
private System.Windows.Forms.Button btnRestartLog;
|
private System.Windows.Forms.Button btnRestartLog;
|
||||||
private System.Windows.Forms.Button btnRestart;
|
private System.Windows.Forms.Button btnRestart;
|
||||||
|
private System.Windows.Forms.Button btnOpenAppFolder;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using TweetDck.Core.Controls;
|
using TweetDck.Core.Controls;
|
||||||
using TweetDck.Core.Other.Settings.Dialogs;
|
using TweetDck.Core.Other.Settings.Dialogs;
|
||||||
@@ -156,6 +157,10 @@ namespace TweetDck.Core.Other.Settings{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void btnOpenAppFolder_Click(object sender, EventArgs e){
|
||||||
|
using(Process.Start("explorer.exe", "\""+Program.ProgramPath+"\"")){}
|
||||||
|
}
|
||||||
|
|
||||||
private void btnRestart_Click(object sender, EventArgs e){
|
private void btnRestart_Click(object sender, EventArgs e){
|
||||||
Program.Restart();
|
Program.Restart();
|
||||||
}
|
}
|
||||||
|
@@ -4,6 +4,7 @@ using System.Globalization;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
|
using CefSharp;
|
||||||
|
|
||||||
namespace TweetDck.Core.Utils{
|
namespace TweetDck.Core.Utils{
|
||||||
static class BrowserUtils{
|
static class BrowserUtils{
|
||||||
@@ -27,7 +28,7 @@ namespace TweetDck.Core.Utils{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void OpenExternalBrowser(string url){ // TODO implement mailto
|
public static void OpenExternalBrowser(string url){ // TODO implement mailto
|
||||||
Process.Start(url);
|
using(Process.Start(url)){}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GetFileNameFromUrl(string url){
|
public static string GetFileNameFromUrl(string url){
|
||||||
@@ -47,5 +48,15 @@ namespace TweetDck.Core.Utils{
|
|||||||
|
|
||||||
client.DownloadFileAsync(new Uri(url), target);
|
client.DownloadFileAsync(new Uri(url), target);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool IsTweetDeckWebsite(IFrame frame){
|
||||||
|
return frame.Url.Contains("//tweetdeck.twitter.com/");
|
||||||
|
}
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
public static void HandleConsoleMessage(object sender, ConsoleMessageEventArgs e){
|
||||||
|
Debug.WriteLine("[Console] {0} ({1}:{2})", e.Message, e.Source, e.Line);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -57,6 +57,10 @@ namespace TweetDck.Core.Utils{
|
|||||||
values[key.ToLowerInvariant()] = value;
|
values[key.ToLowerInvariant()] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool HasValue(string key){
|
||||||
|
return values.ContainsKey(key.ToLowerInvariant());
|
||||||
|
}
|
||||||
|
|
||||||
public string GetValue(string key, string defaultValue){
|
public string GetValue(string key, string defaultValue){
|
||||||
string val;
|
string val;
|
||||||
return values.TryGetValue(key.ToLowerInvariant(), out val) ? val : defaultValue;
|
return values.TryGetValue(key.ToLowerInvariant(), out val) ? val : defaultValue;
|
||||||
|
@@ -15,6 +15,7 @@ namespace TweetDck.Core.Utils{
|
|||||||
public const int MOUSEEVENTF_RIGHTUP = 0x10;
|
public const int MOUSEEVENTF_RIGHTUP = 0x10;
|
||||||
|
|
||||||
public const int SB_HORZ = 0;
|
public const int SB_HORZ = 0;
|
||||||
|
public const int BCM_SETSHIELD = 0x160C;
|
||||||
|
|
||||||
public const int WH_MOUSE_LL = 14;
|
public const int WH_MOUSE_LL = 14;
|
||||||
public const int WH_MOUSEWHEEL = 0x020A;
|
public const int WH_MOUSEWHEEL = 0x020A;
|
||||||
|
@@ -1,33 +1,33 @@
|
|||||||
using System.IO;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.IO;
|
||||||
using System.Security.AccessControl;
|
|
||||||
using System.Security.Principal;
|
|
||||||
|
|
||||||
namespace TweetDck.Core.Utils{
|
namespace TweetDck.Core.Utils{
|
||||||
static class WindowsUtils{
|
static class WindowsUtils{
|
||||||
public static bool CheckFolderPermission(string path, FileSystemRights right){
|
public static bool CheckFolderWritePermission(string path){
|
||||||
|
string testFile = Path.Combine(path, ".test");
|
||||||
|
|
||||||
try{
|
try{
|
||||||
AuthorizationRuleCollection rules = Directory.GetAccessControl(path).GetAccessRules(true, true, typeof(SecurityIdentifier));
|
Directory.CreateDirectory(path);
|
||||||
WindowsIdentity identity = WindowsIdentity.GetCurrent();
|
|
||||||
|
|
||||||
if (identity == null || identity.Groups == null){
|
using(File.Create(testFile)){}
|
||||||
return false;
|
File.Delete(testFile);
|
||||||
}
|
return true;
|
||||||
|
}catch{
|
||||||
bool accessAllow = false, accessDeny = false;
|
|
||||||
|
|
||||||
foreach(FileSystemAccessRule rule in rules.Cast<FileSystemAccessRule>().Where(rule => identity.Groups.Contains(rule.IdentityReference) && (right & rule.FileSystemRights) == right)){
|
|
||||||
switch(rule.AccessControlType){
|
|
||||||
case AccessControlType.Allow: accessAllow = true; break;
|
|
||||||
case AccessControlType.Deny: accessDeny = true; break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return accessAllow && !accessDeny;
|
|
||||||
}
|
|
||||||
catch{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Process StartProcess(string file, string arguments, bool runElevated){
|
||||||
|
ProcessStartInfo processInfo = new ProcessStartInfo{
|
||||||
|
FileName = file,
|
||||||
|
Arguments = arguments
|
||||||
|
};
|
||||||
|
|
||||||
|
if (runElevated){
|
||||||
|
processInfo.Verb = "runas";
|
||||||
|
}
|
||||||
|
|
||||||
|
return Process.Start(processInfo);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
77
Migration/FormBackgroundWork.Designer.cs
generated
77
Migration/FormBackgroundWork.Designer.cs
generated
@@ -1,77 +0,0 @@
|
|||||||
using TweetDck.Core.Controls;
|
|
||||||
|
|
||||||
namespace TweetDck.Migration {
|
|
||||||
partial class FormBackgroundWork {
|
|
||||||
/// <summary>
|
|
||||||
/// Required designer variable.
|
|
||||||
/// </summary>
|
|
||||||
private System.ComponentModel.IContainer components = null;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Clean up any resources being used.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
|
||||||
protected override void Dispose(bool disposing) {
|
|
||||||
if (disposing && (components != null)) {
|
|
||||||
components.Dispose();
|
|
||||||
}
|
|
||||||
base.Dispose(disposing);
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Windows Form Designer generated code
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Required method for Designer support - do not modify
|
|
||||||
/// the contents of this method with the code editor.
|
|
||||||
/// </summary>
|
|
||||||
private void InitializeComponent() {
|
|
||||||
this.progressBarUseless = new System.Windows.Forms.ProgressBar();
|
|
||||||
this.labelDescription = new System.Windows.Forms.Label();
|
|
||||||
this.SuspendLayout();
|
|
||||||
//
|
|
||||||
// progressBarUseless
|
|
||||||
//
|
|
||||||
this.progressBarUseless.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)
|
|
||||||
| System.Windows.Forms.AnchorStyles.Right)));
|
|
||||||
this.progressBarUseless.Location = new System.Drawing.Point(15, 52);
|
|
||||||
this.progressBarUseless.MarqueeAnimationSpeed = 10;
|
|
||||||
this.progressBarUseless.Name = "progressBarUseless";
|
|
||||||
this.progressBarUseless.Size = new System.Drawing.Size(477, 23);
|
|
||||||
this.progressBarUseless.Style = System.Windows.Forms.ProgressBarStyle.Marquee;
|
|
||||||
this.progressBarUseless.TabIndex = 0;
|
|
||||||
//
|
|
||||||
// labelDescription
|
|
||||||
//
|
|
||||||
this.labelDescription.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
|
||||||
| System.Windows.Forms.AnchorStyles.Left)
|
|
||||||
| System.Windows.Forms.AnchorStyles.Right)));
|
|
||||||
this.labelDescription.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
|
|
||||||
this.labelDescription.Location = new System.Drawing.Point(12, 12);
|
|
||||||
this.labelDescription.Name = "labelDescription";
|
|
||||||
this.labelDescription.Size = new System.Drawing.Size(480, 37);
|
|
||||||
this.labelDescription.TabIndex = 1;
|
|
||||||
this.labelDescription.Text = "Please, watch this informationless progress bar showcase while some magic happens" +
|
|
||||||
" in the background...";
|
|
||||||
//
|
|
||||||
// FormBackgroundWork
|
|
||||||
//
|
|
||||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
|
||||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
|
||||||
this.ClientSize = new System.Drawing.Size(504, 87);
|
|
||||||
this.Controls.Add(this.labelDescription);
|
|
||||||
this.Controls.Add(this.progressBarUseless);
|
|
||||||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
|
|
||||||
this.Name = "FormBackgroundWork";
|
|
||||||
this.ShowIcon = false;
|
|
||||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
|
|
||||||
this.Text = "TweetDeck Migration";
|
|
||||||
this.ResumeLayout(false);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
private System.Windows.Forms.ProgressBar progressBarUseless;
|
|
||||||
private System.Windows.Forms.Label labelDescription;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,15 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Windows.Forms;
|
|
||||||
|
|
||||||
namespace TweetDck.Migration{
|
|
||||||
partial class FormBackgroundWork : Form{
|
|
||||||
public FormBackgroundWork(){
|
|
||||||
InitializeComponent();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ShowWorkDialog(Action onBegin){
|
|
||||||
Shown += (sender, args) => onBegin();
|
|
||||||
ShowDialog();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,23 +0,0 @@
|
|||||||
namespace TweetDck.Migration{
|
|
||||||
enum MigrationDecision{
|
|
||||||
/// <summary>
|
|
||||||
/// Copies the important files and then deletes the TweetDeck folder.
|
|
||||||
/// </summary>
|
|
||||||
Migrate,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Does exactly what <see cref="Migrate"/> does, but also silently uninstalls TweetDeck.
|
|
||||||
/// </summary>
|
|
||||||
MigratePurge,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Does not copy any files and does not ask the user about data migration again.
|
|
||||||
/// </summary>
|
|
||||||
Ignore,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Does not copy any files but asks the user again when the program is re-ran.
|
|
||||||
/// </summary>
|
|
||||||
AskLater
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,192 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Windows.Forms;
|
|
||||||
using Microsoft.Win32;
|
|
||||||
using TweetDck.Core.Other;
|
|
||||||
using System.Drawing;
|
|
||||||
|
|
||||||
namespace TweetDck.Migration{
|
|
||||||
static class MigrationManager{
|
|
||||||
private static readonly string TweetDeckPathParent = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "twitter");
|
|
||||||
private static readonly string TweetDeckPath = Path.Combine(TweetDeckPathParent, "TweetDeck");
|
|
||||||
|
|
||||||
private static readonly string TweetDickStorage = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "TweetDick");
|
|
||||||
|
|
||||||
public static void Run(){
|
|
||||||
if (!Program.IsPortable && Directory.Exists(TweetDickStorage) && !Directory.Exists(Program.StoragePath)){
|
|
||||||
if (MessageBox.Show("Welcome to TweetDuck! Would you like to move your old TweetDick configuration and login data?", "TweetDick Migration", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes){
|
|
||||||
try{
|
|
||||||
Directory.Move(TweetDickStorage, Program.StoragePath);
|
|
||||||
MessageBox.Show("All done! You can now uninstall TweetDick.", "TweetDick Migration", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
|
||||||
}catch(Exception ex){
|
|
||||||
Program.Reporter.HandleException("Migration Error", "An unexpected error occurred during the migration process.", true, ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Program.UserConfig.IgnoreMigration && Directory.Exists(TweetDeckPath)){
|
|
||||||
MigrationDecision decision;
|
|
||||||
|
|
||||||
const string prompt = "Hey there, I found some TweetDeck data! Do you want to »Migrate« it and delete the old data folder, »Ignore« the prompt, or try "+Program.BrandName+" out first? You may also »Migrate && Purge« which uninstalls TweetDeck too!";
|
|
||||||
|
|
||||||
using(FormMessage formQuestion = new FormMessage("TweetDeck Migration", prompt, MessageBoxIcon.Question)){
|
|
||||||
formQuestion.AddButton("Ask Later");
|
|
||||||
Button btnIgnore = formQuestion.AddButton("Ignore");
|
|
||||||
Button btnMigrate = formQuestion.AddButton("Migrate");
|
|
||||||
Button btnMigrateAndPurge = formQuestion.AddButton("Migrate && Purge");
|
|
||||||
|
|
||||||
btnMigrateAndPurge.Location = new Point(btnMigrateAndPurge.Location.X-18, btnMigrateAndPurge.Location.Y);
|
|
||||||
btnMigrateAndPurge.Width += 18;
|
|
||||||
|
|
||||||
if (formQuestion.ShowDialog() == DialogResult.OK){
|
|
||||||
decision = formQuestion.ClickedButton == btnMigrateAndPurge ? MigrationDecision.MigratePurge :
|
|
||||||
formQuestion.ClickedButton == btnMigrate ? MigrationDecision.Migrate :
|
|
||||||
formQuestion.ClickedButton == btnIgnore ? MigrationDecision.Ignore : MigrationDecision.AskLater;
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
decision = MigrationDecision.AskLater;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch(decision){
|
|
||||||
case MigrationDecision.MigratePurge:
|
|
||||||
case MigrationDecision.Migrate:
|
|
||||||
FormBackgroundWork formWait = new FormBackgroundWork();
|
|
||||||
|
|
||||||
formWait.ShowWorkDialog(() => {
|
|
||||||
if (!BeginMigration(decision, ex => formWait.Invoke(new Action(() => {
|
|
||||||
formWait.Close();
|
|
||||||
|
|
||||||
if (ex != null){
|
|
||||||
Program.Reporter.HandleException("Migration Error", "An unexpected error occurred during the migration process.", true, ex);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Program.UserConfig.IgnoreMigration = true;
|
|
||||||
Program.UserConfig.Save();
|
|
||||||
})))){
|
|
||||||
formWait.Close();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case MigrationDecision.Ignore:
|
|
||||||
Program.UserConfig.IgnoreMigration = true;
|
|
||||||
Program.UserConfig.Save();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (!Program.UserConfig.IgnoreUninstallCheck){
|
|
||||||
string guid = MigrationUtils.FindProgramGuidByDisplayName("TweetDeck");
|
|
||||||
|
|
||||||
if (guid != null && MessageBox.Show("TweetDeck is still installed on your computer, do you want to uninstall it?", "Uninstall TweetDeck", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2) == DialogResult.Yes){
|
|
||||||
MigrationUtils.RunUninstaller(guid, 0);
|
|
||||||
CleanupTweetDeck();
|
|
||||||
}
|
|
||||||
|
|
||||||
Program.UserConfig.IgnoreUninstallCheck = true;
|
|
||||||
Program.UserConfig.Save();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool BeginMigration(MigrationDecision decision, Action<Exception> onFinished){
|
|
||||||
if (decision != MigrationDecision.MigratePurge && decision != MigrationDecision.Migrate){
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Task task = new Task(() => {
|
|
||||||
Directory.CreateDirectory(Program.StoragePath);
|
|
||||||
Directory.CreateDirectory(Path.Combine(Program.StoragePath, "localStorage"));
|
|
||||||
Directory.CreateDirectory(Path.Combine(Program.StoragePath, "Local Storage"));
|
|
||||||
|
|
||||||
CopyFile("Cookies");
|
|
||||||
CopyFile("Cookies-journal");
|
|
||||||
CopyFile("localStorage"+Path.DirectorySeparatorChar+"qrc__0.localstorage");
|
|
||||||
CopyFile("Local Storage"+Path.DirectorySeparatorChar+"https_tweetdeck.twitter.com_0.localstorage");
|
|
||||||
CopyFile("Local Storage"+Path.DirectorySeparatorChar+"https_tweetdeck.twitter.com_0.localstorage-journal");
|
|
||||||
|
|
||||||
if (decision == MigrationDecision.Migrate || decision == MigrationDecision.MigratePurge){
|
|
||||||
// kill process if running
|
|
||||||
Process runningProcess = null;
|
|
||||||
|
|
||||||
try{
|
|
||||||
runningProcess = Process.GetProcessesByName("TweetDeck").FirstOrDefault(process => process.MainWindowHandle != IntPtr.Zero);
|
|
||||||
}catch(Exception){
|
|
||||||
// process not found
|
|
||||||
}
|
|
||||||
|
|
||||||
if (runningProcess != null){
|
|
||||||
runningProcess.CloseMainWindow();
|
|
||||||
|
|
||||||
for(int wait = 0; wait < 100 && !runningProcess.HasExited; wait++){ // 10 seconds
|
|
||||||
runningProcess.Refresh();
|
|
||||||
Thread.Sleep(100);
|
|
||||||
}
|
|
||||||
|
|
||||||
runningProcess.Close();
|
|
||||||
}
|
|
||||||
|
|
||||||
// delete folders
|
|
||||||
for(int wait = 0; wait < 50; wait++){
|
|
||||||
try{
|
|
||||||
Directory.Delete(TweetDeckPath, true);
|
|
||||||
break;
|
|
||||||
}catch(Exception){
|
|
||||||
// browser subprocess not ended yet, wait
|
|
||||||
Thread.Sleep(300);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try{
|
|
||||||
Directory.Delete(TweetDeckPathParent, false);
|
|
||||||
}catch(IOException){
|
|
||||||
// most likely not empty, ignore
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (decision == MigrationDecision.MigratePurge){
|
|
||||||
// uninstall in the background
|
|
||||||
string guid = MigrationUtils.FindProgramGuidByDisplayName("TweetDeck");
|
|
||||||
|
|
||||||
if (guid != null){
|
|
||||||
MigrationUtils.RunUninstaller(guid, 5000);
|
|
||||||
}
|
|
||||||
|
|
||||||
// registry cleanup
|
|
||||||
CleanupTweetDeck();
|
|
||||||
|
|
||||||
// migration finished like a boss
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
task.ContinueWith(originalTask => onFinished(originalTask.Exception), TaskContinuationOptions.ExecuteSynchronously);
|
|
||||||
task.Start();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void CopyFile(string relativePath){
|
|
||||||
try{
|
|
||||||
File.Copy(Path.Combine(TweetDeckPath, relativePath), Path.Combine(Program.StoragePath, relativePath), true);
|
|
||||||
}catch(FileNotFoundException){
|
|
||||||
}catch(DirectoryNotFoundException){
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void CleanupTweetDeck(){
|
|
||||||
try{
|
|
||||||
Registry.CurrentUser.DeleteSubKeyTree(@"Software\Twitter\TweetDeck", true);
|
|
||||||
Registry.CurrentUser.DeleteSubKey(@"Software\Twitter"); // only if empty
|
|
||||||
}catch(Exception){
|
|
||||||
// not found or too bad
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,56 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Linq;
|
|
||||||
using Microsoft.Win32;
|
|
||||||
|
|
||||||
namespace TweetDck.Migration{
|
|
||||||
static class MigrationUtils{
|
|
||||||
public static void RunUninstaller(string guid, int timeout){
|
|
||||||
Process uninstaller = Process.Start("msiexec.exe", "/x "+guid+" /quiet /qn");
|
|
||||||
|
|
||||||
if (uninstaller != null){
|
|
||||||
if (timeout > 0){
|
|
||||||
uninstaller.WaitForExit(timeout); // it appears that the process is restarted or something that triggers this, but it shouldn't be a problem
|
|
||||||
}
|
|
||||||
|
|
||||||
uninstaller.Close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string FindProgramGuidByDisplayName(string displayName){
|
|
||||||
Predicate<RegistryKey> predicate = key => displayName.Equals(key.GetValue("DisplayName") as string, StringComparison.OrdinalIgnoreCase);
|
|
||||||
string guid;
|
|
||||||
|
|
||||||
return FindMatchingSubKey(Registry.LocalMachine, @"SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall", predicate, out guid) ||
|
|
||||||
FindMatchingSubKey(Registry.LocalMachine, @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall", predicate, out guid) ||
|
|
||||||
FindMatchingSubKey(Registry.CurrentUser, @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall", predicate, out guid)
|
|
||||||
? guid : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool FindMatchingSubKey(RegistryKey keyHandle, string path, Predicate<RegistryKey> predicate, out string guid){
|
|
||||||
string outputId = null;
|
|
||||||
|
|
||||||
try{
|
|
||||||
RegistryKey parentKey = keyHandle.OpenSubKey(path, false);
|
|
||||||
if (parentKey == null)throw new InvalidOperationException();
|
|
||||||
|
|
||||||
foreach(RegistryKey subKey in parentKey.GetSubKeyNames().Select(subName => parentKey.OpenSubKey(subName, false)).Where(subKey => subKey != null)){
|
|
||||||
if (predicate(subKey)){
|
|
||||||
outputId = subKey.Name.Substring(subKey.Name.LastIndexOf('\\')+1);
|
|
||||||
subKey.Close();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
subKey.Close();
|
|
||||||
}
|
|
||||||
|
|
||||||
parentKey.Close();
|
|
||||||
}catch(Exception){
|
|
||||||
guid = null;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (guid = outputId) != null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -50,7 +50,7 @@ namespace TweetDck.Plugins.Controls{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void btnOpenConfig_Click(object sender, EventArgs e){
|
private void btnOpenConfig_Click(object sender, EventArgs e){
|
||||||
using(Process.Start("explorer.exe", "/select,\""+plugin.ConfigPath+"\"")){}
|
using(Process.Start("explorer.exe", "/select,\""+plugin.ConfigPath.Replace('/', '\\')+"\"")){}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void btnToggleState_Click(object sender, EventArgs e){
|
private void btnToggleState_Click(object sender, EventArgs e){
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace TweetDck.Plugins{
|
namespace TweetDck.Plugins.Enums{
|
||||||
[Flags]
|
[Flags]
|
||||||
enum PluginEnvironment{
|
enum PluginEnvironment{
|
||||||
None = 0,
|
None = 0,
|
5
Plugins/Enums/PluginFolder.cs
Normal file
5
Plugins/Enums/PluginFolder.cs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
namespace TweetDck.Plugins.Enums{
|
||||||
|
enum PluginFolder{
|
||||||
|
Root, Data
|
||||||
|
}
|
||||||
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
namespace TweetDck.Plugins{
|
namespace TweetDck.Plugins.Enums{
|
||||||
enum PluginGroup{
|
enum PluginGroup{
|
||||||
Official, Custom
|
Official, Custom
|
||||||
}
|
}
|
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using TweetDck.Plugins.Enums;
|
||||||
|
|
||||||
namespace TweetDck.Plugins{
|
namespace TweetDck.Plugins{
|
||||||
class Plugin{
|
class Plugin{
|
||||||
@@ -26,29 +27,30 @@ namespace TweetDck.Plugins{
|
|||||||
|
|
||||||
public bool HasConfig{
|
public bool HasConfig{
|
||||||
get{
|
get{
|
||||||
return ConfigFile.Length > 0 && GetFullPathIfSafe(ConfigFile).Length > 0;
|
return ConfigFile.Length > 0 && GetFullPathIfSafe(PluginFolder.Data, ConfigFile).Length > 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public string ConfigPath{
|
public string ConfigPath{
|
||||||
get{
|
get{
|
||||||
return HasConfig ? Path.Combine(path, ConfigFile) : string.Empty;
|
return HasConfig ? Path.Combine(GetPluginFolder(PluginFolder.Data), ConfigFile) : string.Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool HasDefaultConfig{
|
public bool HasDefaultConfig{
|
||||||
get{
|
get{
|
||||||
return ConfigDefault.Length > 0 && GetFullPathIfSafe(ConfigDefault).Length > 0;
|
return ConfigDefault.Length > 0 && GetFullPathIfSafe(PluginFolder.Root, ConfigDefault).Length > 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public string DefaultConfigPath{
|
public string DefaultConfigPath{
|
||||||
get{
|
get{
|
||||||
return HasDefaultConfig ? Path.Combine(path, ConfigDefault) : string.Empty;
|
return HasDefaultConfig ? Path.Combine(GetPluginFolder(PluginFolder.Root), ConfigDefault) : string.Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly string path;
|
private readonly string pathRoot;
|
||||||
|
private readonly string pathData;
|
||||||
private readonly string identifier;
|
private readonly string identifier;
|
||||||
private readonly Dictionary<string, string> metadata = new Dictionary<string, string>(4){
|
private readonly Dictionary<string, string> metadata = new Dictionary<string, string>(4){
|
||||||
{ "NAME", "" },
|
{ "NAME", "" },
|
||||||
@@ -64,8 +66,13 @@ namespace TweetDck.Plugins{
|
|||||||
private bool? canRun;
|
private bool? canRun;
|
||||||
|
|
||||||
private Plugin(string path, PluginGroup group){
|
private Plugin(string path, PluginGroup group){
|
||||||
this.path = path;
|
string name = Path.GetFileName(path);
|
||||||
this.identifier = group.GetIdentifierPrefix()+Path.GetFileName(path);
|
System.Diagnostics.Debug.Assert(name != null);
|
||||||
|
|
||||||
|
this.pathRoot = path;
|
||||||
|
this.pathData = Path.Combine(Program.PluginDataPath, group.GetIdentifierPrefix(), name);
|
||||||
|
|
||||||
|
this.identifier = group.GetIdentifierPrefix()+name;
|
||||||
this.Group = group;
|
this.Group = group;
|
||||||
this.Environments = PluginEnvironment.None;
|
this.Environments = PluginEnvironment.None;
|
||||||
}
|
}
|
||||||
@@ -74,7 +81,26 @@ namespace TweetDck.Plugins{
|
|||||||
string configPath = ConfigPath, defaultConfigPath = DefaultConfigPath;
|
string configPath = ConfigPath, defaultConfigPath = DefaultConfigPath;
|
||||||
|
|
||||||
if (configPath.Length > 0 && defaultConfigPath.Length > 0 && !File.Exists(configPath) && File.Exists(defaultConfigPath)){
|
if (configPath.Length > 0 && defaultConfigPath.Length > 0 && !File.Exists(configPath) && File.Exists(defaultConfigPath)){
|
||||||
|
string dataFolder = GetPluginFolder(PluginFolder.Data);
|
||||||
|
|
||||||
|
if (!Directory.Exists(dataFolder)){ // config migration
|
||||||
|
string originalFile = Path.Combine(GetPluginFolder(PluginFolder.Root), ConfigFile);
|
||||||
|
|
||||||
|
if (File.Exists(originalFile)){
|
||||||
|
try{
|
||||||
|
Directory.CreateDirectory(dataFolder);
|
||||||
|
File.Copy(originalFile, configPath, false);
|
||||||
|
File.Delete(originalFile); // will fail without write perms in program folder, ignore if so
|
||||||
|
}catch{
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try{
|
try{
|
||||||
|
Directory.CreateDirectory(dataFolder);
|
||||||
File.Copy(defaultConfigPath, configPath, false);
|
File.Copy(defaultConfigPath, configPath, false);
|
||||||
}catch(Exception e){
|
}catch(Exception e){
|
||||||
Program.Reporter.HandleException("Plugin Loading Error", "Could not generate a configuration file for '"+identifier+"' plugin.", true, e);
|
Program.Reporter.HandleException("Plugin Loading Error", "Could not generate a configuration file for '"+identifier+"' plugin.", true, e);
|
||||||
@@ -85,18 +111,27 @@ namespace TweetDck.Plugins{
|
|||||||
public string GetScriptPath(PluginEnvironment environment){
|
public string GetScriptPath(PluginEnvironment environment){
|
||||||
if (Environments.HasFlag(environment)){
|
if (Environments.HasFlag(environment)){
|
||||||
string file = environment.GetScriptFile();
|
string file = environment.GetScriptFile();
|
||||||
return file != null ? Path.Combine(path, file) : string.Empty;
|
return file != null ? Path.Combine(pathRoot, file) : string.Empty;
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetFullPathIfSafe(string relativePath){
|
public string GetPluginFolder(PluginFolder folder){
|
||||||
string fullPath = Path.Combine(path, relativePath);
|
switch(folder){
|
||||||
|
case PluginFolder.Root: return pathRoot;
|
||||||
|
case PluginFolder.Data: return pathData;
|
||||||
|
default: return string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetFullPathIfSafe(PluginFolder folder, string relativePath){
|
||||||
|
string rootFolder = GetPluginFolder(folder);
|
||||||
|
string fullPath = Path.Combine(rootFolder, relativePath);
|
||||||
|
|
||||||
try{
|
try{
|
||||||
string folderPathName = new DirectoryInfo(path).FullName;
|
string folderPathName = new DirectoryInfo(rootFolder).FullName;
|
||||||
DirectoryInfo currentInfo = new DirectoryInfo(fullPath);
|
DirectoryInfo currentInfo = new DirectoryInfo(fullPath);
|
||||||
|
|
||||||
while(currentInfo.Parent != null){
|
while(currentInfo.Parent != null){
|
||||||
@@ -124,7 +159,7 @@ namespace TweetDck.Plugins{
|
|||||||
|
|
||||||
public override bool Equals(object obj){
|
public override bool Equals(object obj){
|
||||||
Plugin plugin = obj as Plugin;
|
Plugin plugin = obj as Plugin;
|
||||||
return plugin != null && plugin.path.Equals(path);
|
return plugin != null && plugin.identifier.Equals(identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Plugin CreateFromFolder(string path, PluginGroup group, out string error){
|
public static Plugin CreateFromFolder(string path, PluginGroup group, out string error){
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using TweetDck.Plugins.Enums;
|
||||||
using TweetDck.Plugins.Events;
|
using TweetDck.Plugins.Events;
|
||||||
|
|
||||||
namespace TweetDck.Plugins{
|
namespace TweetDck.Plugins{
|
||||||
@@ -18,35 +19,26 @@ namespace TweetDck.Plugins{
|
|||||||
fileCache.Clear();
|
fileCache.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetFullPathIfSafe(int token, string path){
|
private string GetFullPathOrThrow(int token, PluginFolder folder, string path){
|
||||||
Plugin plugin = manager.GetPluginFromToken(token);
|
Plugin plugin = manager.GetPluginFromToken(token);
|
||||||
return plugin == null ? string.Empty : plugin.GetFullPathIfSafe(path);
|
string fullPath = plugin == null ? string.Empty : plugin.GetFullPathIfSafe(folder, path);
|
||||||
}
|
|
||||||
|
|
||||||
public void WriteFile(int token, string path, string contents){
|
|
||||||
string fullPath = GetFullPathIfSafe(token, path);
|
|
||||||
|
|
||||||
if (fullPath == string.Empty){
|
|
||||||
throw new Exception("File path has to be relative to the plugin folder.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReSharper disable once AssignNullToNotNullAttribute
|
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(fullPath));
|
|
||||||
|
|
||||||
File.WriteAllText(fullPath, contents, Encoding.UTF8);
|
|
||||||
fileCache[fullPath] = contents;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string ReadFile(int token, string path, bool cache){
|
|
||||||
string fullPath = GetFullPathIfSafe(token, path);
|
|
||||||
|
|
||||||
if (fullPath.Length == 0){
|
if (fullPath.Length == 0){
|
||||||
throw new Exception("File path has to be relative to the plugin folder.");
|
switch(folder){
|
||||||
|
case PluginFolder.Data: throw new Exception("File path has to be relative to the plugin data folder.");
|
||||||
|
case PluginFolder.Root: throw new Exception("File path has to be relative to the plugin root folder.");
|
||||||
|
default: throw new Exception("Invalid folder type "+folder+", this is a "+Program.BrandName+" error.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
else{
|
||||||
|
return fullPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string ReadFileUnsafe(string fullPath, bool readCached){
|
||||||
string cachedContents;
|
string cachedContents;
|
||||||
|
|
||||||
if (cache && fileCache.TryGetValue(fullPath, out cachedContents)){
|
if (readCached && fileCache.TryGetValue(fullPath, out cachedContents)){
|
||||||
return cachedContents;
|
return cachedContents;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,25 +51,39 @@ namespace TweetDck.Plugins{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DeleteFile(int token, string path){
|
// Public methods
|
||||||
string fullPath = GetFullPathIfSafe(token, path);
|
|
||||||
|
public void WriteFile(int token, string path, string contents){
|
||||||
|
string fullPath = GetFullPathOrThrow(token, PluginFolder.Data, path);
|
||||||
|
|
||||||
|
// ReSharper disable once AssignNullToNotNullAttribute
|
||||||
|
Directory.CreateDirectory(Path.GetDirectoryName(fullPath));
|
||||||
|
|
||||||
|
File.WriteAllText(fullPath, contents, Encoding.UTF8);
|
||||||
|
fileCache[fullPath] = contents;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ReadFile(int token, string path, bool cache){
|
||||||
|
return ReadFileUnsafe(GetFullPathOrThrow(token, PluginFolder.Data, path), cache);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DeleteFile(int token, string path){
|
||||||
|
string fullPath = GetFullPathOrThrow(token, PluginFolder.Data, path);
|
||||||
|
|
||||||
if (fullPath.Length == 0){
|
|
||||||
throw new Exception("File path has to be relative to the plugin folder.");
|
|
||||||
}
|
|
||||||
|
|
||||||
fileCache.Remove(fullPath);
|
fileCache.Remove(fullPath);
|
||||||
File.Delete(fullPath);
|
File.Delete(fullPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool CheckFileExists(int token, string path){
|
public bool CheckFileExists(int token, string path){
|
||||||
string fullPath = GetFullPathIfSafe(token, path);
|
return File.Exists(GetFullPathOrThrow(token, PluginFolder.Data, path));
|
||||||
|
}
|
||||||
|
|
||||||
if (fullPath.Length == 0){
|
public string ReadFileRoot(int token, string path){
|
||||||
throw new Exception("File path has to be relative to the plugin folder.");
|
return ReadFileUnsafe(GetFullPathOrThrow(token, PluginFolder.Root, path), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
return File.Exists(fullPath);
|
public bool CheckFileExistsRoot(int token, string path){
|
||||||
|
return File.Exists(GetFullPathOrThrow(token, PluginFolder.Root, path));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -6,7 +6,7 @@ namespace TweetDck.Plugins{
|
|||||||
[Serializable]
|
[Serializable]
|
||||||
sealed class PluginConfig{
|
sealed class PluginConfig{
|
||||||
[field:NonSerialized]
|
[field:NonSerialized]
|
||||||
public event EventHandler<PluginChangedStateEventArgs> PluginChangedState;
|
public event EventHandler<PluginChangedStateEventArgs> InternalPluginChangedState; // should only be accessed from PluginManager
|
||||||
|
|
||||||
public IEnumerable<string> DisabledPlugins{
|
public IEnumerable<string> DisabledPlugins{
|
||||||
get{
|
get{
|
||||||
@@ -24,8 +24,8 @@ namespace TweetDck.Plugins{
|
|||||||
|
|
||||||
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))){
|
||||||
if (PluginChangedState != null){
|
if (InternalPluginChangedState != null){
|
||||||
PluginChangedState(this, new PluginChangedStateEventArgs(plugin, enabled));
|
InternalPluginChangedState(this, new PluginChangedStateEventArgs(plugin, enabled));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using CefSharp;
|
using CefSharp;
|
||||||
|
using TweetDck.Plugins.Enums;
|
||||||
using TweetDck.Plugins.Events;
|
using TweetDck.Plugins.Events;
|
||||||
using TweetDck.Resources;
|
using TweetDck.Resources;
|
||||||
|
|
||||||
@@ -20,6 +21,7 @@ namespace TweetDck.Plugins{
|
|||||||
public PluginBridge Bridge { get; private set; }
|
public PluginBridge Bridge { get; private set; }
|
||||||
|
|
||||||
public event EventHandler<PluginLoadEventArgs> Reloaded;
|
public event EventHandler<PluginLoadEventArgs> Reloaded;
|
||||||
|
public event EventHandler<PluginChangedStateEventArgs> PluginChangedState;
|
||||||
|
|
||||||
private readonly string rootPath;
|
private readonly string rootPath;
|
||||||
private readonly HashSet<Plugin> plugins = new HashSet<Plugin>();
|
private readonly HashSet<Plugin> plugins = new HashSet<Plugin>();
|
||||||
@@ -30,10 +32,25 @@ namespace TweetDck.Plugins{
|
|||||||
|
|
||||||
public PluginManager(string path, PluginConfig config){
|
public PluginManager(string path, PluginConfig config){
|
||||||
this.rootPath = path;
|
this.rootPath = path;
|
||||||
this.Config = config;
|
this.SetConfig(config);
|
||||||
this.Bridge = new PluginBridge(this);
|
this.Bridge = new PluginBridge(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetConfig(PluginConfig config){
|
||||||
|
this.Config = config;
|
||||||
|
this.Config.InternalPluginChangedState += Config_InternalPluginChangedState;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Config_InternalPluginChangedState(object sender, PluginChangedStateEventArgs e){
|
||||||
|
if (PluginChangedState != null){
|
||||||
|
PluginChangedState(this, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsPluginInstalled(string identifier){
|
||||||
|
return plugins.Any(plugin => plugin.Identifier.Equals(identifier));
|
||||||
|
}
|
||||||
|
|
||||||
public IEnumerable<Plugin> GetPluginsByGroup(PluginGroup group){
|
public IEnumerable<Plugin> GetPluginsByGroup(PluginGroup group){
|
||||||
return plugins.Where(plugin => plugin.Group == group);
|
return plugins.Where(plugin => plugin.Group == group);
|
||||||
}
|
}
|
||||||
@@ -103,6 +120,10 @@ namespace TweetDck.Plugins{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<Plugin> LoadPluginsFrom(string path, PluginGroup group){
|
private IEnumerable<Plugin> LoadPluginsFrom(string path, PluginGroup group){
|
||||||
|
if (!Directory.Exists(path)){
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
foreach(string fullDir in Directory.EnumerateDirectories(path, "*", SearchOption.TopDirectoryOnly)){
|
foreach(string fullDir in Directory.EnumerateDirectories(path, "*", SearchOption.TopDirectoryOnly)){
|
||||||
string error;
|
string error;
|
||||||
Plugin plugin = Plugin.CreateFromFolder(fullDir, group, out error);
|
Plugin plugin = Plugin.CreateFromFolder(fullDir, group, out error);
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
|
using TweetDck.Plugins.Enums;
|
||||||
|
|
||||||
namespace TweetDck.Plugins{
|
namespace TweetDck.Plugins{
|
||||||
static class PluginScriptGenerator{
|
static class PluginScriptGenerator{
|
||||||
|
104
Program.cs
104
Program.cs
@@ -5,7 +5,6 @@ using System.Windows.Forms;
|
|||||||
using CefSharp;
|
using CefSharp;
|
||||||
using TweetDck.Configuration;
|
using TweetDck.Configuration;
|
||||||
using TweetDck.Core;
|
using TweetDck.Core;
|
||||||
using TweetDck.Migration;
|
|
||||||
using TweetDck.Core.Utils;
|
using TweetDck.Core.Utils;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
@@ -13,7 +12,8 @@ using TweetDck.Plugins;
|
|||||||
using TweetDck.Plugins.Events;
|
using TweetDck.Plugins.Events;
|
||||||
using TweetDck.Core.Other.Settings.Export;
|
using TweetDck.Core.Other.Settings.Export;
|
||||||
using TweetDck.Core.Handling;
|
using TweetDck.Core.Handling;
|
||||||
using System.Security.AccessControl;
|
using TweetDck.Core.Other;
|
||||||
|
using TweetDck.Updates;
|
||||||
|
|
||||||
[assembly: CLSCompliant(true)]
|
[assembly: CLSCompliant(true)]
|
||||||
namespace TweetDck{
|
namespace TweetDck{
|
||||||
@@ -21,10 +21,8 @@ namespace TweetDck{
|
|||||||
public const string BrandName = "TweetDuck";
|
public const string BrandName = "TweetDuck";
|
||||||
public const string Website = "https://tweetduck.chylex.com";
|
public const string Website = "https://tweetduck.chylex.com";
|
||||||
|
|
||||||
public const string BrowserSubprocess = BrandName+".Browser.exe";
|
public const string VersionTag = "1.5.1";
|
||||||
|
public const string VersionFull = "1.5.1.0";
|
||||||
public const string VersionTag = "1.4";
|
|
||||||
public const string VersionFull = "1.4.0.0";
|
|
||||||
|
|
||||||
public static readonly Version Version = new Version(VersionTag);
|
public static readonly Version Version = new Version(VersionTag);
|
||||||
|
|
||||||
@@ -35,6 +33,7 @@ namespace TweetDck{
|
|||||||
public static readonly string StoragePath = IsPortable ? Path.Combine(ProgramPath, "portable", "storage") : GetDataStoragePath();
|
public static readonly string StoragePath = IsPortable ? Path.Combine(ProgramPath, "portable", "storage") : GetDataStoragePath();
|
||||||
public static readonly string TemporaryPath = IsPortable ? Path.Combine(ProgramPath, "portable", "tmp") : Path.Combine(Path.GetTempPath(), BrandName+'_'+Path.GetRandomFileName().Substring(0, 6));
|
public static readonly string TemporaryPath = IsPortable ? Path.Combine(ProgramPath, "portable", "tmp") : Path.Combine(Path.GetTempPath(), BrandName+'_'+Path.GetRandomFileName().Substring(0, 6));
|
||||||
|
|
||||||
|
public static readonly string PluginDataPath = Path.Combine(StoragePath, "TD_Plugins");
|
||||||
public static readonly string ConfigFilePath = Path.Combine(StoragePath, "TD_UserConfig.cfg");
|
public static readonly string ConfigFilePath = Path.Combine(StoragePath, "TD_UserConfig.cfg");
|
||||||
private static readonly string LogFilePath = Path.Combine(StoragePath, "TD_Log.txt");
|
private static readonly string LogFilePath = Path.Combine(StoragePath, "TD_Log.txt");
|
||||||
|
|
||||||
@@ -56,48 +55,55 @@ namespace TweetDck{
|
|||||||
|
|
||||||
WindowRestoreMessage = NativeMethods.RegisterWindowMessage("TweetDuckRestore");
|
WindowRestoreMessage = NativeMethods.RegisterWindowMessage("TweetDuckRestore");
|
||||||
|
|
||||||
if (!WindowsUtils.CheckFolderPermission(ProgramPath, FileSystemRights.WriteData)){
|
if (!WindowsUtils.CheckFolderWritePermission(StoragePath)){
|
||||||
MessageBox.Show(BrandName+" does not have write permissions to the program folder. If it is installed in Program Files, please run it as Administrator.", "Administrator Required", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
MessageBox.Show(BrandName+" does not have write permissions to the storage folder: "+StoragePath, "Permission Error", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Reporter = new Reporter(LogFilePath);
|
Reporter = new Reporter(LogFilePath);
|
||||||
|
Reporter.SetupUnhandledExceptionHandler(BrandName+" Has Failed :(");
|
||||||
AppDomain.CurrentDomain.UnhandledException += (sender, args) => {
|
|
||||||
Exception ex = args.ExceptionObject as Exception;
|
|
||||||
|
|
||||||
if (ex != null){
|
|
||||||
Reporter.HandleException(BrandName+" Has Failed :(", "An unhandled exception has occurred.", false, ex);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (Args.HasFlag("-restart")){
|
if (Args.HasFlag("-restart")){
|
||||||
for(int attempt = 0; attempt < 41; attempt++){
|
for(int attempt = 0; attempt < 21; attempt++){
|
||||||
if (LockManager.Lock()){
|
LockManager.Result lockResult = LockManager.Lock();
|
||||||
|
|
||||||
|
if (lockResult == LockManager.Result.Success){
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
else if (attempt == 40){
|
else if (lockResult == LockManager.Result.Fail){
|
||||||
MessageBox.Show(BrandName+" is taking too long to close, please wait and then start the application again manually.", BrandName+" Cannot Restart", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
MessageBox.Show("An unknown error occurred accessing the data folder. Please, make sure "+BrandName+" is not already running. If the problem persists, try restarting your system.", BrandName+" Has Failed :(", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
else if (attempt == 20){
|
||||||
|
using(FormMessage form = new FormMessage(BrandName+" Cannot Restart", BrandName+" is taking too long to close.", MessageBoxIcon.Warning)){
|
||||||
|
form.AddButton("Exit");
|
||||||
|
Button btnRetry = form.AddButton("Retry");
|
||||||
|
|
||||||
|
if (form.ShowDialog() == DialogResult.OK){
|
||||||
|
if (form.ClickedButton == btnRetry){
|
||||||
|
attempt /= 2;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
else{
|
else{
|
||||||
Thread.Sleep(500);
|
Thread.Sleep(500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ReloadConfig();
|
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
ReloadConfig();
|
LockManager.Result lockResult = LockManager.Lock();
|
||||||
MigrationManager.Run();
|
|
||||||
|
|
||||||
if (!LockManager.Lock()){
|
if (lockResult == LockManager.Result.HasProcess){
|
||||||
if (LockManager.LockingProcess.MainWindowHandle == IntPtr.Zero && LockManager.LockingProcess.Responding){ // restore if the original process is in tray
|
if (LockManager.LockingProcess.MainWindowHandle == IntPtr.Zero && LockManager.LockingProcess.Responding){ // restore if the original process is in tray
|
||||||
NativeMethods.SendMessage(NativeMethods.HWND_BROADCAST, WindowRestoreMessage, 0, IntPtr.Zero);
|
NativeMethods.SendMessage(NativeMethods.HWND_BROADCAST, WindowRestoreMessage, 0, IntPtr.Zero);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else if (MessageBox.Show("Another instance of "+BrandName+" is already running.\r\nDo you want to close it?", BrandName+" is Already Running", MessageBoxButtons.YesNo, MessageBoxIcon.Error, MessageBoxDefaultButton.Button2) == DialogResult.Yes){
|
else if (MessageBox.Show("Another instance of "+BrandName+" is already running.\r\nDo you want to close it?", BrandName+" is Already Running", MessageBoxButtons.YesNo, MessageBoxIcon.Error, MessageBoxDefaultButton.Button2) == DialogResult.Yes){
|
||||||
if (!LockManager.CloseLockingProcess(20000)){
|
if (!LockManager.CloseLockingProcess(30000)){
|
||||||
MessageBox.Show("Could not close the other process.", BrandName+" Has Failed :(", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
MessageBox.Show("Could not close the other process.", BrandName+" Has Failed :(", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -106,8 +112,14 @@ namespace TweetDck{
|
|||||||
}
|
}
|
||||||
else return;
|
else return;
|
||||||
}
|
}
|
||||||
|
else if (lockResult != LockManager.Result.Success){
|
||||||
|
MessageBox.Show("An unknown error occurred accessing the data folder. Please, make sure "+BrandName+" is not already running. If the problem persists, try restarting your system.", BrandName+" Has Failed :(", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ReloadConfig();
|
||||||
|
|
||||||
if (Args.HasFlag("-importcookies")){
|
if (Args.HasFlag("-importcookies")){
|
||||||
ExportManager.ImportCookies();
|
ExportManager.ImportCookies();
|
||||||
}
|
}
|
||||||
@@ -119,30 +131,42 @@ namespace TweetDck{
|
|||||||
UserAgent = BrowserUtils.HeaderUserAgent,
|
UserAgent = BrowserUtils.HeaderUserAgent,
|
||||||
Locale = Args.GetValue("-locale", "en"),
|
Locale = Args.GetValue("-locale", "en"),
|
||||||
CachePath = StoragePath,
|
CachePath = StoragePath,
|
||||||
BrowserSubprocessPath = File.Exists(BrowserSubprocess) ? BrowserSubprocess : "CefSharp.BrowserSubprocess.exe",
|
LogFile = Path.Combine(StoragePath, "TD_Console.txt"),
|
||||||
#if !DEBUG
|
#if !DEBUG
|
||||||
|
BrowserSubprocessPath = BrandName+".Browser.exe",
|
||||||
LogSeverity = Args.HasFlag("-log") ? LogSeverity.Info : LogSeverity.Disable
|
LogSeverity = Args.HasFlag("-log") ? LogSeverity.Info : LogSeverity.Disable
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
CommandLineArgsParser.ReadCefArguments(UserConfig.CustomCefArgs).ToDictionary(settings.CefCommandLineArgs);
|
CommandLineArgsParser.ReadCefArguments(UserConfig.CustomCefArgs).ToDictionary(settings.CefCommandLineArgs);
|
||||||
|
|
||||||
|
if (!HardwareAcceleration.IsEnabled){
|
||||||
|
settings.CefCommandLineArgs["disable-gpu"] = "1";
|
||||||
|
settings.CefCommandLineArgs["disable-gpu-vsync"] = "1";
|
||||||
|
}
|
||||||
|
|
||||||
Cef.Initialize(settings, false, new BrowserProcessHandler());
|
Cef.Initialize(settings, false, new BrowserProcessHandler());
|
||||||
|
|
||||||
Application.ApplicationExit += (sender, args) => ExitCleanup();
|
Application.ApplicationExit += (sender, args) => ExitCleanup();
|
||||||
|
|
||||||
PluginManager plugins = new PluginManager(PluginPath, UserConfig.Plugins);
|
PluginManager plugins = new PluginManager(PluginPath, UserConfig.Plugins);
|
||||||
plugins.Reloaded += plugins_Reloaded;
|
plugins.Reloaded += plugins_Reloaded;
|
||||||
plugins.Config.PluginChangedState += (sender, args) => UserConfig.Save();
|
plugins.PluginChangedState += (sender, args) => UserConfig.Save();
|
||||||
plugins.Reload();
|
plugins.Reload();
|
||||||
|
|
||||||
FormBrowser mainForm = new FormBrowser(plugins);
|
FormBrowser mainForm = new FormBrowser(plugins, new UpdaterSettings{
|
||||||
|
AllowPreReleases = Args.HasFlag("-debugupdates")
|
||||||
|
});
|
||||||
|
|
||||||
Application.Run(mainForm);
|
Application.Run(mainForm);
|
||||||
|
|
||||||
if (mainForm.UpdateInstallerPath != null){
|
if (mainForm.UpdateInstallerPath != null){
|
||||||
ExitCleanup();
|
ExitCleanup();
|
||||||
|
|
||||||
Process.Start(mainForm.UpdateInstallerPath, "/SP- /SILENT /NOICONS /CLOSEAPPLICATIONS");
|
string updaterArgs = "/SP- /SILENT /CLOSEAPPLICATIONS /UPDATEPATH=\""+ProgramPath+"\" /RUNARGS=\""+GetArgsClean().ToString().Replace("\"", "^\"")+"\""+(IsPortable ? " /PORTABLE=1" : "");
|
||||||
|
bool runElevated = !IsPortable || !WindowsUtils.CheckFolderWritePermission(ProgramPath);
|
||||||
|
|
||||||
|
WindowsUtils.StartProcess(mainForm.UpdateInstallerPath, updaterArgs, runElevated); // ProgramPath has a trailing backslash
|
||||||
Application.Exit();
|
Application.Exit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -151,6 +175,8 @@ namespace TweetDck{
|
|||||||
if (!e.Success){
|
if (!e.Success){
|
||||||
MessageBox.Show("The following plugins will not be available until the issues are resolved:\n"+string.Join("\n", e.Errors), "Error Loading Plugins", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
MessageBox.Show("The following plugins will not be available until the issues are resolved:\n"+string.Join("\n", e.Errors), "Error Loading Plugins", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
((PluginManager)sender).SetConfig(UserConfig.Plugins);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GetDataStoragePath(){
|
private static string GetDataStoragePath(){
|
||||||
@@ -187,17 +213,31 @@ namespace TweetDck{
|
|||||||
ReloadConfig();
|
ReloadConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static CommandLineArgs GetArgsClean(){
|
||||||
|
CommandLineArgs args = Args.Clone();
|
||||||
|
args.RemoveFlag("-importcookies");
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
public static void Restart(){
|
public static void Restart(){
|
||||||
Restart(new string[0]);
|
Restart(new string[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Restart(string[] extraArgs){
|
public static void Restart(string[] extraArgs){
|
||||||
CommandLineArgs args = Args.Clone();
|
FormBrowser browserForm = Application.OpenForms.OfType<FormBrowser>().FirstOrDefault();
|
||||||
|
|
||||||
|
if (browserForm == null){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CommandLineArgs args = GetArgsClean();
|
||||||
args.AddFlag("-restart");
|
args.AddFlag("-restart");
|
||||||
args.RemoveFlag("-importcookies");
|
|
||||||
|
|
||||||
CommandLineArgs.ReadStringArray('-', extraArgs, args);
|
CommandLineArgs.ReadStringArray('-', extraArgs, args);
|
||||||
|
|
||||||
|
browserForm.ForceClose();
|
||||||
|
ExitCleanup();
|
||||||
|
|
||||||
Process.Start(Application.ExecutablePath, args.ToString());
|
Process.Start(Application.ExecutablePath, args.ToString());
|
||||||
Application.Exit();
|
Application.Exit();
|
||||||
}
|
}
|
||||||
|
18
Reporter.cs
18
Reporter.cs
@@ -15,6 +15,16 @@ namespace TweetDck{
|
|||||||
this.logFile = logFile;
|
this.logFile = logFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetupUnhandledExceptionHandler(string caption){
|
||||||
|
AppDomain.CurrentDomain.UnhandledException += (sender, args) => {
|
||||||
|
Exception ex = args.ExceptionObject as Exception;
|
||||||
|
|
||||||
|
if (ex != null){
|
||||||
|
HandleException(caption, "An unhandled exception has occurred.", false, ex);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public bool Log(string data){
|
public bool Log(string data){
|
||||||
StringBuilder build = new StringBuilder();
|
StringBuilder build = new StringBuilder();
|
||||||
|
|
||||||
@@ -34,7 +44,7 @@ namespace TweetDck{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void HandleException(string caption, string message, bool canIgnore, Exception e){
|
public void HandleException(string caption, string message, bool canIgnore, Exception e){
|
||||||
Log(e.ToString());
|
bool loggedSuccessfully = Log(e.ToString());
|
||||||
|
|
||||||
FormMessage form = new FormMessage(caption, message+"\r\nError: "+e.Message, canIgnore ? MessageBoxIcon.Warning : MessageBoxIcon.Error);
|
FormMessage form = new FormMessage(caption, message+"\r\nError: "+e.Message, canIgnore ? MessageBoxIcon.Warning : MessageBoxIcon.Error);
|
||||||
|
|
||||||
@@ -43,6 +53,8 @@ namespace TweetDck{
|
|||||||
|
|
||||||
Button btnOpenLog = new Button{
|
Button btnOpenLog = new Button{
|
||||||
Anchor = AnchorStyles.Bottom | AnchorStyles.Left,
|
Anchor = AnchorStyles.Bottom | AnchorStyles.Left,
|
||||||
|
Enabled = loggedSuccessfully,
|
||||||
|
Font = SystemFonts.MessageBoxFont,
|
||||||
Location = new Point(12, 12),
|
Location = new Point(12, 12),
|
||||||
Margin = new Padding(0, 0, 48, 0),
|
Margin = new Padding(0, 0, 48, 0),
|
||||||
Size = new Size(88, 26),
|
Size = new Size(88, 26),
|
||||||
@@ -50,7 +62,9 @@ namespace TweetDck{
|
|||||||
UseVisualStyleBackColor = true
|
UseVisualStyleBackColor = true
|
||||||
};
|
};
|
||||||
|
|
||||||
btnOpenLog.Click += (sender, args) => Process.Start(logFile);
|
btnOpenLog.Click += (sender, args) => {
|
||||||
|
using(Process.Start(logFile)){}
|
||||||
|
};
|
||||||
|
|
||||||
form.AddActionControl(btnOpenLog);
|
form.AddActionControl(btnOpenLog);
|
||||||
|
|
||||||
|
@@ -8,7 +8,7 @@ Custom reply account
|
|||||||
chylex
|
chylex
|
||||||
|
|
||||||
[version]
|
[version]
|
||||||
1.1
|
1.2
|
||||||
|
|
||||||
[website]
|
[website]
|
||||||
https://tweetduck.chylex.com
|
https://tweetduck.chylex.com
|
||||||
|
@@ -6,7 +6,7 @@ enabled(){
|
|||||||
this.lastSelectedAccount = null;
|
this.lastSelectedAccount = null;
|
||||||
|
|
||||||
this.uiComposeTweetEvent = (e, data) => {
|
this.uiComposeTweetEvent = (e, data) => {
|
||||||
if (data.type !== "reply"){
|
if (data.type !== "reply" || data.popFromInline || !("element" in data)){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -14,8 +14,25 @@ enabled(){
|
|||||||
|
|
||||||
if (configuration.useAdvancedSelector){
|
if (configuration.useAdvancedSelector){
|
||||||
if (configuration.customSelector){
|
if (configuration.customSelector){
|
||||||
var column = TD.controller.columnManager.get(data.element.closest("section.column").attr("data-column"));
|
if (configuration.customSelector.toString().startsWith("function (column){")){
|
||||||
query = configuration.customSelector(column);
|
$TD.alert("warning", "Plugin reply-account has invalid configuration: customSelector needs to be updated due to TweetDeck changes, please read the default configuration file for the updated guide");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var section = data.element.closest("section.column");
|
||||||
|
|
||||||
|
var column = TD.controller.columnManager.get(section.attr("data-column"));
|
||||||
|
var header = $("h1.column-title", section);
|
||||||
|
|
||||||
|
var columnTitle = header.children(".column-head-title").text();
|
||||||
|
var columnAccount = header.children(".attribution").text();
|
||||||
|
|
||||||
|
try{
|
||||||
|
query = configuration.customSelector(column.getColumnType(), columnTitle, columnAccount, column);
|
||||||
|
}catch(e){
|
||||||
|
$TD.alert("warning", "Plugin reply-account has invalid configuration: customSelector threw an error: "+e.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
$TD.alert("warning", "Plugin reply-account has invalid configuration: useAdvancedSelector is true, but customSelector function is missing");
|
$TD.alert("warning", "Plugin reply-account has invalid configuration: useAdvancedSelector is true, but customSelector function is missing");
|
||||||
|
@@ -34,42 +34,43 @@
|
|||||||
* The 'customSelector' function should return a string in one of the formats supported by 'defaultAccount'.
|
* The 'customSelector' function should return a string in one of the formats supported by 'defaultAccount'.
|
||||||
* If it returns anything else (for example, false or undefined), it falls back to 'defaultAccount' behavior.
|
* If it returns anything else (for example, false or undefined), it falls back to 'defaultAccount' behavior.
|
||||||
*
|
*
|
||||||
* The 'column' parameter is a TweetDeck column object. If you want to see all properties of the object, open your browser, nagivate to TweetDeck,
|
|
||||||
* log in, and run the following code in your browser console, which will return an object containing all of the column objects mapped to their IDs:
|
|
||||||
* TD.controller.columnManager.getAll()
|
|
||||||
*
|
|
||||||
* The example below shows how to extract the column type, title, and account from the object.
|
|
||||||
* Column type is prefixed with col_, and may be one of the following:
|
|
||||||
*
|
*
|
||||||
|
* The 'type' parameter is TweetDeck column type. Here is the full list of column types, note that some are
|
||||||
|
* unused and have misleading names (for example, Home columns are 'col_timeline' instead of 'col_home'):
|
||||||
* col_timeline, col_interactions, col_mentions, col_followers, col_search, col_list,
|
* col_timeline, col_interactions, col_mentions, col_followers, col_search, col_list,
|
||||||
* col_customtimeline, col_messages, col_usertweets, col_favorites, col_activity,
|
* col_customtimeline, col_messages, col_usertweets, col_favorites, col_activity,
|
||||||
* col_dataminr, col_home, col_me, col_inbox, col_scheduled, col_unknown
|
* col_dataminr, col_home, col_me, col_inbox, col_scheduled, col_unknown
|
||||||
*
|
*
|
||||||
* Some of these appear to be unused (for example, Home columns are 'col_timeline' instead of 'col_home').
|
* If you want to see your current column types, run this in your browser console:
|
||||||
|
* TD.controller.columnManager.getAllOrdered().map(obj => obj.getColumnType());
|
||||||
*
|
*
|
||||||
* If you want to see your column types, run this in your browser console:
|
*
|
||||||
* Object.values(TD.controller.columnManager.getAll()).forEach(obj => console.log(obj.getColumnType()));
|
* The 'title' parameter is the column title. Some are fixed (such as 'Home' or 'Notifications'),
|
||||||
*
|
* some contain specific information (for example, Search columns contain the search query).
|
||||||
* You can also get the jQuery column object using: $("section.column[data-column='"+column.ui.state.columnKey+"']")
|
*
|
||||||
|
*
|
||||||
|
* The 'account' parameter is the account name displayed next to the column title (including the @).
|
||||||
|
* This parameter is empty for some columns (such as Messages, or Notifications for all accounts) or can
|
||||||
|
* contain other text (for example, the Scheduled column contains the string 'All accounts').
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* The 'column' parameter is a TweetDeck column object. If you want to see all properties of the object,
|
||||||
|
* run the following code in your browser console, which will return an array containing all of your
|
||||||
|
* current column objects in order:
|
||||||
|
* TD.controller.columnManager.getAllOrdered()
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
useAdvancedSelector: false,
|
useAdvancedSelector: false,
|
||||||
|
|
||||||
/*customSelector: function(column){
|
/*customSelector: function(type, title, account, column){
|
||||||
var titleObj = $(column.getTitleHTML());
|
if (type === "col_search" && title === "TweetDuck"){
|
||||||
|
|
||||||
var columnType = column.getColumnType(); // col_timeline
|
|
||||||
var columnTitle = titleObj.siblings(".column-head-title").text(); // Home
|
|
||||||
var columnAccount = titleObj.siblings(".attribution").text(); // @chylexmc
|
|
||||||
|
|
||||||
if (columnType === "col_search" && columnTitle === "TweetDuck"){
|
|
||||||
// This is a search column that looks for 'TweetDuck' in the tweets,
|
// This is a search column that looks for 'TweetDuck' in the tweets,
|
||||||
// search columns are normally linked to the preferred account
|
// search columns are normally linked to the preferred account
|
||||||
// so this forces the @TryTweetDuck account to be used instead.
|
// so this forces the @TryTweetDuck account to be used instead.
|
||||||
return "@TryTweetDuck";
|
return "@TryTweetDuck";
|
||||||
}
|
}
|
||||||
else if (columnType === "col_timeline" && columnAccount === "@chylexcz"){
|
else if (type === "col_timeline" && account === "@chylexcz"){
|
||||||
// This is a Home column of my test account @chylexcz,
|
// This is a Home column of my test account @chylexcz,
|
||||||
// but I want to reply to tweets from my official account.
|
// but I want to reply to tweets from my official account.
|
||||||
return "@chylexmc";
|
return "@chylexmc";
|
||||||
|
@@ -6,15 +6,11 @@ constructor(){
|
|||||||
|
|
||||||
enabled(){
|
enabled(){
|
||||||
// add a stylesheet
|
// add a stylesheet
|
||||||
this.css = window.TDPF_createCustomStyle(this);
|
window.TDPF_createCustomStyle(this).insert(".column-detail .timeline-poll-container { display: none }");
|
||||||
this.css.insert(".column-detail .timeline-poll-container { display: none }");
|
|
||||||
|
|
||||||
// setup layout injecting
|
// setup layout injecting
|
||||||
this.prevMustaches = {};
|
|
||||||
|
|
||||||
var injectLayout = (mustache, onlyIfNotFound, search, replace) => {
|
var injectLayout = (mustache, onlyIfNotFound, search, replace) => {
|
||||||
if (TD.mustaches[mustache].indexOf(onlyIfNotFound) === -1){
|
if (TD.mustaches[mustache].indexOf(onlyIfNotFound) === -1){
|
||||||
this.prevMustaches[mustache] = TD.mustaches[mustache];
|
|
||||||
TD.mustaches[mustache] = TD.mustaches[mustache].replace(search, replace);
|
TD.mustaches[mustache] = TD.mustaches[mustache].replace(search, replace);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -25,6 +21,5 @@ enabled(){
|
|||||||
}
|
}
|
||||||
|
|
||||||
disabled(){
|
disabled(){
|
||||||
this.css.remove();
|
// not needed, plugin reloads the page when enabled or disabled
|
||||||
Object.keys(this.prevMustaches).forEach(mustache => TD.mustaches[mustache] = this.prevMustaches[mustache]);
|
|
||||||
}
|
}
|
@@ -104,11 +104,14 @@
|
|||||||
var html = $(tweet.render({
|
var html = $(tweet.render({
|
||||||
withFooter: false,
|
withFooter: false,
|
||||||
withTweetActions: false,
|
withTweetActions: false,
|
||||||
withMediaPreview: false
|
withMediaPreview: true,
|
||||||
|
isMediaPreviewOff: true,
|
||||||
|
isMediaPreviewSmall: false,
|
||||||
|
isMediaPreviewLarge: false
|
||||||
}));
|
}));
|
||||||
|
|
||||||
html.css("border", "0");
|
html.css("border", "0");
|
||||||
html.find(".tweet-body").first().children("footer").remove();
|
html.find("footer").last().remove(); // apparently withTweetActions breaks for certain tweets, nice
|
||||||
|
|
||||||
var url = html.find("time").first().children("a").first().attr("href") || "";
|
var url = html.find("time").first().children("a").first().attr("href") || "";
|
||||||
|
|
||||||
@@ -376,32 +379,39 @@
|
|||||||
//
|
//
|
||||||
// Block: Support for extra mouse buttons.
|
// Block: Support for extra mouse buttons.
|
||||||
//
|
//
|
||||||
window.TDGF_onMouseClickExtra = function(button){
|
(function(){
|
||||||
if (button === 1){ // back button
|
var tryClickSelector = function(selector, parent){
|
||||||
var inlineComposer, drawerComposer, modal;
|
return $(selector, parent).click().length;
|
||||||
|
};
|
||||||
if ((modal = $("#open-modal")).is(":visible")){
|
|
||||||
modal.find("a[rel=dismiss]").click();
|
var tryCloseModal = function(){
|
||||||
|
var modal = $("#open-modal");
|
||||||
|
return modal.is(":visible") && tryClickSelector("a[rel=dismiss]", modal);
|
||||||
|
};
|
||||||
|
|
||||||
|
var tryCloseHighlightedColumn = function(){
|
||||||
|
if (highlightedColumnEle){
|
||||||
|
var column = highlightedColumnEle.closest(".js-column");
|
||||||
|
return (column.is(".is-shifted-2") && tryClickSelector(".js-tweet-social-proof-back", column)) || (column.is(".is-shifted-1") && tryClickSelector(".js-column-back", column));
|
||||||
}
|
}
|
||||||
else if ((inlineComposer = $(".js-inline-compose-close")).length === 1){
|
};
|
||||||
inlineComposer.click();
|
|
||||||
}
|
window.TDGF_onMouseClickExtra = function(button){
|
||||||
else if (highlightedColumnEle && highlightedColumnEle.closest(".js-column").is(".is-shifted-1")){
|
if (button === 1){ // back button
|
||||||
highlightedColumnEle.find(".js-column-back").first().click();
|
tryCloseModal() ||
|
||||||
}
|
tryClickSelector(".js-inline-compose-close") ||
|
||||||
else if ((drawerComposer = $(".js-app-content.is-open .js-drawer-close:visible")).length === 1){
|
tryCloseHighlightedColumn() ||
|
||||||
drawerComposer.click();
|
tryClickSelector(".js-app-content.is-open .js-drawer-close:visible") ||
|
||||||
}
|
tryClickSelector(".is-shifted-2 .js-tweet-social-proof-back") ||
|
||||||
else{
|
|
||||||
$(".js-column-back").click();
|
$(".js-column-back").click();
|
||||||
}
|
}
|
||||||
}
|
else if (button === 2){ // forward button
|
||||||
else if (button === 2){ // forward button
|
if (highlightedTweetEle){
|
||||||
if (highlightedTweetEle){
|
highlightedTweetEle.children().first().click();
|
||||||
highlightedTweetEle.children().first().click();
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
};
|
})();
|
||||||
|
|
||||||
//
|
//
|
||||||
// Block: Fix scheduled tweets not showing up sometimes.
|
// Block: Fix scheduled tweets not showing up sometimes.
|
||||||
|
@@ -114,7 +114,7 @@
|
|||||||
var account = embedded[0].getElementsByClassName("account-link");
|
var account = embedded[0].getElementsByClassName("account-link");
|
||||||
if (account.length === 0)return;
|
if (account.length === 0)return;
|
||||||
|
|
||||||
$TD.setNotificationTweetEmbedded(account[0].getAttribute("href")+"/status/"+tweetId);
|
$TD.setNotificationQuotedTweet(account[0].getAttribute("href")+"/status/"+tweetId);
|
||||||
})();
|
})();
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@@ -8,8 +8,8 @@
|
|||||||
|
|
||||||
$TDP.checkFileExists(token, fileNameUser).then(exists => {
|
$TDP.checkFileExists(token, fileNameUser).then(exists => {
|
||||||
var fileName = exists ? fileNameUser : fileNameDefault;
|
var fileName = exists ? fileNameUser : fileNameDefault;
|
||||||
|
|
||||||
$TDP.readFile(token, fileName, true).then(contents => {
|
(exists ? $TDP.readFile(token, fileName, true) : $TDP.readFileRoot(token, fileName)).then(contents => {
|
||||||
var obj;
|
var obj;
|
||||||
|
|
||||||
try{
|
try{
|
||||||
|
@@ -12,14 +12,18 @@
|
|||||||
//
|
//
|
||||||
// Constant: Url that returns JSON data about latest version.
|
// Constant: Url that returns JSON data about latest version.
|
||||||
//
|
//
|
||||||
const updateCheckUrl = "https://api.github.com/repos/chylex/"+$TDU.brandName+"/releases/latest";
|
const updateCheckUrlLatest = "https://api.github.com/repos/chylex/"+$TDU.brandName+"/releases/latest";
|
||||||
|
|
||||||
|
//
|
||||||
|
// Constant: Url that returns JSON data about all versions, including prereleases.
|
||||||
|
//
|
||||||
|
const updateCheckUrlAll = "https://api.github.com/repos/chylex/"+$TDU.brandName+"/releases";
|
||||||
|
|
||||||
//
|
//
|
||||||
// Function: Creates the update notification element. Removes the old one if already exists.
|
// Function: Creates the update notification element. Removes the old one if already exists.
|
||||||
//
|
//
|
||||||
var createUpdateNotificationElement = function(version, download){
|
var createUpdateNotificationElement = function(version, download){
|
||||||
var outdated = version === "unsupported";
|
var outdated = version === "unsupported";
|
||||||
var tweetdick = version === "tweetdick";
|
|
||||||
|
|
||||||
var ele = $("#tweetdck-update");
|
var ele = $("#tweetdck-update");
|
||||||
var existed = ele.length > 0;
|
var existed = ele.length > 0;
|
||||||
@@ -37,15 +41,6 @@
|
|||||||
"<button class='btn btn-negative tdu-btn-dismiss'><span class='label'>Dismiss</span></button>",
|
"<button class='btn btn-negative tdu-btn-dismiss'><span class='label'>Dismiss</span></button>",
|
||||||
"</div>",
|
"</div>",
|
||||||
"</div>"
|
"</div>"
|
||||||
] : tweetdick ? [
|
|
||||||
"<div id='tweetdck-update'>",
|
|
||||||
"<p class='tdu-title'>TweetDick Ending</p>",
|
|
||||||
"<p class='tdu-info'>Please, move to TweetDuck.</p>",
|
|
||||||
"<div class='tdu-buttons'>",
|
|
||||||
"<button class='btn btn-positive tdu-btn-unsupported'><span class='label'>Read More</span></button>",
|
|
||||||
"<button class='btn btn-negative tdu-btn-dismiss'><span class='label'>Dismiss</span></button>",
|
|
||||||
"</div>",
|
|
||||||
"</div>"
|
|
||||||
] : [
|
] : [
|
||||||
"<div id='tweetdck-update'>",
|
"<div id='tweetdck-update'>",
|
||||||
"<p class='tdu-title'>"+$TDU.brandName+" Update</p>",
|
"<p class='tdu-title'>"+$TDU.brandName+" Update</p>",
|
||||||
@@ -118,10 +113,6 @@
|
|||||||
$TDU.openBrowser("https://github.com/chylex/TweetDuck/wiki/Supported-Systems");
|
$TDU.openBrowser("https://github.com/chylex/TweetDuck/wiki/Supported-Systems");
|
||||||
});
|
});
|
||||||
|
|
||||||
buttonDiv.children(".tdu-btn-tweetdick").click(function(){
|
|
||||||
$TDU.openBrowser("https://github.com/chylex/TweetDick/wiki/Future-of-TweetDick");
|
|
||||||
});
|
|
||||||
|
|
||||||
buttonDiv.children(".tdu-btn-dismiss,.tdu-btn-unsupported").click(function(){
|
buttonDiv.children(".tdu-btn-dismiss,.tdu-btn-unsupported").click(function(){
|
||||||
$TDU.onUpdateDismissed(version);
|
$TDU.onUpdateDismissed(version);
|
||||||
ele.slideUp(function(){ ele.remove(); });
|
ele.slideUp(function(){ ele.remove(); });
|
||||||
@@ -145,29 +136,28 @@
|
|||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else if ($TDU.brandName === "TweetDick"){
|
|
||||||
if ($TDU.dismissedVersionTag !== "tweetdick"){
|
|
||||||
createUpdateNotificationElement("tweetdick");
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
clearTimeout(updateCheckTimeoutID);
|
clearTimeout(updateCheckTimeoutID);
|
||||||
updateCheckTimeoutID = setTimeout(runUpdateCheck, 1000*60*60); // 1 hour
|
updateCheckTimeoutID = setTimeout(runUpdateCheck, 1000*60*60); // 1 hour
|
||||||
|
|
||||||
if (!$TDU.updateCheckEnabled && !force)return;
|
if (!$TDU.updateCheckEnabled && !force){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$.getJSON(updateCheckUrl, function(response){
|
var allowPre = $TDU.allowPreReleases;
|
||||||
var tagName = response.tag_name;
|
|
||||||
var hasUpdate = tagName !== $TDU.versionTag && tagName !== $TDU.dismissedVersionTag && response.assets.length > 0;
|
$.getJSON(allowPre ? updateCheckUrlAll : updateCheckUrlLatest, function(response){
|
||||||
|
var release = allowPre ? response[0] : response;
|
||||||
|
|
||||||
|
var tagName = release.tag_name;
|
||||||
|
var hasUpdate = tagName !== $TDU.versionTag && tagName !== $TDU.dismissedVersionTag && release.assets.length > 0;
|
||||||
|
|
||||||
if (hasUpdate){
|
if (hasUpdate){
|
||||||
var obj = response.assets.find(asset => asset.name === updateFileName) || response.assets[0];
|
var obj = release.assets.find(asset => asset.name === updateFileName) || release.assets[0];
|
||||||
createUpdateNotificationElement(tagName, obj.browser_download_url);
|
createUpdateNotificationElement(tagName, obj.browser_download_url);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (eventID !== 0){
|
if (eventID){ // ignore undefined and 0
|
||||||
$TDU.onUpdateCheckFinished(eventID, hasUpdate, tagName);
|
$TDU.onUpdateCheckFinished(eventID, hasUpdate, tagName);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@@ -180,12 +180,6 @@
|
|||||||
<Compile Include="Core\Utils\CommandLineArgsParser.cs" />
|
<Compile Include="Core\Utils\CommandLineArgsParser.cs" />
|
||||||
<Compile Include="Core\Utils\WindowState.cs" />
|
<Compile Include="Core\Utils\WindowState.cs" />
|
||||||
<Compile Include="Core\Utils\WindowsUtils.cs" />
|
<Compile Include="Core\Utils\WindowsUtils.cs" />
|
||||||
<Compile Include="Migration\FormBackgroundWork.cs">
|
|
||||||
<SubType>Form</SubType>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="Migration\FormBackgroundWork.Designer.cs">
|
|
||||||
<DependentUpon>FormBackgroundWork.cs</DependentUpon>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="Core\Handling\TweetDeckBridge.cs" />
|
<Compile Include="Core\Handling\TweetDeckBridge.cs" />
|
||||||
<Compile Include="Core\Other\FormSettings.cs">
|
<Compile Include="Core\Other\FormSettings.cs">
|
||||||
<SubType>Form</SubType>
|
<SubType>Form</SubType>
|
||||||
@@ -193,7 +187,6 @@
|
|||||||
<Compile Include="Core\Other\FormSettings.Designer.cs">
|
<Compile Include="Core\Other\FormSettings.Designer.cs">
|
||||||
<DependentUpon>FormSettings.cs</DependentUpon>
|
<DependentUpon>FormSettings.cs</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="Migration\MigrationUtils.cs" />
|
|
||||||
<Compile Include="Plugins\Controls\PluginControl.cs">
|
<Compile Include="Plugins\Controls\PluginControl.cs">
|
||||||
<SubType>UserControl</SubType>
|
<SubType>UserControl</SubType>
|
||||||
</Compile>
|
</Compile>
|
||||||
@@ -206,12 +199,13 @@
|
|||||||
<Compile Include="Plugins\Controls\PluginListFlowLayout.Designer.cs">
|
<Compile Include="Plugins\Controls\PluginListFlowLayout.Designer.cs">
|
||||||
<DependentUpon>PluginListFlowLayout.cs</DependentUpon>
|
<DependentUpon>PluginListFlowLayout.cs</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="Plugins\Enums\PluginFolder.cs" />
|
||||||
<Compile Include="Plugins\Plugin.cs" />
|
<Compile Include="Plugins\Plugin.cs" />
|
||||||
<Compile Include="Plugins\Events\PluginChangedStateEventArgs.cs" />
|
<Compile Include="Plugins\Events\PluginChangedStateEventArgs.cs" />
|
||||||
<Compile Include="Plugins\PluginBridge.cs" />
|
<Compile Include="Plugins\PluginBridge.cs" />
|
||||||
<Compile Include="Plugins\PluginConfig.cs" />
|
<Compile Include="Plugins\PluginConfig.cs" />
|
||||||
<Compile Include="Plugins\PluginEnvironment.cs" />
|
<Compile Include="Plugins\Enums\PluginEnvironment.cs" />
|
||||||
<Compile Include="Plugins\PluginGroup.cs" />
|
<Compile Include="Plugins\Enums\PluginGroup.cs" />
|
||||||
<Compile Include="Plugins\Events\PluginLoadEventArgs.cs" />
|
<Compile Include="Plugins\Events\PluginLoadEventArgs.cs" />
|
||||||
<Compile Include="Plugins\PluginManager.cs" />
|
<Compile Include="Plugins\PluginManager.cs" />
|
||||||
<Compile Include="Plugins\PluginScriptGenerator.cs" />
|
<Compile Include="Plugins\PluginScriptGenerator.cs" />
|
||||||
@@ -236,8 +230,6 @@
|
|||||||
<Compile Include="Updates\UpdateCheckEventArgs.cs" />
|
<Compile Include="Updates\UpdateCheckEventArgs.cs" />
|
||||||
<Compile Include="Updates\UpdateHandler.cs" />
|
<Compile Include="Updates\UpdateHandler.cs" />
|
||||||
<Compile Include="Updates\UpdateInfo.cs" />
|
<Compile Include="Updates\UpdateInfo.cs" />
|
||||||
<Compile Include="Migration\MigrationDecision.cs" />
|
|
||||||
<Compile Include="Migration\MigrationManager.cs" />
|
|
||||||
<Compile Include="Program.cs" />
|
<Compile Include="Program.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
<Compile Include="Properties\Resources.Designer.cs">
|
<Compile Include="Properties\Resources.Designer.cs">
|
||||||
@@ -246,6 +238,7 @@
|
|||||||
<DependentUpon>Resources.resx</DependentUpon>
|
<DependentUpon>Resources.resx</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="Resources\ScriptLoader.cs" />
|
<Compile Include="Resources\ScriptLoader.cs" />
|
||||||
|
<Compile Include="Updates\UpdaterSettings.cs" />
|
||||||
<None Include="Configuration\app.config" />
|
<None Include="Configuration\app.config" />
|
||||||
<None Include="Configuration\packages.config" />
|
<None Include="Configuration\packages.config" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
@@ -10,21 +10,24 @@ namespace TweetDck.Updates{
|
|||||||
class UpdateHandler{
|
class UpdateHandler{
|
||||||
private readonly ChromiumWebBrowser browser;
|
private readonly ChromiumWebBrowser browser;
|
||||||
private readonly FormBrowser form;
|
private readonly FormBrowser form;
|
||||||
|
private readonly UpdaterSettings settings;
|
||||||
|
|
||||||
public event EventHandler<UpdateAcceptedEventArgs> UpdateAccepted;
|
public event EventHandler<UpdateAcceptedEventArgs> UpdateAccepted;
|
||||||
public event EventHandler<UpdateCheckEventArgs> CheckFinished;
|
public event EventHandler<UpdateCheckEventArgs> CheckFinished;
|
||||||
|
|
||||||
private int lastEventId;
|
private int lastEventId;
|
||||||
|
|
||||||
public UpdateHandler(ChromiumWebBrowser browser, FormBrowser form){
|
public UpdateHandler(ChromiumWebBrowser browser, FormBrowser form, UpdaterSettings settings){
|
||||||
this.browser = browser;
|
this.browser = browser;
|
||||||
this.form = form;
|
this.form = form;
|
||||||
|
this.settings = settings;
|
||||||
|
|
||||||
browser.FrameLoadEnd += browser_FrameLoadEnd;
|
browser.FrameLoadEnd += browser_FrameLoadEnd;
|
||||||
browser.RegisterJsObject("$TDU", new Bridge(this));
|
browser.RegisterJsObject("$TDU", new Bridge(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
|
private void browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
|
||||||
if (e.Frame.IsMain){
|
if (e.Frame.IsMain && BrowserUtils.IsTweetDeckWebsite(e.Frame)){
|
||||||
ScriptLoader.ExecuteFile(e.Frame, "update.js");
|
ScriptLoader.ExecuteFile(e.Frame, "update.js");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,6 +74,12 @@ namespace TweetDck.Updates{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool AllowPreReleases{
|
||||||
|
get{
|
||||||
|
return owner.settings.AllowPreReleases;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public bool IsSystemSupported{
|
public bool IsSystemSupported{
|
||||||
get{
|
get{
|
||||||
return Environment.OSVersion.Version >= new Version("6.1"); // 6.1 NT version = Windows 7
|
return Environment.OSVersion.Version >= new Version("6.1"); // 6.1 NT version = Windows 7
|
||||||
|
5
Updates/UpdaterSettings.cs
Normal file
5
Updates/UpdaterSettings.cs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
namespace TweetDck.Updates{
|
||||||
|
class UpdaterSettings{
|
||||||
|
public bool AllowPreReleases { get; set; }
|
||||||
|
}
|
||||||
|
}
|
9
bld/Resources/LICENSE
Normal file
9
bld/Resources/LICENSE
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2016
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
BIN
bld/Resources/icon.ico
Normal file
BIN
bld/Resources/icon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 31 KiB |
149
bld/gen_full.iss
Normal file
149
bld/gen_full.iss
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
; Script generated by the Inno Script Studio Wizard.
|
||||||
|
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
|
||||||
|
|
||||||
|
#define MyAppName "TweetDuck"
|
||||||
|
#define MyAppPublisher "chylex"
|
||||||
|
#define MyAppURL "https://tweetduck.chylex.com"
|
||||||
|
#define MyAppExeName "TweetDuck.exe"
|
||||||
|
|
||||||
|
#define MyAppVersion GetFileVersion("..\bin\x86\Release\TweetDuck.exe")
|
||||||
|
|
||||||
|
[Setup]
|
||||||
|
AppId={{8C25A716-7E11-4AAD-9992-8B5D0C78AE06}
|
||||||
|
AppName={#MyAppName}
|
||||||
|
AppVersion={#MyAppVersion}
|
||||||
|
AppVerName={#MyAppName} {#MyAppVersion}
|
||||||
|
AppPublisher={#MyAppPublisher}
|
||||||
|
AppPublisherURL={#MyAppURL}
|
||||||
|
AppSupportURL={#MyAppURL}
|
||||||
|
AppUpdatesURL={#MyAppURL}
|
||||||
|
DefaultDirName={pf}\{#MyAppName}
|
||||||
|
DefaultGroupName={#MyAppName}
|
||||||
|
OutputBaseFilename={#MyAppName}
|
||||||
|
VersionInfoVersion={#MyAppVersion}
|
||||||
|
LicenseFile=.\Resources\LICENSE
|
||||||
|
SetupIconFile=.\Resources\icon.ico
|
||||||
|
Uninstallable=TDIsUninstallable
|
||||||
|
UninstallDisplayName={#MyAppName}
|
||||||
|
UninstallDisplayIcon={app}\{#MyAppExeName}
|
||||||
|
Compression=lzma
|
||||||
|
SolidCompression=yes
|
||||||
|
InternalCompressLevel=max
|
||||||
|
MinVersion=0,6.1
|
||||||
|
|
||||||
|
[Languages]
|
||||||
|
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||||
|
|
||||||
|
[Tasks]
|
||||||
|
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
|
||||||
|
|
||||||
|
[Files]
|
||||||
|
Source: "..\bin\x86\Release\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
|
||||||
|
Source: "..\bin\x86\Release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs; Excludes: "*.xml,devtools_resources.pak,d3dcompiler_43.dll"
|
||||||
|
|
||||||
|
[Icons]
|
||||||
|
Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Check: TDIsUninstallable
|
||||||
|
Name: "{commondesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
|
||||||
|
|
||||||
|
[Run]
|
||||||
|
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall shellexec skipifsilent
|
||||||
|
|
||||||
|
[InstallDelete]
|
||||||
|
Type: files; Name: "{app}\td-log.txt"
|
||||||
|
|
||||||
|
[UninstallDelete]
|
||||||
|
Type: files; Name: "{app}\debug.log"
|
||||||
|
Type: filesandordirs; Name: "{localappdata}\{#MyAppName}\Cache"
|
||||||
|
Type: filesandordirs; Name: "{localappdata}\{#MyAppName}\GPUCache"
|
||||||
|
|
||||||
|
[Code]
|
||||||
|
var UpdatePath: String;
|
||||||
|
|
||||||
|
function TDGetNetFrameworkVersion: Cardinal; forward;
|
||||||
|
|
||||||
|
{ Check .NET Framework version on startup, ask user if they want to proceed if older than 4.5.2. }
|
||||||
|
function InitializeSetup: Boolean;
|
||||||
|
begin
|
||||||
|
UpdatePath := ExpandConstant('{param:UPDATEPATH}')
|
||||||
|
|
||||||
|
if TDGetNetFrameworkVersion() >= 379893 then
|
||||||
|
begin
|
||||||
|
Result := True;
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
|
|
||||||
|
if (MsgBox('{#MyAppName} requires .NET Framework 4.5.2 or newer,'+#13+#10+'please download it from {#MyAppURL}'+#13+#10+#13+#10'Do you want to proceed with the setup anyway?', mbCriticalError, MB_YESNO or MB_DEFBUTTON2) = IDNO) then
|
||||||
|
begin
|
||||||
|
Result := False;
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
|
|
||||||
|
Result := True;
|
||||||
|
end;
|
||||||
|
|
||||||
|
{ Set the installation path if updating. }
|
||||||
|
procedure InitializeWizard();
|
||||||
|
begin
|
||||||
|
if (UpdatePath <> '') then
|
||||||
|
begin
|
||||||
|
WizardForm.DirEdit.Text := UpdatePath;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
{ Skip the install path selection page if running from an update installer. }
|
||||||
|
function ShouldSkipPage(PageID: Integer): Boolean;
|
||||||
|
begin
|
||||||
|
Result := (PageID = wpSelectDir) and (UpdatePath <> '')
|
||||||
|
end;
|
||||||
|
|
||||||
|
{ Check for an old TweetDeck profile and show a warning before installation. }
|
||||||
|
procedure CurStepChanged(CurStep: TSetupStep);
|
||||||
|
begin
|
||||||
|
if CurStep = ssInstall then
|
||||||
|
begin
|
||||||
|
if DirExists(ExpandConstant('{localappdata}\twitter\TweetDeck')) then
|
||||||
|
begin
|
||||||
|
MsgBox('Detected a profile from an old TweetDeck installation, you may uninstall the old client to free up some space.', mbInformation, MB_OK)
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
{ Ask user if they want to delete 'AppData\TweetDuck' and 'plugins' folders after uninstallation. }
|
||||||
|
procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
|
||||||
|
var ProfileDataFolder: String;
|
||||||
|
var PluginDataFolder: String;
|
||||||
|
|
||||||
|
begin
|
||||||
|
if CurUninstallStep = usPostUninstall then
|
||||||
|
begin
|
||||||
|
ProfileDataFolder := ExpandConstant('{localappdata}\{#MyAppName}')
|
||||||
|
PluginDataFolder := ExpandConstant('{app}\plugins')
|
||||||
|
|
||||||
|
if (DirExists(ProfileDataFolder) or DirExists(PluginDataFolder)) and (MsgBox('Do you also want to delete your {#MyAppName} profile and plugins?', mbConfirmation, MB_YESNO or MB_DEFBUTTON2) = IDYES) then
|
||||||
|
begin
|
||||||
|
DelTree(ProfileDataFolder, True, True, True);
|
||||||
|
DelTree(PluginDataFolder, True, True, True);
|
||||||
|
DelTree(ExpandConstant('{app}'), True, False, False);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
{ Returns true if the installer should create uninstallation entries (i.e. not running in full update mode). }
|
||||||
|
function TDIsUninstallable: Boolean;
|
||||||
|
begin
|
||||||
|
Result := (UpdatePath = '')
|
||||||
|
end;
|
||||||
|
|
||||||
|
{ Return DWORD value containing the build version of .NET Framework. }
|
||||||
|
function TDGetNetFrameworkVersion: Cardinal;
|
||||||
|
var FrameworkVersion: Cardinal;
|
||||||
|
|
||||||
|
begin
|
||||||
|
if RegQueryDWordValue(HKEY_LOCAL_MACHINE, 'Software\Microsoft\NET Framework Setup\NDP\v4\Full', 'Release', FrameworkVersion) then
|
||||||
|
begin
|
||||||
|
Result := FrameworkVersion;
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
|
|
||||||
|
Result := 0;
|
||||||
|
end;
|
92
bld/gen_port.iss
Normal file
92
bld/gen_port.iss
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
; Script generated by the Inno Script Studio Wizard.
|
||||||
|
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
|
||||||
|
|
||||||
|
#define MyAppName "TweetDuck"
|
||||||
|
#define MyAppPublisher "chylex"
|
||||||
|
#define MyAppURL "https://tweetduck.chylex.com"
|
||||||
|
#define MyAppExeName "TweetDuck.exe"
|
||||||
|
|
||||||
|
#define MyAppVersion GetFileVersion("..\bin\x86\Release\TweetDuck.exe")
|
||||||
|
|
||||||
|
[Setup]
|
||||||
|
AppId={{8C25A716-7E11-4AAD-9992-8B5D0C78AE06}
|
||||||
|
AppName={#MyAppName}
|
||||||
|
AppVersion={#MyAppVersion}
|
||||||
|
AppVerName={#MyAppName} {#MyAppVersion}
|
||||||
|
AppPublisher={#MyAppPublisher}
|
||||||
|
AppPublisherURL={#MyAppURL}
|
||||||
|
AppSupportURL={#MyAppURL}
|
||||||
|
AppUpdatesURL={#MyAppURL}
|
||||||
|
DefaultDirName={pf}\{#MyAppName}
|
||||||
|
DefaultGroupName={#MyAppName}
|
||||||
|
OutputBaseFilename={#MyAppName}.Portable
|
||||||
|
VersionInfoVersion={#MyAppVersion}
|
||||||
|
LicenseFile=.\Resources\LICENSE
|
||||||
|
SetupIconFile=.\Resources\icon.ico
|
||||||
|
Uninstallable=no
|
||||||
|
UsePreviousAppDir=no
|
||||||
|
PrivilegesRequired=lowest
|
||||||
|
Compression=lzma
|
||||||
|
SolidCompression=yes
|
||||||
|
InternalCompressLevel=max
|
||||||
|
MinVersion=0,6.1
|
||||||
|
|
||||||
|
[Languages]
|
||||||
|
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||||
|
|
||||||
|
[Files]
|
||||||
|
Source: "..\bin\x86\Release\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
|
||||||
|
Source: "..\bin\x86\Release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs; Excludes: "*.xml,devtools_resources.pak,d3dcompiler_43.dll"
|
||||||
|
|
||||||
|
[Run]
|
||||||
|
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall shellexec
|
||||||
|
|
||||||
|
[Code]
|
||||||
|
function TDGetNetFrameworkVersion: Cardinal; forward;
|
||||||
|
|
||||||
|
{ Check .NET Framework version on startup, ask user if they want to proceed if older than 4.5.2. }
|
||||||
|
function InitializeSetup: Boolean;
|
||||||
|
begin
|
||||||
|
if TDGetNetFrameworkVersion() >= 379893 then
|
||||||
|
begin
|
||||||
|
Result := True;
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
|
|
||||||
|
if (MsgBox('{#MyAppName} requires .NET Framework 4.5.2 or newer,'+#13+#10+'please download it from {#MyAppURL}'+#13+#10+#13+#10'Do you want to proceed with the setup anyway?', mbCriticalError, MB_YESNO or MB_DEFBUTTON2) = IDNO) then
|
||||||
|
begin
|
||||||
|
Result := False;
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
|
|
||||||
|
Result := True;
|
||||||
|
end;
|
||||||
|
|
||||||
|
{ Return DWORD value containing the build version of .NET Framework. }
|
||||||
|
function TDGetNetFrameworkVersion: Cardinal;
|
||||||
|
var FrameworkVersion: Cardinal;
|
||||||
|
|
||||||
|
begin
|
||||||
|
if RegQueryDWordValue(HKEY_LOCAL_MACHINE, 'Software\Microsoft\NET Framework Setup\NDP\v4\Full', 'Release', FrameworkVersion) then
|
||||||
|
begin
|
||||||
|
Result := FrameworkVersion;
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
|
|
||||||
|
Result := 0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
{ Create a 'makeportable' file if running in portable mode. }
|
||||||
|
procedure CurStepChanged(CurStep: TSetupStep);
|
||||||
|
begin
|
||||||
|
if CurStep = ssPostInstall then
|
||||||
|
begin
|
||||||
|
while not SaveStringToFile(ExpandConstant('{app}\makeportable'), '', False) do
|
||||||
|
begin
|
||||||
|
if MsgBox('Could not create a ''makeportable'' file in the installation folder. If the file is not present, the installation will not be fully portable.', mbCriticalError, MB_RETRYCANCEL) <> IDRETRY then
|
||||||
|
begin
|
||||||
|
break;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
end;
|
266
bld/gen_upd.iss
Normal file
266
bld/gen_upd.iss
Normal file
@@ -0,0 +1,266 @@
|
|||||||
|
; Script generated by the Inno Script Studio Wizard.
|
||||||
|
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
|
||||||
|
|
||||||
|
#define MyAppName "TweetDuck"
|
||||||
|
#define MyAppPublisher "chylex"
|
||||||
|
#define MyAppURL "https://tweetduck.chylex.com"
|
||||||
|
#define MyAppExeName "TweetDuck.exe"
|
||||||
|
|
||||||
|
#define MyAppID "8C25A716-7E11-4AAD-9992-8B5D0C78AE06"
|
||||||
|
#define MyAppVersion GetFileVersion("..\bin\x86\Release\TweetDuck.exe")
|
||||||
|
#define CefVersion "3.2785.1478.0"
|
||||||
|
|
||||||
|
[Setup]
|
||||||
|
AppId={{{#MyAppID}}
|
||||||
|
AppName={#MyAppName}
|
||||||
|
AppVersion={#MyAppVersion}
|
||||||
|
AppVerName={#MyAppName} {#MyAppVersion}
|
||||||
|
AppPublisher={#MyAppPublisher}
|
||||||
|
AppPublisherURL={#MyAppURL}
|
||||||
|
AppSupportURL={#MyAppURL}
|
||||||
|
AppUpdatesURL={#MyAppURL}
|
||||||
|
DefaultDirName={pf}\{#MyAppName}
|
||||||
|
DefaultGroupName={#MyAppName}
|
||||||
|
OutputBaseFilename={#MyAppName}.Update
|
||||||
|
VersionInfoVersion={#MyAppVersion}
|
||||||
|
LicenseFile=.\Resources\LICENSE
|
||||||
|
SetupIconFile=.\Resources\icon.ico
|
||||||
|
Uninstallable=TDIsUninstallable
|
||||||
|
UninstallDisplayName={#MyAppName}
|
||||||
|
UninstallDisplayIcon={app}\{#MyAppExeName}
|
||||||
|
PrivilegesRequired=lowest
|
||||||
|
Compression=lzma
|
||||||
|
SolidCompression=yes
|
||||||
|
InternalCompressLevel=max
|
||||||
|
MinVersion=0,6.1
|
||||||
|
|
||||||
|
#include <idp.iss>
|
||||||
|
|
||||||
|
[Languages]
|
||||||
|
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||||
|
|
||||||
|
[Files]
|
||||||
|
Source: "..\bin\x86\Release\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
|
||||||
|
Source: "..\bin\x86\Release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs; Excludes: "*.xml,*.dll,*.pak,*.bin,*.dat"
|
||||||
|
|
||||||
|
[Icons]
|
||||||
|
Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Check: TDIsUninstallable
|
||||||
|
|
||||||
|
[Run]
|
||||||
|
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Parameters: "{param:RUNARGS}"; Flags: nowait postinstall shellexec
|
||||||
|
|
||||||
|
[InstallDelete]
|
||||||
|
Type: files; Name: "{app}\*.xml"
|
||||||
|
Type: files; Name: "{app}\*.js"
|
||||||
|
Type: files; Name: "{app}\d3dcompiler_43.dll"
|
||||||
|
Type: files; Name: "{app}\devtools_resources.pak"
|
||||||
|
Type: files; Name: "{app}\CefSharp.BrowserSubprocess.exe"
|
||||||
|
Type: files; Name: "{app}\td-log.txt"
|
||||||
|
Type: files; Name: "{app}\debug.log"
|
||||||
|
Type: files; Name: "{localappdata}\{#MyAppName}\ChromeDWriteFontCache"
|
||||||
|
|
||||||
|
[UninstallDelete]
|
||||||
|
Type: files; Name: "{app}\*.*"
|
||||||
|
Type: filesandordirs; Name: "{app}\locales"
|
||||||
|
Type: filesandordirs; Name: "{app}\scripts"
|
||||||
|
Type: filesandordirs; Name: "{localappdata}\{#MyAppName}\Cache"
|
||||||
|
Type: filesandordirs; Name: "{localappdata}\{#MyAppName}\GPUCache"
|
||||||
|
|
||||||
|
[Code]
|
||||||
|
function TDIsUninstallable: Boolean; forward;
|
||||||
|
function TDFindUpdatePath: String; forward;
|
||||||
|
function TDGetNetFrameworkVersion: Cardinal; forward;
|
||||||
|
function TDGetAppVersionClean: String; forward;
|
||||||
|
function TDIsMatchingCEFVersion: Boolean; forward;
|
||||||
|
procedure TDExecuteFullDownload; forward;
|
||||||
|
|
||||||
|
var IsPortable: Boolean;
|
||||||
|
var UpdatePath: String;
|
||||||
|
|
||||||
|
{ Check .NET Framework version on startup, ask user if they want to proceed if older than 4.5.2. Prepare full download package if required. }
|
||||||
|
function InitializeSetup: Boolean;
|
||||||
|
begin
|
||||||
|
IsPortable := ExpandConstant('{param:PORTABLE}') = '1'
|
||||||
|
UpdatePath := TDFindUpdatePath()
|
||||||
|
|
||||||
|
if UpdatePath = '' then
|
||||||
|
begin
|
||||||
|
MsgBox('{#MyAppName} installation could not be found on your system.', mbCriticalError, MB_OK);
|
||||||
|
Result := False;
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
|
|
||||||
|
if not TDIsMatchingCEFVersion() then
|
||||||
|
begin
|
||||||
|
idpAddFile('https://github.com/{#MyAppPublisher}/{#MyAppName}/releases/download/'+TDGetAppVersionClean()+'/{#MyAppName}.exe', ExpandConstant('{tmp}\{#MyAppName}.Full.exe'));
|
||||||
|
end;
|
||||||
|
|
||||||
|
if TDGetNetFrameworkVersion() >= 379893 then
|
||||||
|
begin
|
||||||
|
Result := True;
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
|
|
||||||
|
if (MsgBox('{#MyAppName} requires .NET Framework 4.5.2 or newer,'+#13+#10+'please download it from {#MyAppURL}'+#13+#10+#13+#10'Do you want to proceed with the setup anyway?', mbCriticalError, MB_YESNO or MB_DEFBUTTON2) = IDNO) then
|
||||||
|
begin
|
||||||
|
Result := False;
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
|
|
||||||
|
Result := True;
|
||||||
|
end;
|
||||||
|
|
||||||
|
{ Prepare download plugin if there are any files to download, and set the installation path. }
|
||||||
|
procedure InitializeWizard();
|
||||||
|
begin
|
||||||
|
WizardForm.DirEdit.Text := UpdatePath;
|
||||||
|
|
||||||
|
if idpFilesCount <> 0 then
|
||||||
|
begin
|
||||||
|
idpDownloadAfter(wpReady);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
{ Ask user if they want to delete 'AppData\TweetDuck' and 'plugins' folders after uninstallation. }
|
||||||
|
procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
|
||||||
|
var ProfileDataFolder: String;
|
||||||
|
var PluginDataFolder: String;
|
||||||
|
|
||||||
|
begin
|
||||||
|
if CurUninstallStep = usPostUninstall then
|
||||||
|
begin
|
||||||
|
ProfileDataFolder := ExpandConstant('{localappdata}\{#MyAppName}');
|
||||||
|
PluginDataFolder := ExpandConstant('{app}\plugins');
|
||||||
|
|
||||||
|
if (DirExists(ProfileDataFolder) or DirExists(PluginDataFolder)) and (MsgBox('Do you also want to delete your {#MyAppName} profile and plugins?', mbConfirmation, MB_YESNO or MB_DEFBUTTON2) = IDYES) then
|
||||||
|
begin
|
||||||
|
DelTree(ProfileDataFolder, True, True, True);
|
||||||
|
DelTree(PluginDataFolder, True, True, True);
|
||||||
|
DelTree(ExpandConstant('{app}'), True, False, False);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
{ Remove uninstallation data and application to force them to be replaced with updated ones. }
|
||||||
|
procedure CurStepChanged(CurStep: TSetupStep);
|
||||||
|
begin
|
||||||
|
if CurStep = ssInstall then
|
||||||
|
begin
|
||||||
|
TDExecuteFullDownload();
|
||||||
|
|
||||||
|
if TDIsUninstallable() then
|
||||||
|
begin
|
||||||
|
DeleteFile(ExpandConstant('{app}\unins000.dat'));
|
||||||
|
DeleteFile(ExpandConstant('{app}\unins000.exe'));
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
{ Returns true if the installer should create uninstallation entries (i.e. not running in portable mode). }
|
||||||
|
function TDIsUninstallable: Boolean;
|
||||||
|
begin
|
||||||
|
Result := not IsPortable
|
||||||
|
end;
|
||||||
|
|
||||||
|
{ Returns a validated installation path (including trailing backslash) using the /UPDATEPATH parameter or installation info in registry. Returns empty string on failure. }
|
||||||
|
function TDFindUpdatePath: String;
|
||||||
|
var Path: String;
|
||||||
|
|
||||||
|
begin
|
||||||
|
Path := ExpandConstant('{param:UPDATEPATH}')
|
||||||
|
|
||||||
|
if (Path = '') and not IsPortable and not RegQueryStringValue(HKEY_LOCAL_MACHINE, 'Software\Microsoft\Windows\CurrentVersion\Uninstall\{{#MyAppID}}_is1', 'InstallLocation', Path) then
|
||||||
|
begin
|
||||||
|
Result := ''
|
||||||
|
Exit
|
||||||
|
end;
|
||||||
|
|
||||||
|
if not FileExists(Path+'{#MyAppExeName}') then
|
||||||
|
begin
|
||||||
|
Result := ''
|
||||||
|
Exit
|
||||||
|
end;
|
||||||
|
|
||||||
|
Result := Path
|
||||||
|
end;
|
||||||
|
|
||||||
|
{ Return DWORD value containing the build version of .NET Framework. }
|
||||||
|
function TDGetNetFrameworkVersion: Cardinal;
|
||||||
|
var FrameworkVersion: Cardinal;
|
||||||
|
|
||||||
|
begin
|
||||||
|
if RegQueryDWordValue(HKEY_LOCAL_MACHINE, 'Software\Microsoft\NET Framework Setup\NDP\v4\Full', 'Release', FrameworkVersion) then
|
||||||
|
begin
|
||||||
|
Result := FrameworkVersion;
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
|
|
||||||
|
Result := 0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
{ Return whether the version of the installed libcef.dll library matches internal one. }
|
||||||
|
function TDIsMatchingCEFVersion: Boolean;
|
||||||
|
var CEFVersion: String;
|
||||||
|
|
||||||
|
begin
|
||||||
|
Result := (GetVersionNumbersString(UpdatePath+'libcef.dll', CEFVersion) and (CompareStr(CEFVersion, '{#CefVersion}') = 0))
|
||||||
|
end;
|
||||||
|
|
||||||
|
{ Return a cleaned up form of the app version string (removes all .0 suffixes). }
|
||||||
|
function TDGetAppVersionClean: String;
|
||||||
|
var Substr: String;
|
||||||
|
var CleanVersion: String;
|
||||||
|
|
||||||
|
begin
|
||||||
|
CleanVersion := '{#MyAppVersion}'
|
||||||
|
|
||||||
|
while True do
|
||||||
|
begin
|
||||||
|
Substr := Copy(CleanVersion, Length(CleanVersion)-1, 2);
|
||||||
|
|
||||||
|
if (CompareStr(Substr, '.0') <> 0) then
|
||||||
|
begin
|
||||||
|
break;
|
||||||
|
end;
|
||||||
|
|
||||||
|
CleanVersion := Copy(CleanVersion, 1, Length(CleanVersion)-2);
|
||||||
|
end;
|
||||||
|
|
||||||
|
Result := CleanVersion;
|
||||||
|
end;
|
||||||
|
|
||||||
|
{ Run the full package installer if downloaded. }
|
||||||
|
procedure TDExecuteFullDownload;
|
||||||
|
var InstallFile: String;
|
||||||
|
var ResultCode: Integer;
|
||||||
|
|
||||||
|
begin
|
||||||
|
InstallFile := ExpandConstant('{tmp}\{#MyAppName}.Full.exe')
|
||||||
|
|
||||||
|
if FileExists(InstallFile) then
|
||||||
|
begin
|
||||||
|
WizardForm.ProgressGauge.Style := npbstMarquee;
|
||||||
|
|
||||||
|
try
|
||||||
|
if Exec(InstallFile, '/SP- /SILENT /MERGETASKS="!desktopicon" /UPDATEPATH="'+UpdatePath+'"', '', SW_SHOW, ewWaitUntilTerminated, ResultCode) then
|
||||||
|
begin
|
||||||
|
if ResultCode <> 0 then
|
||||||
|
begin
|
||||||
|
DeleteFile(InstallFile);
|
||||||
|
Abort();
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
|
end else
|
||||||
|
begin
|
||||||
|
MsgBox('Could not run the full installer, please visit {#MyAppURL} and download the latest version manually. Error: '+SysErrorMessage(ResultCode), mbCriticalError, MB_OK);
|
||||||
|
|
||||||
|
DeleteFile(InstallFile);
|
||||||
|
Abort();
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
|
finally
|
||||||
|
WizardForm.ProgressGauge.Style := npbstNormal;
|
||||||
|
DeleteFile(InstallFile);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
end;
|
Reference in New Issue
Block a user