1
0
mirror of https://github.com/chylex/TweetDuck.git synced 2025-09-14 10:32:10 +02:00

Compare commits

...

59 Commits
1.4.1 ... 1.5

Author SHA1 Message Date
09d39df15a Release 1.5 2016-11-15 00:19:24 +01:00
1f9db3bda6 Rewrite folder write permission check to hopefully make it more reliable 2016-11-14 23:32:45 +01:00
b7104c8828 Remove privilege requirement from update & portable installer, handle updater privileges within TweetDuck 2016-11-14 20:53:56 +01:00
5da02b4092 Make the portable installer fully autonomous 2016-11-14 20:52:11 +01:00
802f1e3042 Refactor Process.Start uses (missing using statement, use WindowsUtils for elevation) 2016-11-14 19:39:26 +01:00
66db0df45a Add WindowsUtils.StartProcess for easier elevated process starting 2016-11-14 19:38:36 +01:00
650c2e2eb7 Remove redundant null check from WindowsUtils 2016-11-14 18:54:58 +01:00
6ab3754129 Update write permission check to use the storage folder 2016-11-14 14:06:15 +01:00
7dca0a8cab Add plugin config migration to the new data folder 2016-11-14 14:01:22 +01:00
7cd0b4ad54 Fix highlighted tweets staying in context menu after logging out of TweetDeck 2016-11-14 10:35:58 +01:00
97acb41eee Fix console errors caused by running browser scripts even outside of TweetDeck website 2016-11-14 10:35:42 +01:00
b916b9726e Add a method to check if a frame has a TweetDeck URL to BrowserUtils 2016-11-14 10:34:52 +01:00
32d3990ace Rewrite plugin data export and combined file stream identifiers, add missing plugin warning 2016-11-14 10:15:21 +01:00
cb1fd109cc Prevent missing plugin folders from causing a crash 2016-11-14 10:12:22 +01:00
0e68dd6185 Fix Configure button in PluginControl causing issues with mixed slash types 2016-11-14 09:47:56 +01:00
fb6502bc65 Rename plugin data folder to TD_Plugins for consistency 2016-11-14 09:39:48 +01:00
c7e7403781 Update plugin config to use the data folder instead of plugin root 2016-11-14 06:14:38 +01:00
bf224408a3 Rewrite PluginBridge to accommodate the functions to the new plugin folder handling 2016-11-14 06:14:14 +01:00
84b7078873 Assign a data folder to each plugin and add new folder handling functions 2016-11-14 06:09:05 +01:00
89e5943d8f Add a PluginFolder enum and a plugin data root path to Program 2016-11-14 05:43:30 +01:00
b78c4cb8f0 Move PluginEnvironment and PluginGroup to a separate Enums package 2016-11-14 05:08:18 +01:00
976ba074a8 Add a warning for outdated config in reply-account plugin 2016-11-13 15:18:10 +01:00
5205d59b96 Rewrite custom selector in reply-account plugin to fix and futureproof TweetDeck changes 2016-11-13 15:06:51 +01:00
e8394b9c08 Add browser console logging to debug output 2016-11-13 13:45:10 +01:00
9cd00239f9 Fix update installer creating Start Menu entry in portable mode (applied to 1.4.3) 2016-10-23 00:48:23 +02:00
b6b26142f8 Release 1.4.3 2016-10-22 22:13:15 +02:00
4ee99376fd Add a portable installer that uses the full installer with a custom flag 2016-10-22 21:49:31 +02:00
b0261342ff Add portable functionality to update installer 2016-10-22 21:11:48 +02:00
87fd2a521e Fix quoted tweet link in notification window not resetting
Closes #75
2016-10-21 06:50:34 +02:00
334793c6f6 Add full installer error/abort handling to the update installer
- Will abort and cleanup if full installer fails
- Fixed uninstallation files getting deleted if the full installer could
not be started
2016-10-20 18:48:35 +02:00
3e70d991bb Fix installer unable to run TD when TweetDuck.exe requires admin privileges 2016-10-20 18:45:10 +02:00
feec96fc5c Pass installation path to the updater to allow portability
Closes #77
2016-10-20 18:23:48 +02:00
765984709e Make sure TweetDeck uninstaller runs elevated, add safety nets and shield icons to buttons 2016-10-20 04:23:48 +02:00
c7c9931f68 Add an extension method to add UAC shield to a button 2016-10-18 16:21:08 +02:00
ae64573510 Remove old debug.log and ChromeDWriteFontCache on update install 2016-10-18 16:05:34 +02:00
d675af5aa4 Rename the debug log again to TD_Console.txt for consistency 2016-10-18 16:04:31 +02:00
9480d17cfc Change CEF debug file to jsconsole.log in storage path 2016-10-18 15:55:25 +02:00
5ac1df2283 Fix LockManager not finding correct process in debug 2016-10-18 15:48:55 +02:00
20119db883 Release 1.4.2 2016-10-09 16:04:38 +02:00
a4006deb8c Rewrite extra mouse button handling and fix skipping 'Back to Tweet'
Closes #74
2016-10-09 15:49:08 +02:00
25fa3cefab Fix notification tweet footer displaying in some tweets (after removing it in a prev commit) 2016-10-09 15:13:01 +02:00
bb5161eb34 Fix notifications only displaying the last one when multiple were enqueued at the same time 2016-10-09 14:58:11 +02:00
1bfc403a98 Fix typos in installer script comments 2016-10-09 14:42:13 +02:00
720d10e543 Fix update installer version cleanup issue and move idpDownloadAfter to InitializeWizard 2016-10-09 13:33:38 +02:00
30c117672e Make full installer not automatically run TweetDuck when in silent mode 2016-10-09 13:32:28 +02:00
6d1b5c77d1 Fix program arguments for the full installer execution in update installer 2016-10-09 00:58:13 +02:00
d1cbf608e0 Add WIP full package download to update installation file if CEF needs updating 2016-10-09 00:54:37 +02:00
7e3014c52d Refactor installation files (move .NET Framework check to a function) 2016-10-09 00:53:59 +02:00
82beb1f5a7 Fix context menu state changing when moving mouse quickly
Closes #70
2016-10-08 17:43:55 +02:00
657dc81300 Include ISS installer scripts and resources 2016-10-08 17:36:20 +02:00
8e22192dd3 Update gitignore to include some files from 'bld' folder 2016-10-08 17:35:28 +02:00
dc0b7d58e3 Add an Open Program Folder button to Settings - Advanced 2016-10-08 16:20:52 +02:00
6919e5bdb0 Fix hardware acceleration only being partial 2016-10-08 16:13:50 +02:00
9728a62efa Remove unnecessary code from notification html builder 2016-09-30 23:56:27 +02:00
276e070759 Fix recent TweetDeck change breaking media in notifications 2016-09-30 23:43:46 +02:00
fadea54f8d Remove legacy update notification warnings 2016-09-30 15:12:18 +02:00
523d340ade Remove the disabled() event handler in timeline-polls plugin 2016-09-30 15:09:52 +02:00
96fa7efb66 Fix a crash in reply-account plugin on Popout 2016-09-27 21:30:34 +02:00
03591f8317 Release 1.4.1 2016-09-27 18:25:58 +02:00
42 changed files with 912 additions and 251 deletions

6
.gitignore vendored
View File

@@ -17,9 +17,13 @@
[Rr]eleases/
x64/
x86/
bld/
[Bb]in/
[Oo]bj/
bld/*
!bld/gen_full.iss
!bld/gen_port.iss
!bld/gen_upd.iss
!bld/Resources
# Visual Studio 2015 cache/options directory
.vs/

View File

@@ -58,7 +58,7 @@ namespace TweetDck.Configuration{
Process foundProcess = Process.GetProcessById(pid);
using(Process currentProcess = Process.GetCurrentProcess()){
if (foundProcess.ProcessName == currentProcess.ProcessName){
if (foundProcess.ProcessName == currentProcess.ProcessName || foundProcess.MainWindowTitle == Program.BrandName){
LockingProcess = foundProcess;
}
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Drawing;
using System.Windows.Forms;
using TweetDck.Core.Utils;
namespace TweetDck.Core.Controls{
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){
textBox.KeyDown += (sender, args) => {
if (args.Control && args.KeyCode == Keys.A){

View File

@@ -8,8 +8,10 @@ using TweetDck.Core.Other;
using TweetDck.Resources;
using TweetDck.Core.Controls;
using System.Drawing;
using TweetDck.Core.Utils;
using TweetDck.Updates;
using TweetDck.Plugins;
using TweetDck.Plugins.Enums;
using TweetDck.Plugins.Events;
namespace TweetDck.Core{
@@ -52,6 +54,10 @@ namespace TweetDck.Core{
LifeSpanHandler = new LifeSpanHandler()
};
#if DEBUG
this.browser.ConsoleMessage += BrowserUtils.HandleConsoleMessage;
#endif
this.browser.LoadingStateChanged += Browser_LoadingStateChanged;
this.browser.FrameLoadEnd += Browser_FrameLoadEnd;
this.browser.RegisterJsObject("$TD", new TweetDeckBridge(this, notification));
@@ -113,7 +119,7 @@ namespace TweetDck.Core{
}
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");
if (plugins.HasAnyPlugin(PluginEnvironment.Browser)){

View File

@@ -9,6 +9,7 @@ using TweetDck.Core.Handling;
using TweetDck.Resources;
using TweetDck.Core.Utils;
using TweetDck.Plugins;
using TweetDck.Plugins.Enums;
namespace TweetDck.Core{
sealed partial class FormNotification : Form{
@@ -62,6 +63,7 @@ namespace TweetDck.Core{
public bool FreezeTimer { get; set; }
public bool ContextMenuOpen { get; set; }
public string CurrentUrl { get; private set; }
public string CurrentQuotedTweetUrl { get; set; }
public EventHandler Initialized;
private bool isInitialized;
@@ -99,6 +101,10 @@ namespace TweetDck.Core{
LifeSpanHandler = new LifeSpanHandler()
};
#if DEBUG
browser.ConsoleMessage += BrowserUtils.HandleConsoleMessage;
#endif
browser.IsBrowserInitializedChanged += Browser_IsBrowserInitializedChanged;
browser.FrameLoadEnd += Browser_FrameLoadEnd;
browser.RegisterJsObject("$TD", new TweetDeckBridge(owner, this));
@@ -225,7 +231,7 @@ namespace TweetDck.Core{
tweetQueue.Enqueue(notification);
UpdateTitle();
if (!timerProgress.Enabled){
if (totalTime == 0){
LoadNextNotification();
}
}
@@ -248,6 +254,7 @@ namespace TweetDck.Core{
Location = new Point(-32000, -32000);
progressBarTimer.Value = Program.UserConfig.NotificationTimerCountDown ? 1000 : 0;
timerProgress.Stop();
totalTime = 0;
StopMouseHook();
}
@@ -276,6 +283,7 @@ namespace TweetDck.Core{
private void LoadTweet(TweetNotification tweet){
CurrentUrl = tweet.Url;
CurrentQuotedTweetUrl = string.Empty; // load from JS
timerProgress.Stop();
totalTime = timeLeft = tweet.GetDisplayDuration(Program.UserConfig.NotificationDurationValue);

View File

@@ -18,6 +18,9 @@ namespace TweetDck.Core.Handling{
private readonly FormBrowser form;
private string lastHighlightedTweet;
private string lastHighlightedQuotedTweet;
public ContextMenuBrowser(FormBrowser form){
this.form = form;
}
@@ -31,11 +34,19 @@ namespace TweetDck.Core.Handling{
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)MenuCopyTweetUrl, "Copy tweet address");
if (!string.IsNullOrEmpty(TweetDeckBridge.LastHighlightedQuotedTweet)){
if (!string.IsNullOrEmpty(lastHighlightedQuotedTweet)){
model.AddSeparator();
model.AddItem((CefMenuCommand)MenuOpenQuotedTweetUrl, "Open quoted tweet in browser");
model.AddItem((CefMenuCommand)MenuCopyQuotedTweetUrl, "Copy quoted tweet address");
@@ -91,19 +102,19 @@ namespace TweetDck.Core.Handling{
return true;
case MenuOpenTweetUrl:
BrowserUtils.OpenExternalBrowser(TweetDeckBridge.LastHighlightedTweet);
BrowserUtils.OpenExternalBrowser(lastHighlightedTweet);
return true;
case MenuCopyTweetUrl:
Clipboard.SetText(TweetDeckBridge.LastHighlightedTweet, TextDataFormat.UnicodeText);
Clipboard.SetText(lastHighlightedTweet, TextDataFormat.UnicodeText);
return true;
case MenuOpenQuotedTweetUrl:
BrowserUtils.OpenExternalBrowser(TweetDeckBridge.LastHighlightedQuotedTweet);
BrowserUtils.OpenExternalBrowser(lastHighlightedQuotedTweet);
return true;
case MenuCopyQuotedTweetUrl:
Clipboard.SetText(TweetDeckBridge.LastHighlightedQuotedTweet, TextDataFormat.UnicodeText);
Clipboard.SetText(lastHighlightedQuotedTweet, TextDataFormat.UnicodeText);
return true;
}

View File

@@ -7,7 +7,7 @@ namespace TweetDck.Core.Handling{
private const int MenuSkipTweet = 26600;
private const int MenuFreeze = 26601;
private const int MenuCopyTweetUrl = 26602;
private const int MenuCopyTweetEmbeddedUrl = 26603;
private const int MenuCopyQuotedTweetUrl = 26603;
private readonly FormNotification form;
private readonly bool enableCustomMenu;
@@ -29,8 +29,8 @@ namespace TweetDck.Core.Handling{
if (!string.IsNullOrEmpty(form.CurrentUrl)){
model.AddItem((CefMenuCommand)MenuCopyTweetUrl, "Copy tweet address");
if (!string.IsNullOrEmpty(TweetDeckBridge.NotificationTweetEmbedded)){
model.AddItem((CefMenuCommand)MenuCopyTweetEmbeddedUrl, "Copy quoted tweet address");
if (!string.IsNullOrEmpty(form.CurrentQuotedTweetUrl)){
model.AddItem((CefMenuCommand)MenuCopyQuotedTweetUrl, "Copy quoted tweet address");
}
model.AddSeparator();
@@ -61,8 +61,8 @@ namespace TweetDck.Core.Handling{
Clipboard.SetText(form.CurrentUrl, TextDataFormat.UnicodeText);
return true;
case MenuCopyTweetEmbeddedUrl:
Clipboard.SetText(TweetDeckBridge.NotificationTweetEmbedded, TextDataFormat.UnicodeText);
case MenuCopyQuotedTweetUrl:
Clipboard.SetText(form.CurrentQuotedTweetUrl, TextDataFormat.UnicodeText);
return true;
}

View File

@@ -11,7 +11,6 @@ namespace TweetDck.Core.Handling{
public static string LastRightClickedLink = string.Empty;
public static string LastHighlightedTweet = string.Empty;
public static string LastHighlightedQuotedTweet = string.Empty;
public static string NotificationTweetEmbedded = string.Empty;
public static string ClipboardImagePath = string.Empty;
private readonly FormBrowser form;
@@ -81,8 +80,8 @@ namespace TweetDck.Core.Handling{
});
}
public void SetNotificationTweetEmbedded(string link){
form.InvokeSafe(() => NotificationTweetEmbedded = link);
public void SetNotificationQuotedTweet(string link){
notification.InvokeSafe(() => notification.CurrentQuotedTweetUrl = link);
}
public void OpenSettingsMenu(){

View File

@@ -6,6 +6,7 @@ using System.Windows.Forms;
using TweetDck.Core.Controls;
using TweetDck.Plugins;
using TweetDck.Plugins.Controls;
using TweetDck.Plugins.Enums;
using TweetDck.Plugins.Events;
namespace TweetDck.Core.Other{

View File

@@ -4,7 +4,7 @@ using System.Text;
namespace TweetDck.Core.Other.Settings.Export{
class CombinedFileStream : IDisposable{
public const char KeySeparator = '/';
public const char KeySeparator = '|';
private readonly Stream stream;
@@ -12,6 +12,10 @@ namespace TweetDck.Core.Other.Settings.Export{
this.stream = stream;
}
public void WriteFile(string[] identifier, string path){
WriteFile(string.Join(KeySeparator.ToString(), identifier), path);
}
public void WriteFile(string identifier, string path){
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;
public Entry(string identifier, byte[] contents){

View File

@@ -4,6 +4,7 @@ using System.IO;
using System.Linq;
using System.Windows.Forms;
using TweetDck.Plugins;
using TweetDck.Plugins.Enums;
namespace TweetDck.Core.Other.Settings.Export{
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))){
stream.WriteFile("config", Program.ConfigFilePath);
foreach(PathInfo path in EnumerateFilesRelative(plugins.PathOfficialPlugins)){
string[] split = path.Relative.Split(CombinedFileStream.KeySeparator);
if (split.Length < 3){
continue;
}
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;
foreach(Plugin plugin in plugins.Plugins){
foreach(PathInfo path in EnumerateFilesRelative(plugin.GetPluginFolder(PluginFolder.Data))){
try{
stream.WriteFile(new string[]{ "plugin.data", plugin.Identifier, path.Relative }, path.Full);
}catch(ArgumentOutOfRangeException e){
MessageBox.Show("Could not include a plugin file in the export. "+e.Message, "Export Profile", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
}
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){
@@ -72,6 +54,7 @@ namespace TweetDck.Core.Other.Settings.Export{
public bool Import(){
try{
bool updatedPlugins = false;
HashSet<string> missingPlugins = new HashSet<string>();
using(CombinedFileStream stream = new CombinedFileStream(new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.None))){
CombinedFileStream.Entry entry;
@@ -83,28 +66,24 @@ namespace TweetDck.Core.Other.Settings.Export{
Program.ReloadConfig();
break;
case "plugin.off":
string root = Path.Combine(plugins.PathOfficialPlugins, entry.Identifier.Split(CombinedFileStream.KeySeparator)[1]);
case "plugin.data":
string[] value = entry.KeyValue;
if (Directory.Exists(root)){
entry.WriteToFile(Path.Combine(plugins.PathOfficialPlugins, entry.Identifier.Substring(entry.KeyName.Length+1)), true);
entry.WriteToFile(Path.Combine(Program.PluginDataPath, value[0], value[1]), true);
if (plugins.IsPluginInstalled(value[0])){
updatedPlugins = true;
}
else{
missingPlugins.Add(value[0]);
}
break;
case "plugin.usr":
entry.WriteToFile(Path.Combine(plugins.PathCustomPlugins, entry.Identifier.Substring(entry.KeyName.Length+1)), true);
updatedPlugins = true;
break;
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));
// okay to and restart, 'cookies' is always the last entry
IsRestarting = true;
Program.Restart(new string[]{ "-importcookies" });
}
break;
@@ -112,7 +91,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 if (updatedPlugins){
plugins.Reload();
}
@@ -138,10 +124,10 @@ namespace TweetDck.Core.Other.Settings.Export{
}
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,
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{

View File

@@ -29,14 +29,15 @@
this.toolTip = new System.Windows.Forms.ToolTip(this.components);
this.btnEditCefArgs = 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.btnImport = new System.Windows.Forms.Button();
this.btnExport = new System.Windows.Forms.Button();
this.groupPerformance = new System.Windows.Forms.GroupBox();
this.groupConfiguration = new System.Windows.Forms.GroupBox();
this.groupApp = new System.Windows.Forms.GroupBox();
this.btnRestartLog = new System.Windows.Forms.Button();
this.btnRestart = new System.Windows.Forms.Button();
this.btnOpenAppFolder = new System.Windows.Forms.Button();
this.groupPerformance.SuspendLayout();
this.groupConfiguration.SuspendLayout();
this.groupApp.SuspendLayout();
@@ -90,6 +91,30 @@
this.btnEditCSS.UseVisualStyleBackColor = true;
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
//
this.btnReset.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
@@ -154,38 +179,26 @@
//
// groupApp
//
this.groupApp.Controls.Add(this.btnOpenAppFolder);
this.groupApp.Controls.Add(this.btnRestartLog);
this.groupApp.Controls.Add(this.btnRestart);
this.groupApp.Location = new System.Drawing.Point(198, 9);
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.TabStop = false;
this.groupApp.Text = "App";
//
// btnRestartLog
// btnOpenAppFolder
//
this.btnRestartLog.Location = new System.Drawing.Point(6, 48);
this.btnRestartLog.Name = "btnRestartLog";
this.btnRestartLog.Size = new System.Drawing.Size(171, 23);
this.btnRestartLog.TabIndex = 17;
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, 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);
this.btnOpenAppFolder.Location = new System.Drawing.Point(6, 19);
this.btnOpenAppFolder.Name = "btnOpenAppFolder";
this.btnOpenAppFolder.Size = new System.Drawing.Size(171, 23);
this.btnOpenAppFolder.TabIndex = 16;
this.btnOpenAppFolder.Text = "Open Program Folder";
this.toolTip.SetToolTip(this.btnOpenAppFolder, "Opens the folder where the app is located.");
this.btnOpenAppFolder.UseVisualStyleBackColor = true;
this.btnOpenAppFolder.Click += new System.EventHandler(this.btnOpenAppFolder_Click);
//
// TabSettingsAdvanced
//
@@ -223,5 +236,6 @@
private System.Windows.Forms.GroupBox groupApp;
private System.Windows.Forms.Button btnRestartLog;
private System.Windows.Forms.Button btnRestart;
private System.Windows.Forms.Button btnOpenAppFolder;
}
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Diagnostics;
using System.Windows.Forms;
using TweetDck.Core.Controls;
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){
Program.Restart();
}

View File

@@ -4,6 +4,7 @@ using System.Globalization;
using System.IO;
using System.Net;
using System.Windows.Forms;
using CefSharp;
namespace TweetDck.Core.Utils{
static class BrowserUtils{
@@ -27,7 +28,7 @@ namespace TweetDck.Core.Utils{
}
public static void OpenExternalBrowser(string url){ // TODO implement mailto
Process.Start(url);
using(Process.Start(url)){}
}
public static string GetFileNameFromUrl(string url){
@@ -47,5 +48,15 @@ namespace TweetDck.Core.Utils{
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
}
}

View File

@@ -15,6 +15,7 @@ namespace TweetDck.Core.Utils{
public const int MOUSEEVENTF_RIGHTUP = 0x10;
public const int SB_HORZ = 0;
public const int BCM_SETSHIELD = 0x160C;
public const int WH_MOUSE_LL = 14;
public const int WH_MOUSEWHEEL = 0x020A;

View File

@@ -1,5 +1,5 @@
using System.IO;
using System.Linq;
using System.Diagnostics;
using System.IO;
using System.Security.AccessControl;
using System.Security.Principal;
@@ -7,27 +7,32 @@ namespace TweetDck.Core.Utils{
static class WindowsUtils{
public static bool CheckFolderPermission(string path, FileSystemRights right){
try{
AuthorizationRuleCollection rules = Directory.GetAccessControl(path).GetAccessRules(true, true, typeof(SecurityIdentifier));
WindowsIdentity identity = WindowsIdentity.GetCurrent();
AuthorizationRuleCollection collection = Directory.GetAccessControl(path).GetAccessRules(true, true, typeof(NTAccount));
if (identity == null || identity.Groups == null){
return false;
}
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;
foreach(FileSystemAccessRule rule in collection){
if ((rule.FileSystemRights & right) == right){
return true;
}
}
return accessAllow && !accessDeny;
return false;
}
catch{
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);
}
}
}

View File

@@ -8,6 +8,7 @@ using System.Windows.Forms;
using Microsoft.Win32;
using TweetDck.Core.Other;
using System.Drawing;
using TweetDck.Core.Controls;
namespace TweetDck.Migration{
static class MigrationManager{
@@ -41,8 +42,9 @@ namespace TweetDck.Migration{
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;
btnMigrateAndPurge.Location = new Point(btnMigrateAndPurge.Location.X-20, btnMigrateAndPurge.Location.Y);
btnMigrateAndPurge.Width += 20;
btnMigrateAndPurge.SetElevated();
if (formQuestion.ShowDialog() == DialogResult.OK){
decision = formQuestion.ClickedButton == btnMigrateAndPurge ? MigrationDecision.MigratePurge :
@@ -86,9 +88,19 @@ namespace TweetDck.Migration{
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();
if (guid != null){
const string prompt = "TweetDeck is still installed on your computer, do you want to uninstall it?";
using(FormMessage formQuestion = new FormMessage("Uninstall TweetDeck", prompt, MessageBoxIcon.Question)){
formQuestion.AddButton("No");
Button btnYes = formQuestion.AddButton("Yes");
btnYes.SetElevated();
if (formQuestion.ShowDialog() == DialogResult.OK && formQuestion.ClickedButton == btnYes && MigrationUtils.RunUninstaller(guid, 0)){
CleanupTweetDeck();
}
}
}
Program.UserConfig.IgnoreUninstallCheck = true;
@@ -155,8 +167,8 @@ namespace TweetDck.Migration{
// uninstall in the background
string guid = MigrationUtils.FindProgramGuidByDisplayName("TweetDeck");
if (guid != null){
MigrationUtils.RunUninstaller(guid, 5000);
if (guid != null && !MigrationUtils.RunUninstaller(guid, 5000)){
return;
}
// registry cleanup

View File

@@ -2,18 +2,25 @@
using System.Diagnostics;
using System.Linq;
using Microsoft.Win32;
using TweetDck.Core.Utils;
namespace TweetDck.Migration{
static class MigrationUtils{
public static void RunUninstaller(string guid, int timeout){
Process uninstaller = Process.Start("msiexec.exe", "/x "+guid+" /quiet /qn");
public static bool RunUninstaller(string guid, int timeout){
try{
Process uninstaller = WindowsUtils.StartProcess("msiexec.exe", "/x "+guid+" /quiet /qn", true);
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
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();
}
uninstaller.Close();
return true;
}catch(Exception){
return false;
}
}

View File

@@ -50,7 +50,7 @@ namespace TweetDck.Plugins.Controls{
}
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){

View File

@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
namespace TweetDck.Plugins{
namespace TweetDck.Plugins.Enums{
[Flags]
enum PluginEnvironment{
None = 0,

View File

@@ -0,0 +1,5 @@
namespace TweetDck.Plugins.Enums{
enum PluginFolder{
Root, Data
}
}

View File

@@ -1,4 +1,4 @@
namespace TweetDck.Plugins{
namespace TweetDck.Plugins.Enums{
enum PluginGroup{
Official, Custom
}

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using TweetDck.Plugins.Enums;
namespace TweetDck.Plugins{
class Plugin{
@@ -26,29 +27,30 @@ namespace TweetDck.Plugins{
public bool HasConfig{
get{
return ConfigFile.Length > 0 && GetFullPathIfSafe(ConfigFile).Length > 0;
return ConfigFile.Length > 0 && GetFullPathIfSafe(PluginFolder.Data, ConfigFile).Length > 0;
}
}
public string ConfigPath{
get{
return HasConfig ? Path.Combine(path, ConfigFile) : string.Empty;
return HasConfig ? Path.Combine(GetPluginFolder(PluginFolder.Data), ConfigFile) : string.Empty;
}
}
public bool HasDefaultConfig{
get{
return ConfigDefault.Length > 0 && GetFullPathIfSafe(ConfigDefault).Length > 0;
return ConfigDefault.Length > 0 && GetFullPathIfSafe(PluginFolder.Root, ConfigDefault).Length > 0;
}
}
public string DefaultConfigPath{
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 Dictionary<string, string> metadata = new Dictionary<string, string>(4){
{ "NAME", "" },
@@ -64,8 +66,13 @@ namespace TweetDck.Plugins{
private bool? canRun;
private Plugin(string path, PluginGroup group){
this.path = path;
this.identifier = group.GetIdentifierPrefix()+Path.GetFileName(path);
string name = 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.Environments = PluginEnvironment.None;
}
@@ -74,7 +81,26 @@ namespace TweetDck.Plugins{
string configPath = ConfigPath, defaultConfigPath = 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{
Directory.CreateDirectory(dataFolder);
File.Copy(defaultConfigPath, configPath, false);
}catch(Exception e){
Program.Reporter.HandleException("Plugin Loading Error", "Could not generate a configuration file for '"+identifier+"' plugin.", true, e);
@@ -85,18 +111,27 @@ namespace TweetDck.Plugins{
public string GetScriptPath(PluginEnvironment environment){
if (Environments.HasFlag(environment)){
string file = environment.GetScriptFile();
return file != null ? Path.Combine(path, file) : string.Empty;
return file != null ? Path.Combine(pathRoot, file) : string.Empty;
}
else{
return string.Empty;
}
}
public string GetFullPathIfSafe(string relativePath){
string fullPath = Path.Combine(path, relativePath);
public string GetPluginFolder(PluginFolder folder){
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{
string folderPathName = new DirectoryInfo(path).FullName;
string folderPathName = new DirectoryInfo(rootFolder).FullName;
DirectoryInfo currentInfo = new DirectoryInfo(fullPath);
while(currentInfo.Parent != null){
@@ -124,7 +159,7 @@ namespace TweetDck.Plugins{
public override bool Equals(object obj){
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){

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.IO;
using System.Text;
using TweetDck.Plugins.Enums;
using TweetDck.Plugins.Events;
namespace TweetDck.Plugins{
@@ -18,35 +19,26 @@ namespace TweetDck.Plugins{
fileCache.Clear();
}
private string GetFullPathIfSafe(int token, string path){
private string GetFullPathOrThrow(int token, PluginFolder folder, string path){
Plugin plugin = manager.GetPluginFromToken(token);
return plugin == null ? string.Empty : plugin.GetFullPathIfSafe(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);
string fullPath = plugin == null ? string.Empty : plugin.GetFullPathIfSafe(folder, path);
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;
if (cache && fileCache.TryGetValue(fullPath, out cachedContents)){
if (readCached && fileCache.TryGetValue(fullPath, out cachedContents)){
return cachedContents;
}
@@ -59,25 +51,39 @@ namespace TweetDck.Plugins{
}
}
public void DeleteFile(int token, string path){
string fullPath = GetFullPathIfSafe(token, path);
// Public methods
if (fullPath.Length == 0){
throw new Exception("File path has to be relative to the plugin folder.");
}
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);
fileCache.Remove(fullPath);
File.Delete(fullPath);
}
public bool CheckFileExists(int token, string path){
string fullPath = GetFullPathIfSafe(token, path);
return File.Exists(GetFullPathOrThrow(token, PluginFolder.Data, path));
}
if (fullPath.Length == 0){
throw new Exception("File path has to be relative to the plugin folder.");
}
public string ReadFileRoot(int token, string path){
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));
}
}
}

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using CefSharp;
using TweetDck.Plugins.Enums;
using TweetDck.Plugins.Events;
using TweetDck.Resources;
@@ -34,6 +35,10 @@ namespace TweetDck.Plugins{
this.Bridge = new PluginBridge(this);
}
public bool IsPluginInstalled(string identifier){
return plugins.Any(plugin => plugin.Identifier.Equals(identifier));
}
public IEnumerable<Plugin> GetPluginsByGroup(PluginGroup group){
return plugins.Where(plugin => plugin.Group == group);
}
@@ -103,6 +108,10 @@ namespace TweetDck.Plugins{
}
private IEnumerable<Plugin> LoadPluginsFrom(string path, PluginGroup group){
if (!Directory.Exists(path)){
yield break;
}
foreach(string fullDir in Directory.EnumerateDirectories(path, "*", SearchOption.TopDirectoryOnly)){
string error;
Plugin plugin = Plugin.CreateFromFolder(fullDir, group, out error);

View File

@@ -1,4 +1,5 @@
using System.Text;
using TweetDck.Plugins.Enums;
namespace TweetDck.Plugins{
static class PluginScriptGenerator{

View File

@@ -23,8 +23,8 @@ namespace TweetDck{
public const string BrowserSubprocess = BrandName+".Browser.exe";
public const string VersionTag = "1.4";
public const string VersionFull = "1.4.0.0";
public const string VersionTag = "1.5";
public const string VersionFull = "1.5.0.0";
public static readonly Version Version = new Version(VersionTag);
@@ -35,6 +35,7 @@ namespace TweetDck{
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 PluginDataPath = Path.Combine(StoragePath, "TD_Plugins");
public static readonly string ConfigFilePath = Path.Combine(StoragePath, "TD_UserConfig.cfg");
private static readonly string LogFilePath = Path.Combine(StoragePath, "TD_Log.txt");
@@ -56,8 +57,8 @@ namespace TweetDck{
WindowRestoreMessage = NativeMethods.RegisterWindowMessage("TweetDuckRestore");
if (!WindowsUtils.CheckFolderPermission(ProgramPath, FileSystemRights.WriteData)){
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);
if (!WindowsUtils.CheckFolderPermission(StoragePath, FileSystemRights.WriteData)){
MessageBox.Show(BrandName+" does not have write permissions to the storage folder: "+StoragePath, "Permission Error", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
@@ -120,6 +121,7 @@ namespace TweetDck{
Locale = Args.GetValue("-locale", "en"),
CachePath = StoragePath,
BrowserSubprocessPath = File.Exists(BrowserSubprocess) ? BrowserSubprocess : "CefSharp.BrowserSubprocess.exe",
LogFile = Path.Combine(StoragePath, "TD_Console.txt"),
#if !DEBUG
LogSeverity = Args.HasFlag("-log") ? LogSeverity.Info : LogSeverity.Disable
#endif
@@ -127,6 +129,11 @@ namespace TweetDck{
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());
Application.ApplicationExit += (sender, args) => ExitCleanup();
@@ -142,7 +149,9 @@ namespace TweetDck{
if (mainForm.UpdateInstallerPath != null){
ExitCleanup();
Process.Start(mainForm.UpdateInstallerPath, "/SP- /SILENT /NOICONS /CLOSEAPPLICATIONS");
bool runElevated = !IsPortable || !WindowsUtils.CheckFolderPermission(ProgramPath, FileSystemRights.WriteData);
WindowsUtils.StartProcess(mainForm.UpdateInstallerPath, "/SP- /SILENT /CLOSEAPPLICATIONS /UPDATEPATH=\""+ProgramPath+"\""+(IsPortable ? " /PORTABLE=1" : ""), runElevated); // ProgramPath has a trailing backslash
Application.Exit();
}
}

View File

@@ -50,7 +50,9 @@ namespace TweetDck{
UseVisualStyleBackColor = true
};
btnOpenLog.Click += (sender, args) => Process.Start(logFile);
btnOpenLog.Click += (sender, args) => {
using(Process.Start(logFile)){}
};
form.AddActionControl(btnOpenLog);

View File

@@ -8,7 +8,7 @@ Custom reply account
chylex
[version]
1.1
1.2
[website]
https://tweetduck.chylex.com

View File

@@ -6,7 +6,7 @@ enabled(){
this.lastSelectedAccount = null;
this.uiComposeTweetEvent = (e, data) => {
if (data.type !== "reply"){
if (data.type !== "reply" || data.popFromInline || !("element" in data)){
return;
}
@@ -14,8 +14,25 @@ enabled(){
if (configuration.useAdvancedSelector){
if (configuration.customSelector){
var column = TD.controller.columnManager.get(data.element.closest("section.column").attr("data-column"));
query = configuration.customSelector(column);
if (configuration.customSelector.toString().startsWith("function (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{
$TD.alert("warning", "Plugin reply-account has invalid configuration: useAdvancedSelector is true, but customSelector function is missing");

View File

@@ -34,42 +34,43 @@
* 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.
*
* 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_customtimeline, col_messages, col_usertweets, col_favorites, col_activity,
* 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()));
*
* You can also get the jQuery column object using: $("section.column[data-column='"+column.ui.state.columnKey+"']")
* 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).
*
*
* 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,
/*customSelector: function(column){
var titleObj = $(column.getTitleHTML());
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"){
/*customSelector: function(type, title, account, column){
if (type === "col_search" && title === "TweetDuck"){
// This is a search column that looks for 'TweetDuck' in the tweets,
// search columns are normally linked to the preferred account
// so this forces the @TryTweetDuck account to be used instead.
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,
// but I want to reply to tweets from my official account.
return "@chylexmc";

View File

@@ -6,15 +6,11 @@ constructor(){
enabled(){
// add a stylesheet
this.css = window.TDPF_createCustomStyle(this);
this.css.insert(".column-detail .timeline-poll-container { display: none }");
window.TDPF_createCustomStyle(this).insert(".column-detail .timeline-poll-container { display: none }");
// setup layout injecting
this.prevMustaches = {};
var injectLayout = (mustache, onlyIfNotFound, search, replace) => {
if (TD.mustaches[mustache].indexOf(onlyIfNotFound) === -1){
this.prevMustaches[mustache] = TD.mustaches[mustache];
TD.mustaches[mustache] = TD.mustaches[mustache].replace(search, replace);
}
};
@@ -25,6 +21,5 @@ enabled(){
}
disabled(){
this.css.remove();
Object.keys(this.prevMustaches).forEach(mustache => TD.mustaches[mustache] = this.prevMustaches[mustache]);
// not needed, plugin reloads the page when enabled or disabled
}

View File

@@ -104,11 +104,14 @@
var html = $(tweet.render({
withFooter: false,
withTweetActions: false,
withMediaPreview: false
withMediaPreview: true,
isMediaPreviewOff: true,
isMediaPreviewSmall: false,
isMediaPreviewLarge: false
}));
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") || "";
@@ -376,32 +379,39 @@
//
// Block: Support for extra mouse buttons.
//
window.TDGF_onMouseClickExtra = function(button){
if (button === 1){ // back button
var inlineComposer, drawerComposer, modal;
(function(){
var tryClickSelector = function(selector, parent){
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();
}
else if (highlightedColumnEle && highlightedColumnEle.closest(".js-column").is(".is-shifted-1")){
highlightedColumnEle.find(".js-column-back").first().click();
}
else if ((drawerComposer = $(".js-app-content.is-open .js-drawer-close:visible")).length === 1){
drawerComposer.click();
}
else{
};
window.TDGF_onMouseClickExtra = function(button){
if (button === 1){ // back button
tryCloseModal() ||
tryClickSelector(".js-inline-compose-close") ||
tryCloseHighlightedColumn() ||
tryClickSelector(".js-app-content.is-open .js-drawer-close:visible") ||
tryClickSelector(".is-shifted-2 .js-tweet-social-proof-back") ||
$(".js-column-back").click();
}
}
else if (button === 2){ // forward button
if (highlightedTweetEle){
highlightedTweetEle.children().first().click();
else if (button === 2){ // forward button
if (highlightedTweetEle){
highlightedTweetEle.children().first().click();
}
}
}
};
};
})();
//
// Block: Fix scheduled tweets not showing up sometimes.

View File

@@ -114,7 +114,7 @@
var account = embedded[0].getElementsByClassName("account-link");
if (account.length === 0)return;
$TD.setNotificationTweetEmbedded(account[0].getAttribute("href")+"/status/"+tweetId);
$TD.setNotificationQuotedTweet(account[0].getAttribute("href")+"/status/"+tweetId);
})();
//

View File

@@ -9,7 +9,7 @@
$TDP.checkFileExists(token, fileNameUser).then(exists => {
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;
try{

View File

@@ -19,7 +19,6 @@
//
var createUpdateNotificationElement = function(version, download){
var outdated = version === "unsupported";
var tweetdick = version === "tweetdick";
var ele = $("#tweetdck-update");
var existed = ele.length > 0;
@@ -37,15 +36,6 @@
"<button class='btn btn-negative tdu-btn-dismiss'><span class='label'>Dismiss</span></button>",
"</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'>",
"<p class='tdu-title'>"+$TDU.brandName+" Update</p>",
@@ -118,10 +108,6 @@
$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(){
$TDU.onUpdateDismissed(version);
ele.slideUp(function(){ ele.remove(); });
@@ -145,13 +131,6 @@
return;
}
else if ($TDU.brandName === "TweetDick"){
if ($TDU.dismissedVersionTag !== "tweetdick"){
createUpdateNotificationElement("tweetdick");
}
return;
}
clearTimeout(updateCheckTimeoutID);
updateCheckTimeoutID = setTimeout(runUpdateCheck, 1000*60*60); // 1 hour

View File

@@ -206,12 +206,13 @@
<Compile Include="Plugins\Controls\PluginListFlowLayout.Designer.cs">
<DependentUpon>PluginListFlowLayout.cs</DependentUpon>
</Compile>
<Compile Include="Plugins\Enums\PluginFolder.cs" />
<Compile Include="Plugins\Plugin.cs" />
<Compile Include="Plugins\Events\PluginChangedStateEventArgs.cs" />
<Compile Include="Plugins\PluginBridge.cs" />
<Compile Include="Plugins\PluginConfig.cs" />
<Compile Include="Plugins\PluginEnvironment.cs" />
<Compile Include="Plugins\PluginGroup.cs" />
<Compile Include="Plugins\Enums\PluginEnvironment.cs" />
<Compile Include="Plugins\Enums\PluginGroup.cs" />
<Compile Include="Plugins\Events\PluginLoadEventArgs.cs" />
<Compile Include="Plugins\PluginManager.cs" />
<Compile Include="Plugins\PluginScriptGenerator.cs" />

9
bld/Resources/LICENSE Normal file
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

137
bld/gen_full.iss Normal file
View File

@@ -0,0 +1,137 @@
; 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;
{ 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
View 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;

265
bld/gen_upd.iss Normal file
View File

@@ -0,0 +1,265 @@
; 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, '&', '&&')}}"; 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;