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

Compare commits

...

192 Commits
1.3.2 ... 1.5.1

Author SHA1 Message Date
04cd662d78 Release 1.5.1 2016-11-23 05:03:49 +01:00
da597f076f Fix quote escaping in updater arguments 2016-11-23 04:57:13 +01:00
fab3efdcf5 Fix update checker running outside of TweetDeck website 2016-11-23 04:55:58 +01:00
a55509a34d Add an old TweetDeck profile detection & warning message to the full installer 2016-11-23 03:51:58 +01:00
84fb1c5b2b Make update installer use TweetDuck's initial command line arguments 2016-11-23 03:20:38 +01:00
391a90e1df Add a -debugupdates command line argument to allow prereleases in update checker 2016-11-23 02:08:33 +01:00
e0fe39195d Add HasValue method to CommandLineArgs 2016-11-23 02:06:41 +01:00
385fead81a Fix updater calling onUpdateCheckFinished when eventID parameter is undefined 2016-11-23 02:05:44 +01:00
648d1b9aa9 Rewrite lock system to be more reliable and handle exceptions better 2016-11-19 05:57:55 +01:00
3f0028913d Move unhandled exception handler from Program to Reporter class 2016-11-19 03:11:37 +01:00
45e6ec8b0f Fix FormMessage fonts 2016-11-18 20:28:00 +01:00
a3fbaa0b34 Make program restarts as reliable as possible
Closes #80
2016-11-18 19:59:21 +01:00
7102cbfb3b Add a retry button to the warning message when TweetDuck takes too long to restart 2016-11-18 19:35:02 +01:00
cb61dc742f Push minor tweak in ExecuteScriptAsync in image pasting code 2016-11-16 18:29:35 +01:00
cd53f6e757 Disable 'Show Error Log' button if the logging failed 2016-11-16 14:51:47 +01:00
c64f7daa8d Cleanup browser subprocess path code 2016-11-16 14:25:52 +01:00
e70d792654 Fix plugin status not updating from new config after importing profile
Closes #79
2016-11-16 04:10:17 +01:00
9ae533f907 Remove TweetDick config file compatibility 2016-11-15 19:26:23 +01:00
cfe92f18e3 Remove all TweetDeck and other migration code 2016-11-15 19:20:43 +01:00
e2a34ea28e Remove original CheckFolderPermission and replace it with the lazy workaround 2016-11-15 18:10:25 +01:00
ec8000360e Windows file permissions can go to hell 2016-11-15 01:01:41 +01:00
57b0821e19 Revert "Rewrite folder write permission check to hopefully make it more reliable"
This reverts commit 1f9db3bda6.
2016-11-15 00:47:15 +01:00
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
28cc60d007 Tweak CommandLineArgsParser to slightly improve reliability with quoted strings 2016-09-27 18:11:50 +02:00
1fa69bdb3b Add invalid data folder error reporting and env variable expansion for absolute paths 2016-09-27 18:00:05 +02:00
11f5f9b72e Add Reporter.HandleEarlyFailure for non-recoverable errors before Reporter is setup 2016-09-27 17:58:13 +02:00
61f6543041 Change Source Code link to Tips & Tricks 2016-09-27 17:37:19 +02:00
0c9ab32f37 Add additional functionality to the back mouse button (inline and drawer composer, fix order) 2016-09-27 04:16:50 +02:00
fde984d02b Add a hover class to Notification body element and only display skip button when over 2016-09-27 03:48:14 +02:00
f23db31306 Increase default size of Edit CSS dialog 2016-09-26 17:20:25 +02:00
8dce99b8b3 Add support for ctrl+a shortcut in multiline textboxes 2016-09-26 17:20:10 +02:00
342ac51cda Tweak the skip button position in Notification window 2016-09-26 17:14:17 +02:00
ba31f7ae01 Update TweetDuck website to https 2016-09-26 16:31:06 +02:00
8d0fa030f8 Remove d3dcompiler_43.dll that used to be required for Win XP 2016-09-26 16:16:20 +02:00
d030a79c81 Fix Release build compliation error 2016-09-26 16:10:21 +02:00
6690efc4d9 Address code analysis issues 2016-09-26 15:53:08 +02:00
afa8098463 Add two restart buttons to Settings - Advanced 2016-09-26 14:28:14 +02:00
c064e579d2 Add Program.Restart and fix restarting causing loss of initial program arguments 2016-09-26 14:05:44 +02:00
01dc4e4714 Update CommandLineArgsParser to use CommandLineArgs 2016-09-26 13:58:59 +02:00
6fbc246b12 Use CommandLineArgs in Program and fix broken -locale parameter 2016-09-26 13:56:13 +02:00
1efe2a02f7 Add a CommandLineArgs class for easy arg management 2016-09-26 13:52:55 +02:00
ab2ab06f60 Reorganize utility classes and methods in TweetDeck Migration code 2016-09-26 04:19:50 +02:00
a71d889682 Replace FormMigrationQuestion with a custom FormMessage 2016-09-25 23:10:18 +02:00
2252d85b27 Add MessageBoxIcon.Question support to FormMessage 2016-09-25 23:09:36 +02:00
3f09100177 Update version of the design-revert plugin 2016-09-25 19:01:39 +02:00
6b79c89f42 Add a button to skip current notification 2016-09-25 16:53:00 +02:00
5f249d4603 Add a <body> td-example-notification attribute for example notifications 2016-09-25 16:52:22 +02:00
fa64309909 Hide the old TweetDeck column Clear button when clear-columns plugin is enabled 2016-09-25 15:35:15 +02:00
c46aafdab6 Add column resetting to clear-columns plugin 2016-09-25 15:31:18 +02:00
3e9c397494 Fix inconsistency with loadConfigurationFile's onFailure parameter 2016-09-25 03:21:44 +02:00
47935165db Update current plugins to use the new CSS functionality 2016-09-25 02:37:16 +02:00
be77f753e7 Add a global function to easily add custom styles in plugins 2016-09-25 02:34:19 +02:00
2c30613279 Fix reply-account plugin for docked replies 2016-09-23 22:09:06 +02:00
d83eaec987 Update reply-account plugin version 2016-09-23 21:14:53 +02:00
e6f199a224 Implement #last in reply-account and fix bugs in its code 2016-09-23 21:13:54 +02:00
6636a2aa9e Add an experimental timeline-polls plugin 2016-09-23 19:49:56 +02:00
221bdc58fe Tweak support for old configs in reply-account plugin 2016-09-23 16:05:53 +02:00
e48a068e9d Redesign reply-account plugin configuration and add WIP last account setting 2016-09-23 15:55:13 +02:00
3371c31e63 Release 1.4 2016-09-22 00:58:27 +02:00
aa2c60f7e9 Move cookie import code to ExportManager 2016-09-20 17:15:34 +02:00
f7dc200684 Kill process instead of Environment.FailFast if possible 2016-09-20 17:11:36 +02:00
ad306c56c7 Move log file to storage path 2016-09-20 16:58:00 +02:00
86aba1eb52 Update all exception handlers 2016-09-20 16:55:51 +02:00
826f1aba7a Tweak exception handling in Reporter (use Environment.FailFast and change log button title) 2016-09-20 16:51:29 +02:00
8abf4364c6 Replace calls to Program.Log 2016-09-20 16:39:31 +02:00
4c45d91d4e Move logging to Reporter and make a static instance of it in Program 2016-09-20 16:38:15 +02:00
3176b6cb8f Create a Reporter class with improved HandleException code 2016-09-20 16:34:56 +02:00
27971e09cd Add new Control related functionality to FormMessage 2016-09-20 16:31:53 +02:00
bf95ae00de Tweak the message for missing write permission 2016-09-20 16:10:46 +02:00
0dbfa7e101 Improve reliability of directory write perm checking 2016-09-20 16:00:41 +02:00
85d09c4b5e Add a FormMessage class for custom message boxes 2016-09-20 07:47:16 +02:00
4f9bc40476 Update cache clearing for CEF 53 and clear old cache files on first startup 2016-09-19 14:03:37 +02:00
757ccbf9d3 Switch locale to English and add spell check setting
Closes #62
2016-09-19 13:33:44 +02:00
4cf9730130 Make sure the config is not loaded before old process exits when restarting 2016-09-19 13:24:23 +02:00
1dd0d70ab9 Reorganize Program.LogFile from property to readonly field 2016-09-19 13:08:27 +02:00
340eaece0f Update CefSharp to 53 and update the readme 2016-09-19 00:12:48 +02:00
c151e7cd37 Remove everything TweetDick related 2016-09-18 23:24:11 +02:00
ca0baae832 Release 1.3.3 2016-09-18 22:23:48 +02:00
478e6ed8df Move config loading and migration execution to an earlier point 2016-09-18 22:19:02 +02:00
7388eb07ca Increase process close timeouts to 20 seconds from 10 2016-09-18 21:49:41 +02:00
c38d507e50 Hopefully fix an occasional crash when importing session and restarting 2016-09-18 21:48:40 +02:00
5aee087a57 Add a Program Files warning if not running as administrator 2016-09-18 21:17:30 +02:00
0cda3702ea Change notification scroll trigger cursor position for windows without side borders 2016-09-18 21:08:09 +02:00
ce55226d0c Add error handling to logging 2016-09-18 20:50:43 +02:00
0bab0a9963 Update reply-account plugin metadata and default config warning 2016-09-18 20:29:49 +02:00
34955b7083 Rewrite Plugin default config handling and config error reporting 2016-09-18 20:29:31 +02:00
ba6ce072ac Add support for a -datafolder command line argument 2016-09-18 19:42:00 +02:00
f39fd00697 Add a TweetDick warning and migration prompt 2016-09-18 19:41:37 +02:00
58fc1be1d5 Add CefSharp version to NuGet instructions 2016-09-12 14:29:48 +02:00
1fdf9bffb6 Disable global context menu items when in textarea or text selection 2016-09-07 21:47:31 +02:00
2ad179ef8e Rename configuration file in reply-account plugin 2016-09-07 15:03:06 +02:00
d42cc5b762 Remove .js file block in Plugin folders 2016-09-07 15:01:08 +02:00
403658f622 Add config file to reply-account plugin metadata 2016-09-07 14:43:17 +02:00
6ca35685db Add button for easy Plugin config file access 2016-09-07 14:42:21 +02:00
bfc6822f69 Fix Plugins window title 2016-09-07 14:20:35 +02:00
b0386937d7 Remove lnk updater from the TweetDeck migration system 2016-09-05 21:25:25 +02:00
9436540b2f Add CultureInfo.CurrentCulture to time in logger 2016-09-05 20:45:39 +02:00
45b0ece342 Update Plugin sorting and design (separator, colors) 2016-09-05 20:44:58 +02:00
5c147d3648 Fix missing and unfocused scrollbar in Plugins window 2016-09-05 20:44:07 +02:00
8fa68c428f Add 'Open (quoted) tweet in browser' context menu items and move them down 2016-09-05 16:17:23 +02:00
e2be90191e Standardize context menu items using Chrome's style 2016-09-05 14:30:07 +02:00
339da2f1a9 Add unsupported system notification
Closes #58
2016-09-04 20:30:35 +02:00
b2cc5d50bd Address code analysis issues and fix unused parameter in CombinedFileStream.WriteToFile 2016-09-04 19:32:33 +02:00
f38b188046 Add a safety net to plugin file export in case the path is too long 2016-09-04 18:10:50 +02:00
9e45628e87 Rename 'Export/Import Settings' to Profile and add Plugin exporting 2016-09-04 16:58:03 +02:00
bf76398627 Optimize icon loading and size in Forms, fix Plugins Form title inconsistency 2016-09-04 15:32:00 +02:00
81d5728964 Add scrollbars to Edit CSS window 2016-09-04 13:57:38 +02:00
d76027558b Add a custom alert function with customizable icon and no text length limit 2016-09-04 13:19:52 +02:00
1450cc24a3 Reorganize global context menu items into a sub-menu 2016-09-04 13:10:32 +02:00
f41523c1b2 Remove redundant 'using' directives 2016-09-04 04:53:37 +02:00
4019463e68 Fix occasional incorrect location of child windows 2016-09-04 04:52:59 +02:00
5e2e239f5e Force Reload All button in Plugins window to trigger the event even without changes 2016-09-04 04:44:08 +02:00
bc7856b6c0 Tweak wording in reply-account configuration file 2016-09-04 04:39:19 +02:00
e2bba8d9e1 Update reply-account plugin to use default config and swap event order when enabled late 2016-09-04 04:36:01 +02:00
520db2c32e Rewrite loadConfigurationFile in plugins.js to accept default config file 2016-09-04 04:33:55 +02:00
da71f2de2b Add CheckFileExists function to PluginBridge 2016-09-04 04:33:13 +02:00
6dd2c6678b Add custom exception messages to file reading in PluginBridge 2016-09-04 04:01:40 +02:00
fb3d9e6d6b Add a reply-account plugin with config 2016-09-04 04:01:00 +02:00
36473c2df9 Add a global plugins.js file with loadConfigurationFile utility function 2016-09-04 03:59:37 +02:00
81aa30b2ec Allow custom .js files in plugin folders when prefixed by 'user.' 2016-09-04 03:50:35 +02:00
85d5160782 Add plugin identifier to the object itself 2016-09-04 03:38:42 +02:00
44bf7b870e Rewrote notification mouse hook to only be active if the window is visible
Closes #57
2016-09-04 00:59:13 +02:00
44da2e6082 Add debug example notification padding for scrollbar testing 2016-09-04 00:58:47 +02:00
d576bc3972 Work around Scheduled column update delay bug in TweetDeck
Closes #53
2016-09-03 16:15:28 +02:00
dbeb4c7205 Reorganize path constants and allow the program to be portable 2016-09-02 20:59:03 +02:00
e5223a852e Fix profile picture in example tweet 2016-09-02 20:58:18 +02:00
f3884315c0 Fix visual issues with clear-columns plugin (icon in temp columns, misaligned Messages icons) 2016-09-02 20:46:54 +02:00
53cd9dc9a6 Move script files to /scripts/ folder and exclude them from Visual Studio 2016-09-02 19:05:48 +02:00
a3666a7ab2 Fix formatting in JavaScript files 2016-09-02 17:35:21 +02:00
ca4eb17308 Add a clear-columns plugins 2016-09-02 17:28:32 +02:00
b729dca2e5 Seal PluginConfig and add a method to disable official plugins from config 2016-09-02 17:25:25 +02:00
21354e675a Fix TweetDeck keyboard shortcut list alignment 2016-09-02 17:17:08 +02:00
2085ddd347 Update website in design-revert plugin to https 2016-09-02 16:41:52 +02:00
31a475861b Reformat (missing space after comma), minor code tweaks 2016-09-02 13:34:41 +02:00
98 changed files with 2994 additions and 2234 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

@@ -4,7 +4,11 @@ using System.IO;
using System.Threading;
namespace TweetDck.Configuration{
class LockManager{
sealed class LockManager{
public enum Result{
Success, HasProcess, Fail
}
public Process LockingProcess { get; private set; }
private readonly string file;
@@ -14,87 +18,97 @@ namespace TweetDck.Configuration{
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){
throw new InvalidOperationException("Lock file already exists.");
}
try{
lockStream = new FileStream(file,FileMode.Create,FileAccess.Write,FileShare.Read);
byte[] id = BitConverter.GetBytes(Process.GetCurrentProcess().Id);
lockStream.Write(id,0,id.Length);
lockStream.Flush();
if (LockingProcess != null){
LockingProcess.Close();
LockingProcess = null;
}
return true;
}catch(Exception){
if (lockStream != null){
lockStream.Close();
lockStream.Dispose();
}
return false;
}
}
public bool Lock(){
if (lockStream != null)return true;
CreateLockFileStream();
return Result.Success;
}catch(DirectoryNotFoundException){
try{
byte[] bytes = new byte[4];
CreateLockFileStream();
return Result.Success;
}catch{
ReleaseLockFileStream();
return Result.Fail;
}
}catch(IOException){
return Result.HasProcess;
}catch{
ReleaseLockFileStream();
return Result.Fail;
}
}
public Result Lock(){
if (lockStream != null){
return Result.Success;
}
Result initialResult = TryCreateLockFile();
if (initialResult == Result.HasProcess){
try{
int pid;
using(FileStream fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)){
fileStream.Read(bytes,0,4);
pid = ReadIntFromStream(fileStream);
}
int pid = BitConverter.ToInt32(bytes,0);
try{
Process foundProcess = Process.GetProcessById(pid);
using(Process currentProcess = Process.GetCurrentProcess()){
if (foundProcess.ProcessName == currentProcess.ProcessName){
if (foundProcess.MainModule.FileVersionInfo.InternalName == currentProcess.MainModule.FileVersionInfo.InternalName){
LockingProcess = foundProcess;
}
else{
foundProcess.Close();
}
}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;
}catch{
// GetProcessById throws ArgumentException if the process is missing
// Process.MainModule can throw exceptions in some cases
}
return false;
return LockingProcess == null ? Result.Fail : Result.HasProcess;
}catch{
return Result.Fail;
}
}
return initialResult;
}
public bool Unlock(){
bool result = true;
if (lockStream != null){
lockStream.Dispose();
if (ReleaseLockFileStream()){
try{
File.Delete(file);
}catch(Exception e){
Program.Log(e.ToString());
Program.Reporter.Log(e.ToString());
result = false;
}
lockStream = null;
}
return result;
@@ -104,11 +118,9 @@ namespace TweetDck.Configuration{
if (LockingProcess != null){
LockingProcess.CloseMainWindow();
for(int waited = 0; waited < timeout && !LockingProcess.HasExited;){
for(int waited = 0; waited < timeout && !LockingProcess.HasExited; waited += 250){
LockingProcess.Refresh();
Thread.Sleep(100);
waited += 100;
Thread.Sleep(250);
}
if (LockingProcess.HasExited){
@@ -120,5 +132,24 @@ namespace TweetDck.Configuration{
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;
}
}
}
}

View File

@@ -1,6 +1,5 @@
using System;
using System.Drawing;
using System.Globalization;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
@@ -12,17 +11,12 @@ using TweetDck.Plugins;
namespace TweetDck.Configuration{
[Serializable]
sealed class UserConfig{
private static readonly IFormatter Formatter = new BinaryFormatter{
Binder = new SerializationCompatibilityHandler()
};
private static readonly IFormatter Formatter = new BinaryFormatter();
private const int CurrentFileVersion = 4;
private const int CurrentFileVersion = 5;
// START OF CONFIGURATION
public bool IgnoreMigration { get; set; }
public bool IgnoreUninstallCheck { get; set; }
public WindowState BrowserWindow { get; set; }
public bool DisplayNotificationTimer { get; set; }
public bool NotificationTimerCountDown { get; set; }
@@ -35,6 +29,7 @@ namespace TweetDck.Configuration{
public int NotificationDurationValue { get; set; }
public bool NotificationLegacyLoad { get; set; }
public bool EnableSpellCheck { get; set; }
public bool ExpandLinksOnHover { get; set; }
public bool EnableTrayHighlight { get; set; }
@@ -116,6 +111,9 @@ namespace TweetDck.Configuration{
EnableTrayHighlight = true;
Plugins = new PluginConfig();
PluginsWindow = new WindowState();
Plugins.DisableOfficialFromConfig("clear-columns");
Plugins.DisableOfficialFromConfig("reply-account");
}
private void UpgradeFile(){
@@ -155,6 +153,12 @@ namespace TweetDck.Configuration{
++fileVersion;
}
if (fileVersion == 4){
Plugins.DisableOfficialFromConfig("clear-columns");
Plugins.DisableOfficialFromConfig("reply-account");
++fileVersion;
}
// update the version
fileVersion = CurrentFileVersion;
Save();
@@ -179,13 +183,14 @@ namespace TweetDck.Configuration{
return true;
}catch(Exception e){
Program.HandleException("Could not save the configuration file.",e);
Program.Reporter.HandleException("Configuration Error", "Could not save the configuration file.", true, e);
return false;
}
}
public static UserConfig Load(string file){
UserConfig config = null;
Exception firstException = null;
for(int attempt = 0; attempt < 2; attempt++){
try{
@@ -201,9 +206,21 @@ namespace TweetDck.Configuration{
break;
}catch(FileNotFoundException){
}catch(DirectoryNotFoundException){
break;
}catch(Exception e){
Program.HandleException("Could not open the configuration file.",e);
if (attempt == 0){
firstException = e;
Program.Reporter.Log(e.ToString());
}
else if (firstException != null){
Program.Reporter.HandleException("Configuration Error", "Could not open the backup configuration file. If you continue, you may lose your settings and list of enabled plugins.", true, e);
}
}
}
if (firstException != null && config == null){
Program.Reporter.HandleException("Configuration Error", "Could not open the configuration file.", true, firstException);
}
return config ?? new UserConfig(file);
@@ -212,18 +229,5 @@ namespace TweetDck.Configuration{
public static string GetBackupFile(string file){
return file+".bak";
}
private class SerializationCompatibilityHandler : SerializationBinder{
public override Type BindToType(string assemblyName, string typeName){
#if DUCK
assemblyName = assemblyName.Replace("TweetDick","TweetDuck");
#else
assemblyName = assemblyName.Replace("TweetDuck","TweetDick");
#endif
typeName = typeName.Replace("TweetDick","TweetDck");
return Type.GetType(string.Format(CultureInfo.CurrentCulture,"{0}, {1}",typeName,assemblyName));
}
}
}
}

View File

@@ -1,3 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0,Profile=Client"/></startup></configuration>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2"/></startup></configuration>

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<packages xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<package id="cef.redist.x64" version="3.2623.1396" targetFramework="net40-Client" />
<package id="cef.redist.x86" version="3.2623.1396" targetFramework="net40-Client" />
<package id="CefSharp.Common" version="49.0.0-pre02" targetFramework="net40-Client" />
<package id="CefSharp.WinForms" version="49.0.0-pre02" targetFramework="net40-Client" />
<package id="Microsoft.VC120.CRT.JetBrains" version="12.0.21005.2" targetFramework="net40-Client" />
<package id="cef.redist.x64" version="3.2785.1478" targetFramework="net452" />
<package id="cef.redist.x86" version="3.2785.1478" targetFramework="net452" />
<package id="CefSharp.Common" version="53.0.0-pre01" targetFramework="net452" />
<package id="CefSharp.WinForms" version="53.0.0-pre01" targetFramework="net452" />
<package id="Microsoft.VC120.CRT.JetBrains" version="12.0.21005.2" targetFramework="net452" />
</packages>

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{
@@ -34,5 +35,21 @@ namespace TweetDck.Core.Controls{
trackBar.Value = value;
}
}
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){
((TextBox)sender).SelectAll();
args.SuppressKeyPress = true;
args.Handled = true;
}
};
}
}
}

View File

@@ -2,7 +2,7 @@
using System.Windows.Forms;
namespace TweetDck.Core.Controls{
public partial class FlatProgressBar : ProgressBar{
sealed partial class FlatProgressBar : ProgressBar{
private readonly SolidBrush brush;
public FlatProgressBar(){

View File

@@ -24,7 +24,6 @@
/// </summary>
private void InitializeComponent() {
this.components = new System.ComponentModel.Container();
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(FormBrowser));
this.trayIcon = new TweetDck.Core.TrayIcon();
this.toolTip = new System.Windows.Forms.ToolTip(this.components);
this.SuspendLayout();
@@ -38,7 +37,7 @@
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(324, 386);
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
this.Icon = Properties.Resources.icon;
this.Location = new System.Drawing.Point(-32000, -32000);
this.MinimumSize = new System.Drawing.Size(340, 424);
this.Name = "FormBrowser";

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{
@@ -33,14 +35,14 @@ namespace TweetDck.Core{
private FormWindowState prevState;
public FormBrowser(PluginManager pluginManager){
public FormBrowser(PluginManager pluginManager, UpdaterSettings updaterSettings){
InitializeComponent();
Text = Program.BrandName;
this.plugins = pluginManager;
this.plugins.Reloaded += plugins_Reloaded;
this.plugins.Config.PluginChangedState += plugins_PluginChangedState;
this.plugins.PluginChangedState += plugins_PluginChangedState;
FormNotification notification = CreateNotificationForm(true);
notification.CanMoveWindow = () => false;
@@ -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));
@@ -67,16 +73,16 @@ namespace TweetDck.Core{
UpdateTrayIcon();
this.updates = new UpdateHandler(browser,this);
this.updates = new UpdateHandler(browser, this, updaterSettings);
this.updates.UpdateAccepted += updates_UpdateAccepted;
}
private void ShowChildForm(Form form){
form.Show(this);
form.MoveToCenter(this);
form.Shown += (sender, args) => form.MoveToCenter(this);
}
private void ForceClose(){
public void ForceClose(){
trayIcon.Visible = false; // checked in FormClosing event
Close();
}
@@ -101,17 +107,24 @@ namespace TweetDck.Core{
private void Browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e){
if (!e.IsLoading){
browser.AddWordToDictionary("tweetdeck");
browser.AddWordToDictionary("TweetDeck");
browser.AddWordToDictionary("tweetduck");
browser.AddWordToDictionary("TweetDuck");
browser.AddWordToDictionary("TD");
Invoke(new Action(SetupWindow));
browser.LoadingStateChanged -= Browser_LoadingStateChanged;
}
}
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)){
ScriptLoader.ExecuteFile(e.Frame, PluginManager.PluginBrowserScriptFile);
ScriptLoader.ExecuteFile(e.Frame, PluginManager.PluginGlobalScriptFile);
plugins.ExecutePlugins(e.Frame, PluginEnvironment.Browser, true);
}
}
@@ -230,7 +243,7 @@ namespace TweetDck.Core{
else{
bool prevEnableUpdateCheck = Config.EnableUpdateCheck;
currentFormSettings = new FormSettings(this,updates);
currentFormSettings = new FormSettings(this, plugins, updates);
currentFormSettings.FormClosed += (sender, args) => {
currentFormSettings = null;
@@ -290,11 +303,11 @@ namespace TweetDck.Core{
}
public void OnImagePasted(){
browser.ExecuteScriptAsync("TDGF_tryPasteImage",new object[0]);
browser.ExecuteScriptAsync("TDGF_tryPasteImage()");
}
public void OnImagePastedFinish(){
browser.ExecuteScriptAsync("TDGF_tryPasteImageFinish",new object[0]);
browser.ExecuteScriptAsync("TDGF_tryPasteImageFinish()");
}
public void ReloadBrowser(){

View File

@@ -117,267 +117,11 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<metadata name="trayIcon.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
</metadata>
<metadata name="toolTip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>112, 17</value>
</metadata>
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
AAABAAMAMDAAAAEAIACoJQAANgAAACAgAAABACAAqBAAAN4lAAAQEAAAAQAgAGgEAACGNgAAKAAAADAA
AABgAAAAAQAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqHg5IrF+OlS+hjyMw4g7tcmM
PM/MjTzezY485s2OPObMjjzfyIw80MOJPLe8hDyNs387WKx8OyQAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAApHU4FrB8OGXGijzAzo889dKR
PP/SkTz/0pE8/9GRPP/RkDz/0JA8/9CQPP/RkDz/0ZE8/9KRPP/TkTz/0pE8/86PPPbGijzCt4E8bKp6
OxoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKN1OB+9hTuLzI486NOR
PP/SkTz/0ZA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9GQ
PP/SkTz/0pE8/82OPOu9hTuOq3w8IgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACidTkFuoM8csyN
POrTkjz/0ZE8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9GRPP/Tkjz/zY887bqEPHimeDsHAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKp6
OynDiDvA05I8/9GRPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0ZA8/9ORPP/GijzFr308LgAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAArXw7UM2OPOvTkTz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/SkTz/zo887rOAPFYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAACxfjxm0ZA8/NGRPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0ZE8/9GQPP64gjxtAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAALaBPGXRkTz/0ZE8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9GQPP/SkTz/uIM8bAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqnk6TtCQPPzRkTz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/RkDz/0ZA8/rSA
PFUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACtfDwmzo887dKRPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0ZE8/86PPPGtfDwrAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKZ4PAXFijzC0pE8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CURf/TpGT/2baJ/9/GpP/l0bf/59W//+fVvf/kz7P/3sOg/9mz
hP/ToWH/0JND/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9KRPP/EiTvJo3Y7CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALiC
PG/Tkjz/0JA8/9CQPP/QkDz/0JA8/9CSQf/Vq3P/5tO7//j07v//////////////////////////////
////////////////////////9vHq/+TQtf/UqG7/0JA+/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/Tkjz/uYM8dgAAAAAAAAAAAAAAAAAA
AAAAAAAArXw8Gc2OPOzRkTz/0JA8/9CQPP/QlEX/27qP//fz7P//////////////////////////////
////////////////////////////////////////////////////////8ObZ/9auev/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/RkTz/zI488J1x
OB8AAAAAAAAAAAAAAAAAAAAAuoM7h9OSPP/QkDz/0JA8/9GXTf/TpGb/27uS/9u6kP/ewZz/59W9//Xv
5///////////////////////////////////////////////////////////////////////////////
///r3cr/0ZxV/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/1JI8/7mDO5AAAAAAAAAAAAAAAADFj0cR0pNB6s+PO//Ojjv/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CRQP/TpGf/5NC2//38+v//////////////////////////////////////////////
////////////////////////+vf0/9auev/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0ZE8/8uOPO6SajYVAAAAAAAAAADzr1df7qxV/+WkT//ZmUT/0JA9/86O
O//Pjzv/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9GaUv/l07r/////////////////////////
///////////////////////////////////////////////////buo//0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9OSPP+ldjdoAAAAAAAAAADurFW67qxV//Ct
Vv/vrVb/6adR/92dSP/Skz//zo47/8+PO//QkDz/0JA8/9CQPP/QkDz/0JJB/9Smaf/dv5j/+fXw////
////////////////////////////////////////////////////////////////////////2riM/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9KRPP/FiTzGoHQ6Ae6s
VRrurFXz7qxV/+6sVf/urFX/761W//CuVv/sqlT/4qFM/9aVQv/Pjzv/zo47/9CQPP/VqnH/8+vh////
////////////////////////////////////////////////////////////////////////////////
/////////////9aueP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9GQ
PP/Ojzz4oHM5Iu6sVU3urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/vrVX/8K5W/+6sVf/mpU//2plF/9q6
j///////////////////////////////////////////////////////////////////////////////
//////////////////////////////n18P/Rm1P/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/SkTz/rXs6Vu6sVYPurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/wrVb/6r2B////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////o18L/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/SkTz/v4Y8j+6sVa3urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFb/9Onb///////+/v3/////////////////////////////////////////
////////////////////////////////////////////////////////////////////////1qx2/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/SkTz/wYc7uO6sVcrurFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/trlr/67Zy/+m+h//y5NP/////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////7+TW/9CQPf/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/RkTz/yYw80e6s
VdvurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/6sme//v49P//////////////
////////////////////////////////////////////////////////////////////////////////
/////////////////////////////9arc//QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/RkDz/zI083+6sVeLurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/u2b3/////////
////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////+fVvv/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/zo885u6sVeLurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+vL
of//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////r28v/Rl0r/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/zo885u6sVdrurFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7LBj//r28f//////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
///Vq3P/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/RkDz/zI083+6sVcrurFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/6cKO////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
///////////////////ewp7/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/RkTz/yYw80O6s
VazurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7NOy//z59v/u2b7/+fPr////////////////////
////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////n1sD/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/SkTz/xIo8uO6sVYHurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/67Zx/+2uWf/qy6H/////////
////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////v5dj/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/SkTz/vYU8je6sVUvurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+nC
kP///////////////////////////////////////////////////////fz6//fu5P/w4Mv/793F//7+
/v/////////////////////////////////////////////////////////////////17eT/0JA//86O
Ov/Pjzv/0JA8/9CQPP/QkDz/0JA8/9CQPP/SkTz/tYA8Vu6sVRnurFXy7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7a5b//fw5///////////////////////////////////////+/jz/+7av//pwIr/67Jn/+6t
Vv/urFX/6rd1////////////////////////////////////////////////////////////////////
///48ej/6KhU/92cR//Tkj7/zo47/8+PO//QkDz/0JA8/9GQPP/Pjzz3rXw8IAAAAADurFW37qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/6b2D//////////////////////////////////nz6//qyZ3/7K9h/+6s
Vf/urFX/7qxV/+6sVf/urFX/6rl6////////////////////////////////////////////////////
///////////////////9/Pr/6r6C//CuV//sqlT/4aBL/9aWQv/Pjzv/zo47/9GRPP/GijzCp3g8AQAA
AADurFVb7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/68yk///////////////////////+/fz/68+q/+2v
XP/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/6rNr//7+/f//////////////////////////////
/////////////////////////////////////////////+nEk//vrVX/8K5W/+6sVf/lpE//2ZlF/9KR
Pf+zfzpoAAAAAAAAAADurFUP7qxV5u6sVf/urFX/7qxV/+6sVf/urFX/7NGu//////////////////fu
5P/quHb/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxW//Xr3///////////////
///////////////////////////////////////////////////7+PP/+vXu///////pxJL/7qxV/+6s
Vf/wrVb/8K1W/+mnUevUmUsUAAAAAAAAAAAAAAAA7qxVgO6sVf/urFX/7qxV/+6sVf/urFX/6sqg////
////////8eLO/+yvX//urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+nF
lf/////////////////////////////////////////////////////////////////8+vf/6cWV/+q5
ev/s07H/6rd1/+6sVf/urFX/7qxV/++tVogAAAAAAAAAAAAAAAAAAAAA7qxVFe6sVejurFX/7qxV/+6s
Vf/urFX/6bp8///////w4Mn/7a1Z/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/x4cz////////////////////////////////////////////////////+//7+
/v/////////+/+m8gP/urFX/7a1Y/+6sVf/urFX/7qxV7O6sVRoAAAAAAAAAAAAAAAAAAAAAAAAAAO6s
VWburFX/7qxV/+6sVf/urFX/7a5b/+zTs//sr1//7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/sr1//8uPQ////////////////////////////////////
////////6suj/+qzav/pxpj/9Onb//n07f/rr2D/7qxV/+6sVf/urFX/7qxVbwAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAO6sVQPurFW67qxV/+6sVf/urFX/7qxV/+6sVv/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7axY/+nGl//27uP/////////
/////////fz6//Lj0P/qunv/7qxV/+6sVf/urFX/7qxV/+m9gv/qt3X/7qxV/+6sVf/urFXB7qxVBQAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADurFUg7qxV6O6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/qtnP/6sqg/+3WuP/s07P/6cSS/+uyZf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVv/trVr/7qxV/+6s
VezurFUlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7qxVRu6sVfjurFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV++6sVUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAO6s
VVzurFX97qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxVYwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAADurFVd7qxV+O6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVfvurFVjAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7qxVR+6sVeXurFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV6O6sVU0AAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAO6sVSPurFW37qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFW77qxVJgAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AADurFUB7qxVae6sVeTurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV5+6sVW3urFUDAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAO6sVRjurFWA7qxV4u6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVeTurFWE7qxVGwAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7qxVEu6sVV7urFW17qxV7+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVfDurFW47qxVX+6s
VRMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAA7qxVG+6sVUzurFWA7qxVrO6sVcnurFXa7qxV4u6sVeLurFXa7qxVye6sVa3urFWC7qxVTe6s
VR0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAP//gAH//6gr//wAAD//qCv/8AAAD/+oK//AAAAD/6gr/4AAAAH/qCv/AAAAAP+oK/4A
AAAAf6gr/AAAAAA/qCv4AAAAAB+oK/AAAAAAD6gr4AAAAAAHqCvgAAAAAAeoK8AAAAAAA6grwAAAAAAD
qCuAAAAAAAGoK4AAAAAAAagrgAAAAAAAqCsAAAAAAACoKwAAAAAAAKgrAAAAAAAAqCsAAAAAAACoKwAA
AAAAAKgrAAAAAAAAqCsAAAAAAACoKwAAAAAAAKgrAAAAAAAAqCsAAAAAAACoKwAAAAAAAKgrAAAAAAAA
qCsAAAAAAACoKwAAAAAAAKgrgAAAAAAAqCuAAAAAAAGoK4AAAAAAAagrwAAAAAADqCvAAAAAAAOoK+AA
AAAAB6gr4AAAAAAHqCvwAAAAAA+oK/gAAAAAH6gr/AAAAAA/qCv+AAAAAH+oK/8AAAAA/6gr/4AAAAH/
qCv/wAAAA/+oK//wAAAP/6gr//wAAD//qCv//4AB//+oKygAAAAgAAAAQAAAAAEAIAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAApHY4FrR/
Ole+hjuVxos7w8uNPNvOjzznzo8858uNPNzGijzEvoU7l7WBO1mpejsYAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqHg5Kr6F
O5XLjTvo0pE8/9ORPP/SkTz/0ZE8/9CQPP/QkDz/0ZE8/9KRPP/TkTz/0pE8/82OPOq/hjyZrXw7LAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAo3U6CbuE
O4bPjzz005I8/9GRPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0ZE8/9OS
PP/PkDz1vYU8iah6PAsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKR3
OibGizvG05E8/9GQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9GQPP/TkTz/yIs8yqx8PCkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AACsezsxy40849KRPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/SkTz/zI485rKAPTUAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAApXc6JMuNPOTSkTz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/SkTz/zI485659PCcAAAAAAAAAAAAA
AAAAAAAAAAAAAKd4OwnHizzJ0pE8/9CQPP/QkDz/0JA9/9OgYP/YtIX/38Sh/+LMr//iy67/3sOf/9iy
gv/Tn13/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/SkTz/x4s8zaV4
OwsAAAAAAAAAAAAAAAAAAAAAu4Q8hNOSPP/QkDz/0JA8/9GXS//YtIX/6NfC//Xv5//8+vj/////////
///8+vf/9O3k/+fVvv/YsYH/0JRF/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/Tkjz/u4Q7iQAAAAAAAAAAAAAAAKx7OyTPjzz10ZA8/9GXS//Ztoj/8une////////////////////
///////////////////////////////////38uz/3L2V/9CRP//QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9GRPP/Ojzz3onU5KQAAAAAAAAAAxo1BlNGQO//Ojjv/0JNC/9GWSP/QlET/0ZpR/9au
eP/n1b3//v79////////////////////////////////////////////7+TV/9GdWP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9SSPP+4gjqaAAAAAPGuVhHvrVXq5qRO/9qZRP/QkT3/zo46/8+P
O//QkDz/0JA8/9CQPP/To2P/8+vh////////////////////////////////////////////+fXw/9Oh
Yf/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0pE8/8qMO+2WbTYV7qxVUe6sVf/wrVb/761W/+mo
Uv/enUj/05M//86PO//PkD7/3cCZ//Lp3v/7+PT/////////////////////////////////////////
////////+PTv/9GcVf/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/05I8/699OljurFWR7qxV/+6s
Vf/urFX/761W//CuV//sqlT/4qJQ/+zdyf//////////////////////////////////////////////
////////////////////////7+TV/9CRP//QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/Tkjz/vIQ7mO6s
Vb/urFX/7qxV/+6sVf/urFX/7qxV/++tVf/ry5///v79//7+/f//////////////////////////////
////////////////////////////////////////2rqO/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9KR
PP/GijvF7qxV2u6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+u1bv/t1bb//fv4////////////////////
///////////////////////////////////////////////////48+3/0ZRG/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0ZA8/8uNPNzurFXl7qxV/+6sVf/urFX/7qxV/+6sVf/rsWP/9OjZ////////////////////
///////////////////////////////////////////////////////////////////YtIX/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/zo886O6sVeXurFX/7qxV/+6sVf/urFX/7qxW//Hiz///////////////
/////////////////////////////////////////////////////////////////////////////+rb
yP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/Ojzzo7qxV2e6sVf/urFX/7qxV/+6sVf/quHb/////////
////////////////////////////////////////////////////////////////////////////////
////////+fXw/9CURf/QkDz/0JA8/9CQPP/QkDz/0ZA8/8yOPNzurFW/7qxV/+6sVf/urFX/7qxV/+nA
if/s1LT/9Onb////////////////////////////////////////////////////////////////////
////////////////////////0Z5b/9CQPP/QkDz/0JA8/9CQPP/SkTz/x4s8xe6sVY/urFX/7qxV/+6s
Vf/urFX/7qxV/+nEk//+/fz////////////////////////////+/v3/+PHo//To2f/+/v7/////////
///////////////////////////////////VqGz/zo46/8+PO//QkDz/0JA8/9KRPP+/hjyX7qxVUO6s
Vf/urFX/7qxV/+6sVf/ssGH//Pr2///////////////////////y49D/6cOR/+qyaf/urVb/6rJp////
/////////////////////////////////////////////+e7f//goEr/1ZVB/8+PO//Ojzv/0pE8/7WB
PFfurFUQ7qxV6O6sVf/urFX/7qxV/+m+hv/////////////////z5tX/6rl6/+6sVf/urFX/7qxV/+6s
Vf/qs2r////+////////////////////////////////////////////+PLp/+y4c//urFX/5aRO/9qZ
RP/Mjjztp3g6FQAAAADurFWQ7qxV/+6sVf/urFX/6cKP////////////682m/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+2sV//17N////////////////////////////////////////7+/v/06Nn/8uTT/+qz
a//wrVb/761W/+alUZYAAAAAAAAAAO6sVSHurFX07qxV/+6sVf/quXj//////+nDkf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+m+hP////////////////////////////////////////////jy
6v/qtnH/67Fj/+6sVf/urFX2761WJQAAAAAAAAAAAAAAAO6sVX7urFX/7qxV/+2vW//pvID/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+nEkv/7+PT//////////////////fz6/+rJ
n//rtW7/682n/+zTs//urFb/7qxV/+6sVYQAAAAAAAAAAAAAAAAAAAAA7qxVB+6sVcTurFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+u0bP/qy6H/7da4/+vN
pv/qtnP/7qxV/+6sVf/urFX/7K9f/+2sWP/urFXI7qxVCQAAAAAAAAAAAAAAAAAAAAAAAAAA7qxVIO6s
VeDurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV4+6sVSMAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAA7qxVLO6sVd/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVeLurFUwAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAA7qxVIu6sVcHurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFXE7qxVJAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7qxVBu6sVX/urFXw7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFXy7qxVgu6sVQgAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAO6sVSXurFWP7qxV5e6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFXl7qxVke6sVScAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AADurFUS7qxVUO6sVY7urFW97qxV2O6sVeXurFXl7qxV2O6sVb7urFWP7qxVUu6sVRMAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/8AD//8AAP/8AAA/+AAAH/AAAA/gAAAHwAAAA8AA
AAOAAAABgAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAA
AAGAAAABwAAAA8AAAAPgAAAH8AAAD/gAAB/8AAA//wAA///AA/8oAAAAEAAAACAAAAABACAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKx7Oja9hTqTyYw8zs6PPOfOjzznyYw8zr6G
PJSvfjw3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAo3Y7CbyEO5PRkDz805I8/9GRPP/QkDz/0JA8/9GR
PP/TkTz/0ZA8/b+GPJWrfDwKAAAAAAAAAAAAAAAAp3g8CcSJPLfTkjz/0ZA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/Tkjz/xoo8uqx8PQoAAAAAAAAAAL2FPJPUk0D/06Rm/92/mf/izK//4sut/9u6
j//Snlr/0JA8/9CQPP/QkDz/0JA8/9OSPP+9hTyWAAAAALiEQDLQkDz90JpS/93Amf/u4tP/////////
/////////fv5/+DIqP/QkkH/0JA8/9CQPP/RkDz/0JA8/qN1OTTtq1WS56VP/9qaRf/RkT3/0JdO/+rc
yP//////////////////////693L/9CSQf/QkDz/0JA8/9SSPP+3gTqW761Vz++tVv/wrVb/6K5h//fy
6v/////////////////////////////////gx6b/0JA8/9CQPP/RkTz/yYw80e6sVefurFX/7qxV/+q+
hP/69vD//////////////////////////////////fz7/9GbU//QkDz/0JA8/8+PPOnurFXn7qxV/+yw
Y//79/P////////////////////////////////////////////ZuIz/0JA8/9CQPP/Pjzzp7qxVz+6s
Vf/sr2H/8eLO/////////////v7+//n07f/+/v7/////////////////48yu/8+PO//Qjzv/yYs80e6s
VZLurFX/67Jm//7+/f/8+fb/68yl/+qza//rsmf//v37//////////////////Pn2P/ip1n/2phC/8CH
PJXurFUw7qxV/eq2c//169//6rNr/+6sVf/urFX/7q1W//Pm1f/////////////////8+fX/67+G/++t
Vf3lpVEyAAAAAO6sVZHtrVr/7a9c/+6sVf/urFX/7qxV/+6sVf/rsWP/7de5//Lj0P/pxJL/67Zy/+qz
av/urFWTAAAAAAAAAADurFUI7qxVtO6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFW37qxVCQAAAAAAAAAAAAAAAO6sVQjurFWQ7qxV++6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
VfvurFWS7qxVCQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAO6sVTPurFWQ7qxVzO6sVeburFXm7qxVzO6s
VZDurFU0AAAAAAAAAAAAAAAAAAAAAPAPrEHAA6xBgAGsQYABrEEAAKxBAACsQQAArEEAAKxBAACsQQAA
rEEAAKxBAACsQYABrEGAAaxBwAOsQfAPrEE=
</value>
<data name="trayIcon.TrayLocation" type="System.Drawing.Point, System.Drawing">
<value>17, 17</value>
</data>
<data name="toolTip.TrayLocation" type="System.Drawing.Point, System.Drawing">
<value>112, 17</value>
</data>
</root>

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));
@@ -112,7 +118,6 @@ namespace TweetDck.Core{
}
mouseHookDelegate = MouseHookProc;
mouseHook = NativeMethods.SetWindowsHookEx(NativeMethods.WH_MOUSE_LL,mouseHookDelegate,IntPtr.Zero,0);
Disposed += FormNotification_Disposed;
}
@@ -125,11 +130,26 @@ namespace TweetDck.Core{
base.WndProc(ref m);
}
// mouse wheel hook
private void StartMouseHook(){
if (mouseHook == IntPtr.Zero){
mouseHook = NativeMethods.SetWindowsHookEx(NativeMethods.WH_MOUSE_LL, mouseHookDelegate, IntPtr.Zero, 0);
}
}
private void StopMouseHook(){
if (mouseHook != IntPtr.Zero){
NativeMethods.UnhookWindowsHookEx(mouseHook);
mouseHook = IntPtr.Zero;
}
}
private IntPtr MouseHookProc(int nCode, IntPtr wParam, IntPtr lParam){
if (!ContainsFocus && wParam.ToInt32() == NativeMethods.WH_MOUSEWHEEL && browser.Bounds.Contains(PointToClient(Cursor.Position))){
// fuck it, Activate() doesn't work with this
Point prevPos = Cursor.Position;
Cursor.Position = PointToScreen(new Point(-1,-1));
Cursor.Position = PointToScreen(new Point(0, -1));
NativeMethods.SimulateMouseClick(NativeMethods.MouseButton.Left);
Cursor.Position = prevPos;
}
@@ -182,6 +202,7 @@ namespace TweetDck.Core{
if (plugins.HasAnyPlugin(PluginEnvironment.Notification)){
ScriptLoader.ExecuteScript(e.Frame, pluginJS, PluginScriptIdentifier);
ScriptLoader.ExecuteFile(e.Frame, PluginManager.PluginGlobalScriptFile);
plugins.ExecutePlugins(e.Frame, PluginEnvironment.Notification, false);
}
}
@@ -197,11 +218,7 @@ namespace TweetDck.Core{
private void FormNotification_Disposed(object sender, EventArgs e){
browser.Dispose();
if (mouseHook != IntPtr.Zero){
NativeMethods.UnhookWindowsHookEx(mouseHook);
mouseHook = IntPtr.Zero;
}
StopMouseHook();
}
// notification methods
@@ -214,7 +231,7 @@ namespace TweetDck.Core{
tweetQueue.Enqueue(notification);
UpdateTitle();
if (!timerProgress.Enabled){
if (totalTime == 0){
LoadNextNotification();
}
}
@@ -237,6 +254,9 @@ namespace TweetDck.Core{
Location = new Point(-32000, -32000);
progressBarTimer.Value = Program.UserConfig.NotificationTimerCountDown ? 1000 : 0;
timerProgress.Stop();
totalTime = 0;
StopMouseHook();
}
public void OnNotificationReady(){
@@ -263,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);
@@ -332,6 +353,8 @@ namespace TweetDck.Core{
if (needsReactivating){
NativeMethods.SetFormPos(this, NativeMethods.HWND_TOPMOST, NativeMethods.SWP_NOACTIVATE);
}
StartMouseHook();
}
private void UpdateTitle(){

View File

@@ -0,0 +1,16 @@
using CefSharp;
using System;
namespace TweetDck.Core.Handling{
class BrowserProcessHandler : IBrowserProcessHandler{
void IBrowserProcessHandler.OnContextInitialized(){
using(IRequestContext ctx = Cef.GetGlobalRequestContext()){
string err;
ctx.SetPreference("browser.enable_spellchecking", Program.UserConfig.EnableSpellCheck, out err);
}
}
void IBrowserProcessHandler.OnScheduleMessagePumpWork(long delay){}
void IDisposable.Dispose(){}
}
}

View File

@@ -6,38 +6,38 @@ using TweetDck.Core.Utils;
namespace TweetDck.Core.Handling{
abstract class ContextMenuBase : IContextMenuHandler{
private const int MenuOpenUrlInBrowser = 26500;
private const int MenuCopyUrl = 26501;
private const int MenuOpenImageInBrowser = 26502;
private const int MenuOpenLinkUrl = 26500;
private const int MenuCopyLinkUrl = 26501;
private const int MenuOpenImage = 26502;
private const int MenuSaveImage = 26503;
private const int MenuCopyImageUrl = 26504;
public virtual void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){
if (parameters.TypeFlags.HasFlag(ContextMenuType.Link) && !parameters.UnfilteredLinkUrl.EndsWith("tweetdeck.twitter.com/#", StringComparison.Ordinal)){
model.AddItem((CefMenuCommand)MenuOpenUrlInBrowser,"Open in browser");
model.AddItem((CefMenuCommand)MenuCopyUrl,"Copy link address");
model.AddItem((CefMenuCommand)MenuOpenLinkUrl, "Open link in browser");
model.AddItem((CefMenuCommand)MenuCopyLinkUrl, "Copy link address");
model.AddSeparator();
}
if (parameters.TypeFlags.HasFlag(ContextMenuType.Media) && parameters.HasImageContents){
model.AddItem((CefMenuCommand)MenuOpenImageInBrowser,"Open image in browser");
model.AddItem((CefMenuCommand)MenuOpenImage, "Open image in browser");
model.AddItem((CefMenuCommand)MenuSaveImage, "Save image as...");
model.AddItem((CefMenuCommand)MenuCopyImageUrl,"Copy image URL");
model.AddItem((CefMenuCommand)MenuCopyImageUrl, "Copy image address");
model.AddSeparator();
}
}
public virtual bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags){
switch((int)commandId){
case MenuOpenUrlInBrowser:
case MenuOpenLinkUrl:
BrowserUtils.OpenExternalBrowser(parameters.LinkUrl);
break;
case MenuCopyUrl:
case MenuCopyLinkUrl:
Clipboard.SetText(string.IsNullOrEmpty(TweetDeckBridge.LastRightClickedLink) ? parameters.UnfilteredLinkUrl : TweetDeckBridge.LastRightClickedLink, TextDataFormat.UnicodeText);
break;
case MenuOpenImageInBrowser:
case MenuOpenImage:
BrowserUtils.OpenExternalBrowser(parameters.SourceUrl);
break;
@@ -84,6 +84,12 @@ namespace TweetDck.Core.Handling{
}
}
protected static void AddSeparator(IMenuModel model){
if (model.Count > 0 && model.GetTypeAt(model.Count-1) != MenuItemType.Separator){ // do not add separators if there is nothing to separate
model.AddSeparator();
}
}
private static string GetImageFileName(string url){
// twimg adds a colon after file extension
int dot = url.LastIndexOf('.');

View File

@@ -1,18 +1,26 @@
using CefSharp;
using System.Windows.Forms;
using TweetDck.Core.Controls;
using TweetDck.Core.Utils;
namespace TweetDck.Core.Handling{
class ContextMenuBrowser : ContextMenuBase{
private const int MenuSettings = 26600;
private const int MenuPlugins = 26005;
private const int MenuAbout = 26601;
private const int MenuMute = 26602;
private const int MenuCopyTweetUrl = 26603;
private const int MenuCopyTweetEmbeddedUrl = 26604;
private const int MenuGlobal = 26600;
private const int MenuMute = 26601;
private const int MenuSettings = 26602;
private const int MenuPlugins = 26003;
private const int MenuAbout = 26604;
private const int MenuOpenTweetUrl = 26610;
private const int MenuCopyTweetUrl = 26611;
private const int MenuOpenQuotedTweetUrl = 26612;
private const int MenuCopyQuotedTweetUrl = 26613;
private readonly FormBrowser form;
private string lastHighlightedTweet;
private string lastHighlightedQuotedTweet;
public ContextMenuBrowser(FormBrowser form){
this.form = form;
}
@@ -24,31 +32,43 @@ namespace TweetDck.Core.Handling{
model.Remove(CefMenuCommand.ViewSource);
RemoveSeparatorIfLast(model);
if (!string.IsNullOrEmpty(TweetDeckBridge.LastHighlightedTweet)){
model.AddItem((CefMenuCommand)MenuCopyTweetUrl,"Copy tweet address");
if (!string.IsNullOrEmpty(TweetDeckBridge.LastHighlightedTweetEmbedded)){
model.AddItem((CefMenuCommand)MenuCopyTweetEmbeddedUrl,"Copy quoted tweet address");
}
model.AddSeparator();
}
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
if (model.Count > 0){
RemoveSeparatorIfLast(model);
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(lastHighlightedQuotedTweet)){
model.AddSeparator();
model.AddItem((CefMenuCommand)MenuOpenQuotedTweetUrl, "Open quoted tweet in browser");
model.AddItem((CefMenuCommand)MenuCopyQuotedTweetUrl, "Copy quoted tweet address");
}
model.AddSeparator();
}
model.AddItem(CefMenuCommand.Reload,"Reload");
model.AddCheckItem((CefMenuCommand)MenuMute,"Mute notifications");
model.SetChecked((CefMenuCommand)MenuMute,Program.UserConfig.MuteNotifications);
model.AddSeparator();
if ((parameters.TypeFlags & (ContextMenuType.Editable | ContextMenuType.Selection)) == 0){
AddSeparator(model);
model.AddItem((CefMenuCommand)MenuSettings,"Settings");
model.AddItem((CefMenuCommand)MenuPlugins,"Plugins");
model.AddItem((CefMenuCommand)MenuAbout,"About "+Program.BrandName);
IMenuModel globalMenu = model.Count == 0 ? model : model.AddSubMenu((CefMenuCommand)MenuGlobal, Program.BrandName);
globalMenu.AddItem(CefMenuCommand.Reload, "Reload browser");
globalMenu.AddCheckItem((CefMenuCommand)MenuMute, "Mute notifications");
globalMenu.SetChecked((CefMenuCommand)MenuMute, Program.UserConfig.MuteNotifications);
globalMenu.AddSeparator();
globalMenu.AddItem((CefMenuCommand)MenuSettings, "Settings");
globalMenu.AddItem((CefMenuCommand)MenuPlugins, "Plugins");
globalMenu.AddItem((CefMenuCommand)MenuAbout, "About "+Program.BrandName);
}
}
public override bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags){
@@ -81,12 +101,20 @@ namespace TweetDck.Core.Handling{
return true;
case MenuCopyTweetUrl:
Clipboard.SetText(TweetDeckBridge.LastHighlightedTweet,TextDataFormat.UnicodeText);
case MenuOpenTweetUrl:
BrowserUtils.OpenExternalBrowser(lastHighlightedTweet);
return true;
case MenuCopyTweetEmbeddedUrl:
Clipboard.SetText(TweetDeckBridge.LastHighlightedTweetEmbedded,TextDataFormat.UnicodeText);
case MenuCopyTweetUrl:
Clipboard.SetText(lastHighlightedTweet, TextDataFormat.UnicodeText);
return true;
case MenuOpenQuotedTweetUrl:
BrowserUtils.OpenExternalBrowser(lastHighlightedQuotedTweet);
return true;
case MenuCopyQuotedTweetUrl:
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

@@ -10,8 +10,7 @@ namespace TweetDck.Core.Handling{
class TweetDeckBridge{
public static string LastRightClickedLink = string.Empty;
public static string LastHighlightedTweet = string.Empty;
public static string LastHighlightedTweetEmbedded = string.Empty;
public static string NotificationTweetEmbedded = string.Empty;
public static string LastHighlightedQuotedTweet = string.Empty;
public static string ClipboardImagePath = string.Empty;
private readonly FormBrowser form;
@@ -74,15 +73,15 @@ namespace TweetDck.Core.Handling{
form.InvokeSafe(() => LastRightClickedLink = link);
}
public void SetLastHighlightedTweet(string link, string embeddedLink){
public void SetLastHighlightedTweet(string link, string quotedLink){
form.InvokeSafe(() => {
LastHighlightedTweet = link;
LastHighlightedTweetEmbedded = embeddedLink;
LastHighlightedQuotedTweet = quotedLink;
});
}
public void SetNotificationTweetEmbedded(string link){
form.InvokeSafe(() => NotificationTweetEmbedded = link);
public void SetNotificationQuotedTweet(string link){
notification.InvokeSafe(() => notification.CurrentQuotedTweetUrl = link);
}
public void OpenSettingsMenu(){
@@ -119,6 +118,10 @@ namespace TweetDck.Core.Handling{
}
}
public void LoadNextNotification(){
notification.InvokeSafe(notification.FinishCurrentTweet);
}
public void TryPasteImage(){
form.InvokeSafe(() => {
if (Clipboard.ContainsImage()){
@@ -133,7 +136,7 @@ namespace TweetDck.Core.Handling{
form.OnImagePasted();
}catch(Exception e){
Program.HandleException("Could not paste image from clipboard.",e);
Program.Reporter.HandleException("Clipboard Image Error", "Could not paste image from clipboard.", true, e);
}
}
});
@@ -155,6 +158,19 @@ namespace TweetDck.Core.Handling{
BrowserUtils.OpenExternalBrowser(url);
}
public void Alert(string type, string contents){
MessageBoxIcon icon;
switch(type){
case "error": icon = MessageBoxIcon.Error; break;
case "warning": icon = MessageBoxIcon.Warning; break;
case "info": icon = MessageBoxIcon.Information; break;
default: icon = MessageBoxIcon.None; break;
}
MessageBox.Show(contents, Program.BrandName+" Browser Message", MessageBoxButtons.OK, icon);
}
public void Log(string data){
System.Diagnostics.Debug.WriteLine(data);
}

View File

@@ -20,7 +20,7 @@ namespace TweetDck.Core.Handling{
private static string CustomCSS{
get{
return @".scroll-styled-v::-webkit-scrollbar{width:8px}.scroll-styled-v::-webkit-scrollbar-thumb{border-radius:0}a[data-full-url]{word-break:break-all}";
return @".scroll-styled-v::-webkit-scrollbar{width:8px}.scroll-styled-v::-webkit-scrollbar-thumb{border-radius:0}a[data-full-url]{word-break:break-all}#td-skip{opacity:0;cursor:pointer;transition:opacity 0.15s ease}.td-hover #td-skip{opacity:0.75}#td-skip:hover{opacity:1}";
}
}
@@ -43,14 +43,19 @@ namespace TweetDck.Core.Handling{
build.Append(@"<header class='tweet-header'>");
build.Append(@"<time class='tweet-timestamp js-timestamp pull-right txt-mute'><a target='_blank' rel='url' href='https://twitter.com/chylexmc' class='txt-small'>0s</a></time>");
build.Append(@"<a target='_blank' rel='user' href='https://twitter.com/chylexmc' class='account-link link-complex block'>");
build.Append(@"<div class='obj-left item-img tweet-img'><img width='48' height='48' alt='chylexmc's avatar' src='https://pbs.twimg.com/profile_images/645532929930608642/J56NBJVY_normal.png' class='tweet-avatar avatar pull-right'></div>");
build.Append(@"<div class='obj-left item-img tweet-img'><img width='48' height='48' alt='chylexmc's avatar' src='https://pbs.twimg.com/profile_images/765161905312980992/AhDP9iY-_normal.jpg' class='tweet-avatar avatar pull-right'></div>");
build.Append(@"<div class='nbfc'><span class='account-inline txt-ellipsis'><b class='fullname link-complex-target'>chylex</b> <span class='username txt-mute'>@chylexmc</span></span></div>");
build.Append(@"</a>");
build.Append(@"</header>");
build.Append(@"<div class='tweet-body'><p class='js-tweet-text tweet-text with-linebreaks'>This is an example tweet, which lets you test the location and duration of popup notifications.</p></div>");
#if DEBUG
build.Append(@"<div style='margin-top:64px'>Scrollbar test padding...</div>");
#endif
build.Append(@"</div></div></article>");
return new TweetNotification(build.ToString(),"",95);
return new TweetNotification(build.ToString(), "", 95, true);
}
}
@@ -79,11 +84,15 @@ namespace TweetDck.Core.Handling{
private readonly string html;
private readonly string url;
private readonly int characters;
private readonly bool isExample;
public TweetNotification(string html, string url, int characters){
public TweetNotification(string html, string url, int characters) : this(html, url, characters, false){}
private TweetNotification(string html, string url, int characters, bool isExample){
this.html = html;
this.url = url;
this.characters = characters;
this.isExample = isExample;
}
public int GetDisplayDuration(int value){
@@ -101,7 +110,7 @@ namespace TweetDck.Core.Handling{
}
build.Append("</head>");
build.Append("<body class='hearty'><div class='app-columns-container'><div class='column scroll-styled-v' style='width:100%;overflow-y:auto'>");
build.Append("<body class='hearty'").Append(isExample ? " td-example-notification" : "").Append("><div class='app-columns-container'><div class='column scroll-styled-v' style='width:100%;overflow-y:auto'>");
build.Append(html);
build.Append("</div></div></body>");
build.Append("</html>");

View File

@@ -28,7 +28,7 @@ namespace TweetDck.Core.Other {
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(FormAbout));
this.pictureLogo = new System.Windows.Forms.PictureBox();
this.labelDescription = new System.Windows.Forms.Label();
this.labelSourceCode = new System.Windows.Forms.LinkLabel();
this.labelTips = new System.Windows.Forms.LinkLabel();
this.labelWebsite = new System.Windows.Forms.LinkLabel();
this.tablePanelLinks = new System.Windows.Forms.TableLayoutPanel();
this.labelIssues = new System.Windows.Forms.LinkLabel();
@@ -59,19 +59,19 @@ namespace TweetDck.Core.Other {
this.labelDescription.Size = new System.Drawing.Size(232, 109);
this.labelDescription.TabIndex = 1;
//
// labelSourceCode
// labelTips
//
this.labelSourceCode.Dock = System.Windows.Forms.DockStyle.Fill;
this.labelSourceCode.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.labelSourceCode.LinkArea = new System.Windows.Forms.LinkArea(0, 0);
this.labelSourceCode.Location = new System.Drawing.Point(117, 0);
this.labelSourceCode.Margin = new System.Windows.Forms.Padding(0);
this.labelSourceCode.Name = "labelSourceCode";
this.labelSourceCode.Size = new System.Drawing.Size(99, 16);
this.labelSourceCode.TabIndex = 3;
this.labelSourceCode.Text = "Source Code";
this.labelSourceCode.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
this.labelSourceCode.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.OnLinkClicked);
this.labelTips.Dock = System.Windows.Forms.DockStyle.Fill;
this.labelTips.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.labelTips.LinkArea = new System.Windows.Forms.LinkArea(0, 0);
this.labelTips.Location = new System.Drawing.Point(117, 0);
this.labelTips.Margin = new System.Windows.Forms.Padding(0);
this.labelTips.Name = "labelTips";
this.labelTips.Size = new System.Drawing.Size(99, 16);
this.labelTips.TabIndex = 3;
this.labelTips.Text = "Tips && Tricks";
this.labelTips.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
this.labelTips.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.OnLinkClicked);
//
// labelWebsite
//
@@ -98,7 +98,7 @@ namespace TweetDck.Core.Other {
this.tablePanelLinks.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 35.14F));
this.tablePanelLinks.Controls.Add(this.labelIssues, 2, 0);
this.tablePanelLinks.Controls.Add(this.labelWebsite, 0, 0);
this.tablePanelLinks.Controls.Add(this.labelSourceCode, 1, 0);
this.tablePanelLinks.Controls.Add(this.labelTips, 1, 0);
this.tablePanelLinks.Location = new System.Drawing.Point(12, 124);
this.tablePanelLinks.Name = "tablePanelLinks";
this.tablePanelLinks.RowCount = 1;
@@ -146,7 +146,7 @@ namespace TweetDck.Core.Other {
private System.Windows.Forms.PictureBox pictureLogo;
private System.Windows.Forms.Label labelDescription;
private System.Windows.Forms.LinkLabel labelSourceCode;
private System.Windows.Forms.LinkLabel labelTips;
private System.Windows.Forms.LinkLabel labelWebsite;
private System.Windows.Forms.TableLayoutPanel tablePanelLinks;
private System.Windows.Forms.LinkLabel labelIssues;

View File

@@ -3,7 +3,7 @@ using TweetDck.Core.Utils;
namespace TweetDck.Core.Other{
sealed partial class FormAbout : Form{
private const string GitHubLink = "https://github.com/chylex/TweetDuck";
private const string TipsLink = "https://github.com/chylex/TweetDuck/wiki";
private const string IssuesLink = "https://github.com/chylex/TweetDuck/issues";
public FormAbout(){
@@ -14,7 +14,7 @@ namespace TweetDck.Core.Other{
labelDescription.Text = Program.BrandName+" was created by chylex as a replacement to the discontinued official TweetDeck client for Windows.\n\nThe program is available for free under the open source MIT license.";
labelWebsite.Links.Add(new LinkLabel.Link(0, labelWebsite.Text.Length, Program.Website));
labelSourceCode.Links.Add(new LinkLabel.Link(0,labelSourceCode.Text.Length,GitHubLink));
labelTips.Links.Add(new LinkLabel.Link(0, labelTips.Text.Length, TipsLink));
labelIssues.Links.Add(new LinkLabel.Link(0, labelIssues.Text.Length, IssuesLink));
}

79
Core/Other/FormMessage.Designer.cs generated Normal file
View File

@@ -0,0 +1,79 @@
namespace TweetDck.Core.Other {
partial class FormMessage {
/// <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.panelActions = new System.Windows.Forms.Panel();
this.labelMessage = new System.Windows.Forms.Label();
this.SuspendLayout();
//
// panelActions
//
this.panelActions.BackColor = System.Drawing.SystemColors.Control;
this.panelActions.Dock = System.Windows.Forms.DockStyle.Bottom;
this.panelActions.Location = new System.Drawing.Point(0, 84);
this.panelActions.Name = "panelActions";
this.panelActions.Size = new System.Drawing.Size(233, 49);
this.panelActions.TabIndex = 0;
//
// labelMessage
//
this.labelMessage.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.labelMessage.AutoSize = true;
this.labelMessage.Font = System.Drawing.SystemFonts.MessageBoxFont;
this.labelMessage.Location = new System.Drawing.Point(62, 34);
this.labelMessage.Margin = new System.Windows.Forms.Padding(53, 24, 27, 24);
this.labelMessage.MaximumSize = new System.Drawing.Size(600, 0);
this.labelMessage.MinimumSize = new System.Drawing.Size(0, 24);
this.labelMessage.Name = "labelMessage";
this.labelMessage.Size = new System.Drawing.Size(0, 24);
this.labelMessage.TabIndex = 1;
this.labelMessage.SizeChanged += new System.EventHandler(this.labelMessage_SizeChanged);
//
// FormMessage
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.BackColor = System.Drawing.Color.White;
this.ClientSize = new System.Drawing.Size(98, 133);
this.Controls.Add(this.labelMessage);
this.Controls.Add(this.panelActions);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "FormMessage";
this.ShowIcon = false;
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Panel panelActions;
private System.Windows.Forms.Label labelMessage;
}
}

119
Core/Other/FormMessage.cs Normal file
View File

@@ -0,0 +1,119 @@
using System;
using System.Drawing;
using System.Windows.Forms;
namespace TweetDck.Core.Other{
sealed partial class FormMessage : Form{
public Button ClickedButton { get; private set; }
private readonly Icon icon;
private readonly bool isReady;
private int realFormWidth, minFormWidth;
private int buttonCount;
private int prevLabelWidth, prevLabelHeight;
private bool wasLabelMultiline;
public FormMessage(string caption, string text, MessageBoxIcon messageIcon){
InitializeComponent();
this.prevLabelWidth = labelMessage.Width;
this.prevLabelHeight = labelMessage.Height;
this.minFormWidth = 18;
switch(messageIcon){
case MessageBoxIcon.Information:
icon = SystemIcons.Information;
break;
case MessageBoxIcon.Warning:
icon = SystemIcons.Warning;
break;
case MessageBoxIcon.Error:
icon = SystemIcons.Error;
break;
case MessageBoxIcon.Question:
icon = SystemIcons.Question;
break;
default:
icon = null;
labelMessage.Location = new Point(labelMessage.Location.X-32, labelMessage.Location.Y);
break;
}
this.isReady = true;
this.Text = caption;
this.labelMessage.Text = text;
}
public Button AddButton(string title){
Button button = new Button{
Anchor = AnchorStyles.Bottom | AnchorStyles.Right,
Font = SystemFonts.MessageBoxFont,
Location = new Point(Width-112-buttonCount*96, 12),
Size = new Size(88, 26),
TabIndex = buttonCount,
Text = title,
UseVisualStyleBackColor = true
};
button.Click += (sender, args) => {
ClickedButton = (Button)sender;
DialogResult = DialogResult.OK;
Close();
};
panelActions.Controls.Add(button);
minFormWidth += 96;
Width = Math.Max(realFormWidth, minFormWidth);
++buttonCount;
return button;
}
public void AddActionControl(Control control){
panelActions.Controls.Add(control);
minFormWidth += control.Width+control.Margin.Horizontal;
Width = Math.Max(realFormWidth, minFormWidth);
}
private void labelMessage_SizeChanged(object sender, EventArgs e){
if (!isReady){
return;
}
bool isMultiline = labelMessage.Height > labelMessage.MinimumSize.Height;
if (isMultiline && !wasLabelMultiline){
labelMessage.Location = new Point(labelMessage.Location.X, labelMessage.Location.Y-8);
prevLabelHeight += 8;
}
else if (!isMultiline && wasLabelMultiline){
labelMessage.Location = new Point(labelMessage.Location.X, labelMessage.Location.Y+8);
prevLabelHeight -= 8;
}
realFormWidth = Width-(icon == null ? 32+35+(labelMessage.Margin.Left-labelMessage.Margin.Right) : 0)+labelMessage.Margin.Right+labelMessage.Width-prevLabelWidth;
Width = Math.Max(realFormWidth, minFormWidth);
Height += labelMessage.Height-prevLabelHeight;
prevLabelWidth = labelMessage.Width;
prevLabelHeight = labelMessage.Height;
wasLabelMultiline = isMultiline;
}
protected override void OnPaint(PaintEventArgs e){
if (icon != null){
e.Graphics.DrawIcon(icon, 25, 26);
}
base.OnPaint(e);
}
}
}

View File

@@ -23,7 +23,6 @@
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent() {
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(FormPlugins));
this.btnClose = new System.Windows.Forms.Button();
this.btnReload = new System.Windows.Forms.Button();
this.btnOpenFolder = new System.Windows.Forms.Button();
@@ -69,7 +68,7 @@
this.btnOpenFolder.UseVisualStyleBackColor = true;
this.btnOpenFolder.Click += new System.EventHandler(this.btnOpenFolder_Click);
//
// pluginList
// tabPanelPlugins
//
this.tabPanelPlugins.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
@@ -88,11 +87,10 @@
this.Controls.Add(this.btnOpenFolder);
this.Controls.Add(this.btnReload);
this.Controls.Add(this.btnClose);
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
this.Icon = global::TweetDck.Properties.Resources.icon;
this.MinimumSize = new System.Drawing.Size(480, 320);
this.Name = "FormPlugins";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
this.Text = "Plugins";
this.ResumeLayout(false);
this.PerformLayout();

View File

@@ -1,13 +1,16 @@
using System;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
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{
partial class FormPlugins : Form{
sealed partial class FormPlugins : Form{
private readonly PluginManager pluginManager;
private readonly TabButton tabBtnOfficial, tabBtnCustom;
private readonly PluginListFlowLayout flowLayoutPlugins;
@@ -16,6 +19,8 @@ namespace TweetDck.Core.Other{
public FormPlugins(){
InitializeComponent();
Text = Program.BrandName+" Plugins";
}
public FormPlugins(PluginManager pluginManager) : this(){
@@ -58,8 +63,17 @@ namespace TweetDck.Core.Other{
flowLayoutPlugins.SuspendLayout();
flowLayoutPlugins.Controls.Clear();
foreach(Plugin plugin in pluginManager.GetPluginsByGroup(selectedGroup.Value)){
flowLayoutPlugins.Controls.Add(new PluginControl(pluginManager,plugin));
Plugin[] plugins = pluginManager.GetPluginsByGroup(selectedGroup.Value).OrderBy(plugin => !plugin.CanRun ? 0 : pluginManager.Config.IsEnabled(plugin) ? 1 : 2).ThenBy(plugin => plugin.Name).ToArray();
for(int index = 0; index < plugins.Length; index++){
flowLayoutPlugins.Controls.Add(new PluginControl(pluginManager, plugins[index]));
if (index < plugins.Length-1){
flowLayoutPlugins.Controls.Add(new Panel{
BackColor = Color.DimGray,
Size = new Size(1, 1)
});
}
}
flowLayoutPlugins_Resize(flowLayoutPlugins, new EventArgs());
@@ -72,11 +86,22 @@ namespace TweetDck.Core.Other{
}
private void flowLayoutPlugins_Resize(object sender, EventArgs e){
int horizontalOffset = 8+(flowLayoutPlugins.VerticalScroll.Visible ? SystemInformation.VerticalScrollBarWidth : 0);
if (flowLayoutPlugins.Controls.Count == 0){
return;
}
Control lastControl = flowLayoutPlugins.Controls[flowLayoutPlugins.Controls.Count-1];
bool showScrollBar = lastControl.Location.Y+lastControl.Height >= flowLayoutPlugins.Height;
int horizontalOffset = showScrollBar ? SystemInformation.VerticalScrollBarWidth : 0;
flowLayoutPlugins.AutoScroll = showScrollBar;
flowLayoutPlugins.VerticalScroll.Visible = showScrollBar;
foreach(Control control in flowLayoutPlugins.Controls){
control.Width = flowLayoutPlugins.Width-control.Margin.Horizontal-horizontalOffset;
}
flowLayoutPlugins.Focus();
}
private void btnOpenFolder_Click(object sender, EventArgs e){
@@ -84,9 +109,11 @@ namespace TweetDck.Core.Other{
}
private void btnReload_Click(object sender, EventArgs e){
if (MessageBox.Show("This will also reload the browser window. Do you want to proceed?", "Reloading Plugins", MessageBoxButtons.YesNo, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2) == DialogResult.Yes){
pluginManager.Reload();
ReloadPluginTab();
}
}
private void btnClose_Click(object sender, EventArgs e){
Close();

View File

@@ -117,261 +117,4 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
AAABAAMAMDAAAAEAIACoJQAANgAAACAgAAABACAAqBAAAN4lAAAQEAAAAQAgAGgEAACGNgAAKAAAADAA
AABgAAAAAQAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqHg5IrF+OlS+hjyMw4g7tcmM
PM/MjTzezY485s2OPObMjjzfyIw80MOJPLe8hDyNs387WKx8OyQAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAApHU4FrB8OGXGijzAzo889dKR
PP/SkTz/0pE8/9GRPP/RkDz/0JA8/9CQPP/RkDz/0ZE8/9KRPP/TkTz/0pE8/86PPPbGijzCt4E8bKp6
OxoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKN1OB+9hTuLzI486NOR
PP/SkTz/0ZA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9GQ
PP/SkTz/0pE8/82OPOu9hTuOq3w8IgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACidTkFuoM8csyN
POrTkjz/0ZE8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9GRPP/Tkjz/zY887bqEPHimeDsHAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKp6
OynDiDvA05I8/9GRPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0ZA8/9ORPP/GijzFr308LgAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAArXw7UM2OPOvTkTz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/SkTz/zo887rOAPFYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAACxfjxm0ZA8/NGRPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0ZE8/9GQPP64gjxtAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAALaBPGXRkTz/0ZE8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9GQPP/SkTz/uIM8bAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqnk6TtCQPPzRkTz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/RkDz/0ZA8/rSA
PFUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACtfDwmzo887dKRPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0ZE8/86PPPGtfDwrAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKZ4PAXFijzC0pE8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CURf/TpGT/2baJ/9/GpP/l0bf/59W//+fVvf/kz7P/3sOg/9mz
hP/ToWH/0JND/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9KRPP/EiTvJo3Y7CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALiC
PG/Tkjz/0JA8/9CQPP/QkDz/0JA8/9CSQf/Vq3P/5tO7//j07v//////////////////////////////
////////////////////////9vHq/+TQtf/UqG7/0JA+/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/Tkjz/uYM8dgAAAAAAAAAAAAAAAAAA
AAAAAAAArXw8Gc2OPOzRkTz/0JA8/9CQPP/QlEX/27qP//fz7P//////////////////////////////
////////////////////////////////////////////////////////8ObZ/9auev/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/RkTz/zI488J1x
OB8AAAAAAAAAAAAAAAAAAAAAuoM7h9OSPP/QkDz/0JA8/9GXTf/TpGb/27uS/9u6kP/ewZz/59W9//Xv
5///////////////////////////////////////////////////////////////////////////////
///r3cr/0ZxV/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/1JI8/7mDO5AAAAAAAAAAAAAAAADFj0cR0pNB6s+PO//Ojjv/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CRQP/TpGf/5NC2//38+v//////////////////////////////////////////////
////////////////////////+vf0/9auev/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0ZE8/8uOPO6SajYVAAAAAAAAAADzr1df7qxV/+WkT//ZmUT/0JA9/86O
O//Pjzv/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9GaUv/l07r/////////////////////////
///////////////////////////////////////////////////buo//0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9OSPP+ldjdoAAAAAAAAAADurFW67qxV//Ct
Vv/vrVb/6adR/92dSP/Skz//zo47/8+PO//QkDz/0JA8/9CQPP/QkDz/0JJB/9Smaf/dv5j/+fXw////
////////////////////////////////////////////////////////////////////////2riM/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9KRPP/FiTzGoHQ6Ae6s
VRrurFXz7qxV/+6sVf/urFX/761W//CuVv/sqlT/4qFM/9aVQv/Pjzv/zo47/9CQPP/VqnH/8+vh////
////////////////////////////////////////////////////////////////////////////////
/////////////9aueP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9GQ
PP/Ojzz4oHM5Iu6sVU3urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/vrVX/8K5W/+6sVf/mpU//2plF/9q6
j///////////////////////////////////////////////////////////////////////////////
//////////////////////////////n18P/Rm1P/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/SkTz/rXs6Vu6sVYPurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/wrVb/6r2B////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////o18L/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/SkTz/v4Y8j+6sVa3urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFb/9Onb///////+/v3/////////////////////////////////////////
////////////////////////////////////////////////////////////////////////1qx2/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/SkTz/wYc7uO6sVcrurFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/trlr/67Zy/+m+h//y5NP/////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////7+TW/9CQPf/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/RkTz/yYw80e6s
VdvurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/6sme//v49P//////////////
////////////////////////////////////////////////////////////////////////////////
/////////////////////////////9arc//QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/RkDz/zI083+6sVeLurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/u2b3/////////
////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////+fVvv/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/zo885u6sVeLurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+vL
of//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////r28v/Rl0r/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/zo885u6sVdrurFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7LBj//r28f//////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
///Vq3P/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/RkDz/zI083+6sVcrurFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/6cKO////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
///////////////////ewp7/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/RkTz/yYw80O6s
VazurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7NOy//z59v/u2b7/+fPr////////////////////
////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////n1sD/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/SkTz/xIo8uO6sVYHurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/67Zx/+2uWf/qy6H/////////
////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////v5dj/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/SkTz/vYU8je6sVUvurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+nC
kP///////////////////////////////////////////////////////fz6//fu5P/w4Mv/793F//7+
/v/////////////////////////////////////////////////////////////////17eT/0JA//86O
Ov/Pjzv/0JA8/9CQPP/QkDz/0JA8/9CQPP/SkTz/tYA8Vu6sVRnurFXy7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7a5b//fw5///////////////////////////////////////+/jz/+7av//pwIr/67Jn/+6t
Vv/urFX/6rd1////////////////////////////////////////////////////////////////////
///48ej/6KhU/92cR//Tkj7/zo47/8+PO//QkDz/0JA8/9GQPP/Pjzz3rXw8IAAAAADurFW37qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/6b2D//////////////////////////////////nz6//qyZ3/7K9h/+6s
Vf/urFX/7qxV/+6sVf/urFX/6rl6////////////////////////////////////////////////////
///////////////////9/Pr/6r6C//CuV//sqlT/4aBL/9aWQv/Pjzv/zo47/9GRPP/GijzCp3g8AQAA
AADurFVb7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/68yk///////////////////////+/fz/68+q/+2v
XP/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/6rNr//7+/f//////////////////////////////
/////////////////////////////////////////////+nEk//vrVX/8K5W/+6sVf/lpE//2ZlF/9KR
Pf+zfzpoAAAAAAAAAADurFUP7qxV5u6sVf/urFX/7qxV/+6sVf/urFX/7NGu//////////////////fu
5P/quHb/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxW//Xr3///////////////
///////////////////////////////////////////////////7+PP/+vXu///////pxJL/7qxV/+6s
Vf/wrVb/8K1W/+mnUevUmUsUAAAAAAAAAAAAAAAA7qxVgO6sVf/urFX/7qxV/+6sVf/urFX/6sqg////
////////8eLO/+yvX//urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+nF
lf/////////////////////////////////////////////////////////////////8+vf/6cWV/+q5
ev/s07H/6rd1/+6sVf/urFX/7qxV/++tVogAAAAAAAAAAAAAAAAAAAAA7qxVFe6sVejurFX/7qxV/+6s
Vf/urFX/6bp8///////w4Mn/7a1Z/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/x4cz////////////////////////////////////////////////////+//7+
/v/////////+/+m8gP/urFX/7a1Y/+6sVf/urFX/7qxV7O6sVRoAAAAAAAAAAAAAAAAAAAAAAAAAAO6s
VWburFX/7qxV/+6sVf/urFX/7a5b/+zTs//sr1//7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/sr1//8uPQ////////////////////////////////////
////////6suj/+qzav/pxpj/9Onb//n07f/rr2D/7qxV/+6sVf/urFX/7qxVbwAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAO6sVQPurFW67qxV/+6sVf/urFX/7qxV/+6sVv/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7axY/+nGl//27uP/////////
/////////fz6//Lj0P/qunv/7qxV/+6sVf/urFX/7qxV/+m9gv/qt3X/7qxV/+6sVf/urFXB7qxVBQAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADurFUg7qxV6O6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/qtnP/6sqg/+3WuP/s07P/6cSS/+uyZf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVv/trVr/7qxV/+6s
VezurFUlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7qxVRu6sVfjurFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV++6sVUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAO6s
VVzurFX97qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxVYwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAADurFVd7qxV+O6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVfvurFVjAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7qxVR+6sVeXurFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV6O6sVU0AAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAO6sVSPurFW37qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFW77qxVJgAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AADurFUB7qxVae6sVeTurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV5+6sVW3urFUDAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAO6sVRjurFWA7qxV4u6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVeTurFWE7qxVGwAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7qxVEu6sVV7urFW17qxV7+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVfDurFW47qxVX+6s
VRMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAA7qxVG+6sVUzurFWA7qxVrO6sVcnurFXa7qxV4u6sVeLurFXa7qxVye6sVa3urFWC7qxVTe6s
VR0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAP//gAH//6gr//wAAD//qCv/8AAAD/+oK//AAAAD/6gr/4AAAAH/qCv/AAAAAP+oK/4A
AAAAf6gr/AAAAAA/qCv4AAAAAB+oK/AAAAAAD6gr4AAAAAAHqCvgAAAAAAeoK8AAAAAAA6grwAAAAAAD
qCuAAAAAAAGoK4AAAAAAAagrgAAAAAAAqCsAAAAAAACoKwAAAAAAAKgrAAAAAAAAqCsAAAAAAACoKwAA
AAAAAKgrAAAAAAAAqCsAAAAAAACoKwAAAAAAAKgrAAAAAAAAqCsAAAAAAACoKwAAAAAAAKgrAAAAAAAA
qCsAAAAAAACoKwAAAAAAAKgrgAAAAAAAqCuAAAAAAAGoK4AAAAAAAagrwAAAAAADqCvAAAAAAAOoK+AA
AAAAB6gr4AAAAAAHqCvwAAAAAA+oK/gAAAAAH6gr/AAAAAA/qCv+AAAAAH+oK/8AAAAA/6gr/4AAAAH/
qCv/wAAAA/+oK//wAAAP/6gr//wAAD//qCv//4AB//+oKygAAAAgAAAAQAAAAAEAIAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAApHY4FrR/
Ole+hjuVxos7w8uNPNvOjzznzo8858uNPNzGijzEvoU7l7WBO1mpejsYAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqHg5Kr6F
O5XLjTvo0pE8/9ORPP/SkTz/0ZE8/9CQPP/QkDz/0ZE8/9KRPP/TkTz/0pE8/82OPOq/hjyZrXw7LAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAo3U6CbuE
O4bPjzz005I8/9GRPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0ZE8/9OS
PP/PkDz1vYU8iah6PAsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKR3
OibGizvG05E8/9GQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9GQPP/TkTz/yIs8yqx8PCkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AACsezsxy40849KRPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/SkTz/zI485rKAPTUAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAApXc6JMuNPOTSkTz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/SkTz/zI485659PCcAAAAAAAAAAAAA
AAAAAAAAAAAAAKd4OwnHizzJ0pE8/9CQPP/QkDz/0JA9/9OgYP/YtIX/38Sh/+LMr//iy67/3sOf/9iy
gv/Tn13/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/SkTz/x4s8zaV4
OwsAAAAAAAAAAAAAAAAAAAAAu4Q8hNOSPP/QkDz/0JA8/9GXS//YtIX/6NfC//Xv5//8+vj/////////
///8+vf/9O3k/+fVvv/YsYH/0JRF/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/Tkjz/u4Q7iQAAAAAAAAAAAAAAAKx7OyTPjzz10ZA8/9GXS//Ztoj/8une////////////////////
///////////////////////////////////38uz/3L2V/9CRP//QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9GRPP/Ojzz3onU5KQAAAAAAAAAAxo1BlNGQO//Ojjv/0JNC/9GWSP/QlET/0ZpR/9au
eP/n1b3//v79////////////////////////////////////////////7+TV/9GdWP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9SSPP+4gjqaAAAAAPGuVhHvrVXq5qRO/9qZRP/QkT3/zo46/8+P
O//QkDz/0JA8/9CQPP/To2P/8+vh////////////////////////////////////////////+fXw/9Oh
Yf/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0pE8/8qMO+2WbTYV7qxVUe6sVf/wrVb/761W/+mo
Uv/enUj/05M//86PO//PkD7/3cCZ//Lp3v/7+PT/////////////////////////////////////////
////////+PTv/9GcVf/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/05I8/699OljurFWR7qxV/+6s
Vf/urFX/761W//CuV//sqlT/4qJQ/+zdyf//////////////////////////////////////////////
////////////////////////7+TV/9CRP//QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/Tkjz/vIQ7mO6s
Vb/urFX/7qxV/+6sVf/urFX/7qxV/++tVf/ry5///v79//7+/f//////////////////////////////
////////////////////////////////////////2rqO/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9KR
PP/GijvF7qxV2u6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+u1bv/t1bb//fv4////////////////////
///////////////////////////////////////////////////48+3/0ZRG/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0ZA8/8uNPNzurFXl7qxV/+6sVf/urFX/7qxV/+6sVf/rsWP/9OjZ////////////////////
///////////////////////////////////////////////////////////////////YtIX/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/zo886O6sVeXurFX/7qxV/+6sVf/urFX/7qxW//Hiz///////////////
/////////////////////////////////////////////////////////////////////////////+rb
yP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/Ojzzo7qxV2e6sVf/urFX/7qxV/+6sVf/quHb/////////
////////////////////////////////////////////////////////////////////////////////
////////+fXw/9CURf/QkDz/0JA8/9CQPP/QkDz/0ZA8/8yOPNzurFW/7qxV/+6sVf/urFX/7qxV/+nA
if/s1LT/9Onb////////////////////////////////////////////////////////////////////
////////////////////////0Z5b/9CQPP/QkDz/0JA8/9CQPP/SkTz/x4s8xe6sVY/urFX/7qxV/+6s
Vf/urFX/7qxV/+nEk//+/fz////////////////////////////+/v3/+PHo//To2f/+/v7/////////
///////////////////////////////////VqGz/zo46/8+PO//QkDz/0JA8/9KRPP+/hjyX7qxVUO6s
Vf/urFX/7qxV/+6sVf/ssGH//Pr2///////////////////////y49D/6cOR/+qyaf/urVb/6rJp////
/////////////////////////////////////////////+e7f//goEr/1ZVB/8+PO//Ojzv/0pE8/7WB
PFfurFUQ7qxV6O6sVf/urFX/7qxV/+m+hv/////////////////z5tX/6rl6/+6sVf/urFX/7qxV/+6s
Vf/qs2r////+////////////////////////////////////////////+PLp/+y4c//urFX/5aRO/9qZ
RP/Mjjztp3g6FQAAAADurFWQ7qxV/+6sVf/urFX/6cKP////////////682m/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+2sV//17N////////////////////////////////////////7+/v/06Nn/8uTT/+qz
a//wrVb/761W/+alUZYAAAAAAAAAAO6sVSHurFX07qxV/+6sVf/quXj//////+nDkf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+m+hP////////////////////////////////////////////jy
6v/qtnH/67Fj/+6sVf/urFX2761WJQAAAAAAAAAAAAAAAO6sVX7urFX/7qxV/+2vW//pvID/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+nEkv/7+PT//////////////////fz6/+rJ
n//rtW7/682n/+zTs//urFb/7qxV/+6sVYQAAAAAAAAAAAAAAAAAAAAA7qxVB+6sVcTurFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+u0bP/qy6H/7da4/+vN
pv/qtnP/7qxV/+6sVf/urFX/7K9f/+2sWP/urFXI7qxVCQAAAAAAAAAAAAAAAAAAAAAAAAAA7qxVIO6s
VeDurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV4+6sVSMAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAA7qxVLO6sVd/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVeLurFUwAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAA7qxVIu6sVcHurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFXE7qxVJAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7qxVBu6sVX/urFXw7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFXy7qxVgu6sVQgAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAO6sVSXurFWP7qxV5e6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFXl7qxVke6sVScAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AADurFUS7qxVUO6sVY7urFW97qxV2O6sVeXurFXl7qxV2O6sVb7urFWP7qxVUu6sVRMAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/8AD//8AAP/8AAA/+AAAH/AAAA/gAAAHwAAAA8AA
AAOAAAABgAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAA
AAGAAAABwAAAA8AAAAPgAAAH8AAAD/gAAB/8AAA//wAA///AA/8oAAAAEAAAACAAAAABACAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKx7Oja9hTqTyYw8zs6PPOfOjzznyYw8zr6G
PJSvfjw3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAo3Y7CbyEO5PRkDz805I8/9GRPP/QkDz/0JA8/9GR
PP/TkTz/0ZA8/b+GPJWrfDwKAAAAAAAAAAAAAAAAp3g8CcSJPLfTkjz/0ZA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/Tkjz/xoo8uqx8PQoAAAAAAAAAAL2FPJPUk0D/06Rm/92/mf/izK//4sut/9u6
j//Snlr/0JA8/9CQPP/QkDz/0JA8/9OSPP+9hTyWAAAAALiEQDLQkDz90JpS/93Amf/u4tP/////////
/////////fv5/+DIqP/QkkH/0JA8/9CQPP/RkDz/0JA8/qN1OTTtq1WS56VP/9qaRf/RkT3/0JdO/+rc
yP//////////////////////693L/9CSQf/QkDz/0JA8/9SSPP+3gTqW761Vz++tVv/wrVb/6K5h//fy
6v/////////////////////////////////gx6b/0JA8/9CQPP/RkTz/yYw80e6sVefurFX/7qxV/+q+
hP/69vD//////////////////////////////////fz7/9GbU//QkDz/0JA8/8+PPOnurFXn7qxV/+yw
Y//79/P////////////////////////////////////////////ZuIz/0JA8/9CQPP/Pjzzp7qxVz+6s
Vf/sr2H/8eLO/////////////v7+//n07f/+/v7/////////////////48yu/8+PO//Qjzv/yYs80e6s
VZLurFX/67Jm//7+/f/8+fb/68yl/+qza//rsmf//v37//////////////////Pn2P/ip1n/2phC/8CH
PJXurFUw7qxV/eq2c//169//6rNr/+6sVf/urFX/7q1W//Pm1f/////////////////8+fX/67+G/++t
Vf3lpVEyAAAAAO6sVZHtrVr/7a9c/+6sVf/urFX/7qxV/+6sVf/rsWP/7de5//Lj0P/pxJL/67Zy/+qz
av/urFWTAAAAAAAAAADurFUI7qxVtO6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFW37qxVCQAAAAAAAAAAAAAAAO6sVQjurFWQ7qxV++6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
VfvurFWS7qxVCQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAO6sVTPurFWQ7qxVzO6sVeburFXm7qxVzO6s
VZDurFU0AAAAAAAAAAAAAAAAAAAAAPAPrEHAA6xBgAGsQYABrEEAAKxBAACsQQAArEEAAKxBAACsQQAA
rEEAAKxBAACsQYABrEGAAaxBwAOsQfAPrEE=
</value>
</data>
</root>

View File

@@ -23,7 +23,6 @@
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent() {
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(FormSettings));
this.btnClose = new System.Windows.Forms.Button();
this.labelTip = new System.Windows.Forms.Label();
this.tabPanel = new TweetDck.Core.Controls.TabPanel();
@@ -70,7 +69,7 @@
this.Controls.Add(this.btnClose);
this.Controls.Add(this.tabPanel);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
this.Icon = Properties.Resources.icon;
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "FormSettings";

View File

@@ -3,14 +3,14 @@ using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using TweetDck.Core.Other.Settings;
using TweetDck.Core.Other.Settings.Export;
using TweetDck.Plugins;
using TweetDck.Updates;
namespace TweetDck.Core.Other{
sealed partial class FormSettings : Form{
private readonly Dictionary<Type, BaseTabSettings> tabs = new Dictionary<Type, BaseTabSettings>(4);
public FormSettings(FormBrowser browserForm, UpdateHandler updates){
public FormSettings(FormBrowser browserForm, PluginManager plugins, UpdateHandler updates){
InitializeComponent();
Text = Program.BrandName+" Settings";
@@ -19,7 +19,7 @@ namespace TweetDck.Core.Other{
this.tabPanel.AddButton("General", SelectTab<TabSettingsGeneral>);
this.tabPanel.AddButton("Notifications", () => SelectTab(() => new TabSettingsNotifications(browserForm.CreateNotificationForm(false))));
this.tabPanel.AddButton("Updates", () => SelectTab(() => new TabSettingsUpdates(updates)));
this.tabPanel.AddButton("Advanced",() => SelectTab(() => new TabSettingsAdvanced(browserForm.ReloadBrowser)));
this.tabPanel.AddButton("Advanced", () => SelectTab(() => new TabSettingsAdvanced(browserForm.ReloadBrowser, plugins)));
this.tabPanel.SelectTab(tabPanel.Buttons.First());
}

View File

@@ -117,261 +117,4 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
AAABAAMAMDAAAAEAIACoJQAANgAAACAgAAABACAAqBAAAN4lAAAQEAAAAQAgAGgEAACGNgAAKAAAADAA
AABgAAAAAQAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqHg5IrF+OlS+hjyMw4g7tcmM
PM/MjTzezY485s2OPObMjjzfyIw80MOJPLe8hDyNs387WKx8OyQAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAApHU4FrB8OGXGijzAzo889dKR
PP/SkTz/0pE8/9GRPP/RkDz/0JA8/9CQPP/RkDz/0ZE8/9KRPP/TkTz/0pE8/86PPPbGijzCt4E8bKp6
OxoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKN1OB+9hTuLzI486NOR
PP/SkTz/0ZA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9GQ
PP/SkTz/0pE8/82OPOu9hTuOq3w8IgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACidTkFuoM8csyN
POrTkjz/0ZE8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9GRPP/Tkjz/zY887bqEPHimeDsHAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKp6
OynDiDvA05I8/9GRPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0ZA8/9ORPP/GijzFr308LgAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAArXw7UM2OPOvTkTz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/SkTz/zo887rOAPFYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAACxfjxm0ZA8/NGRPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0ZE8/9GQPP64gjxtAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAALaBPGXRkTz/0ZE8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9GQPP/SkTz/uIM8bAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqnk6TtCQPPzRkTz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/RkDz/0ZA8/rSA
PFUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACtfDwmzo887dKRPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0ZE8/86PPPGtfDwrAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKZ4PAXFijzC0pE8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CURf/TpGT/2baJ/9/GpP/l0bf/59W//+fVvf/kz7P/3sOg/9mz
hP/ToWH/0JND/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9KRPP/EiTvJo3Y7CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALiC
PG/Tkjz/0JA8/9CQPP/QkDz/0JA8/9CSQf/Vq3P/5tO7//j07v//////////////////////////////
////////////////////////9vHq/+TQtf/UqG7/0JA+/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/Tkjz/uYM8dgAAAAAAAAAAAAAAAAAA
AAAAAAAArXw8Gc2OPOzRkTz/0JA8/9CQPP/QlEX/27qP//fz7P//////////////////////////////
////////////////////////////////////////////////////////8ObZ/9auev/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/RkTz/zI488J1x
OB8AAAAAAAAAAAAAAAAAAAAAuoM7h9OSPP/QkDz/0JA8/9GXTf/TpGb/27uS/9u6kP/ewZz/59W9//Xv
5///////////////////////////////////////////////////////////////////////////////
///r3cr/0ZxV/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/1JI8/7mDO5AAAAAAAAAAAAAAAADFj0cR0pNB6s+PO//Ojjv/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CRQP/TpGf/5NC2//38+v//////////////////////////////////////////////
////////////////////////+vf0/9auev/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0ZE8/8uOPO6SajYVAAAAAAAAAADzr1df7qxV/+WkT//ZmUT/0JA9/86O
O//Pjzv/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9GaUv/l07r/////////////////////////
///////////////////////////////////////////////////buo//0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9OSPP+ldjdoAAAAAAAAAADurFW67qxV//Ct
Vv/vrVb/6adR/92dSP/Skz//zo47/8+PO//QkDz/0JA8/9CQPP/QkDz/0JJB/9Smaf/dv5j/+fXw////
////////////////////////////////////////////////////////////////////////2riM/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9KRPP/FiTzGoHQ6Ae6s
VRrurFXz7qxV/+6sVf/urFX/761W//CuVv/sqlT/4qFM/9aVQv/Pjzv/zo47/9CQPP/VqnH/8+vh////
////////////////////////////////////////////////////////////////////////////////
/////////////9aueP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9GQ
PP/Ojzz4oHM5Iu6sVU3urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/vrVX/8K5W/+6sVf/mpU//2plF/9q6
j///////////////////////////////////////////////////////////////////////////////
//////////////////////////////n18P/Rm1P/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/SkTz/rXs6Vu6sVYPurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/wrVb/6r2B////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////o18L/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/SkTz/v4Y8j+6sVa3urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFb/9Onb///////+/v3/////////////////////////////////////////
////////////////////////////////////////////////////////////////////////1qx2/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/SkTz/wYc7uO6sVcrurFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/trlr/67Zy/+m+h//y5NP/////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////7+TW/9CQPf/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/RkTz/yYw80e6s
VdvurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/6sme//v49P//////////////
////////////////////////////////////////////////////////////////////////////////
/////////////////////////////9arc//QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/RkDz/zI083+6sVeLurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/u2b3/////////
////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////+fVvv/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/zo885u6sVeLurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+vL
of//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////r28v/Rl0r/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/zo885u6sVdrurFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7LBj//r28f//////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
///Vq3P/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/RkDz/zI083+6sVcrurFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/6cKO////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
///////////////////ewp7/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/RkTz/yYw80O6s
VazurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7NOy//z59v/u2b7/+fPr////////////////////
////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////n1sD/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/SkTz/xIo8uO6sVYHurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/67Zx/+2uWf/qy6H/////////
////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////v5dj/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/SkTz/vYU8je6sVUvurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+nC
kP///////////////////////////////////////////////////////fz6//fu5P/w4Mv/793F//7+
/v/////////////////////////////////////////////////////////////////17eT/0JA//86O
Ov/Pjzv/0JA8/9CQPP/QkDz/0JA8/9CQPP/SkTz/tYA8Vu6sVRnurFXy7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7a5b//fw5///////////////////////////////////////+/jz/+7av//pwIr/67Jn/+6t
Vv/urFX/6rd1////////////////////////////////////////////////////////////////////
///48ej/6KhU/92cR//Tkj7/zo47/8+PO//QkDz/0JA8/9GQPP/Pjzz3rXw8IAAAAADurFW37qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/6b2D//////////////////////////////////nz6//qyZ3/7K9h/+6s
Vf/urFX/7qxV/+6sVf/urFX/6rl6////////////////////////////////////////////////////
///////////////////9/Pr/6r6C//CuV//sqlT/4aBL/9aWQv/Pjzv/zo47/9GRPP/GijzCp3g8AQAA
AADurFVb7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/68yk///////////////////////+/fz/68+q/+2v
XP/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/6rNr//7+/f//////////////////////////////
/////////////////////////////////////////////+nEk//vrVX/8K5W/+6sVf/lpE//2ZlF/9KR
Pf+zfzpoAAAAAAAAAADurFUP7qxV5u6sVf/urFX/7qxV/+6sVf/urFX/7NGu//////////////////fu
5P/quHb/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxW//Xr3///////////////
///////////////////////////////////////////////////7+PP/+vXu///////pxJL/7qxV/+6s
Vf/wrVb/8K1W/+mnUevUmUsUAAAAAAAAAAAAAAAA7qxVgO6sVf/urFX/7qxV/+6sVf/urFX/6sqg////
////////8eLO/+yvX//urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+nF
lf/////////////////////////////////////////////////////////////////8+vf/6cWV/+q5
ev/s07H/6rd1/+6sVf/urFX/7qxV/++tVogAAAAAAAAAAAAAAAAAAAAA7qxVFe6sVejurFX/7qxV/+6s
Vf/urFX/6bp8///////w4Mn/7a1Z/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/x4cz////////////////////////////////////////////////////+//7+
/v/////////+/+m8gP/urFX/7a1Y/+6sVf/urFX/7qxV7O6sVRoAAAAAAAAAAAAAAAAAAAAAAAAAAO6s
VWburFX/7qxV/+6sVf/urFX/7a5b/+zTs//sr1//7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/sr1//8uPQ////////////////////////////////////
////////6suj/+qzav/pxpj/9Onb//n07f/rr2D/7qxV/+6sVf/urFX/7qxVbwAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAO6sVQPurFW67qxV/+6sVf/urFX/7qxV/+6sVv/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7axY/+nGl//27uP/////////
/////////fz6//Lj0P/qunv/7qxV/+6sVf/urFX/7qxV/+m9gv/qt3X/7qxV/+6sVf/urFXB7qxVBQAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADurFUg7qxV6O6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/qtnP/6sqg/+3WuP/s07P/6cSS/+uyZf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVv/trVr/7qxV/+6s
VezurFUlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7qxVRu6sVfjurFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV++6sVUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAO6s
VVzurFX97qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxVYwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAADurFVd7qxV+O6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVfvurFVjAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7qxVR+6sVeXurFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV6O6sVU0AAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAO6sVSPurFW37qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFW77qxVJgAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AADurFUB7qxVae6sVeTurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV5+6sVW3urFUDAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAO6sVRjurFWA7qxV4u6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVeTurFWE7qxVGwAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7qxVEu6sVV7urFW17qxV7+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVfDurFW47qxVX+6s
VRMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAA7qxVG+6sVUzurFWA7qxVrO6sVcnurFXa7qxV4u6sVeLurFXa7qxVye6sVa3urFWC7qxVTe6s
VR0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAP//gAH//6gr//wAAD//qCv/8AAAD/+oK//AAAAD/6gr/4AAAAH/qCv/AAAAAP+oK/4A
AAAAf6gr/AAAAAA/qCv4AAAAAB+oK/AAAAAAD6gr4AAAAAAHqCvgAAAAAAeoK8AAAAAAA6grwAAAAAAD
qCuAAAAAAAGoK4AAAAAAAagrgAAAAAAAqCsAAAAAAACoKwAAAAAAAKgrAAAAAAAAqCsAAAAAAACoKwAA
AAAAAKgrAAAAAAAAqCsAAAAAAACoKwAAAAAAAKgrAAAAAAAAqCsAAAAAAACoKwAAAAAAAKgrAAAAAAAA
qCsAAAAAAACoKwAAAAAAAKgrgAAAAAAAqCuAAAAAAAGoK4AAAAAAAagrwAAAAAADqCvAAAAAAAOoK+AA
AAAAB6gr4AAAAAAHqCvwAAAAAA+oK/gAAAAAH6gr/AAAAAA/qCv+AAAAAH+oK/8AAAAA/6gr/4AAAAH/
qCv/wAAAA/+oK//wAAAP/6gr//wAAD//qCv//4AB//+oKygAAAAgAAAAQAAAAAEAIAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAApHY4FrR/
Ole+hjuVxos7w8uNPNvOjzznzo8858uNPNzGijzEvoU7l7WBO1mpejsYAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqHg5Kr6F
O5XLjTvo0pE8/9ORPP/SkTz/0ZE8/9CQPP/QkDz/0ZE8/9KRPP/TkTz/0pE8/82OPOq/hjyZrXw7LAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAo3U6CbuE
O4bPjzz005I8/9GRPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0ZE8/9OS
PP/PkDz1vYU8iah6PAsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKR3
OibGizvG05E8/9GQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9GQPP/TkTz/yIs8yqx8PCkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AACsezsxy40849KRPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/SkTz/zI485rKAPTUAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAApXc6JMuNPOTSkTz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/SkTz/zI485659PCcAAAAAAAAAAAAA
AAAAAAAAAAAAAKd4OwnHizzJ0pE8/9CQPP/QkDz/0JA9/9OgYP/YtIX/38Sh/+LMr//iy67/3sOf/9iy
gv/Tn13/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/SkTz/x4s8zaV4
OwsAAAAAAAAAAAAAAAAAAAAAu4Q8hNOSPP/QkDz/0JA8/9GXS//YtIX/6NfC//Xv5//8+vj/////////
///8+vf/9O3k/+fVvv/YsYH/0JRF/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/Tkjz/u4Q7iQAAAAAAAAAAAAAAAKx7OyTPjzz10ZA8/9GXS//Ztoj/8une////////////////////
///////////////////////////////////38uz/3L2V/9CRP//QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9GRPP/Ojzz3onU5KQAAAAAAAAAAxo1BlNGQO//Ojjv/0JNC/9GWSP/QlET/0ZpR/9au
eP/n1b3//v79////////////////////////////////////////////7+TV/9GdWP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9SSPP+4gjqaAAAAAPGuVhHvrVXq5qRO/9qZRP/QkT3/zo46/8+P
O//QkDz/0JA8/9CQPP/To2P/8+vh////////////////////////////////////////////+fXw/9Oh
Yf/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0pE8/8qMO+2WbTYV7qxVUe6sVf/wrVb/761W/+mo
Uv/enUj/05M//86PO//PkD7/3cCZ//Lp3v/7+PT/////////////////////////////////////////
////////+PTv/9GcVf/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/05I8/699OljurFWR7qxV/+6s
Vf/urFX/761W//CuV//sqlT/4qJQ/+zdyf//////////////////////////////////////////////
////////////////////////7+TV/9CRP//QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/Tkjz/vIQ7mO6s
Vb/urFX/7qxV/+6sVf/urFX/7qxV/++tVf/ry5///v79//7+/f//////////////////////////////
////////////////////////////////////////2rqO/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9KR
PP/GijvF7qxV2u6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+u1bv/t1bb//fv4////////////////////
///////////////////////////////////////////////////48+3/0ZRG/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0ZA8/8uNPNzurFXl7qxV/+6sVf/urFX/7qxV/+6sVf/rsWP/9OjZ////////////////////
///////////////////////////////////////////////////////////////////YtIX/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/zo886O6sVeXurFX/7qxV/+6sVf/urFX/7qxW//Hiz///////////////
/////////////////////////////////////////////////////////////////////////////+rb
yP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/Ojzzo7qxV2e6sVf/urFX/7qxV/+6sVf/quHb/////////
////////////////////////////////////////////////////////////////////////////////
////////+fXw/9CURf/QkDz/0JA8/9CQPP/QkDz/0ZA8/8yOPNzurFW/7qxV/+6sVf/urFX/7qxV/+nA
if/s1LT/9Onb////////////////////////////////////////////////////////////////////
////////////////////////0Z5b/9CQPP/QkDz/0JA8/9CQPP/SkTz/x4s8xe6sVY/urFX/7qxV/+6s
Vf/urFX/7qxV/+nEk//+/fz////////////////////////////+/v3/+PHo//To2f/+/v7/////////
///////////////////////////////////VqGz/zo46/8+PO//QkDz/0JA8/9KRPP+/hjyX7qxVUO6s
Vf/urFX/7qxV/+6sVf/ssGH//Pr2///////////////////////y49D/6cOR/+qyaf/urVb/6rJp////
/////////////////////////////////////////////+e7f//goEr/1ZVB/8+PO//Ojzv/0pE8/7WB
PFfurFUQ7qxV6O6sVf/urFX/7qxV/+m+hv/////////////////z5tX/6rl6/+6sVf/urFX/7qxV/+6s
Vf/qs2r////+////////////////////////////////////////////+PLp/+y4c//urFX/5aRO/9qZ
RP/Mjjztp3g6FQAAAADurFWQ7qxV/+6sVf/urFX/6cKP////////////682m/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+2sV//17N////////////////////////////////////////7+/v/06Nn/8uTT/+qz
a//wrVb/761W/+alUZYAAAAAAAAAAO6sVSHurFX07qxV/+6sVf/quXj//////+nDkf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+m+hP////////////////////////////////////////////jy
6v/qtnH/67Fj/+6sVf/urFX2761WJQAAAAAAAAAAAAAAAO6sVX7urFX/7qxV/+2vW//pvID/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+nEkv/7+PT//////////////////fz6/+rJ
n//rtW7/682n/+zTs//urFb/7qxV/+6sVYQAAAAAAAAAAAAAAAAAAAAA7qxVB+6sVcTurFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+u0bP/qy6H/7da4/+vN
pv/qtnP/7qxV/+6sVf/urFX/7K9f/+2sWP/urFXI7qxVCQAAAAAAAAAAAAAAAAAAAAAAAAAA7qxVIO6s
VeDurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV4+6sVSMAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAA7qxVLO6sVd/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVeLurFUwAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAA7qxVIu6sVcHurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFXE7qxVJAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7qxVBu6sVX/urFXw7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFXy7qxVgu6sVQgAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAO6sVSXurFWP7qxV5e6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFXl7qxVke6sVScAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AADurFUS7qxVUO6sVY7urFW97qxV2O6sVeXurFXl7qxV2O6sVb7urFWP7qxVUu6sVRMAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/8AD//8AAP/8AAA/+AAAH/AAAA/gAAAHwAAAA8AA
AAOAAAABgAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAA
AAGAAAABwAAAA8AAAAPgAAAH8AAAD/gAAB/8AAA//wAA///AA/8oAAAAEAAAACAAAAABACAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKx7Oja9hTqTyYw8zs6PPOfOjzznyYw8zr6G
PJSvfjw3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAo3Y7CbyEO5PRkDz805I8/9GRPP/QkDz/0JA8/9GR
PP/TkTz/0ZA8/b+GPJWrfDwKAAAAAAAAAAAAAAAAp3g8CcSJPLfTkjz/0ZA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/Tkjz/xoo8uqx8PQoAAAAAAAAAAL2FPJPUk0D/06Rm/92/mf/izK//4sut/9u6
j//Snlr/0JA8/9CQPP/QkDz/0JA8/9OSPP+9hTyWAAAAALiEQDLQkDz90JpS/93Amf/u4tP/////////
/////////fv5/+DIqP/QkkH/0JA8/9CQPP/RkDz/0JA8/qN1OTTtq1WS56VP/9qaRf/RkT3/0JdO/+rc
yP//////////////////////693L/9CSQf/QkDz/0JA8/9SSPP+3gTqW761Vz++tVv/wrVb/6K5h//fy
6v/////////////////////////////////gx6b/0JA8/9CQPP/RkTz/yYw80e6sVefurFX/7qxV/+q+
hP/69vD//////////////////////////////////fz7/9GbU//QkDz/0JA8/8+PPOnurFXn7qxV/+yw
Y//79/P////////////////////////////////////////////ZuIz/0JA8/9CQPP/Pjzzp7qxVz+6s
Vf/sr2H/8eLO/////////////v7+//n07f/+/v7/////////////////48yu/8+PO//Qjzv/yYs80e6s
VZLurFX/67Jm//7+/f/8+fb/68yl/+qza//rsmf//v37//////////////////Pn2P/ip1n/2phC/8CH
PJXurFUw7qxV/eq2c//169//6rNr/+6sVf/urFX/7q1W//Pm1f/////////////////8+fX/67+G/++t
Vf3lpVEyAAAAAO6sVZHtrVr/7a9c/+6sVf/urFX/7qxV/+6sVf/rsWP/7de5//Lj0P/pxJL/67Zy/+qz
av/urFWTAAAAAAAAAADurFUI7qxVtO6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFW37qxVCQAAAAAAAAAAAAAAAO6sVQjurFWQ7qxV++6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
VfvurFWS7qxVCQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAO6sVTPurFWQ7qxVzO6sVeburFXm7qxVzO6s
VZDurFU0AAAAAAAAAAAAAAAAAAAAAPAPrEHAA6xBgAGsQYABrEEAAKxBAACsQQAArEEAAKxBAACsQQAA
rEEAAKxBAACsQYABrEGAAaxBwAOsQfAPrEE=
</value>
</data>
</root>

View File

@@ -12,7 +12,13 @@ namespace TweetDck.Core.Other.Settings{
public bool Ready { get; set; }
public BaseTabSettings(){
Padding = new Padding(6,6,6,6);
Padding = new Padding(6);
}
protected static void PromptRestart(){
if (MessageBox.Show("The application must restart for the setting to take place. Do you want to restart now?", Program.BrandName+" Settings", MessageBoxButtons.YesNo, MessageBoxIcon.Information) == DialogResult.Yes){
Program.Restart();
}
}
}
}

View File

@@ -47,14 +47,15 @@
this.textBoxBrowserCSS.Margin = new System.Windows.Forms.Padding(0, 3, 0, 0);
this.textBoxBrowserCSS.Multiline = true;
this.textBoxBrowserCSS.Name = "textBoxBrowserCSS";
this.textBoxBrowserCSS.Size = new System.Drawing.Size(226, 193);
this.textBoxBrowserCSS.ScrollBars = System.Windows.Forms.ScrollBars.Both;
this.textBoxBrowserCSS.Size = new System.Drawing.Size(373, 253);
this.textBoxBrowserCSS.TabIndex = 0;
this.textBoxBrowserCSS.WordWrap = false;
//
// btnCancel
//
this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.btnCancel.Location = new System.Drawing.Point(354, 227);
this.btnCancel.Location = new System.Drawing.Point(654, 287);
this.btnCancel.Name = "btnCancel";
this.btnCancel.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnCancel.Size = new System.Drawing.Size(56, 23);
@@ -66,7 +67,7 @@
// btnApply
//
this.btnApply.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.btnApply.Location = new System.Drawing.Point(416, 227);
this.btnApply.Location = new System.Drawing.Point(716, 287);
this.btnApply.Name = "btnApply";
this.btnApply.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnApply.Size = new System.Drawing.Size(56, 23);
@@ -94,8 +95,8 @@
this.splitContainer.Panel2.Controls.Add(this.labelNotification);
this.splitContainer.Panel2.Controls.Add(this.textBoxNotificationCSS);
this.splitContainer.Panel2MinSize = 64;
this.splitContainer.Size = new System.Drawing.Size(460, 209);
this.splitContainer.SplitterDistance = 226;
this.splitContainer.Size = new System.Drawing.Size(760, 269);
this.splitContainer.SplitterDistance = 373;
this.splitContainer.SplitterWidth = 5;
this.splitContainer.TabIndex = 5;
//
@@ -129,7 +130,8 @@
this.textBoxNotificationCSS.Margin = new System.Windows.Forms.Padding(0, 3, 0, 0);
this.textBoxNotificationCSS.Multiline = true;
this.textBoxNotificationCSS.Name = "textBoxNotificationCSS";
this.textBoxNotificationCSS.Size = new System.Drawing.Size(227, 193);
this.textBoxNotificationCSS.ScrollBars = System.Windows.Forms.ScrollBars.Both;
this.textBoxNotificationCSS.Size = new System.Drawing.Size(378, 253);
this.textBoxNotificationCSS.TabIndex = 1;
this.textBoxNotificationCSS.WordWrap = false;
//
@@ -137,7 +139,7 @@
//
this.labelWarning.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.labelWarning.AutoSize = true;
this.labelWarning.Location = new System.Drawing.Point(9, 232);
this.labelWarning.Location = new System.Drawing.Point(9, 292);
this.labelWarning.Name = "labelWarning";
this.labelWarning.Size = new System.Drawing.Size(341, 13);
this.labelWarning.TabIndex = 6;
@@ -147,7 +149,7 @@
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(484, 262);
this.ClientSize = new System.Drawing.Size(784, 322);
this.Controls.Add(this.labelWarning);
this.Controls.Add(this.splitContainer);
this.Controls.Add(this.btnApply);

View File

@@ -1,5 +1,6 @@
using System;
using System.Windows.Forms;
using TweetDck.Core.Controls;
namespace TweetDck.Core.Other.Settings.Dialogs{
sealed partial class DialogSettingsCSS : Form{
@@ -20,7 +21,10 @@ namespace TweetDck.Core.Other.Settings.Dialogs{
Text = Program.BrandName+" Settings - CSS";
textBoxBrowserCSS.EnableMultilineShortcuts();
textBoxBrowserCSS.Text = Program.UserConfig.CustomBrowserCSS ?? "";
textBoxNotificationCSS.EnableMultilineShortcuts();
textBoxNotificationCSS.Text = Program.UserConfig.CustomNotificationCSS ?? "";
}

View File

@@ -1,6 +1,6 @@
using System;
using System.Collections.Generic;
using System.Windows.Forms;
using TweetDck.Core.Controls;
using TweetDck.Core.Utils;
namespace TweetDck.Core.Other.Settings.Dialogs{
@@ -16,6 +16,7 @@ namespace TweetDck.Core.Other.Settings.Dialogs{
Text = Program.BrandName+" Settings - CEF Arguments";
textBoxArgs.EnableMultilineShortcuts();
textBoxArgs.Text = Program.UserConfig.CustomCefArgs ?? "";
textBoxArgs.Select(textBoxArgs.Text.Length, 0);
}
@@ -33,7 +34,7 @@ namespace TweetDck.Core.Other.Settings.Dialogs{
return;
}
int count = CommandLineArgsParser.AddToDictionary(CefArgs,new Dictionary<string,string>());
int count = CommandLineArgsParser.ReadCefArguments(CefArgs).Count;
string prompt = count == 0 && !string.IsNullOrWhiteSpace(prevArgs) ? "All arguments will be removed from the settings. Continue?" : count+(count == 1 ? " argument" : " arguments")+" will be added to the settings. Continue?";
if (MessageBox.Show(prompt, "Confirm CEF Arguments", MessageBoxButtons.OKCancel, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2) == DialogResult.OK){

View File

@@ -4,13 +4,25 @@ using System.Text;
namespace TweetDck.Core.Other.Settings.Export{
class CombinedFileStream : IDisposable{
public const char KeySeparator = '|';
private readonly Stream stream;
public CombinedFileStream(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){
byte[] name = Encoding.UTF8.GetBytes(identifier);
if (name.Length > 255){
throw new ArgumentOutOfRangeException("Identifier cannot be 256 or more characters long: "+identifier);
}
byte[] contents;
using(FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)){
@@ -26,12 +38,9 @@ namespace TweetDck.Core.Other.Settings.Export{
}
}
byte[] name = Encoding.UTF8.GetBytes(identifier);
byte[] contentsLength = BitConverter.GetBytes(contents.Length);
stream.WriteByte((byte)name.Length);
stream.Write(name, 0, name.Length);
stream.Write(contentsLength,0,4);
stream.Write(BitConverter.GetBytes(contents.Length), 0, 4);
stream.Write(contents, 0, contents.Length);
}
@@ -65,6 +74,20 @@ namespace TweetDck.Core.Other.Settings.Export{
public class Entry{
public string Identifier { get; private set; }
public string KeyName{
get{
int index = Identifier.IndexOf(KeySeparator);
return index == -1 ? Identifier : Identifier.Substring(0, index);
}
}
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){
@@ -75,6 +98,15 @@ namespace TweetDck.Core.Other.Settings.Export{
public void WriteToFile(string path){
File.WriteAllBytes(path, contents);
}
public void WriteToFile(string path, bool createDirectory){
if (createDirectory){
// ReSharper disable once AssignNullToNotNullAttribute
Directory.CreateDirectory(Path.GetDirectoryName(path));
}
File.WriteAllBytes(path, contents);
}
}
}
}

View File

@@ -1,19 +1,25 @@
using System;
using System.Diagnostics;
using System.Collections.Generic;
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{
public static readonly string CookiesPath = Path.Combine(Program.StoragePath,"Cookies");
public static readonly string TempCookiesPath = Path.Combine(Program.StoragePath,"CookiesTmp");
private static readonly string CookiesPath = Path.Combine(Program.StoragePath, "Cookies");
private static readonly string TempCookiesPath = Path.Combine(Program.StoragePath, "CookiesTmp");
public bool IsRestarting { get; private set; }
public Exception LastException { get; private set; }
private readonly string file;
private readonly PluginManager plugins;
public ExportManager(string file){
public ExportManager(string file, PluginManager plugins){
this.file = file;
this.plugins = plugins;
}
public bool Export(bool includeSession){
@@ -21,6 +27,16 @@ 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(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);
}
}
}
if (includeSession){
stream.WriteFile("cookies", CookiesPath);
}
@@ -37,23 +53,33 @@ namespace TweetDck.Core.Other.Settings.Export{
public bool Import(){
try{
HashSet<string> missingPlugins = new HashSet<string>();
using(CombinedFileStream stream = new CombinedFileStream(new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.None))){
CombinedFileStream.Entry entry;
while((entry = stream.ReadFile()) != null){
switch(entry.Identifier){
switch(entry.KeyName){
case "config":
entry.WriteToFile(Program.ConfigFilePath);
Program.ReloadConfig();
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){
entry.WriteToFile(Path.Combine(Program.StoragePath,TempCookiesPath));
case "plugin.data":
string[] value = entry.KeyValue;
// okay to and restart, 'cookies' is always the last entry
Process.Start(Application.ExecutablePath,"-restart -importcookies");
Application.Exit();
entry.WriteToFile(Path.Combine(Program.PluginDataPath, value[0], value[1]), true);
if (!plugins.IsPluginInstalled(value[0])){
missingPlugins.Add(value[0]);
}
break;
case "cookies":
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));
IsRestarting = true;
}
break;
@@ -61,11 +87,48 @@ namespace TweetDck.Core.Other.Settings.Export{
}
}
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();
}
return true;
}catch(Exception e){
LastException = e;
return false;
}
}
public static void ImportCookies(){
if (File.Exists(TempCookiesPath)){
try{
if (File.Exists(CookiesPath)){
File.Delete(CookiesPath);
}
File.Move(TempCookiesPath, CookiesPath);
}catch(Exception e){
Program.Reporter.HandleException("Profile Import Error", "Could not import the cookie file to restore login session.", true, e);
}
}
}
private static IEnumerable<PathInfo> EnumerateFilesRelative(string root){
return Directory.Exists(root) ? Directory.EnumerateFiles(root, "*.*", SearchOption.AllDirectories).Select(fullPath => new PathInfo{
Full = fullPath,
Relative = fullPath.Substring(root.Length).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) // strip leading separator character
}) : Enumerable.Empty<PathInfo>();
}
private class PathInfo{
public string Full { get; set; }
public string Relative { get; set; }
}
}
}

View File

@@ -28,14 +28,19 @@
this.checkHardwareAcceleration = new System.Windows.Forms.CheckBox();
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.btnEditCSS = new System.Windows.Forms.Button();
this.groupApp = new System.Windows.Forms.GroupBox();
this.btnOpenAppFolder = new System.Windows.Forms.Button();
this.groupPerformance.SuspendLayout();
this.groupConfiguration.SuspendLayout();
this.groupApp.SuspendLayout();
this.SuspendLayout();
//
// btnClearCache
@@ -75,11 +80,46 @@
this.btnEditCefArgs.UseVisualStyleBackColor = true;
this.btnEditCefArgs.Click += new System.EventHandler(this.btnEditCefArgs_Click);
//
// btnEditCSS
//
this.btnEditCSS.Location = new System.Drawing.Point(6, 48);
this.btnEditCSS.Name = "btnEditCSS";
this.btnEditCSS.Size = new System.Drawing.Size(171, 23);
this.btnEditCSS.TabIndex = 16;
this.btnEditCSS.Text = "Edit CSS";
this.toolTip.SetToolTip(this.btnEditCSS, "Set custom CSS for browser and notification windows.");
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)));
this.btnReset.AutoSize = true;
this.btnReset.Location = new System.Drawing.Point(209, 250);
this.btnReset.Location = new System.Drawing.Point(190, 250);
this.btnReset.Name = "btnReset";
this.btnReset.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnReset.Size = new System.Drawing.Size(102, 23);
@@ -92,12 +132,12 @@
//
this.btnImport.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.btnImport.AutoSize = true;
this.btnImport.Location = new System.Drawing.Point(109, 250);
this.btnImport.Location = new System.Drawing.Point(100, 250);
this.btnImport.Name = "btnImport";
this.btnImport.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnImport.Size = new System.Drawing.Size(94, 23);
this.btnImport.Size = new System.Drawing.Size(84, 23);
this.btnImport.TabIndex = 16;
this.btnImport.Text = "Import Settings";
this.btnImport.Text = "Import Profile";
this.btnImport.UseVisualStyleBackColor = true;
this.btnImport.Click += new System.EventHandler(this.btnImport_Click);
//
@@ -105,12 +145,13 @@
//
this.btnExport.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.btnExport.AutoSize = true;
this.btnExport.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
this.btnExport.Location = new System.Drawing.Point(9, 250);
this.btnExport.Name = "btnExport";
this.btnExport.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnExport.Size = new System.Drawing.Size(94, 23);
this.btnExport.Size = new System.Drawing.Size(85, 23);
this.btnExport.TabIndex = 15;
this.btnExport.Text = "Export Settings";
this.btnExport.Text = "Export Profile";
this.btnExport.UseVisualStyleBackColor = true;
this.btnExport.Click += new System.EventHandler(this.btnExport_Click);
//
@@ -136,21 +177,34 @@
this.groupConfiguration.TabStop = false;
this.groupConfiguration.Text = "Configuration";
//
// btnEditCSS
// groupApp
//
this.btnEditCSS.Location = new System.Drawing.Point(6, 48);
this.btnEditCSS.Name = "btnEditCSS";
this.btnEditCSS.Size = new System.Drawing.Size(171, 23);
this.btnEditCSS.TabIndex = 16;
this.btnEditCSS.Text = "Edit CSS";
this.toolTip.SetToolTip(this.btnEditCSS, "Set custom CSS for browser and notification windows.");
this.btnEditCSS.UseVisualStyleBackColor = true;
this.btnEditCSS.Click += new System.EventHandler(this.btnEditCSS_Click);
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, 106);
this.groupApp.TabIndex = 20;
this.groupApp.TabStop = false;
this.groupApp.Text = "App";
//
// btnOpenAppFolder
//
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
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.groupApp);
this.Controls.Add(this.groupConfiguration);
this.Controls.Add(this.groupPerformance);
this.Controls.Add(this.btnReset);
@@ -161,6 +215,7 @@
this.groupPerformance.ResumeLayout(false);
this.groupPerformance.PerformLayout();
this.groupConfiguration.ResumeLayout(false);
this.groupApp.ResumeLayout(false);
this.ResumeLayout(false);
this.PerformLayout();
@@ -178,5 +233,9 @@
private System.Windows.Forms.GroupBox groupConfiguration;
private System.Windows.Forms.Button btnEditCefArgs;
private System.Windows.Forms.Button btnEditCSS;
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

@@ -5,15 +5,18 @@ using TweetDck.Core.Controls;
using TweetDck.Core.Other.Settings.Dialogs;
using TweetDck.Core.Other.Settings.Export;
using TweetDck.Core.Utils;
using TweetDck.Plugins;
namespace TweetDck.Core.Other.Settings{
partial class TabSettingsAdvanced : BaseTabSettings{
private readonly Action browserReloadAction;
private readonly PluginManager plugins;
public TabSettingsAdvanced(Action browserReloadAction){
public TabSettingsAdvanced(Action browserReloadAction, PluginManager plugins){
InitializeComponent();
this.browserReloadAction = browserReloadAction;
this.plugins = plugins;
checkHardwareAcceleration.Checked = HardwareAcceleration.IsEnabled;
@@ -68,13 +71,16 @@ namespace TweetDck.Core.Other.Settings{
if (form.ShowDialog(ParentForm) == DialogResult.OK){
Config.CustomCefArgs = form.CefArgs;
form.Dispose();
PromptRestart();
}
else{
form.Dispose();
}
}
private void btnEditCSS_Click(object sender, EventArgs e){
DialogSettingsCSS form = new DialogSettingsCSS();
using(DialogSettingsCSS form = new DialogSettingsCSS()){
if (form.ShowDialog(ParentForm) == DialogResult.OK){
bool hasChangedBrowser = form.BrowserCSS != Config.CustomBrowserCSS;
@@ -86,6 +92,7 @@ namespace TweetDck.Core.Other.Settings{
}
}
}
}
private void btnExport_Click(object sender, EventArgs e){
DialogResult resultSaveCredentials = MessageBox.Show("Do you want to include your login session? This will not save your password into the file, but it will allow anyone with the file to login into TweetDeck as you.", "Export "+Program.BrandName+" Settings", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question, MessageBoxDefaultButton.Button3);
@@ -109,10 +116,10 @@ namespace TweetDck.Core.Other.Settings{
if (file != null){
Program.UserConfig.Save();
ExportManager manager = new ExportManager(file);
ExportManager manager = new ExportManager(file, plugins);
if (!manager.Export(saveCredentials)){
Program.HandleException("An exception happened while exporting "+Program.BrandName+" settings.",manager.LastException);
Program.Reporter.HandleException("Profile Export Error", "An exception happened while exporting "+Program.BrandName+" settings.", true, manager.LastException);
}
}
}
@@ -130,13 +137,15 @@ namespace TweetDck.Core.Other.Settings{
}
if (file != null){
ExportManager manager = new ExportManager(file);
ExportManager manager = new ExportManager(file, plugins);
if (manager.Import()){
if (!manager.IsRestarting){
((FormSettings)ParentForm).ReloadUI();
}
}
else{
Program.HandleException("An exception happened while importing "+Program.BrandName+" settings.",manager.LastException);
Program.Reporter.HandleException("Profile Import Error", "An exception happened while importing "+Program.BrandName+" settings.", true, manager.LastException);
}
}
}
@@ -148,11 +157,16 @@ namespace TweetDck.Core.Other.Settings{
}
}
private static void PromptRestart(){
if (MessageBox.Show("The application must restart for the setting to take place. Do you want to restart now?",Program.BrandName+" Settings",MessageBoxButtons.YesNo,MessageBoxIcon.Information) == DialogResult.Yes){
Process.Start(Application.ExecutablePath,"-restart");
Application.Exit();
}
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();
}
private void btnRestartLog_Click(object sender, EventArgs e){
Program.Restart(new string[]{ "-log" });
}
}
}

View File

@@ -28,6 +28,7 @@
this.comboBoxTrayType = new System.Windows.Forms.ComboBox();
this.toolTip = new System.Windows.Forms.ToolTip(this.components);
this.checkTrayHighlight = new System.Windows.Forms.CheckBox();
this.checkSpellCheck = new System.Windows.Forms.CheckBox();
this.groupTray = new System.Windows.Forms.GroupBox();
this.labelTrayIcon = new System.Windows.Forms.Label();
this.groupInterface = new System.Windows.Forms.GroupBox();
@@ -74,12 +75,24 @@
this.checkTrayHighlight.UseVisualStyleBackColor = true;
this.checkTrayHighlight.CheckedChanged += new System.EventHandler(this.checkTrayHighlight_CheckedChanged);
//
// checkSpellCheck
//
this.checkSpellCheck.AutoSize = true;
this.checkSpellCheck.Location = new System.Drawing.Point(9, 44);
this.checkSpellCheck.Name = "checkSpellCheck";
this.checkSpellCheck.Size = new System.Drawing.Size(119, 17);
this.checkSpellCheck.TabIndex = 15;
this.checkSpellCheck.Text = "Enable Spell Check";
this.toolTip.SetToolTip(this.checkSpellCheck, "Underlines words that are spelled incorrectly.");
this.checkSpellCheck.UseVisualStyleBackColor = true;
this.checkSpellCheck.CheckedChanged += new System.EventHandler(this.checkSpellCheck_CheckedChanged);
//
// groupTray
//
this.groupTray.Controls.Add(this.checkTrayHighlight);
this.groupTray.Controls.Add(this.labelTrayIcon);
this.groupTray.Controls.Add(this.comboBoxTrayType);
this.groupTray.Location = new System.Drawing.Point(9, 63);
this.groupTray.Location = new System.Drawing.Point(9, 86);
this.groupTray.Name = "groupTray";
this.groupTray.Size = new System.Drawing.Size(183, 93);
this.groupTray.TabIndex = 15;
@@ -98,10 +111,11 @@
//
// groupInterface
//
this.groupInterface.Controls.Add(this.checkSpellCheck);
this.groupInterface.Controls.Add(this.checkExpandLinks);
this.groupInterface.Location = new System.Drawing.Point(9, 9);
this.groupInterface.Name = "groupInterface";
this.groupInterface.Size = new System.Drawing.Size(183, 48);
this.groupInterface.Size = new System.Drawing.Size(183, 71);
this.groupInterface.TabIndex = 16;
this.groupInterface.TabStop = false;
this.groupInterface.Text = "User Interface";
@@ -131,5 +145,6 @@
private System.Windows.Forms.GroupBox groupInterface;
private System.Windows.Forms.Label labelTrayIcon;
private System.Windows.Forms.CheckBox checkTrayHighlight;
private System.Windows.Forms.CheckBox checkSpellCheck;
}
}

View File

@@ -12,8 +12,9 @@ namespace TweetDck.Core.Other.Settings{
comboBoxTrayType.Items.Add("Combined");
comboBoxTrayType.SelectedIndex = Math.Min(Math.Max((int)Config.TrayBehavior, 0), comboBoxTrayType.Items.Count-1);
checkExpandLinks.Checked = Program.UserConfig.ExpandLinksOnHover;
checkTrayHighlight.Checked = Program.UserConfig.EnableTrayHighlight;
checkExpandLinks.Checked = Config.ExpandLinksOnHover;
checkSpellCheck.Checked = Config.EnableSpellCheck;
checkTrayHighlight.Checked = Config.EnableTrayHighlight;
}
private void checkExpandLinks_CheckedChanged(object sender, EventArgs e){
@@ -22,6 +23,13 @@ namespace TweetDck.Core.Other.Settings{
Config.ExpandLinksOnHover = checkExpandLinks.Checked;
}
private void checkSpellCheck_CheckedChanged(object sender, EventArgs e){
if (!Ready)return;
Config.EnableSpellCheck = checkSpellCheck.Checked;
PromptRestart();
}
private void comboBoxTrayType_SelectedIndexChanged(object sender, EventArgs e){
if (!Ready)return;

View File

@@ -8,14 +8,11 @@ namespace TweetDck.Core.Utils{
static class BrowserCache{
private static bool ClearOnExit { get; set; }
private static readonly string IndexFile = Path.Combine(Program.StoragePath,"index");
private static readonly string CacheFolder = Path.Combine(Program.StoragePath, "Cache");
private static IEnumerable<string> CacheFiles{
get{
return Directory.EnumerateFiles(Program.StoragePath).Where(path => {
string file = Path.GetFileName(path);
return file != null && (file.StartsWith("data_",StringComparison.Ordinal) || file.StartsWith("f_",StringComparison.Ordinal));
}).Concat(new[]{ IndexFile });
return Directory.EnumerateFiles(CacheFolder);
}
}
@@ -34,6 +31,21 @@ namespace TweetDck.Core.Utils{
task.Start();
}
public static void ClearOldCacheFiles(){
if (!Directory.Exists(CacheFolder)){
foreach(string file in Directory.EnumerateFiles(Program.StoragePath).Where(path => {
string file = Path.GetFileName(path);
return file != null && (file.StartsWith("data_", StringComparison.Ordinal) || file.StartsWith("f_", StringComparison.Ordinal));
}).Concat(new[]{ Path.Combine(Program.StoragePath, "index") })){
try{
File.Delete(file);
}catch{
// welp, too bad
}
}
}
}
public static void SetClearOnExit(){
ClearOnExit = true;
}

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

@@ -0,0 +1,111 @@
using System.Collections.Generic;
using System.Text;
namespace TweetDck.Core.Utils{
class CommandLineArgs{
public static CommandLineArgs FromStringArray(char entryChar, string[] array){
CommandLineArgs args = new CommandLineArgs();
ReadStringArray(entryChar, array, args);
return args;
}
public static void ReadStringArray(char entryChar, string[] array, CommandLineArgs targetArgs){
for(int index = 0; index < array.Length; index++){
string entry = array[index];
if (entry.Length > 0 && entry[0] == entryChar){
if (index < array.Length-1){
string potentialValue = array[index+1];
if (potentialValue.Length > 0 && potentialValue[0] == entryChar){
targetArgs.AddFlag(entry);
}
else{
targetArgs.SetValue(entry, potentialValue);
++index;
}
}
else{
targetArgs.AddFlag(entry);
}
}
}
}
private readonly HashSet<string> flags = new HashSet<string>();
private readonly Dictionary<string, string> values = new Dictionary<string, string>();
public int Count{
get{
return flags.Count+values.Count;
}
}
public void AddFlag(string flag){
flags.Add(flag.ToLowerInvariant());
}
public bool HasFlag(string flag){
return flags.Contains(flag.ToLowerInvariant());
}
public void RemoveFlag(string flag){
flags.Remove(flag.ToLowerInvariant());
}
public void SetValue(string key, string value){
values[key.ToLowerInvariant()] = value;
}
public bool HasValue(string key){
return values.ContainsKey(key.ToLowerInvariant());
}
public string GetValue(string key, string defaultValue){
string val;
return values.TryGetValue(key.ToLowerInvariant(), out val) ? val : defaultValue;
}
public void RemoveValue(string key){
values.Remove(key.ToLowerInvariant());
}
public CommandLineArgs Clone(){
CommandLineArgs copy = new CommandLineArgs();
foreach(string flag in flags){
copy.AddFlag(flag);
}
foreach(KeyValuePair<string, string> kvp in values){
copy.SetValue(kvp.Key, kvp.Value);
}
return copy;
}
public void ToDictionary(IDictionary<string, string> target){
foreach(string flag in flags){
target[flag] = "1";
}
foreach(KeyValuePair<string, string> kvp in values){
target[kvp.Key] = kvp.Value;
}
}
public override string ToString(){
StringBuilder build = new StringBuilder();
foreach(string flag in flags){
build.Append(flag).Append(' ');
}
foreach(KeyValuePair<string, string> kvp in values){
build.Append(kvp.Key).Append(" \"").Append(kvp.Value).Append("\" ");
}
return build.Remove(build.Length-1, 1).ToString();
}
}
}

View File

@@ -1,5 +1,4 @@
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Text.RegularExpressions;
namespace TweetDck.Core.Utils{
static class CommandLineArgsParser{
@@ -7,18 +6,18 @@ namespace TweetDck.Core.Utils{
private static Regex SplitRegex{
get{
return splitRegex ?? (splitRegex = new Regex(@"([^=\s]+(?:=(?:""[^""]*?""|[^ ]*))?)",RegexOptions.Compiled));
return splitRegex ?? (splitRegex = new Regex(@"([^=\s]+(?:=(?:[^ ]*""[^""]*?""[^ ]*|[^ ]*))?)", RegexOptions.Compiled));
}
}
public static int AddToDictionary(string args, IDictionary<string,string> dictionary){
if (string.IsNullOrWhiteSpace(args)){
return 0;
public static CommandLineArgs ReadCefArguments(string argumentString){
CommandLineArgs args = new CommandLineArgs();
if (string.IsNullOrWhiteSpace(argumentString)){
return args;
}
int count = 0;
foreach(Match match in SplitRegex.Matches(args)){
foreach(Match match in SplitRegex.Matches(argumentString)){
string matchValue = match.Value;
int indexEquals = matchValue.IndexOf('=');
@@ -33,13 +32,12 @@ namespace TweetDck.Core.Utils{
value = matchValue.Substring(indexEquals+1).Trim('"');
}
if (key != string.Empty){
dictionary[key] = value;
++count;
if (key.Length != 0){
args.SetValue(key, value);
}
}
return count;
return args;
}
}
}

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;
@@ -25,9 +26,6 @@ namespace TweetDck.Core.Utils{
public delegate IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam);
[DllImport("Shell32.dll")]
public static extern int SHChangeNotify(int eventId, int flags, IntPtr item1, IntPtr item2);
[DllImport("user32.dll", EntryPoint = "SetWindowPos")]
public static extern bool SetWindowPos(int hWnd, int hWndOrder, int x, int y, int width, int height, uint flags);

View File

@@ -0,0 +1,33 @@
using System.Diagnostics;
using System.IO;
namespace TweetDck.Core.Utils{
static class WindowsUtils{
public static bool CheckFolderWritePermission(string path){
string testFile = Path.Combine(path, ".test");
try{
Directory.CreateDirectory(path);
using(File.Create(testFile)){}
File.Delete(testFile);
return true;
}catch{
return false;
}
}
public static 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

@@ -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;
}
}

View File

@@ -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();
}
}
}

View File

@@ -1,148 +0,0 @@
using TweetDck.Core.Controls;
namespace TweetDck.Migration {
partial class FormMigrationQuestion {
/// <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.btnIgnore = new System.Windows.Forms.Button();
this.panelButtons = new System.Windows.Forms.FlowLayoutPanel();
this.btnAskLater = new System.Windows.Forms.Button();
this.btnMigrate = new System.Windows.Forms.Button();
this.btnMigrateUninstall = new System.Windows.Forms.Button();
this.labelQuestion = new System.Windows.Forms.Label();
this.panelButtons.SuspendLayout();
this.SuspendLayout();
//
// btnIgnore
//
this.btnIgnore.AutoSize = true;
this.btnIgnore.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
this.btnIgnore.Location = new System.Drawing.Point(391, 0);
this.btnIgnore.Margin = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnIgnore.Name = "btnIgnore";
this.btnIgnore.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnIgnore.Size = new System.Drawing.Size(53, 23);
this.btnIgnore.TabIndex = 1;
this.btnIgnore.Text = "Ignore";
this.btnIgnore.UseVisualStyleBackColor = true;
this.btnIgnore.Click += new System.EventHandler(this.btnIgnore_Click);
//
// panelButtons
//
this.panelButtons.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.panelButtons.Controls.Add(this.btnAskLater);
this.panelButtons.Controls.Add(this.btnIgnore);
this.panelButtons.Controls.Add(this.btnMigrate);
this.panelButtons.Controls.Add(this.btnMigrateUninstall);
this.panelButtons.FlowDirection = System.Windows.Forms.FlowDirection.RightToLeft;
this.panelButtons.Location = new System.Drawing.Point(12, 67);
this.panelButtons.Name = "panelButtons";
this.panelButtons.Size = new System.Drawing.Size(518, 23);
this.panelButtons.TabIndex = 0;
//
// btnAskLater
//
this.btnAskLater.AutoSize = true;
this.btnAskLater.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
this.btnAskLater.Location = new System.Drawing.Point(450, 0);
this.btnAskLater.Margin = new System.Windows.Forms.Padding(3, 0, 0, 0);
this.btnAskLater.Name = "btnAskLater";
this.btnAskLater.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnAskLater.Size = new System.Drawing.Size(68, 23);
this.btnAskLater.TabIndex = 0;
this.btnAskLater.Text = "Ask Later";
this.btnAskLater.UseVisualStyleBackColor = true;
this.btnAskLater.Click += new System.EventHandler(this.btnAskLater_Click);
//
// btnMigrate
//
this.btnMigrate.AutoSize = true;
this.btnMigrate.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
this.btnMigrate.Location = new System.Drawing.Point(327, 0);
this.btnMigrate.Margin = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnMigrate.Name = "btnMigrate";
this.btnMigrate.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnMigrate.Size = new System.Drawing.Size(58, 23);
this.btnMigrate.TabIndex = 3;
this.btnMigrate.Text = "Migrate";
this.btnMigrate.UseVisualStyleBackColor = true;
this.btnMigrate.Click += new System.EventHandler(this.btnMigrate_Click);
//
// btnMigrateUninstall
//
this.btnMigrateUninstall.AutoSize = true;
this.btnMigrateUninstall.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
this.btnMigrateUninstall.Location = new System.Drawing.Point(223, 0);
this.btnMigrateUninstall.Margin = new System.Windows.Forms.Padding(0, 0, 3, 0);
this.btnMigrateUninstall.Name = "btnMigrateUninstall";
this.btnMigrateUninstall.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnMigrateUninstall.Size = new System.Drawing.Size(98, 23);
this.btnMigrateUninstall.TabIndex = 4;
this.btnMigrateUninstall.Text = "Migrate && Purge";
this.btnMigrateUninstall.UseVisualStyleBackColor = true;
this.btnMigrateUninstall.Click += new System.EventHandler(this.btnMigrateUninstall_Click);
//
// labelQuestion
//
this.labelQuestion.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.labelQuestion.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.labelQuestion.Location = new System.Drawing.Point(49, 9);
this.labelQuestion.Margin = new System.Windows.Forms.Padding(40, 3, 3, 3);
this.labelQuestion.Name = "labelQuestion";
this.labelQuestion.Size = new System.Drawing.Size(481, 52);
this.labelQuestion.TabIndex = 2;
//
// FormMigrationQuestion
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(542, 102);
this.Controls.Add(this.labelQuestion);
this.Controls.Add(this.panelButtons);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "FormMigrationQuestion";
this.ShowIcon = false;
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "TweetDeck Migration";
this.panelButtons.ResumeLayout(false);
this.panelButtons.PerformLayout();
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.Button btnIgnore;
private System.Windows.Forms.FlowLayoutPanel panelButtons;
private System.Windows.Forms.Button btnMigrate;
private System.Windows.Forms.Label labelQuestion;
private System.Windows.Forms.Button btnAskLater;
private System.Windows.Forms.Button btnMigrateUninstall;
}
}

View File

@@ -1,42 +0,0 @@
using System;
using System.Drawing;
using System.Windows.Forms;
namespace TweetDck.Migration{
partial class FormMigrationQuestion : Form{
public MigrationDecision Decision { get; private set; }
public FormMigrationQuestion(){
InitializeComponent();
labelQuestion.Text = "Hey there, I found some TweetDeck data! Do you want to »Migrate« it and delete the old data folder, »Ignore« the request forever, or try "+Program.BrandName+" out first?\r\nYou may also »Migrate && Purge« which uninstalls TweetDeck too!";
}
protected override void OnPaint(PaintEventArgs e){
e.Graphics.DrawIcon(SystemIcons.Question,10,10);
base.OnPaint(e);
}
private void btnMigrateUninstall_Click(object sender, EventArgs e){
Close(MigrationDecision.MigratePurge);
}
private void btnMigrate_Click(object sender, EventArgs e){
Close(MigrationDecision.Migrate);
}
private void btnIgnore_Click(object sender, EventArgs e){
Close(MigrationDecision.Ignore);
}
private void btnAskLater_Click(object sender, EventArgs e){
Close(MigrationDecision.AskLater);
}
private void Close(MigrationDecision decision){
Decision = decision;
DialogResult = DialogResult.OK;
Close();
}
}
}

View File

@@ -1,42 +0,0 @@
using System;
using System.IO;
using Shell32;
namespace TweetDck.Migration.Helpers{
sealed class LnkEditor{
private readonly ShellLinkObject obj;
public LnkEditor(string file){
try{
Shell shell = new Shell();
Folder folder = shell.NameSpace(Path.GetDirectoryName(file));
FolderItem item = folder.Items().Item(Path.GetFileName(file));
obj = item.GetLink as ShellLinkObject;
}catch(Exception){
obj = null;
}
}
public void SetComment(string newComment){
if (obj == null)return;
obj.Description = newComment;
}
public void SetPath(string newPath){
if (obj == null)return;
obj.Path = newPath;
obj.SetIconLocation(newPath,0);
}
public void SetWorkingDirectory(string newWorkingDirectory){
if (obj == null)return;
obj.WorkingDirectory = newWorkingDirectory;
}
public void Save(){
if (obj == null)return;
obj.Save();
}
}
}

View File

@@ -1,43 +0,0 @@
using System;
using System.Linq;
using Microsoft.Win32;
namespace TweetDck.Migration.Helpers{
static class ProgramRegistrySearch{
public static string FindByDisplayName(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;
}
}
}

View File

@@ -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
}
}

View File

@@ -1,209 +0,0 @@
using System;
using System.Collections.Generic;
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.Migration.Helpers;
using TweetDck.Core.Utils;
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");
public static void Run(){
if (!Program.UserConfig.IgnoreMigration && Directory.Exists(TweetDeckPath)){
MigrationDecision decision;
using(FormMigrationQuestion formQuestion = new FormMigrationQuestion()){
decision = formQuestion.ShowDialog() == DialogResult.OK ? formQuestion.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.HandleException("An unexpected exception has occurred during the migration process.",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 = ProgramRegistrySearch.FindByDisplayName("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){
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){
// update the lnk files wherever possible (desktop icons, pinned taskbar, start menu)
foreach(string location in GetLnkDirectories()){
if (string.IsNullOrEmpty(location))continue;
string linkFile = Path.Combine(location,"TweetDeck.lnk");
if (File.Exists(linkFile)){
LnkEditor lnk = new LnkEditor(linkFile);
lnk.SetPath(Application.ExecutablePath);
lnk.SetWorkingDirectory(Environment.CurrentDirectory);
lnk.SetComment(Program.BrandName+" client for Windows");
lnk.Save();
string renamed = Path.Combine(location,Program.BrandName+".lnk");
try{
if (!File.Exists(renamed)){
File.Move(linkFile,renamed);
}
else{
File.Delete(linkFile);
}
}catch{
// eh, too bad
}
}
}
NativeMethods.SHChangeNotify(0x8000000,0x1000,IntPtr.Zero,IntPtr.Zero); // refreshes desktop
// uninstall in the background
string guid = ProgramRegistrySearch.FindByDisplayName("TweetDeck");
if (guid != null){
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 IEnumerable<string> GetLnkDirectories(){
yield return Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory);
yield return Environment.GetFolderPath(Environment.SpecialFolder.CommonDesktopDirectory);
yield return Environment.ExpandEnvironmentVariables(@"%APPDATA%\Microsoft\Internet Explorer\Quick Launch\User Pinned\TaskBar");
}
private 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();
}
}
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
}
}
}
}

View File

@@ -31,6 +31,7 @@
this.flowLayoutInfo = new System.Windows.Forms.FlowLayoutPanel();
this.labelWebsite = new System.Windows.Forms.Label();
this.labelVersion = new System.Windows.Forms.Label();
this.btnOpenConfig = new System.Windows.Forms.Button();
this.panelDescription.SuspendLayout();
this.flowLayoutInfo.SuspendLayout();
this.SuspendLayout();
@@ -38,9 +39,9 @@
// btnToggleState
//
this.btnToggleState.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.btnToggleState.Location = new System.Drawing.Point(449, 80);
this.btnToggleState.Location = new System.Drawing.Point(459, 80);
this.btnToggleState.Name = "btnToggleState";
this.btnToggleState.Size = new System.Drawing.Size(75, 23);
this.btnToggleState.Size = new System.Drawing.Size(65, 23);
this.btnToggleState.TabIndex = 0;
this.btnToggleState.Text = "Disable";
this.btnToggleState.UseVisualStyleBackColor = true;
@@ -99,7 +100,7 @@
this.flowLayoutInfo.Controls.Add(this.labelWebsite);
this.flowLayoutInfo.Location = new System.Drawing.Point(11, 85);
this.flowLayoutInfo.Name = "flowLayoutInfo";
this.flowLayoutInfo.Size = new System.Drawing.Size(432, 18);
this.flowLayoutInfo.Size = new System.Drawing.Size(368, 18);
this.flowLayoutInfo.TabIndex = 4;
this.flowLayoutInfo.WrapContents = false;
//
@@ -128,10 +129,22 @@
this.labelVersion.Text = "Version";
this.labelVersion.TextAlign = System.Drawing.ContentAlignment.TopRight;
//
// btnOpenConfig
//
this.btnOpenConfig.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.btnOpenConfig.Location = new System.Drawing.Point(385, 80);
this.btnOpenConfig.Name = "btnOpenConfig";
this.btnOpenConfig.Size = new System.Drawing.Size(68, 23);
this.btnOpenConfig.TabIndex = 6;
this.btnOpenConfig.Text = "Configure";
this.btnOpenConfig.UseVisualStyleBackColor = true;
this.btnOpenConfig.Click += new System.EventHandler(this.btnOpenConfig_Click);
//
// PluginControl
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.btnOpenConfig);
this.Controls.Add(this.flowLayoutInfo);
this.Controls.Add(this.panelDescription);
this.Controls.Add(this.labelName);
@@ -161,5 +174,6 @@
private System.Windows.Forms.FlowLayoutPanel flowLayoutInfo;
private System.Windows.Forms.Label labelWebsite;
private System.Windows.Forms.Label labelVersion;
private System.Windows.Forms.Button btnOpenConfig;
}
}

View File

@@ -1,5 +1,7 @@
using System;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Windows.Forms;
using TweetDck.Core.Utils;
@@ -21,13 +23,10 @@ namespace TweetDck.Plugins.Controls{
this.labelVersion.Text = plugin.Version;
this.labelAuthor.Text = plugin.Author;
this.labelWebsite.Text = plugin.Website;
this.btnToggleState.Text = pluginManager.Config.IsEnabled(plugin) ? "Disable" : "Enable";
if (!plugin.CanRun){
this.labelName.ForeColor = Color.DarkRed;
this.btnToggleState.Enabled = false;
}
else if (labelDescription.Text == string.Empty){
UpdatePluginState();
if (labelDescription.Text.Length == 0){
labelDescription.Visible = false;
}
@@ -35,7 +34,7 @@ namespace TweetDck.Plugins.Controls{
}
private void panelDescription_Resize(object sender, EventArgs e){
if (labelDescription.Text == string.Empty){
if (labelDescription.Text.Length == 0){
Height = MinimumSize.Height;
}
else{
@@ -50,11 +49,38 @@ namespace TweetDck.Plugins.Controls{
}
}
private void btnOpenConfig_Click(object sender, EventArgs e){
using(Process.Start("explorer.exe", "/select,\""+plugin.ConfigPath.Replace('/', '\\')+"\"")){}
}
private void btnToggleState_Click(object sender, EventArgs e){
bool newState = !pluginManager.Config.IsEnabled(plugin);
pluginManager.Config.SetEnabled(plugin, newState);
btnToggleState.Text = newState ? "Disable" : "Enable";
UpdatePluginState();
}
private void UpdatePluginState(){
bool isEnabled = plugin.CanRun && pluginManager.Config.IsEnabled(plugin);
Color textColor = isEnabled ? Color.Black : Color.FromArgb(90, 90, 90);
labelVersion.ForeColor = textColor;
labelAuthor.ForeColor = textColor;
labelWebsite.ForeColor = isEnabled ? Color.Blue : Color.FromArgb(90, 90, 249);
if (plugin.CanRun){
labelName.ForeColor = textColor;
labelDescription.ForeColor = textColor;
btnToggleState.Text = isEnabled ? "Disable" : "Enable";
btnOpenConfig.Visible = plugin.HasConfig;
btnOpenConfig.Enabled = btnOpenConfig.Visible && File.Exists(plugin.ConfigPath);
}
else{
labelName.ForeColor = Color.DarkRed;
labelDescription.ForeColor = Color.DarkRed;
btnToggleState.Visible = false;
btnOpenConfig.Visible = false;
}
}
}
}

View File

@@ -3,6 +3,11 @@ using TweetDck.Core.Utils;
namespace TweetDck.Plugins.Controls{
sealed partial class PluginListFlowLayout : FlowLayoutPanel{
public PluginListFlowLayout(){
FlowDirection = FlowDirection.TopDown;
WrapContents = false;
}
protected override void WndProc(ref Message m){
NativeMethods.ShowScrollBar(Handle, NativeMethods.SB_HORZ, false); // basically fuck the horizontal scrollbar very much
base.WndProc(ref m);

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{
@@ -12,6 +13,8 @@ namespace TweetDck.Plugins{
public string Author { get { return metadata["AUTHOR"]; } }
public string Version { get { return metadata["VERSION"]; } }
public string Website { get { return metadata["WEBSITE"]; } }
public string ConfigFile { get { return metadata["CONFIGFILE"]; } }
public string ConfigDefault { get { return metadata["CONFIGDEFAULT"]; } }
public string RequiredVersion { get { return metadata["REQUIRES"]; } }
public PluginGroup Group { get; private set; }
public PluginEnvironment Environments { get; private set; }
@@ -22,13 +25,32 @@ namespace TweetDck.Plugins{
}
}
public string FolderPath{
public bool HasConfig{
get{
return path;
return ConfigFile.Length > 0 && GetFullPathIfSafe(PluginFolder.Data, ConfigFile).Length > 0;
}
}
private readonly string path;
public string ConfigPath{
get{
return HasConfig ? Path.Combine(GetPluginFolder(PluginFolder.Data), ConfigFile) : string.Empty;
}
}
public bool HasDefaultConfig{
get{
return ConfigDefault.Length > 0 && GetFullPathIfSafe(PluginFolder.Root, ConfigDefault).Length > 0;
}
}
public string DefaultConfigPath{
get{
return HasDefaultConfig ? Path.Combine(GetPluginFolder(PluginFolder.Root), ConfigDefault) : string.Empty;
}
}
private readonly string pathRoot;
private readonly string pathData;
private readonly string identifier;
private readonly Dictionary<string, string> metadata = new Dictionary<string, string>(4){
{ "NAME", "" },
@@ -36,28 +58,97 @@ namespace TweetDck.Plugins{
{ "AUTHOR", "(anonymous)" },
{ "VERSION", "(unknown)" },
{ "WEBSITE", "" },
{ "CONFIGFILE", "" },
{ "CONFIGDEFAULT", "" },
{ "REQUIRES", "*" }
};
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;
}
private void OnMetadataLoaded(){
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);
}
}
}
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 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(rootFolder).FullName;
DirectoryInfo currentInfo = new DirectoryInfo(fullPath);
while(currentInfo.Parent != null){
if (currentInfo.Parent.FullName == folderPathName){
return fullPath;
}
currentInfo = currentInfo.Parent;
}
}
catch{
// ignore
}
return string.Empty;
}
public override string ToString(){
return Identifier;
}
@@ -68,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){
@@ -88,15 +179,7 @@ namespace TweetDck.Plugins{
private static bool LoadEnvironments(string path, Plugin plugin, out string error){
foreach(string file in Directory.EnumerateFiles(path, "*.js", SearchOption.TopDirectoryOnly).Select(Path.GetFileName)){
PluginEnvironment environment = PluginEnvironmentExtensions.Values.FirstOrDefault(env => file.Equals(env.GetScriptFile(),StringComparison.Ordinal));
if (environment != PluginEnvironment.None){
plugin.Environments |= environment;
}
else{
error = "Unknown script file: "+file;
return false;
}
plugin.Environments |= PluginEnvironmentExtensions.Values.FirstOrDefault(env => file.Equals(env.GetScriptFile(), StringComparison.Ordinal));
}
if (plugin.Environments == PluginEnvironment.None){
@@ -160,6 +243,8 @@ namespace TweetDck.Plugins{
return false;
}
plugin.OnMetadataLoaded();
error = string.Empty;
return true;
}

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,40 +19,42 @@ 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);
string fullPath = plugin == null ? string.Empty : plugin.GetFullPathIfSafe(folder, path);
if (plugin == null){
return string.Empty;
if (fullPath.Length == 0){
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.");
}
string fullPath = Path.Combine(plugin.FolderPath,path);
try{
string folderPathName = new DirectoryInfo(plugin.FolderPath).FullName;
DirectoryInfo currentInfo = new DirectoryInfo(fullPath);
while(currentInfo.Parent != null){
if (currentInfo.Parent.FullName == folderPathName){
}
else{
return fullPath;
}
currentInfo = currentInfo.Parent;
}
}
catch{
// ignore
}
return string.Empty;
private string ReadFileUnsafe(string fullPath, bool readCached){
string cachedContents;
if (readCached && fileCache.TryGetValue(fullPath, out cachedContents)){
return cachedContents;
}
try{
return fileCache[fullPath] = File.ReadAllText(fullPath, Encoding.UTF8);
}catch(FileNotFoundException){
throw new Exception("File not found.");
}catch(DirectoryNotFoundException){
throw new Exception("Directory not found.");
}
}
// Public methods
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.");
}
string fullPath = GetFullPathOrThrow(token, PluginFolder.Data, path);
// ReSharper disable once AssignNullToNotNullAttribute
Directory.CreateDirectory(Path.GetDirectoryName(fullPath));
@@ -61,30 +64,26 @@ namespace TweetDck.Plugins{
}
public string ReadFile(int token, string path, bool cache){
string fullPath = GetFullPathIfSafe(token,path);
if (fullPath == string.Empty){
throw new Exception("File path has to be relative to the plugin folder.");
}
string cachedContents;
if (cache && fileCache.TryGetValue(fullPath,out cachedContents)){
return cachedContents;
}
return fileCache[fullPath] = File.ReadAllText(fullPath,Encoding.UTF8);
return ReadFileUnsafe(GetFullPathOrThrow(token, PluginFolder.Data, path), cache);
}
public void DeleteFile(int token, string path){
string fullPath = GetFullPathIfSafe(token,path);
if (fullPath == string.Empty){
throw new Exception("File path has to be relative to the plugin folder.");
}
string fullPath = GetFullPathOrThrow(token, PluginFolder.Data, path);
fileCache.Remove(fullPath);
File.Delete(fullPath);
}
public bool CheckFileExists(int token, string path){
return File.Exists(GetFullPathOrThrow(token, PluginFolder.Data, path));
}
public string ReadFileRoot(int token, string path){
return ReadFileUnsafe(GetFullPathOrThrow(token, PluginFolder.Root, path), true);
}
public bool CheckFileExistsRoot(int token, string path){
return File.Exists(GetFullPathOrThrow(token, PluginFolder.Root, path));
}
}
}

View File

@@ -4,9 +4,9 @@ using TweetDck.Plugins.Events;
namespace TweetDck.Plugins{
[Serializable]
class PluginConfig{
sealed class PluginConfig{
[field:NonSerialized]
public event EventHandler<PluginChangedStateEventArgs> PluginChangedState;
public event EventHandler<PluginChangedStateEventArgs> InternalPluginChangedState; // should only be accessed from PluginManager
public IEnumerable<string> DisabledPlugins{
get{
@@ -24,8 +24,8 @@ namespace TweetDck.Plugins{
public void SetEnabled(Plugin plugin, bool enabled){
if ((enabled && Disabled.Remove(plugin.Identifier)) || (!enabled && Disabled.Add(plugin.Identifier))){
if (PluginChangedState != null){
PluginChangedState(this,new PluginChangedStateEventArgs(plugin,enabled));
if (InternalPluginChangedState != null){
InternalPluginChangedState(this, new PluginChangedStateEventArgs(plugin, enabled));
}
}
}
@@ -33,5 +33,9 @@ namespace TweetDck.Plugins{
public bool IsEnabled(Plugin plugin){
return !Disabled.Contains(plugin.Identifier) && plugin.CanRun;
}
public void DisableOfficialFromConfig(string pluginName){
Disabled.Add("official/"+pluginName);
}
}
}

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;
@@ -10,6 +11,7 @@ namespace TweetDck.Plugins{
class PluginManager{
public const string PluginBrowserScriptFile = "plugins.browser.js";
public const string PluginNotificationScriptFile = "plugins.notification.js";
public const string PluginGlobalScriptFile = "plugins.js";
public string PathOfficialPlugins { get { return Path.Combine(rootPath, "official"); } }
public string PathCustomPlugins { get { return Path.Combine(rootPath, "user"); } }
@@ -19,6 +21,7 @@ namespace TweetDck.Plugins{
public PluginBridge Bridge { get; private set; }
public event EventHandler<PluginLoadEventArgs> Reloaded;
public event EventHandler<PluginChangedStateEventArgs> PluginChangedState;
private readonly string rootPath;
private readonly HashSet<Plugin> plugins = new HashSet<Plugin>();
@@ -29,10 +32,25 @@ namespace TweetDck.Plugins{
public PluginManager(string path, PluginConfig config){
this.rootPath = path;
this.Config = config;
this.SetConfig(config);
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){
return plugins.Where(plugin => plugin.Group == group);
}
@@ -51,7 +69,6 @@ namespace TweetDck.Plugins{
}
public void Reload(){
HashSet<Plugin> prevPlugins = new HashSet<Plugin>(plugins);
plugins.Clear();
tokens.Clear();
@@ -65,7 +82,7 @@ namespace TweetDck.Plugins{
plugins.Add(plugin);
}
if (Reloaded != null && (loadErrors.Count > 0 || !prevPlugins.SetEquals(plugins))){
if (Reloaded != null){
Reloaded(this, new PluginLoadEventArgs(loadErrors));
}
}
@@ -103,6 +120,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{
@@ -7,7 +8,7 @@ namespace TweetDck.Plugins{
}
public static string GeneratePlugin(string pluginIdentifier, string pluginContents, int pluginToken, PluginEnvironment environment){
StringBuilder build = new StringBuilder(pluginIdentifier.Length+pluginContents.Length+150);
StringBuilder build = new StringBuilder(2*pluginIdentifier.Length+pluginContents.Length+165);
build.Append("(function(").Append(environment.GetScriptVariables()).Append("){");
@@ -16,6 +17,7 @@ namespace TweetDck.Plugins{
build.Append("obj:new class extends PluginBase{").Append(pluginContents).Append("}");
build.Append("};");
build.Append("tmp.obj.$id=\"").Append(pluginIdentifier).Append("\";");
build.Append("tmp.obj.$token=").Append(pluginToken).Append(";");
build.Append("window.TD_PLUGINS.install(tmp);");

View File

@@ -1,42 +1,44 @@
using System;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Text;
using System.Windows.Forms;
using CefSharp;
using TweetDck.Configuration;
using TweetDck.Core;
using TweetDck.Migration;
using TweetDck.Core.Utils;
using System.Linq;
using System.Threading;
using TweetDck.Plugins;
using TweetDck.Plugins.Events;
using TweetDck.Core.Other.Settings.Export;
using TweetDck.Core.Handling;
using TweetDck.Core.Other;
using TweetDck.Updates;
[assembly: CLSCompliant(true)]
namespace TweetDck{
static class Program{
#if DUCK
public const string BrandName = "TweetDuck";
public const string Website = "http://tweetduck.chylex.com";
#else
public const string BrandName = "TweetDick";
public const string Website = "http://tweetdick.chylex.com";
#endif
public const string Website = "https://tweetduck.chylex.com";
public const string BrowserSubprocess = BrandName+".Browser.exe";
public const string VersionTag = "1.3.2";
public const string VersionFull = "1.3.2.0";
public const string VersionTag = "1.5.1";
public const string VersionFull = "1.5.1.0";
public static readonly Version Version = new Version(VersionTag);
public static readonly string StoragePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),BrandName);
public static readonly string PluginPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory,"plugins");
public static readonly string TemporaryPath = Path.Combine(Path.GetTempPath(),BrandName);
public static readonly bool IsPortable = File.Exists("makeportable");
private static readonly CommandLineArgs Args = CommandLineArgs.FromStringArray('-', Environment.GetCommandLineArgs());
public static readonly string ProgramPath = AppDomain.CurrentDomain.BaseDirectory;
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");
public static readonly string ScriptPath = Path.Combine(ProgramPath, "scripts");
public static readonly string PluginPath = Path.Combine(ProgramPath, "plugins");
public static uint WindowRestoreMessage;
@@ -44,12 +46,7 @@ namespace TweetDck{
private static bool HasCleanedUp;
public static UserConfig UserConfig { get; private set; }
public static string LogFile{
get{
return Path.Combine(AppDomain.CurrentDomain.BaseDirectory,"td-log.txt");
}
}
public static Reporter Reporter { get; private set; }
[STAThread]
private static void Main(){
@@ -58,30 +55,55 @@ namespace TweetDck{
WindowRestoreMessage = NativeMethods.RegisterWindowMessage("TweetDuckRestore");
string[] programArguments = Environment.GetCommandLineArgs();
if (!WindowsUtils.CheckFolderWritePermission(StoragePath)){
MessageBox.Show(BrandName+" does not have write permissions to the storage folder: "+StoragePath, "Permission Error", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
if (programArguments.Contains("-restart")){
Reporter = new Reporter(LogFilePath);
Reporter.SetupUnhandledExceptionHandler(BrandName+" Has Failed :(");
if (Args.HasFlag("-restart")){
for(int attempt = 0; attempt < 21; attempt++){
if (LockManager.Lock()){
LockManager.Result lockResult = LockManager.Lock();
if (lockResult == LockManager.Result.Success){
break;
}
else if (attempt == 20){
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);
else if (lockResult == LockManager.Result.Fail){
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;
}
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{
Thread.Sleep(500);
}
}
}
else{
if (!LockManager.Lock()){
LockManager.Result lockResult = LockManager.Lock();
if (lockResult == LockManager.Result.HasProcess){
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);
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){
if (!LockManager.CloseLockingProcess(10000)){
if (!LockManager.CloseLockingProcess(30000)){
MessageBox.Show("Could not close the other process.", BrandName+" Has Failed :(", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
@@ -90,68 +112,61 @@ namespace TweetDck{
}
else return;
}
}
if (programArguments.Contains("-importcookies") && File.Exists(ExportManager.TempCookiesPath)){
try{
if (File.Exists(ExportManager.CookiesPath)){
File.Delete(ExportManager.CookiesPath);
}
File.Move(ExportManager.TempCookiesPath,ExportManager.CookiesPath);
}catch(Exception e){
HandleException("Could not import the cookie file to restore login session.",e);
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();
MigrationManager.Run();
Cef.OnContextInitialized = () => {
using(IRequestContext ctx = Cef.GetGlobalRequestContext()){
string err;
ctx.SetPreference("browser.enable_spellchecking",false,out err);
if (Args.HasFlag("-importcookies")){
ExportManager.ImportCookies();
}
};
BrowserCache.ClearOldCacheFiles();
CefSettings settings = new CefSettings{
AcceptLanguageList = BrowserUtils.HeaderAcceptLanguage,
UserAgent = BrowserUtils.HeaderUserAgent,
Locale = CultureInfo.CurrentCulture.TwoLetterISOLanguageName,
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 = programArguments.Contains("-log") ? LogSeverity.Info : LogSeverity.Disable
BrowserSubprocessPath = BrandName+".Browser.exe",
LogSeverity = Args.HasFlag("-log") ? LogSeverity.Info : LogSeverity.Disable
#endif
};
CommandLineArgsParser.AddToDictionary(UserConfig.CustomCefArgs,settings.CefCommandLineArgs);
CommandLineArgsParser.ReadCefArguments(UserConfig.CustomCefArgs).ToDictionary(settings.CefCommandLineArgs);
Cef.Initialize(settings);
AppDomain.CurrentDomain.UnhandledException += (sender, args) => {
Exception ex = args.ExceptionObject as Exception;
if (ex != null){
HandleException("An unhandled exception has occurred.",ex);
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();
PluginManager plugins = new PluginManager(PluginPath, UserConfig.Plugins);
plugins.Reloaded += plugins_Reloaded;
plugins.Config.PluginChangedState += (sender, args) => UserConfig.Save();
plugins.PluginChangedState += (sender, args) => UserConfig.Save();
plugins.Reload();
FormBrowser mainForm = new FormBrowser(plugins);
FormBrowser mainForm = new FormBrowser(plugins, new UpdaterSettings{
AllowPreReleases = Args.HasFlag("-debugupdates")
});
Application.Run(mainForm);
if (mainForm.UpdateInstallerPath != null){
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();
}
}
@@ -160,30 +175,25 @@ namespace TweetDck{
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);
}
((PluginManager)sender).SetConfig(UserConfig.Plugins);
}
public static void HandleException(string message, Exception e){
Log(e.ToString());
private static string GetDataStoragePath(){
string custom = Args.GetValue("-datafolder", null);
if (MessageBox.Show(message+"\r\nDo you want to open the log file to report the issue?",BrandName+" Has Failed :(",MessageBoxButtons.YesNo,MessageBoxIcon.Error,MessageBoxDefaultButton.Button2) == DialogResult.Yes){
Process.Start(LogFile);
if (custom != null && (custom.Contains(Path.DirectorySeparatorChar) || custom.Contains(Path.AltDirectorySeparatorChar))){
if (Path.GetInvalidPathChars().Any(custom.Contains)){
Reporter.HandleEarlyFailure("Data Folder Invalid", "The data folder contains invalid characters:\n"+custom);
}
else if (!Path.IsPathRooted(custom)){
Reporter.HandleEarlyFailure("Data Folder Invalid", "The data folder has to be either a simple folder name, or a full path:\n"+custom);
}
public static void Log(string data){
StringBuilder build = new StringBuilder();
if (!File.Exists(LogFile)){
build.Append("Please, report all issues to: https://github.com/chylex/TweetDuck/issues\r\n\r\n");
return Environment.ExpandEnvironmentVariables(custom);
}
build.Append("[").Append(DateTime.Now.ToString("G")).Append("]\r\n");
build.Append(data).Append("\r\n\r\n");
try{
File.AppendAllText(LogFile,build.ToString(),Encoding.UTF8);
}catch{
// oops
else{
return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), custom ?? BrandName);
}
}
@@ -196,13 +206,42 @@ namespace TweetDck{
File.Delete(ConfigFilePath);
File.Delete(UserConfig.GetBackupFile(ConfigFilePath));
}catch(Exception e){
HandleException("Could not delete configuration files to reset the settings.",e);
Reporter.HandleException("Configuration Reset Error", "Could not delete configuration files to reset the settings.", true, e);
return;
}
ReloadConfig();
}
private static CommandLineArgs GetArgsClean(){
CommandLineArgs args = Args.Clone();
args.RemoveFlag("-importcookies");
return args;
}
public static void Restart(){
Restart(new string[0]);
}
public static void Restart(string[] extraArgs){
FormBrowser browserForm = Application.OpenForms.OfType<FormBrowser>().FirstOrDefault();
if (browserForm == null){
return;
}
CommandLineArgs args = GetArgsClean();
args.AddFlag("-restart");
CommandLineArgs.ReadStringArray('-', extraArgs, args);
browserForm.ForceClose();
ExitCleanup();
Process.Start(Application.ExecutablePath, args.ToString());
Application.Exit();
}
private static void ExitCleanup(){
if (HasCleanedUp)return;

View File

@@ -1,13 +1,11 @@
# Build Instructions
The program was build using Visual Studio 2013. After opening the solution, make sure you have **CefSharp.WinForms** and **Microsoft.VC120.CRT.JetBrains** included - if not, download them using NuGet. For **CefSharp**, you will need version 49 or newer currently available as a pre-release.
The program was build using Visual Studio 2013. After opening the solution, make sure you have **CefSharp.WinForms** and **Microsoft.VC120.CRT.JetBrains** included - if not, download them using NuGet. For **CefSharp**, you will need version 53 or newer currently available as a pre-release.
```
PM> Install-Package CefSharp.WinForms -Pre
PM> Install-Package CefSharp.WinForms -Pre -Version 53.0.0-pre01
PM> Install-Package Microsoft.VC120.CRT.JetBrains
```
TweetD\*ck comes in two variants - TweetDick and TweetDuck. The solution includes both configurations under the names **Release Dick** and **Release Duck**, so make sure to select the correct one, or build both using Batch Build.
After building, run **_postbuild.bat** which deletes unnecessary files that CefSharp adds after post-build events >_>
Built files are then available in **bin/x86** and/or **bin/x64**.

103
Reporter.cs Normal file
View File

@@ -0,0 +1,103 @@
using System;
using System.Diagnostics;
using System.Drawing;
using System.Globalization;
using System.IO;
using System.Text;
using System.Windows.Forms;
using TweetDck.Core.Other;
namespace TweetDck{
class Reporter{
private readonly string logFile;
public Reporter(string 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){
StringBuilder build = new StringBuilder();
if (!File.Exists(logFile)){
build.Append("Please, report all issues to: https://github.com/chylex/TweetDuck/issues\r\n\r\n");
}
build.Append("[").Append(DateTime.Now.ToString("G", CultureInfo.CurrentCulture)).Append("]\r\n");
build.Append(data).Append("\r\n\r\n");
try{
File.AppendAllText(logFile, build.ToString(), Encoding.UTF8);
return true;
}catch{
return false;
}
}
public void HandleException(string caption, string message, bool canIgnore, Exception e){
bool loggedSuccessfully = Log(e.ToString());
FormMessage form = new FormMessage(caption, message+"\r\nError: "+e.Message, canIgnore ? MessageBoxIcon.Warning : MessageBoxIcon.Error);
form.AddButton("Exit");
Button btnIgnore = form.AddButton("Ignore");
Button btnOpenLog = new Button{
Anchor = AnchorStyles.Bottom | AnchorStyles.Left,
Enabled = loggedSuccessfully,
Font = SystemFonts.MessageBoxFont,
Location = new Point(12, 12),
Margin = new Padding(0, 0, 48, 0),
Size = new Size(88, 26),
Text = "Show Error Log",
UseVisualStyleBackColor = true
};
btnOpenLog.Click += (sender, args) => {
using(Process.Start(logFile)){}
};
form.AddActionControl(btnOpenLog);
if (!canIgnore){
btnIgnore.Enabled = false;
}
if (form.ShowDialog() == DialogResult.OK){
if (form.ClickedButton == btnIgnore){
return;
}
}
try{
Process.GetCurrentProcess().Kill();
}catch{
Environment.FailFast(message, e);
}
}
public static void HandleEarlyFailure(string caption, string message){
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
FormMessage form = new FormMessage(caption, message, MessageBoxIcon.Error);
form.AddButton("Exit");
form.ShowDialog();
try{
Process.GetCurrentProcess().Kill();
}catch{
Environment.FailFast(message, new Exception(message));
}
}
}
}

View File

@@ -0,0 +1,18 @@
[name]
Clear columns
[description]
- Adds buttons and keyboard shortcuts to quickly clear columns
- Hold Shift when clicking or using a keyboard shortcut to reset the column instead
[author]
chylex
[version]
1.1
[website]
https://tweetduck.chylex.com
[requires]
1.4.1

View File

@@ -0,0 +1,128 @@
constructor(){
super({
requiresPageReload: true
});
}
enabled(){
// prepare variables and functions
var clearColumn = (columnName) => {
TD.controller.columnManager.get(columnName).clear();
TD.controller.stats.columnActionClick("clear");
};
var resetColumn = (columnName) => {
var col = TD.controller.columnManager.get(columnName);
col.model.setClearedTimestamp(0);
col.reloadTweets();
};
var forEachColumn = (func) => {
Object.keys(TD.controller.columnManager.getAll()).forEach(func);
};
var replaceMustache = (key, search, replace) => {
TD.mustaches[key] = TD.mustaches[key].replace(search, replace);
};
var wasShiftPressed = false;
var updateShiftState = (pressed) => {
if (pressed != wasShiftPressed){
wasShiftPressed = pressed;
if (pressed){
$(document).on("mousemove", this.eventKeyUp);
}
else{
$(document).off("mousemove", this.eventKeyUp);
}
$("#clear-columns-btn-all").text(pressed ? "Reset all" : "Clear all");
}
};
// prepare event handlers
this.eventClickSingle = function(e){
var name = $(this).closest(".js-column").attr("data-column");
e.shiftKey ? resetColumn(name) : clearColumn(name);
};
this.eventClickAll = function(e){
forEachColumn(e.shiftKey ? resetColumn : clearColumn);
};
this.eventKeyDown = function(e){
if (!(document.activeElement === null || document.activeElement === document.body)){
return;
}
updateShiftState(e.shiftKey);
if (e.keyCode === 46){ // 46 = delete
if (e.altKey){
forEachColumn(e.shiftKey ? resetColumn : clearColumn);
}
else{
var focusedColumn = $(".js-column.is-focused");
if (focusedColumn.length){
var name = focusedColumn.attr("data-column");
e.shiftKey ? resetColumn(name) : clearColumn(name);
}
}
}
};
this.eventKeyUp = function(e){
if (!e.shiftKey){
updateShiftState(false);
}
};
// add column buttons and keyboard shortcut info to UI
replaceMustache("column/column_header.mustache", "</header>", [
'{{^isTemporary}}',
'<a class="column-header-link" href="#" data-action="td-clearcolumns-dosingle" style="right:34px">',
'<i class="icon icon-clear-timeline"></i>',
'</a>',
'{{/isTemporary}}',
'</header>'
].join(""));
replaceMustache("keyboard_shortcut_list.mustache", "</dl> <dl", [
'<dd class="keyboard-shortcut-definition" style="white-space:nowrap">',
'<span class="text-like-keyboard-key">1</span> … <span class="text-like-keyboard-key">9</span> + <span class="text-like-keyboard-key">Del</span> Clear column 19',
'</dd><dd class="keyboard-shortcut-definition">',
'<span class="text-like-keyboard-key">Alt</span> + <span class="text-like-keyboard-key">Del</span> Clear all',
'</dd></dl><dl'
].join(""));
// load custom style
var css = window.TDPF_createCustomStyle(this);
css.insert(".column-title { margin-right: 60px !important; }");
css.insert(".column-type-message .column-title { margin-right: 115px !important; }");
css.insert(".mark-all-read-link { right: 59px !important; }");
css.insert(".open-compose-dm-link { right: 90px !important; }");
css.insert("button[data-action='clear'].btn-options-tray { display: none !important; }");
}
ready(){
// setup events
$(document).on("click", "[data-action='td-clearcolumns-dosingle']", this.eventClickSingle);
$(document).on("click", "[data-action='td-clearcolumns-doall']", this.eventClickAll);
$(document).on("keydown", this.eventKeyDown);
$(document).on("keyup", this.eventKeyUp);
// add clear all button
$("nav.app-navigator").first().append([
'<a class="link-clean cf app-nav-link padding-hl" data-title="Clear all" data-action="td-clearcolumns-doall">',
'<div class="obj-left"><i class="icon icon-large icon-clear-timeline"></i></div>',
'<div id="clear-columns-btn-all" class="nbfc padding-ts hide-condensed">Clear all</div>',
'</a></nav>'
].join(""));
}
disabled(){
// not needed, plugin reloads the page when enabled or disabled
}

View File

@@ -9,7 +9,10 @@ Revert TweetDeck design changes
chylex
[version]
1.0
1.1
[website]
http://tweetduck.chylex.com
https://tweetduck.chylex.com
[requires]
1.4.1

View File

@@ -1,15 +1,11 @@
enabled(){
// add a stylesheet to change tweet actions
var style = document.createElement("style");
style.id = "design-revert";
document.head.appendChild(style);
var sheet = style.sheet;
sheet.insertRule(".tweet-actions { float: right !important; width: auto !important; }",0);
sheet.insertRule(".tweet-action { opacity: 0; }",0);
sheet.insertRule(".is-favorite .tweet-action, .is-retweet .tweet-action { opacity: 0.5; visibility: visible !important; }",0);
sheet.insertRule(".tweet:hover .tweet-action, .is-favorite .tweet-action[rel='favorite'], .is-retweet .tweet-action[rel='retweet'] { opacity: 1; visibility: visible !important; }",0);
sheet.insertRule(".tweet-actions > li:nth-child(4) { margin-right: 2px !important; }",0);
this.css = window.TDPF_createCustomStyle(this);
this.css.insert(".tweet-actions { float: right !important; width: auto !important; }");
this.css.insert(".tweet-action { opacity: 0; }");
this.css.insert(".is-favorite .tweet-action, .is-retweet .tweet-action { opacity: 0.5; visibility: visible !important; }");
this.css.insert(".tweet:hover .tweet-action, .is-favorite .tweet-action[rel='favorite'], .is-retweet .tweet-action[rel='retweet'] { opacity: 1; visibility: visible !important; }");
this.css.insert(".tweet-actions > li:nth-child(4) { margin-right: 2px !important; }");
// revert small links around the tweet
this.prevFooterMustache = TD.mustaches["status/tweet_single_footer.mustache"];
@@ -31,7 +27,8 @@ ready(){
}
disabled(){
$("#design-revert").remove();
this.css.remove();
$(document).off("uiShowActionsMenu", this.uiShowActionsMenuEvent);
TD.mustaches["status/tweet_single_footer.mustache"] = this.prevFooterMustache;
}

View File

@@ -0,0 +1,23 @@
[name]
Custom reply account
[description]
- Allows customizing the automatically selected reply account per column
[author]
chylex
[version]
1.2
[website]
https://tweetduck.chylex.com
[configfile]
configuration.js
[configdefault]
configuration.default.js
[requires]
1.3.3

View File

@@ -0,0 +1,137 @@
enabled(){
var configuration = { defaultAccount: "#preferred" };
window.TDPF_loadConfigurationFile(this, "configuration.js", "configuration.default.js", obj => configuration = obj);
this.lastSelectedAccount = null;
this.uiComposeTweetEvent = (e, data) => {
if (data.type !== "reply" || data.popFromInline || !("element" in data)){
return;
}
var query;
if (configuration.useAdvancedSelector){
if (configuration.customSelector){
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");
return;
}
}
else{
query = configuration.defaultAccount;
if (query === ""){
query = "#preferred";
}
else if (typeof query !== "string"){
query = "#default";
}
}
if (typeof query === "undefined"){
query = "#preferred";
}
if (typeof query !== "string"){
return;
}
else if (query.length === 0){
$TD.alert("warning", "Plugin reply-account has invalid configuration: the requested account is empty");
return;
}
else if (query[0] !== '@' && query[0] !== '#'){
$TD.alert("warning", "Plugin reply-account has invalid configuration: the requested account does not begin with @ or #: "+query);
return;
}
var identifier = null;
switch(query){
case "#preferred":
identifier = TD.storage.clientController.client.getDefaultAccount();
break;
case "#last":
if (this.lastSelectedAccount === null){
return;
}
identifier = this.lastSelectedAccount;
break;
case "#default":
return;
default:
if (query[0] === '@'){
var obj = TD.storage.accountController.getAccountFromUsername(query.substring(1));
if (obj.length === 0){
$TD.alert("warning", "Plugin reply-account has invalid configuration: requested account not found: "+query);
return;
}
else{
identifier = obj[0].privateState.key;
}
}
else{
$TD.alert("warning", "Plugin reply-account has invalid configuration: unknown requested account query: "+query);
return;
}
}
data.singleFrom = data.from = [ identifier ];
};
this.onSelectedAccountChanged = () => {
var selected = $(".js-account-item.is-selected", ".js-account-list");
this.lastSelectedAccount = selected.length === 1 ? selected.attr("data-account-key") : null;
};
}
ready(){
var events = $._data(document, "events");
for(var event of [ "uiInlineComposeTweet", "uiDockedComposeTweet" ]){
$(document).on(event, this.uiComposeTweetEvent);
var handlers = events[event];
var newHandler = handlers[handlers.length-1];
for(var index = handlers.length-1; index > 0; index--){
handlers[index] = handlers[index-1];
}
handlers[0] = newHandler;
}
$(document).on("click", ".js-account-list .js-account-item", this.onSelectedAccountChanged);
}
disabled(){
$(document).off("uiInlineComposeTweet", this.uiComposeTweetEvent);
$(document).off("uiDockedComposeTweet", this.uiComposeTweetEvent);
$(document).off("click", ".js-account-list .js-account-item", this.onSelectedAccountChanged);
}

View File

@@ -0,0 +1,81 @@
{
/*
* WARNING
* -------
*
* Make sure you are editing 'configuration.js' and not the default configuration file, as the default one will be replaced with each update.
*
*/
/*
* Simple way of configuring the plugin
* ------------------------------------
*
* Set value of 'defaultAccount' to one of the following values:
*
* "#preferred" to use your preferred TweetDeck account (the one used to log into TweetDeck)
* "#last" to specify the account that was selected last time (only updates if a single account is selected)
* "#default" to fall back to default TweetDeck behavior; useful for advanced configuration below, otherwise disable the plugin instead
* "@myAccount" to specify an account name to use; has to be one of your registered account names
*
*/
defaultAccount: "#preferred",
/*
* Advanced way of configuring the plugin
* --------------------------------------
*
* This assumes a basic knowledge of JavaScript and jQuery.
*
* 1. Set value of 'useAdvancedSelector' to true
* 2. Uncomment the 'customSelector' function, and replace the example code with your desired behavior
*
* 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 '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
*
* If you want to see your current column types, run this in your browser console:
* TD.controller.columnManager.getAllOrdered().map(obj => 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).
*
*
* 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(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 (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";
}
// otherwise returns 'undefined' which falls back to 'defaultAccount' behavior
}*/
}

View File

@@ -0,0 +1,18 @@
[name]
Polls in timelines
[description]
- Adds poll result display directly into timelines
- Experimental, may be buggy or break when TweetDeck updates
[author]
chylex
[version]
1.0
[website]
https://tweetduck.chylex.com
[requires]
1.4.1

View File

@@ -0,0 +1,25 @@
constructor(){
super({
requiresPageReload: true
});
}
enabled(){
// add a stylesheet
window.TDPF_createCustomStyle(this).insert(".column-detail .timeline-poll-container { display: none }");
// setup layout injecting
var injectLayout = (mustache, onlyIfNotFound, search, replace) => {
if (TD.mustaches[mustache].indexOf(onlyIfNotFound) === -1){
TD.mustaches[mustache] = TD.mustaches[mustache].replace(search, replace);
}
};
// add poll rendering to tweets
injectLayout("status/tweet_single.mustache", "status/poll", "{{/quotedTweetMissing}} {{#translation}}", "{{/quotedTweetMissing}} <div class='timeline-poll-container'>{{>duck/tweet_single/poll}}</div> {{#translation}}");
TD.mustaches["duck/tweet_single/poll.mustache"] = '<div class="js-poll margin-tl"> {{#poll}} <ul class="margin-b--12"> {{#choices}} <li class="position-rel margin-b--8 height-3"> <div class="poll-bar pin-top height-p--100 br-1 {{#isWinner}}poll-bar--winner{{/isWinner}} {{#hasTimeLeft}}br-left{{/hasTimeLeft}} width-p--{{percentage}}"/> <div class="poll-label position-rel padding-a--4"> <span class="txt-bold txt-right inline-block width-5 padding-r--4">{{percentage}}%</span> {{{label}}} {{#isSelectedChoice}} <i class="icon icon-check txt-size-variable--11"></i> {{/isSelectedChoice}} </div> </li> {{/choices}} </ul> <span class="inline-block txt-small padding-ls txt-seamful-deep-gray"> {{{prettyCount}}} &middot; {{#hasTimeLeft}} {{{prettyTimeLeft}}} {{/hasTimeLeft}} {{^hasTimeLeft}} {{_i}}Final results{{/i}} {{/hasTimeLeft}} </span> {{/poll}} </div>';
}
disabled(){
// not needed, plugin reloads the page when enabled or disabled
}

View File

@@ -11,7 +11,7 @@ namespace TweetDck.Resources{
public static string LoadResource(string name){
try{
return File.ReadAllText(Path.Combine(AppDomain.CurrentDomain.BaseDirectory,name),Encoding.UTF8);
return File.ReadAllText(Path.Combine(Program.ScriptPath, name), Encoding.UTF8);
}catch(Exception ex){
MessageBox.Show("Unfortunately, "+Program.BrandName+" could not load the "+name+" file. The program will continue running with limited functionality.\r\n\r\n"+ex.Message, Program.BrandName+" Has Failed :(", MessageBoxButtons.OK, MessageBoxIcon.Error);
return null;

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,26 +379,54 @@
//
// Block: Support for extra mouse buttons.
//
(function(){
var tryClickSelector = function(selector, parent){
return $(selector, parent).click().length;
};
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));
}
};
window.TDGF_onMouseClickExtra = function(button){
if (button === 1){ // back button
var modal = $("#open-modal");
if (highlightedColumnEle && highlightedColumnEle.closest(".js-column").is(".is-shifted-1")){
highlightedColumnEle.find(".js-column-back").first().click();
}
else if (modal.is(":visible")){
modal.find("a[rel=dismiss]").click();
}
else{
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();
}
}
};
})();
//
// Block: Fix scheduled tweets not showing up sometimes.
//
$(document).on("dataTweetSent", function(e, data){
if (data.response.state && data.response.state === "scheduled"){
var column = Object.values(TD.controller.columnManager.getAll()).find(column => column.model.state.type === "scheduled");
if (column){
setTimeout(function(){
column.reloadTweets();
}, 1000);
}
}
});
//
// Block: Inject custom CSS and layout into the page.
@@ -407,6 +438,7 @@
styleOfficial.sheet.insertRule("a[data-full-url] { word-break: break-all; }", 0); // break long urls
styleOfficial.sheet.insertRule(".column-nav-link .attribution { position: absolute; }", 0); // fix cut off account names
styleOfficial.sheet.insertRule(".txt-base-smallest .badge-verified:before { height: 13px !important; }", 0); // fix cut off badge icon
styleOfficial.sheet.insertRule(".keyboard-shortcut-list { vertical-align: top; }", 0); // fix keyboard navigation alignment
if ($TD.hasCustomBrowserCSS){
var styleCustom = document.createElement("style");

View File

@@ -114,9 +114,39 @@
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);
})();
//
// Block: Setup a skip button.
//
(function(){
if (document.body.hasAttribute("td-example-notification")){
return;
}
document.body.insertAdjacentHTML("afterbegin", [
'<svg id="td-skip" xmlns="http://www.w3.org/2000/svg" width="10" height="17" viewBox="0 0 350 600" style="position:fixed;left:30px;bottom:10px;z-index:1000">',
'<path fill="#888" d="M0,151.656l102.208-102.22l247.777,247.775L102.208,544.986L0,442.758l145.546-145.547">',
'</svg>'
].join(""));
document.getElementById("td-skip").addEventListener("click", function(){
$TD.loadNextNotification();
});
})();
//
// Block: Setup a hover class on body.
//
document.body.addEventListener("mouseenter", function(){
document.body.classList.add("td-hover");
});
document.body.addEventListener("mouseleave", function(){
document.body.classList.remove("td-hover");
});
//
// Block: Page fully loaded.
//

View File

@@ -0,0 +1,54 @@
(function($TDP){
//
// Block: Setup a simple JavaScript object configuration loader.
//
window.TDPF_loadConfigurationFile = function(pluginObject, fileNameUser, fileNameDefault, onSuccess, onFailure){
var identifier = pluginObject.$id;
var token = pluginObject.$token;
$TDP.checkFileExists(token, fileNameUser).then(exists => {
var fileName = exists ? fileNameUser : fileNameDefault;
(exists ? $TDP.readFile(token, fileName, true) : $TDP.readFileRoot(token, fileName)).then(contents => {
var obj;
try{
obj = eval("("+contents+")");
}catch(err){
if (!(onFailure && onFailure(err))){
$TD.alert("warning", "Problem loading '"+fileName+"' file for '"+identifier+"' plugin, the JavaScript syntax is invalid: "+err.message);
}
return;
}
onSuccess && onSuccess(obj);
}).catch(err => {
if (!(onFailure && onFailure(err))){
$TD.alert("warning", "Problem loading '"+fileName+"' file for '"+identifier+"' plugin: "+err.message);
}
});
}).catch(err => {
if (!(onFailure && onFailure(err))){
$TD.alert("warning", "Problem checking '"+fileNameUser+"' file for '"+identifier+"' plugin: "+err.message);
}
});
};
//
// Block: Setup a function to add/remove custom CSS.
//
window.TDPF_createCustomStyle = function(pluginObject){
var element = document.createElement("style");
element.id = "plugin-"+pluginObject.$id+"-"+Math.random().toString(36).substring(2, 7);
document.head.appendChild(element);
var obj = {
insert: (rule) => element.sheet.insertRule(rule, 0),
remove: () => $(element).remove()
};
obj.element = element;
return obj;
};
})($TDP);

View File

@@ -12,12 +12,19 @@
//
// 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.
//
var createUpdateNotificationElement = function(version, download){
var outdated = version === "unsupported";
var ele = $("#tweetdck-update");
var existed = ele.length > 0;
@@ -25,13 +32,22 @@
ele.remove();
}
var html = [
var html = outdated ? [
"<div id='tweetdck-update'>",
"<p class='tdu-title'>Unsupported System</p>",
"<p class='tdu-info'>You will not receive updates.</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>",
"<p class='tdu-info'>Version "+version+" is now available.</p>",
"<div class='tdu-buttons'>",
"<button class='btn btn-positive tdu-btn-download'><span class='label'>Download</button>",
"<button class='btn btn-negative tdu-btn-dismiss'><span class='label'>Dismiss</button>",
"<button class='btn btn-positive tdu-btn-download'><span class='label'>Download</span></button>",
"<button class='btn btn-negative tdu-btn-dismiss'><span class='label'>Dismiss</span></button>",
"</div>",
"</div>"
];
@@ -60,7 +76,7 @@
fontWeight: "bold",
textAlign: "center",
letterSpacing: "0.2px",
margin: "4px auto 2px"
margin: "5px auto 2px"
});
ele.children("p.tdu-info").first().css({
@@ -93,7 +109,11 @@
$TDU.onUpdateAccepted(version, download);
});
buttonDiv.children(".tdu-btn-dismiss").click(function(){
buttonDiv.children(".tdu-btn-unsupported").click(function(){
$TDU.openBrowser("https://github.com/chylex/TweetDuck/wiki/Supported-Systems");
});
buttonDiv.children(".tdu-btn-dismiss,.tdu-btn-unsupported").click(function(){
$TDU.onUpdateDismissed(version);
ele.slideUp(function(){ ele.remove(); });
});
@@ -109,21 +129,35 @@
// Function: Runs an update check and updates all DOM elements appropriately.
//
var runUpdateCheck = function(force, eventID){
if (!$TDU.isSystemSupported){
if ($TDU.dismissedVersionTag !== "unsupported"){
createUpdateNotificationElement("unsupported");
}
return;
}
clearTimeout(updateCheckTimeoutID);
updateCheckTimeoutID = setTimeout(runUpdateCheck, 1000*60*60); // 1 hour
if (!$TDU.updateCheckEnabled && !force)return;
if (!$TDU.updateCheckEnabled && !force){
return;
}
$.getJSON(updateCheckUrl,function(response){
var tagName = response.tag_name;
var hasUpdate = tagName !== $TDU.versionTag && tagName !== $TDU.dismissedVersionTag && response.assets.length > 0;
var allowPre = $TDU.allowPreReleases;
$.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){
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);
}
if (eventID !== 0){
if (eventID){ // ignore undefined and 0
$TDU.onUpdateCheckFinished(eventID, hasUpdate, tagName);
}
});

View File

@@ -1,5 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="packages\CefSharp.WinForms.53.0.0-pre01\build\CefSharp.WinForms.props" Condition="Exists('packages\CefSharp.WinForms.53.0.0-pre01\build\CefSharp.WinForms.props')" />
<Import Project="packages\CefSharp.Common.53.0.0-pre01\build\CefSharp.Common.props" Condition="Exists('packages\CefSharp.Common.53.0.0-pre01\build\CefSharp.Common.props')" />
<Import Project="packages\CefSharp.WinForms.49.0.0-pre02\build\CefSharp.WinForms.props" Condition="Exists('packages\CefSharp.WinForms.49.0.0-pre02\build\CefSharp.WinForms.props')" />
<Import Project="packages\CefSharp.Common.49.0.0-pre02\build\CefSharp.Common.props" Condition="Exists('packages\CefSharp.Common.49.0.0-pre02\build\CefSharp.Common.props')" />
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
@@ -11,12 +13,12 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>TweetDck</RootNamespace>
<AssemblyName Condition=" '$(Configuration)' == 'Debug' ">TweetDick</AssemblyName>
<AssemblyName Condition=" '$(Configuration)' == 'Release Dick' ">TweetDick</AssemblyName>
<AssemblyName Condition=" '$(Configuration)' == 'Release Duck' ">TweetDuck</AssemblyName>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<AssemblyName Condition=" '$(Configuration)' == 'Release' ">TweetDuck</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<NuGetPackageImportStamp>ff8ce4f3</NuGetPackageImportStamp>
<TargetFrameworkProfile>Client</TargetFrameworkProfile>
<NuGetPackageImportStamp>e83161d1</NuGetPackageImportStamp>
<TargetFrameworkProfile>
</TargetFrameworkProfile>
<PublishUrl>publish\</PublishUrl>
<Install>true</Install>
<InstallFrom>Disk</InstallFrom>
@@ -41,24 +43,13 @@
<PlatformTarget>x86</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release Dick|x86' ">
<OutputPath>bin\x86\Release Dick\</OutputPath>
<DefineConstants>
</DefineConstants>
<Optimize>true</Optimize>
<DebugType>none</DebugType>
<PlatformTarget>x86</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
<UseVSHostingProcess>false</UseVSHostingProcess>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup>
<ApplicationIcon>Resources\icon.ico</ApplicationIcon>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release Duck|x86' ">
<OutputPath>bin\x86\Release Duck\</OutputPath>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
<OutputPath>bin\x86\Release\</OutputPath>
<Optimize>true</Optimize>
<PlatformTarget>x86</PlatformTarget>
<UseVSHostingProcess>false</UseVSHostingProcess>
@@ -66,7 +57,12 @@
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<DefineConstants>DUCK</DefineConstants>
<DefineConstants>
</DefineConstants>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup>
<AssemblyName>TweetDuck</AssemblyName>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
@@ -99,6 +95,7 @@
<Compile Include="Core\Controls\TabPanel.Designer.cs">
<DependentUpon>TabPanel.cs</DependentUpon>
</Compile>
<Compile Include="Core\Handling\BrowserProcessHandler.cs" />
<Compile Include="Core\Handling\ContextMenuBase.cs" />
<Compile Include="Core\Handling\ContextMenuBrowser.cs" />
<Compile Include="Core\FormBrowser.cs">
@@ -123,6 +120,12 @@
<Compile Include="Core\Other\FormAbout.Designer.cs">
<DependentUpon>FormAbout.cs</DependentUpon>
</Compile>
<Compile Include="Core\Other\FormMessage.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Core\Other\FormMessage.Designer.cs">
<DependentUpon>FormMessage.cs</DependentUpon>
</Compile>
<Compile Include="Core\Other\FormPlugins.cs">
<SubType>Form</SubType>
</Compile>
@@ -173,14 +176,10 @@
<Compile Include="Core\Other\Settings\TabSettingsUpdates.Designer.cs">
<DependentUpon>TabSettingsUpdates.cs</DependentUpon>
</Compile>
<Compile Include="Core\Utils\CommandLineArgs.cs" />
<Compile Include="Core\Utils\CommandLineArgsParser.cs" />
<Compile Include="Core\Utils\WindowState.cs" />
<Compile Include="Migration\FormBackgroundWork.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Migration\FormBackgroundWork.Designer.cs">
<DependentUpon>FormBackgroundWork.cs</DependentUpon>
</Compile>
<Compile Include="Core\Utils\WindowsUtils.cs" />
<Compile Include="Core\Handling\TweetDeckBridge.cs" />
<Compile Include="Core\Other\FormSettings.cs">
<SubType>Form</SubType>
@@ -200,15 +199,17 @@
<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" />
<Compile Include="Reporter.cs" />
<Compile Include="Updates\FormUpdateDownload.cs">
<SubType>Form</SubType>
</Compile>
@@ -229,16 +230,6 @@
<Compile Include="Updates\UpdateCheckEventArgs.cs" />
<Compile Include="Updates\UpdateHandler.cs" />
<Compile Include="Updates\UpdateInfo.cs" />
<Compile Include="Migration\FormMigrationQuestion.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Migration\FormMigrationQuestion.Designer.cs">
<DependentUpon>FormMigrationQuestion.cs</DependentUpon>
</Compile>
<Compile Include="Migration\Helpers\LnkEditor.cs" />
<Compile Include="Migration\MigrationDecision.cs" />
<Compile Include="Migration\MigrationManager.cs" />
<Compile Include="Migration\Helpers\ProgramRegistrySearch.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Properties\Resources.Designer.cs">
@@ -247,6 +238,7 @@
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<Compile Include="Resources\ScriptLoader.cs" />
<Compile Include="Updates\UpdaterSettings.cs" />
<None Include="Configuration\app.config" />
<None Include="Configuration\packages.config" />
</ItemGroup>
@@ -272,17 +264,6 @@
<Install>true</Install>
</BootstrapperPackage>
</ItemGroup>
<ItemGroup>
<COMReference Include="Shell32">
<Guid>{50A7E9B0-70EF-11D1-B75A-00A0C90564FE}</Guid>
<VersionMajor>1</VersionMajor>
<VersionMinor>0</VersionMinor>
<Lcid>0</Lcid>
<WrapperTool>tlbimp</WrapperTool>
<Isolated>False</Isolated>
<EmbedInteropTypes>True</EmbedInteropTypes>
</COMReference>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Core\FormBrowser.resx">
<DependentUpon>FormBrowser.cs</DependentUpon>
@@ -310,36 +291,6 @@
<TargetPath>icon.ico</TargetPath>
</ContentWithTargetPath>
</ItemGroup>
<ItemGroup>
<ContentWithTargetPath Include="Resources\Scripts\code.js">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<TargetPath>code.js</TargetPath>
</ContentWithTargetPath>
</ItemGroup>
<ItemGroup>
<ContentWithTargetPath Include="Resources\Scripts\notification.js">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<TargetPath>notification.js</TargetPath>
</ContentWithTargetPath>
</ItemGroup>
<ItemGroup>
<ContentWithTargetPath Include="Resources\Scripts\plugins.browser.js">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<TargetPath>plugins.browser.js</TargetPath>
</ContentWithTargetPath>
</ItemGroup>
<ItemGroup>
<ContentWithTargetPath Include="Resources\Scripts\plugins.notification.js">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<TargetPath>plugins.notification.js</TargetPath>
</ContentWithTargetPath>
</ItemGroup>
<ItemGroup>
<ContentWithTargetPath Include="Resources\Scripts\update.js">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<TargetPath>update.js</TargetPath>
</ContentWithTargetPath>
</ItemGroup>
<ItemGroup>
<None Include="Resources\icon-small.ico" />
<None Include="Resources\icon-tray-new.ico" />
@@ -347,21 +298,20 @@
</ItemGroup>
<ItemGroup>
<Folder Include="Resources\Plugins\" />
<Folder Include="Resources\Scripts\" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('packages\cef.redist.x86.3.2623.1396\build\cef.redist.x86.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\cef.redist.x86.3.2623.1396\build\cef.redist.x86.targets'))" />
<Error Condition="!Exists('packages\cef.redist.x64.3.2623.1396\build\cef.redist.x64.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\cef.redist.x64.3.2623.1396\build\cef.redist.x64.targets'))" />
<Error Condition="!Exists('packages\CefSharp.Common.49.0.0-pre02\build\CefSharp.Common.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.Common.49.0.0-pre02\build\CefSharp.Common.props'))" />
<Error Condition="!Exists('packages\CefSharp.Common.49.0.0-pre02\build\CefSharp.Common.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.Common.49.0.0-pre02\build\CefSharp.Common.targets'))" />
<Error Condition="!Exists('packages\CefSharp.WinForms.49.0.0-pre02\build\CefSharp.WinForms.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.WinForms.49.0.0-pre02\build\CefSharp.WinForms.props'))" />
<Error Condition="!Exists('packages\cef.redist.x86.3.2785.1478\build\cef.redist.x86.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\cef.redist.x86.3.2785.1478\build\cef.redist.x86.targets'))" />
<Error Condition="!Exists('packages\cef.redist.x64.3.2785.1478\build\cef.redist.x64.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\cef.redist.x64.3.2785.1478\build\cef.redist.x64.targets'))" />
<Error Condition="!Exists('packages\CefSharp.Common.53.0.0-pre01\build\CefSharp.Common.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.Common.53.0.0-pre01\build\CefSharp.Common.props'))" />
<Error Condition="!Exists('packages\CefSharp.Common.53.0.0-pre01\build\CefSharp.Common.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.Common.53.0.0-pre01\build\CefSharp.Common.targets'))" />
<Error Condition="!Exists('packages\CefSharp.WinForms.53.0.0-pre01\build\CefSharp.WinForms.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.WinForms.53.0.0-pre01\build\CefSharp.WinForms.props'))" />
<Error Condition="!Exists('packages\CefSharp.WinForms.53.0.0-pre01\build\CefSharp.WinForms.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.WinForms.53.0.0-pre01\build\CefSharp.WinForms.targets'))" />
</Target>
<Import Project="packages\cef.redist.x86.3.2623.1396\build\cef.redist.x86.targets" Condition="Exists('packages\cef.redist.x86.3.2623.1396\build\cef.redist.x86.targets')" />
<Import Project="packages\cef.redist.x64.3.2623.1396\build\cef.redist.x64.targets" Condition="Exists('packages\cef.redist.x64.3.2623.1396\build\cef.redist.x64.targets')" />
<Import Project="packages\CefSharp.Common.49.0.0-pre02\build\CefSharp.Common.targets" Condition="Exists('packages\CefSharp.Common.49.0.0-pre02\build\CefSharp.Common.targets')" />
<PropertyGroup>
<PostBuildEvent>del "$(TargetPath).config"
xcopy "$(ProjectDir)LICENSE.md" "$(TargetDir)" /Y
@@ -370,6 +320,10 @@ ren "$(TargetDir)LICENSE.md" "LICENSE.txt"
xcopy "$(ProjectDir)Libraries\CEFSHARP-LICENSE.txt" "$(TargetDir)" /Y
xcopy "$(ProjectDir)packages\Microsoft.VC120.CRT.JetBrains.12.0.21005.2\DotFiles\msvcp120.dll" "$(TargetDir)" /Y
xcopy "$(ProjectDir)packages\Microsoft.VC120.CRT.JetBrains.12.0.21005.2\DotFiles\msvcr120.dll" "$(TargetDir)" /Y
rmdir "$(TargetDir)scripts"
mkdir "$(TargetDir)scripts"
xcopy "$(ProjectDir)Resources\Scripts\*" "$(TargetDir)scripts\" /E /Y
rmdir "$(TargetDir)plugins"
mkdir "$(TargetDir)plugins"
mkdir "$(TargetDir)plugins\official"
mkdir "$(TargetDir)plugins\user"
@@ -377,6 +331,10 @@ xcopy "$(ProjectDir)Resources\Plugins\*" "$(TargetDir)plugins\official\" /E /Y
rmdir "$(ProjectDir)\bin\Debug"
rmdir "$(ProjectDir)\bin\Release"</PostBuildEvent>
</PropertyGroup>
<Import Project="packages\cef.redist.x86.3.2785.1478\build\cef.redist.x86.targets" Condition="Exists('packages\cef.redist.x86.3.2785.1478\build\cef.redist.x86.targets')" />
<Import Project="packages\cef.redist.x64.3.2785.1478\build\cef.redist.x64.targets" Condition="Exists('packages\cef.redist.x64.3.2785.1478\build\cef.redist.x64.targets')" />
<Import Project="packages\CefSharp.Common.53.0.0-pre01\build\CefSharp.Common.targets" Condition="Exists('packages\CefSharp.Common.53.0.0-pre01\build\CefSharp.Common.targets')" />
<Import Project="packages\CefSharp.WinForms.53.0.0-pre01\build\CefSharp.WinForms.targets" Condition="Exists('packages\CefSharp.WinForms.53.0.0-pre01\build\CefSharp.WinForms.targets')" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">

View File

@@ -1,4 +1,4 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2013
VisualStudioVersion = 12.0.40629.0
@@ -8,17 +8,14 @@ EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x86 = Debug|x86
Release Dick|x86 = Release Dick|x86
Release Duck|x86 = Release Duck|x86
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{2389A7CD-E0D3-4706-8294-092929A33A2D}.Debug|x86.ActiveCfg = Debug|x86
{2389A7CD-E0D3-4706-8294-092929A33A2D}.Debug|x86.Build.0 = Debug|x86
{2389A7CD-E0D3-4706-8294-092929A33A2D}.Debug|x86.Deploy.0 = Debug|x86
{2389A7CD-E0D3-4706-8294-092929A33A2D}.Release Dick|x86.ActiveCfg = Release Dick|x86
{2389A7CD-E0D3-4706-8294-092929A33A2D}.Release Dick|x86.Build.0 = Release Dick|x86
{2389A7CD-E0D3-4706-8294-092929A33A2D}.Release Duck|x86.ActiveCfg = Release Duck|x86
{2389A7CD-E0D3-4706-8294-092929A33A2D}.Release Duck|x86.Build.0 = Release Duck|x86
{2389A7CD-E0D3-4706-8294-092929A33A2D}.Release|x86.ActiveCfg = Release|x86
{2389A7CD-E0D3-4706-8294-092929A33A2D}.Release|x86.Build.0 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@@ -90,7 +90,7 @@ namespace TweetDck.Updates{
}
}
else if (e.Error != null){
Program.Log(e.Error.ToString());
Program.Reporter.Log(e.Error.ToString());
if (MessageBox.Show("Could not download the update: "+e.Error.Message+"\r\n\r\nDo you want to open the website and try downloading the update manually?", "Update Has Failed", MessageBoxButtons.YesNo, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1) == DialogResult.Yes){
BrowserUtils.OpenExternalBrowser(Program.Website);

View File

@@ -3,27 +3,31 @@ using CefSharp;
using CefSharp.WinForms;
using TweetDck.Core;
using TweetDck.Core.Controls;
using TweetDck.Core.Utils;
using TweetDck.Resources;
namespace TweetDck.Updates{
class UpdateHandler{
private readonly ChromiumWebBrowser browser;
private readonly FormBrowser form;
private readonly UpdaterSettings settings;
public event EventHandler<UpdateAcceptedEventArgs> UpdateAccepted;
public event EventHandler<UpdateCheckEventArgs> CheckFinished;
private int lastEventId;
public UpdateHandler(ChromiumWebBrowser browser, FormBrowser form){
public UpdateHandler(ChromiumWebBrowser browser, FormBrowser form, UpdaterSettings settings){
this.browser = browser;
this.form = form;
this.settings = settings;
browser.FrameLoadEnd += browser_FrameLoadEnd;
browser.RegisterJsObject("$TDU", new Bridge(this));
}
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");
}
}
@@ -70,6 +74,18 @@ namespace TweetDck.Updates{
}
}
public bool AllowPreReleases{
get{
return owner.settings.AllowPreReleases;
}
}
public bool IsSystemSupported{
get{
return Environment.OSVersion.Version >= new Version("6.1"); // 6.1 NT version = Windows 7
}
}
private readonly UpdateHandler owner;
public Bridge(UpdateHandler owner){
@@ -90,6 +106,10 @@ namespace TweetDck.Updates{
Program.UserConfig.Save();
});
}
public void OpenBrowser(string url){
BrowserUtils.OpenExternalBrowser(url);
}
}
}
}

View File

@@ -0,0 +1,5 @@
namespace TweetDck.Updates{
class UpdaterSettings{
public bool AllowPreReleases { get; set; }
}
}

View File

@@ -1,9 +1,6 @@
del "bin\x86\Release Dick\*.xml"
del "bin\x86\Release Dick\devtools_resources.pak"
del "bin\x86\Release Duck\*.xml"
del "bin\x86\Release Duck\devtools_resources.pak"
del "bin\x86\Release\*.xml"
del "bin\x86\Release\devtools_resources.pak"
del "bin\x86\Release\d3dcompiler_43.dll"
del "bin\x86\Release Dick\TweetDick.Browser.exe"
ren "bin\x86\Release Dick\CefSharp.BrowserSubprocess.exe" "TweetDick.Browser.exe"
del "bin\x86\Release Duck\TweetDuck.Browser.exe"
ren "bin\x86\Release Duck\CefSharp.BrowserSubprocess.exe" "TweetDuck.Browser.exe"
del "bin\x86\Release\TweetDuck.Browser.exe"
ren "bin\x86\Release\CefSharp.BrowserSubprocess.exe" "TweetDuck.Browser.exe"

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

149
bld/gen_full.iss Normal file
View 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
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;

266
bld/gen_upd.iss Normal file
View 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;