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

Compare commits

..

187 Commits
1.8.1 ... 1.8.6

Author SHA1 Message Date
5e9ed5d713 Improve video player startup and ensure it's always closed with the main app 2017-08-12 23:36:14 +02:00
78e492c764 Tweak 'stay open' pin position and tooltip 2017-08-12 20:33:52 +02:00
59c2a3642b Bump version of subprocess exe (should have been done a long time ago) 2017-08-12 19:10:38 +02:00
40ca923745 Cleanup FormPlayer code and set sync timer interval to 15 instead of 10 2017-08-12 19:08:17 +02:00
03af6cecaa Replace 'Stay open' checkbox with a pin icon
Closes #154
2017-08-12 17:49:27 +02:00
3992e447f4 Change tooltip border radius to be almost square 2017-08-12 17:47:55 +02:00
14a9edeb73 Fix various focus issues with video player and fix double-clicking control panel 2017-08-12 15:12:54 +02:00
92f1e9f7ec Make video player progress bar seek on mouse down instead of up 2017-08-12 14:31:46 +02:00
19c294c53e Terminate video player when pressing back mouse button over it 2017-08-12 13:43:53 +02:00
fe88ea5c05 Fix ctrl key not opening gifs externally 2017-08-12 03:37:24 +02:00
c9d551213a Remove license screen from installers 2017-08-12 03:15:51 +02:00
1e86a33ceb Hide video player overlay when video process exits gracelessly 2017-08-12 03:12:50 +02:00
551dd229f5 Make back mouse button hide video player and overlay 2017-08-12 03:04:24 +02:00
5ecf3c4147 Fix video player going past the end of a video when paused near the end 2017-08-12 02:26:52 +02:00
91bb2f4df0 Fix video player control panel not disappearing & improve error handling 2017-08-12 01:02:09 +02:00
ae3a0ae83d Fix crash when trying to update with 'Edit CSS' or 'Edit CEF Arguments' open 2017-08-12 00:05:56 +02:00
63ce7523de Fix oversight from previous commit 2017-08-12 00:01:13 +02:00
9e3b92bfc1 Move PluginManager initialization and move Form manipulation to FormManager 2017-08-11 23:57:44 +02:00
bc1767fb84 Change namespace of BrowserProcesses, MemoryUsageTracker, VideoPlayer 2017-08-11 23:50:16 +02:00
f917096cc7 Refactor plugin execution code 2017-08-11 23:32:47 +02:00
308926a2ae Add video player volume sync with user config 2017-08-11 20:58:37 +02:00
76f2b1a454 Make video player volume slider constant width 2017-08-11 20:20:07 +02:00
d899e4b38b Refactor video player control outside designer for dev convenience 2017-08-11 20:14:45 +02:00
e1422e35cc Add seeking + current time and duration to video player 2017-08-11 16:49:23 +02:00
2c00c6bb81 Expand the video player control panel and add progress bar 2017-08-11 16:21:31 +02:00
7e56ba6408 Make custom video player triggerable in tweet detail 2017-08-11 15:52:20 +02:00
8ceb70e67d Fix back button and context menu handling with a video playing 2017-08-11 15:22:45 +02:00
37d5efef1d Add an icon to TweetDuck.Video.exe 2017-08-11 15:06:38 +02:00
924065c26e Change video play icon color and handle playback errors 2017-08-11 13:59:05 +02:00
58cc7ea10d Add WIP video player for MP4s 2017-08-11 13:27:15 +02:00
f93e275ddf Add a volume slider to video player 2017-08-11 13:22:12 +02:00
06d2a5f715 Make video player pause/unpause when pressing space 2017-08-11 13:20:50 +02:00
3a7455eafe Fix video player cursor & pause/unpause on click 2017-08-11 12:33:34 +02:00
8b676fe6ce Implement video player in TweetDeck 2017-08-11 11:56:19 +02:00
54d12686af Tweak video player UI handling 2017-08-11 11:32:20 +02:00
f231256402 Improve player UI handling (cursor, position, setting owner handle) 2017-08-11 10:31:23 +02:00
410ead66f8 Add video player args and adjust location and size to owner window 2017-08-11 09:36:29 +02:00
c833a810af Add TweetDuck.Video project for video playback 2017-08-11 08:22:12 +02:00
50f1336b1d Tweak headings in update changelog renderer 2017-08-10 16:33:45 +02:00
60ed0b8cde Release 1.8.5.1 2017-08-10 16:25:55 +02:00
cc55a81c1b Remove emoji-instructions.txt during an update 2017-08-10 16:25:49 +02:00
f832e04e9e Remove unnecessary resx files and cleanup csproj 2017-08-10 15:09:15 +02:00
fc760b9a0c Fix another case in duplicate DM notifications 2017-08-10 13:46:13 +02:00
9addff0521 Exclude emoji-instructions.txt from build 2017-08-10 13:45:32 +02:00
dcaa3aab19 Work around duplicate DM notifications and rewrite recent tweet check 2017-08-10 00:51:38 +02:00
628785c68c Move _postbuild.bat to an MSBuild target directly in the project file 2017-08-10 00:20:58 +02:00
a5aa396fda Fix image quality setting not working in columns with large previews 2017-08-09 18:48:36 +02:00
f53a9f05e3 Fix image download filename for avatars and add more unit tests 2017-08-07 14:48:20 +02:00
7749b14156 Increment emoji keyboard plugin version 2017-08-06 20:32:13 +02:00
c15f339718 Fix emoji keyboard not disappearing after pressing ctrl+enter to tweet 2017-08-06 20:31:44 +02:00
775f590bfa Release 1.8.5 2017-08-06 15:58:28 +02:00
76408ea56f Increment verison of edit-design and emoji-keyboard plugins 2017-08-06 15:58:23 +02:00
a391d8ee83 Fix image pasting allowing more than 1 image in DMs 2017-08-05 21:52:38 +02:00
48c38f6e1d Include tweet author and quality in image download filename 2017-08-05 21:32:07 +02:00
37c5fba162 Change text color of sound notification file option for invalid paths 2017-08-05 19:50:30 +02:00
23e99b1d44 Update GC memory threshold defaults, also GC reload is enabled by default 2017-08-05 19:42:10 +02:00
8432240a47 Update HW acceleration & GC reload tooltips to note they won't be exported 2017-08-05 19:37:14 +02:00
a4bab743d6 Remove notification warning in GC reload option tooltip 2017-08-05 19:34:20 +02:00
60766789ab Move GC reload options to SystemConfig 2017-08-05 19:27:20 +02:00
ca014f881c Rewrite unknown property handling in FileSerializer 2017-08-05 19:23:42 +02:00
886eabe26c Show notifications that were missed during a browser reload 2017-08-05 18:43:57 +02:00
65b7167b5f Rewrite browser reload to save column notification state in session data 2017-08-05 18:36:17 +02:00
abbdde851e Make quoted tweets and RT account selectors square, fix RT account selector heading 2017-08-05 18:30:42 +02:00
54ac54aba6 Add session data that persists across browser reloads 2017-08-05 18:08:22 +02:00
184340f400 Increase delay for clearing recent notifications to prevent duplicates 2017-08-05 17:06:02 +02:00
93dd6813e8 Fix old icon alignment in 'Add column' dialog 2017-08-05 14:44:49 +02:00
b689b08711 Make follow notification button less visible when not hovered 2017-08-05 12:56:18 +02:00
1479a097d6 Fix alignment of old icons on buttons after TweetDeck update 2017-08-05 02:39:23 +02:00
e4967ea46d Add paragraphs and level 1-2 headings to update notification markdown renderer 2017-08-01 17:33:47 +02:00
3f28f18fb4 Release 1.8.4.1 2017-08-01 17:11:34 +02:00
1b90e0f65e Slightly increase default notification height 2017-08-01 17:05:31 +02:00
756ed649e6 Change default avatar shape to square, rename 'Default' to 'Legacy' 2017-08-01 17:03:29 +02:00
fbc423e2a7 Fix like/retweet notifications having invisible space with notification media previews disabled 2017-08-01 16:59:25 +02:00
f04cdb6a13 Fix PropertyBridge not updating properly 2017-08-01 16:58:46 +02:00
63b58b1cfe Release 1.8.4 2017-08-01 15:07:03 +02:00
77e656d8e4 Tweak JS prompt dialog layout on high DPI 2017-08-01 15:06:09 +02:00
a673957bd0 Tweak JS prompt dialog layout 2017-08-01 14:54:21 +02:00
c99a0c9974 Add Layout & Design plugin button to the TweetDeck settings modal 2017-08-01 13:45:44 +02:00
0fb06d0ff2 Remove reply revert option from edit-design plugin 2017-08-01 12:28:52 +02:00
c51eebfe22 Add new unit tests for TwitterUtils and CombinedFileStream 2017-07-31 22:27:02 +02:00
a51b34b48f Move CommandLineArgsParser code to CommandLineArgs 2017-07-31 22:26:48 +02:00
1b239bada1 Delay screenshots again due to iframes 2017-07-31 21:17:31 +02:00
50ab1a6ac3 Improve login/logout page design 2017-07-31 20:29:07 +02:00
f181f1fadc Refactor PropertyBridge 2017-07-31 19:58:23 +02:00
c686349922 Refactor Program (tweak properties, move locking code) 2017-07-31 18:04:04 +02:00
5f44a1f4ad Fix semicolons in code.js 2017-07-31 14:58:42 +02:00
a968938832 Move square scrollbars from edit-design plugin to code.js 2017-07-31 14:55:31 +02:00
8d67f3dfdc Move code.js notification setup and fix dropdown border radius 2017-07-31 14:42:26 +02:00
973ae8cb5d Move twitter account regex to TwitterUtils 2017-07-31 14:31:32 +02:00
a4747b0d7b Add JS dialog handler to notifications 2017-07-31 14:25:00 +02:00
f07640cc84 Reorganize CEF handlers 2017-07-31 14:24:42 +02:00
c235c55b19 Add option to show media previews in notification 2017-07-31 14:12:24 +02:00
485ef684be Prevent notification keyboard controls from triggering in dev tools 2017-07-31 13:36:44 +02:00
7caca22e57 Remove 'TweetDuck' from JS dialog captions 2017-07-31 01:42:22 +02:00
f1d9e32bf5 Add keyboard controls to notifications
Closes #153
2017-07-31 01:23:57 +02:00
23d5fa3107 Tweak emoji keyboard border radius and character count width 2017-07-30 23:58:35 +02:00
4e7d8aba1c Improve FormMessage to match MessageBox closer and look better on high DPI 2017-07-30 23:50:24 +02:00
98ba871a71 Fix back mouse button ignoring columns inside User modals
Closes #155
2017-07-30 21:38:38 +02:00
3ff23c0264 Remove unnecessary TweetDeck logo CSS rule 2017-07-30 21:29:02 +02:00
e21f89477b Fix ISerializedObject not being removed from unit tests and csproj file 2017-07-30 21:28:26 +02:00
f177f514f5 Fix column type icons jumping when opening column settings 2017-07-30 21:19:03 +02:00
af30f3b348 Square-ify many elements of TweetDeck (buttons, inputs, dialogs, menus, previews) 2017-07-30 21:15:39 +02:00
82df618429 Fix code.js after refactoring CSS insertion 2017-07-30 21:13:45 +02:00
bb3538e270 Refocus tweet textarea after selecting a different account
Closes #156
2017-07-30 20:36:17 +02:00
71925e1126 Refactor parts of code.js (make code shorter, use 'let') 2017-07-30 20:19:59 +02:00
93c1cbd231 Update SystemConfig to use FileSerializer and migrate old files 2017-07-30 19:54:28 +02:00
894b890fe5 Tweak serialization code and remove ISerializedObject 2017-07-30 19:28:03 +02:00
8e9e8f7fad Fix magic number and add a comment 2017-07-30 19:02:30 +02:00
2a0461a76f Add safeguards for accessing TweetDeckBridge.LastHighlightedTweetImages 2017-07-21 12:43:10 +02:00
85f923a6fc Add StringUtils.EmptyArray and use it instead of new string[0] 2017-07-21 12:37:30 +02:00
b35e4d4d01 Add "Save all images as..." context menu option for tweets with multiple images 2017-07-21 12:14:15 +02:00
cb24a859f4 Fix file type description in Save image dialog 2017-07-21 11:16:47 +02:00
b1ef00746f Hide open/copy link context menu items for media previews 2017-07-21 11:07:40 +02:00
aebe82e3a7 Add context menu for image previews that use background-image 2017-07-21 10:46:28 +02:00
7c87856b4d Show waiting cursor while taking a tweet screenshot 2017-07-20 16:29:39 +02:00
d1b1dd539f Add an option to use :orig image links in context menu 2017-07-17 05:39:59 +02:00
55eea88ace Add twitter image link & download methods to TwitterUtils 2017-07-17 05:10:06 +02:00
a70f64e1f6 Move some stuff from BrowserUtils to a new TwitterUtils class 2017-07-17 02:09:20 +02:00
fa0cb120a7 Add a 'Close' button to the modal dialog in the template plugin
Closes #143
2017-07-13 05:57:12 +02:00
e3080d07dc Ensure plugin config exists after first run, fixes profile export crash
Closes #147
2017-07-13 05:21:22 +02:00
34726c533e Release 1.8.3 2017-07-09 20:17:33 +02:00
4a0d72d2cc Fix FormMessage icon position on high DPI 2017-07-09 17:24:01 +02:00
fe3fc5c9f7 Add WindowsUtils.CreateDirectoryForFile and use it 2017-07-09 14:12:27 +02:00
441228e2b0 Stop using BrandName in msg dialogs, update msg titles, fix mistakes from prev commits 2017-07-09 04:21:33 +02:00
7538aee4f2 Replace all MessageBox.Show calls with FormMessage 2017-07-09 03:50:04 +02:00
acf809268e Add many helper methods to FormMessage 2017-07-09 03:45:35 +02:00
4ebc0c10b6 Forgot something! 2017-07-09 02:55:48 +02:00
a453888ca2 Tweak new lines in FormMessage, add ControlType enum for FormMessage buttons 2017-07-09 02:40:37 +02:00
530b44762b Make \n the only new line character in FormMessage 2017-07-09 01:52:44 +02:00
f85587fb0b Bump emoji keyboard plugin version 2017-07-09 00:36:22 +02:00
edb8799b1a Update emoji keyboard w/ emoji 9.0, instructions, and code tweaks 2017-07-09 00:30:03 +02:00
e47aeb37f0 Designer, why 2017-07-08 20:19:22 +02:00
776e9968dc Fix tab order in Advanced tab in Options 2017-07-08 19:25:20 +02:00
1898bf4731 Add a tooltip to browser GC reload checkbox 2017-07-08 19:21:36 +02:00
78df020737 Add a modal with release info to update notifications
Closes #139
2017-07-08 18:00:00 +02:00
b93f9a4b9a Fix compose textarea not being focused after pasting an image in a reply 2017-07-08 03:17:20 +02:00
748b230ef5 Fix missing BrowserProcesses in project file after merge 2017-07-08 02:55:45 +02:00
deb8dde9e1 Merge pull request #141 from chylex/memory
Merge browser process identification & GC reload with memory threshold
2017-07-08 02:50:03 +02:00
dbb2f10754 Update from master 2017-07-08 02:49:21 +02:00
0ded03ab92 Fix more analysis violations (exceptions, native method pointers, form disposal) 2017-07-08 00:21:41 +02:00
2198e84f3b Fix subprocess NativeMethods to use pointers instead of value types 2017-07-07 23:58:45 +02:00
14d44528b0 Fuck CultureInfo some more and fix analysis violations (dispose pattern, lang features) 2017-07-07 23:53:04 +02:00
eb8159ca0f Add a tooltip to text box in the Sounds tab in Options 2017-07-07 23:49:57 +02:00
9811f40a53 Go fuck yourself CurrentCulture and stop messing with string interpolation 2017-07-07 22:56:36 +02:00
8de7e13aa3 Reorganize and refactor UserConfig and PluginConfig 2017-07-07 19:22:33 +02:00
c63e6a1e49 More refactoring (seal classes, fix names and comments) 2017-07-07 16:15:10 +02:00
5a21d2cb10 Add StringUtils with unit tests and use it 2017-07-07 15:52:13 +02:00
424c0e596c Add legacy config detection and replace UserConfig serialization with FileSerializer 2017-07-07 02:56:02 +02:00
d431b63c27 Add SingleTypeConverter and update names in FileSerializer 2017-07-07 01:47:14 +02:00
38c2781cd3 Add an enum test to FileSerializer unit test 2017-07-07 00:53:19 +02:00
796fb348a3 Add classes for serializing objects to/from text files 2017-07-07 00:48:00 +02:00
71b306d5fd Fix unit test project file after refactoring 2017-07-06 21:26:43 +02:00
4c610ea32d Move TweetDeck URL into a constant 2017-07-06 20:58:40 +02:00
4bff006743 Refactor (move files into different namespaces) 2017-07-06 20:58:06 +02:00
1645079bc0 Allow plugins to modify screenshot css and include a 'td-screenshot' body class 2017-07-06 03:47:59 +02:00
9afb58e4a7 Remove unused 'using' statement 2017-07-06 03:30:15 +02:00
2820fc8acf Fix some modals not closing when pressing the back button 2017-07-04 22:01:33 +02:00
4d77a498f6 Add a WIP memory tracker that runs GC reload, and fix config 2017-07-04 22:00:03 +02:00
d77de3bb12 Remove debug code 2017-06-30 23:53:36 +02:00
29e7ad6ce6 Add a way to track browser process IDs 2017-06-30 23:46:52 +02:00
1712b5120e Merge remote-tracking branch 'refs/remotes/origin/master' into memory 2017-06-30 20:47:22 +02:00
06c0153cf5 Fix tray restoration from another process if the original process is hung 2017-06-30 20:44:39 +02:00
44f7ecda6d Merge remote-tracking branch 'refs/remotes/origin/master' into memory 2017-06-30 20:17:21 +02:00
fb94bf1b80 Add WindowsUtils.IsChildProcess to check process parent 2017-06-30 20:14:49 +02:00
4818652582 Add current PID into WindowsUtils.CurrentProcessID and use it 2017-06-30 17:07:37 +02:00
c69b9784fc Add option to enable GC reload with a custom memory threshold (currently unused) 2017-06-30 16:47:31 +02:00
0ac244a3ea Merge remote-tracking branch 'refs/remotes/origin/master' into memory 2017-06-30 00:00:33 +02:00
19a445fdab Add a NumericUpDown control with a text suffix 2017-06-30 00:00:20 +02:00
c90a18a2c0 Merge remote-tracking branch 'refs/remotes/origin/master' into memory 2017-06-29 23:47:00 +02:00
502310c413 Prevent TrackBar from stealing focus when scrolling 2017-06-29 23:34:00 +02:00
6f9424d4ec Force GC cleanup when clicking 'Reload browser' 2017-06-29 18:21:09 +02:00
bb379fe667 Expose gc() in JS 2017-06-29 04:01:50 +02:00
0fd86bf214 Move CEF argument setup to BrowserUtils 2017-06-29 03:52:55 +02:00
29b75d4391 Release 1.8.2 2017-06-29 02:25:07 +02:00
a7124e5449 Fix FormMessage not scaling well with high DPI 2017-06-29 02:21:39 +02:00
a714f3480a Refactor Program.Restart and UpdaterSettings initialization 2017-06-28 23:09:49 +02:00
c10e0df898 Fix user profile modals not closing with back mouse button 2017-06-27 22:05:03 +02:00
fba734fd5a Fix new lines not being recognized in CEF argument parser 2017-06-27 20:28:55 +02:00
27e2372097 Add an option for custom notification window size 2017-06-27 17:54:52 +02:00
7f5b99495c Move some code (config, notifications, settings) 2017-06-27 16:49:37 +02:00
1efe2a56af Fix Win+Arrow and Win+Shift+Arrow not saving new window location
Closes #135
2017-06-26 20:23:32 +02:00
850873aec8 Add a delay to notification duration option to fix example notification flicker
Closes #136
2017-06-26 19:40:20 +02:00
d9e6afbf36 Replace Math.Round calls with a more convenient custom method 2017-06-26 17:33:07 +02:00
7f3bd2715c Enable high DPI support and fix all known DPI-related UI issues
Closes #99
2017-06-26 17:08:51 +02:00
c81cb393e9 Allow scroll speed option to use increments of 5 instead of 25 2017-06-26 16:22:52 +02:00
4800faa783 Fix a race condition crash in update checker events 2017-06-24 12:10:25 +02:00
1087b5e1d1 Release 1.8.1 2017-06-24 11:49:25 +02:00
134 changed files with 4043 additions and 1636 deletions

View File

@@ -1,5 +1,5 @@
using System; using System;
using TweetDuck.Core.Utils; using TweetDuck.Data;
namespace TweetDuck.Configuration{ namespace TweetDuck.Configuration{
static class Arguments{ static class Arguments{

View File

@@ -2,28 +2,26 @@
using System.ComponentModel; using System.ComponentModel;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Threading;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
namespace TweetDuck.Configuration{ namespace TweetDuck.Configuration{
sealed class LockManager{ sealed class LockManager{
private const int RetryDelay = 250;
public enum Result{ public enum Result{
Success, HasProcess, Fail Success, HasProcess, Fail
} }
public Process LockingProcess { get; private set; }
private readonly string file; private readonly string file;
private FileStream lockStream; private FileStream lockStream;
private Process lockingProcess;
public LockManager(string file){ public LockManager(string file){
this.file = file; this.file = file;
} }
private void CreateLockFileStream(){ // Lock file
lockStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.Read);
WriteIntToStream(lockStream, GetCurrentProcessId());
lockStream.Flush(true);
}
private bool ReleaseLockFileStream(){ private bool ReleaseLockFileStream(){
if (lockStream != null){ if (lockStream != null){
@@ -37,8 +35,10 @@ namespace TweetDuck.Configuration{
} }
private Result TryCreateLockFile(){ private Result TryCreateLockFile(){
if (lockStream != null){ void CreateLockFileStream(){
throw new InvalidOperationException("Lock file already exists."); lockStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.Read);
lockStream.Write(BitConverter.GetBytes(WindowsUtils.CurrentProcessID), 0, sizeof(int));
lockStream.Flush(true);
} }
try{ try{
@@ -60,6 +60,8 @@ namespace TweetDuck.Configuration{
} }
} }
// Lock management
public Result Lock(){ public Result Lock(){
if (lockStream != null){ if (lockStream != null){
return Result.Success; return Result.Success;
@@ -72,7 +74,9 @@ namespace TweetDuck.Configuration{
int pid; int pid;
using(FileStream fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)){ using(FileStream fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)){
pid = ReadIntFromStream(fileStream); byte[] bytes = new byte[sizeof(int)];
fileStream.Read(bytes, 0, bytes.Length);
pid = BitConverter.ToInt32(bytes, 0);
} }
try{ try{
@@ -80,7 +84,7 @@ namespace TweetDuck.Configuration{
using(Process currentProcess = Process.GetCurrentProcess()){ using(Process currentProcess = Process.GetCurrentProcess()){
if (foundProcess.MainModule.FileVersionInfo.InternalName == currentProcess.MainModule.FileVersionInfo.InternalName){ if (foundProcess.MainModule.FileVersionInfo.InternalName == currentProcess.MainModule.FileVersionInfo.InternalName){
LockingProcess = foundProcess; lockingProcess = foundProcess;
} }
else{ else{
foundProcess.Close(); foundProcess.Close();
@@ -91,7 +95,7 @@ namespace TweetDuck.Configuration{
// Process.MainModule can throw exceptions in some cases // Process.MainModule can throw exceptions in some cases
} }
return LockingProcess == null ? Result.Fail : Result.HasProcess; return lockingProcess == null ? Result.Fail : Result.HasProcess;
}catch{ }catch{
return Result.Fail; return Result.Fail;
} }
@@ -100,45 +104,72 @@ namespace TweetDuck.Configuration{
return initialResult; return initialResult;
} }
public bool Unlock(){ public Result LockWait(int timeout){
bool result = true; for(int elapsed = 0; elapsed < timeout; elapsed += RetryDelay){
Result result = Lock();
if (result == Result.HasProcess){
Thread.Sleep(RetryDelay);
}
else{
return result;
}
}
return Lock();
}
public bool Unlock(){
if (ReleaseLockFileStream()){ if (ReleaseLockFileStream()){
try{ try{
File.Delete(file); File.Delete(file);
}catch(Exception e){ }catch(Exception e){
Program.Reporter.Log(e.ToString()); Program.Reporter.Log(e.ToString());
result = false; return false;
} }
} }
return result; return true;
}
// Locking process
public bool RestoreLockingProcess(int failTimeout){
if (lockingProcess != null){
if (lockingProcess.MainWindowHandle == IntPtr.Zero){ // restore if the original process is in tray
NativeMethods.PostMessage(NativeMethods.HWND_BROADCAST, Program.WindowRestoreMessage, new UIntPtr((uint)lockingProcess.Id), IntPtr.Zero);
if (WindowsUtils.TrySleepUntil(() => CheckLockingProcessExited() || (lockingProcess.MainWindowHandle != IntPtr.Zero && lockingProcess.Responding), failTimeout, RetryDelay)){
return true;
}
}
}
return false;
} }
public bool CloseLockingProcess(int closeTimeout, int killTimeout){ public bool CloseLockingProcess(int closeTimeout, int killTimeout){
if (LockingProcess != null){ if (lockingProcess != null){
try{ try{
if (LockingProcess.CloseMainWindow()){ if (lockingProcess.CloseMainWindow()){
WindowsUtils.TrySleepUntil(CheckLockingProcessExited, closeTimeout, 250); WindowsUtils.TrySleepUntil(CheckLockingProcessExited, closeTimeout, RetryDelay);
} }
if (!LockingProcess.HasExited){ if (!lockingProcess.HasExited){
LockingProcess.Kill(); lockingProcess.Kill();
WindowsUtils.TrySleepUntil(CheckLockingProcessExited, killTimeout, 250); WindowsUtils.TrySleepUntil(CheckLockingProcessExited, killTimeout, RetryDelay);
} }
if (LockingProcess.HasExited){ if (lockingProcess.HasExited){
LockingProcess.Dispose(); lockingProcess.Dispose();
LockingProcess = null; lockingProcess = null;
return true; return true;
} }
}catch(Exception ex){ }catch(Exception ex){
if (ex is InvalidOperationException || ex is Win32Exception){ if (ex is InvalidOperationException || ex is Win32Exception){
if (LockingProcess != null){ if (lockingProcess != null){
LockingProcess.Refresh(); bool hasExited = CheckLockingProcessExited();
lockingProcess.Dispose();
bool hasExited = LockingProcess.HasExited;
LockingProcess.Dispose();
return hasExited; return hasExited;
} }
} }
@@ -150,27 +181,8 @@ namespace TweetDuck.Configuration{
} }
private bool CheckLockingProcessExited(){ private bool CheckLockingProcessExited(){
LockingProcess.Refresh(); lockingProcess.Refresh();
return LockingProcess.HasExited; return lockingProcess.HasExited;
}
// Utility functions
private static void WriteIntToStream(Stream stream, int value){
byte[] id = BitConverter.GetBytes(value);
stream.Write(id, 0, id.Length);
}
private static int ReadIntFromStream(Stream stream){
byte[] bytes = new byte[4];
stream.Read(bytes, 0, 4);
return BitConverter.ToInt32(bytes, 0);
}
private static int GetCurrentProcessId(){
using(Process process = Process.GetCurrentProcess()){
return process.Id;
}
} }
} }
} }

View File

@@ -1,45 +1,43 @@
using System; using System;
using System.IO; using System.IO;
using TweetDuck.Core.Utils;
using TweetDuck.Data.Serialization;
namespace TweetDuck.Configuration{ namespace TweetDuck.Configuration{
sealed class SystemConfig{ sealed class SystemConfig{
private static readonly FileSerializer<SystemConfig> Serializer = new FileSerializer<SystemConfig>{
// HandleUnknownProperties = (obj, data) => {}
};
public static readonly bool IsHardwareAccelerationSupported = File.Exists(Path.Combine(Program.ProgramPath, "libEGL.dll")) && public static readonly bool IsHardwareAccelerationSupported = File.Exists(Path.Combine(Program.ProgramPath, "libEGL.dll")) &&
File.Exists(Path.Combine(Program.ProgramPath, "libGLESv2.dll")); File.Exists(Path.Combine(Program.ProgramPath, "libGLESv2.dll"));
// CONFIGURATION DATA
private bool _hardwareAcceleration = true;
public bool EnableBrowserGCReload { get; set; } = true;
public int BrowserMemoryThreshold { get; set; } = 400;
// SPECIAL PROPERTIES
public bool HardwareAcceleration{ public bool HardwareAcceleration{
get => hardwareAcceleration && IsHardwareAccelerationSupported; get => _hardwareAcceleration && IsHardwareAccelerationSupported;
set => hardwareAcceleration = value; set => _hardwareAcceleration = value;
} }
// END OF CONFIG
private readonly string file; private readonly string file;
private bool hardwareAcceleration;
private SystemConfig(string file){ private SystemConfig(string file){
this.file = file; this.file = file;
HardwareAcceleration = true;
}
private void WriteToStream(Stream stream){
stream.WriteByte((byte)(HardwareAcceleration ? 1 : 0));
}
private void ReadFromStream(Stream stream){
HardwareAcceleration = stream.ReadByte() > 0;
} }
public bool Save(){ public bool Save(){
try{ try{
string directory = Path.GetDirectoryName(file); WindowsUtils.CreateDirectoryForFile(file);
if (directory == null)return false; Serializer.Write(file, this);
Directory.CreateDirectory(directory);
using(Stream stream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None)){
WriteToStream(stream);
}
return true; return true;
}catch(Exception e){ }catch(Exception e){
Program.Reporter.HandleException("Configuration Error", "Could not save the system configuration file.", true, e); Program.Reporter.HandleException("Configuration Error", "Could not save the system configuration file.", true, e);
@@ -51,11 +49,20 @@ namespace TweetDuck.Configuration{
SystemConfig config = new SystemConfig(file); SystemConfig config = new SystemConfig(file);
try{ try{
using(Stream stream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read)){ Serializer.Read(file, config);
config.ReadFromStream(stream); return config;
}
}catch(FileNotFoundException){ }catch(FileNotFoundException){
}catch(DirectoryNotFoundException){ }catch(DirectoryNotFoundException){
}catch(FormatException){
try{
using(Stream stream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read)){
config.HardwareAcceleration = stream.ReadByte() > 0;
}
config.Save();
}catch(Exception e){
Program.Reporter.HandleException("Configuration Error", "Could not update the system configuration file.", true, e);
}
}catch(Exception e){ }catch(Exception e){
Program.Reporter.HandleException("Configuration Error", "Could not open the system configuration file. If you continue, you will lose system specific configuration such as Hardware Acceleration.", true, e); Program.Reporter.HandleException("Configuration Error", "Could not open the system configuration file. If you continue, you will lose system specific configuration such as Hardware Acceleration.", true, e);
} }

View File

@@ -1,206 +1,165 @@
using System; using System;
using System.Collections.Generic;
using System.Drawing; using System.Drawing;
using System.IO; using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using TweetDuck.Core; using TweetDuck.Core;
using TweetDuck.Core.Controls; using TweetDuck.Core.Controls;
using TweetDuck.Core.Notification; using TweetDuck.Core.Notification;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
using TweetDuck.Data;
using TweetDuck.Data.Serialization;
namespace TweetDuck.Configuration{ namespace TweetDuck.Configuration{
[Serializable]
sealed class UserConfig{ sealed class UserConfig{
private static readonly IFormatter Formatter = new BinaryFormatter{ Binder = new LegacyBinder() }; private static readonly FileSerializer<UserConfig> Serializer = new FileSerializer<UserConfig>{ HandleUnknownProperties = HandleUnknownProperties };
private class LegacyBinder : SerializationBinder{ private static void HandleUnknownProperties(UserConfig obj, Dictionary<string, string> data){
public override Type BindToType(string assemblyName, string typeName){ if (data.TryGetValue("EnableBrowserGCReload", out string propGCReload) && data.TryGetValue("BrowserMemoryThreshold", out string propMemThreshold)){
return Type.GetType(string.Format("{0}, {1}", typeName.Replace("TweetDck", "TweetDuck"), assemblyName.Replace("TweetDck", "TweetDuck"))); if (bool.TryParse(propGCReload, out bool isGCReloadEnabled) && isGCReloadEnabled && int.TryParse(propMemThreshold, out int memThreshold)){
// SystemConfig initialization was moved before UserConfig to allow for this
// TODO remove the migration soon
Program.SystemConfig.EnableBrowserGCReload = true;
Program.SystemConfig.BrowserMemoryThreshold = memThreshold;
Program.SystemConfig.Save();
} }
} }
private const int CurrentFileVersion = 10; data.Remove("EnableBrowserGCReload");
data.Remove("BrowserMemoryThreshold");
// START OF CONFIGURATION if (data.Count == 0){
obj.Save();
}
}
public WindowState BrowserWindow { get; set; } static UserConfig(){
public bool DisplayNotificationColumn { get; set; } Serializer.RegisterTypeConverter(typeof(WindowState), WindowState.Converter);
public bool DisplayNotificationTimer { get; set; }
public bool NotificationTimerCountDown { get; set; }
public bool NotificationSkipOnLinkClick { get; set; }
public bool NotificationNonIntrusiveMode { get; set; }
public TweetNotification.Position NotificationPosition { get; set; } Serializer.RegisterTypeConverter(typeof(Point), new SingleTypeConverter<Point>{
public Point CustomNotificationPosition { get; set; } ConvertToString = value => $"{value.X} {value.Y}",
public int NotificationEdgeDistance { get; set; } ConvertToObject = value => {
public int NotificationDisplay { get; set; } int[] elements = StringUtils.ParseInts(value, ' ');
public int NotificationIdlePauseSeconds { get; set; } return new Point(elements[0], elements[1]);
public int NotificationDurationValue { get; set; } }
public int NotificationScrollSpeed { get; set; } });
public bool EnableSpellCheck { get; set; } Serializer.RegisterTypeConverter(typeof(Size), new SingleTypeConverter<Size>{
public bool ExpandLinksOnHover { get; set; } ConvertToString = value => $"{value.Width} {value.Height}",
public bool SwitchAccountSelectors { get; set; } ConvertToObject = value => {
public bool EnableTrayHighlight { get; set; } int[] elements = StringUtils.ParseInts(value, ' ');
return new Size(elements[0], elements[1]);
}
});
}
public bool EnableUpdateCheck { get; set; } // CONFIGURATION DATA
public string DismissedUpdate { get; set; }
public WindowState PluginsWindow { get; set; } public WindowState BrowserWindow { get; set; } = new WindowState();
public WindowState PluginsWindow { get; set; } = new WindowState();
public string CustomCefArgs { get; set; } public bool ExpandLinksOnHover { get; set; } = true;
public string CustomBrowserCSS { get; set; } public bool SwitchAccountSelectors { get; set; } = true;
public string CustomNotificationCSS { get; set; } public bool BestImageQuality { get; set; } = true;
public bool EnableSpellCheck { get; set; } = false;
public int VideoPlayerVolume { get; set; } = 50;
private int _zoomLevel = 100;
private bool _muteNotifications;
private TrayIcon.Behavior _trayBehavior = TrayIcon.Behavior.Disabled;
public bool EnableTrayHighlight { get; set; } = true;
public bool EnableUpdateCheck { get; set; } = true;
public string DismissedUpdate { get; set; } = null;
public bool DisplayNotificationColumn { get; set; } = false;
public bool NotificationMediaPreviews { get; set; } = true;
public bool NotificationSkipOnLinkClick { get; set; } = false;
public bool NotificationNonIntrusiveMode { get; set; } = true;
public int NotificationIdlePauseSeconds { get; set; } = 0;
public bool DisplayNotificationTimer { get; set; } = true;
public bool NotificationTimerCountDown { get; set; } = false;
public int NotificationDurationValue { get; set; } = 25;
public TweetNotification.Position NotificationPosition { get; set; } = TweetNotification.Position.TopRight;
public Point CustomNotificationPosition { get; set; } = ControlExtensions.InvisibleLocation;
public int NotificationDisplay { get; set; } = 0;
public int NotificationEdgeDistance { get; set; } = 8;
public TweetNotification.Size NotificationSize { get; set; } = TweetNotification.Size.Auto;
public Size CustomNotificationSize { get; set; } = Size.Empty;
public int NotificationScrollSpeed { get; set; } = 10;
private string _notificationSoundPath;
public string CustomCefArgs { get; set; } = null;
public string CustomBrowserCSS { get; set; } = null;
public string CustomNotificationCSS { get; set; } = null;
// SPECIAL PROPERTIES
public bool IsCustomNotificationPositionSet => CustomNotificationPosition != ControlExtensions.InvisibleLocation; public bool IsCustomNotificationPositionSet => CustomNotificationPosition != ControlExtensions.InvisibleLocation;
public bool IsCustomNotificationSizeSet => CustomNotificationSize != Size.Empty;
public TwitterUtils.ImageQuality TwitterImageQuality => BestImageQuality ? TwitterUtils.ImageQuality.Orig : TwitterUtils.ImageQuality.Default;
public string NotificationSoundPath{ public string NotificationSoundPath{
get => string.IsNullOrEmpty(notificationSoundPath) ? string.Empty : notificationSoundPath; get => string.IsNullOrEmpty(_notificationSoundPath) ? string.Empty : _notificationSoundPath;
set => notificationSoundPath = value; set => _notificationSoundPath = value;
} }
public bool MuteNotifications{ public bool MuteNotifications{
get => muteNotifications; get => _muteNotifications;
set{ set{
if (muteNotifications != value){ if (_muteNotifications != value){
muteNotifications = value; _muteNotifications = value;
MuteToggled?.Invoke(this, new EventArgs()); MuteToggled?.Invoke(this, new EventArgs());
} }
} }
} }
public int ZoomLevel{ public int ZoomLevel{
get => zoomLevel; get => _zoomLevel;
set{ set{
if (zoomLevel != value){ if (_zoomLevel != value){
zoomLevel = value; _zoomLevel = value;
ZoomLevelChanged?.Invoke(this, new EventArgs()); ZoomLevelChanged?.Invoke(this, new EventArgs());
} }
} }
} }
public double ZoomMultiplier => zoomLevel/100.0; public double ZoomMultiplier => _zoomLevel/100.0;
public TrayIcon.Behavior TrayBehavior{ public TrayIcon.Behavior TrayBehavior{
get => trayBehavior; get => _trayBehavior;
set{ set{
if (trayBehavior != value){ if (_trayBehavior != value){
trayBehavior = value; _trayBehavior = value;
TrayBehaviorChanged?.Invoke(this, new EventArgs()); TrayBehaviorChanged?.Invoke(this, new EventArgs());
} }
} }
} }
// END OF CONFIGURATION // EVENTS
[field:NonSerialized]
public event EventHandler MuteToggled; public event EventHandler MuteToggled;
[field:NonSerialized]
public event EventHandler ZoomLevelChanged; public event EventHandler ZoomLevelChanged;
[field:NonSerialized]
public event EventHandler TrayBehaviorChanged; public event EventHandler TrayBehaviorChanged;
[NonSerialized] // END OF CONFIG
private string file;
private int fileVersion; private readonly string file;
private bool muteNotifications;
private int zoomLevel;
private string notificationSoundPath;
private TrayIcon.Behavior trayBehavior;
private UserConfig(string file){ public UserConfig(string file){ // TODO make private after removing UserConfigLegacy
this.file = file; this.file = file;
BrowserWindow = new WindowState();
ZoomLevel = 100;
DisplayNotificationTimer = true;
NotificationNonIntrusiveMode = true;
NotificationPosition = TweetNotification.Position.TopRight;
CustomNotificationPosition = ControlExtensions.InvisibleLocation;
NotificationEdgeDistance = 8;
NotificationDurationValue = 25;
NotificationScrollSpeed = 100;
EnableUpdateCheck = true;
ExpandLinksOnHover = true;
SwitchAccountSelectors = true;
EnableTrayHighlight = true;
PluginsWindow = new WindowState();
}
private void UpgradeFile(){
if (fileVersion == CurrentFileVersion){
return;
}
// if outdated, cycle through all versions
if (fileVersion == 0){
DisplayNotificationTimer = true;
EnableUpdateCheck = true;
++fileVersion;
}
if (fileVersion == 1){
ExpandLinksOnHover = true;
++fileVersion;
}
if (fileVersion == 2){
BrowserWindow = new WindowState();
PluginsWindow = new WindowState();
++fileVersion;
}
if (fileVersion == 3){
EnableTrayHighlight = true;
NotificationDurationValue = 25;
++fileVersion;
}
if (fileVersion == 4){
++fileVersion;
}
if (fileVersion == 5){
++fileVersion;
}
if (fileVersion == 6){
NotificationNonIntrusiveMode = true;
++fileVersion;
}
if (fileVersion == 7){
ZoomLevel = 100;
++fileVersion;
}
if (fileVersion == 8){
SwitchAccountSelectors = true;
++fileVersion;
}
if (fileVersion == 9){
NotificationScrollSpeed = 100;
++fileVersion;
}
// update the version
fileVersion = CurrentFileVersion;
Save();
} }
public bool Save(){ public bool Save(){
try{ try{
string directory = Path.GetDirectoryName(file); WindowsUtils.CreateDirectoryForFile(file);
if (directory == null)return false;
Directory.CreateDirectory(directory);
if (File.Exists(file)){ if (File.Exists(file)){
string backupFile = GetBackupFile(file); string backupFile = GetBackupFile(file);
@@ -208,10 +167,7 @@ namespace TweetDuck.Configuration{
File.Move(file, backupFile); File.Move(file, backupFile);
} }
using(Stream stream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None)){ Serializer.Write(file, this);
Formatter.Serialize(stream, this);
}
return true; return true;
}catch(Exception e){ }catch(Exception e){
Program.Reporter.HandleException("Configuration Error", "Could not save the configuration file.", true, e); Program.Reporter.HandleException("Configuration Error", "Could not save the configuration file.", true, e);
@@ -220,22 +176,20 @@ namespace TweetDuck.Configuration{
} }
public static UserConfig Load(string file){ public static UserConfig Load(string file){
UserConfig config = null;
Exception firstException = null; Exception firstException = null;
for(int attempt = 0; attempt < 2; attempt++){ for(int attempt = 0; attempt < 2; attempt++){
try{ try{
using(Stream stream = new FileStream(attempt == 0 ? file : GetBackupFile(file), FileMode.Open, FileAccess.Read, FileShare.Read)){ UserConfig config = new UserConfig(file);
if ((config = Formatter.Deserialize(stream) as UserConfig) != null){ Serializer.Read(attempt == 0 ? file : GetBackupFile(file), config);
config.file = file; return config;
}
}
config?.UpgradeFile();
break;
}catch(FileNotFoundException){ }catch(FileNotFoundException){
}catch(DirectoryNotFoundException){ }catch(DirectoryNotFoundException){
break; break;
}catch(FormatException){
UserConfig config = UserConfigLegacy.Load(file);
config.Save();
return config;
}catch(Exception e){ }catch(Exception e){
if (attempt == 0){ if (attempt == 0){
firstException = e; firstException = e;
@@ -247,11 +201,11 @@ namespace TweetDuck.Configuration{
} }
} }
if (firstException != null && config == null){ if (firstException != null){
Program.Reporter.HandleException("Configuration Error", "Could not open the configuration file.", true, firstException); Program.Reporter.HandleException("Configuration Error", "Could not open the configuration file.", true, firstException);
} }
return config ?? new UserConfig(file); return new UserConfig(file);
} }
public static string GetBackupFile(string file){ public static string GetBackupFile(string file){

View File

@@ -0,0 +1,210 @@
using System;
using System.Drawing;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using TweetDuck.Core;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Notification;
using TweetDuck.Data;
namespace TweetDuck.Configuration{
[Serializable]
sealed class UserConfigLegacy{ // TODO remove eventually
private static readonly IFormatter Formatter = new BinaryFormatter{ Binder = new LegacyBinder() };
private class LegacyBinder : SerializationBinder{
public override Type BindToType(string assemblyName, string typeName){
return Type.GetType(string.Format("{0}, {1}", typeName.Replace("TweetDck", "TweetDuck").Replace(".UserConfig", ".UserConfigLegacy").Replace("Core.Utils.WindowState", "Data.WindowState"), assemblyName.Replace("TweetDck", "TweetDuck")));
}
}
private const int CurrentFileVersion = 11;
// START OF CONFIGURATION
public WindowState BrowserWindow { get; set; }
public WindowState PluginsWindow { get; set; }
public bool DisplayNotificationColumn { get; set; }
public bool DisplayNotificationTimer { get; set; }
public bool NotificationTimerCountDown { get; set; }
public bool NotificationSkipOnLinkClick { get; set; }
public bool NotificationNonIntrusiveMode { get; set; }
public int NotificationIdlePauseSeconds { get; set; }
public int NotificationDurationValue { get; set; }
public int NotificationScrollSpeed { get; set; }
public TweetNotification.Position NotificationPosition { get; set; }
public Point CustomNotificationPosition { get; set; }
public int NotificationEdgeDistance { get; set; }
public int NotificationDisplay { get; set; }
public TweetNotification.Size NotificationSize { get; set; }
public Size CustomNotificationSize { get; set; }
public bool EnableSpellCheck { get; set; }
public bool ExpandLinksOnHover { get; set; }
public bool SwitchAccountSelectors { get; set; }
public bool EnableTrayHighlight { get; set; }
public bool EnableUpdateCheck { get; set; }
public string DismissedUpdate { get; set; }
public string CustomCefArgs { get; set; }
public string CustomBrowserCSS { get; set; }
public string CustomNotificationCSS { get; set; }
public string NotificationSoundPath{
get => string.IsNullOrEmpty(notificationSoundPath) ? string.Empty : notificationSoundPath;
set => notificationSoundPath = value;
}
public bool MuteNotifications{
get => muteNotifications;
set => muteNotifications = value;
}
public int ZoomLevel{
get => zoomLevel;
set => zoomLevel = value;
}
public TrayIcon.Behavior TrayBehavior{
get => trayBehavior;
set => trayBehavior = value;
}
// END OF CONFIGURATION
[NonSerialized]
private string file;
private int fileVersion;
private bool muteNotifications;
private int zoomLevel;
private string notificationSoundPath;
private TrayIcon.Behavior trayBehavior;
private UserConfigLegacy(string file){
this.file = file;
BrowserWindow = new WindowState();
ZoomLevel = 100;
DisplayNotificationTimer = true;
NotificationNonIntrusiveMode = true;
NotificationPosition = TweetNotification.Position.TopRight;
CustomNotificationPosition = ControlExtensions.InvisibleLocation;
NotificationSize = TweetNotification.Size.Auto;
NotificationEdgeDistance = 8;
NotificationDurationValue = 25;
NotificationScrollSpeed = 100;
EnableUpdateCheck = true;
ExpandLinksOnHover = true;
SwitchAccountSelectors = true;
EnableTrayHighlight = true;
PluginsWindow = new WindowState();
}
private void UpgradeFile(){
if (fileVersion == CurrentFileVersion){
return;
}
// if outdated, cycle through all versions
if (fileVersion <= 5){
DisplayNotificationTimer = true;
EnableUpdateCheck = true;
ExpandLinksOnHover = true;
BrowserWindow = new WindowState();
PluginsWindow = new WindowState();
EnableTrayHighlight = true;
NotificationDurationValue = 25;
fileVersion = 6;
}
if (fileVersion == 6){
NotificationNonIntrusiveMode = true;
++fileVersion;
}
if (fileVersion == 7){
ZoomLevel = 100;
++fileVersion;
}
if (fileVersion == 8){
SwitchAccountSelectors = true;
++fileVersion;
}
if (fileVersion == 9){
NotificationScrollSpeed = 100;
++fileVersion;
}
if (fileVersion == 10){
NotificationSize = TweetNotification.Size.Auto;
++fileVersion;
}
// update the version
fileVersion = CurrentFileVersion;
}
public UserConfig ConvertLegacy(){
return new UserConfig(file){
BrowserWindow = BrowserWindow,
PluginsWindow = PluginsWindow,
DisplayNotificationColumn = DisplayNotificationColumn,
DisplayNotificationTimer = DisplayNotificationTimer,
NotificationTimerCountDown = NotificationTimerCountDown,
NotificationSkipOnLinkClick = NotificationSkipOnLinkClick,
NotificationNonIntrusiveMode = NotificationNonIntrusiveMode,
NotificationIdlePauseSeconds = NotificationIdlePauseSeconds,
NotificationDurationValue = NotificationDurationValue,
NotificationScrollSpeed = NotificationScrollSpeed,
NotificationPosition = NotificationPosition,
CustomNotificationPosition = CustomNotificationPosition,
NotificationEdgeDistance = NotificationEdgeDistance,
NotificationDisplay = NotificationDisplay,
NotificationSize = NotificationSize,
CustomNotificationSize = CustomNotificationSize,
EnableSpellCheck = EnableSpellCheck,
ExpandLinksOnHover = ExpandLinksOnHover,
SwitchAccountSelectors = SwitchAccountSelectors,
EnableTrayHighlight = EnableTrayHighlight,
EnableUpdateCheck = EnableUpdateCheck,
DismissedUpdate = DismissedUpdate,
CustomCefArgs = CustomCefArgs,
CustomBrowserCSS = CustomBrowserCSS,
CustomNotificationCSS = CustomNotificationCSS,
NotificationSoundPath = NotificationSoundPath,
MuteNotifications = MuteNotifications,
ZoomLevel = ZoomLevel,
TrayBehavior = TrayBehavior
};
}
public static UserConfig Load(string file){
UserConfigLegacy config = null;
try{
using(Stream stream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read)){
if ((config = Formatter.Deserialize(stream) as UserConfigLegacy) != null){
config.file = file;
}
}
config?.UpgradeFile();
}catch(FileNotFoundException){
}catch(DirectoryNotFoundException){
}catch(Exception e){
Program.Reporter.HandleException("Configuration Error", "Could not open the configuration file.", true, e);
}
return (config ?? new UserConfigLegacy(file)).ConvertLegacy();
}
}
}

View File

@@ -1,45 +1,32 @@
using System; using System.Text;
using System.Text;
namespace TweetDuck.Core.Bridge{ namespace TweetDuck.Core.Bridge{
static class PropertyBridge{ static class PropertyBridge{
[Flags] public enum Environment{
public enum Properties{ Browser, Notification
ExpandLinksOnHover = 1,
MuteNotifications = 2,
HasCustomNotificationSound = 4,
SkipOnLinkClick = 8,
SwitchAccountSelectors = 16,
AllBrowser = ExpandLinksOnHover | SwitchAccountSelectors | MuteNotifications | HasCustomNotificationSound,
AllNotification = ExpandLinksOnHover | SkipOnLinkClick
} }
public static string GenerateScript(Properties properties){ public static string GenerateScript(Environment environment){
StringBuilder build = new StringBuilder(); string Bool(bool value){
build.Append("(function(c){"); return value ? "true;" : "false;";
if (properties.HasFlag(Properties.ExpandLinksOnHover)){
build.Append("c.expandLinksOnHover=").Append(Program.UserConfig.ExpandLinksOnHover ? "true;" : "false;");
} }
if (properties.HasFlag(Properties.SwitchAccountSelectors)){ StringBuilder build = new StringBuilder().Append("(function(x){");
build.Append("c.switchAccountSelectors=").Append(Program.UserConfig.SwitchAccountSelectors ? "true;" : "false;");
build.Append("x.expandLinksOnHover=").Append(Bool(Program.UserConfig.ExpandLinksOnHover));
if (environment == Environment.Browser){
build.Append("x.switchAccountSelectors=").Append(Bool(Program.UserConfig.SwitchAccountSelectors));
build.Append("x.muteNotifications=").Append(Bool(Program.UserConfig.MuteNotifications));
build.Append("x.hasCustomNotificationSound=").Append(Bool(Program.UserConfig.NotificationSoundPath.Length > 0));
build.Append("x.notificationMediaPreviews=").Append(Bool(Program.UserConfig.NotificationMediaPreviews));
} }
if (properties.HasFlag(Properties.MuteNotifications)){ if (environment == Environment.Notification){
build.Append("c.muteNotifications=").Append(Program.UserConfig.MuteNotifications ? "true;" : "false;"); build.Append("x.skipOnLinkClick=").Append(Bool(Program.UserConfig.NotificationSkipOnLinkClick));
} }
if (properties.HasFlag(Properties.HasCustomNotificationSound)){ return build.Append("})(window.$TDX=window.$TDX||{})").ToString();
build.Append("c.hasCustomNotificationSound=").Append(Program.UserConfig.NotificationSoundPath.Length > 0 ? "true;" : "false;");
}
if (properties.HasFlag(Properties.SkipOnLinkClick)){
build.Append("c.skipOnLinkClick=").Append(Program.UserConfig.NotificationSkipOnLinkClick ? "true;" : "false;");
}
build.Append("})(window.$TDX=window.$TDX||{})");
return build.ToString();
} }
} }
} }

View File

@@ -1,16 +1,39 @@
using System.Windows.Forms; using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using CefSharp;
using TweetDuck.Core.Controls; using TweetDuck.Core.Controls;
using TweetDuck.Core.Notification; using TweetDuck.Core.Notification;
using TweetDuck.Core.Other;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
using TweetDuck.Resources;
namespace TweetDuck.Core.Bridge{ namespace TweetDuck.Core.Bridge{
sealed class TweetDeckBridge{ sealed class TweetDeckBridge{
public static string LastRightClickedLink = string.Empty; public static string LastRightClickedLink = string.Empty;
public static string LastRightClickedImage = string.Empty;
public static string LastHighlightedTweet = string.Empty; public static string LastHighlightedTweet = string.Empty;
public static string LastHighlightedQuotedTweet = string.Empty; public static string LastHighlightedQuotedTweet = string.Empty;
public static string LastHighlightedTweetAuthor = string.Empty;
public static string[] LastHighlightedTweetImages = StringUtils.EmptyArray;
public static Dictionary<string, string> SessionData = new Dictionary<string, string>(2);
public static void ResetStaticProperties(){ public static void ResetStaticProperties(){
LastRightClickedLink = LastHighlightedTweet = LastHighlightedQuotedTweet = string.Empty; LastRightClickedLink = LastRightClickedImage = LastHighlightedTweet = LastHighlightedQuotedTweet = LastHighlightedTweetAuthor = string.Empty;
LastHighlightedTweetImages = StringUtils.EmptyArray;
}
public static void RestoreSessionData(IFrame frame){
if (SessionData.Count > 0){
StringBuilder build = new StringBuilder().Append("window.TD_SESSION={");
foreach(KeyValuePair<string, string> kvp in SessionData){
build.Append(kvp.Key).Append(":'").Append(kvp.Value.Replace("'", "\\'")).Append("',");
}
ScriptLoader.ExecuteScript(frame, build.Append("}").ToString(), "gen:session");
SessionData.Clear();
}
} }
private readonly FormBrowser form; private readonly FormBrowser form;
@@ -37,10 +60,16 @@ namespace TweetDuck.Core.Bridge{
form.InvokeAsyncSafe(() => LastRightClickedLink = link); form.InvokeAsyncSafe(() => LastRightClickedLink = link);
} }
public void SetLastHighlightedTweet(string link, string quotedLink){ public void SetLastRightClickedImage(string link){
form.InvokeAsyncSafe(() => LastRightClickedImage = link);
}
public void SetLastHighlightedTweet(string link, string quotedLink, string author, string imageList){
form.InvokeAsyncSafe(() => { form.InvokeAsyncSafe(() => {
LastHighlightedTweet = link; LastHighlightedTweet = link;
LastHighlightedQuotedTweet = quotedLink; LastHighlightedQuotedTweet = quotedLink;
LastHighlightedTweetAuthor = author;
LastHighlightedTweetImages = imageList.Split(';');
}); });
} }
@@ -71,6 +100,12 @@ namespace TweetDuck.Core.Bridge{
} }
} }
public void SetSessionData(string key, string value){
form.InvokeSafe(() => { // do not use InvokeAsyncSafe, return only after invocation
SessionData.Add(key, value);
});
}
public void LoadNextNotification(){ public void LoadNextNotification(){
notification.InvokeAsyncSafe(notification.FinishCurrentNotification); notification.InvokeAsyncSafe(notification.FinishCurrentNotification);
} }
@@ -79,6 +114,10 @@ namespace TweetDuck.Core.Bridge{
form.InvokeAsyncSafe(() => form.OnTweetScreenshotReady(html, width, height)); form.InvokeAsyncSafe(() => form.OnTweetScreenshotReady(html, width, height));
} }
public void PlayVideo(string url){
form.InvokeAsyncSafe(() => form.PlayVideo(url));
}
public void FixClipboard(){ public void FixClipboard(){
form.InvokeAsyncSafe(WindowsUtils.ClipboardStripHtmlStyles); form.InvokeAsyncSafe(WindowsUtils.ClipboardStripHtmlStyles);
} }
@@ -101,7 +140,7 @@ namespace TweetDuck.Core.Bridge{
default: icon = MessageBoxIcon.None; break; default: icon = MessageBoxIcon.None; break;
} }
MessageBox.Show(contents, Program.BrandName+" Browser Message", MessageBoxButtons.OK, icon); FormMessage.Show("TweetDuck Browser Message", contents, icon, FormMessage.OK);
} }
public void CrashDebug(string message){ public void CrashDebug(string message){

View File

@@ -21,6 +21,12 @@ namespace TweetDuck.Core.Controls{
control.BeginInvoke(func); control.BeginInvoke(func);
} }
public static float GetDPIScale(this Control control){
using(Graphics graphics = control.CreateGraphics()){
return graphics.DpiY/96F;
}
}
public static bool IsFullyOutsideView(this Form form){ public static bool IsFullyOutsideView(this Form form){
return !Screen.AllScreens.Any(screen => screen.WorkingArea.IntersectsWith(form.Bounds)); return !Screen.AllScreens.Any(screen => screen.WorkingArea.IntersectsWith(form.Bounds));
} }
@@ -41,6 +47,12 @@ namespace TweetDuck.Core.Controls{
} }
} }
public static void SetValueSafe(this NumericUpDown numUpDown, int value){
if (value >= numUpDown.Minimum && value <= numUpDown.Maximum){
numUpDown.Value = value;
}
}
public static void SetValueSafe(this TrackBar trackBar, int value){ public static void SetValueSafe(this TrackBar trackBar, int value){
if (value >= trackBar.Minimum && value <= trackBar.Maximum){ if (value >= trackBar.Minimum && value <= trackBar.Maximum){
trackBar.Value = value; trackBar.Value = value;
@@ -58,7 +70,7 @@ namespace TweetDuck.Core.Controls{
public static void SetElevated(this Button button){ public static void SetElevated(this Button button){
button.Text = " "+button.Text; button.Text = " "+button.Text;
button.FlatStyle = FlatStyle.System; button.FlatStyle = FlatStyle.System;
NativeMethods.SendMessage(button.Handle, NativeMethods.BCM_SETSHIELD, 0, new IntPtr(1)); NativeMethods.SendMessage(button.Handle, NativeMethods.BCM_SETSHIELD, new UIntPtr(0), new IntPtr(1));
} }
public static void EnableMultilineShortcuts(this TextBox textBox){ public static void EnableMultilineShortcuts(this TextBox textBox){

View File

@@ -23,7 +23,7 @@ namespace TweetDuck.Core.Controls{
Rectangle rect = e.ClipRectangle; Rectangle rect = e.ClipRectangle;
rect.Width = (int)(rect.Width*((double)Value/Maximum)); rect.Width = (int)(rect.Width*((double)Value/Maximum));
e.Graphics.FillRectangle(brush,rect); e.Graphics.FillRectangle(brush, rect);
} }
protected override void Dispose(bool disposing){ protected override void Dispose(bool disposing){

View File

@@ -0,0 +1,18 @@
using System.ComponentModel;
using System.Windows.Forms;
namespace TweetDuck.Core.Controls{
sealed class NumericUpDownEx : NumericUpDown{
public string TextSuffix { get; set ; }
protected override void UpdateEditText(){
base.UpdateEditText();
if (LicenseManager.UsageMode != LicenseUsageMode.Designtime){
ChangingText = true;
Text += TextSuffix;
ChangingText = false;
}
}
}
}

View File

@@ -26,13 +26,19 @@
this.components = new System.ComponentModel.Container(); this.components = new System.ComponentModel.Container();
this.trayIcon = new TweetDuck.Core.TrayIcon(this.components); this.trayIcon = new TweetDuck.Core.TrayIcon(this.components);
this.toolTip = new System.Windows.Forms.ToolTip(this.components); this.toolTip = new System.Windows.Forms.ToolTip(this.components);
this.timerResize = new System.Windows.Forms.Timer(this.components);
this.SuspendLayout(); this.SuspendLayout();
// //
// timerResize
//
this.timerResize.Interval = 500;
this.timerResize.Tick += new System.EventHandler(this.timerResize_Tick);
//
// FormBrowser // FormBrowser
// //
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.BackColor = TweetDuck.Core.Utils.BrowserUtils.BackgroundColor; this.BackColor = TweetDuck.Core.Utils.TwitterUtils.BackgroundColor;
this.ClientSize = new System.Drawing.Size(324, 386); this.ClientSize = new System.Drawing.Size(324, 386);
this.Icon = Properties.Resources.icon; this.Icon = Properties.Resources.icon;
this.Location = TweetDuck.Core.Controls.ControlExtensions.InvisibleLocation; this.Location = TweetDuck.Core.Controls.ControlExtensions.InvisibleLocation;
@@ -42,6 +48,7 @@
this.Activated += new System.EventHandler(this.FormBrowser_Activated); this.Activated += new System.EventHandler(this.FormBrowser_Activated);
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.FormBrowser_FormClosing); this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.FormBrowser_FormClosing);
this.FormClosed += new System.Windows.Forms.FormClosedEventHandler(this.FormBrowser_FormClosed); this.FormClosed += new System.Windows.Forms.FormClosedEventHandler(this.FormBrowser_FormClosed);
this.LocationChanged += new System.EventHandler(this.FormBrowser_LocationChanged);
this.ResizeEnd += new System.EventHandler(this.FormBrowser_ResizeEnd); this.ResizeEnd += new System.EventHandler(this.FormBrowser_ResizeEnd);
this.Resize += new System.EventHandler(this.FormBrowser_Resize); this.Resize += new System.EventHandler(this.FormBrowser_Resize);
this.ResumeLayout(false); this.ResumeLayout(false);
@@ -52,6 +59,7 @@
private TrayIcon trayIcon; private TrayIcon trayIcon;
private System.Windows.Forms.ToolTip toolTip; private System.Windows.Forms.ToolTip toolTip;
private System.Windows.Forms.Timer timerResize;
} }
} }

View File

@@ -1,17 +1,17 @@
using CefSharp; using CefSharp;
using CefSharp.WinForms; using CefSharp.WinForms;
using System; using System;
using System.Diagnostics;
using System.Drawing; using System.Drawing;
using System.Linq;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Configuration; using TweetDuck.Configuration;
using TweetDuck.Core.Bridge; using TweetDuck.Core.Bridge;
using TweetDuck.Core.Controls; using TweetDuck.Core.Controls;
using TweetDuck.Core.Handling; using TweetDuck.Core.Handling;
using TweetDuck.Core.Handling.General;
using TweetDuck.Core.Notification; using TweetDuck.Core.Notification;
using TweetDuck.Core.Notification.Screenshot; using TweetDuck.Core.Notification.Screenshot;
using TweetDuck.Core.Other; using TweetDuck.Core.Other;
using TweetDuck.Core.Other.Management;
using TweetDuck.Core.Other.Settings; using TweetDuck.Core.Other.Settings;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
using TweetDuck.Plugins; using TweetDuck.Plugins;
@@ -20,12 +20,29 @@ using TweetDuck.Plugins.Events;
using TweetDuck.Resources; using TweetDuck.Resources;
using TweetDuck.Updates; using TweetDuck.Updates;
using TweetDuck.Updates.Events; using TweetDuck.Updates.Events;
using TweetLib.Audio.Utils; using TweetLib.Audio;
namespace TweetDuck.Core{ namespace TweetDuck.Core{
sealed partial class FormBrowser : Form{ sealed partial class FormBrowser : Form{
private static UserConfig Config => Program.UserConfig; private static UserConfig Config => Program.UserConfig;
public bool IsWaiting{
set{
if (value){
browser.Enabled = false;
Cursor = Cursors.WaitCursor;
}
else{
browser.Enabled = true;
Cursor = Cursors.Default;
if (Focused){ // re-focus browser only if the window or a child is activated
browser.Focus();
}
}
}
}
public string UpdateInstallerPath { get; private set; } public string UpdateInstallerPath { get; private set; }
private readonly ChromiumWebBrowser browser; private readonly ChromiumWebBrowser browser;
@@ -33,6 +50,7 @@ namespace TweetDuck.Core{
private readonly UpdateHandler updates; private readonly UpdateHandler updates;
private readonly FormNotificationTweet notification; private readonly FormNotificationTweet notification;
private readonly ContextMenu contextMenu; private readonly ContextMenu contextMenu;
private readonly MemoryUsageTracker memoryUsageTracker;
private bool isLoaded; private bool isLoaded;
private bool isBrowserReady; private bool isBrowserReady;
@@ -40,17 +58,21 @@ namespace TweetDuck.Core{
private TweetScreenshotManager notificationScreenshotManager; private TweetScreenshotManager notificationScreenshotManager;
private SoundNotification soundNotification; private SoundNotification soundNotification;
private VideoPlayer videoPlayer;
public FormBrowser(PluginManager pluginManager, UpdaterSettings updaterSettings){ public FormBrowser(UpdaterSettings updaterSettings){
InitializeComponent(); InitializeComponent();
Text = Program.BrandName; Text = Program.BrandName;
this.plugins = pluginManager; this.plugins = new PluginManager(Program.PluginPath, Program.PluginConfigFilePath);
this.plugins.Reloaded += plugins_Reloaded; this.plugins.Reloaded += plugins_Reloaded;
this.plugins.Executed += plugins_Executed;
this.plugins.PluginChangedState += plugins_PluginChangedState; this.plugins.PluginChangedState += plugins_PluginChangedState;
this.plugins.Reload();
this.contextMenu = ContextMenuBrowser.CreateMenu(this); this.contextMenu = ContextMenuBrowser.CreateMenu(this);
this.memoryUsageTracker = new MemoryUsageTracker("TDGF_tryRunCleanup");
this.notification = new FormNotificationTweet(this, plugins){ this.notification = new FormNotificationTweet(this, plugins){
#if DEBUG #if DEBUG
@@ -80,7 +102,7 @@ namespace TweetDuck.Core{
this.browser.RegisterAsyncJsObject("$TD", new TweetDeckBridge(this, notification)); this.browser.RegisterAsyncJsObject("$TD", new TweetDeckBridge(this, notification));
this.browser.RegisterAsyncJsObject("$TDP", plugins.Bridge); this.browser.RegisterAsyncJsObject("$TDP", plugins.Bridge);
browser.BrowserSettings.BackgroundColor = (uint)BrowserUtils.BackgroundColor.ToArgb(); browser.BrowserSettings.BackgroundColor = (uint)TwitterUtils.BackgroundColor.ToArgb();
browser.Dock = DockStyle.None; browser.Dock = DockStyle.None;
browser.Location = ControlExtensions.InvisibleLocation; browser.Location = ControlExtensions.InvisibleLocation;
Controls.Add(browser); Controls.Add(browser);
@@ -88,11 +110,14 @@ namespace TweetDuck.Core{
Controls.Add(new MenuStrip{ Visible = false }); // fixes Alt freezing the program in Win 10 Anniversary Update Controls.Add(new MenuStrip{ Visible = false }); // fixes Alt freezing the program in Win 10 Anniversary Update
Disposed += (sender, args) => { Disposed += (sender, args) => {
memoryUsageTracker.Dispose();
browser.Dispose(); browser.Dispose();
contextMenu.Dispose(); contextMenu.Dispose();
notificationScreenshotManager?.Dispose(); notificationScreenshotManager?.Dispose();
soundNotification?.Dispose(); soundNotification?.Dispose();
videoPlayer?.Dispose();
}; };
this.trayIcon.ClickRestore += trayIcon_ClickRestore; this.trayIcon.ClickRestore += trayIcon_ClickRestore;
@@ -104,23 +129,13 @@ namespace TweetDuck.Core{
Config.MuteToggled += Config_MuteToggled; Config.MuteToggled += Config_MuteToggled;
Config.ZoomLevelChanged += Config_ZoomLevelChanged; Config.ZoomLevelChanged += Config_ZoomLevelChanged;
this.updates = new UpdateHandler(browser, this, updaterSettings); this.updates = new UpdateHandler(browser, updaterSettings);
this.updates.UpdateAccepted += updates_UpdateAccepted; this.updates.UpdateAccepted += updates_UpdateAccepted;
this.updates.UpdateDismissed += updates_UpdateDismissed; this.updates.UpdateDismissed += updates_UpdateDismissed;
RestoreWindow(); RestoreWindow();
} }
private bool TryBringToFront<T>() where T : Form{
T form = Application.OpenForms.OfType<T>().FirstOrDefault();
if (form != null){
form.BringToFront();
return true;
}
else return false;
}
private void ShowChildForm(Form form){ private void ShowChildForm(Form form){
form.VisibleChanged += (sender, args) => form.MoveToCenter(this); form.VisibleChanged += (sender, args) => form.MoveToCenter(this);
form.Show(this); form.Show(this);
@@ -155,7 +170,7 @@ namespace TweetDuck.Core{
private void browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e){ private void browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e){
if (!e.IsLoading){ if (!e.IsLoading){
foreach(string word in BrowserUtils.DictionaryWords){ foreach(string word in TwitterUtils.DictionaryWords){
browser.AddWordToDictionary(word); browser.AddWordToDictionary(word);
} }
@@ -166,31 +181,33 @@ namespace TweetDuck.Core{
private void browser_FrameLoadStart(object sender, FrameLoadStartEventArgs e){ private void browser_FrameLoadStart(object sender, FrameLoadStartEventArgs e){
if (e.Frame.IsMain){ if (e.Frame.IsMain){
memoryUsageTracker.Stop();
if (Config.ZoomLevel != 100){ if (Config.ZoomLevel != 100){
BrowserUtils.SetZoomLevel(browser.GetBrowser(), Config.ZoomLevel); BrowserUtils.SetZoomLevel(browser.GetBrowser(), Config.ZoomLevel);
} }
if (BrowserUtils.IsTwitterWebsite(e.Frame)){ if (TwitterUtils.IsTwitterWebsite(e.Frame)){
ScriptLoader.ExecuteFile(e.Frame, "twitter.js"); ScriptLoader.ExecuteFile(e.Frame, "twitter.js");
} }
} }
} }
private void browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){ private void browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
if (e.Frame.IsMain && BrowserUtils.IsTweetDeckWebsite(e.Frame)){ if (e.Frame.IsMain && TwitterUtils.IsTweetDeckWebsite(e.Frame)){
e.Frame.ExecuteJavaScriptAsync(BrowserUtils.BackgroundColorFix); e.Frame.ExecuteJavaScriptAsync(TwitterUtils.BackgroundColorFix);
UpdateProperties(PropertyBridge.Properties.AllBrowser); UpdateProperties(PropertyBridge.Environment.Browser);
TweetDeckBridge.RestoreSessionData(e.Frame);
ScriptLoader.ExecuteFile(e.Frame, "code.js"); ScriptLoader.ExecuteFile(e.Frame, "code.js");
ReinjectCustomCSS(Config.CustomBrowserCSS); ReinjectCustomCSS(Config.CustomBrowserCSS);
plugins.ExecutePlugins(e.Frame, PluginEnvironment.Browser);
if (plugins.HasAnyPlugin(PluginEnvironment.Browser)){
ScriptLoader.ExecuteFile(e.Frame, PluginManager.PluginBrowserScriptFile);
ScriptLoader.ExecuteFile(e.Frame, PluginManager.PluginGlobalScriptFile);
plugins.ExecutePlugins(e.Frame, PluginEnvironment.Browser, true);
}
TweetDeckBridge.ResetStaticProperties(); TweetDeckBridge.ResetStaticProperties();
if (Program.SystemConfig.EnableBrowserGCReload){
memoryUsageTracker.Start(this, e.Browser, Program.SystemConfig.BrowserMemoryThreshold);
}
} }
} }
@@ -199,7 +216,7 @@ namespace TweetDuck.Core{
return; return;
} }
if (!e.FailedUrl.StartsWith("http://td/")){ if (!e.FailedUrl.StartsWith("http://td/", StringComparison.Ordinal)){
string errorPage = ScriptLoader.LoadResource("pages/error.html", true); string errorPage = ScriptLoader.LoadResource("pages/error.html", true);
if (errorPage != null){ if (errorPage != null){
@@ -208,10 +225,25 @@ namespace TweetDuck.Core{
} }
} }
private void timerResize_Tick(object sender, EventArgs e){
FormBrowser_ResizeEnd(this, e); // also stops timer
}
private void FormBrowser_Activated(object sender, EventArgs e){ private void FormBrowser_Activated(object sender, EventArgs e){
if (!isLoaded)return; if (!isLoaded)return;
trayIcon.HasNotifications = false; trayIcon.HasNotifications = false;
if (!browser.Enabled){ // when taking a screenshot, the window is unfocused and
browser.Enabled = true; // the browser is disabled; if the user clicks back into
} // the window, enable the browser again
}
private void FormBrowser_LocationChanged(object sender, EventArgs e){
if (!isLoaded)return;
timerResize.Stop();
timerResize.Start();
} }
private void FormBrowser_Resize(object sender, EventArgs e){ private void FormBrowser_Resize(object sender, EventArgs e){
@@ -229,11 +261,17 @@ namespace TweetDuck.Core{
FormBrowser_ResizeEnd(sender, e); FormBrowser_ResizeEnd(sender, e);
} }
} }
else{
timerResize.Stop();
timerResize.Start();
}
} }
private void FormBrowser_ResizeEnd(object sender, EventArgs e){ // also triggers when the window moves private void FormBrowser_ResizeEnd(object sender, EventArgs e){ // also triggers when the window moves
if (!isLoaded)return; if (!isLoaded)return;
timerResize.Stop();
if (Location != ControlExtensions.InvisibleLocation){ if (Location != ControlExtensions.InvisibleLocation){
Config.BrowserWindow.Save(this); Config.BrowserWindow.Save(this);
Config.Save(); Config.Save();
@@ -256,7 +294,7 @@ namespace TweetDuck.Core{
} }
private void Config_MuteToggled(object sender, EventArgs e){ private void Config_MuteToggled(object sender, EventArgs e){
UpdateProperties(PropertyBridge.Properties.MuteNotifications); UpdateProperties(PropertyBridge.Environment.Browser);
} }
private void Config_ZoomLevelChanged(object sender, EventArgs e){ private void Config_ZoomLevelChanged(object sender, EventArgs e){
@@ -279,7 +317,19 @@ namespace TweetDuck.Core{
} }
private void plugins_Reloaded(object sender, PluginErrorEventArgs e){ private void plugins_Reloaded(object sender, PluginErrorEventArgs e){
browser.GetBrowser().Reload(); if (e.HasErrors){
FormMessage.Error("Error Loading Plugins", "The following plugins will not be available until the issues are resolved:\n\n"+string.Join("\n\n", e.Errors), FormMessage.OK);
}
if (isLoaded){
ReloadToTweetDeck();
}
}
private static void plugins_Executed(object sender, PluginErrorEventArgs e){
if (e.HasErrors){
FormMessage.Error("Error Executing Plugins", "Failed to execute the following plugins:\n\n"+string.Join("\n\n", e.Errors), FormMessage.OK);
}
} }
private void plugins_PluginChangedState(object sender, PluginChangedStateEventArgs e){ private void plugins_PluginChangedState(object sender, PluginChangedStateEventArgs e){
@@ -287,11 +337,8 @@ namespace TweetDuck.Core{
} }
private void updates_UpdateAccepted(object sender, UpdateAcceptedEventArgs e){ private void updates_UpdateAccepted(object sender, UpdateAcceptedEventArgs e){
foreach(Form form in Application.OpenForms.Cast<Form>().Reverse()){ this.InvokeAsyncSafe(() => {
if (form is FormSettings || form is FormPlugins || form is FormAbout){ FormManager.CloseAllDialogs();
form.Close();
}
}
updates.BeginUpdateDownload(this, e.UpdateInfo, update => { updates.BeginUpdateDownload(this, e.UpdateInfo, update => {
if (update.DownloadStatus == UpdateDownloadStatus.Done){ if (update.DownloadStatus == UpdateDownloadStatus.Done){
@@ -300,18 +347,21 @@ namespace TweetDuck.Core{
ForceClose(); ForceClose();
}); });
});
} }
private void updates_UpdateDismissed(object sender, UpdateDismissedEventArgs e){ private void updates_UpdateDismissed(object sender, UpdateDismissedEventArgs e){
this.InvokeAsyncSafe(() => {
Config.DismissedUpdate = e.VersionTag; Config.DismissedUpdate = e.VersionTag;
Config.Save(); Config.Save();
});
} }
private void soundNotification_PlaybackError(object sender, PlaybackErrorEventArgs e){ private void soundNotification_PlaybackError(object sender, PlaybackErrorEventArgs e){
e.Ignore = true; e.Ignore = true;
using(FormMessage form = new FormMessage("Notification Sound Error", "Could not play custom notification sound."+Environment.NewLine+e.Message, MessageBoxIcon.Error)){ using(FormMessage form = new FormMessage("Notification Sound Error", "Could not play custom notification sound.\n"+e.Message, MessageBoxIcon.Error)){
form.CancelButton = form.AddButton("Ignore"); form.AddButton(FormMessage.Ignore, ControlType.Cancel | ControlType.Focused);
Button btnOpenSettings = form.AddButton("View Options"); Button btnOpenSettings = form.AddButton("View Options");
btnOpenSettings.Width += 16; btnOpenSettings.Width += 16;
@@ -324,18 +374,43 @@ namespace TweetDuck.Core{
} }
protected override void WndProc(ref Message m){ protected override void WndProc(ref Message m){
if (isLoaded && m.Msg == Program.WindowRestoreMessage){ if (isLoaded){
using(Process process = Process.GetCurrentProcess()){ if (m.Msg == Program.WindowRestoreMessage){
if (process.Id == m.WParam.ToInt32()){ if (WindowsUtils.CurrentProcessID == m.WParam.ToInt32()){
trayIcon_ClickRestore(trayIcon, new EventArgs()); trayIcon_ClickRestore(trayIcon, new EventArgs());
} }
}
return; return;
} }
else if (m.Msg == Program.SubProcessMessage){
int processId = m.WParam.ToInt32();
if (WindowsUtils.IsChildProcess(processId)){ // child process is checked in two places for safety
BrowserProcesses.Link(m.LParam.ToInt32(), processId);
}
return;
}
else if (m.Msg == Program.VideoPlayerMessage){
int volume = m.WParam.ToInt32();
if (Handle == m.LParam && volume != Config.VideoPlayerVolume){
Config.VideoPlayerVolume = volume;
Config.Save();
}
return;
}
}
if (isBrowserReady && m.Msg == NativeMethods.WM_PARENTNOTIFY && (m.WParam.ToInt32() & 0xFFFF) == NativeMethods.WM_XBUTTONDOWN){ if (isBrowserReady && m.Msg == NativeMethods.WM_PARENTNOTIFY && (m.WParam.ToInt32() & 0xFFFF) == NativeMethods.WM_XBUTTONDOWN){
if (videoPlayer != null && videoPlayer.Running){
videoPlayer.Close();
}
else{
browser.ExecuteScriptAsync("TDGF_onMouseClickExtra", (m.WParam.ToInt32() >> 16) & 0xFFFF); browser.ExecuteScriptAsync("TDGF_onMouseClickExtra", (m.WParam.ToInt32() >> 16) & 0xFFFF);
}
return; return;
} }
@@ -362,12 +437,12 @@ namespace TweetDuck.Core{
browser.ExecuteScriptAsync("TDGF_reinjectCustomCSS", css?.Replace(Environment.NewLine, " ") ?? string.Empty); browser.ExecuteScriptAsync("TDGF_reinjectCustomCSS", css?.Replace(Environment.NewLine, " ") ?? string.Empty);
} }
public void UpdateProperties(PropertyBridge.Properties properties){ public void UpdateProperties(PropertyBridge.Environment environment){
browser.ExecuteScriptAsync(PropertyBridge.GenerateScript(properties)); browser.ExecuteScriptAsync(PropertyBridge.GenerateScript(environment));
} }
public void ReloadToTweetDeck(){ public void ReloadToTweetDeck(){
browser.ExecuteScriptAsync("window.location.href = 'https://tweetdeck.twitter.com'"); browser.ExecuteScriptAsync($"if(window.TDGF_reload)window.TDGF_reload();else window.location.href='{TwitterUtils.TweetDeckURL}'");
} }
// callback handlers // callback handlers
@@ -381,7 +456,7 @@ namespace TweetDuck.Core{
} }
public void OpenSettings(Type startTab){ public void OpenSettings(Type startTab){
if (!TryBringToFront<FormSettings>()){ if (!FormManager.TryBringToFront<FormSettings>()){
bool prevEnableUpdateCheck = Config.EnableUpdateCheck; bool prevEnableUpdateCheck = Config.EnableUpdateCheck;
FormSettings form = new FormSettings(this, plugins, updates, startTab); FormSettings form = new FormSettings(this, plugins, updates, startTab);
@@ -396,7 +471,16 @@ namespace TweetDuck.Core{
trayIcon.HasNotifications = false; trayIcon.HasNotifications = false;
} }
UpdateProperties(PropertyBridge.Properties.ExpandLinksOnHover | PropertyBridge.Properties.SwitchAccountSelectors | PropertyBridge.Properties.HasCustomNotificationSound); if (Program.SystemConfig.EnableBrowserGCReload){
memoryUsageTracker.Start(this, browser.GetBrowser(), Program.SystemConfig.BrowserMemoryThreshold);
}
else{
memoryUsageTracker.Stop();
}
UpdateProperties(PropertyBridge.Environment.Browser);
notification.RequiresResize = true;
form.Dispose(); form.Dispose();
}; };
@@ -405,13 +489,13 @@ namespace TweetDuck.Core{
} }
public void OpenAbout(){ public void OpenAbout(){
if (!TryBringToFront<FormAbout>()){ if (!FormManager.TryBringToFront<FormAbout>()){
ShowChildForm(new FormAbout()); ShowChildForm(new FormAbout());
} }
} }
public void OpenPlugins(){ public void OpenPlugins(){
if (!TryBringToFront<FormPlugins>()){ if (!FormManager.TryBringToFront<FormPlugins>()){
ShowChildForm(new FormPlugins(plugins)); ShowChildForm(new FormPlugins(plugins));
} }
} }
@@ -435,9 +519,31 @@ namespace TweetDuck.Core{
soundNotification.Play(Config.NotificationSoundPath); soundNotification.Play(Config.NotificationSoundPath);
} }
public void PlayVideo(string url){
if (string.IsNullOrEmpty(url)){
videoPlayer?.Close();
return;
}
if (videoPlayer == null){
videoPlayer = new VideoPlayer(this);
videoPlayer.ProcessExited += (sender, args) => {
browser.GetBrowser().GetHost().SendFocusEvent(true);
HideVideoOverlay();
};
}
videoPlayer.Launch(url);
}
public void HideVideoOverlay(){
browser.ExecuteScriptAsync("$('#td-video-player-overlay').remove()");
}
public void OnTweetScreenshotReady(string html, int width, int height){ public void OnTweetScreenshotReady(string html, int width, int height){
if (notificationScreenshotManager == null){ if (notificationScreenshotManager == null){
notificationScreenshotManager = new TweetScreenshotManager(this); notificationScreenshotManager = new TweetScreenshotManager(this, plugins);
} }
notificationScreenshotManager.Trigger(html, width, height); notificationScreenshotManager.Trigger(html, width, height);

View File

@@ -117,11 +117,13 @@
<resheader name="writer"> <resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader> </resheader>
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" /> <metadata name="trayIcon.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<data name="trayIcon.TrayLocation" type="System.Drawing.Point, System.Drawing">
<value>17, 17</value> <value>17, 17</value>
</data> </metadata>
<data name="toolTip.TrayLocation" type="System.Drawing.Point, System.Drawing"> <metadata name="toolTip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>112, 17</value> <value>112, 17</value>
</data> </metadata>
<metadata name="timerResize.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>202, 17</value>
</metadata>
</root> </root>

25
Core/FormManager.cs Normal file
View File

@@ -0,0 +1,25 @@
using System.Linq;
using System.Windows.Forms;
using TweetDuck.Core.Other;
namespace TweetDuck.Core{
static class FormManager{
public static bool TryBringToFront<T>() where T : Form{
T form = Application.OpenForms.OfType<T>().FirstOrDefault();
if (form != null){
form.BringToFront();
return true;
}
else return false;
}
public static void CloseAllDialogs(){
foreach(Form form in Application.OpenForms.Cast<Form>().Reverse()){
if (form is FormSettings || form is FormPlugins || form is FormAbout){
form.Close();
}
}
}
}
}

View File

@@ -1,34 +1,56 @@
using CefSharp; using System;
using System;
using System.IO; using System.IO;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Windows.Forms; using System.Windows.Forms;
using CefSharp;
using TweetDuck.Core.Bridge; using TweetDuck.Core.Bridge;
using TweetDuck.Core.Controls; using TweetDuck.Core.Controls;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
namespace TweetDuck.Core.Handling{ namespace TweetDuck.Core.Handling{
abstract class ContextMenuBase : IContextMenuHandler{ abstract class ContextMenuBase : IContextMenuHandler{
private static readonly Lazy<Regex> RegexTwitterAccount = new Lazy<Regex>(() => new Regex(@"^https?://twitter\.com/([^/]+)/?$", RegexOptions.Compiled), false);
protected static readonly bool HasDevTools = File.Exists(Path.Combine(Program.ProgramPath, "devtools_resources.pak")); protected static readonly bool HasDevTools = File.Exists(Path.Combine(Program.ProgramPath, "devtools_resources.pak"));
private static TwitterUtils.ImageQuality ImageQuality => Program.UserConfig.TwitterImageQuality;
private static string GetLink(IContextMenuParams parameters){
return string.IsNullOrEmpty(TweetDeckBridge.LastRightClickedLink) ? parameters.UnfilteredLinkUrl : TweetDeckBridge.LastRightClickedLink;
}
private static string GetImage(IContextMenuParams parameters){
return string.IsNullOrEmpty(TweetDeckBridge.LastRightClickedImage) ? parameters.SourceUrl : TweetDeckBridge.LastRightClickedImage;
}
private const int MenuOpenLinkUrl = 26500; private const int MenuOpenLinkUrl = 26500;
private const int MenuCopyLinkUrl = 26501; private const int MenuCopyLinkUrl = 26501;
private const int MenuCopyUsername = 26502; private const int MenuCopyUsername = 26502;
private const int MenuOpenImage = 26503; private const int MenuOpenImageUrl = 26503;
private const int MenuSaveImage = 26504; private const int MenuCopyImageUrl = 26504;
private const int MenuCopyImageUrl = 26505; private const int MenuSaveImage = 26505;
private const int MenuSaveAllImages = 26506;
private const int MenuOpenDevTools = 26599; private const int MenuOpenDevTools = 26599;
private readonly Form form; private readonly Form form;
private string lastHighlightedTweetAuthor;
private string[] lastHighlightedTweetImageList;
protected ContextMenuBase(Form form){ protected ContextMenuBase(Form form){
this.form = form; this.form = form;
} }
public virtual void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){ public virtual void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){
if (parameters.TypeFlags.HasFlag(ContextMenuType.Link) && !parameters.UnfilteredLinkUrl.EndsWith("tweetdeck.twitter.com/#", StringComparison.Ordinal)){ bool hasTweetImage = !string.IsNullOrEmpty(TweetDeckBridge.LastRightClickedImage);
if (RegexTwitterAccount.Value.IsMatch(parameters.UnfilteredLinkUrl)){ lastHighlightedTweetAuthor = TweetDeckBridge.LastHighlightedTweetAuthor;
lastHighlightedTweetImageList = TweetDeckBridge.LastHighlightedTweetImages;
if (!TwitterUtils.IsTweetDeckWebsite(frame) || browser.IsLoading){
lastHighlightedTweetAuthor = string.Empty;
lastHighlightedTweetImageList = StringUtils.EmptyArray;
}
if (parameters.TypeFlags.HasFlag(ContextMenuType.Link) && !parameters.UnfilteredLinkUrl.EndsWith("tweetdeck.twitter.com/#", StringComparison.Ordinal) && !hasTweetImage){
if (TwitterUtils.RegexAccount.IsMatch(parameters.UnfilteredLinkUrl)){
model.AddItem((CefMenuCommand)MenuOpenLinkUrl, "Open account in browser"); model.AddItem((CefMenuCommand)MenuOpenLinkUrl, "Open account in browser");
model.AddItem((CefMenuCommand)MenuCopyLinkUrl, "Copy account address"); model.AddItem((CefMenuCommand)MenuCopyLinkUrl, "Copy account address");
model.AddItem((CefMenuCommand)MenuCopyUsername, "Copy account username"); model.AddItem((CefMenuCommand)MenuCopyUsername, "Copy account username");
@@ -41,10 +63,15 @@ namespace TweetDuck.Core.Handling{
model.AddSeparator(); model.AddSeparator();
} }
if (parameters.TypeFlags.HasFlag(ContextMenuType.Media) && parameters.HasImageContents){ if ((parameters.TypeFlags.HasFlag(ContextMenuType.Media) && parameters.HasImageContents) || hasTweetImage){
model.AddItem((CefMenuCommand)MenuOpenImage, "Open image in browser"); model.AddItem((CefMenuCommand)MenuOpenImageUrl, "Open image in browser");
model.AddItem((CefMenuCommand)MenuSaveImage, "Save image as...");
model.AddItem((CefMenuCommand)MenuCopyImageUrl, "Copy image address"); model.AddItem((CefMenuCommand)MenuCopyImageUrl, "Copy image address");
model.AddItem((CefMenuCommand)MenuSaveImage, "Save image as...");
if (lastHighlightedTweetImageList.Length > 1){
model.AddItem((CefMenuCommand)MenuSaveAllImages, "Save all images as...");
}
model.AddSeparator(); model.AddSeparator();
} }
} }
@@ -56,42 +83,27 @@ namespace TweetDuck.Core.Handling{
break; break;
case MenuCopyLinkUrl: case MenuCopyLinkUrl:
SetClipboardText(string.IsNullOrEmpty(TweetDeckBridge.LastRightClickedLink) ? parameters.UnfilteredLinkUrl : TweetDeckBridge.LastRightClickedLink); SetClipboardText(GetLink(parameters));
break; break;
case MenuOpenImage: case MenuOpenImageUrl:
BrowserUtils.OpenExternalBrowser(parameters.SourceUrl); BrowserUtils.OpenExternalBrowser(TwitterUtils.GetImageLink(GetImage(parameters), ImageQuality));
break; break;
case MenuSaveImage: case MenuSaveImage:
string fileName = GetImageFileName(parameters.SourceUrl); TwitterUtils.DownloadImage(GetImage(parameters), lastHighlightedTweetAuthor, ImageQuality);
string extension = Path.GetExtension(fileName); break;
string saveTarget;
using(SaveFileDialog dialog = new SaveFileDialog{
AutoUpgradeEnabled = true,
OverwritePrompt = true,
Title = "Save image",
FileName = fileName,
Filter = "Image ("+(string.IsNullOrEmpty(extension) ? "unknown" : extension)+")|*.*"
}){
saveTarget = dialog.ShowDialog() == DialogResult.OK ? dialog.FileName : null;
}
if (saveTarget != null){
BrowserUtils.DownloadFileAsync(parameters.SourceUrl, saveTarget, null, ex => {
MessageBox.Show("An error occurred while downloading the image: "+ex.Message, Program.BrandName+" Has Failed :(", MessageBoxButtons.OK, MessageBoxIcon.Error);
});
}
case MenuSaveAllImages:
TwitterUtils.DownloadImages(lastHighlightedTweetImageList, lastHighlightedTweetAuthor, ImageQuality);
break; break;
case MenuCopyImageUrl: case MenuCopyImageUrl:
SetClipboardText(parameters.SourceUrl); SetClipboardText(TwitterUtils.GetImageLink(GetImage(parameters), ImageQuality));
break; break;
case MenuCopyUsername: case MenuCopyUsername:
Match match = RegexTwitterAccount.Value.Match(parameters.UnfilteredLinkUrl); Match match = TwitterUtils.RegexAccount.Match(parameters.UnfilteredLinkUrl);
SetClipboardText(match.Success ? match.Groups[1].Value : parameters.UnfilteredLinkUrl); SetClipboardText(match.Success ? match.Groups[1].Value : parameters.UnfilteredLinkUrl);
break; break;
@@ -103,7 +115,10 @@ namespace TweetDuck.Core.Handling{
return false; return false;
} }
public virtual void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame){} public virtual void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame){
TweetDeckBridge.LastRightClickedLink = string.Empty;
TweetDeckBridge.LastRightClickedImage = string.Empty;
}
public virtual bool RunContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model, IRunContextMenuCallback callback){ public virtual bool RunContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model, IRunContextMenuCallback callback){
return false; return false;
@@ -113,7 +128,7 @@ namespace TweetDuck.Core.Handling{
form.InvokeAsyncSafe(() => WindowsUtils.SetClipboard(text, TextDataFormat.UnicodeText)); form.InvokeAsyncSafe(() => WindowsUtils.SetClipboard(text, TextDataFormat.UnicodeText));
} }
protected void AddDebugMenuItems(IMenuModel model){ protected static void AddDebugMenuItems(IMenuModel model){
model.AddItem((CefMenuCommand)MenuOpenDevTools, "Open dev tools"); model.AddItem((CefMenuCommand)MenuOpenDevTools, "Open dev tools");
} }
@@ -128,21 +143,5 @@ namespace TweetDuck.Core.Handling{
model.AddSeparator(); model.AddSeparator();
} }
} }
private static string GetImageFileName(string url){
// twimg adds a colon after file extension
int dot = url.LastIndexOf('.');
if (dot != -1){
int colon = url.IndexOf(':', dot);
if (colon != -1){
url = url.Substring(0, colon);
}
}
// return file name
return BrowserUtils.GetFileNameFromUrl(url) ?? "unknown";
}
} }
} }

View File

@@ -49,7 +49,7 @@ namespace TweetDuck.Core.Handling{
lastHighlightedTweet = TweetDeckBridge.LastHighlightedTweet; lastHighlightedTweet = TweetDeckBridge.LastHighlightedTweet;
lastHighlightedQuotedTweet = TweetDeckBridge.LastHighlightedQuotedTweet; lastHighlightedQuotedTweet = TweetDeckBridge.LastHighlightedQuotedTweet;
if (!BrowserUtils.IsTweetDeckWebsite(frame) || browser.IsLoading){ if (!TwitterUtils.IsTweetDeckWebsite(frame) || browser.IsLoading){
lastHighlightedTweet = string.Empty; lastHighlightedTweet = string.Empty;
lastHighlightedQuotedTweet = string.Empty; lastHighlightedQuotedTweet = string.Empty;
} }

View File

@@ -1,7 +1,7 @@
using CefSharp; using System;
using System; using CefSharp;
namespace TweetDuck.Core.Handling{ namespace TweetDuck.Core.Handling.General{
class BrowserProcessHandler : IBrowserProcessHandler{ class BrowserProcessHandler : IBrowserProcessHandler{
void IBrowserProcessHandler.OnContextInitialized(){ void IBrowserProcessHandler.OnContextInitialized(){
using(IRequestContext ctx = Cef.GetGlobalRequestContext()){ using(IRequestContext ctx = Cef.GetGlobalRequestContext()){

View File

@@ -1,38 +1,48 @@
using CefSharp; using System.Drawing;
using CefSharp.WinForms;
using System.Drawing;
using System.Windows.Forms; using System.Windows.Forms;
using CefSharp;
using CefSharp.WinForms;
using TweetDuck.Core.Controls; using TweetDuck.Core.Controls;
using TweetDuck.Core.Other; using TweetDuck.Core.Other;
using TweetDuck.Core.Utils;
namespace TweetDuck.Core.Handling { namespace TweetDuck.Core.Handling.General{
class JavaScriptDialogHandler : IJsDialogHandler{ class JavaScriptDialogHandler : IJsDialogHandler{
bool IJsDialogHandler.OnJSDialog(IWebBrowser browserControl, IBrowser browser, string originUrl, CefJsDialogType dialogType, string messageText, string defaultPromptText, IJsDialogCallback callback, ref bool suppressMessage){ bool IJsDialogHandler.OnJSDialog(IWebBrowser browserControl, IBrowser browser, string originUrl, CefJsDialogType dialogType, string messageText, string defaultPromptText, IJsDialogCallback callback, ref bool suppressMessage){
((ChromiumWebBrowser)browserControl).InvokeSafe(() => { ((ChromiumWebBrowser)browserControl).InvokeSafe(() => {
FormMessage form = new FormMessage(Program.BrandName, messageText, MessageBoxIcon.None); FormMessage form;
TextBox input = null; TextBox input = null;
if (dialogType == CefJsDialogType.Alert){ if (dialogType == CefJsDialogType.Alert){
form.AcceptButton = form.AddButton("OK"); form = new FormMessage("Browser Message", messageText, MessageBoxIcon.None);
form.AddButton(FormMessage.OK, ControlType.Accept | ControlType.Focused);
} }
else if (dialogType == CefJsDialogType.Confirm){ else if (dialogType == CefJsDialogType.Confirm){
form.CancelButton = form.AddButton("No", DialogResult.No); form = new FormMessage("Browser Confirmation", messageText, MessageBoxIcon.None);
form.AcceptButton = form.AddButton("Yes"); form.AddButton(FormMessage.No, DialogResult.No, ControlType.Cancel);
form.AddButton(FormMessage.Yes, ControlType.Focused);
} }
else if (dialogType == CefJsDialogType.Prompt){ else if (dialogType == CefJsDialogType.Prompt){
form.CancelButton = form.AddButton("Cancel", DialogResult.Cancel); form = new FormMessage("Browser Prompt", messageText, MessageBoxIcon.None);
form.AcceptButton = form.AddButton("OK"); form.AddButton(FormMessage.Cancel, DialogResult.Cancel, ControlType.Cancel);
form.AddButton(FormMessage.OK, ControlType.Accept | ControlType.Focused);
float dpiScale = form.GetDPIScale();
input = new TextBox{ input = new TextBox{
Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Bottom, Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Bottom,
Location = new Point(27, form.ActionPanelY-46), Location = new Point(BrowserUtils.Scale(22, dpiScale), form.ActionPanelY-BrowserUtils.Scale(46, dpiScale)),
Size = new Size(form.ClientSize.Width-54, 20) Size = new Size(form.ClientSize.Width-BrowserUtils.Scale(44, dpiScale), 20)
}; };
form.Controls.Add(input); form.Controls.Add(input);
form.ActiveControl = input; form.ActiveControl = input;
form.Height += input.Size.Height+input.Margin.Vertical; form.Height += input.Size.Height+input.Margin.Vertical;
} }
else{
callback.Continue(false);
return;
}
bool success = form.ShowDialog() == DialogResult.OK; bool success = form.ShowDialog() == DialogResult.OK;

View File

@@ -1,7 +1,7 @@
using CefSharp; using CefSharp;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
namespace TweetDuck.Core.Handling{ namespace TweetDuck.Core.Handling.General{
class LifeSpanHandler : ILifeSpanHandler{ class LifeSpanHandler : ILifeSpanHandler{
public bool OnBeforePopup(IWebBrowser browserControl, IBrowser browser, IFrame frame, string targetUrl, string targetFrameName, WindowOpenDisposition targetDisposition, bool userGesture, IPopupFeatures popupFeatures, IWindowInfo windowInfo, IBrowserSettings browserSettings, ref bool noJavascriptAccess, out IWebBrowser newBrowser){ public bool OnBeforePopup(IWebBrowser browserControl, IBrowser browser, IFrame frame, string targetUrl, string targetFrameName, WindowOpenDisposition targetDisposition, bool userGesture, IPopupFeatures popupFeatures, IWindowInfo windowInfo, IBrowserSettings browserSettings, ref bool noJavascriptAccess, out IWebBrowser newBrowser){
newBrowser = null; newBrowser = null;

View File

@@ -1,8 +1,8 @@
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using CefSharp; using CefSharp;
namespace TweetDuck.Core.Handling{ namespace TweetDuck.Core.Handling.General{
abstract class RequestHandler : IRequestHandler{ abstract class RequestHandlerBase : IRequestHandler{
// Browser // Browser
public virtual void OnRenderViewReady(IWebBrowser browserControl, IBrowser browser){} public virtual void OnRenderViewReady(IWebBrowser browserControl, IBrowser browser){}

View File

@@ -0,0 +1,38 @@
using CefSharp;
using System.Windows.Forms;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Notification;
namespace TweetDuck.Core.Handling {
sealed class KeyboardHandlerNotification : IKeyboardHandler{
private readonly FormNotificationBase notification;
public KeyboardHandlerNotification(FormNotificationBase notification){
this.notification = notification;
}
bool IKeyboardHandler.OnPreKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey, ref bool isKeyboardShortcut){
if (type == KeyType.RawKeyDown && !browser.FocusedFrame.Url.StartsWith("chrome-devtools://")){
switch((Keys)windowsKeyCode){
case Keys.Enter:
notification.InvokeAsyncSafe(notification.FinishCurrentNotification);
return true;
case Keys.Escape:
notification.InvokeAsyncSafe(() => notification.HideNotification(true));
return true;
case Keys.Space:
notification.InvokeAsyncSafe(() => notification.FreezeTimer = !notification.FreezeTimer);
return true;
}
}
return false;
}
bool IKeyboardHandler.OnKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey){
return false;
}
}
}

View File

@@ -1,7 +1,8 @@
using CefSharp; using CefSharp;
using TweetDuck.Core.Handling.General;
namespace TweetDuck.Core.Handling{ namespace TweetDuck.Core.Handling{
class RequestHandlerBrowser : RequestHandler{ class RequestHandlerBrowser : RequestHandlerBase{
public override void OnRenderProcessTerminated(IWebBrowser browserControl, IBrowser browser, CefTerminationStatus status){ public override void OnRenderProcessTerminated(IWebBrowser browserControl, IBrowser browser, CefTerminationStatus status){
browser.Reload(); browser.Reload();
} }

View File

@@ -6,6 +6,8 @@ using System.Windows.Forms;
using TweetDuck.Configuration; using TweetDuck.Configuration;
using TweetDuck.Core.Controls; using TweetDuck.Core.Controls;
using TweetDuck.Core.Handling; using TweetDuck.Core.Handling;
using TweetDuck.Core.Handling.General;
using TweetDuck.Core.Other.Management;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
namespace TweetDuck.Core.Notification{ namespace TweetDuck.Core.Notification{
@@ -57,20 +59,25 @@ namespace TweetDuck.Core.Notification{
set{ set{
Visible = (base.Location = value) != ControlExtensions.InvisibleLocation; Visible = (base.Location = value) != ControlExtensions.InvisibleLocation;
FormBorderStyle = GetBorderStyle(CanResizeWindow);
if (WindowsUtils.ShouldAvoidToolWindow){
FormBorderStyle = Visible ? FormBorderStyle.FixedSingle : FormBorderStyle.FixedToolWindow; // workaround for alt+tab
}
} }
} }
public Func<bool> CanMoveWindow = () => true; public bool CanResizeWindow{
get => FormBorderStyle == FormBorderStyle.Sizable || FormBorderStyle == FormBorderStyle.SizableToolWindow;
set => FormBorderStyle = GetBorderStyle(value);
}
public Func<bool> CanMoveWindow { get; set; } = () => true;
protected override bool ShowWithoutActivation => true; protected override bool ShowWithoutActivation => true;
protected double SizeScale => dpiScale*Program.UserConfig.ZoomMultiplier;
protected readonly Form owner; protected readonly Form owner;
protected readonly ChromiumWebBrowser browser; protected readonly ChromiumWebBrowser browser;
private readonly ResourceHandlerNotification resourceHandler = new ResourceHandlerNotification(); private readonly ResourceHandlerNotification resourceHandler = new ResourceHandlerNotification();
private readonly float dpiScale;
private string currentColumn; private string currentColumn;
private int pauseCounter; private int pauseCounter;
@@ -92,6 +99,7 @@ namespace TweetDuck.Core.Notification{
this.browser = new ChromiumWebBrowser("about:blank"){ this.browser = new ChromiumWebBrowser("about:blank"){
MenuHandler = new ContextMenuNotification(this, enableContextMenu), MenuHandler = new ContextMenuNotification(this, enableContextMenu),
JsDialogHandler = new JavaScriptDialogHandler(),
LifeSpanHandler = new LifeSpanHandler() LifeSpanHandler = new LifeSpanHandler()
}; };
@@ -103,8 +111,10 @@ namespace TweetDuck.Core.Notification{
this.browser.ConsoleMessage += BrowserUtils.HandleConsoleMessage; this.browser.ConsoleMessage += BrowserUtils.HandleConsoleMessage;
#endif #endif
this.dpiScale = this.GetDPIScale();
DefaultResourceHandlerFactory handlerFactory = (DefaultResourceHandlerFactory)browser.ResourceHandlerFactory; DefaultResourceHandlerFactory handlerFactory = (DefaultResourceHandlerFactory)browser.ResourceHandlerFactory;
handlerFactory.RegisterHandler("https://tweetdeck.twitter.com", this.resourceHandler); handlerFactory.RegisterHandler(TwitterUtils.TweetDeckURL, this.resourceHandler);
Controls.Add(browser); Controls.Add(browser);
@@ -134,6 +144,9 @@ namespace TweetDuck.Core.Notification{
private void Browser_IsBrowserInitializedChanged(object sender, IsBrowserInitializedChangedEventArgs e){ private void Browser_IsBrowserInitializedChanged(object sender, IsBrowserInitializedChangedEventArgs e){
if (e.IsBrowserInitialized){ if (e.IsBrowserInitialized){
Initialized?.Invoke(this, new EventArgs()); Initialized?.Invoke(this, new EventArgs());
int identifier = browser.GetBrowser().Identifier;
Disposed += (sender2, args2) => BrowserProcesses.Forget(identifier);
} }
} }
@@ -173,11 +186,11 @@ namespace TweetDuck.Core.Notification{
currentColumn = tweet.Column; currentColumn = tweet.Column;
resourceHandler.SetHTML(GetTweetHTML(tweet)); resourceHandler.SetHTML(GetTweetHTML(tweet));
browser.Load("https://tweetdeck.twitter.com"); browser.Load(TwitterUtils.TweetDeckURL);
} }
protected virtual void SetNotificationSize(int width, int height){ protected virtual void SetNotificationSize(int width, int height){
browser.ClientSize = ClientSize = new Size((int)Math.Round(width*Program.UserConfig.ZoomMultiplier), (int)Math.Round(height*Program.UserConfig.ZoomMultiplier)); browser.ClientSize = ClientSize = new Size(BrowserUtils.Scale(width, SizeScale), BrowserUtils.Scale(height, SizeScale));
} }
protected virtual void OnNotificationReady(){ protected virtual void OnNotificationReady(){
@@ -207,5 +220,14 @@ namespace TweetDuck.Core.Notification{
toolTip.Show(text, this, position); toolTip.Show(text, this, position);
} }
} }
private FormBorderStyle GetBorderStyle(bool sizable){
if (WindowsUtils.ShouldAvoidToolWindow && Visible){ // Visible = workaround for alt+tab
return sizable ? FormBorderStyle.Sizable : FormBorderStyle.FixedSingle;
}
else{
return sizable ? FormBorderStyle.SizableToolWindow : FormBorderStyle.FixedToolWindow;
}
}
} }
} }

View File

@@ -49,7 +49,7 @@
this.progressBarTimer.Margin = new System.Windows.Forms.Padding(0); this.progressBarTimer.Margin = new System.Windows.Forms.Padding(0);
this.progressBarTimer.Maximum = 1000; this.progressBarTimer.Maximum = 1000;
this.progressBarTimer.Name = "progressBarTimer"; this.progressBarTimer.Name = "progressBarTimer";
this.progressBarTimer.Size = new System.Drawing.Size(284, 4); this.progressBarTimer.Size = new System.Drawing.Size(284, TimerBarHeight);
this.progressBarTimer.TabIndex = 1; this.progressBarTimer.TabIndex = 1;
// //
// FormNotification // FormNotification

View File

@@ -4,7 +4,9 @@ using System.Drawing;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Core.Bridge; using TweetDuck.Core.Bridge;
using TweetDuck.Core.Controls; using TweetDuck.Core.Controls;
using TweetDuck.Core.Handling;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
using TweetDuck.Data;
using TweetDuck.Plugins; using TweetDuck.Plugins;
using TweetDuck.Plugins.Enums; using TweetDuck.Plugins.Enums;
using TweetDuck.Resources; using TweetDuck.Resources;
@@ -12,32 +14,10 @@ using TweetDuck.Resources;
namespace TweetDuck.Core.Notification{ namespace TweetDuck.Core.Notification{
partial class FormNotificationMain : FormNotificationBase{ partial class FormNotificationMain : FormNotificationBase{
private const string NotificationScriptFile = "notification.js"; private const string NotificationScriptFile = "notification.js";
private const int TimerBarHeight = 4;
private static readonly string NotificationScriptIdentifier = ScriptLoader.GetRootIdentifier(NotificationScriptFile); private static readonly string NotificationScriptIdentifier = ScriptLoader.GetRootIdentifier(NotificationScriptFile);
private static readonly string PluginScriptIdentifier = ScriptLoader.GetRootIdentifier(PluginManager.PluginNotificationScriptFile); private static readonly string NotificationJS = ScriptLoader.LoadResource(NotificationScriptFile);
private static readonly string NotificationJS, PluginJS;
static FormNotificationMain(){
NotificationJS = ScriptLoader.LoadResource(NotificationScriptFile);
PluginJS = ScriptLoader.LoadResource(PluginManager.PluginNotificationScriptFile);
}
private static int BaseClientWidth{
get{
int level = TweetNotification.FontSizeLevel;
int width = level == 0 ? 284 : (int)Math.Round(284.0*(1.0+0.05*level));
return (int)Math.Round(width*Program.UserConfig.ZoomMultiplier);
}
}
private static int BaseClientHeight{
get{
int level = TweetNotification.FontSizeLevel;
int height = level == 0 ? 118 : (int)Math.Round(118.0*(1.0+0.075*level));
return (int)Math.Round(height*Program.UserConfig.ZoomMultiplier);
}
}
private readonly PluginManager plugins; private readonly PluginManager plugins;
@@ -51,9 +31,9 @@ namespace TweetDuck.Core.Notification{
private bool? prevDisplayTimer; private bool? prevDisplayTimer;
private int? prevFontSize; private int? prevFontSize;
private bool RequiresResize{ public bool RequiresResize{
get{ get{
return !prevDisplayTimer.HasValue || !prevFontSize.HasValue || prevDisplayTimer != Program.UserConfig.DisplayNotificationTimer || prevFontSize != TweetNotification.FontSizeLevel; return !prevDisplayTimer.HasValue || !prevFontSize.HasValue || prevDisplayTimer != Program.UserConfig.DisplayNotificationTimer || prevFontSize != TweetNotification.FontSizeLevel || CanResizeWindow;
} }
set{ set{
@@ -68,11 +48,41 @@ namespace TweetDuck.Core.Notification{
} }
} }
private int BaseClientWidth{
get{
switch(Program.UserConfig.NotificationSize){
default:
return BrowserUtils.Scale(284, SizeScale*(1.0+0.05*TweetNotification.FontSizeLevel));
case TweetNotification.Size.Custom:
return Program.UserConfig.CustomNotificationSize.Width;
}
}
}
private int BaseClientHeight{
get{
switch(Program.UserConfig.NotificationSize){
default:
return BrowserUtils.Scale(122, SizeScale*(1.0+0.075*TweetNotification.FontSizeLevel));
case TweetNotification.Size.Custom:
return Program.UserConfig.CustomNotificationSize.Height;
}
}
}
public Size BrowserSize{
get => Program.UserConfig.DisplayNotificationTimer ? new Size(ClientSize.Width, ClientSize.Height-TimerBarHeight) : ClientSize;
}
public FormNotificationMain(FormBrowser owner, PluginManager pluginManager, bool enableContextMenu) : base(owner, enableContextMenu){ public FormNotificationMain(FormBrowser owner, PluginManager pluginManager, bool enableContextMenu) : base(owner, enableContextMenu){
InitializeComponent(); InitializeComponent();
this.plugins = pluginManager; this.plugins = pluginManager;
browser.KeyboardHandler = new KeyboardHandlerNotification(this);
browser.RegisterAsyncJsObject("$TD", new TweetDeckBridge(owner, this)); browser.RegisterAsyncJsObject("$TD", new TweetDeckBridge(owner, this));
browser.RegisterAsyncJsObject("$TDP", plugins.Bridge); browser.RegisterAsyncJsObject("$TDP", plugins.Bridge);
@@ -104,9 +114,7 @@ namespace TweetDuck.Core.Notification{
int eventType = wParam.ToInt32(); int eventType = wParam.ToInt32();
if (eventType == NativeMethods.WM_MOUSEWHEEL && browser.Bounds.Contains(PointToClient(Cursor.Position))){ if (eventType == NativeMethods.WM_MOUSEWHEEL && browser.Bounds.Contains(PointToClient(Cursor.Position))){
int distance = (int)Math.Round(NativeMethods.GetMouseHookData(lParam)*(Program.UserConfig.NotificationScrollSpeed/100.0)); browser.SendMouseWheelEvent(0, 0, 0, BrowserUtils.Scale(NativeMethods.GetMouseHookData(lParam), Program.UserConfig.NotificationScrollSpeed/100.0), CefEventFlags.None);
browser.SendMouseWheelEvent(0, 0, 0, distance, CefEventFlags.None);
return NativeMethods.HOOK_HANDLED; return NativeMethods.HOOK_HANDLED;
} }
else if (eventType == NativeMethods.WM_XBUTTONDOWN && DesktopBounds.Contains(Cursor.Position)){ else if (eventType == NativeMethods.WM_XBUTTONDOWN && DesktopBounds.Contains(Cursor.Position)){
@@ -156,14 +164,9 @@ namespace TweetDuck.Core.Notification{
private void Browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){ private void Browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
if (e.Frame.IsMain && NotificationJS != null && browser.Address != "about:blank"){ if (e.Frame.IsMain && NotificationJS != null && browser.Address != "about:blank"){
e.Frame.ExecuteJavaScriptAsync(PropertyBridge.GenerateScript(PropertyBridge.Properties.AllNotification)); e.Frame.ExecuteJavaScriptAsync(PropertyBridge.GenerateScript(PropertyBridge.Environment.Notification));
ScriptLoader.ExecuteScript(e.Frame, NotificationJS, NotificationScriptIdentifier); ScriptLoader.ExecuteScript(e.Frame, NotificationJS, NotificationScriptIdentifier);
plugins.ExecutePlugins(e.Frame, PluginEnvironment.Notification);
if (plugins.HasAnyPlugin(PluginEnvironment.Notification)){
ScriptLoader.ExecuteScript(e.Frame, PluginJS, PluginScriptIdentifier);
ScriptLoader.ExecuteFile(e.Frame, PluginManager.PluginGlobalScriptFile);
plugins.ExecutePlugins(e.Frame, PluginEnvironment.Notification, false);
}
} }
} }
@@ -177,7 +180,7 @@ namespace TweetDuck.Core.Notification{
timeLeft -= timerProgress.Interval; timeLeft -= timerProgress.Interval;
int value = (int)Math.Round(1025.0*(totalTime-timeLeft)/totalTime); int value = BrowserUtils.Scale(1025, (totalTime-timeLeft)/(double)totalTime);
progressBarTimer.SetValueInstant(Math.Min(1000, Math.Max(0, Program.UserConfig.NotificationTimerCountDown ? 1000-value : value))); progressBarTimer.SetValueInstant(Math.Min(1000, Math.Max(0, Program.UserConfig.NotificationTimerCountDown ? 1000-value : value)));
if (timeLeft <= 0){ if (timeLeft <= 0){
@@ -255,7 +258,7 @@ namespace TweetDuck.Core.Notification{
protected override void SetNotificationSize(int width, int height){ protected override void SetNotificationSize(int width, int height){
if (Program.UserConfig.DisplayNotificationTimer){ if (Program.UserConfig.DisplayNotificationTimer){
ClientSize = new Size(width, height+4); ClientSize = new Size(width, height+TimerBarHeight);
progressBarTimer.Visible = true; progressBarTimer.Visible = true;
} }
else{ else{

View File

@@ -2,24 +2,40 @@
using System.Drawing; using System.Drawing;
using System.Drawing.Imaging; using System.Drawing.Imaging;
using System.Windows.Forms; using System.Windows.Forms;
using CefSharp;
using TweetDuck.Core.Bridge; using TweetDuck.Core.Bridge;
using TweetDuck.Core.Other;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
using TweetDuck.Data;
using TweetDuck.Plugins;
using TweetDuck.Resources; using TweetDuck.Resources;
namespace TweetDuck.Core.Notification.Screenshot{ namespace TweetDuck.Core.Notification.Screenshot{
sealed class FormNotificationScreenshotable : FormNotificationBase{ sealed class FormNotificationScreenshotable : FormNotificationBase{
public FormNotificationScreenshotable(Action callback, Form owner) : base(owner, false){ private readonly PluginManager plugins;
public FormNotificationScreenshotable(Action callback, Form owner, PluginManager pluginManager) : base(owner, false){
this.plugins = pluginManager;
browser.RegisterAsyncJsObject("$TD_NotificationScreenshot", new CallbackBridge(this, callback)); browser.RegisterAsyncJsObject("$TD_NotificationScreenshot", new CallbackBridge(this, callback));
browser.FrameLoadEnd += (sender, args) => { browser.LoadingStateChanged += (sender, args) => {
if (args.Frame.IsMain && browser.Address != "about:blank"){ if (!args.IsLoading){
ScriptLoader.ExecuteScript(args.Frame, "window.setTimeout($TD_NotificationScreenshot.trigger, 67)", "gen:screenshot"); using(IFrame frame = args.Browser.MainFrame){
ScriptLoader.ExecuteScript(frame, "window.setTimeout($TD_NotificationScreenshot.trigger, 129)", "gen:screenshot");
}
} }
}; };
} }
protected override string GetTweetHTML(TweetNotification tweet){ protected override string GetTweetHTML(TweetNotification tweet){
return tweet.GenerateHtml(enableCustomCSS: false); string html = tweet.GenerateHtml("td-screenshot", false);
foreach(InjectedHTML injection in plugins.Bridge.NotificationInjections){
html = injection.Inject(html);
}
return html;
} }
public void LoadNotificationForScreenshot(TweetNotification tweet, int width, int height){ public void LoadNotificationForScreenshot(TweetNotification tweet, int width, int height){
@@ -31,7 +47,7 @@ namespace TweetDuck.Core.Notification.Screenshot{
IntPtr context = NativeMethods.GetDC(this.Handle); IntPtr context = NativeMethods.GetDC(this.Handle);
if (context == IntPtr.Zero){ if (context == IntPtr.Zero){
MessageBox.Show("Could not retrieve a graphics context handle for the notification window to take the screenshot.", "Screenshot Failed", MessageBoxButtons.OK, MessageBoxIcon.Error); FormMessage.Error("Screenshot Failed", "Could not retrieve a graphics context handle for the notification window to take the screenshot.", FormMessage.OK);
} }
else{ else{
using(Bitmap bmp = new Bitmap(ClientSize.Width, ClientSize.Height, PixelFormat.Format32bppRgb)){ using(Bitmap bmp = new Bitmap(ClientSize.Width, ClientSize.Height, PixelFormat.Format32bppRgb)){

View File

@@ -4,17 +4,20 @@
using System; using System;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Core.Controls; using TweetDuck.Core.Controls;
using TweetDuck.Plugins;
namespace TweetDuck.Core.Notification.Screenshot{ namespace TweetDuck.Core.Notification.Screenshot{
sealed class TweetScreenshotManager : IDisposable{ sealed class TweetScreenshotManager : IDisposable{
private readonly Form owner; private readonly FormBrowser owner;
private readonly PluginManager plugins;
private readonly Timer timeout; private readonly Timer timeout;
private readonly Timer disposer; private readonly Timer disposer;
private FormNotificationScreenshotable screenshot; private FormNotificationScreenshotable screenshot;
public TweetScreenshotManager(Form owner){ public TweetScreenshotManager(FormBrowser owner, PluginManager pluginManager){
this.owner = owner; this.owner = owner;
this.plugins = pluginManager;
this.timeout = new Timer{ Interval = 8000 }; this.timeout = new Timer{ Interval = 8000 };
this.timeout.Tick += timeout_Tick; this.timeout.Tick += timeout_Tick;
@@ -25,8 +28,7 @@ namespace TweetDuck.Core.Notification.Screenshot{
private void timeout_Tick(object sender, EventArgs e){ private void timeout_Tick(object sender, EventArgs e){
timeout.Stop(); timeout.Stop();
screenshot.Location = ControlExtensions.InvisibleLocation; OnFinished();
disposer.Start();
} }
private void disposer_Tick(object sender, EventArgs e){ private void disposer_Tick(object sender, EventArgs e){
@@ -40,13 +42,17 @@ namespace TweetDuck.Core.Notification.Screenshot{
return; return;
} }
screenshot = new FormNotificationScreenshotable(Callback, owner){ screenshot = new FormNotificationScreenshotable(Callback, owner, plugins){
CanMoveWindow = () => false CanMoveWindow = () => false
}; };
screenshot.LoadNotificationForScreenshot(new TweetNotification(string.Empty, html, 0, string.Empty, string.Empty), width, height); screenshot.LoadNotificationForScreenshot(new TweetNotification(string.Empty, html, 0, string.Empty, string.Empty), width, height);
screenshot.Show(); screenshot.Show();
timeout.Start(); timeout.Start();
#if !(DEBUG && NO_HIDE_SCREENSHOTS)
owner.IsWaiting = true;
#endif
} }
private void Callback(){ private void Callback(){
@@ -58,14 +64,19 @@ namespace TweetDuck.Core.Notification.Screenshot{
screenshot.TakeScreenshot(); screenshot.TakeScreenshot();
#if !(DEBUG && NO_HIDE_SCREENSHOTS) #if !(DEBUG && NO_HIDE_SCREENSHOTS)
screenshot.Location = ControlExtensions.InvisibleLocation; OnFinished();
disposer.Start();
#else #else
screenshot.MoveToVisibleLocation(); screenshot.MoveToVisibleLocation();
screenshot.FormClosed += (sender, args) => disposer.Start(); screenshot.FormClosed += (sender, args) => disposer.Start();
#endif #endif
} }
private void OnFinished(){
screenshot.Location = ControlExtensions.InvisibleLocation;
owner.IsWaiting = false;
disposer.Start();
}
public void Dispose(){ public void Dispose(){
timeout.Dispose(); timeout.Dispose();
disposer.Dispose(); disposer.Dispose();

View File

@@ -1,6 +1,5 @@
using System; using System;
using TweetLib.Audio; using TweetLib.Audio;
using TweetLib.Audio.Utils;
namespace TweetDuck.Core.Notification{ namespace TweetDuck.Core.Notification{
sealed class SoundNotification : IDisposable{ sealed class SoundNotification : IDisposable{

View File

@@ -9,7 +9,7 @@ namespace TweetDuck.Core.Notification{
private const string DefaultFontSizeClass = "medium"; private const string DefaultFontSizeClass = "medium";
private const string DefaultHeadTag = @"<meta charset='utf-8'><meta http-equiv='X-UA-Compatible' content='chrome=1'><link rel='stylesheet' href='https://ton.twimg.com/tweetdeck-web/web/css/font.5ef884f9f9.css'><link rel='stylesheet' href='https://ton.twimg.com/tweetdeck-web/web/css/app-dark.5631e0dd42.css'><style type='text/css'>body{background:#222426}</style>"; private const string DefaultHeadTag = @"<meta charset='utf-8'><meta http-equiv='X-UA-Compatible' content='chrome=1'><link rel='stylesheet' href='https://ton.twimg.com/tweetdeck-web/web/css/font.5ef884f9f9.css'><link rel='stylesheet' href='https://ton.twimg.com/tweetdeck-web/web/css/app-dark.5631e0dd42.css'><style type='text/css'>body{background:#222426}</style>";
private const string CustomCSS = @"body:before{content:none}body{overflow-y:auto}.scroll-styled-v::-webkit-scrollbar{width:7px}.scroll-styled-v::-webkit-scrollbar-thumb{border-radius:0}.scroll-styled-v::-webkit-scrollbar-track{border-left:0}#td-skip{opacity:0;cursor:pointer;transition:opacity 0.15s ease}.td-hover #td-skip{opacity:0.75}#td-skip:hover{opacity:1}"; private const string CustomCSS = @"body:before{content:none}body{overflow-y:auto}.scroll-styled-v::-webkit-scrollbar{width:7px}.scroll-styled-v::-webkit-scrollbar-thumb{border-radius:0}.scroll-styled-v::-webkit-scrollbar-track{border-left:0}#td-skip{opacity:0;cursor:pointer;transition:opacity 0.15s ease}.td-hover #td-skip{opacity:0.75}#td-skip:hover{opacity:1}.media-size-medium{height:calc(100vh - 16px)!important;max-height:240px;border-radius:1px!important}.js-quote-detail .media-size-medium{height:calc(100vh - 28px)!important;}.js-media.margin-vm, .js-media-preview-container.margin-vm{margin-bottom:0!important}";
public static int FontSizeLevel{ public static int FontSizeLevel{
get{ get{
@@ -51,6 +51,10 @@ namespace TweetDuck.Core.Notification{
TopLeft, TopRight, BottomLeft, BottomRight, Custom TopLeft, TopRight, BottomLeft, BottomRight, Custom
} }
public enum Size{
Auto, Custom
}
public string Column { get; } public string Column { get; }
public string TweetUrl { get; } public string TweetUrl { get; }
public string QuoteUrl { get; } public string QuoteUrl { get; }

View File

@@ -11,7 +11,7 @@ namespace TweetDuck.Core.Other{
Text = "About "+Program.BrandName+" "+Program.VersionTag; Text = "About "+Program.BrandName+" "+Program.VersionTag;
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."; labelDescription.Text = "TweetDuck was created by chylex as a replacement to the discontinued official TweetDeck client for Windows.\n\nThe program is available for free under the open source MIT license.";
labelWebsite.Links.Add(new LinkLabel.Link(0, labelWebsite.Text.Length, Program.Website)); labelWebsite.Links.Add(new LinkLabel.Link(0, labelWebsite.Text.Length, Program.Website));
labelTips.Links.Add(new LinkLabel.Link(0, labelTips.Text.Length, TipsLink)); labelTips.Links.Add(new LinkLabel.Link(0, labelTips.Text.Length, TipsLink));

View File

@@ -1,9 +1,63 @@
using System; using System;
using System.Drawing; using System.Drawing;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Utils;
namespace TweetDuck.Core.Other{ namespace TweetDuck.Core.Other{
[Flags]
enum ControlType{
None = 0,
Accept = 1, // triggered by pressing enter when a non-button is focused
Cancel = 2, // triggered by closing the dialog without pressing a button
Focused = 4 // active control after the dialog is showed
}
sealed partial class FormMessage : Form{ sealed partial class FormMessage : Form{
public const string OK = "OK";
public const string Yes = "Yes";
public const string No = "No";
public const string Cancel = "Cancel";
public const string Retry = "Retry";
public const string Ignore = "Ignore";
public const string Exit = "Exit";
public static bool Information(string caption, string text, string buttonAccept, string buttonCancel = null){
return Show(caption, text, MessageBoxIcon.Information, buttonAccept, buttonCancel);
}
public static bool Warning(string caption, string text, string buttonAccept, string buttonCancel = null){
return Show(caption, text, MessageBoxIcon.Warning, buttonAccept, buttonCancel);
}
public static bool Error(string caption, string text, string buttonAccept, string buttonCancel = null){
return Show(caption, text, MessageBoxIcon.Error, buttonAccept, buttonCancel);
}
public static bool Question(string caption, string text, string buttonAccept, string buttonCancel = null){
return Show(caption, text, MessageBoxIcon.Question, buttonAccept, buttonCancel);
}
public static bool Show(string caption, string text, MessageBoxIcon icon, string button){
return Show(caption, text, icon, button, null);
}
public static bool Show(string caption, string text, MessageBoxIcon icon, string buttonAccept, string buttonCancel){
using(FormMessage message = new FormMessage(caption, text, icon)){
if (buttonCancel == null){
message.AddButton(buttonAccept, DialogResult.OK, ControlType.Cancel | ControlType.Focused);
}
else{
message.AddButton(buttonCancel, DialogResult.Cancel, ControlType.Cancel);
message.AddButton(buttonAccept, DialogResult.OK, ControlType.Accept | ControlType.Focused);
}
return message.ShowDialog() == DialogResult.OK;
}
}
// Instance
public Button ClickedButton { get; private set; } public Button ClickedButton { get; private set; }
public int ActionPanelY => panelActions.Location.Y; public int ActionPanelY => panelActions.Location.Y;
@@ -13,8 +67,13 @@ namespace TweetDuck.Core.Other{
set => ClientSize = new Size(value, ClientSize.Height); set => ClientSize = new Size(value, ClientSize.Height);
} }
private int ButtonDistance{
get => BrowserUtils.Scale(96, dpiScale);
}
private readonly Icon icon; private readonly Icon icon;
private readonly bool isReady; private readonly bool isReady;
private readonly float dpiScale;
private int realFormWidth, minFormWidth; private int realFormWidth, minFormWidth;
private int buttonCount; private int buttonCount;
@@ -24,9 +83,11 @@ namespace TweetDuck.Core.Other{
public FormMessage(string caption, string text, MessageBoxIcon messageIcon){ public FormMessage(string caption, string text, MessageBoxIcon messageIcon){
InitializeComponent(); InitializeComponent();
this.dpiScale = this.GetDPIScale();
this.prevLabelWidth = labelMessage.Width; this.prevLabelWidth = labelMessage.Width;
this.prevLabelHeight = labelMessage.Height; this.prevLabelHeight = labelMessage.Height;
this.minFormWidth = 40; this.minFormWidth = BrowserUtils.Scale(42, dpiScale);
switch(messageIcon){ switch(messageIcon){
case MessageBoxIcon.Information: case MessageBoxIcon.Information:
@@ -47,26 +108,30 @@ namespace TweetDuck.Core.Other{
default: default:
icon = null; icon = null;
labelMessage.Location = new Point(labelMessage.Location.X-38, labelMessage.Location.Y); labelMessage.Location = new Point(BrowserUtils.Scale(19, dpiScale), labelMessage.Location.Y); // 19 instead of 9 due to larger height
break; break;
} }
this.isReady = true; this.isReady = true;
this.Text = caption; this.Text = caption;
this.labelMessage.Text = text; this.labelMessage.Text = text.Replace("\r", "").Replace("\n", Environment.NewLine);
} }
private void FormMessage_SizeChanged(object sender, EventArgs e){ private void FormMessage_SizeChanged(object sender, EventArgs e){
RecalculateButtonLocation(); RecalculateButtonLocation();
} }
public Button AddButton(string title, DialogResult result = DialogResult.OK){ public Button AddButton(string title, ControlType type){
return AddButton(title, DialogResult.OK, type);
}
public Button AddButton(string title, DialogResult result = DialogResult.OK, ControlType type = ControlType.None){
Button button = new Button{ Button button = new Button{
Anchor = AnchorStyles.Bottom, Anchor = AnchorStyles.Bottom,
Font = SystemFonts.MessageBoxFont, Font = SystemFonts.MessageBoxFont,
Location = new Point(0, 12), Location = new Point(0, 12),
Size = new Size(88, 26), Size = new Size(BrowserUtils.Scale(88, dpiScale), BrowserUtils.Scale(26, dpiScale)),
TabIndex = buttonCount, TabIndex = buttonCount,
Text = title, Text = title,
UseVisualStyleBackColor = true UseVisualStyleBackColor = true
@@ -81,24 +146,41 @@ namespace TweetDuck.Core.Other{
panelActions.Controls.Add(button); panelActions.Controls.Add(button);
++buttonCount; ++buttonCount;
minFormWidth += 96; minFormWidth += ButtonDistance;
ClientWidth = Math.Max(realFormWidth, minFormWidth); ClientWidth = Math.Max(realFormWidth, minFormWidth);
RecalculateButtonLocation(); RecalculateButtonLocation();
if (type.HasFlag(ControlType.Accept)){
AcceptButton = button;
}
if (type.HasFlag(ControlType.Cancel)){
CancelButton = button;
}
if (type.HasFlag(ControlType.Focused)){
ActiveControl = button;
}
return button; return button;
} }
public void AddActionControl(Control control){ public void AddActionControl(Control control){
panelActions.Controls.Add(control); panelActions.Controls.Add(control);
control.Size = new Size(BrowserUtils.Scale(control.Width, dpiScale), BrowserUtils.Scale(control.Height, dpiScale));
minFormWidth += control.Width+control.Margin.Horizontal; minFormWidth += control.Width+control.Margin.Horizontal;
ClientWidth = Math.Max(realFormWidth, minFormWidth); ClientWidth = Math.Max(realFormWidth, minFormWidth);
} }
private void RecalculateButtonLocation(){ private void RecalculateButtonLocation(){
int dist = ButtonDistance;
int start = ClientWidth-dist;
for(int index = 0; index < buttonCount; index++){ for(int index = 0; index < buttonCount; index++){
Control control = panelActions.Controls[index]; Control control = panelActions.Controls[index];
control.Location = new Point(ClientWidth-97-index*96, control.Location.Y); control.Location = new Point(start-index*dist, control.Location.Y);
} }
} }
@@ -108,17 +190,18 @@ namespace TweetDuck.Core.Other{
} }
bool isMultiline = labelMessage.Height > labelMessage.MinimumSize.Height; bool isMultiline = labelMessage.Height > labelMessage.MinimumSize.Height;
int labelOffset = BrowserUtils.Scale(8, dpiScale);
if (isMultiline && !wasLabelMultiline){ if (isMultiline && !wasLabelMultiline){
labelMessage.Location = new Point(labelMessage.Location.X, labelMessage.Location.Y-8); labelMessage.Location = new Point(labelMessage.Location.X, labelMessage.Location.Y-labelOffset);
prevLabelHeight += 8; prevLabelHeight += labelOffset;
} }
else if (!isMultiline && wasLabelMultiline){ else if (!isMultiline && wasLabelMultiline){
labelMessage.Location = new Point(labelMessage.Location.X, labelMessage.Location.Y+8); labelMessage.Location = new Point(labelMessage.Location.X, labelMessage.Location.Y+labelOffset);
prevLabelHeight -= 8; prevLabelHeight -= labelOffset;
} }
realFormWidth = ClientWidth-(icon == null ? 50 : 0)+labelMessage.Width-prevLabelWidth; realFormWidth = ClientWidth-(icon == null ? BrowserUtils.Scale(50, dpiScale) : 0)+labelMessage.Width-prevLabelWidth;
ClientWidth = Math.Max(realFormWidth, minFormWidth); ClientWidth = Math.Max(realFormWidth, minFormWidth);
Height += labelMessage.Height-prevLabelHeight; Height += labelMessage.Height-prevLabelHeight;
@@ -129,7 +212,7 @@ namespace TweetDuck.Core.Other{
protected override void OnPaint(PaintEventArgs e){ protected override void OnPaint(PaintEventArgs e){
if (icon != null){ if (icon != null){
e.Graphics.DrawIcon(icon, 25, 26); e.Graphics.DrawIcon(icon, BrowserUtils.Scale(25, dpiScale), 1+BrowserUtils.Scale(25, dpiScale));
} }
base.OnPaint(e); base.OnPaint(e);

View File

@@ -80,7 +80,7 @@ namespace TweetDuck.Core.Other{
} }
private void btnReload_Click(object sender, EventArgs e){ 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){ if (FormMessage.Warning("Reloading Plugins", "This will also reload the browser window. Do you want to proceed?", FormMessage.Yes, FormMessage.No)){
pluginManager.Reload(); pluginManager.Reload();
ReloadPluginList(); ReloadPluginList();
} }

View File

@@ -1,120 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@@ -5,6 +5,7 @@ using System.Windows.Forms;
using TweetDuck.Core.Controls; using TweetDuck.Core.Controls;
using TweetDuck.Core.Other.Settings; using TweetDuck.Core.Other.Settings;
using TweetDuck.Core.Other.Settings.Dialogs; using TweetDuck.Core.Other.Settings.Dialogs;
using TweetDuck.Core.Utils;
using TweetDuck.Plugins; using TweetDuck.Plugins;
using TweetDuck.Updates; using TweetDuck.Updates;
@@ -13,6 +14,8 @@ namespace TweetDuck.Core.Other{
private readonly FormBrowser browser; private readonly FormBrowser browser;
private readonly PluginManager plugins; private readonly PluginManager plugins;
private readonly int buttonHeight;
private readonly Dictionary<Type, SettingsTab> tabs = new Dictionary<Type, SettingsTab>(4); private readonly Dictionary<Type, SettingsTab> tabs = new Dictionary<Type, SettingsTab>(4);
private SettingsTab currentTab; private SettingsTab currentTab;
@@ -26,6 +29,8 @@ namespace TweetDuck.Core.Other{
this.plugins = plugins; this.plugins = plugins;
this.buttonHeight = BrowserUtils.Scale(39, this.GetDPIScale()) | 1;
AddButton("General", () => new TabSettingsGeneral(updates)); AddButton("General", () => new TabSettingsGeneral(updates));
AddButton("Notifications", () => new TabSettingsNotifications(browser.CreateNotificationForm(false))); AddButton("Notifications", () => new TabSettingsNotifications(browser.CreateNotificationForm(false)));
AddButton("Sounds", () => new TabSettingsSounds()); AddButton("Sounds", () => new TabSettingsSounds());
@@ -63,14 +68,12 @@ namespace TweetDuck.Core.Other{
} }
private void AddButton<T>(string title, Func<T> constructor) where T : BaseTabSettings{ private void AddButton<T>(string title, Func<T> constructor) where T : BaseTabSettings{
const int btnHeight = 39;
FlatButton btn = new FlatButton{ FlatButton btn = new FlatButton{
BackColor = SystemColors.Control, BackColor = SystemColors.Control,
FlatStyle = FlatStyle.Flat, FlatStyle = FlatStyle.Flat,
Location = new Point(0, (btnHeight+1)*(panelButtons.Controls.Count/2)), Location = new Point(0, (buttonHeight+1)*(panelButtons.Controls.Count/2)),
Margin = new Padding(0), Margin = new Padding(0),
Size = new Size(panelButtons.Width, btnHeight), Size = new Size(panelButtons.Width, buttonHeight),
Text = title, Text = title,
UseVisualStyleBackColor = true UseVisualStyleBackColor = true
}; };
@@ -83,7 +86,7 @@ namespace TweetDuck.Core.Other{
panelButtons.Controls.Add(new Panel{ panelButtons.Controls.Add(new Panel{
BackColor = Color.DimGray, BackColor = Color.DimGray,
Location = new Point(0, panelButtons.Controls[panelButtons.Controls.Count-1].Location.Y+btnHeight), Location = new Point(0, panelButtons.Controls[panelButtons.Controls.Count-1].Location.Y+buttonHeight),
Margin = new Padding(0), Margin = new Padding(0),
Size = new Size(panelButtons.Width, 1) Size = new Size(panelButtons.Width, 1)
}); });
@@ -106,8 +109,13 @@ namespace TweetDuck.Core.Other{
if (!tab.IsInitialized){ if (!tab.IsInitialized){
foreach(Control control in tab.Control.InteractiveControls){ foreach(Control control in tab.Control.InteractiveControls){
if (control is ComboBox){
control.MouseLeave += control_MouseLeave; control.MouseLeave += control_MouseLeave;
} }
else if (control is TrackBar){
control.MouseWheel += control_MouseWheel;
}
}
tab.Control.OnReady(); tab.Control.OnReady();
} }
@@ -126,6 +134,11 @@ namespace TweetDuck.Core.Other{
panelContents.Focus(); panelContents.Focus();
} }
private void control_MouseWheel(object sender, MouseEventArgs e){
((HandledMouseEventArgs)e).Handled = true;
panelContents.Focus();
}
private class SettingsTab{ private class SettingsTab{
public Button Button { get; } public Button Button { get; }

View File

@@ -1,120 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@@ -0,0 +1,27 @@
using System.Collections.Generic;
using System.Diagnostics;
using CefSharp;
using TweetDuck.Core.Utils;
namespace TweetDuck.Core.Other.Management{
static class BrowserProcesses{
private static readonly Dictionary<int, int> PIDs = new Dictionary<int, int>();
public static void Link(int identifier, int pid){
PIDs[identifier] = pid;
}
public static void Forget(int identifier){
PIDs.Remove(identifier);
}
public static Process FindProcess(IBrowser browser){
if (PIDs.TryGetValue(browser.Identifier, out int pid) && WindowsUtils.IsChildProcess(pid)){ // child process is checked in two places for safety
return Process.GetProcessById(pid);
}
else{
return null;
}
}
}
}

View File

@@ -0,0 +1,91 @@
using System;
using System.Diagnostics;
using System.Timers;
using System.Windows.Forms;
using CefSharp;
using Timer = System.Timers.Timer;
namespace TweetDuck.Core.Other.Management{
sealed class MemoryUsageTracker : IDisposable{
private const int IntervalMemoryCheck = 60000*30; // 30 minutes
private const int IntervalCleanupAttempt = 60000*5; // 5 minutes
private readonly string script;
private readonly Timer timer;
private Form owner;
private IBrowser browser;
private long threshold;
private bool needsCleanup;
public MemoryUsageTracker(string cleanupFunctionName){
this.script = $"window.{cleanupFunctionName} && window.{cleanupFunctionName}()";
this.timer = new Timer{ Interval = IntervalMemoryCheck };
this.timer.Elapsed += timer_Elapsed;
}
public void Start(Form owner, IBrowser browser, int thresholdMB){
Stop();
this.owner = owner;
this.browser = browser;
this.threshold = thresholdMB*1024L*1024L;
this.timer.SynchronizingObject = owner;
this.timer.Start();
}
public void Stop(){
timer.Stop();
timer.SynchronizingObject = null;
owner = null;
browser = null;
SetNeedsCleanup(false);
}
public void Dispose(){
timer.SynchronizingObject = null;
timer.Dispose();
owner = null;
browser = null;
}
private void SetNeedsCleanup(bool value){
if (needsCleanup != value){
needsCleanup = value;
timer.Interval = value ? IntervalCleanupAttempt : IntervalMemoryCheck; // restarts timer
}
}
private void timer_Elapsed(object sender, ElapsedEventArgs e){
if (owner == null || browser == null){
return;
}
if (needsCleanup){
if (!owner.ContainsFocus){
using(IFrame frame = browser.MainFrame){
frame.EvaluateScriptAsync(script).ContinueWith(task => {
JavascriptResponse response = task.Result;
if (response.Success && (response.Result as bool? ?? false)){
SetNeedsCleanup(false);
}
});
}
}
}
else{
try{
using(Process process = BrowserProcesses.FindProcess(browser)){
if (process?.PrivateMemorySize64 > threshold){
SetNeedsCleanup(true);
}
}
}catch{
// ignore I guess?
}
}
}
}
}

View File

@@ -0,0 +1,114 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Windows.Forms;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Utils;
namespace TweetDuck.Core.Other.Management{
sealed class VideoPlayer : IDisposable{
private readonly string PlayerExe = Path.Combine(Program.ProgramPath, "TweetDuck.Video.exe");
public bool Running{
get{
if (currentProcess == null){
return false;
}
currentProcess.Refresh();
return !currentProcess.HasExited;
}
}
public event EventHandler ProcessExited;
private readonly Form owner;
private Process currentProcess;
private string lastUrl;
public VideoPlayer(Form owner){
this.owner = owner;
this.owner.FormClosing += owner_FormClosing;
}
public void Launch(string url){
Close();
lastUrl = url;
try{
if ((currentProcess = Process.Start(new ProcessStartInfo{
FileName = PlayerExe,
Arguments = $"{owner.Handle} {Program.UserConfig.VideoPlayerVolume} \"{url}\"",
UseShellExecute = false,
RedirectStandardOutput = true
})) != null){
currentProcess.EnableRaisingEvents = true;
currentProcess.Exited += process_Exited;
#if DEBUG
currentProcess.BeginOutputReadLine();
currentProcess.OutputDataReceived += (sender, args) => Debug.WriteLine("VideoPlayer: "+args.Data);
#endif
}
}catch(Exception e){
Program.Reporter.HandleException("Video Playback Error", "Error launching video player.", true, e);
}
}
public void Close(){
if (currentProcess != null){
currentProcess.Exited -= process_Exited;
try{
currentProcess.Kill();
}catch{
// kill me instead then
}
currentProcess.Dispose();
currentProcess = null;
owner.InvokeAsyncSafe(TriggerProcessExitEventUnsafe);
}
}
public void Dispose(){
ProcessExited = null;
Close();
}
private void owner_FormClosing(object sender, FormClosingEventArgs e){
if (currentProcess != null){
currentProcess.Exited -= process_Exited;
}
}
private void process_Exited(object sender, EventArgs e){
switch(currentProcess.ExitCode){
case 3: // CODE_LAUNCH_FAIL
if (FormMessage.Error("Video Playback Error", "Error launching video player, this may be caused by missing Windows Media Player. Do you want to open the video in a browser?", FormMessage.Yes, FormMessage.No)){
BrowserUtils.OpenExternalBrowser(lastUrl);
}
break;
case 4: // CODE_MEDIA_ERROR
if (FormMessage.Error("Video Playback Error", "The video could not be loaded, most likely due to unknown format. Do you want to open the video in a browser?", FormMessage.Yes, FormMessage.No)){
BrowserUtils.OpenExternalBrowser(lastUrl);
}
break;
}
currentProcess.Dispose();
currentProcess = null;
owner.InvokeAsyncSafe(TriggerProcessExitEventUnsafe);
}
private void TriggerProcessExitEventUnsafe(){
ProcessExited?.Invoke(this, new EventArgs());
}
}
}

View File

@@ -25,7 +25,7 @@ namespace TweetDuck.Core.Other.Settings{
public virtual void OnClosing(){} public virtual void OnClosing(){}
protected static void PromptRestart(){ protected static void PromptRestart(){
if (MessageBox.Show("The application must restart for the option to take place. Do you want to restart now?", Program.BrandName+" Options", MessageBoxButtons.YesNo, MessageBoxIcon.Information) == DialogResult.Yes){ if (FormMessage.Information("TweetDuck Options", "The application must restart for the option to take place. Do you want to restart now?", FormMessage.Yes, FormMessage.No)){
Program.Restart(); Program.Restart();
} }
} }

View File

@@ -42,16 +42,14 @@
// //
// textBoxBrowserCSS // textBoxBrowserCSS
// //
this.textBoxBrowserCSS.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) this.textBoxBrowserCSS.Dock = System.Windows.Forms.DockStyle.Bottom;
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.textBoxBrowserCSS.Font = new System.Drawing.Font("Courier New", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))); this.textBoxBrowserCSS.Font = new System.Drawing.Font("Courier New", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.textBoxBrowserCSS.Location = new System.Drawing.Point(0, 16); this.textBoxBrowserCSS.Location = new System.Drawing.Point(0, 16);
this.textBoxBrowserCSS.Margin = new System.Windows.Forms.Padding(0, 3, 0, 0); this.textBoxBrowserCSS.Margin = new System.Windows.Forms.Padding(0, 3, 0, 0);
this.textBoxBrowserCSS.Multiline = true; this.textBoxBrowserCSS.Multiline = true;
this.textBoxBrowserCSS.Name = "textBoxBrowserCSS"; this.textBoxBrowserCSS.Name = "textBoxBrowserCSS";
this.textBoxBrowserCSS.ScrollBars = System.Windows.Forms.ScrollBars.Both; this.textBoxBrowserCSS.ScrollBars = System.Windows.Forms.ScrollBars.Both;
this.textBoxBrowserCSS.Size = new System.Drawing.Size(373, 253); this.textBoxBrowserCSS.Size = new System.Drawing.Size(378, 253);
this.textBoxBrowserCSS.TabIndex = 1; this.textBoxBrowserCSS.TabIndex = 1;
this.textBoxBrowserCSS.WordWrap = false; this.textBoxBrowserCSS.WordWrap = false;
this.textBoxBrowserCSS.KeyUp += new System.Windows.Forms.KeyEventHandler(this.textBoxBrowserCSS_KeyUp); this.textBoxBrowserCSS.KeyUp += new System.Windows.Forms.KeyEventHandler(this.textBoxBrowserCSS_KeyUp);
@@ -100,7 +98,7 @@
this.splitContainer.Panel2.Controls.Add(this.textBoxNotificationCSS); this.splitContainer.Panel2.Controls.Add(this.textBoxNotificationCSS);
this.splitContainer.Panel2MinSize = 64; this.splitContainer.Panel2MinSize = 64;
this.splitContainer.Size = new System.Drawing.Size(760, 269); this.splitContainer.Size = new System.Drawing.Size(760, 269);
this.splitContainer.SplitterDistance = 373; this.splitContainer.SplitterDistance = 378;
this.splitContainer.SplitterWidth = 5; this.splitContainer.SplitterWidth = 5;
this.splitContainer.TabIndex = 0; this.splitContainer.TabIndex = 0;
// //
@@ -126,16 +124,14 @@
// //
// textBoxNotificationCSS // textBoxNotificationCSS
// //
this.textBoxNotificationCSS.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) this.textBoxNotificationCSS.Dock = System.Windows.Forms.DockStyle.Bottom;
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.textBoxNotificationCSS.Font = new System.Drawing.Font("Courier New", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))); this.textBoxNotificationCSS.Font = new System.Drawing.Font("Courier New", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.textBoxNotificationCSS.Location = new System.Drawing.Point(0, 16); this.textBoxNotificationCSS.Location = new System.Drawing.Point(0, 16);
this.textBoxNotificationCSS.Margin = new System.Windows.Forms.Padding(0, 3, 0, 0); this.textBoxNotificationCSS.Margin = new System.Windows.Forms.Padding(0, 3, 0, 0);
this.textBoxNotificationCSS.Multiline = true; this.textBoxNotificationCSS.Multiline = true;
this.textBoxNotificationCSS.Name = "textBoxNotificationCSS"; this.textBoxNotificationCSS.Name = "textBoxNotificationCSS";
this.textBoxNotificationCSS.ScrollBars = System.Windows.Forms.ScrollBars.Both; this.textBoxNotificationCSS.ScrollBars = System.Windows.Forms.ScrollBars.Both;
this.textBoxNotificationCSS.Size = new System.Drawing.Size(372, 253); this.textBoxNotificationCSS.Size = new System.Drawing.Size(377, 253);
this.textBoxNotificationCSS.TabIndex = 1; this.textBoxNotificationCSS.TabIndex = 1;
this.textBoxNotificationCSS.WordWrap = false; this.textBoxNotificationCSS.WordWrap = false;
// //
@@ -152,7 +148,6 @@
// btnOpenWiki // btnOpenWiki
// //
this.btnOpenWiki.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); this.btnOpenWiki.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.btnOpenWiki.AutoSize = true;
this.btnOpenWiki.Location = new System.Drawing.Point(12, 287); this.btnOpenWiki.Location = new System.Drawing.Point(12, 287);
this.btnOpenWiki.Name = "btnOpenWiki"; this.btnOpenWiki.Name = "btnOpenWiki";
this.btnOpenWiki.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0); this.btnOpenWiki.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);

View File

@@ -69,7 +69,6 @@
// btnHelp // btnHelp
// //
this.btnHelp.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); this.btnHelp.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.btnHelp.AutoSize = true;
this.btnHelp.Location = new System.Drawing.Point(12, 227); this.btnHelp.Location = new System.Drawing.Point(12, 227);
this.btnHelp.Name = "btnHelp"; this.btnHelp.Name = "btnHelp";
this.btnHelp.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0); this.btnHelp.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);

View File

@@ -2,6 +2,7 @@
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Core.Controls; using TweetDuck.Core.Controls;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
using TweetDuck.Data;
namespace TweetDuck.Core.Other.Settings.Dialogs{ namespace TweetDuck.Core.Other.Settings.Dialogs{
sealed partial class DialogSettingsCefArgs : Form{ sealed partial class DialogSettingsCefArgs : Form{
@@ -30,10 +31,10 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
return; return;
} }
int count = CommandLineArgsParser.ReadCefArguments(CefArgs).Count; int count = CommandLineArgs.ReadCefArguments(CefArgs).Count;
string prompt = count == 0 && !string.IsNullOrWhiteSpace(prevArgs) ? "All current arguments will be removed. Continue?" : count+(count == 1 ? " argument was" : " arguments were")+" detected. Continue?"; string prompt = count == 0 && !string.IsNullOrWhiteSpace(prevArgs) ? "All current arguments will be removed. Continue?" : count+(count == 1 ? " argument was" : " arguments were")+" detected. Continue?";
if (MessageBox.Show(prompt, "Confirm CEF Arguments", MessageBoxButtons.OKCancel, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2) == DialogResult.OK){ if (FormMessage.Question("Confirm CEF Arguments", prompt, FormMessage.OK, FormMessage.Cancel)){
DialogResult = DialogResult.OK; DialogResult = DialogResult.OK;
Close(); Close();
} }

View File

@@ -42,7 +42,6 @@
// btnCancel // btnCancel
// //
this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.btnCancel.AutoSize = true;
this.btnCancel.Location = new System.Drawing.Point(176, 97); this.btnCancel.Location = new System.Drawing.Point(176, 97);
this.btnCancel.Name = "btnCancel"; this.btnCancel.Name = "btnCancel";
this.btnCancel.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0); this.btnCancel.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
@@ -56,7 +55,6 @@
// //
this.btnContinue.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.btnContinue.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.btnContinue.AutoSize = true; this.btnContinue.AutoSize = true;
this.btnContinue.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
this.btnContinue.Enabled = false; this.btnContinue.Enabled = false;
this.btnContinue.Location = new System.Drawing.Point(125, 97); this.btnContinue.Location = new System.Drawing.Point(125, 97);
this.btnContinue.Name = "btnContinue"; this.btnContinue.Name = "btnContinue";

View File

@@ -58,7 +58,7 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
case State.Deciding: case State.Deciding:
// Reset // Reset
if (radioReset.Checked){ if (radioReset.Checked){
if (MessageBox.Show("This will reset all of your program options. Plugins will not be affected. Do you want to proceed?", "Reset "+Program.BrandName+" Options", MessageBoxButtons.YesNo, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2) == DialogResult.Yes){ if (FormMessage.Warning("Reset TweetDuck Options", "This will reset all of your program options. Plugins will not be affected. Do you want to proceed?", FormMessage.Yes, FormMessage.No)){
Program.ResetConfig(); Program.ResetConfig();
ShouldReloadUI = true; ShouldReloadUI = true;
@@ -74,8 +74,8 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
using(OpenFileDialog dialog = new OpenFileDialog{ using(OpenFileDialog dialog = new OpenFileDialog{
AutoUpgradeEnabled = true, AutoUpgradeEnabled = true,
DereferenceLinks = true, DereferenceLinks = true,
Title = "Import "+Program.BrandName+" Profile", Title = "Import TweetDuck Profile",
Filter = Program.BrandName+" Profile (*.tdsettings)|*.tdsettings" Filter = "TweetDuck Profile (*.tdsettings)|*.tdsettings"
}){ }){
if (dialog.ShowDialog() != DialogResult.OK){ if (dialog.ShowDialog() != DialogResult.OK){
return; return;
@@ -116,7 +116,7 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
} }
} }
else{ else{
Program.Reporter.HandleException("Profile Import Error", "An exception happened while importing "+Program.BrandName+" profile.", true, importManager.LastException); Program.Reporter.HandleException("Profile Import Error", "An exception happened while importing TweetDuck profile.", true, importManager.LastException);
} }
DialogResult = DialogResult.OK; DialogResult = DialogResult.OK;
@@ -129,9 +129,9 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
AutoUpgradeEnabled = true, AutoUpgradeEnabled = true,
OverwritePrompt = true, OverwritePrompt = true,
DefaultExt = "tdsettings", DefaultExt = "tdsettings",
FileName = Program.BrandName+".tdsettings", FileName = "TweetDuck.tdsettings",
Title = "Export "+Program.BrandName+" Profile", Title = "Export TweetDuck Profile",
Filter = Program.BrandName+" Profile (*.tdsettings)|*.tdsettings" Filter = "TweetDuck Profile (*.tdsettings)|*.tdsettings"
}){ }){
if (dialog.ShowDialog() != DialogResult.OK){ if (dialog.ShowDialog() != DialogResult.OK){
return; return;
@@ -144,7 +144,7 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
ExportManager manager = new ExportManager(file, plugins); ExportManager manager = new ExportManager(file, plugins);
if (!manager.Export(Flags)){ if (!manager.Export(Flags)){
Program.Reporter.HandleException("Profile Export Error", "An exception happened while exporting "+Program.BrandName+" profile.", true, manager.LastException); Program.Reporter.HandleException("Profile Export Error", "An exception happened while exporting TweetDuck profile.", true, manager.LastException);
} }
DialogResult = DialogResult.OK; DialogResult = DialogResult.OK;

View File

@@ -38,7 +38,6 @@
// btnCancel // btnCancel
// //
this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.btnCancel.AutoSize = true;
this.btnCancel.Location = new System.Drawing.Point(160, 171); this.btnCancel.Location = new System.Drawing.Point(160, 171);
this.btnCancel.Name = "btnCancel"; this.btnCancel.Name = "btnCancel";
this.btnCancel.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0); this.btnCancel.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
@@ -51,7 +50,6 @@
// btnRestart // btnRestart
// //
this.btnRestart.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.btnRestart.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.btnRestart.AutoSize = true;
this.btnRestart.Location = new System.Drawing.Point(97, 171); this.btnRestart.Location = new System.Drawing.Point(97, 171);
this.btnRestart.Name = "btnRestart"; this.btnRestart.Name = "btnRestart";
this.btnRestart.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0); this.btnRestart.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);

View File

@@ -3,7 +3,7 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Configuration; using TweetDuck.Configuration;
using TweetDuck.Core.Utils; using TweetDuck.Data;
namespace TweetDuck.Core.Other.Settings.Dialogs{ namespace TweetDuck.Core.Other.Settings.Dialogs{
sealed partial class DialogSettingsRestart : Form{ sealed partial class DialogSettingsRestart : Form{

View File

@@ -2,8 +2,8 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Windows.Forms;
using TweetDuck.Configuration; using TweetDuck.Configuration;
using TweetDuck.Data;
using TweetDuck.Plugins; using TweetDuck.Plugins;
using TweetDuck.Plugins.Enums; using TweetDuck.Plugins.Enums;
@@ -38,7 +38,7 @@ namespace TweetDuck.Core.Other.Settings.Export{
try{ try{
stream.WriteFile(new string[]{ "plugin.data", plugin.Identifier, path.Relative }, path.Full); stream.WriteFile(new string[]{ "plugin.data", plugin.Identifier, path.Relative }, path.Full);
}catch(ArgumentOutOfRangeException e){ }catch(ArgumentOutOfRangeException e){
MessageBox.Show("Could not include a plugin file in the export. "+e.Message, "Export Profile", MessageBoxButtons.OK, MessageBoxIcon.Warning); FormMessage.Warning("Export Profile", "Could not include a plugin file in the export. "+e.Message, FormMessage.OK);
} }
} }
} }
@@ -138,11 +138,11 @@ namespace TweetDuck.Core.Other.Settings.Export{
} }
if (missingPlugins.Count > 0){ 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); FormMessage.Information("Importing TweetDuck Profile", "Detected missing plugins when importing plugin data:\n"+string.Join("\n", missingPlugins), FormMessage.OK);
} }
if (IsRestarting){ if (IsRestarting){
Program.Restart(new string[]{ Arguments.ArgImportCookies }); Program.Restart(Arguments.ArgImportCookies);
} }
else{ else{
Program.ReloadConfig(); Program.ReloadConfig();

View File

@@ -33,12 +33,16 @@
this.btnRestart = new System.Windows.Forms.Button(); this.btnRestart = new System.Windows.Forms.Button();
this.btnOpenAppFolder = new System.Windows.Forms.Button(); this.btnOpenAppFolder = new System.Windows.Forms.Button();
this.btnOpenDataFolder = new System.Windows.Forms.Button(); this.btnOpenDataFolder = new System.Windows.Forms.Button();
this.numMemoryThreshold = new TweetDuck.Core.Controls.NumericUpDownEx();
this.checkBrowserGCReload = new System.Windows.Forms.CheckBox();
this.labelApp = new System.Windows.Forms.Label(); this.labelApp = new System.Windows.Forms.Label();
this.panelApp = new System.Windows.Forms.Panel(); this.panelApp = new System.Windows.Forms.Panel();
this.labelPerformance = new System.Windows.Forms.Label(); this.labelPerformance = new System.Windows.Forms.Label();
this.panelPerformance = new System.Windows.Forms.Panel(); this.panelPerformance = new System.Windows.Forms.Panel();
this.labelMemoryUsage = new System.Windows.Forms.Label();
this.panelConfiguration = new System.Windows.Forms.Panel(); this.panelConfiguration = new System.Windows.Forms.Panel();
this.labelConfiguration = new System.Windows.Forms.Label(); this.labelConfiguration = new System.Windows.Forms.Label();
((System.ComponentModel.ISupportInitialize)(this.numMemoryThreshold)).BeginInit();
this.panelApp.SuspendLayout(); this.panelApp.SuspendLayout();
this.panelPerformance.SuspendLayout(); this.panelPerformance.SuspendLayout();
this.panelConfiguration.SuspendLayout(); this.panelConfiguration.SuspendLayout();
@@ -52,8 +56,7 @@
this.btnClearCache.Size = new System.Drawing.Size(144, 23); this.btnClearCache.Size = new System.Drawing.Size(144, 23);
this.btnClearCache.TabIndex = 1; this.btnClearCache.TabIndex = 1;
this.btnClearCache.Text = "Clear Cache (calculating)"; this.btnClearCache.Text = "Clear Cache (calculating)";
this.toolTip.SetToolTip(this.btnClearCache, "Clearing cache will free up space taken by downloaded images and other resources." + this.toolTip.SetToolTip(this.btnClearCache, "Clearing cache will free up space taken by downloaded images and other resources.");
"");
this.btnClearCache.UseVisualStyleBackColor = true; this.btnClearCache.UseVisualStyleBackColor = true;
// //
// checkHardwareAcceleration // checkHardwareAcceleration
@@ -65,8 +68,7 @@
this.checkHardwareAcceleration.Size = new System.Drawing.Size(134, 17); this.checkHardwareAcceleration.Size = new System.Drawing.Size(134, 17);
this.checkHardwareAcceleration.TabIndex = 0; this.checkHardwareAcceleration.TabIndex = 0;
this.checkHardwareAcceleration.Text = "Hardware Acceleration"; this.checkHardwareAcceleration.Text = "Hardware Acceleration";
this.toolTip.SetToolTip(this.checkHardwareAcceleration, "Uses your graphics card to improve performance.\r\nDisable if you experience issues" + this.toolTip.SetToolTip(this.checkHardwareAcceleration, "Uses graphics card to improve performance. Disable if you experience\r\nvisual glitches. This option will not be exported in a profile.");
" with rendering.");
this.checkHardwareAcceleration.UseVisualStyleBackColor = true; this.checkHardwareAcceleration.UseVisualStyleBackColor = true;
// //
// btnEditCefArgs // btnEditCefArgs
@@ -107,8 +109,7 @@
this.btnRestart.Size = new System.Drawing.Size(144, 23); this.btnRestart.Size = new System.Drawing.Size(144, 23);
this.btnRestart.TabIndex = 2; this.btnRestart.TabIndex = 2;
this.btnRestart.Text = "Restart the Program"; 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" + this.toolTip.SetToolTip(this.btnRestart, "Restarts the program using the same command\r\nline arguments that were used at launch.");
"nch.");
this.btnRestart.UseVisualStyleBackColor = true; this.btnRestart.UseVisualStyleBackColor = true;
// //
// btnOpenAppFolder // btnOpenAppFolder
@@ -133,6 +134,35 @@
this.toolTip.SetToolTip(this.btnOpenDataFolder, "Opens the folder where your profile data is located."); this.toolTip.SetToolTip(this.btnOpenDataFolder, "Opens the folder where your profile data is located.");
this.btnOpenDataFolder.UseVisualStyleBackColor = true; this.btnOpenDataFolder.UseVisualStyleBackColor = true;
// //
// numMemoryThreshold
//
this.numMemoryThreshold.Increment = new decimal(new int[] {
50,
0,
0,
0});
this.numMemoryThreshold.Location = new System.Drawing.Point(202, 82);
this.numMemoryThreshold.Maximum = 2000;
this.numMemoryThreshold.Minimum = 200;
this.numMemoryThreshold.Name = "numMemoryThreshold";
this.numMemoryThreshold.Size = new System.Drawing.Size(97, 20);
this.numMemoryThreshold.TabIndex = 4;
this.numMemoryThreshold.TextSuffix = " MB";
this.toolTip.SetToolTip(this.numMemoryThreshold, "Minimum amount of memory usage by the browser process to trigger the cleanup.\r\nThis is not a limit, the usage is allowed to exceed this value.");
this.numMemoryThreshold.Value = 400;
//
// checkBrowserGCReload
//
this.checkBrowserGCReload.AutoSize = true;
this.checkBrowserGCReload.Location = new System.Drawing.Point(6, 84);
this.checkBrowserGCReload.Margin = new System.Windows.Forms.Padding(6, 5, 3, 3);
this.checkBrowserGCReload.Name = "checkBrowserGCReload";
this.checkBrowserGCReload.Size = new System.Drawing.Size(190, 17);
this.checkBrowserGCReload.TabIndex = 3;
this.checkBrowserGCReload.Text = "Enable Browser Memory Threshold";
this.toolTip.SetToolTip(this.checkBrowserGCReload, "Automatically reloads TweetDeck to save memory. This option only works if\r\nthe browser is in a \'default state\', i.e. all modals and drawers are closed, and\r\nall columns are scrolled to top. This option will not be exported in a profile.");
this.checkBrowserGCReload.UseVisualStyleBackColor = true;
//
// labelApp // labelApp
// //
this.labelApp.AutoSize = true; this.labelApp.AutoSize = true;
@@ -172,20 +202,33 @@
// //
this.panelPerformance.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) this.panelPerformance.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right))); | System.Windows.Forms.AnchorStyles.Right)));
this.panelPerformance.Controls.Add(this.checkBrowserGCReload);
this.panelPerformance.Controls.Add(this.numMemoryThreshold);
this.panelPerformance.Controls.Add(this.labelMemoryUsage);
this.panelPerformance.Controls.Add(this.checkHardwareAcceleration); this.panelPerformance.Controls.Add(this.checkHardwareAcceleration);
this.panelPerformance.Controls.Add(this.btnClearCache); this.panelPerformance.Controls.Add(this.btnClearCache);
this.panelPerformance.Location = new System.Drawing.Point(9, 137); this.panelPerformance.Location = new System.Drawing.Point(9, 137);
this.panelPerformance.Name = "panelPerformance"; this.panelPerformance.Name = "panelPerformance";
this.panelPerformance.Size = new System.Drawing.Size(322, 54); this.panelPerformance.Size = new System.Drawing.Size(322, 105);
this.panelPerformance.TabIndex = 3; this.panelPerformance.TabIndex = 3;
// //
// labelMemoryUsage
//
this.labelMemoryUsage.AutoSize = true;
this.labelMemoryUsage.Location = new System.Drawing.Point(3, 66);
this.labelMemoryUsage.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
this.labelMemoryUsage.Name = "labelMemoryUsage";
this.labelMemoryUsage.Size = new System.Drawing.Size(78, 13);
this.labelMemoryUsage.TabIndex = 2;
this.labelMemoryUsage.Text = "Memory Usage";
//
// panelConfiguration // panelConfiguration
// //
this.panelConfiguration.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) this.panelConfiguration.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right))); | System.Windows.Forms.AnchorStyles.Right)));
this.panelConfiguration.Controls.Add(this.btnEditCSS); this.panelConfiguration.Controls.Add(this.btnEditCSS);
this.panelConfiguration.Controls.Add(this.btnEditCefArgs); this.panelConfiguration.Controls.Add(this.btnEditCefArgs);
this.panelConfiguration.Location = new System.Drawing.Point(9, 238); this.panelConfiguration.Location = new System.Drawing.Point(9, 289);
this.panelConfiguration.Name = "panelConfiguration"; this.panelConfiguration.Name = "panelConfiguration";
this.panelConfiguration.Size = new System.Drawing.Size(322, 29); this.panelConfiguration.Size = new System.Drawing.Size(322, 29);
this.panelConfiguration.TabIndex = 5; this.panelConfiguration.TabIndex = 5;
@@ -194,7 +237,7 @@
// //
this.labelConfiguration.AutoSize = true; this.labelConfiguration.AutoSize = true;
this.labelConfiguration.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))); this.labelConfiguration.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.labelConfiguration.Location = new System.Drawing.Point(6, 215); this.labelConfiguration.Location = new System.Drawing.Point(6, 266);
this.labelConfiguration.Margin = new System.Windows.Forms.Padding(0, 21, 0, 0); this.labelConfiguration.Margin = new System.Windows.Forms.Padding(0, 21, 0, 0);
this.labelConfiguration.Name = "labelConfiguration"; this.labelConfiguration.Name = "labelConfiguration";
this.labelConfiguration.Size = new System.Drawing.Size(104, 20); this.labelConfiguration.Size = new System.Drawing.Size(104, 20);
@@ -212,7 +255,8 @@
this.Controls.Add(this.panelApp); this.Controls.Add(this.panelApp);
this.Controls.Add(this.labelApp); this.Controls.Add(this.labelApp);
this.Name = "TabSettingsAdvanced"; this.Name = "TabSettingsAdvanced";
this.Size = new System.Drawing.Size(340, 277); this.Size = new System.Drawing.Size(340, 328);
((System.ComponentModel.ISupportInitialize)(this.numMemoryThreshold)).EndInit();
this.panelApp.ResumeLayout(false); this.panelApp.ResumeLayout(false);
this.panelPerformance.ResumeLayout(false); this.panelPerformance.ResumeLayout(false);
this.panelPerformance.PerformLayout(); this.panelPerformance.PerformLayout();
@@ -239,5 +283,8 @@
private System.Windows.Forms.Panel panelPerformance; private System.Windows.Forms.Panel panelPerformance;
private System.Windows.Forms.Panel panelConfiguration; private System.Windows.Forms.Panel panelConfiguration;
private System.Windows.Forms.Label labelConfiguration; private System.Windows.Forms.Label labelConfiguration;
private System.Windows.Forms.Label labelMemoryUsage;
private Controls.NumericUpDownEx numMemoryThreshold;
private System.Windows.Forms.CheckBox checkBrowserGCReload;
} }
} }

View File

@@ -8,6 +8,8 @@ using TweetDuck.Core.Utils;
namespace TweetDuck.Core.Other.Settings{ namespace TweetDuck.Core.Other.Settings{
partial class TabSettingsAdvanced : BaseTabSettings{ partial class TabSettingsAdvanced : BaseTabSettings{
private static SystemConfig SysConfig => Program.SystemConfig;
private readonly Action<string> reinjectBrowserCSS; private readonly Action<string> reinjectBrowserCSS;
public TabSettingsAdvanced(Action<string> reinjectBrowserCSS){ public TabSettingsAdvanced(Action<string> reinjectBrowserCSS){
@@ -16,13 +18,17 @@ namespace TweetDuck.Core.Other.Settings{
this.reinjectBrowserCSS = reinjectBrowserCSS; this.reinjectBrowserCSS = reinjectBrowserCSS;
if (SystemConfig.IsHardwareAccelerationSupported){ if (SystemConfig.IsHardwareAccelerationSupported){
checkHardwareAcceleration.Checked = Program.SystemConfig.HardwareAcceleration; checkHardwareAcceleration.Checked = SysConfig.HardwareAcceleration;
} }
else{ else{
checkHardwareAcceleration.Enabled = false; checkHardwareAcceleration.Enabled = false;
checkHardwareAcceleration.Checked = false; checkHardwareAcceleration.Checked = false;
} }
checkBrowserGCReload.Checked = SysConfig.EnableBrowserGCReload;
numMemoryThreshold.Enabled = checkBrowserGCReload.Checked;
numMemoryThreshold.SetValueSafe(SysConfig.BrowserMemoryThreshold);
BrowserCache.CalculateCacheSize(bytes => this.InvokeSafe(() => { BrowserCache.CalculateCacheSize(bytes => this.InvokeSafe(() => {
if (bytes == -1L){ if (bytes == -1L){
btnClearCache.Text = "Clear Cache (unknown size)"; btnClearCache.Text = "Clear Cache (unknown size)";
@@ -37,6 +43,9 @@ namespace TweetDuck.Core.Other.Settings{
btnClearCache.Click += btnClearCache_Click; btnClearCache.Click += btnClearCache_Click;
checkHardwareAcceleration.CheckedChanged += checkHardwareAcceleration_CheckedChanged; checkHardwareAcceleration.CheckedChanged += checkHardwareAcceleration_CheckedChanged;
checkBrowserGCReload.CheckedChanged += checkBrowserGCReload_CheckedChanged;
numMemoryThreshold.ValueChanged += numMemoryThreshold_ValueChanged;
btnEditCefArgs.Click += btnEditCefArgs_Click; btnEditCefArgs.Click += btnEditCefArgs_Click;
btnEditCSS.Click += btnEditCSS_Click; btnEditCSS.Click += btnEditCSS_Click;
@@ -46,17 +55,28 @@ namespace TweetDuck.Core.Other.Settings{
btnRestartArgs.Click += btnRestartArgs_Click; btnRestartArgs.Click += btnRestartArgs_Click;
} }
public override void OnClosing(){
SysConfig.Save();
}
private void btnClearCache_Click(object sender, EventArgs e){ private void btnClearCache_Click(object sender, EventArgs e){
btnClearCache.Enabled = false; btnClearCache.Enabled = false;
BrowserCache.SetClearOnExit(); BrowserCache.SetClearOnExit();
FormMessage.Information("Clear Cache", "Cache will be automatically cleared when TweetDuck exits.", FormMessage.OK);
MessageBox.Show("Cache will be automatically cleared when "+Program.BrandName+" exits.", "Clear Cache", MessageBoxButtons.OK, MessageBoxIcon.Information);
} }
private void checkHardwareAcceleration_CheckedChanged(object sender, EventArgs e){ private void checkHardwareAcceleration_CheckedChanged(object sender, EventArgs e){
Program.SystemConfig.HardwareAcceleration = checkHardwareAcceleration.Checked; SysConfig.HardwareAcceleration = checkHardwareAcceleration.Checked;
Program.SystemConfig.Save(); PromptRestart(); // calls OnClosing
PromptRestart(); }
private void checkBrowserGCReload_CheckedChanged(object sender, EventArgs e){
SysConfig.EnableBrowserGCReload = checkBrowserGCReload.Checked;
numMemoryThreshold.Enabled = checkBrowserGCReload.Checked;
}
private void numMemoryThreshold_ValueChanged(object sender, EventArgs e){
SysConfig.BrowserMemoryThreshold = (int)numMemoryThreshold.Value;
} }
private void btnEditCefArgs_Click(object sender, EventArgs e){ private void btnEditCefArgs_Click(object sender, EventArgs e){
@@ -67,7 +87,7 @@ namespace TweetDuck.Core.Other.Settings{
}; };
form.FormClosed += (sender2, args2) => { form.FormClosed += (sender2, args2) => {
NativeMethods.SetFormDisabled(ParentForm, false); RestoreParentForm();
if (form.DialogResult == DialogResult.OK){ if (form.DialogResult == DialogResult.OK){
Config.CustomCefArgs = form.CefArgs; Config.CustomCefArgs = form.CefArgs;
@@ -89,7 +109,7 @@ namespace TweetDuck.Core.Other.Settings{
}; };
form.FormClosed += (sender2, args2) => { form.FormClosed += (sender2, args2) => {
NativeMethods.SetFormDisabled(ParentForm, false); RestoreParentForm();
if (form.DialogResult == DialogResult.OK){ if (form.DialogResult == DialogResult.OK){
Config.CustomBrowserCSS = form.BrowserCSS; Config.CustomBrowserCSS = form.BrowserCSS;
@@ -123,5 +143,11 @@ namespace TweetDuck.Core.Other.Settings{
} }
} }
} }
private void RestoreParentForm(){
if (ParentForm != null){ // when the parent is closed first, ParentForm is null in FormClosed event
NativeMethods.SetFormDisabled(ParentForm, false);
}
}
} }
} }

View File

@@ -43,6 +43,7 @@
this.panelUpdates = new System.Windows.Forms.Panel(); this.panelUpdates = new System.Windows.Forms.Panel();
this.panelTray = new System.Windows.Forms.Panel(); this.panelTray = new System.Windows.Forms.Panel();
this.labelUpdates = new System.Windows.Forms.Label(); this.labelUpdates = new System.Windows.Forms.Label();
this.checkBestImageQuality = new System.Windows.Forms.CheckBox();
((System.ComponentModel.ISupportInitialize)(this.trackBarZoom)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.trackBarZoom)).BeginInit();
this.panelUI.SuspendLayout(); this.panelUI.SuspendLayout();
this.panelUpdates.SuspendLayout(); this.panelUpdates.SuspendLayout();
@@ -58,8 +59,7 @@
this.checkExpandLinks.Size = new System.Drawing.Size(166, 17); this.checkExpandLinks.Size = new System.Drawing.Size(166, 17);
this.checkExpandLinks.TabIndex = 0; this.checkExpandLinks.TabIndex = 0;
this.checkExpandLinks.Text = "Expand Links When Hovered"; this.checkExpandLinks.Text = "Expand Links When Hovered";
this.toolTip.SetToolTip(this.checkExpandLinks, "Expands links inside the tweets. If disabled,\r\nthe full links show up in a toolti" + this.toolTip.SetToolTip(this.checkExpandLinks, "Expands links inside the tweets. If disabled,\r\nthe full links show up in a tooltip instead.");
"p instead.");
this.checkExpandLinks.UseVisualStyleBackColor = true; this.checkExpandLinks.UseVisualStyleBackColor = true;
// //
// comboBoxTrayType // comboBoxTrayType
@@ -82,18 +82,17 @@
this.checkTrayHighlight.Size = new System.Drawing.Size(103, 17); this.checkTrayHighlight.Size = new System.Drawing.Size(103, 17);
this.checkTrayHighlight.TabIndex = 2; this.checkTrayHighlight.TabIndex = 2;
this.checkTrayHighlight.Text = "Enable Highlight"; this.checkTrayHighlight.Text = "Enable Highlight";
this.toolTip.SetToolTip(this.checkTrayHighlight, "Highlights the tray icon if there are new tweets.\r\nOnly works for columns with po" + this.toolTip.SetToolTip(this.checkTrayHighlight, "Highlights the tray icon if there are new tweets.\r\nOnly works for columns with popup or audio notifications.\r\nThe icon resets when the main window is restored.");
"pup or audio notifications.\r\nThe icon resets when the main window is restored.");
this.checkTrayHighlight.UseVisualStyleBackColor = true; this.checkTrayHighlight.UseVisualStyleBackColor = true;
// //
// checkSpellCheck // checkSpellCheck
// //
this.checkSpellCheck.AutoSize = true; this.checkSpellCheck.AutoSize = true;
this.checkSpellCheck.Location = new System.Drawing.Point(6, 51); this.checkSpellCheck.Location = new System.Drawing.Point(6, 74);
this.checkSpellCheck.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3); this.checkSpellCheck.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
this.checkSpellCheck.Name = "checkSpellCheck"; this.checkSpellCheck.Name = "checkSpellCheck";
this.checkSpellCheck.Size = new System.Drawing.Size(119, 17); this.checkSpellCheck.Size = new System.Drawing.Size(119, 17);
this.checkSpellCheck.TabIndex = 2; this.checkSpellCheck.TabIndex = 3;
this.checkSpellCheck.Text = "Enable Spell Check"; this.checkSpellCheck.Text = "Enable Spell Check";
this.toolTip.SetToolTip(this.checkSpellCheck, "Underlines words that are spelled incorrectly."); this.toolTip.SetToolTip(this.checkSpellCheck, "Underlines words that are spelled incorrectly.");
this.checkSpellCheck.UseVisualStyleBackColor = true; this.checkSpellCheck.UseVisualStyleBackColor = true;
@@ -107,8 +106,7 @@
this.checkUpdateNotifications.Size = new System.Drawing.Size(165, 17); this.checkUpdateNotifications.Size = new System.Drawing.Size(165, 17);
this.checkUpdateNotifications.TabIndex = 0; this.checkUpdateNotifications.TabIndex = 0;
this.checkUpdateNotifications.Text = "Check Updates Automatically"; this.checkUpdateNotifications.Text = "Check Updates Automatically";
this.toolTip.SetToolTip(this.checkUpdateNotifications, "Checks for updates every hour.\r\nIf an update is dismissed, it will not appear aga" + this.toolTip.SetToolTip(this.checkUpdateNotifications, "Checks for updates every hour.\r\nIf an update is dismissed, it will not appear again.");
"in.");
this.checkUpdateNotifications.UseVisualStyleBackColor = true; this.checkUpdateNotifications.UseVisualStyleBackColor = true;
// //
// btnCheckUpdates // btnCheckUpdates
@@ -125,11 +123,11 @@
// labelZoomValue // labelZoomValue
// //
this.labelZoomValue.BackColor = System.Drawing.Color.Transparent; this.labelZoomValue.BackColor = System.Drawing.Color.Transparent;
this.labelZoomValue.Location = new System.Drawing.Point(141, 100); this.labelZoomValue.Location = new System.Drawing.Point(141, 123);
this.labelZoomValue.Margin = new System.Windows.Forms.Padding(0, 0, 3, 0); this.labelZoomValue.Margin = new System.Windows.Forms.Padding(0, 0, 3, 0);
this.labelZoomValue.Name = "labelZoomValue"; this.labelZoomValue.Name = "labelZoomValue";
this.labelZoomValue.Size = new System.Drawing.Size(38, 13); this.labelZoomValue.Size = new System.Drawing.Size(38, 13);
this.labelZoomValue.TabIndex = 5; this.labelZoomValue.TabIndex = 6;
this.labelZoomValue.Text = "100%"; this.labelZoomValue.Text = "100%";
this.labelZoomValue.TextAlign = System.Drawing.ContentAlignment.TopRight; this.labelZoomValue.TextAlign = System.Drawing.ContentAlignment.TopRight;
this.toolTip.SetToolTip(this.labelZoomValue, "Changes the zoom level.\r\nAlso affects notifications and screenshots."); this.toolTip.SetToolTip(this.labelZoomValue, "Changes the zoom level.\r\nAlso affects notifications and screenshots.");
@@ -143,8 +141,7 @@
this.checkSwitchAccountSelectors.Size = new System.Drawing.Size(172, 17); this.checkSwitchAccountSelectors.Size = new System.Drawing.Size(172, 17);
this.checkSwitchAccountSelectors.TabIndex = 1; this.checkSwitchAccountSelectors.TabIndex = 1;
this.checkSwitchAccountSelectors.Text = "Shift Selects Multiple Accounts"; this.checkSwitchAccountSelectors.Text = "Shift Selects Multiple Accounts";
this.toolTip.SetToolTip(this.checkSwitchAccountSelectors, "When (re)tweeting, click to select a single account or hold Shift to\r\nselect mult" + this.toolTip.SetToolTip(this.checkSwitchAccountSelectors, "When (re)tweeting, click to select a single account or hold Shift to\r\nselect multiple accounts, instead of TweetDeck\'s default behavior.");
"iple accounts, instead of TweetDeck\'s default behavior.");
this.checkSwitchAccountSelectors.UseVisualStyleBackColor = true; this.checkSwitchAccountSelectors.UseVisualStyleBackColor = true;
// //
// labelTrayIcon // labelTrayIcon
@@ -162,24 +159,24 @@
this.trackBarZoom.AutoSize = false; this.trackBarZoom.AutoSize = false;
this.trackBarZoom.BackColor = System.Drawing.SystemColors.Control; this.trackBarZoom.BackColor = System.Drawing.SystemColors.Control;
this.trackBarZoom.LargeChange = 25; this.trackBarZoom.LargeChange = 25;
this.trackBarZoom.Location = new System.Drawing.Point(3, 99); this.trackBarZoom.Location = new System.Drawing.Point(3, 122);
this.trackBarZoom.Maximum = 200; this.trackBarZoom.Maximum = 200;
this.trackBarZoom.Minimum = 50; this.trackBarZoom.Minimum = 50;
this.trackBarZoom.Name = "trackBarZoom"; this.trackBarZoom.Name = "trackBarZoom";
this.trackBarZoom.Size = new System.Drawing.Size(148, 30); this.trackBarZoom.Size = new System.Drawing.Size(148, 30);
this.trackBarZoom.SmallChange = 5; this.trackBarZoom.SmallChange = 5;
this.trackBarZoom.TabIndex = 4; this.trackBarZoom.TabIndex = 5;
this.trackBarZoom.TickFrequency = 25; this.trackBarZoom.TickFrequency = 25;
this.trackBarZoom.Value = 100; this.trackBarZoom.Value = 100;
// //
// labelZoom // labelZoom
// //
this.labelZoom.AutoSize = true; this.labelZoom.AutoSize = true;
this.labelZoom.Location = new System.Drawing.Point(3, 83); this.labelZoom.Location = new System.Drawing.Point(3, 106);
this.labelZoom.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0); this.labelZoom.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
this.labelZoom.Name = "labelZoom"; this.labelZoom.Name = "labelZoom";
this.labelZoom.Size = new System.Drawing.Size(34, 13); this.labelZoom.Size = new System.Drawing.Size(34, 13);
this.labelZoom.TabIndex = 3; this.labelZoom.TabIndex = 4;
this.labelZoom.Text = "Zoom"; this.labelZoom.Text = "Zoom";
// //
// zoomUpdateTimer // zoomUpdateTimer
@@ -202,6 +199,7 @@
// //
this.panelUI.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) this.panelUI.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right))); | System.Windows.Forms.AnchorStyles.Right)));
this.panelUI.Controls.Add(this.checkBestImageQuality);
this.panelUI.Controls.Add(this.checkExpandLinks); this.panelUI.Controls.Add(this.checkExpandLinks);
this.panelUI.Controls.Add(this.checkSwitchAccountSelectors); this.panelUI.Controls.Add(this.checkSwitchAccountSelectors);
this.panelUI.Controls.Add(this.checkSpellCheck); this.panelUI.Controls.Add(this.checkSpellCheck);
@@ -210,14 +208,14 @@
this.panelUI.Controls.Add(this.trackBarZoom); this.panelUI.Controls.Add(this.trackBarZoom);
this.panelUI.Location = new System.Drawing.Point(9, 31); this.panelUI.Location = new System.Drawing.Point(9, 31);
this.panelUI.Name = "panelUI"; this.panelUI.Name = "panelUI";
this.panelUI.Size = new System.Drawing.Size(322, 134); this.panelUI.Size = new System.Drawing.Size(322, 157);
this.panelUI.TabIndex = 1; this.panelUI.TabIndex = 1;
// //
// labelTray // labelTray
// //
this.labelTray.AutoSize = true; this.labelTray.AutoSize = true;
this.labelTray.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))); this.labelTray.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.labelTray.Location = new System.Drawing.Point(5, 189); this.labelTray.Location = new System.Drawing.Point(6, 212);
this.labelTray.Margin = new System.Windows.Forms.Padding(0, 21, 0, 0); this.labelTray.Margin = new System.Windows.Forms.Padding(0, 21, 0, 0);
this.labelTray.Name = "labelTray"; this.labelTray.Name = "labelTray";
this.labelTray.Size = new System.Drawing.Size(96, 20); this.labelTray.Size = new System.Drawing.Size(96, 20);
@@ -230,7 +228,7 @@
| System.Windows.Forms.AnchorStyles.Right))); | System.Windows.Forms.AnchorStyles.Right)));
this.panelUpdates.Controls.Add(this.checkUpdateNotifications); this.panelUpdates.Controls.Add(this.checkUpdateNotifications);
this.panelUpdates.Controls.Add(this.btnCheckUpdates); this.panelUpdates.Controls.Add(this.btnCheckUpdates);
this.panelUpdates.Location = new System.Drawing.Point(9, 335); this.panelUpdates.Location = new System.Drawing.Point(9, 358);
this.panelUpdates.Name = "panelUpdates"; this.panelUpdates.Name = "panelUpdates";
this.panelUpdates.Size = new System.Drawing.Size(322, 55); this.panelUpdates.Size = new System.Drawing.Size(322, 55);
this.panelUpdates.TabIndex = 5; this.panelUpdates.TabIndex = 5;
@@ -242,7 +240,7 @@
this.panelTray.Controls.Add(this.checkTrayHighlight); this.panelTray.Controls.Add(this.checkTrayHighlight);
this.panelTray.Controls.Add(this.comboBoxTrayType); this.panelTray.Controls.Add(this.comboBoxTrayType);
this.panelTray.Controls.Add(this.labelTrayIcon); this.panelTray.Controls.Add(this.labelTrayIcon);
this.panelTray.Location = new System.Drawing.Point(9, 212); this.panelTray.Location = new System.Drawing.Point(9, 235);
this.panelTray.Name = "panelTray"; this.panelTray.Name = "panelTray";
this.panelTray.Size = new System.Drawing.Size(322, 76); this.panelTray.Size = new System.Drawing.Size(322, 76);
this.panelTray.TabIndex = 3; this.panelTray.TabIndex = 3;
@@ -251,13 +249,25 @@
// //
this.labelUpdates.AutoSize = true; this.labelUpdates.AutoSize = true;
this.labelUpdates.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))); this.labelUpdates.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.labelUpdates.Location = new System.Drawing.Point(6, 312); this.labelUpdates.Location = new System.Drawing.Point(6, 335);
this.labelUpdates.Margin = new System.Windows.Forms.Padding(0, 21, 0, 0); this.labelUpdates.Margin = new System.Windows.Forms.Padding(0, 21, 0, 0);
this.labelUpdates.Name = "labelUpdates"; this.labelUpdates.Name = "labelUpdates";
this.labelUpdates.Size = new System.Drawing.Size(70, 20); this.labelUpdates.Size = new System.Drawing.Size(70, 20);
this.labelUpdates.TabIndex = 4; this.labelUpdates.TabIndex = 4;
this.labelUpdates.Text = "Updates"; this.labelUpdates.Text = "Updates";
// //
// checkBestImageQuality
//
this.checkBestImageQuality.AutoSize = true;
this.checkBestImageQuality.Location = new System.Drawing.Point(6, 51);
this.checkBestImageQuality.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
this.checkBestImageQuality.Name = "checkBestImageQuality";
this.checkBestImageQuality.Size = new System.Drawing.Size(114, 17);
this.checkBestImageQuality.TabIndex = 2;
this.checkBestImageQuality.Text = "Best Image Quality";
this.toolTip.SetToolTip(this.checkBestImageQuality, "When right-clicking a tweet image, the context menu options\r\nwill use links to the original image size (:orig in the URL).");
this.checkBestImageQuality.UseVisualStyleBackColor = true;
//
// TabSettingsGeneral // TabSettingsGeneral
// //
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
@@ -269,7 +279,7 @@
this.Controls.Add(this.panelUI); this.Controls.Add(this.panelUI);
this.Controls.Add(this.labelUI); this.Controls.Add(this.labelUI);
this.Name = "TabSettingsGeneral"; this.Name = "TabSettingsGeneral";
this.Size = new System.Drawing.Size(340, 400); this.Size = new System.Drawing.Size(340, 422);
((System.ComponentModel.ISupportInitialize)(this.trackBarZoom)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.trackBarZoom)).EndInit();
this.panelUI.ResumeLayout(false); this.panelUI.ResumeLayout(false);
this.panelUI.PerformLayout(); this.panelUI.PerformLayout();
@@ -303,5 +313,6 @@
private System.Windows.Forms.Panel panelUpdates; private System.Windows.Forms.Panel panelUpdates;
private System.Windows.Forms.Panel panelTray; private System.Windows.Forms.Panel panelTray;
private System.Windows.Forms.Label labelUpdates; private System.Windows.Forms.Label labelUpdates;
private System.Windows.Forms.CheckBox checkBestImageQuality;
} }
} }

View File

@@ -1,5 +1,4 @@
using System; using System;
using System.Windows.Forms;
using TweetDuck.Core.Controls; using TweetDuck.Core.Controls;
using TweetDuck.Updates; using TweetDuck.Updates;
using TweetDuck.Updates.Events; using TweetDuck.Updates.Events;
@@ -29,6 +28,7 @@ namespace TweetDuck.Core.Other.Settings{
checkExpandLinks.Checked = Config.ExpandLinksOnHover; checkExpandLinks.Checked = Config.ExpandLinksOnHover;
checkSwitchAccountSelectors.Checked = Config.SwitchAccountSelectors; checkSwitchAccountSelectors.Checked = Config.SwitchAccountSelectors;
checkBestImageQuality.Checked = Config.BestImageQuality;
checkSpellCheck.Checked = Config.EnableSpellCheck; checkSpellCheck.Checked = Config.EnableSpellCheck;
checkTrayHighlight.Checked = Config.EnableTrayHighlight; checkTrayHighlight.Checked = Config.EnableTrayHighlight;
@@ -38,6 +38,7 @@ namespace TweetDuck.Core.Other.Settings{
public override void OnReady(){ public override void OnReady(){
checkExpandLinks.CheckedChanged += checkExpandLinks_CheckedChanged; checkExpandLinks.CheckedChanged += checkExpandLinks_CheckedChanged;
checkSwitchAccountSelectors.CheckedChanged += checkSwitchAccountSelectors_CheckedChanged; checkSwitchAccountSelectors.CheckedChanged += checkSwitchAccountSelectors_CheckedChanged;
checkBestImageQuality.CheckedChanged += checkBestImageQuality_CheckedChanged;
checkSpellCheck.CheckedChanged += checkSpellCheck_CheckedChanged; checkSpellCheck.CheckedChanged += checkSpellCheck_CheckedChanged;
trackBarZoom.ValueChanged += trackBarZoom_ValueChanged; trackBarZoom.ValueChanged += trackBarZoom_ValueChanged;
@@ -60,6 +61,10 @@ namespace TweetDuck.Core.Other.Settings{
Config.SwitchAccountSelectors = checkSwitchAccountSelectors.Checked; Config.SwitchAccountSelectors = checkSwitchAccountSelectors.Checked;
} }
private void checkBestImageQuality_CheckedChanged(object sender, EventArgs e){
Config.BestImageQuality = checkBestImageQuality.Checked;
}
private void checkSpellCheck_CheckedChanged(object sender, EventArgs e){ private void checkSpellCheck_CheckedChanged(object sender, EventArgs e){
Config.EnableSpellCheck = checkSpellCheck.Checked; Config.EnableSpellCheck = checkSpellCheck.Checked;
PromptRestart(); PromptRestart();
@@ -89,7 +94,7 @@ namespace TweetDuck.Core.Other.Settings{
updateCheckEventId = updates.Check(true); updateCheckEventId = updates.Check(true);
if (updateCheckEventId == -1){ if (updateCheckEventId == -1){
MessageBox.Show("Sorry, your system is no longer supported.", "Unsupported System", MessageBoxButtons.OK, MessageBoxIcon.Warning); FormMessage.Warning("Unsupported System", "Sorry, your system is no longer supported.", FormMessage.OK);
} }
else{ else{
btnCheckUpdates.Enabled = false; btnCheckUpdates.Enabled = false;
@@ -98,13 +103,15 @@ namespace TweetDuck.Core.Other.Settings{
} }
private void updates_CheckFinished(object sender, UpdateCheckEventArgs e){ private void updates_CheckFinished(object sender, UpdateCheckEventArgs e){
this.InvokeAsyncSafe(() => {
if (e.EventId == updateCheckEventId){ if (e.EventId == updateCheckEventId){
btnCheckUpdates.Enabled = true; btnCheckUpdates.Enabled = true;
if (!e.UpdateAvailable){ if (!e.UpdateAvailable){
MessageBox.Show("Your version of "+Program.BrandName+" is up to date.", "No Updates Available", MessageBoxButtons.OK, MessageBoxIcon.Information); FormMessage.Information("No Updates Available", "Your version of TweetDuck is up to date.", FormMessage.OK);
} }
} }
});
} }
private void zoomUpdateTimer_Tick(object sender, EventArgs e){ private void zoomUpdateTimer_Tick(object sender, EventArgs e){

View File

@@ -48,6 +48,8 @@
this.checkTimerCountDown = new System.Windows.Forms.CheckBox(); this.checkTimerCountDown = new System.Windows.Forms.CheckBox();
this.checkNotificationTimer = new System.Windows.Forms.CheckBox(); this.checkNotificationTimer = new System.Windows.Forms.CheckBox();
this.toolTip = new System.Windows.Forms.ToolTip(this.components); this.toolTip = new System.Windows.Forms.ToolTip(this.components);
this.radioSizeAuto = new System.Windows.Forms.RadioButton();
this.radioSizeCustom = new System.Windows.Forms.RadioButton();
this.labelGeneral = new System.Windows.Forms.Label(); this.labelGeneral = new System.Windows.Forms.Label();
this.panelGeneral = new System.Windows.Forms.Panel(); this.panelGeneral = new System.Windows.Forms.Panel();
this.labelScrollSpeedValue = new System.Windows.Forms.Label(); this.labelScrollSpeedValue = new System.Windows.Forms.Label();
@@ -58,8 +60,10 @@
this.panelTimer = new System.Windows.Forms.Panel(); this.panelTimer = new System.Windows.Forms.Panel();
this.labelDuration = new System.Windows.Forms.Label(); this.labelDuration = new System.Windows.Forms.Label();
this.labelTimer = new System.Windows.Forms.Label(); this.labelTimer = new System.Windows.Forms.Label();
this.labelMiscellaneous = new System.Windows.Forms.Label(); this.labelSize = new System.Windows.Forms.Label();
this.panelMiscellaneous = new System.Windows.Forms.Panel(); this.panelMiscellaneous = new System.Windows.Forms.Panel();
this.durationUpdateTimer = new System.Windows.Forms.Timer(this.components);
this.checkMediaPreviews = new System.Windows.Forms.CheckBox();
((System.ComponentModel.ISupportInitialize)(this.trackBarEdgeDistance)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.trackBarEdgeDistance)).BeginInit();
this.tableLayoutDurationButtons.SuspendLayout(); this.tableLayoutDurationButtons.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.trackBarDuration)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.trackBarDuration)).BeginInit();
@@ -121,7 +125,7 @@
this.radioLocCustom.TabIndex = 4; this.radioLocCustom.TabIndex = 4;
this.radioLocCustom.TabStop = true; this.radioLocCustom.TabStop = true;
this.radioLocCustom.Text = "Custom"; this.radioLocCustom.Text = "Custom";
this.toolTip.SetToolTip(this.radioLocCustom, "Drag the notification window to the desired location."); this.toolTip.SetToolTip(this.radioLocCustom, "Drag the example notification window to the desired location.");
this.radioLocCustom.UseVisualStyleBackColor = true; this.radioLocCustom.UseVisualStyleBackColor = true;
// //
// radioLocBR // radioLocBR
@@ -270,11 +274,11 @@
// checkSkipOnLinkClick // checkSkipOnLinkClick
// //
this.checkSkipOnLinkClick.AutoSize = true; this.checkSkipOnLinkClick.AutoSize = true;
this.checkSkipOnLinkClick.Location = new System.Drawing.Point(6, 28); this.checkSkipOnLinkClick.Location = new System.Drawing.Point(6, 51);
this.checkSkipOnLinkClick.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3); this.checkSkipOnLinkClick.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
this.checkSkipOnLinkClick.Name = "checkSkipOnLinkClick"; this.checkSkipOnLinkClick.Name = "checkSkipOnLinkClick";
this.checkSkipOnLinkClick.Size = new System.Drawing.Size(113, 17); this.checkSkipOnLinkClick.Size = new System.Drawing.Size(113, 17);
this.checkSkipOnLinkClick.TabIndex = 1; this.checkSkipOnLinkClick.TabIndex = 2;
this.checkSkipOnLinkClick.Text = "Skip On Link Click"; this.checkSkipOnLinkClick.Text = "Skip On Link Click";
this.toolTip.SetToolTip(this.checkSkipOnLinkClick, "Skips current notification when a link\r\ninside the notification is clicked."); this.toolTip.SetToolTip(this.checkSkipOnLinkClick, "Skips current notification when a link\r\ninside the notification is clicked.");
this.checkSkipOnLinkClick.UseVisualStyleBackColor = true; this.checkSkipOnLinkClick.UseVisualStyleBackColor = true;
@@ -288,42 +292,40 @@
this.checkColumnName.Size = new System.Drawing.Size(129, 17); this.checkColumnName.Size = new System.Drawing.Size(129, 17);
this.checkColumnName.TabIndex = 0; this.checkColumnName.TabIndex = 0;
this.checkColumnName.Text = "Display Column Name"; this.checkColumnName.Text = "Display Column Name";
this.toolTip.SetToolTip(this.checkColumnName, "Shows column name each notification originated\r\nfrom in the notification window t" + this.toolTip.SetToolTip(this.checkColumnName, "Shows column name each notification originated\r\nfrom in the notification window title.");
"itle.");
this.checkColumnName.UseVisualStyleBackColor = true; this.checkColumnName.UseVisualStyleBackColor = true;
// //
// labelIdlePause // labelIdlePause
// //
this.labelIdlePause.AutoSize = true; this.labelIdlePause.AutoSize = true;
this.labelIdlePause.Location = new System.Drawing.Point(3, 60); this.labelIdlePause.Location = new System.Drawing.Point(3, 106);
this.labelIdlePause.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0); this.labelIdlePause.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
this.labelIdlePause.Name = "labelIdlePause"; this.labelIdlePause.Name = "labelIdlePause";
this.labelIdlePause.Size = new System.Drawing.Size(89, 13); this.labelIdlePause.Size = new System.Drawing.Size(89, 13);
this.labelIdlePause.TabIndex = 2; this.labelIdlePause.TabIndex = 4;
this.labelIdlePause.Text = "Pause When Idle"; this.labelIdlePause.Text = "Pause When Idle";
// //
// comboBoxIdlePause // comboBoxIdlePause
// //
this.comboBoxIdlePause.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; this.comboBoxIdlePause.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.comboBoxIdlePause.FormattingEnabled = true; this.comboBoxIdlePause.FormattingEnabled = true;
this.comboBoxIdlePause.Location = new System.Drawing.Point(5, 76); this.comboBoxIdlePause.Location = new System.Drawing.Point(5, 122);
this.comboBoxIdlePause.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3); this.comboBoxIdlePause.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3);
this.comboBoxIdlePause.Name = "comboBoxIdlePause"; this.comboBoxIdlePause.Name = "comboBoxIdlePause";
this.comboBoxIdlePause.Size = new System.Drawing.Size(144, 21); this.comboBoxIdlePause.Size = new System.Drawing.Size(144, 21);
this.comboBoxIdlePause.TabIndex = 3; this.comboBoxIdlePause.TabIndex = 5;
this.toolTip.SetToolTip(this.comboBoxIdlePause, "Pauses new notifications after going idle for a set amount of time."); this.toolTip.SetToolTip(this.comboBoxIdlePause, "Pauses new notifications after going idle for a set amount of time.");
// //
// checkNonIntrusive // checkNonIntrusive
// //
this.checkNonIntrusive.AutoSize = true; this.checkNonIntrusive.AutoSize = true;
this.checkNonIntrusive.Location = new System.Drawing.Point(6, 5); this.checkNonIntrusive.Location = new System.Drawing.Point(6, 74);
this.checkNonIntrusive.Margin = new System.Windows.Forms.Padding(6, 5, 3, 3); this.checkNonIntrusive.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
this.checkNonIntrusive.Name = "checkNonIntrusive"; this.checkNonIntrusive.Name = "checkNonIntrusive";
this.checkNonIntrusive.Size = new System.Drawing.Size(128, 17); this.checkNonIntrusive.Size = new System.Drawing.Size(128, 17);
this.checkNonIntrusive.TabIndex = 0; this.checkNonIntrusive.TabIndex = 3;
this.checkNonIntrusive.Text = "Non-Intrusive Popups"; this.checkNonIntrusive.Text = "Non-Intrusive Popups";
this.toolTip.SetToolTip(this.checkNonIntrusive, "When not idle and the cursor is within the notification window area,\r\nit will be " + this.toolTip.SetToolTip(this.checkNonIntrusive, "When not idle and the cursor is within the notification window area,\r\nit will be delayed until the cursor moves away to prevent accidental clicks.");
"delayed until the cursor moves away to prevent accidental clicks.");
this.checkNonIntrusive.UseVisualStyleBackColor = true; this.checkNonIntrusive.UseVisualStyleBackColor = true;
// //
// checkTimerCountDown // checkTimerCountDown
@@ -349,6 +351,30 @@
this.checkNotificationTimer.Text = "Display Notification Timer"; this.checkNotificationTimer.Text = "Display Notification Timer";
this.checkNotificationTimer.UseVisualStyleBackColor = true; this.checkNotificationTimer.UseVisualStyleBackColor = true;
// //
// radioSizeAuto
//
this.radioSizeAuto.Location = new System.Drawing.Point(6, 4);
this.radioSizeAuto.Margin = new System.Windows.Forms.Padding(5, 4, 3, 3);
this.radioSizeAuto.Name = "radioSizeAuto";
this.radioSizeAuto.Size = new System.Drawing.Size(92, 17);
this.radioSizeAuto.TabIndex = 0;
this.radioSizeAuto.TabStop = true;
this.radioSizeAuto.Text = "Auto";
this.toolTip.SetToolTip(this.radioSizeAuto, "Notification size is based on the font size and browser zoom level.");
this.radioSizeAuto.UseVisualStyleBackColor = true;
//
// radioSizeCustom
//
this.radioSizeCustom.Location = new System.Drawing.Point(106, 4);
this.radioSizeCustom.Margin = new System.Windows.Forms.Padding(5, 4, 3, 3);
this.radioSizeCustom.Name = "radioSizeCustom";
this.radioSizeCustom.Size = new System.Drawing.Size(92, 17);
this.radioSizeCustom.TabIndex = 1;
this.radioSizeCustom.TabStop = true;
this.radioSizeCustom.Text = "Custom";
this.toolTip.SetToolTip(this.radioSizeCustom, "Resize the example notification window to the desired size.");
this.radioSizeCustom.UseVisualStyleBackColor = true;
//
// labelGeneral // labelGeneral
// //
this.labelGeneral.AutoSize = true; this.labelGeneral.AutoSize = true;
@@ -364,22 +390,24 @@
// //
this.panelGeneral.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) this.panelGeneral.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right))); | System.Windows.Forms.AnchorStyles.Right)));
this.panelGeneral.Controls.Add(this.checkMediaPreviews);
this.panelGeneral.Controls.Add(this.checkColumnName); this.panelGeneral.Controls.Add(this.checkColumnName);
this.panelGeneral.Controls.Add(this.checkSkipOnLinkClick); this.panelGeneral.Controls.Add(this.checkSkipOnLinkClick);
this.panelGeneral.Controls.Add(this.checkNonIntrusive);
this.panelGeneral.Controls.Add(this.labelIdlePause); this.panelGeneral.Controls.Add(this.labelIdlePause);
this.panelGeneral.Controls.Add(this.comboBoxIdlePause); this.panelGeneral.Controls.Add(this.comboBoxIdlePause);
this.panelGeneral.Location = new System.Drawing.Point(9, 31); this.panelGeneral.Location = new System.Drawing.Point(9, 31);
this.panelGeneral.Name = "panelGeneral"; this.panelGeneral.Name = "panelGeneral";
this.panelGeneral.Size = new System.Drawing.Size(322, 103); this.panelGeneral.Size = new System.Drawing.Size(322, 149);
this.panelGeneral.TabIndex = 1; this.panelGeneral.TabIndex = 1;
// //
// labelScrollSpeedValue // labelScrollSpeedValue
// //
this.labelScrollSpeedValue.Location = new System.Drawing.Point(147, 54); this.labelScrollSpeedValue.Location = new System.Drawing.Point(147, 53);
this.labelScrollSpeedValue.Margin = new System.Windows.Forms.Padding(0, 0, 3, 0); this.labelScrollSpeedValue.Margin = new System.Windows.Forms.Padding(0, 0, 3, 0);
this.labelScrollSpeedValue.Name = "labelScrollSpeedValue"; this.labelScrollSpeedValue.Name = "labelScrollSpeedValue";
this.labelScrollSpeedValue.Size = new System.Drawing.Size(34, 13); this.labelScrollSpeedValue.Size = new System.Drawing.Size(34, 13);
this.labelScrollSpeedValue.TabIndex = 3; this.labelScrollSpeedValue.TabIndex = 4;
this.labelScrollSpeedValue.Text = "100%"; this.labelScrollSpeedValue.Text = "100%";
this.labelScrollSpeedValue.TextAlign = System.Drawing.ContentAlignment.TopRight; this.labelScrollSpeedValue.TextAlign = System.Drawing.ContentAlignment.TopRight;
// //
@@ -387,35 +415,35 @@
// //
this.trackBarScrollSpeed.AutoSize = false; this.trackBarScrollSpeed.AutoSize = false;
this.trackBarScrollSpeed.LargeChange = 25; this.trackBarScrollSpeed.LargeChange = 25;
this.trackBarScrollSpeed.Location = new System.Drawing.Point(5, 53); this.trackBarScrollSpeed.Location = new System.Drawing.Point(5, 52);
this.trackBarScrollSpeed.Maximum = 200; this.trackBarScrollSpeed.Maximum = 200;
this.trackBarScrollSpeed.Minimum = 25; this.trackBarScrollSpeed.Minimum = 25;
this.trackBarScrollSpeed.Name = "trackBarScrollSpeed"; this.trackBarScrollSpeed.Name = "trackBarScrollSpeed";
this.trackBarScrollSpeed.Size = new System.Drawing.Size(148, 30); this.trackBarScrollSpeed.Size = new System.Drawing.Size(148, 30);
this.trackBarScrollSpeed.SmallChange = 25; this.trackBarScrollSpeed.SmallChange = 5;
this.trackBarScrollSpeed.TabIndex = 2; this.trackBarScrollSpeed.TabIndex = 3;
this.trackBarScrollSpeed.TickFrequency = 25; this.trackBarScrollSpeed.TickFrequency = 25;
this.trackBarScrollSpeed.Value = 100; this.trackBarScrollSpeed.Value = 100;
// //
// labelScrollSpeed // labelScrollSpeed
// //
this.labelScrollSpeed.AutoSize = true; this.labelScrollSpeed.AutoSize = true;
this.labelScrollSpeed.Location = new System.Drawing.Point(3, 37); this.labelScrollSpeed.Location = new System.Drawing.Point(3, 36);
this.labelScrollSpeed.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0); this.labelScrollSpeed.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
this.labelScrollSpeed.Name = "labelScrollSpeed"; this.labelScrollSpeed.Name = "labelScrollSpeed";
this.labelScrollSpeed.Size = new System.Drawing.Size(67, 13); this.labelScrollSpeed.Size = new System.Drawing.Size(67, 13);
this.labelScrollSpeed.TabIndex = 1; this.labelScrollSpeed.TabIndex = 2;
this.labelScrollSpeed.Text = "Scroll Speed"; this.labelScrollSpeed.Text = "Scroll Speed";
// //
// labelLocation // labelLocation
// //
this.labelLocation.AutoSize = true; this.labelLocation.AutoSize = true;
this.labelLocation.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))); this.labelLocation.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.labelLocation.Location = new System.Drawing.Point(6, 158); this.labelLocation.Location = new System.Drawing.Point(6, 395);
this.labelLocation.Margin = new System.Windows.Forms.Padding(0, 21, 0, 0); this.labelLocation.Margin = new System.Windows.Forms.Padding(0, 21, 0, 0);
this.labelLocation.Name = "labelLocation"; this.labelLocation.Name = "labelLocation";
this.labelLocation.Size = new System.Drawing.Size(70, 20); this.labelLocation.Size = new System.Drawing.Size(70, 20);
this.labelLocation.TabIndex = 2; this.labelLocation.TabIndex = 4;
this.labelLocation.Text = "Location"; this.labelLocation.Text = "Location";
// //
// panelLocation // panelLocation
@@ -432,10 +460,10 @@
this.panelLocation.Controls.Add(this.radioLocBL); this.panelLocation.Controls.Add(this.radioLocBL);
this.panelLocation.Controls.Add(this.radioLocCustom); this.panelLocation.Controls.Add(this.radioLocCustom);
this.panelLocation.Controls.Add(this.radioLocBR); this.panelLocation.Controls.Add(this.radioLocBR);
this.panelLocation.Location = new System.Drawing.Point(9, 181); this.panelLocation.Location = new System.Drawing.Point(9, 418);
this.panelLocation.Name = "panelLocation"; this.panelLocation.Name = "panelLocation";
this.panelLocation.Size = new System.Drawing.Size(322, 165); this.panelLocation.Size = new System.Drawing.Size(322, 165);
this.panelLocation.TabIndex = 3; this.panelLocation.TabIndex = 5;
// //
// panelTimer // panelTimer
// //
@@ -447,10 +475,10 @@
this.panelTimer.Controls.Add(this.checkTimerCountDown); this.panelTimer.Controls.Add(this.checkTimerCountDown);
this.panelTimer.Controls.Add(this.labelDurationValue); this.panelTimer.Controls.Add(this.labelDurationValue);
this.panelTimer.Controls.Add(this.trackBarDuration); this.panelTimer.Controls.Add(this.trackBarDuration);
this.panelTimer.Location = new System.Drawing.Point(9, 393); this.panelTimer.Location = new System.Drawing.Point(9, 227);
this.panelTimer.Name = "panelTimer"; this.panelTimer.Name = "panelTimer";
this.panelTimer.Size = new System.Drawing.Size(322, 144); this.panelTimer.Size = new System.Drawing.Size(322, 144);
this.panelTimer.TabIndex = 5; this.panelTimer.TabIndex = 3;
// //
// labelDuration // labelDuration
// //
@@ -466,43 +494,61 @@
// //
this.labelTimer.AutoSize = true; this.labelTimer.AutoSize = true;
this.labelTimer.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))); this.labelTimer.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.labelTimer.Location = new System.Drawing.Point(6, 370); this.labelTimer.Location = new System.Drawing.Point(6, 204);
this.labelTimer.Margin = new System.Windows.Forms.Padding(0, 21, 0, 0); this.labelTimer.Margin = new System.Windows.Forms.Padding(0, 21, 0, 0);
this.labelTimer.Name = "labelTimer"; this.labelTimer.Name = "labelTimer";
this.labelTimer.Size = new System.Drawing.Size(48, 20); this.labelTimer.Size = new System.Drawing.Size(48, 20);
this.labelTimer.TabIndex = 4; this.labelTimer.TabIndex = 2;
this.labelTimer.Text = "Timer"; this.labelTimer.Text = "Timer";
// //
// labelMiscellaneous // labelSize
// //
this.labelMiscellaneous.AutoSize = true; this.labelSize.AutoSize = true;
this.labelMiscellaneous.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))); this.labelSize.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.labelMiscellaneous.Location = new System.Drawing.Point(6, 561); this.labelSize.Location = new System.Drawing.Point(6, 607);
this.labelMiscellaneous.Margin = new System.Windows.Forms.Padding(0, 21, 0, 0); this.labelSize.Margin = new System.Windows.Forms.Padding(0, 21, 0, 0);
this.labelMiscellaneous.Name = "labelMiscellaneous"; this.labelSize.Name = "labelSize";
this.labelMiscellaneous.Size = new System.Drawing.Size(109, 20); this.labelSize.Size = new System.Drawing.Size(40, 20);
this.labelMiscellaneous.TabIndex = 6; this.labelSize.TabIndex = 6;
this.labelMiscellaneous.Text = "Miscellaneous"; this.labelSize.Text = "Size";
// //
// panelMiscellaneous // panelMiscellaneous
// //
this.panelMiscellaneous.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) this.panelMiscellaneous.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right))); | System.Windows.Forms.AnchorStyles.Right)));
this.panelMiscellaneous.Controls.Add(this.radioSizeCustom);
this.panelMiscellaneous.Controls.Add(this.radioSizeAuto);
this.panelMiscellaneous.Controls.Add(this.labelScrollSpeedValue); this.panelMiscellaneous.Controls.Add(this.labelScrollSpeedValue);
this.panelMiscellaneous.Controls.Add(this.trackBarScrollSpeed); this.panelMiscellaneous.Controls.Add(this.trackBarScrollSpeed);
this.panelMiscellaneous.Controls.Add(this.checkNonIntrusive);
this.panelMiscellaneous.Controls.Add(this.labelScrollSpeed); this.panelMiscellaneous.Controls.Add(this.labelScrollSpeed);
this.panelMiscellaneous.Location = new System.Drawing.Point(9, 584); this.panelMiscellaneous.Location = new System.Drawing.Point(9, 630);
this.panelMiscellaneous.Name = "panelMiscellaneous"; this.panelMiscellaneous.Name = "panelMiscellaneous";
this.panelMiscellaneous.Size = new System.Drawing.Size(322, 90); this.panelMiscellaneous.Size = new System.Drawing.Size(322, 92);
this.panelMiscellaneous.TabIndex = 7; this.panelMiscellaneous.TabIndex = 7;
// //
// durationUpdateTimer
//
this.durationUpdateTimer.Interval = 200;
this.durationUpdateTimer.Tick += new System.EventHandler(this.durationUpdateTimer_Tick);
//
// checkMediaPreviews
//
this.checkMediaPreviews.AutoSize = true;
this.checkMediaPreviews.Location = new System.Drawing.Point(6, 28);
this.checkMediaPreviews.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
this.checkMediaPreviews.Name = "checkMediaPreviews";
this.checkMediaPreviews.Size = new System.Drawing.Size(131, 17);
this.checkMediaPreviews.TabIndex = 1;
this.checkMediaPreviews.Text = "Show Media Previews";
this.toolTip.SetToolTip(this.checkMediaPreviews, "Shows image and video thumbnails in the notification window.");
this.checkMediaPreviews.UseVisualStyleBackColor = true;
//
// TabSettingsNotifications // TabSettingsNotifications
// //
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.panelMiscellaneous); this.Controls.Add(this.panelMiscellaneous);
this.Controls.Add(this.labelMiscellaneous); this.Controls.Add(this.labelSize);
this.Controls.Add(this.labelTimer); this.Controls.Add(this.labelTimer);
this.Controls.Add(this.panelLocation); this.Controls.Add(this.panelLocation);
this.Controls.Add(this.labelLocation); this.Controls.Add(this.labelLocation);
@@ -510,7 +556,7 @@
this.Controls.Add(this.labelGeneral); this.Controls.Add(this.labelGeneral);
this.Controls.Add(this.panelTimer); this.Controls.Add(this.panelTimer);
this.Name = "TabSettingsNotifications"; this.Name = "TabSettingsNotifications";
this.Size = new System.Drawing.Size(340, 684); this.Size = new System.Drawing.Size(340, 731);
this.ParentChanged += new System.EventHandler(this.TabSettingsNotifications_ParentChanged); this.ParentChanged += new System.EventHandler(this.TabSettingsNotifications_ParentChanged);
((System.ComponentModel.ISupportInitialize)(this.trackBarEdgeDistance)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.trackBarEdgeDistance)).EndInit();
this.tableLayoutDurationButtons.ResumeLayout(false); this.tableLayoutDurationButtons.ResumeLayout(false);
@@ -563,8 +609,12 @@
private System.Windows.Forms.Label labelScrollSpeedValue; private System.Windows.Forms.Label labelScrollSpeedValue;
private System.Windows.Forms.TrackBar trackBarScrollSpeed; private System.Windows.Forms.TrackBar trackBarScrollSpeed;
private System.Windows.Forms.Label labelScrollSpeed; private System.Windows.Forms.Label labelScrollSpeed;
private System.Windows.Forms.Label labelMiscellaneous; private System.Windows.Forms.Label labelSize;
private System.Windows.Forms.Panel panelMiscellaneous; private System.Windows.Forms.Panel panelMiscellaneous;
private System.Windows.Forms.Label labelDuration; private System.Windows.Forms.Label labelDuration;
private System.Windows.Forms.Timer durationUpdateTimer;
private System.Windows.Forms.RadioButton radioSizeCustom;
private System.Windows.Forms.RadioButton radioSizeAuto;
private System.Windows.Forms.CheckBox checkMediaPreviews;
} }
} }

View File

@@ -1,5 +1,4 @@
using System; using System;
using System.Globalization;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Core.Controls; using TweetDuck.Core.Controls;
using TweetDuck.Core.Notification; using TweetDuck.Core.Notification;
@@ -14,16 +13,13 @@ namespace TweetDuck.Core.Other.Settings{
InitializeComponent(); InitializeComponent();
this.notification = notification; this.notification = notification;
this.notification.CanMoveWindow = () => radioLocCustom.Checked;
this.notification.Move += (sender, args) => {
if (radioLocCustom.Checked && this.notification.Location != ControlExtensions.InvisibleLocation){
Config.CustomNotificationPosition = this.notification.Location;
}
};
this.notification.Initialized += (sender, args) => { this.notification.Initialized += (sender, args) => {
this.InvokeAsyncSafe(() => this.notification.ShowNotificationForSettings(true)); this.InvokeAsyncSafe(() => {
this.notification.ShowNotificationForSettings(true);
this.notification.Move += notification_Move;
this.notification.ResizeEnd += notification_ResizeEnd;
});
}; };
this.notification.Activated += notification_Activated; this.notification.Activated += notification_Activated;
@@ -39,6 +35,11 @@ namespace TweetDuck.Core.Other.Settings{
comboBoxDisplay.Enabled = trackBarEdgeDistance.Enabled = !radioLocCustom.Checked; comboBoxDisplay.Enabled = trackBarEdgeDistance.Enabled = !radioLocCustom.Checked;
switch(Config.NotificationSize){
case TweetNotification.Size.Auto: radioSizeAuto.Checked = true; break;
case TweetNotification.Size.Custom: radioSizeCustom.Checked = true; break;
}
toolTip.SetToolTip(trackBarDuration, toolTip.GetToolTip(labelDurationValue)); toolTip.SetToolTip(trackBarDuration, toolTip.GetToolTip(labelDurationValue));
trackBarDuration.SetValueSafe(Config.NotificationDurationValue); trackBarDuration.SetValueSafe(Config.NotificationDurationValue);
labelDurationValue.Text = Config.NotificationDurationValue+" ms/c"; labelDurationValue.Text = Config.NotificationDurationValue+" ms/c";
@@ -50,7 +51,7 @@ namespace TweetDuck.Core.Other.Settings{
comboBoxIdlePause.Items.Add("5 minutes"); comboBoxIdlePause.Items.Add("5 minutes");
comboBoxIdlePause.SelectedIndex = Math.Max(0, Array.FindIndex(IdlePauseSeconds, val => val == Config.NotificationIdlePauseSeconds)); comboBoxIdlePause.SelectedIndex = Math.Max(0, Array.FindIndex(IdlePauseSeconds, val => val == Config.NotificationIdlePauseSeconds));
comboBoxDisplay.Items.Add("(Same As "+Program.BrandName+")"); comboBoxDisplay.Items.Add("(Same as TweetDuck)");
foreach(Screen screen in Screen.AllScreens){ foreach(Screen screen in Screen.AllScreens){
comboBoxDisplay.Items.Add(screen.DeviceName.TrimStart('\\', '.')+" ("+screen.Bounds.Width+"x"+screen.Bounds.Height+")"); comboBoxDisplay.Items.Add(screen.DeviceName.TrimStart('\\', '.')+" ("+screen.Bounds.Width+"x"+screen.Bounds.Height+")");
@@ -62,14 +63,18 @@ namespace TweetDuck.Core.Other.Settings{
checkNotificationTimer.Checked = Config.DisplayNotificationTimer; checkNotificationTimer.Checked = Config.DisplayNotificationTimer;
checkTimerCountDown.Enabled = checkNotificationTimer.Checked; checkTimerCountDown.Enabled = checkNotificationTimer.Checked;
checkTimerCountDown.Checked = Config.NotificationTimerCountDown; checkTimerCountDown.Checked = Config.NotificationTimerCountDown;
checkMediaPreviews.Checked = Config.NotificationMediaPreviews;
checkSkipOnLinkClick.Checked = Config.NotificationSkipOnLinkClick; checkSkipOnLinkClick.Checked = Config.NotificationSkipOnLinkClick;
checkNonIntrusive.Checked = Config.NotificationNonIntrusiveMode; checkNonIntrusive.Checked = Config.NotificationNonIntrusiveMode;
trackBarScrollSpeed.SetValueSafe(Config.NotificationScrollSpeed); trackBarScrollSpeed.SetValueSafe(Config.NotificationScrollSpeed);
labelScrollSpeedValue.Text = trackBarScrollSpeed.Value.ToString(CultureInfo.InvariantCulture)+"%"; labelScrollSpeedValue.Text = trackBarScrollSpeed.Value+"%";
trackBarEdgeDistance.SetValueSafe(Config.NotificationEdgeDistance); trackBarEdgeDistance.SetValueSafe(Config.NotificationEdgeDistance);
labelEdgeDistanceValue.Text = trackBarEdgeDistance.Value.ToString(CultureInfo.InvariantCulture)+" px"; labelEdgeDistanceValue.Text = trackBarEdgeDistance.Value+" px";
this.notification.CanMoveWindow = () => radioLocCustom.Checked;
this.notification.CanResizeWindow = radioSizeCustom.Checked;
Disposed += (sender, args) => this.notification.Dispose(); Disposed += (sender, args) => this.notification.Dispose();
} }
@@ -81,6 +86,9 @@ namespace TweetDuck.Core.Other.Settings{
radioLocBR.CheckedChanged += radioLoc_CheckedChanged; radioLocBR.CheckedChanged += radioLoc_CheckedChanged;
radioLocCustom.Click += radioLocCustom_Click; radioLocCustom.Click += radioLocCustom_Click;
radioSizeAuto.CheckedChanged += radioSize_CheckedChanged;
radioSizeCustom.Click += radioSizeCustom_Click;
trackBarDuration.ValueChanged += trackBarDuration_ValueChanged; trackBarDuration.ValueChanged += trackBarDuration_ValueChanged;
btnDurationShort.Click += btnDurationShort_Click; btnDurationShort.Click += btnDurationShort_Click;
btnDurationMedium.Click += btnDurationMedium_Click; btnDurationMedium.Click += btnDurationMedium_Click;
@@ -89,6 +97,7 @@ namespace TweetDuck.Core.Other.Settings{
checkColumnName.CheckedChanged += checkColumnName_CheckedChanged; checkColumnName.CheckedChanged += checkColumnName_CheckedChanged;
checkNotificationTimer.CheckedChanged += checkNotificationTimer_CheckedChanged; checkNotificationTimer.CheckedChanged += checkNotificationTimer_CheckedChanged;
checkTimerCountDown.CheckedChanged += checkTimerCountDown_CheckedChanged; checkTimerCountDown.CheckedChanged += checkTimerCountDown_CheckedChanged;
checkMediaPreviews.CheckedChanged += checkMediaPreviews_CheckedChanged;
checkSkipOnLinkClick.CheckedChanged += checkSkipOnLinkClick_CheckedChanged; checkSkipOnLinkClick.CheckedChanged += checkSkipOnLinkClick_CheckedChanged;
checkNonIntrusive.CheckedChanged += checkNonIntrusive_CheckedChanged; checkNonIntrusive.CheckedChanged += checkNonIntrusive_CheckedChanged;
@@ -113,6 +122,19 @@ namespace TweetDuck.Core.Other.Settings{
notification.Activated -= notification_Activated; notification.Activated -= notification_Activated;
} }
private void notification_Move(object sender, EventArgs e){
if (radioLocCustom.Checked && notification.Location != ControlExtensions.InvisibleLocation){
Config.CustomNotificationPosition = notification.Location;
}
}
private void notification_ResizeEnd(object sender, EventArgs e){
if (radioSizeCustom.Checked){
Config.CustomNotificationSize = notification.BrowserSize;
notification.ShowNotificationForSettings(false);
}
}
private void radioLoc_CheckedChanged(object sender, EventArgs e){ private void radioLoc_CheckedChanged(object sender, EventArgs e){
if (radioLocTL.Checked)Config.NotificationPosition = TweetNotification.Position.TopLeft; if (radioLocTL.Checked)Config.NotificationPosition = TweetNotification.Position.TopLeft;
else if (radioLocTR.Checked)Config.NotificationPosition = TweetNotification.Position.TopRight; else if (radioLocTR.Checked)Config.NotificationPosition = TweetNotification.Position.TopRight;
@@ -133,7 +155,7 @@ namespace TweetDuck.Core.Other.Settings{
comboBoxDisplay.Enabled = trackBarEdgeDistance.Enabled = false; comboBoxDisplay.Enabled = trackBarEdgeDistance.Enabled = false;
notification.ShowNotificationForSettings(false); notification.ShowNotificationForSettings(false);
if (notification.IsFullyOutsideView() && MessageBox.Show("The notification seems to be outside of view, would you like to reset its position?", "Notification is outside view", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2) == DialogResult.Yes){ if (notification.IsFullyOutsideView() && FormMessage.Question("Notification is outside view", "The notification seems to be outside of view, would you like to reset its position?", FormMessage.Yes, FormMessage.No)){
Config.NotificationPosition = TweetNotification.Position.TopRight; Config.NotificationPosition = TweetNotification.Position.TopRight;
notification.MoveToVisibleLocation(); notification.MoveToVisibleLocation();
@@ -144,11 +166,30 @@ namespace TweetDuck.Core.Other.Settings{
} }
} }
private void radioSize_CheckedChanged(object sender, EventArgs e){
if (radioSizeAuto.Checked)Config.NotificationSize = TweetNotification.Size.Auto;
notification.ShowNotificationForSettings(false);
notification.CanResizeWindow = false; // must be after ShowNotificationForSettings
}
private void radioSizeCustom_Click(object sender, EventArgs e){
if (!Config.IsCustomNotificationSizeSet){
Config.CustomNotificationSize = notification.BrowserSize;
}
Config.NotificationSize = TweetNotification.Size.Custom;
notification.CanResizeWindow = true;
notification.ShowNotificationForSettings(false);
}
private void trackBarDuration_ValueChanged(object sender, EventArgs e){ private void trackBarDuration_ValueChanged(object sender, EventArgs e){
durationUpdateTimer.Stop();
durationUpdateTimer.Start();
Config.NotificationDurationValue = trackBarDuration.Value; Config.NotificationDurationValue = trackBarDuration.Value;
labelDurationValue.Text = Config.NotificationDurationValue+" ms/c"; labelDurationValue.Text = Config.NotificationDurationValue+" ms/c";
notification.ShowNotificationForSettings(true);
} }
private void btnDurationShort_Click(object sender, EventArgs e){ private void btnDurationShort_Click(object sender, EventArgs e){
@@ -179,6 +220,10 @@ namespace TweetDuck.Core.Other.Settings{
notification.ShowNotificationForSettings(true); notification.ShowNotificationForSettings(true);
} }
private void checkMediaPreviews_CheckedChanged(object sender, EventArgs e){
Config.NotificationMediaPreviews = checkMediaPreviews.Checked;
}
private void checkSkipOnLinkClick_CheckedChanged(object sender, EventArgs e){ private void checkSkipOnLinkClick_CheckedChanged(object sender, EventArgs e){
Config.NotificationSkipOnLinkClick = checkSkipOnLinkClick.Checked; Config.NotificationSkipOnLinkClick = checkSkipOnLinkClick.Checked;
} }
@@ -193,7 +238,7 @@ namespace TweetDuck.Core.Other.Settings{
private void trackBarScrollSpeed_ValueChanged(object sender, EventArgs e){ private void trackBarScrollSpeed_ValueChanged(object sender, EventArgs e){
if (trackBarScrollSpeed.AlignValueToTick()){ if (trackBarScrollSpeed.AlignValueToTick()){
labelScrollSpeedValue.Text = trackBarScrollSpeed.Value.ToString(CultureInfo.InvariantCulture)+"%"; labelScrollSpeedValue.Text = trackBarScrollSpeed.Value+"%";
Config.NotificationScrollSpeed = trackBarScrollSpeed.Value; Config.NotificationScrollSpeed = trackBarScrollSpeed.Value;
} }
} }
@@ -204,9 +249,14 @@ namespace TweetDuck.Core.Other.Settings{
} }
private void trackBarEdgeDistance_ValueChanged(object sender, EventArgs e){ private void trackBarEdgeDistance_ValueChanged(object sender, EventArgs e){
labelEdgeDistanceValue.Text = trackBarEdgeDistance.Value.ToString(CultureInfo.InvariantCulture)+" px"; labelEdgeDistanceValue.Text = trackBarEdgeDistance.Value+" px";
Config.NotificationEdgeDistance = trackBarEdgeDistance.Value; Config.NotificationEdgeDistance = trackBarEdgeDistance.Value;
notification.ShowNotificationForSettings(false); notification.ShowNotificationForSettings(false);
} }
private void durationUpdateTimer_Tick(object sender, EventArgs e){
notification.ShowNotificationForSettings(true);
durationUpdateTimer.Stop();
}
} }
} }

View File

@@ -77,6 +77,7 @@
this.tbCustomSound.Name = "tbCustomSound"; this.tbCustomSound.Name = "tbCustomSound";
this.tbCustomSound.Size = new System.Drawing.Size(316, 20); this.tbCustomSound.Size = new System.Drawing.Size(316, 20);
this.tbCustomSound.TabIndex = 0; this.tbCustomSound.TabIndex = 0;
this.toolTip.SetToolTip(this.tbCustomSound, "When empty, the default TweetDeck sound notification is used.");
// //
// labelSoundNotification // labelSoundNotification
// //

View File

@@ -3,7 +3,7 @@ using System.Drawing;
using System.IO; using System.IO;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Core.Notification; using TweetDuck.Core.Notification;
using TweetLib.Audio.Utils; using TweetLib.Audio;
namespace TweetDuck.Core.Other.Settings{ namespace TweetDuck.Core.Other.Settings{
partial class TabSettingsSounds : BaseTabSettings{ partial class TabSettingsSounds : BaseTabSettings{
@@ -34,7 +34,7 @@ namespace TweetDuck.Core.Other.Settings{
private void tbCustomSound_TextChanged(object sender, EventArgs e){ private void tbCustomSound_TextChanged(object sender, EventArgs e){
bool isEmpty = string.IsNullOrEmpty(tbCustomSound.Text); bool isEmpty = string.IsNullOrEmpty(tbCustomSound.Text);
tbCustomSound.ForeColor = isEmpty || File.Exists(tbCustomSound.Text) ? SystemColors.WindowText : Color.Maroon; tbCustomSound.ForeColor = isEmpty || File.Exists(tbCustomSound.Text) ? SystemColors.WindowText : Color.Red;
btnPlaySound.Enabled = !isEmpty; btnPlaySound.Enabled = !isEmpty;
btnResetSound.Enabled = !isEmpty; btnResetSound.Enabled = !isEmpty;
} }
@@ -44,7 +44,7 @@ namespace TweetDuck.Core.Other.Settings{
} }
private void sound_PlaybackError(object sender, PlaybackErrorEventArgs e){ private void sound_PlaybackError(object sender, PlaybackErrorEventArgs e){
MessageBox.Show("Could not play custom notification sound."+Environment.NewLine+e.Message, "Notification Sound Error", MessageBoxButtons.OK, MessageBoxIcon.Error); FormMessage.Error("Notification Sound Error", "Could not play custom notification sound.\n"+e.Message, FormMessage.OK);
} }
private void btnBrowseSound_Click(object sender, EventArgs e){ private void btnBrowseSound_Click(object sender, EventArgs e){

View File

@@ -1,36 +1,46 @@
using CefSharp; using CefSharp;
using System; using System;
using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Drawing;
using System.Globalization;
using System.IO; using System.IO;
using System.Net; using System.Net;
using System.Text.RegularExpressions;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Core.Other;
namespace TweetDuck.Core.Utils{ namespace TweetDuck.Core.Utils{
static class BrowserUtils{ static class BrowserUtils{
public static string HeaderAcceptLanguage{ public static string HeaderAcceptLanguage{
get{ get{
string culture = CultureInfo.CurrentCulture.Name; string culture = Program.Culture.Name;
if (culture == "en"){ if (culture == "en"){
return "en-us,en"; return "en-us,en";
} }
else{ else{
return culture.ToLowerInvariant()+",en;q=0.9"; return culture.ToLower()+",en;q=0.9";
} }
} }
} }
public static string HeaderUserAgent => Program.BrandName+" "+Application.ProductVersion; public static string HeaderUserAgent => Program.BrandName+" "+Application.ProductVersion;
public static readonly Color BackgroundColor = Color.FromArgb(28, 99, 153); public static void SetupCefArgs(IDictionary<string, string> args){
public const string BackgroundColorFix = "let e=document.createElement('style');document.head.appendChild(e);e.innerHTML='body::before{background:#1c6399!important}'"; if (!Program.SystemConfig.HardwareAcceleration){
args["disable-gpu"] = "1";
args["disable-gpu-vsync"] = "1";
}
public static readonly string[] DictionaryWords = { args["disable-extensions"] = "1";
"tweetdeck", "TweetDeck", "tweetduck", "TweetDuck", "TD" args["disable-plugins-discovery"] = "1";
}; args["enable-system-flash"] = "0";
if (args.TryGetValue("js-flags", out string jsFlags)){
args["js-flags"] = "--expose-gc "+jsFlags;
}
else{
args["js-flags"] = "--expose-gc";
}
}
public static bool IsValidUrl(string url){ public static bool IsValidUrl(string url){
if (Uri.TryCreate(url, UriKind.Absolute, out Uri uri)){ if (Uri.TryCreate(url, UriKind.Absolute, out Uri uri)){
@@ -48,7 +58,7 @@ namespace TweetDuck.Core.Utils{
OpenExternalBrowserUnsafe(url); OpenExternalBrowserUnsafe(url);
} }
else{ else{
MessageBox.Show("A potentially malicious URL was blocked from opening:"+Environment.NewLine+url, "Blocked URL", MessageBoxButtons.OK, MessageBoxIcon.Warning); FormMessage.Warning("Blocked URL", "A potentially malicious URL was blocked from opening:\n"+url, FormMessage.OK);
} }
} }
@@ -61,12 +71,8 @@ namespace TweetDuck.Core.Utils{
return string.IsNullOrEmpty(file) ? null : file; return string.IsNullOrEmpty(file) ? null : file;
} }
public static string ConvertPascalCaseToScreamingSnakeCase(string str){
return Regex.Replace(str, @"(\p{Ll})(\P{Ll})|(\P{Ll})(\P{Ll}\p{Ll})", "$1$3_$2$4").ToUpperInvariant();
}
public static string GetErrorName(CefErrorCode code){ public static string GetErrorName(CefErrorCode code){
return ConvertPascalCaseToScreamingSnakeCase(Enum.GetName(typeof(CefErrorCode), code) ?? string.Empty); return StringUtils.ConvertPascalCaseToScreamingSnakeCase(Enum.GetName(typeof(CefErrorCode), code) ?? string.Empty);
} }
public static WebClient DownloadFileAsync(string url, string target, Action onSuccess, Action<Exception> onFailure){ public static WebClient DownloadFileAsync(string url, string target, Action onSuccess, Action<Exception> onFailure){
@@ -93,18 +99,14 @@ namespace TweetDuck.Core.Utils{
return client; return client;
} }
public static int Scale(int baseValue, double scaleFactor){
return (int)Math.Round(baseValue*scaleFactor);
}
public static void SetZoomLevel(IBrowser browser, int percentage){ public static void SetZoomLevel(IBrowser browser, int percentage){
browser.GetHost().SetZoomLevel(Math.Log(percentage/100.0, 1.2)); browser.GetHost().SetZoomLevel(Math.Log(percentage/100.0, 1.2));
} }
public static bool IsTweetDeckWebsite(IFrame frame){
return frame.Url.Contains("//tweetdeck.twitter.com/");
}
public static bool IsTwitterWebsite(IFrame frame){
return frame.Url.Contains("//twitter.com/");
}
#if DEBUG #if DEBUG
public static void HandleConsoleMessage(object sender, ConsoleMessageEventArgs e){ public static void HandleConsoleMessage(object sender, ConsoleMessageEventArgs e){
Debug.WriteLine("[Console] {0} ({1}:{2})", e.Message, e.Source, e.Line); Debug.WriteLine("[Console] {0} ({1}:{2})", e.Message, e.Source, e.Line);

View File

@@ -1,38 +0,0 @@
using System;
using System.Text.RegularExpressions;
namespace TweetDuck.Core.Utils{
static class CommandLineArgsParser{
private static readonly Lazy<Regex> SplitRegex = new Lazy<Regex>(() => new Regex(@"([^=\s]+(?:=(?:[^ ]*""[^""]*?""[^ ]*|[^ ]*))?)", RegexOptions.Compiled), false);
public static CommandLineArgs ReadCefArguments(string argumentString){
CommandLineArgs args = new CommandLineArgs();
if (string.IsNullOrWhiteSpace(argumentString)){
return args;
}
foreach(Match match in SplitRegex.Value.Matches(argumentString)){
string matchValue = match.Value;
int indexEquals = matchValue.IndexOf('=');
string key, value;
if (indexEquals == -1){
key = matchValue.TrimStart('-');
value = "1";
}
else{
key = matchValue.Substring(0, indexEquals).TrimStart('-');
value = matchValue.Substring(indexEquals+1).Trim('"');
}
if (key.Length != 0){
args.SetValue(key, value);
}
}
return args;
}
}
}

View File

@@ -67,7 +67,10 @@ namespace TweetDuck.Core.Utils{
private static extern bool BitBlt(IntPtr hdc, int nXDest, int nYDest, int nWidth, int nHeight, IntPtr hdcSrc, int nXSrc, int nYSrc, uint dwRop); private static extern bool BitBlt(IntPtr hdc, int nXDest, int nYDest, int nWidth, int nHeight, IntPtr hdcSrc, int nXSrc, int nYSrc, uint dwRop);
[DllImport("user32.dll")] [DllImport("user32.dll")]
public static extern IntPtr SendMessage(IntPtr hWnd, uint msg, int wParam, IntPtr lParam); public static extern IntPtr SendMessage(IntPtr hWnd, uint msg, UIntPtr wParam, IntPtr lParam);
[DllImport("user32.dll")]
public static extern bool PostMessage(IntPtr hWnd, uint msg, UIntPtr wParam, IntPtr lParam);
[DllImport("user32.dll")] [DllImport("user32.dll")]
public static extern uint RegisterWindowMessage(string messageName); public static extern uint RegisterWindowMessage(string messageName);

22
Core/Utils/StringUtils.cs Normal file
View File

@@ -0,0 +1,22 @@
using System;
using System.Linq;
using System.Text.RegularExpressions;
namespace TweetDuck.Core.Utils{
static class StringUtils{
public static readonly string[] EmptyArray = new string[0];
public static string ExtractBefore(string str, char search, int startIndex = 0){
int index = str.IndexOf(search, startIndex);
return index == -1 ? str : str.Substring(0, index);
}
public static int[] ParseInts(string str, char separator){
return str.Split(new char[]{ separator }, StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).ToArray();
}
public static string ConvertPascalCaseToScreamingSnakeCase(string str){
return Regex.Replace(str, @"(\p{Ll})(\P{Ll})|(\P{Ll})(\P{Ll}\p{Ll})", "$1$3_$2$4").ToUpper();
}
}
}

106
Core/Utils/TwitterUtils.cs Normal file
View File

@@ -0,0 +1,106 @@
using System;
using CefSharp;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using TweetDuck.Core.Other;
namespace TweetDuck.Core.Utils{
static class TwitterUtils{
public const string TweetDeckURL = "https://tweetdeck.twitter.com";
public static readonly Color BackgroundColor = Color.FromArgb(28, 99, 153);
public const string BackgroundColorFix = "let e=document.createElement('style');document.head.appendChild(e);e.innerHTML='body::before{background:#1c6399!important}'";
private static readonly Lazy<Regex> RegexAccountLazy = new Lazy<Regex>(() => new Regex(@"^https?://twitter\.com/([^/]+)/?$", RegexOptions.Compiled), false);
public static Regex RegexAccount => RegexAccountLazy.Value;
public static readonly string[] DictionaryWords = {
"tweetdeck", "TweetDeck", "tweetduck", "TweetDuck", "TD"
};
public enum ImageQuality{
Default, Orig
}
public static bool IsTweetDeckWebsite(IFrame frame){
return frame.Url.Contains("//tweetdeck.twitter.com/");
}
public static bool IsTwitterWebsite(IFrame frame){
return frame.Url.Contains("//twitter.com/");
}
private static string ExtractImageBaseLink(string url){
int dot = url.LastIndexOf('/');
return dot == -1 ? url : StringUtils.ExtractBefore(url, ':', dot);
}
public static string GetImageLink(string url, ImageQuality quality){
if (quality == ImageQuality.Orig){
string result = ExtractImageBaseLink(url);
if (result != url || url.Contains("//pbs.twimg.com/media/")){
result += ":orig";
}
return result;
}
else{
return url;
}
}
public static void DownloadImage(string url, string username, ImageQuality quality){
DownloadImages(new string[]{ url }, username, quality);
}
public static void DownloadImages(string[] urls, string username, ImageQuality quality){
if (urls.Length == 0){
return;
}
string firstImageLink = GetImageLink(urls[0], quality);
int qualityIndex = firstImageLink.IndexOf(':', firstImageLink.LastIndexOf('/'));
string file = BrowserUtils.GetFileNameFromUrl(ExtractImageBaseLink(firstImageLink));
string ext = Path.GetExtension(file); // includes dot
string[] fileNameParts = qualityIndex == -1 ? new string[]{
Path.ChangeExtension(file, null)
} : new string[]{
username,
Path.ChangeExtension(file, null),
firstImageLink.Substring(qualityIndex+1)
};
using(SaveFileDialog dialog = new SaveFileDialog{
AutoUpgradeEnabled = true,
OverwritePrompt = urls.Length == 1,
Title = "Save image",
FileName = $"{string.Join(" ", fileNameParts.Where(part => part.Length > 0))}{ext}",
Filter = (urls.Length == 1 ? "Image" : "Images")+(string.IsNullOrEmpty(ext) ? " (unknown)|*.*" : $" (*{ext})|*{ext}")
}){
if (dialog.ShowDialog() == DialogResult.OK){
void OnFailure(Exception ex){
FormMessage.Error("Image Download", "An error occurred while downloading the image: "+ex.Message, FormMessage.OK);
}
if (urls.Length == 1){
BrowserUtils.DownloadFileAsync(firstImageLink, dialog.FileName, null, OnFailure);
}
else{
string pathBase = Path.ChangeExtension(dialog.FileName, null);
string pathExt = Path.GetExtension(dialog.FileName);
for(int index = 0; index < urls.Length; index++){
BrowserUtils.DownloadFileAsync(GetImageLink(urls[index], quality), $"{pathBase} {index+1}{pathExt}", null, OnFailure);
}
}
}
}
}
}
}

View File

@@ -1,6 +1,7 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Management;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
@@ -11,13 +12,29 @@ namespace TweetDuck.Core.Utils{
private static readonly Lazy<Regex> RegexStripHtmlStyles = new Lazy<Regex>(() => new Regex(@"\s?(?:style|class)="".*?"""), false); private static readonly Lazy<Regex> RegexStripHtmlStyles = new Lazy<Regex>(() => new Regex(@"\s?(?:style|class)="".*?"""), false);
private static readonly Lazy<Regex> RegexOffsetClipboardHtml = new Lazy<Regex>(() => new Regex(@"(?<=EndHTML:|EndFragment:)(\d+)"), false); private static readonly Lazy<Regex> RegexOffsetClipboardHtml = new Lazy<Regex>(() => new Regex(@"(?<=EndHTML:|EndFragment:)(\d+)"), false);
public static int CurrentProcessID { get; }
public static bool ShouldAvoidToolWindow { get; } public static bool ShouldAvoidToolWindow { get; }
static WindowsUtils(){ static WindowsUtils(){
using(Process me = Process.GetCurrentProcess()){
CurrentProcessID = me.Id;
}
Version ver = Environment.OSVersion.Version; Version ver = Environment.OSVersion.Version;
ShouldAvoidToolWindow = ver.Major == 6 && ver.Minor == 2; // windows 8/10 ShouldAvoidToolWindow = ver.Major == 6 && ver.Minor == 2; // windows 8/10
} }
public static void CreateDirectoryForFile(string file){
string dir = Path.GetDirectoryName(file);
if (dir == null){
throw new ArgumentException("Invalid file path: "+file);
}
else if (dir.Length > 0){
Directory.CreateDirectory(dir);
}
}
public static bool CheckFolderWritePermission(string path){ public static bool CheckFolderWritePermission(string path){
string testFile = Path.Combine(path, ".test"); string testFile = Path.Combine(path, ".test");
@@ -72,6 +89,20 @@ namespace TweetDuck.Core.Utils{
}).Start(); }).Start();
} }
public static bool IsChildProcess(int pid){
try{
using(ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT ParentProcessId FROM Win32_Process WHERE ProcessId = "+pid)){
foreach(ManagementBaseObject obj in searcher.Get()){
return (uint)obj["ParentProcessId"] == CurrentProcessID;
}
}
return false;
}catch{
return false;
}
}
public static void ClipboardStripHtmlStyles(){ public static void ClipboardStripHtmlStyles(){
if (!Clipboard.ContainsText(TextDataFormat.Html)){ if (!Clipboard.ContainsText(TextDataFormat.Html)){
return; return;
@@ -105,7 +136,7 @@ namespace TweetDuck.Core.Utils{
try{ try{
Clipboard.SetDataObject(obj); Clipboard.SetDataObject(obj);
}catch(ExternalException e){ }catch(ExternalException e){
Program.Reporter.HandleException("Clipboard Error", Program.BrandName+" could not access the clipboard as it is currently used by another process.", true, e); Program.Reporter.HandleException("Clipboard Error", "TweetDuck could not access the clipboard as it is currently used by another process.", true, e);
} }
} }
} }

View File

@@ -1,9 +1,10 @@
using System; using System;
using System.IO; using System.IO;
using System.Text; using System.Text;
using TweetDuck.Core.Utils;
namespace TweetDuck.Core.Other.Settings.Export{ namespace TweetDuck.Data{
class CombinedFileStream : IDisposable{ sealed class CombinedFileStream : IDisposable{
public const char KeySeparator = '|'; public const char KeySeparator = '|';
private readonly Stream stream; private readonly Stream stream;
@@ -79,8 +80,7 @@ namespace TweetDuck.Core.Other.Settings.Export{
stream.Position += BitConverter.ToInt32(contentLength, 0); stream.Position += BitConverter.ToInt32(contentLength, 0);
string keyName = Encoding.UTF8.GetString(name); string keyName = Encoding.UTF8.GetString(name);
int separatorIndex = keyName.IndexOf(KeySeparator); return StringUtils.ExtractBefore(keyName, KeySeparator);
return separatorIndex == -1 ? keyName : keyName.Substring(0, separatorIndex);
} }
public void Flush(){ public void Flush(){
@@ -96,15 +96,14 @@ namespace TweetDuck.Core.Other.Settings.Export{
public string KeyName{ public string KeyName{
get{ get{
int index = Identifier.IndexOf(KeySeparator); return StringUtils.ExtractBefore(Identifier, KeySeparator);
return index == -1 ? Identifier : Identifier.Substring(0, index);
} }
} }
public string[] KeyValue{ public string[] KeyValue{
get{ get{
int index = Identifier.IndexOf(KeySeparator); int index = Identifier.IndexOf(KeySeparator);
return index == -1 ? new string[0] : Identifier.Substring(index+1).Split(KeySeparator); return index == -1 ? StringUtils.EmptyArray : Identifier.Substring(index+1).Split(KeySeparator);
} }
} }
@@ -121,11 +120,7 @@ namespace TweetDuck.Core.Other.Settings.Export{
public void WriteToFile(string path, bool createDirectory){ public void WriteToFile(string path, bool createDirectory){
if (createDirectory){ if (createDirectory){
string dir = Path.GetDirectoryName(path); WindowsUtils.CreateDirectoryForFile(path);
if (!string.IsNullOrEmpty(dir)){
Directory.CreateDirectory(dir);
}
} }
File.WriteAllBytes(path, contents); File.WriteAllBytes(path, contents);

View File

@@ -1,8 +1,9 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
using System.Text.RegularExpressions;
namespace TweetDuck.Core.Utils{ namespace TweetDuck.Data{
class CommandLineArgs{ sealed class CommandLineArgs{
public static CommandLineArgs FromStringArray(char entryChar, string[] array){ public static CommandLineArgs FromStringArray(char entryChar, string[] array){
CommandLineArgs args = new CommandLineArgs(); CommandLineArgs args = new CommandLineArgs();
ReadStringArray(entryChar, array, args); ReadStringArray(entryChar, array, args);
@@ -32,37 +33,67 @@ namespace TweetDuck.Core.Utils{
} }
} }
public static CommandLineArgs ReadCefArguments(string argumentString){
CommandLineArgs args = new CommandLineArgs();
if (string.IsNullOrWhiteSpace(argumentString)){
return args;
}
foreach(Match match in Regex.Matches(argumentString, @"([^=\s]+(?:=(?:\S*""[^""]*?""\S*|\S*))?)")){
string matchValue = match.Value;
int indexEquals = matchValue.IndexOf('=');
string key, value;
if (indexEquals == -1){
key = matchValue.TrimStart('-');
value = "1";
}
else{
key = matchValue.Substring(0, indexEquals).TrimStart('-');
value = matchValue.Substring(indexEquals+1).Trim('"');
}
if (key.Length != 0){
args.SetValue(key, value);
}
}
return args;
}
private readonly HashSet<string> flags = new HashSet<string>(); private readonly HashSet<string> flags = new HashSet<string>();
private readonly Dictionary<string, string> values = new Dictionary<string, string>(); private readonly Dictionary<string, string> values = new Dictionary<string, string>();
public int Count => flags.Count+values.Count; public int Count => flags.Count+values.Count;
public void AddFlag(string flag){ public void AddFlag(string flag){
flags.Add(flag.ToLowerInvariant()); flags.Add(flag.ToLower());
} }
public bool HasFlag(string flag){ public bool HasFlag(string flag){
return flags.Contains(flag.ToLowerInvariant()); return flags.Contains(flag.ToLower());
} }
public void RemoveFlag(string flag){ public void RemoveFlag(string flag){
flags.Remove(flag.ToLowerInvariant()); flags.Remove(flag.ToLower());
} }
public void SetValue(string key, string value){ public void SetValue(string key, string value){
values[key.ToLowerInvariant()] = value; values[key.ToLower()] = value;
} }
public bool HasValue(string key){ public bool HasValue(string key){
return values.ContainsKey(key.ToLowerInvariant()); return values.ContainsKey(key.ToLower());
} }
public string GetValue(string key, string defaultValue){ public string GetValue(string key, string defaultValue){
return values.TryGetValue(key.ToLowerInvariant(), out string val) ? val : defaultValue; return values.TryGetValue(key.ToLower(), out string val) ? val : defaultValue;
} }
public void RemoveValue(string key){ public void RemoveValue(string key){
values.Remove(key.ToLowerInvariant()); values.Remove(key.ToLower());
} }
public CommandLineArgs Clone(){ public CommandLineArgs Clone(){

View File

@@ -1,7 +1,7 @@
using System; using System;
namespace TweetDuck.Core.Utils{ namespace TweetDuck.Data{
class InjectedHTML{ sealed class InjectedHTML{
public enum Position{ public enum Position{
Before, After Before, After
} }

View File

@@ -0,0 +1,146 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
namespace TweetDuck.Data.Serialization{
sealed class FileSerializer<T>{
private const string NewLineReal = "\r\n";
private const string NewLineCustom = "\r~\n";
private static readonly ITypeConverter BasicSerializerObj = new BasicTypeConverter();
public delegate void HandleUnknownPropertiesHandler(T obj, Dictionary<string, string> data);
public HandleUnknownPropertiesHandler HandleUnknownProperties { get; set; }
private readonly Dictionary<string, PropertyInfo> props;
private readonly Dictionary<Type, ITypeConverter> converters;
public FileSerializer(){
this.props = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public).Where(prop => prop.CanWrite).ToDictionary(prop => prop.Name);
this.converters = new Dictionary<Type, ITypeConverter>();
}
public void RegisterTypeConverter(Type type, ITypeConverter converter){
converters[type] = converter;
}
public void Write(string file, T obj){
using(StreamWriter writer = new StreamWriter(new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None))){
foreach(KeyValuePair<string, PropertyInfo> prop in props){
Type type = prop.Value.PropertyType;
object value = prop.Value.GetValue(obj);
if (!converters.TryGetValue(type, out ITypeConverter serializer)) {
serializer = BasicSerializerObj;
}
if (serializer.TryWriteType(type, value, out string converted)){
if (converted != null){
writer.Write($"{prop.Key} {converted.Replace(Environment.NewLine, NewLineCustom)}");
writer.Write(NewLineReal);
}
}
else{
throw new SerializationException($"Invalid serialization type, conversion failed for: {type}");
}
}
}
}
public void Read(string file, T obj){
Dictionary<string, string> unknownProperties = new Dictionary<string, string>(4);
using(StreamReader reader = new StreamReader(new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read))){
if (reader.Peek() <= 1){
throw new FormatException("Input appears to be a binary file.");
}
foreach(string line in reader.ReadToEnd().Split(new string[]{ NewLineReal }, StringSplitOptions.RemoveEmptyEntries)){
int space = line.IndexOf(' ');
if (space == -1){
throw new SerializationException($"Invalid file format, missing separator: {line}");
}
string property = line.Substring(0, space);
string value = line.Substring(space+1).Replace(NewLineCustom, Environment.NewLine);
if (props.TryGetValue(property, out PropertyInfo info)){
if (!converters.TryGetValue(info.PropertyType, out ITypeConverter serializer)) {
serializer = BasicSerializerObj;
}
if (serializer.TryReadType(info.PropertyType, value, out object converted)){
info.SetValue(obj, converted);
}
else{
throw new SerializationException($"Invalid file format, cannot convert value: {value} (property: {property})");
}
}
else{
unknownProperties[property] = value;
}
}
}
if (unknownProperties.Count > 0){
HandleUnknownProperties?.Invoke(obj, unknownProperties);
if (unknownProperties.Count > 0){
throw new SerializationException($"Invalid file format, unknown properties: {string.Join(", ", unknownProperties.Keys)}+");
}
}
}
private class BasicTypeConverter : ITypeConverter{
bool ITypeConverter.TryWriteType(Type type, object value, out string converted){
switch(Type.GetTypeCode(type)){
case TypeCode.Boolean:
converted = value.ToString();
return true;
case TypeCode.Int32:
converted = ((int)value).ToString(); // cast required for enums
return true;
case TypeCode.String:
converted = value?.ToString();
return true;
default:
converted = null;
return false;
}
}
bool ITypeConverter.TryReadType(Type type, string value, out object converted){
switch(Type.GetTypeCode(type)){
case TypeCode.Boolean:
if (bool.TryParse(value, out bool b)){
converted = b;
return true;
}
else goto default;
case TypeCode.Int32:
if (int.TryParse(value, out int i)){
converted = i;
return true;
}
else goto default;
case TypeCode.String:
converted = value;
return true;
default:
converted = null;
return false;
}
}
}
}
}

View File

@@ -0,0 +1,8 @@
using System;
namespace TweetDuck.Data.Serialization{
interface ITypeConverter{
bool TryWriteType(Type type, object value, out string converted);
bool TryReadType(Type type, string value, out object converted);
}
}

View File

@@ -0,0 +1,28 @@
using System;
namespace TweetDuck.Data.Serialization{
sealed class SingleTypeConverter<T> : ITypeConverter{
public Func<T, string> ConvertToString { get; set; }
public Func<string, T> ConvertToObject { get; set; }
bool ITypeConverter.TryWriteType(Type type, object value, out string converted){
try{
converted = ConvertToString((T)value);
return true;
}catch{
converted = null;
return false;
}
}
bool ITypeConverter.TryReadType(Type type, string value, out object converted){
try{
converted = ConvertToObject(value);
return true;
}catch{
converted = null;
return false;
}
}
}
}

View File

@@ -1,8 +1,8 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
namespace TweetDuck.Core.Utils{ namespace TweetDuck.Data{
class TwoKeyDictionary<K1, K2, V>{ sealed class TwoKeyDictionary<K1, K2, V>{
private readonly Dictionary<K1, Dictionary<K2, V>> dict; private readonly Dictionary<K1, Dictionary<K2, V>> dict;
private readonly int innerCapacity; private readonly int innerCapacity;

View File

@@ -2,10 +2,12 @@
using System.Drawing; using System.Drawing;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Core.Controls; using TweetDuck.Core.Controls;
using TweetDuck.Core.Utils;
using TweetDuck.Data.Serialization;
namespace TweetDuck.Core.Utils{ namespace TweetDuck.Data{
[Serializable] [Serializable] // TODO remove attribute with UserConfigLegacy
class WindowState{ sealed class WindowState{
private Rectangle rect; private Rectangle rect;
private bool isMaximized; private bool isMaximized;
@@ -26,5 +28,17 @@ namespace TweetDuck.Core.Utils{
Save(form); Save(form);
} }
} }
public static readonly SingleTypeConverter<WindowState> Converter = new SingleTypeConverter<WindowState>{
ConvertToString = value => $"{(value.isMaximized ? 'M' : '_')}{value.rect.X} {value.rect.Y} {value.rect.Width} {value.rect.Height}",
ConvertToObject = value => {
int[] elements = StringUtils.ParseInts(value.Substring(1), ' ');
return new WindowState{
rect = new Rectangle(elements[0], elements[1], elements[2], elements[3]),
isMaximized = value[0] == 'M'
};
}
};
} }
} }

View File

@@ -152,7 +152,6 @@
| System.Windows.Forms.AnchorStyles.Left))); | System.Windows.Forms.AnchorStyles.Left)));
this.labelType.BackColor = System.Drawing.Color.DarkGray; this.labelType.BackColor = System.Drawing.Color.DarkGray;
this.labelType.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(238))); this.labelType.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.labelType.LineHeight = 9;
this.labelType.Location = new System.Drawing.Point(0, 0); this.labelType.Location = new System.Drawing.Point(0, 0);
this.labelType.Name = "labelType"; this.labelType.Name = "labelType";
this.labelType.Size = new System.Drawing.Size(18, 109); this.labelType.Size = new System.Drawing.Size(18, 109);

View File

@@ -3,6 +3,7 @@ using System.Diagnostics;
using System.Drawing; using System.Drawing;
using System.IO; using System.IO;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
using TweetDuck.Plugins.Enums; using TweetDuck.Plugins.Enums;
@@ -11,6 +12,8 @@ namespace TweetDuck.Plugins.Controls{
private readonly PluginManager pluginManager; private readonly PluginManager pluginManager;
private readonly Plugin plugin; private readonly Plugin plugin;
private readonly float dpiScale;
public PluginControl(){ public PluginControl(){
InitializeComponent(); InitializeComponent();
} }
@@ -19,12 +22,16 @@ namespace TweetDuck.Plugins.Controls{
this.pluginManager = pluginManager; this.pluginManager = pluginManager;
this.plugin = plugin; this.plugin = plugin;
this.dpiScale = this.GetDPIScale();
this.labelName.Text = plugin.Name; this.labelName.Text = plugin.Name;
this.labelDescription.Text = plugin.CanRun ? plugin.Description : "This plugin requires "+Program.BrandName+" "+plugin.RequiredVersion+" or newer."; this.labelDescription.Text = plugin.CanRun ? plugin.Description : "This plugin requires TweetDuck "+plugin.RequiredVersion+" or newer.";
this.labelVersion.Text = plugin.Version; this.labelVersion.Text = plugin.Version;
this.labelAuthor.Text = plugin.Author; this.labelAuthor.Text = plugin.Author;
this.labelWebsite.Text = plugin.Website; this.labelWebsite.Text = plugin.Website;
this.labelType.LineHeight = BrowserUtils.Scale(9, dpiScale);
UpdatePluginState(); UpdatePluginState();
if (labelDescription.Text.Length == 0){ if (labelDescription.Text.Length == 0){
@@ -40,7 +47,7 @@ namespace TweetDuck.Plugins.Controls{
} }
else{ else{
labelDescription.MaximumSize = new Size(panelDescription.Width-SystemInformation.VerticalScrollBarWidth, 0); labelDescription.MaximumSize = new Size(panelDescription.Width-SystemInformation.VerticalScrollBarWidth, 0);
Height = Math.Min(MinimumSize.Height+9+labelDescription.Height, MaximumSize.Height); Height = Math.Min(MinimumSize.Height+BrowserUtils.Scale(9, dpiScale)+labelDescription.Height, MaximumSize.Height);
} }
} }

View File

@@ -17,7 +17,20 @@ namespace TweetDuck.Plugins.Enums{
} }
} }
public static string GetScriptFile(this PluginEnvironment environment){ public static bool IncludesDisabledPlugins(this PluginEnvironment environment){
return environment == PluginEnvironment.Browser;
}
public static string GetScriptIdentifier(this PluginEnvironment environment){
switch(environment){
case PluginEnvironment.None: return "root:plugins";
case PluginEnvironment.Browser: return "root:plugins.browser";
case PluginEnvironment.Notification: return "root:plugins.notification";
default: return null;
}
}
public static string GetPluginScriptFile(this PluginEnvironment environment){
switch(environment){ switch(environment){
case PluginEnvironment.Browser: return "browser.js"; case PluginEnvironment.Browser: return "browser.js";
case PluginEnvironment.Notification: return "notification.js"; case PluginEnvironment.Notification: return "notification.js";
@@ -25,7 +38,7 @@ namespace TweetDuck.Plugins.Enums{
} }
} }
public static string GetScriptVariables(this PluginEnvironment environment){ public static string GetPluginScriptVariables(this PluginEnvironment environment){
switch(environment){ switch(environment){
case PluginEnvironment.Browser: return "$,$TD,$TDP,TD"; case PluginEnvironment.Browser: return "$,$TD,$TDP,TD";
case PluginEnvironment.Notification: return "$TD,$TDP"; case PluginEnvironment.Notification: return "$TD,$TDP";

View File

@@ -6,7 +6,7 @@ using System.Text;
using TweetDuck.Plugins.Enums; using TweetDuck.Plugins.Enums;
namespace TweetDuck.Plugins{ namespace TweetDuck.Plugins{
class Plugin{ sealed class Plugin{
public string Identifier { get; } public string Identifier { get; }
public PluginGroup Group { get; } public PluginGroup Group { get; }
public PluginEnvironment Environments { get; private set; } public PluginEnvironment Environments { get; private set; }
@@ -42,7 +42,7 @@ namespace TweetDuck.Plugins{
private readonly string pathRoot; private readonly string pathRoot;
private readonly string pathData; private readonly string pathData;
private readonly Dictionary<string, string> metadata = new Dictionary<string, string>(4){ private readonly Dictionary<string, string> metadata = new Dictionary<string, string>(8){
{ "NAME", "" }, { "NAME", "" },
{ "DESCRIPTION", "" }, { "DESCRIPTION", "" },
{ "AUTHOR", "(anonymous)" }, { "AUTHOR", "(anonymous)" },
@@ -84,7 +84,7 @@ namespace TweetDuck.Plugins{
public string GetScriptPath(PluginEnvironment environment){ public string GetScriptPath(PluginEnvironment environment){
if (Environments.HasFlag(environment)){ if (Environments.HasFlag(environment)){
string file = environment.GetScriptFile(); string file = environment.GetPluginScriptFile();
return file != null ? Path.Combine(pathRoot, file) : string.Empty; return file != null ? Path.Combine(pathRoot, file) : string.Empty;
} }
else{ else{
@@ -132,8 +132,7 @@ namespace TweetDuck.Plugins{
} }
public override bool Equals(object obj){ public override bool Equals(object obj){
Plugin plugin = obj as Plugin; return obj is Plugin plugin && plugin.Identifier.Equals(Identifier);
return plugin != null && plugin.Identifier.Equals(Identifier);
} }
public static Plugin CreateFromFolder(string path, PluginGroup group, out string error){ public static Plugin CreateFromFolder(string path, PluginGroup group, out string error){
@@ -153,7 +152,7 @@ namespace TweetDuck.Plugins{
private static bool LoadEnvironments(string path, Plugin plugin, out string error){ private static bool LoadEnvironments(string path, Plugin plugin, out string error){
foreach(string file in Directory.EnumerateFiles(path, "*.js", SearchOption.TopDirectoryOnly).Select(Path.GetFileName)){ foreach(string file in Directory.EnumerateFiles(path, "*.js", SearchOption.TopDirectoryOnly).Select(Path.GetFileName)){
plugin.Environments |= PluginEnvironmentExtensions.Values.FirstOrDefault(env => file.Equals(env.GetScriptFile(), StringComparison.Ordinal)); plugin.Environments |= PluginEnvironmentExtensions.Values.FirstOrDefault(env => file.Equals(env.GetPluginScriptFile(), StringComparison.Ordinal));
} }
if (plugin.Environments == PluginEnvironment.None){ if (plugin.Environments == PluginEnvironment.None){
@@ -184,7 +183,7 @@ namespace TweetDuck.Plugins{
plugin.metadata[currentTag] = currentContents; plugin.metadata[currentTag] = currentContents;
} }
currentTag = line.Substring(1, line.Length-2).ToUpperInvariant(); currentTag = line.Substring(1, line.Length-2).ToUpper();
currentContents = ""; currentContents = "";
if (line.Equals(endTag[0])){ if (line.Equals(endTag[0])){

View File

@@ -3,11 +3,12 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text; using System.Text;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
using TweetDuck.Data;
using TweetDuck.Plugins.Enums; using TweetDuck.Plugins.Enums;
using TweetDuck.Plugins.Events; using TweetDuck.Plugins.Events;
namespace TweetDuck.Plugins{ namespace TweetDuck.Plugins{
class PluginBridge{ sealed class PluginBridge{
private static string SanitizeCacheKey(string key){ private static string SanitizeCacheKey(string key){
return key.Replace('\\', '/').Trim(); return key.Replace('\\', '/').Trim();
} }
@@ -47,9 +48,9 @@ namespace TweetDuck.Plugins{
if (fullPath.Length == 0){ if (fullPath.Length == 0){
switch(folder){ switch(folder){
case PluginFolder.Data: throw new Exception("File path has to be relative to the plugin data folder."); case PluginFolder.Data: throw new ArgumentException("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."); case PluginFolder.Root: throw new ArgumentException("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."); default: throw new ArgumentException("Invalid folder type "+folder+", this is a TweetDuck error.");
} }
} }
else{ else{
@@ -67,9 +68,9 @@ namespace TweetDuck.Plugins{
try{ try{
return fileCache[token, cacheKey] = File.ReadAllText(fullPath, Encoding.UTF8); return fileCache[token, cacheKey] = File.ReadAllText(fullPath, Encoding.UTF8);
}catch(FileNotFoundException){ }catch(FileNotFoundException){
throw new Exception("File not found."); throw new FileNotFoundException("File not found.");
}catch(DirectoryNotFoundException){ }catch(DirectoryNotFoundException){
throw new Exception("Directory not found."); throw new DirectoryNotFoundException("Directory not found.");
} }
} }
@@ -78,9 +79,7 @@ namespace TweetDuck.Plugins{
public void WriteFile(int token, string path, string contents){ public void WriteFile(int token, string path, string contents){
string fullPath = GetFullPathOrThrow(token, PluginFolder.Data, path); string fullPath = GetFullPathOrThrow(token, PluginFolder.Data, path);
// ReSharper disable once AssignNullToNotNullAttribute WindowsUtils.CreateDirectoryForFile(fullPath);
Directory.CreateDirectory(Path.GetDirectoryName(fullPath));
File.WriteAllText(fullPath, contents, Encoding.UTF8); File.WriteAllText(fullPath, contents, Encoding.UTF8);
fileCache[token, SanitizeCacheKey(path)] = contents; fileCache[token, SanitizeCacheKey(path)] = contents;
} }

View File

@@ -28,8 +28,7 @@ namespace TweetDuck.Plugins{
public void Load(string file){ public void Load(string file){
try{ try{
using(FileStream stream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read)) using(StreamReader reader = new StreamReader(new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read), Encoding.UTF8)){
using(StreamReader reader = new StreamReader(stream, Encoding.UTF8)){
string line = reader.ReadLine(); string line = reader.ReadLine();
if (line == "#Disabled"){ if (line == "#Disabled"){
@@ -41,6 +40,7 @@ namespace TweetDuck.Plugins{
} }
} }
}catch(FileNotFoundException){ }catch(FileNotFoundException){
Save(file);
}catch(DirectoryNotFoundException){ }catch(DirectoryNotFoundException){
}catch(Exception e){ }catch(Exception e){
Program.Reporter.HandleException("Plugin Configuration Error", "Could not read the plugin configuration file. If you continue, the list of disabled plugins will be reset to default.", true, e); Program.Reporter.HandleException("Plugin Configuration Error", "Could not read the plugin configuration file. If you continue, the list of disabled plugins will be reset to default.", true, e);
@@ -49,8 +49,7 @@ namespace TweetDuck.Plugins{
public void Save(string file){ public void Save(string file){
try{ try{
using(FileStream stream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None)) using(StreamWriter writer = new StreamWriter(new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None), Encoding.UTF8)){
using(StreamWriter writer = new StreamWriter(stream, Encoding.UTF8)){
writer.WriteLine("#Disabled"); writer.WriteLine("#Disabled");
foreach(string disabled in Disabled){ foreach(string disabled in Disabled){

View File

@@ -9,12 +9,14 @@ using TweetDuck.Resources;
namespace TweetDuck.Plugins{ namespace TweetDuck.Plugins{
sealed class PluginManager{ sealed class PluginManager{
public const string PluginBrowserScriptFile = "plugins.browser.js";
public const string PluginNotificationScriptFile = "plugins.notification.js";
public const string PluginGlobalScriptFile = "plugins.js";
private const int InvalidToken = 0; private const int InvalidToken = 0;
private static readonly Dictionary<PluginEnvironment, string> PluginSetupScripts = new Dictionary<PluginEnvironment, string>(4){
{ PluginEnvironment.None, ScriptLoader.LoadResource("plugins.js") },
{ PluginEnvironment.Browser, ScriptLoader.LoadResource("plugins.browser.js") },
{ PluginEnvironment.Notification, ScriptLoader.LoadResource("plugins.notification.js") }
};
public string PathOfficialPlugins => Path.Combine(rootPath, "official"); public string PathOfficialPlugins => Path.Combine(rootPath, "official");
public string PathCustomPlugins => Path.Combine(rootPath, "user"); public string PathCustomPlugins => Path.Combine(rootPath, "user");
@@ -97,7 +99,17 @@ namespace TweetDuck.Plugins{
Reloaded?.Invoke(this, new PluginErrorEventArgs(loadErrors)); Reloaded?.Invoke(this, new PluginErrorEventArgs(loadErrors));
} }
public void ExecutePlugins(IFrame frame, PluginEnvironment environment, bool includeDisabled){ public void ExecutePlugins(IFrame frame, PluginEnvironment environment){
if (HasAnyPlugin(environment)){
ScriptLoader.ExecuteScript(frame, PluginSetupScripts[environment], environment.GetScriptIdentifier());
ScriptLoader.ExecuteScript(frame, PluginSetupScripts[PluginEnvironment.None], PluginEnvironment.None.GetScriptIdentifier());
ExecutePluginScripts(frame, environment);
}
}
private void ExecutePluginScripts(IFrame frame, PluginEnvironment environment){
bool includeDisabled = environment.IncludesDisabledPlugins();
if (includeDisabled){ if (includeDisabled){
ScriptLoader.ExecuteScript(frame, PluginScriptGenerator.GenerateConfig(Config), "gen:pluginconfig"); ScriptLoader.ExecuteScript(frame, PluginScriptGenerator.GenerateConfig(Config), "gen:pluginconfig");
} }

View File

@@ -1,5 +1,4 @@
using System.Globalization; using TweetDuck.Plugins.Enums;
using TweetDuck.Plugins.Enums;
namespace TweetDuck.Plugins{ namespace TweetDuck.Plugins{
static class PluginScriptGenerator{ static class PluginScriptGenerator{
@@ -9,9 +8,9 @@ namespace TweetDuck.Plugins{
public static string GeneratePlugin(string pluginIdentifier, string pluginContents, int pluginToken, PluginEnvironment environment){ public static string GeneratePlugin(string pluginIdentifier, string pluginContents, int pluginToken, PluginEnvironment environment){
return PluginGen return PluginGen
.Replace("%params", environment.GetScriptVariables()) .Replace("%params", environment.GetPluginScriptVariables())
.Replace("%id", pluginIdentifier) .Replace("%id", pluginIdentifier)
.Replace("%token", pluginToken.ToString(CultureInfo.InvariantCulture)) .Replace("%token", pluginToken.ToString())
.Replace("%contents", pluginContents); .Replace("%contents", pluginContents);
} }
@@ -19,7 +18,7 @@ namespace TweetDuck.Plugins{
/* PluginGen /* PluginGen
(function(%params, $i, $d){ (function(%params, $d){
let tmp = { let tmp = {
id: '%id', id: '%id',
obj: new class extends PluginBase{%contents} obj: new class extends PluginBase{%contents}

View File

@@ -1,18 +1,18 @@
using CefSharp; using CefSharp;
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Configuration; using TweetDuck.Configuration;
using TweetDuck.Core; using TweetDuck.Core;
using TweetDuck.Core.Handling; using TweetDuck.Core.Handling.General;
using TweetDuck.Core.Other; using TweetDuck.Core.Other;
using TweetDuck.Core.Other.Settings.Export; using TweetDuck.Core.Other.Settings.Export;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
using TweetDuck.Plugins; using TweetDuck.Data;
using TweetDuck.Plugins.Events;
using TweetDuck.Updates; using TweetDuck.Updates;
namespace TweetDuck{ namespace TweetDuck{
@@ -20,8 +20,8 @@ namespace TweetDuck{
public const string BrandName = "TweetDuck"; public const string BrandName = "TweetDuck";
public const string Website = "https://tweetduck.chylex.com"; public const string Website = "https://tweetduck.chylex.com";
public const string VersionTag = "1.8"; public const string VersionTag = "1.8.5.1";
public const string VersionFull = "1.8.0.0"; public const string VersionFull = "1.8.5.1";
public static readonly Version Version = new Version(VersionTag); public static readonly Version Version = new Version(VersionTag);
public static readonly bool IsPortable = File.Exists("makeportable"); public static readonly bool IsPortable = File.Exists("makeportable");
@@ -32,102 +32,91 @@ namespace TweetDuck{
public static readonly string ScriptPath = Path.Combine(ProgramPath, "scripts"); public static readonly string ScriptPath = Path.Combine(ProgramPath, "scripts");
public static readonly string PluginPath = Path.Combine(ProgramPath, "plugins"); public static readonly string PluginPath = Path.Combine(ProgramPath, "plugins");
public static readonly string UserConfigFilePath = Path.Combine(StoragePath, "TD_UserConfig.cfg");
public static readonly string SystemConfigFilePath = Path.Combine(StoragePath, "TD_SystemConfig.cfg");
public static readonly string PluginConfigFilePath = Path.Combine(StoragePath, "TD_PluginConfig.cfg");
public static readonly string PluginDataPath = Path.Combine(StoragePath, "TD_Plugins"); public static readonly string PluginDataPath = Path.Combine(StoragePath, "TD_Plugins");
private static readonly string InstallerPath = Path.Combine(StoragePath, "TD_Updates"); private static readonly string InstallerPath = Path.Combine(StoragePath, "TD_Updates");
public static string UserConfigFilePath => Path.Combine(StoragePath, "TD_UserConfig.cfg");
public static string SystemConfigFilePath => Path.Combine(StoragePath, "TD_SystemConfig.cfg");
public static string PluginConfigFilePath => Path.Combine(StoragePath, "TD_PluginConfig.cfg");
private static string ErrorLogFilePath => Path.Combine(StoragePath, "TD_Log.txt"); private static string ErrorLogFilePath => Path.Combine(StoragePath, "TD_Log.txt");
private static string ConsoleLogFilePath => Path.Combine(StoragePath, "TD_Console.txt"); private static string ConsoleLogFilePath => Path.Combine(StoragePath, "TD_Console.txt");
public static uint WindowRestoreMessage; public static uint WindowRestoreMessage;
public static uint SubProcessMessage;
public static uint VideoPlayerMessage;
private static readonly LockManager LockManager = new LockManager(Path.Combine(StoragePath, ".lock")); private static readonly LockManager LockManager = new LockManager(Path.Combine(StoragePath, ".lock"));
private static bool HasCleanedUp; private static bool HasCleanedUp;
public static UserConfig UserConfig { get; private set; } public static UserConfig UserConfig { get; private set; }
public static SystemConfig SystemConfig { get; private set; } public static SystemConfig SystemConfig { get; private set; }
public static Reporter Reporter { get; private set; } public static Reporter Reporter { get; }
public static CultureInfo Culture { get; }
public static event EventHandler UserConfigReplaced; public static event EventHandler UserConfigReplaced;
static Program(){
Culture = CultureInfo.CurrentCulture;
Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
CultureInfo.DefaultThreadCurrentCulture = CultureInfo.InvariantCulture;
Reporter = new Reporter(ErrorLogFilePath);
Reporter.SetupUnhandledExceptionHandler("TweetDuck Has Failed :(");
}
[STAThread] [STAThread]
private static void Main(){ private static void Main(){
Application.EnableVisualStyles(); Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false); Application.SetCompatibleTextRenderingDefault(false);
WindowRestoreMessage = NativeMethods.RegisterWindowMessage("TweetDuckRestore"); WindowRestoreMessage = NativeMethods.RegisterWindowMessage("TweetDuckRestore");
SubProcessMessage = NativeMethods.RegisterWindowMessage("TweetDuckSubProcess");
VideoPlayerMessage = NativeMethods.RegisterWindowMessage("TweetDuckVideoPlayer");
if (!WindowsUtils.CheckFolderWritePermission(StoragePath)){ if (!WindowsUtils.CheckFolderWritePermission(StoragePath)){
MessageBox.Show(BrandName+" does not have write permissions to the storage folder: "+StoragePath, "Permission Error", MessageBoxButtons.OK, MessageBoxIcon.Warning); FormMessage.Warning("Permission Error", "TweetDuck does not have write permissions to the storage folder: "+StoragePath, FormMessage.OK);
return; return;
} }
Reporter = new Reporter(ErrorLogFilePath);
Reporter.SetupUnhandledExceptionHandler(BrandName+" Has Failed :(");
if (Arguments.HasFlag(Arguments.ArgRestart)){ if (Arguments.HasFlag(Arguments.ArgRestart)){
for(int attempt = 0; attempt < 21; attempt++){ LockManager.Result lockResult = LockManager.LockWait(10000);
LockManager.Result lockResult = LockManager.Lock();
if (lockResult == LockManager.Result.Success){ while(lockResult != LockManager.Result.Success){
break; if (lockResult == LockManager.Result.Fail){
} FormMessage.Error("TweetDuck Has Failed :(", "An unknown error occurred accessing the data folder. Please, make sure TweetDuck is not already running. If the problem persists, try restarting your system.", FormMessage.OK);
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; return;
} }
else if (attempt == 20){ else if (!FormMessage.Warning("TweetDuck Cannot Restart", "TweetDuck is taking too long to close.", FormMessage.Retry, FormMessage.Exit)){
using(FormMessage form = new FormMessage(BrandName+" Cannot Restart", BrandName+" is taking too long to close.", MessageBoxIcon.Warning)){
form.CancelButton = form.AddButton("Exit");
form.ActiveControl = form.AddButton("Retry", DialogResult.Retry);
if (form.ShowDialog() == DialogResult.Retry){
attempt /= 2;
continue;
}
return; return;
} }
}
else Thread.Sleep(500); lockResult = LockManager.LockWait(5000);
} }
} }
else{ else{
LockManager.Result lockResult = LockManager.Lock(); LockManager.Result lockResult = LockManager.Lock();
if (lockResult == LockManager.Result.HasProcess){ if (lockResult == LockManager.Result.HasProcess){
if (LockManager.LockingProcess.MainWindowHandle == IntPtr.Zero){ // restore if the original process is in tray if (!LockManager.RestoreLockingProcess(2000) && FormMessage.Error("TweetDuck is Already Running", "Another instance of TweetDuck is already running.\nDo you want to close it?", FormMessage.Yes, FormMessage.No)){
NativeMethods.SendMessage(NativeMethods.HWND_BROADCAST, WindowRestoreMessage, LockManager.LockingProcess.Id, IntPtr.Zero);
if (WindowsUtils.TrySleepUntil(() => {
LockManager.LockingProcess.Refresh();
return LockManager.LockingProcess.HasExited || (LockManager.LockingProcess.MainWindowHandle != IntPtr.Zero && LockManager.LockingProcess.Responding);
}, 2000, 250)){
return; // should trigger on first attempt if succeeded, but wait just in case
}
}
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, 5000)){ if (!LockManager.CloseLockingProcess(10000, 5000)){
MessageBox.Show("Could not close the other process.", BrandName+" Has Failed :(", MessageBoxButtons.OK, MessageBoxIcon.Error); FormMessage.Error("TweetDuck Has Failed :(", "Could not close the other process.", FormMessage.OK);
return; return;
} }
LockManager.Lock(); lockResult = LockManager.Lock();
} }
else return; else return;
} }
else if (lockResult != LockManager.Result.Success){
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); if (lockResult != LockManager.Result.Success){
FormMessage.Error("TweetDuck Has Failed :(", "An unknown error occurred accessing the data folder. Please, make sure TweetDuck is not already running. If the problem persists, try restarting your system.", FormMessage.OK);
return; return;
} }
} }
ReloadConfig();
SystemConfig = SystemConfig.Load(SystemConfigFilePath); SystemConfig = SystemConfig.Load(SystemConfigFilePath);
ReloadConfig();
if (Arguments.HasFlag(Arguments.ArgImportCookies)){ if (Arguments.HasFlag(Arguments.ArgImportCookies)){
ExportManager.ImportCookies(); ExportManager.ImportCookies();
@@ -151,32 +140,21 @@ namespace TweetDuck{
#endif #endif
}; };
CommandLineArgsParser.ReadCefArguments(UserConfig.CustomCefArgs).ToDictionary(settings.CefCommandLineArgs); CommandLineArgs.ReadCefArguments(UserConfig.CustomCefArgs).ToDictionary(settings.CefCommandLineArgs);
BrowserUtils.SetupCefArgs(settings.CefCommandLineArgs);
if (!SystemConfig.HardwareAcceleration){
settings.CefCommandLineArgs["disable-gpu"] = "1";
settings.CefCommandLineArgs["disable-gpu-vsync"] = "1";
}
settings.CefCommandLineArgs["disable-extensions"] = "1";
settings.CefCommandLineArgs["disable-plugins-discovery"] = "1";
settings.CefCommandLineArgs["enable-system-flash"] = "0";
Cef.EnableHighDPISupport();
Cef.Initialize(settings, false, new BrowserProcessHandler()); Cef.Initialize(settings, false, new BrowserProcessHandler());
Application.ApplicationExit += (sender, args) => ExitCleanup(); Application.ApplicationExit += (sender, args) => ExitCleanup();
PluginManager plugins = new PluginManager(PluginPath, PluginConfigFilePath); UpdaterSettings updaterSettings = new UpdaterSettings{
plugins.Reloaded += plugins_Reloaded;
plugins.Executed += plugins_Executed;
plugins.Reload();
FormBrowser mainForm = new FormBrowser(plugins, new UpdaterSettings{
AllowPreReleases = Arguments.HasFlag(Arguments.ArgDebugUpdates), AllowPreReleases = Arguments.HasFlag(Arguments.ArgDebugUpdates),
DismissedUpdate = UserConfig.DismissedUpdate, DismissedUpdate = UserConfig.DismissedUpdate,
InstallerDownloadFolder = InstallerPath InstallerDownloadFolder = InstallerPath
}); };
FormBrowser mainForm = new FormBrowser(updaterSettings);
Application.Run(mainForm); Application.Run(mainForm);
if (mainForm.UpdateInstallerPath != null){ if (mainForm.UpdateInstallerPath != null){
@@ -191,20 +169,6 @@ namespace TweetDuck{
} }
} }
private static void plugins_Reloaded(object sender, PluginErrorEventArgs e){
if (e.HasErrors){
string doubleNL = Environment.NewLine+Environment.NewLine;
MessageBox.Show("The following plugins will not be available until the issues are resolved:"+doubleNL+string.Join(doubleNL, e.Errors), "Error Loading Plugins", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private static void plugins_Executed(object sender, PluginErrorEventArgs e){
if (e.HasErrors){
string doubleNL = Environment.NewLine+Environment.NewLine;
MessageBox.Show("Failed to execute the following plugins:"+doubleNL+string.Join(doubleNL, e.Errors), "Error Executing Plugins", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private static string GetDataStoragePath(){ private static string GetDataStoragePath(){
string custom = Arguments.GetValue(Arguments.ArgDataFolder, null); string custom = Arguments.GetValue(Arguments.ArgDataFolder, null);
@@ -240,11 +204,7 @@ namespace TweetDuck{
ReloadConfig(); ReloadConfig();
} }
public static void Restart(){ public static void Restart(params string[] extraArgs){
Restart(new string[0]);
}
public static void Restart(string[] extraArgs){
CommandLineArgs args = Arguments.GetCurrentClean(); CommandLineArgs args = Arguments.GetCurrentClean();
CommandLineArgs.ReadStringArray('-', extraArgs, args); CommandLineArgs.ReadStringArray('-', extraArgs, args);
RestartWithArgs(args); RestartWithArgs(args);

View File

@@ -6,6 +6,6 @@ PM> Install-Package CefSharp.WinForms -Version 57.0.0
PM> Install-Package Microsoft.VC120.CRT.JetBrains PM> Install-Package Microsoft.VC120.CRT.JetBrains
``` ```
After building, run either `_postbuild.bat` if you want to package the files yourself, or `bld/RUN BUILD.bat` to generate installer files using Inno Setup (make sure the Inno Setup binaries are on your PATH). When building the `Release` configuration, the folder will only contain files intended for distribution (no debug symbols or other unnecessary files). You may package the files yourself, or use `bld/RUN BUILD.bat` to generate installer files using Inno Setup (make sure the Inno Setup binaries are on your PATH).
Built files are then available in **bin/x86**, installer files are generated in **bld/Output**. If you decide to release a custom version publicly, please make it clear that it is not the original TweetDuck. Built files are available in **bin/x86**, installer files are generated in **bld/Output**. If you decide to release a custom version publicly, please make it clear that it is not the original TweetDuck.

View File

@@ -1,14 +1,13 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Drawing; using System.Drawing;
using System.Globalization;
using System.IO; using System.IO;
using System.Text; using System.Text;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Core.Other; using TweetDuck.Core.Other;
namespace TweetDuck{ namespace TweetDuck{
class Reporter{ sealed class Reporter{
private readonly string logFile; private readonly string logFile;
public Reporter(string logFile){ public Reporter(string logFile){
@@ -17,9 +16,7 @@ namespace TweetDuck{
public void SetupUnhandledExceptionHandler(string caption){ public void SetupUnhandledExceptionHandler(string caption){
AppDomain.CurrentDomain.UnhandledException += (sender, args) => { AppDomain.CurrentDomain.UnhandledException += (sender, args) => {
Exception ex = args.ExceptionObject as Exception; if (args.ExceptionObject is Exception ex) {
if (ex != null){
HandleException(caption, "An unhandled exception has occurred.", false, ex); HandleException(caption, "An unhandled exception has occurred.", false, ex);
} }
}; };
@@ -32,7 +29,7 @@ namespace TweetDuck{
build.Append("Please, report all issues to: https://github.com/chylex/TweetDuck/issues\r\n\r\n"); 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("[").Append(DateTime.Now.ToString("G", Program.Culture)).Append("]\r\n");
build.Append(data).Append("\r\n\r\n"); build.Append(data).Append("\r\n\r\n");
try{ try{
@@ -46,22 +43,21 @@ namespace TweetDuck{
public void HandleException(string caption, string message, bool canIgnore, Exception e){ public void HandleException(string caption, string message, bool canIgnore, Exception e){
bool loggedSuccessfully = Log(e.ToString()); bool loggedSuccessfully = Log(e.ToString());
FormMessage form = new FormMessage(caption, message+"\r\nError: "+e.Message, canIgnore ? MessageBoxIcon.Warning : MessageBoxIcon.Error); FormMessage form = new FormMessage(caption, message+"\nError: "+e.Message, canIgnore ? MessageBoxIcon.Warning : MessageBoxIcon.Error);
Button btnExit = form.AddButton("Exit"); Button btnExit = form.AddButton(FormMessage.Exit);
Button btnIgnore = form.AddButton("Ignore", DialogResult.Ignore); Button btnIgnore = form.AddButton(FormMessage.Ignore, DialogResult.Ignore, ControlType.Cancel);
btnIgnore.Enabled = canIgnore; btnIgnore.Enabled = canIgnore;
form.ActiveControl = canIgnore ? btnIgnore : btnExit; form.ActiveControl = canIgnore ? btnIgnore : btnExit;
form.CancelButton = btnIgnore;
Button btnOpenLog = new Button{ Button btnOpenLog = new Button{
Anchor = AnchorStyles.Bottom | AnchorStyles.Left, Anchor = AnchorStyles.Bottom | AnchorStyles.Left,
Enabled = loggedSuccessfully, Enabled = loggedSuccessfully,
Font = SystemFonts.MessageBoxFont, Font = SystemFonts.MessageBoxFont,
Location = new Point(6, 12), Location = new Point(9, 12),
Margin = new Padding(0, 0, 48, 0), Margin = new Padding(0, 0, 48, 0),
Size = new Size(88, 26), Size = new Size(106, 26),
Text = "Show Error Log", Text = "Show Error Log",
UseVisualStyleBackColor = true UseVisualStyleBackColor = true
}; };
@@ -87,9 +83,7 @@ namespace TweetDuck{
Application.EnableVisualStyles(); Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false); Application.SetCompatibleTextRenderingDefault(false);
FormMessage form = new FormMessage(caption, message, MessageBoxIcon.Error); FormMessage.Error(caption, message, "Exit");
form.ActiveControl = form.AddButton("Exit");
form.ShowDialog();
try{ try{
Process.GetCurrentProcess().Kill(); Process.GetCurrentProcess().Kill();

View File

@@ -8,7 +8,7 @@ Edit layout & design
chylex chylex
[version] [version]
1.1.1 1.1.2
[website] [website]
https://tweetduck.chylex.com https://tweetduck.chylex.com

View File

@@ -1,9 +1,3 @@
constructor(){
super({
requiresPageReload: true
})
}
enabled(){ enabled(){
// elements & data // elements & data
this.css = null; this.css = null;
@@ -16,13 +10,11 @@ enabled(){
fontSize: "12px", fontSize: "12px",
hideTweetActions: true, hideTweetActions: true,
moveTweetActionsToRight: true, moveTweetActionsToRight: true,
revertReplies: false,
themeColorTweaks: true, themeColorTweaks: true,
roundedScrollBars: false,
revertIcons: true, revertIcons: true,
smallComposeTextSize: false, smallComposeTextSize: false,
optimizeAnimations: true, optimizeAnimations: true,
avatarRadius: 10 avatarRadius: 2
}; };
this.firstTimeLoad = null; this.firstTimeLoad = null;
@@ -60,7 +52,6 @@ enabled(){
this.firstTimeLoad = obj === null; this.firstTimeLoad = obj === null;
this.onStageReady(); this.onStageReady();
this.injectDeciderReplyHook(obj && obj.revertReplies);
}; };
if (this.$$wasLoadedBefore){ if (this.$$wasLoadedBefore){
@@ -129,9 +120,7 @@ enabled(){
let itemEditDesign = $('<li class="is-selectable"><a href="#" data-action>Edit layout &amp; design</a></li>'); let itemEditDesign = $('<li class="is-selectable"><a href="#" data-action>Edit layout &amp; design</a></li>');
itemTD.after(itemEditDesign); itemTD.after(itemEditDesign);
itemEditDesign.on("click", "a", function(){ itemEditDesign.on("click", "a", this.openEditDesignDialog);
new customDesignModal();
});
itemEditDesign.hover(function(){ itemEditDesign.hover(function(){
$(this).addClass("is-selected"); $(this).addClass("is-selected");
@@ -249,18 +238,7 @@ enabled(){
} }
}); });
// decider injections this.openEditDesignDialog = () => new customDesignModal();
this.injectDeciderReplyHook = enable => {
let prevFunc = TD.decider.updateFromBackend;
TD.decider.updateFromBackend = function(data){
data["simplified_replies"] = !enable;
return prevFunc.apply(this, arguments);
};
TD.decider.updateForGuestId();
this.$requiresReload = enable;
};
// animation optimization // animation optimization
this.optimizations = null; this.optimizations = null;
@@ -353,7 +331,7 @@ enabled(){
} }
this.css.insert("#general_settings .cf { display: none !important }"); this.css.insert("#general_settings .cf { display: none !important }");
this.css.insert("#general_settings .divider-bar::after { display: inline-block; padding-top: 10px; line-height: 17px; content: 'Use the new | Edit layout & design | option in the Settings to modify TweetDeck theme, column width, font size, and other features.' }"); this.css.insert("#settings-modal .js-setting-list li:nth-child(3) { border-bottom: 1px solid #ccd6dd }");
this.css.insert(".txt-base-smallest:not(.icon), .txt-base-largest:not(.icon) { font-size: "+this.config.fontSize+" !important }"); this.css.insert(".txt-base-smallest:not(.icon), .txt-base-largest:not(.icon) { font-size: "+this.config.fontSize+" !important }");
this.css.insert(".avatar { border-radius: "+this.config.avatarRadius+"% !important }"); this.css.insert(".avatar { border-radius: "+this.config.avatarRadius+"% !important }");
@@ -389,22 +367,6 @@ enabled(){
this.css.insert(".compose-text { font-size: 12px !important; height: 120px !important }"); this.css.insert(".compose-text { font-size: 12px !important; height: 120px !important }");
} }
if (!this.config.roundedScrollBars){
this.css.insert(".scroll-styled-v:not(.antiscroll-inner)::-webkit-scrollbar { width: 8px }");
this.css.insert(".scroll-styled-h:not(.antiscroll-inner)::-webkit-scrollbar { height: 8px }");
this.css.insert(".scroll-styled-v::-webkit-scrollbar-thumb { border-radius: 0 }");
this.css.insert(".scroll-styled-h::-webkit-scrollbar-thumb { border-radius: 0 }");
this.css.insert(".antiscroll-scrollbar { border-radius: 0 }");
this.css.insert(".antiscroll-scrollbar-vertical { margin-top: 0 }");
this.css.insert(".antiscroll-scrollbar-horizontal { margin-left: 0 }");
this.css.insert(".app-columns-container::-webkit-scrollbar { height: 9px !important }");
}
if (this.config.revertReplies){
this.css.insert(".activity-header + .tweet .tweet-context { margin-left: -35px }");
this.css.insert(".activity-header + .tweet .tweet-context .obj-left { margin-right: 5px }");
}
if (this.config.revertIcons){ if (this.config.revertIcons){
this.icons = document.createElement("style"); this.icons = document.createElement("style");
this.icons.innerHTML = ` this.icons.innerHTML = `
@@ -497,7 +459,9 @@ enabled(){
.icon-list-filled:before{content:"\\f014";font-family:tweetdeckold} .icon-list-filled:before{content:"\\f014";font-family:tweetdeckold}
.icon-user-filled:before{content:"\\f035";font-family:tweetdeckold} .icon-user-filled:before{content:"\\f035";font-family:tweetdeckold}
.drawer .btn .icon, .app-header .btn .icon { line-height: 1em !important }
.column-header .column-type-icon { bottom: 26px !important } .column-header .column-type-icon { bottom: 26px !important }
.is-options-open .column-type-icon { bottom: 25px !important }
.tweet-footer { margin-top: 6px !important }`; .tweet-footer { margin-top: 6px !important }`;
document.head.appendChild(this.icons); document.head.appendChild(this.icons);
@@ -540,11 +504,6 @@ enabled(){
.txt-base-smallest:not(.icon), .txt-base-largest:not(.icon) { font-size: ${this.config.fontSize} !important } .txt-base-smallest:not(.icon), .txt-base-largest:not(.icon) { font-size: ${this.config.fontSize} !important }
.avatar { border-radius: ${this.config.avatarRadius}% !important } .avatar { border-radius: ${this.config.avatarRadius}% !important }
${this.config.revertReplies ? `
.activity-header + .tweet .tweet-context { margin-left: -35px }
.activity-header + .tweet .tweet-context .obj-left { margin-right: 5px }
` : ``}
${this.config.revertIcons ? ` ${this.config.revertIcons ? `
@font-face { font-family: 'tweetdeckold'; src: url(\"https://ton.twimg.com/tweetdeck-web/web/assets/fonts/tweetdeck-regular-webfont.5f4ea87976.woff\") format(\"woff\"); font-weight: normal; font-style: normal } @font-face { font-family: 'tweetdeckold'; src: url(\"https://ton.twimg.com/tweetdeck-web/web/assets/fonts/tweetdeck-regular-webfont.5f4ea87976.woff\") format(\"woff\"); font-weight: normal; font-style: normal }
.icon-reply:before{content:"\\f006";font-family:tweetdeckold} .icon-reply:before{content:"\\f006";font-family:tweetdeckold}
@@ -575,6 +534,32 @@ ready(){
// modal // modal
$("[data-action='settings-menu']").on("click", this.onSettingsMenuClickedEvent); $("[data-action='settings-menu']").on("click", this.onSettingsMenuClickedEvent);
$(".js-app").append('<div id="td-design-plugin-modal" class="js-modal settings-modal ovl scroll-v scroll-styled-v"></div>'); $(".js-app").append('<div id="td-design-plugin-modal" class="js-modal settings-modal ovl scroll-v scroll-styled-v"></div>');
// global settings override
var me = this;
this.prevFuncSettingsGetInfo = TD.components.GlobalSettings.prototype.getInfo;
this.prevFuncSettingsSwitchTab = TD.components.GlobalSettings.prototype.switchTab;
TD.components.GlobalSettings.prototype.getInfo = function(){
let data = me.prevFuncSettingsGetInfo.apply(this, arguments);
data.tabs.push({
title: "Layout & Design",
action: "tdp-edit-design"
});
return data;
};
TD.components.GlobalSettings.prototype.switchTab = function(tab){
if (tab === "tdp-edit-design"){
me.openEditDesignDialog();
}
else{
return me.prevFuncSettingsSwitchTab.apply(this, arguments);
}
};
} }
disabled(){ disabled(){
@@ -598,6 +583,9 @@ disabled(){
$(window).off("focus", this.onWindowFocusEvent); $(window).off("focus", this.onWindowFocusEvent);
$(window).off("blur", this.onWindowBlurEvent); $(window).off("blur", this.onWindowBlurEvent);
TD.components.GlobalSettings.prototype.getInfo = this.prevFuncSettingsGetInfo;
TD.components.GlobalSettings.prototype.switchTab = this.prevFuncSettingsSwitchTab;
$("[data-action='settings-menu']").off("click", this.onSettingsMenuClickedEvent); $("[data-action='settings-menu']").off("click", this.onSettingsMenuClickedEvent);
$("#td-design-plugin-modal").remove(); $("#td-design-plugin-modal").remove();
} }

View File

@@ -88,10 +88,6 @@
<input data-td-key="moveTweetActionsToRight" class="js-theme-checkbox touch-larger-label" type="checkbox"> <input data-td-key="moveTweetActionsToRight" class="js-theme-checkbox touch-larger-label" type="checkbox">
Tweet actions on the right side Tweet actions on the right side
</label> </label>
<label class="checkbox">
<input data-td-key="revertReplies" data-td-reload class="js-theme-checkbox touch-larger-label" type="checkbox">
Revert reply style (reloads page)
</label>
<!-- DESIGN --> <!-- DESIGN -->
@@ -102,10 +98,6 @@
<input data-td-key="themeColorTweaks" class="js-theme-checkbox touch-larger-label" type="checkbox"> <input data-td-key="themeColorTweaks" class="js-theme-checkbox touch-larger-label" type="checkbox">
Theme color tweaks Theme color tweaks
</label> </label>
<label class="checkbox">
<input data-td-key="roundedScrollBars" class="js-theme-checkbox touch-larger-label" type="checkbox">
Rounded scroll bars
</label>
<label class="checkbox"> <label class="checkbox">
<input data-td-key="revertIcons" class="js-theme-checkbox touch-larger-label" type="checkbox"> <input data-td-key="revertIcons" class="js-theme-checkbox touch-larger-label" type="checkbox">
Revert icon design Revert icon design
@@ -130,7 +122,7 @@
</div> </div>
<div class="td-avatar-shape-item-outer" data-td-key="avatarRadius" data-td-value="10"> <div class="td-avatar-shape-item-outer" data-td-key="avatarRadius" data-td-value="10">
<div class="td-avatar-shape" style="border-radius:10%"></div> <div class="td-avatar-shape" style="border-radius:10%"></div>
<label>Default</label> <label>Legacy</label>
</div> </div>
<div class="td-avatar-shape-item-outer" data-td-key="avatarRadius" data-td-value="30"> <div class="td-avatar-shape-item-outer" data-td-key="avatarRadius" data-td-value="30">
<div class="td-avatar-shape" style="border-radius:30%"></div> <div class="td-avatar-shape" style="border-radius:30%"></div>

View File

@@ -9,7 +9,7 @@ Emoji keyboard
chylex chylex
[version] [version]
1.1 1.2.2
[website] [website]
https://tweetduck.chylex.com https://tweetduck.chylex.com

View File

@@ -29,13 +29,13 @@ enabled(){
// styles // styles
this.css = window.TDPF_createCustomStyle(this); this.css = window.TDPF_createCustomStyle(this);
this.css.insert(".emoji-keyboard { position: absolute; width: 15.35em; background-color: white; border-radius: 2px 2px 3px 3px; font-size: 24px; z-index: 9999 }"); this.css.insert(".emoji-keyboard { position: absolute; width: 15.35em; background-color: white; border-radius: 1px; font-size: 24px; z-index: 9999 }");
this.css.insert(".emoji-keyboard-list { height: 10.14em; padding: 0.1em; box-sizing: border-box; overflow-y: auto }"); this.css.insert(".emoji-keyboard-list { height: 10.14em; padding: 0.1em; box-sizing: border-box; overflow-y: auto }");
this.css.insert(".emoji-keyboard-list .separator { height: 26px }"); this.css.insert(".emoji-keyboard-list .separator { height: 26px }");
this.css.insert(".emoji-keyboard-list img { padding: 0.1em !important; width: 1em; height: 1em; vertical-align: -0.1em; cursor: pointer }"); this.css.insert(".emoji-keyboard-list img { padding: 0.1em !important; width: 1em; height: 1em; vertical-align: -0.1em; cursor: pointer }");
this.css.insert(".emoji-keyboard-search { height: auto; padding: 4px 10px 8px; background-color: #292f33; border-radius: 2px 2px 0 0 }"); this.css.insert(".emoji-keyboard-search { height: auto; padding: 4px 10px 8px; background-color: #292f33; border-radius: 1px 1px 0 0 }");
this.css.insert(".emoji-keyboard-search input { width: 100%; border-radius: 1px; }"); this.css.insert(".emoji-keyboard-search input { width: 100%; border-radius: 1px; }");
this.css.insert(".emoji-keyboard-skintones { height: 1.3em; text-align: center; background-color: #292f33; border-radius: 0 0 2px 2px }"); this.css.insert(".emoji-keyboard-skintones { height: 1.3em; text-align: center; background-color: #292f33; border-radius: 0 0 1px 1px }");
this.css.insert(".emoji-keyboard-skintones div { width: 0.8em; height: 0.8em; margin: 0.25em 0.1em; border-radius: 50%; display: inline-block; box-sizing: border-box; cursor: pointer }"); this.css.insert(".emoji-keyboard-skintones div { width: 0.8em; height: 0.8em; margin: 0.25em 0.1em; border-radius: 50%; display: inline-block; box-sizing: border-box; cursor: pointer }");
this.css.insert(".emoji-keyboard-skintones .sel { border: 2px solid rgba(0, 0, 0, 0.35); box-shadow: 0 0 2px 0 rgba(255, 255, 255, 0.65), 0 0 1px 0 rgba(255, 255, 255, 0.4) inset }"); this.css.insert(".emoji-keyboard-skintones .sel { border: 2px solid rgba(0, 0, 0, 0.35); box-shadow: 0 0 2px 0 rgba(255, 255, 255, 0.65), 0 0 1px 0 rgba(255, 255, 255, 0.4) inset }");
this.css.insert(".emoji-keyboard-skintones :hover { border: 2px solid rgba(0, 0, 0, 0.25); box-shadow: 0 0 1px 0 rgba(255, 255, 255, 0.65), 0 0 1px 0 rgba(255, 255, 255, 0.25) inset }"); this.css.insert(".emoji-keyboard-skintones :hover { border: 2px solid rgba(0, 0, 0, 0.25); box-shadow: 0 0 1px 0 rgba(255, 255, 255, 0.65), 0 0 1px 0 rgba(255, 255, 255, 0.25) inset }");
@@ -225,6 +225,10 @@ enabled(){
} }
}; };
this.composerSendingEvent = function(e){
hideKeyboard();
};
this.documentClickEvent = function(e){ this.documentClickEvent = function(e){
if (me.currentKeyboard && !e.target.classList.contains("js-compose-text")){ if (me.currentKeyboard && !e.target.classList.contains("js-compose-text")){
hideKeyboard(); hideKeyboard();
@@ -246,6 +250,8 @@ enabled(){
} }
ready(){ ready(){
this.composeDrawer = $("[data-drawer='compose']");
this.composePanelScroller = $(".js-compose-scroller", ".js-docked-compose").first().children().first(); this.composePanelScroller = $(".js-compose-scroller", ".js-docked-compose").first().children().first();
this.composePanelScroller.on("scroll", this.composerScrollEvent); this.composePanelScroller.on("scroll", this.composerScrollEvent);
@@ -253,6 +259,7 @@ ready(){
$(document).on("click", this.documentClickEvent); $(document).on("click", this.documentClickEvent);
$(document).on("keydown", this.documentKeyEvent); $(document).on("keydown", this.documentKeyEvent);
$(document).on("uiComposeImageAdded", this.uploadFilesEvent); $(document).on("uiComposeImageAdded", this.uploadFilesEvent);
this.composeDrawer.on("uiComposeTweetSending", this.composerSendingEvent);
// HTML generation // HTML generation
@@ -306,6 +313,9 @@ ready(){
case 2: this.emojiData3.push("___"); break; case 2: this.emojiData3.push("___"); break;
} }
continue;
}
else if (line[0] === '#'){
if (line[1] === '1'){ if (line[1] === '1'){
skinToneState = 1; skinToneState = 1;
} }
@@ -370,5 +380,7 @@ disabled(){
$(document).off("click", this.documentClickEvent); $(document).off("click", this.documentClickEvent);
$(document).off("keydown", this.documentKeyEvent); $(document).off("keydown", this.documentKeyEvent);
$(document).off("uiComposeImageAdded", this.uploadFilesEvent); $(document).off("uiComposeImageAdded", this.uploadFilesEvent);
this.composeDrawer.off("uiComposeTweetSending", this.composerSendingEvent);
TD.mustaches["compose/docked_compose.mustache"] = this.prevComposeMustache; TD.mustaches["compose/docked_compose.mustache"] = this.prevComposeMustache;
} }

View File

@@ -1,6 +1,9 @@
Emoji list: http://unicode.org/emoji/charts/emoji-ordering.html Emoji list: http://unicode.org/emoji/charts/emoji-ordering.html
Emoji order: http://unicode.org/emoji/charts/emoji-ordering.txt Emoji order: http://unicode.org/emoji/charts/emoji-ordering.txt
The instructions contain search & replace regexes. Make sure your editor supports LF characters (\n).
For Brackets, use Find -> Replace in Files with any regex that includes LF.
------------------------ ------------------------
Remove unnecessary info: Remove unnecessary info:
@@ -8,7 +11,7 @@ Remove unnecessary info:
Search: \s;.+?#.+?\s Search: \s;.+?#.+?\s
Replace: ; Replace: ;
Search: U+ Search: U\+
Replace: Replace:
@@ -27,7 +30,18 @@ Replace skin tone variations:
1F9D2;child 1F9D2;child
1F9D2 $;child 1F9D2 $;child
TODO: Update this section with exact regexes
Search: 1F3FB
Replace: $
Search: ^.*1F3F[C-F].*$
Replace:
Search: tone\n\n\n\n
Replace:
Search: : light skin.*$
Replace:
---------------- ----------------
@@ -37,19 +51,9 @@ Move some emoji:
> 1F91D;handshake > 1F91D;handshake
1F463;footprints 1F463;footprints
1F939 $ 200D 2640 FE0F;woman juggling
> 1F6CC;person in bed
> 1F6CC $;person in bed
> 1F6C0;person taking bath
> 1F6C0 $;person taking bath
1F46B;man and woman holding hands
-------------------------
------------------ Remove unsupported emoji:
Remove some emoji:
1F469 $ 200D 1F692;woman firefighter
> remove all non-gendered duplicates below here
3030;wavy dash 3030;wavy dash
> remove copyright > remove copyright
@@ -59,12 +63,81 @@ Remove some emoji:
1F441;eye 1F441;eye
> remove eye in speech bubble > remove eye in speech bubble
1F445;tongue 1F9E0;brain
(more may be present after an emoji update)
------------------------- -------------------------------
Add preprocessor symbols: Remove non-gendered duplicates:
@ = group separator Search: ^(1F(46E|575|482|477|473|471|9D9|9DA|9DB|9DC|9DD|9DE|9DF|64D|64E|645|646|481|64B|647|926|937|486|487|6B6|3C3|46F|9D6|9D7|9D8|3CC|3C4|6A3|3CA|3CB|6B4|6B5|938|93C|93D|93E|939)|26F9)(\s\$;|;).*?\n
@1 = enable skin tones below Replace:
@2 = disable skin tones below
(new ones may be added with an emoji update)
-----------------------------------
Add skin tone preprocessor symbols:
#1 = enable skin tones below
find and replace '$;light skin ' with #1
#2 = disable skin tones below
find the last entry containing '$' and insert #2 below
----------------------
Add separator symbols:
1F64A;speak-no-evil monkey
> @
1F476;baby
1F469 200D 1F467 200D 1F467;family: woman, girl, girl
> @
1F933;selfie
1F48E;gem stone
> @
1F435;monkey face
1F982;scorpion
> @
1F490;bouquet
1F52A;kitchen knife
> @
1F3FA;amphora
1F6C1;bathtub
> @
231B;hourglass done
1F6D2;shopping cart
> @
1F3E7;ATM sign
1F3F3 FE0F 200D 1F308;rainbow flag
> @
1F1E6 1F1E8;Ascension Island
--------------
Final cleanup:
Search: & |,|“|”|o
Replace:
---
Make sure all emoji are formatted correctly and there are no empty lines (no line at the end of file).
Test emoji search by searching for the first and last emoji. If broken, a single emoji could be formatted wrong, or an emoji is not supported by Twitter (such as the 'eye in speech bubble' which causes two images to display and messes up the offsets).
If there is an error in 'td:plugin:official/emoji-keyboard' when typing into the search bar, add a breakpoint to the erroring line and check if 'me.emojiNames.length' equals 'emoji.length'.

View File

@@ -1,14 +1,14 @@
1F600;grinning face 1F600;grinning face
1F601;grinning face with smiling eyes 1F601;beaming face with smiling eyes
1F602;face with tears of joy 1F602;face with tears of joy
1F923;rolling on the floor laughing 1F923;rolling on the floor laughing
1F603;smiling face with open mouth 1F603;grinning face with big eyes
1F604;smiling face with open mouth & smiling eyes 1F604;grinning face with smiling eyes
1F605;smiling face with open mouth & cold sweat 1F605;grinning face with sweat
1F606;smiling face with open mouth & closed eyes 1F606;grinning squinting face
1F609;winking face 1F609;winking face
1F60A;smiling face with smiling eyes 1F60A;smiling face with smiling eyes
1F60B;face savouring delicious food 1F60B;face savoring food
1F60E;smiling face with sunglasses 1F60E;smiling face with sunglasses
1F60D;smiling face with heart-eyes 1F60D;smiling face with heart-eyes
1F618;face blowing a kiss 1F618;face blowing a kiss
@@ -18,14 +18,16 @@
263A;smiling face 263A;smiling face
1F642;slightly smiling face 1F642;slightly smiling face
1F917;hugging face 1F917;hugging face
1F929;star-struck
1F914;thinking face 1F914;thinking face
1F928;face with raised eyebrow
1F610;neutral face 1F610;neutral face
1F611;expressionless face 1F611;expressionless face
1F636;face without mouth 1F636;face without mouth
1F644;face with rolling eyes 1F644;face with rolling eyes
1F60F;smirking face 1F60F;smirking face
1F623;persevering face 1F623;persevering face
1F625;disappointed but relieved face 1F625;sad but relieved face
1F62E;face with open mouth 1F62E;face with open mouth
1F910;zipper-mouth face 1F910;zipper-mouth face
1F62F;hushed face 1F62F;hushed face
@@ -33,13 +35,12 @@
1F62B;tired face 1F62B;tired face
1F634;sleeping face 1F634;sleeping face
1F60C;relieved face 1F60C;relieved face
1F913;nerd face 1F61B;face with tongue
1F61B;face with stuck-out tongue 1F61C;winking face with tongue
1F61C;face with stuck-out tongue & winking eye 1F61D;squinting face with tongue
1F61D;face with stuck-out tongue & closed eyes
1F924;drooling face 1F924;drooling face
1F612;unamused face 1F612;unamused face
1F613;face with cold sweat 1F613;downcast face with sweat
1F614;pensive face 1F614;pensive face
1F615;confused face 1F615;confused face
1F643;upside-down face 1F643;upside-down face
@@ -57,22 +58,30 @@
1F627;anguished face 1F627;anguished face
1F628;fearful face 1F628;fearful face
1F629;weary face 1F629;weary face
1F92F;exploding head
1F62C;grimacing face 1F62C;grimacing face
1F630;face with open mouth & cold sweat 1F630;anxious face with sweat
1F631;face screaming in fear 1F631;face screaming in fear
1F633;flushed face 1F633;flushed face
1F92A;crazy face
1F635;dizzy face 1F635;dizzy face
1F621;pouting face 1F621;pouting face
1F620;angry face 1F620;angry face
1F607;smiling face with halo 1F92C;face with symbols on mouth
1F920;cowboy hat face
1F921;clown face
1F925;lying face
1F637;face with medical mask 1F637;face with medical mask
1F912;face with thermometer 1F912;face with thermometer
1F915;face with head-bandage 1F915;face with head-bandage
1F922;nauseated face 1F922;nauseated face
1F92E;face vomiting
1F927;sneezing face 1F927;sneezing face
1F607;smiling face with halo
1F920;cowboy hat face
1F921;clown face
1F925;lying face
1F92B;shushing face
1F92D;face with hand over mouth
1F9D0;face with monocle
1F913;nerd face
1F608;smiling face with horns 1F608;smiling face with horns
1F47F;angry face with horns 1F47F;angry face with horns
1F479;ogre 1F479;ogre
@@ -84,35 +93,40 @@
1F47E;alien monster 1F47E;alien monster
1F916;robot face 1F916;robot face
1F4A9;pile of poo 1F4A9;pile of poo
1F63A;smiling cat face with open mouth 1F63A;grinning cat face
1F638;grinning cat face with smiling eyes 1F638;grinning cat face with smiling eyes
1F639;cat face with tears of joy 1F639;cat face with tears of joy
1F63B;smiling cat face with heart-eyes 1F63B;smiling cat face with heart-eyes
1F63C;cat face with wry smile 1F63C;cat face with wry smile
1F63D;kissing cat face with closed eyes 1F63D;kissing cat face
1F640;weary cat face 1F640;weary cat face
1F63F;crying cat face 1F63F;crying cat face
1F63E;pouting cat face 1F63E;pouting cat face
1F648;see-no-evil monkey 1F648;see-no-evil monkey
1F649;hear-no-evil monkey 1F649;hear-no-evil monkey
1F64A;speak-no-evil monkey 1F64A;speak-no-evil monkey
@1 enable skin tones @
#1 enable skin tones
1F476;baby
1F476 $;baby
1F9D2;child
1F9D2 $;child
1F466;boy 1F466;boy
1F466 $;boy 1F466 $;boy
1F467;girl 1F467;girl
1F467 $;girl 1F467 $;girl
1F9D1;adult
1F9D1 $;adult
1F468;man 1F468;man
1F468 $;man 1F468 $;man
1F469;woman 1F469;woman
1F469 $;woman 1F469 $;woman
1F9D3;older adult
1F9D3 $;older adult
1F474;old man 1F474;old man
1F474 $;old man 1F474 $;old man
1F475;old woman 1F475;old woman
1F475 $;old woman 1F475 $;old woman
1F476;baby
1F476 $;baby
1F47C;baby angel
1F47C $;baby angel
1F468 200D 2695 FE0F;man health worker 1F468 200D 2695 FE0F;man health worker
1F468 $ 200D 2695 FE0F;man health worker 1F468 $ 200D 2695 FE0F;man health worker
1F469 200D 2695 FE0F;woman health worker 1F469 200D 2695 FE0F;woman health worker
@@ -193,30 +207,62 @@
1F477 $ 200D 2642 FE0F;man construction worker 1F477 $ 200D 2642 FE0F;man construction worker
1F477 200D 2640 FE0F;woman construction worker 1F477 200D 2640 FE0F;woman construction worker
1F477 $ 200D 2640 FE0F;woman construction worker 1F477 $ 200D 2640 FE0F;woman construction worker
1F934;prince
1F934 $;prince
1F478;princess
1F478 $;princess
1F473 200D 2642 FE0F;man wearing turban 1F473 200D 2642 FE0F;man wearing turban
1F473 $ 200D 2642 FE0F;man wearing turban 1F473 $ 200D 2642 FE0F;man wearing turban
1F473 200D 2640 FE0F;woman wearing turban 1F473 200D 2640 FE0F;woman wearing turban
1F473 $ 200D 2640 FE0F;woman wearing turban 1F473 $ 200D 2640 FE0F;woman wearing turban
1F472;man with Chinese cap
1F472 $;man with Chinese cap
1F9D5;woman with headscarf
1F9D5 $;woman with headscarf
1F9D4;bearded person
1F9D4 $;bearded person
1F471 200D 2642 FE0F;blond-haired man 1F471 200D 2642 FE0F;blond-haired man
1F471 $ 200D 2642 FE0F;blond-haired man 1F471 $ 200D 2642 FE0F;blond-haired man
1F471 200D 2640 FE0F;blond-haired woman 1F471 200D 2640 FE0F;blond-haired woman
1F471 $ 200D 2640 FE0F;blond-haired woman 1F471 $ 200D 2640 FE0F;blond-haired woman
1F935;man in tuxedo
1F935 $;man in tuxedo
1F470;bride with veil
1F470 $;bride with veil
1F930;pregnant woman
1F930 $;pregnant woman
1F931;breast-feeding
1F931 $;breast-feeding
1F47C;baby angel
1F47C $;baby angel
1F385;Santa Claus 1F385;Santa Claus
1F385 $;Santa Claus 1F385 $;Santa Claus
1F936;Mrs. Claus 1F936;Mrs. Claus
1F936 $;Mrs. Claus 1F936 $;Mrs. Claus
1F478;princess 1F9D9 200D 2640 FE0F;woman mage
1F478 $;princess 1F9D9 $ 200D 2640 FE0F;woman mage
1F934;prince 1F9D9 200D 2642 FE0F;man mage
1F934 $;prince 1F9D9 $ 200D 2642 FE0F;man mage
1F470;bride with veil 1F9DA 200D 2640 FE0F;woman fairy
1F470 $;bride with veil 1F9DA $ 200D 2640 FE0F;woman fairy
1F935;man in tuxedo 1F9DA 200D 2642 FE0F;man fairy
1F935 $;man in tuxedo 1F9DA $ 200D 2642 FE0F;man fairy
1F930;pregnant woman 1F9DB 200D 2640 FE0F;woman vampire
1F930 $;pregnant woman 1F9DB $ 200D 2640 FE0F;woman vampire
1F472;man with Chinese cap 1F9DB 200D 2642 FE0F;man vampire
1F472 $;man with Chinese cap 1F9DB $ 200D 2642 FE0F;man vampire
1F9DC 200D 2640 FE0F;mermaid
1F9DC $ 200D 2640 FE0F;mermaid
1F9DC 200D 2642 FE0F;merman
1F9DC $ 200D 2642 FE0F;merman
1F9DD 200D 2640 FE0F;woman elf
1F9DD $ 200D 2640 FE0F;woman elf
1F9DD 200D 2642 FE0F;man elf
1F9DD $ 200D 2642 FE0F;man elf
1F9DE 200D 2640 FE0F;woman genie
1F9DE 200D 2642 FE0F;man genie
1F9DF 200D 2640 FE0F;woman zombie
1F9DF 200D 2642 FE0F;man zombie
1F64D 200D 2642 FE0F;man frowning 1F64D 200D 2642 FE0F;man frowning
1F64D $ 200D 2642 FE0F;man frowning 1F64D $ 200D 2642 FE0F;man frowning
1F64D 200D 2640 FE0F;woman frowning 1F64D 200D 2640 FE0F;woman frowning
@@ -273,10 +319,26 @@
1F483 $;woman dancing 1F483 $;woman dancing
1F57A;man dancing 1F57A;man dancing
1F57A $;man dancing 1F57A $;man dancing
1F46F 200D 2642 FE0F;men with bunny ears partying 1F46F 200D 2642 FE0F;men with bunny ears
1F46F 200D 2640 FE0F;women with bunny ears partying 1F46F 200D 2640 FE0F;women with bunny ears
1F574;man in business suit levitating 1F9D6 200D 2640 FE0F;woman in steamy room
1F574 $;man in business suit levitating 1F9D6 $ 200D 2640 FE0F;woman in steamy room
1F9D6 200D 2642 FE0F;man in steamy room
1F9D6 $ 200D 2642 FE0F;man in steamy room
1F9D7 200D 2640 FE0F;woman climbing
1F9D7 $ 200D 2640 FE0F;woman climbing
1F9D7 200D 2642 FE0F;man climbing
1F9D7 $ 200D 2642 FE0F;man climbing
1F9D8 200D 2640 FE0F;woman in lotus position
1F9D8 $ 200D 2640 FE0F;woman in lotus position
1F9D8 200D 2642 FE0F;man in lotus position
1F9D8 $ 200D 2642 FE0F;man in lotus position
1F6C0;person taking bath
1F6C0 $;person taking bath
1F6CC;person in bed
1F6CC $;person in bed
1F574;man in suit levitating
1F574 $;man in suit levitating
1F5E3;speaking head 1F5E3;speaking head
1F464;bust in silhouette 1F464;bust in silhouette
1F465;busts in silhouette 1F465;busts in silhouette
@@ -338,52 +400,48 @@
1F939 $ 200D 2642 FE0F;man juggling 1F939 $ 200D 2642 FE0F;man juggling
1F939 200D 2640 FE0F;woman juggling 1F939 200D 2640 FE0F;woman juggling
1F939 $ 200D 2640 FE0F;woman juggling 1F939 $ 200D 2640 FE0F;woman juggling
1F6CC;person in bed
1F6CC $;person in bed
1F6C0;person taking bath
1F6C0 $;person taking bath
1F46B;man and woman holding hands 1F46B;man and woman holding hands
1F46C;two men holding hands 1F46C;two men holding hands
1F46D;two women holding hands 1F46D;two women holding hands
1F48F;kiss 1F48F;kiss
1F469 200D 2764 FE0F 200D 1F48B 200D 1F468;kiss 1F469 200D 2764 FE0F 200D 1F48B 200D 1F468;kiss woman man
1F468 200D 2764 FE0F 200D 1F48B 200D 1F468;kiss 1F468 200D 2764 FE0F 200D 1F48B 200D 1F468;kiss man man
1F469 200D 2764 FE0F 200D 1F48B 200D 1F469;kiss 1F469 200D 2764 FE0F 200D 1F48B 200D 1F469;kiss woman woman
1F491;couple with heart 1F491;couple with heart
1F469 200D 2764 FE0F 200D 1F468;couple with heart 1F469 200D 2764 FE0F 200D 1F468;couple with heart woman man
1F468 200D 2764 FE0F 200D 1F468;couple with heart 1F468 200D 2764 FE0F 200D 1F468;couple with heart man man
1F469 200D 2764 FE0F 200D 1F469;couple with heart 1F469 200D 2764 FE0F 200D 1F469;couple with heart woman woman
1F46A;family 1F46A;family
1F468 200D 1F469 200D 1F466;family 1F468 200D 1F469 200D 1F466;family man woman boy
1F468 200D 1F469 200D 1F467;family 1F468 200D 1F469 200D 1F467;family man woman girl
1F468 200D 1F469 200D 1F467 200D 1F466;family 1F468 200D 1F469 200D 1F467 200D 1F466;family man woman girl boy
1F468 200D 1F469 200D 1F466 200D 1F466;family 1F468 200D 1F469 200D 1F466 200D 1F466;family man woman boy boy
1F468 200D 1F469 200D 1F467 200D 1F467;family 1F468 200D 1F469 200D 1F467 200D 1F467;family man woman girl girl
1F468 200D 1F468 200D 1F466;family 1F468 200D 1F468 200D 1F466;family man man boy
1F468 200D 1F468 200D 1F467;family 1F468 200D 1F468 200D 1F467;family man man girl
1F468 200D 1F468 200D 1F467 200D 1F466;family 1F468 200D 1F468 200D 1F467 200D 1F466;family man man girl boy
1F468 200D 1F468 200D 1F466 200D 1F466;family 1F468 200D 1F468 200D 1F466 200D 1F466;family man man boy boy
1F468 200D 1F468 200D 1F467 200D 1F467;family 1F468 200D 1F468 200D 1F467 200D 1F467;family man man girl girl
1F469 200D 1F469 200D 1F466;family 1F469 200D 1F469 200D 1F466;family woman woman boy
1F469 200D 1F469 200D 1F467;family 1F469 200D 1F469 200D 1F467;family woman woman girl
1F469 200D 1F469 200D 1F467 200D 1F466;family 1F469 200D 1F469 200D 1F467 200D 1F466;family woman woman girl boy
1F469 200D 1F469 200D 1F466 200D 1F466;family 1F469 200D 1F469 200D 1F466 200D 1F466;family woman woman boy boy
1F469 200D 1F469 200D 1F467 200D 1F467;family 1F469 200D 1F469 200D 1F467 200D 1F467;family woman woman girl girl
1F468 200D 1F466;family 1F468 200D 1F466;family man boy
1F468 200D 1F466 200D 1F466;family 1F468 200D 1F466 200D 1F466;family man boy boy
1F468 200D 1F467;family 1F468 200D 1F467;family man girl
1F468 200D 1F467 200D 1F466;family 1F468 200D 1F467 200D 1F466;family man girl boy
1F468 200D 1F467 200D 1F467;family 1F468 200D 1F467 200D 1F467;family man girl girl
1F469 200D 1F466;family 1F469 200D 1F466;family woman boy
1F469 200D 1F466 200D 1F466;family 1F469 200D 1F466 200D 1F466;family woman boy boy
1F469 200D 1F467;family 1F469 200D 1F467;family woman girl
1F469 200D 1F467 200D 1F466;family 1F469 200D 1F467 200D 1F466;family woman girl boy
1F469 200D 1F467 200D 1F467;family 1F469 200D 1F467 200D 1F467;family woman girl girl
@ @
1F4AA;flexed biceps
1F4AA $;flexed biceps
1F933;selfie 1F933;selfie
1F933 $;selfie 1F933 $;selfie
1F4AA;flexed biceps
1F4AA $;flexed biceps
1F448;backhand index pointing left 1F448;backhand index pointing left
1F448 $;backhand index pointing left 1F448 $;backhand index pointing left
1F449;backhand index pointing right 1F449;backhand index pointing right
@@ -406,8 +464,8 @@
1F918 $;sign of the horns 1F918 $;sign of the horns
1F919;call me hand 1F919;call me hand
1F919 $;call me hand 1F919 $;call me hand
1F590;raised hand with fingers splayed 1F590;hand with fingers splayed
1F590 $;raised hand with fingers splayed 1F590 $;hand with fingers splayed
270B;raised hand 270B;raised hand
270B $;raised hand 270B $;raised hand
1F44C;OK hand 1F44C;OK hand
@@ -428,14 +486,18 @@
1F91A $;raised back of hand 1F91A $;raised back of hand
1F44B;waving hand 1F44B;waving hand
1F44B $;waving hand 1F44B $;waving hand
1F44F;clapping hands 1F91F;love-you gesture
1F44F $;clapping hands 1F91F $;love-you gesture
270D;writing hand 270D;writing hand
270D $;writing hand 270D $;writing hand
1F44F;clapping hands
1F44F $;clapping hands
1F450;open hands 1F450;open hands
1F450 $;open hands 1F450 $;open hands
1F64C;raising hands 1F64C;raising hands
1F64C $;raising hands 1F64C $;raising hands
1F932;palms up together
1F932 $;palms up together
1F64F;folded hands 1F64F;folded hands
1F64F $;folded hands 1F64F $;folded hands
1F485;nail polish 1F485;nail polish
@@ -444,10 +506,12 @@
1F442 $;ear 1F442 $;ear
1F443;nose 1F443;nose
1F443 $;nose 1F443 $;nose
#2 no more skin tones beyond this point
1F91D;handshake 1F91D;handshake
1F463;footprints 1F463;footprints
1F440;eyes 1F440;eyes
1F441;eye 1F441;eye
1F9E0;brain
1F445;tongue 1F445;tongue
1F444;mouth 1F444;mouth
1F48B;kiss mark 1F48B;kiss mark
@@ -461,6 +525,7 @@
1F499;blue heart 1F499;blue heart
1F49A;green heart 1F49A;green heart
1F49B;yellow heart 1F49B;yellow heart
1F9E1;orange heart
1F49C;purple heart 1F49C;purple heart
1F5A4;black heart 1F5A4;black heart
1F49D;heart with ribbon 1F49D;heart with ribbon
@@ -485,6 +550,10 @@
1F454;necktie 1F454;necktie
1F455;t-shirt 1F455;t-shirt
1F456;jeans 1F456;jeans
1F9E3;scarf
1F9E4;gloves
1F9E5;coat
1F9E6;socks
1F457;dress 1F457;dress
1F458;kimono 1F458;kimono
1F459;bikini 1F459;bikini
@@ -503,12 +572,13 @@
1F452;womans hat 1F452;womans hat
1F3A9;top hat 1F3A9;top hat
1F393;graduation cap 1F393;graduation cap
1F9E2;billed cap
26D1;rescue workers helmet 26D1;rescue workers helmet
1F4FF;prayer beads 1F4FF;prayer beads
1F484;lipstick 1F484;lipstick
1F48D;ring 1F48D;ring
1F48E;gem stone 1F48E;gem stone
@2 no more skin tones beyond this point @
1F435;monkey face 1F435;monkey face
1F412;monkey 1F412;monkey
1F98D;gorilla 1F98D;gorilla
@@ -525,8 +595,9 @@
1F406;leopard 1F406;leopard
1F434;horse face 1F434;horse face
1F40E;horse 1F40E;horse
1F98C;deer
1F984;unicorn face 1F984;unicorn face
1F993;zebra
1F98C;deer
1F42E;cow face 1F42E;cow face
1F402;ox 1F402;ox
1F403;water buffalo 1F403;water buffalo
@@ -540,6 +611,7 @@
1F410;goat 1F410;goat
1F42A;camel 1F42A;camel
1F42B;two-hump camel 1F42B;two-hump camel
1F992;giraffe
1F418;elephant 1F418;elephant
1F98F;rhinoceros 1F98F;rhinoceros
1F42D;mouse face 1F42D;mouse face
@@ -549,6 +621,7 @@
1F430;rabbit face 1F430;rabbit face
1F407;rabbit 1F407;rabbit
1F43F;chipmunk 1F43F;chipmunk
1F994;hedgehog
1F987;bat 1F987;bat
1F43B;bear face 1F43B;bear face
1F428;koala 1F428;koala
@@ -573,6 +646,8 @@
1F40D;snake 1F40D;snake
1F432;dragon face 1F432;dragon face
1F409;dragon 1F409;dragon
1F995;sauropod
1F996;T-Rex
1F433;spouting whale 1F433;spouting whale
1F40B;whale 1F40B;whale
1F42C;dolphin 1F42C;dolphin
@@ -585,12 +660,13 @@
1F980;crab 1F980;crab
1F990;shrimp 1F990;shrimp
1F991;squid 1F991;squid
1F98B;butterfly
1F40C;snail 1F40C;snail
1F98B;butterfly
1F41B;bug 1F41B;bug
1F41C;ant 1F41C;ant
1F41D;honeybee 1F41D;honeybee
1F41E;lady beetle 1F41E;lady beetle
1F997;cricket
1F577;spider 1F577;spider
1F578;spider web 1F578;spider web
1F982;scorpion 1F982;scorpion
@@ -618,7 +694,6 @@
1F342;fallen leaf 1F342;fallen leaf
1F343;leaf fluttering in wind 1F343;leaf fluttering in wind
1F347;grapes 1F347;grapes
@
1F348;melon 1F348;melon
1F349;watermelon 1F349;watermelon
1F34A;tangerine 1F34A;tangerine
@@ -633,6 +708,7 @@
1F353;strawberry 1F353;strawberry
1F95D;kiwi fruit 1F95D;kiwi fruit
1F345;tomato 1F345;tomato
1F965;coconut
1F951;avocado 1F951;avocado
1F346;eggplant 1F346;eggplant
1F954;potato 1F954;potato
@@ -640,21 +716,25 @@
1F33D;ear of corn 1F33D;ear of corn
1F336;hot pepper 1F336;hot pepper
1F952;cucumber 1F952;cucumber
1F966;broccoli
1F344;mushroom 1F344;mushroom
1F95C;peanuts 1F95C;peanuts
1F330;chestnut 1F330;chestnut
1F35E;bread 1F35E;bread
1F950;croissant 1F950;croissant
1F956;baguette bread 1F956;baguette bread
1F968;pretzel
1F95E;pancakes 1F95E;pancakes
1F9C0;cheese wedge 1F9C0;cheese wedge
1F356;meat on bone 1F356;meat on bone
1F357;poultry leg 1F357;poultry leg
1F969;cut of meat
1F953;bacon 1F953;bacon
1F354;hamburger 1F354;hamburger
1F35F;french fries 1F35F;french fries
1F355;pizza 1F355;pizza
1F32D;hot dog 1F32D;hot dog
1F96A;sandwich
1F32E;taco 1F32E;taco
1F32F;burrito 1F32F;burrito
1F959;stuffed flatbread 1F959;stuffed flatbread
@@ -662,8 +742,10 @@
1F373;cooking 1F373;cooking
1F958;shallow pan of food 1F958;shallow pan of food
1F372;pot of food 1F372;pot of food
1F963;bowl with spoon
1F957;green salad 1F957;green salad
1F37F;popcorn 1F37F;popcorn
1F96B;canned food
1F371;bento box 1F371;bento box
1F358;rice cracker 1F358;rice cracker
1F359;rice ball 1F359;rice ball
@@ -677,6 +759,9 @@
1F364;fried shrimp 1F364;fried shrimp
1F365;fish cake with swirl 1F365;fish cake with swirl
1F361;dango 1F361;dango
1F95F;dumpling
1F960;fortune cookie
1F961;takeout box
1F366;soft ice cream 1F366;soft ice cream
1F367;shaved ice 1F367;shaved ice
1F368;ice cream 1F368;ice cream
@@ -684,6 +769,7 @@
1F36A;cookie 1F36A;cookie
1F382;birthday cake 1F382;birthday cake
1F370;shortcake 1F370;shortcake
1F967;pie
1F36B;chocolate bar 1F36B;chocolate bar
1F36C;candy 1F36C;candy
1F36D;lollipop 1F36D;lollipop
@@ -702,6 +788,8 @@
1F37B;clinking beer mugs 1F37B;clinking beer mugs
1F942;clinking glasses 1F942;clinking glasses
1F943;tumbler glass 1F943;tumbler glass
1F964;cup with straw
1F962;chopsticks
1F37D;fork and knife with plate 1F37D;fork and knife with plate
1F374;fork and knife 1F374;fork and knife
1F944;spoon 1F944;spoon
@@ -726,7 +814,7 @@
1F3DF;stadium 1F3DF;stadium
1F3DB;classical building 1F3DB;classical building
1F3D7;building construction 1F3D7;building construction
1F3D8;house 1F3D8;houses
1F3D9;cityscape 1F3D9;cityscape
1F3DA;derelict house 1F3DA;derelict house
1F3E0;house 1F3E0;house
@@ -775,7 +863,7 @@
1F682;locomotive 1F682;locomotive
1F683;railway car 1F683;railway car
1F684;high-speed train 1F684;high-speed train
1F685;high-speed train with bullet nose 1F685;bullet train
1F686;train 1F686;train
1F687;metro 1F687;metro
1F688;light rail 1F688;light rail
@@ -829,8 +917,9 @@
1F69F;suspension railway 1F69F;suspension railway
1F6A0;mountain cableway 1F6A0;mountain cableway
1F6A1;aerial tramway 1F6A1;aerial tramway
1F680;rocket
1F6F0;satellite 1F6F0;satellite
1F680;rocket
1F6F8;flying saucer
1F6CE;bellhop bell 1F6CE;bellhop bell
1F6AA;door 1F6AA;door
1F6CF;bed 1F6CF;bed
@@ -839,36 +928,36 @@
1F6BF;shower 1F6BF;shower
1F6C1;bathtub 1F6C1;bathtub
@ @
231B;hourglass 231B;hourglass done
23F3;hourglass with flowing sand 23F3;hourglass not done
231A;watch 231A;watch
23F0;alarm clock 23F0;alarm clock
23F1;stopwatch 23F1;stopwatch
23F2;timer clock 23F2;timer clock
1F570;mantelpiece clock 1F570;mantelpiece clock
1F55B;twelve oclock 1F55B;twelve clock
1F567;twelve-thirty 1F567;twelve-thirty
1F550;one oclock 1F550;one clock
1F55C;one-thirty 1F55C;one-thirty
1F551;two oclock 1F551;two clock
1F55D;two-thirty 1F55D;two-thirty
1F552;three oclock 1F552;three clock
1F55E;three-thirty 1F55E;three-thirty
1F553;four oclock 1F553;four clock
1F55F;four-thirty 1F55F;four-thirty
1F554;five oclock 1F554;five clock
1F560;five-thirty 1F560;five-thirty
1F555;six oclock 1F555;six clock
1F561;six-thirty 1F561;six-thirty
1F556;seven oclock 1F556;seven clock
1F562;seven-thirty 1F562;seven-thirty
1F557;eight oclock 1F557;eight clock
1F563;eight-thirty 1F563;eight-thirty
1F558;nine oclock 1F558;nine clock
1F564;nine-thirty 1F564;nine-thirty
1F559;ten oclock 1F559;ten clock
1F565;ten-thirty 1F565;ten-thirty
1F55A;eleven oclock 1F55A;eleven clock
1F566;eleven-thirty 1F566;eleven-thirty
1F311;new moon 1F311;new moon
1F312;waxing crescent moon 1F312;waxing crescent moon
@@ -880,11 +969,11 @@
1F318;waning crescent moon 1F318;waning crescent moon
1F319;crescent moon 1F319;crescent moon
1F31A;new moon face 1F31A;new moon face
1F31B;first quarter moon with face 1F31B;first quarter moon face
1F31C;last quarter moon with face 1F31C;last quarter moon face
1F321;thermometer 1F321;thermometer
2600;sun 2600;sun
1F31D;full moon with face 1F31D;full moon face
1F31E;sun with face 1F31E;sun with face
2B50;white medium star 2B50;white medium star
1F31F;glowing star 1F31F;glowing star
@@ -949,7 +1038,7 @@
1F3BE;tennis 1F3BE;tennis
1F3B1;pool 8 ball 1F3B1;pool 8 ball
1F3B3;bowling 1F3B3;bowling
1F3CF;cricket 1F3CF;cricket game
1F3D1;field hockey 1F3D1;field hockey
1F3D2;ice hockey 1F3D2;ice hockey
1F3D3;ping pong 1F3D3;ping pong
@@ -963,6 +1052,8 @@
1F3A3;fishing pole 1F3A3;fishing pole
1F3BD;running shirt 1F3BD;running shirt
1F3BF;skis 1F3BF;skis
1F6F7;sled
1F94C;curling stone
1F3AE;video game 1F3AE;video game
1F579;joystick 1F579;joystick
1F3B2;game die 1F3B2;game die
@@ -1024,8 +1115,8 @@
1F4F8;camera with flash 1F4F8;camera with flash
1F4F9;video camera 1F4F9;video camera
1F4FC;videocassette 1F4FC;videocassette
1F50D;left-pointing magnifying glass 1F50D;magnifying glass tilted left
1F50E;right-pointing magnifying glass 1F50E;magnifying glass tilted right
1F52C;microscope 1F52C;microscope
1F52D;telescope 1F52D;telescope
1F4E1;satellite antenna 1F4E1;satellite antenna
@@ -1177,7 +1268,7 @@
2934;right arrow curving up 2934;right arrow curving up
2935;right arrow curving down 2935;right arrow curving down
1F503;clockwise vertical arrows 1F503;clockwise vertical arrows
1F504;anticlockwise arrows button 1F504;counterclockwise arrows button
1F519;BACK arrow 1F519;BACK arrow
1F51A;END arrow 1F51A;END arrow
1F51B;ON! arrow 1F51B;ON! arrow
@@ -1232,11 +1323,14 @@
1F4F6;antenna bars 1F4F6;antenna bars
1F4F3;vibration mode 1F4F3;vibration mode
1F4F4;mobile phone off 1F4F4;mobile phone off
2640;female sign
2642;male sign
2695;medical symbol
267B;recycling symbol 267B;recycling symbol
1F4DB;name badge
269C;fleur-de-lis 269C;fleur-de-lis
1F530;Japanese symbol for beginner
1F531;trident emblem 1F531;trident emblem
1F4DB;name badge
1F530;Japanese symbol for beginner
2B55;heavy large circle 2B55;heavy large circle
2705;white heavy check mark 2705;white heavy check mark
2611;ballot box with check 2611;ballot box with check
@@ -1245,9 +1339,6 @@
274C;cross mark 274C;cross mark
274E;cross mark button 274E;cross mark button
2795;heavy plus sign 2795;heavy plus sign
2640;female sign
2642;male sign
2695;medical symbol
2796;heavy minus sign 2796;heavy minus sign
2797;heavy division sign 2797;heavy division sign
27B0;curly loop 27B0;curly loop
@@ -1263,18 +1354,18 @@
2755;white exclamation mark 2755;white exclamation mark
2757;exclamation mark 2757;exclamation mark
3030;wavy dash 3030;wavy dash
0023 FE0F 20E3;keycap 0023 FE0F 20E3;keycap #
002A FE0F 20E3;keycap 002A FE0F 20E3;keycap *
0030 FE0F 20E3;keycap 0030 FE0F 20E3;keycap 0
0031 FE0F 20E3;keycap 0031 FE0F 20E3;keycap 1
0032 FE0F 20E3;keycap 0032 FE0F 20E3;keycap 2
0033 FE0F 20E3;keycap 0033 FE0F 20E3;keycap 3
0034 FE0F 20E3;keycap 0034 FE0F 20E3;keycap 4
0035 FE0F 20E3;keycap 0035 FE0F 20E3;keycap 5
0036 FE0F 20E3;keycap 0036 FE0F 20E3;keycap 6
0037 FE0F 20E3;keycap 0037 FE0F 20E3;keycap 7
0038 FE0F 20E3;keycap 0038 FE0F 20E3;keycap 8
0039 FE0F 20E3;keycap 0039 FE0F 20E3;keycap 9
1F51F;keycap 10 1F51F;keycap 10
1F4AF;hundred points 1F4AF;hundred points
1F520;input latin uppercase 1F520;input latin uppercase
@@ -1299,23 +1390,23 @@
1F198;SOS button 1F198;SOS button
1F199;UP! button 1F199;UP! button
1F19A;VS button 1F19A;VS button
1F201;Japanese here button 1F201;Japanese here button
1F202;Japanese service charge button 1F202;Japanese service charge button
1F237;Japanese monthly amount button 1F237;Japanese monthly amount button
1F236;Japanese not free of charge button 1F236;Japanese not free of charge button
1F22F;Japanese reserved button 1F22F;Japanese reserved button
1F250;Japanese bargain button 1F250;Japanese bargain button
1F239;Japanese discount button 1F239;Japanese discount button
1F21A;Japanese free of charge button 1F21A;Japanese free of charge button
1F232;Japanese prohibited button 1F232;Japanese prohibited button
1F251;Japanese acceptable button 1F251;Japanese acceptable button
1F238;Japanese application button 1F238;Japanese application button
1F234;Japanese passing grade button 1F234;Japanese passing grade button
1F233;Japanese vacancy button 1F233;Japanese vacancy button
3297;Japanese congratulations button 3297;Japanese congratulations button
3299;Japanese secret button 3299;Japanese secret button
1F23A;Japanese open for business button 1F23A;Japanese open for business button
1F235;Japanese no vacancy button 1F235;Japanese no vacancy button
25AA;black small square 25AA;black small square
25AB;white small square 25AB;white small square
25FB;white medium square 25FB;white medium square
@@ -1349,7 +1440,7 @@
1F1E6 1F1E9;Andorra 1F1E6 1F1E9;Andorra
1F1E6 1F1EA;United Arab Emirates 1F1E6 1F1EA;United Arab Emirates
1F1E6 1F1EB;Afghanistan 1F1E6 1F1EB;Afghanistan
1F1E6 1F1EC;Antigua & Barbuda 1F1E6 1F1EC;Antigua Barbuda
1F1E6 1F1EE;Anguilla 1F1E6 1F1EE;Anguilla
1F1E6 1F1F1;Albania 1F1E6 1F1F1;Albania
1F1E6 1F1F2;Armenia 1F1E6 1F1F2;Armenia
@@ -1362,7 +1453,7 @@
1F1E6 1F1FC;Aruba 1F1E6 1F1FC;Aruba
1F1E6 1F1FD;Åland Islands 1F1E6 1F1FD;Åland Islands
1F1E6 1F1FF;Azerbaijan 1F1E6 1F1FF;Azerbaijan
1F1E7 1F1E6;Bosnia & Herzegovina 1F1E7 1F1E6;Bosnia Herzegovina
1F1E7 1F1E7;Barbados 1F1E7 1F1E7;Barbados
1F1E7 1F1E9;Bangladesh 1F1E7 1F1E9;Bangladesh
1F1E7 1F1EA;Belgium 1F1E7 1F1EA;Belgium
@@ -1402,7 +1493,7 @@
1F1E8 1F1FC;Curaçao 1F1E8 1F1FC;Curaçao
1F1E8 1F1FD;Christmas Island 1F1E8 1F1FD;Christmas Island
1F1E8 1F1FE;Cyprus 1F1E8 1F1FE;Cyprus
1F1E8 1F1FF;Czech Republic 1F1E8 1F1FF;Czechia
1F1E9 1F1EA;Germany 1F1E9 1F1EA;Germany
1F1E9 1F1EC;Diego Garcia 1F1E9 1F1EC;Diego Garcia
1F1E9 1F1EF;Djibouti 1F1E9 1F1EF;Djibouti
@@ -1410,7 +1501,7 @@
1F1E9 1F1F2;Dominica 1F1E9 1F1F2;Dominica
1F1E9 1F1F4;Dominican Republic 1F1E9 1F1F4;Dominican Republic
1F1E9 1F1FF;Algeria 1F1E9 1F1FF;Algeria
1F1EA 1F1E6;Ceuta & Melilla 1F1EA 1F1E6;Ceuta Melilla
1F1EA 1F1E8;Ecuador 1F1EA 1F1E8;Ecuador
1F1EA 1F1EA;Estonia 1F1EA 1F1EA;Estonia
1F1EA 1F1EC;Egypt 1F1EA 1F1EC;Egypt
@@ -1439,13 +1530,13 @@
1F1EC 1F1F5;Guadeloupe 1F1EC 1F1F5;Guadeloupe
1F1EC 1F1F6;Equatorial Guinea 1F1EC 1F1F6;Equatorial Guinea
1F1EC 1F1F7;Greece 1F1EC 1F1F7;Greece
1F1EC 1F1F8;South Georgia & South Sandwich Islands 1F1EC 1F1F8;South Georgia South Sandwich Islands
1F1EC 1F1F9;Guatemala 1F1EC 1F1F9;Guatemala
1F1EC 1F1FA;Guam 1F1EC 1F1FA;Guam
1F1EC 1F1FC;Guinea-Bissau 1F1EC 1F1FC;Guinea-Bissau
1F1EC 1F1FE;Guyana 1F1EC 1F1FE;Guyana
1F1ED 1F1F0;Hong Kong SAR China 1F1ED 1F1F0;Hong Kong SAR China
1F1ED 1F1F2;Heard & McDonald Islands 1F1ED 1F1F2;Heard McDonald Islands
1F1ED 1F1F3;Honduras 1F1ED 1F1F3;Honduras
1F1ED 1F1F7;Croatia 1F1ED 1F1F7;Croatia
1F1ED 1F1F9;Haiti 1F1ED 1F1F9;Haiti
@@ -1470,7 +1561,7 @@
1F1F0 1F1ED;Cambodia 1F1F0 1F1ED;Cambodia
1F1F0 1F1EE;Kiribati 1F1F0 1F1EE;Kiribati
1F1F0 1F1F2;Comoros 1F1F0 1F1F2;Comoros
1F1F0 1F1F3;St. Kitts & Nevis 1F1F0 1F1F3;St. Kitts Nevis
1F1F0 1F1F5;North Korea 1F1F0 1F1F5;North Korea
1F1F0 1F1F7;South Korea 1F1F0 1F1F7;South Korea
1F1F0 1F1FC;Kuwait 1F1F0 1F1FC;Kuwait
@@ -1530,7 +1621,7 @@
1F1F5 1F1ED;Philippines 1F1F5 1F1ED;Philippines
1F1F5 1F1F0;Pakistan 1F1F5 1F1F0;Pakistan
1F1F5 1F1F1;Poland 1F1F5 1F1F1;Poland
1F1F5 1F1F2;St. Pierre & Miquelon 1F1F5 1F1F2;St. Pierre Miquelon
1F1F5 1F1F3;Pitcairn Islands 1F1F5 1F1F3;Pitcairn Islands
1F1F5 1F1F7;Puerto Rico 1F1F5 1F1F7;Puerto Rico
1F1F5 1F1F8;Palestinian Territories 1F1F5 1F1F8;Palestinian Territories
@@ -1551,7 +1642,7 @@
1F1F8 1F1EC;Singapore 1F1F8 1F1EC;Singapore
1F1F8 1F1ED;St. Helena 1F1F8 1F1ED;St. Helena
1F1F8 1F1EE;Slovenia 1F1F8 1F1EE;Slovenia
1F1F8 1F1EF;Svalbard & Jan Mayen 1F1F8 1F1EF;Svalbard Jan Mayen
1F1F8 1F1F0;Slovakia 1F1F8 1F1F0;Slovakia
1F1F8 1F1F1;Sierra Leone 1F1F8 1F1F1;Sierra Leone
1F1F8 1F1F2;San Marino 1F1F8 1F1F2;San Marino
@@ -1559,13 +1650,13 @@
1F1F8 1F1F4;Somalia 1F1F8 1F1F4;Somalia
1F1F8 1F1F7;Suriname 1F1F8 1F1F7;Suriname
1F1F8 1F1F8;South Sudan 1F1F8 1F1F8;South Sudan
1F1F8 1F1F9;São Tomé & Príncipe 1F1F8 1F1F9;São Tomé Príncipe
1F1F8 1F1FB;El Salvador 1F1F8 1F1FB;El Salvador
1F1F8 1F1FD;Sint Maarten 1F1F8 1F1FD;Sint Maarten
1F1F8 1F1FE;Syria 1F1F8 1F1FE;Syria
1F1F8 1F1FF;Swaziland 1F1F8 1F1FF;Swaziland
1F1F9 1F1E6;Tristan da Cunha 1F1F9 1F1E6;Tristan da Cunha
1F1F9 1F1E8;Turks & Caicos Islands 1F1F9 1F1E8;Turks Caicos Islands
1F1F9 1F1E9;Chad 1F1F9 1F1E9;Chad
1F1F9 1F1EB;French Southern Territories 1F1F9 1F1EB;French Southern Territories
1F1F9 1F1EC;Togo 1F1F9 1F1EC;Togo
@@ -1577,7 +1668,7 @@
1F1F9 1F1F3;Tunisia 1F1F9 1F1F3;Tunisia
1F1F9 1F1F4;Tonga 1F1F9 1F1F4;Tonga
1F1F9 1F1F7;Turkey 1F1F9 1F1F7;Turkey
1F1F9 1F1F9;Trinidad & Tobago 1F1F9 1F1F9;Trinidad Tobago
1F1F9 1F1FB;Tuvalu 1F1F9 1F1FB;Tuvalu
1F1F9 1F1FC;Taiwan 1F1F9 1F1FC;Taiwan
1F1F9 1F1FF;Tanzania 1F1F9 1F1FF;Tanzania
@@ -1589,13 +1680,13 @@
1F1FA 1F1FE;Uruguay 1F1FA 1F1FE;Uruguay
1F1FA 1F1FF;Uzbekistan 1F1FA 1F1FF;Uzbekistan
1F1FB 1F1E6;Vatican City 1F1FB 1F1E6;Vatican City
1F1FB 1F1E8;St. Vincent & Grenadines 1F1FB 1F1E8;St. Vincent Grenadines
1F1FB 1F1EA;Venezuela 1F1FB 1F1EA;Venezuela
1F1FB 1F1EC;British Virgin Islands 1F1FB 1F1EC;British Virgin Islands
1F1FB 1F1EE;U.S. Virgin Islands 1F1FB 1F1EE;U.S. Virgin Islands
1F1FB 1F1F3;Vietnam 1F1FB 1F1F3;Vietnam
1F1FB 1F1FA;Vanuatu 1F1FB 1F1FA;Vanuatu
1F1FC 1F1EB;Wallis & Futuna 1F1FC 1F1EB;Wallis Futuna
1F1FC 1F1F8;Samoa 1F1FC 1F1F8;Samoa
1F1FD 1F1F0;Kosovo 1F1FD 1F1F0;Kosovo
1F1FE 1F1EA;Yemen 1F1FE 1F1EA;Yemen
@@ -1603,3 +1694,6 @@
1F1FF 1F1E6;South Africa 1F1FF 1F1E6;South Africa
1F1FF 1F1F2;Zambia 1F1FF 1F1F2;Zambia
1F1FF 1F1FC;Zimbabwe 1F1FF 1F1FC;Zimbabwe
1F3F4 E0067 E0062 E0065 E006E E0067 E007F;England
1F3F4 E0067 E0062 E0073 E0063 E0074 E007F;Scotland
1F3F4 E0067 E0062 E0077 E006C E0073 E007F;Wales

View File

@@ -8,7 +8,7 @@ Templates
chylex chylex
[version] [version]
1.0 1.0.1
[website] [website]
https://tweetduck.chylex.com https://tweetduck.chylex.com

View File

@@ -53,8 +53,10 @@ enabled(){
this.css.insert(".templates-modal-wrap { width: 100%; height: 100%; padding: 49px; position: absolute; z-index: 999; box-sizing: border-box; background-color: rgba(0, 0, 0, 0.5); }"); this.css.insert(".templates-modal-wrap { width: 100%; height: 100%; padding: 49px; position: absolute; z-index: 999; box-sizing: border-box; background-color: rgba(0, 0, 0, 0.5); }");
this.css.insert(".templates-modal { width: 100%; height: 100%; background-color: #fff; display: flex; }"); this.css.insert(".templates-modal { width: 100%; height: 100%; background-color: #fff; display: flex; }");
this.css.insert(".templates-modal > div { display: flex; flex-direction: column; }"); this.css.insert(".templates-modal > div { display: flex; flex-direction: column; }");
this.css.insert(".templates-modal-bottom { flex: 0 0 auto; padding: 16px; text-align: right; }");
this.css.insert(".templates-modal-bottom button { margin-left: 4px; }"); this.css.insert(".templates-modal-bottom { flex: 0 0 auto; padding: 16px; }");
this.css.insert(".template-list .templates-modal-bottom { display: flex; justify-content: space-between; }");
this.css.insert(".template-editor .templates-modal-bottom { text-align: right; }");
this.css.insert(".template-list { height: 100%; flex: 1 1 auto; }"); this.css.insert(".template-list { height: 100%; flex: 1 1 auto; }");
this.css.insert(".template-list ul { list-style-type: none; font-size: 24px; color: #222; flex: 1 1 auto; padding: 12px; overflow-y: auto; }"); this.css.insert(".template-list ul { list-style-type: none; font-size: 24px; color: #222; flex: 1 1 auto; padding: 12px; overflow-y: auto; }");
@@ -265,6 +267,7 @@ enabled(){
<ul></ul> <ul></ul>
<div class="templates-modal-bottom"> <div class="templates-modal-bottom">
<button data-action="close" class="btn" style="background-color:#d2d2d2;border-color:#ccd0d2"><i class="icon icon-close icon-small padding-rs"></i><span class="label">Close</span></button>
<button data-action="new-template" class="btn btn-positive"><i class="icon icon-plus icon-small padding-rs"></i><span class="label">New Template</span></button> <button data-action="new-template" class="btn btn-positive"><i class="icon icon-plus icon-small padding-rs"></i><span class="label">New Template</span></button>
</div> </div>
</div> </div>
@@ -298,7 +301,7 @@ enabled(){
<div class="templates-modal-bottom"> <div class="templates-modal-bottom">
<button data-action="editor-cancel" class="btn"><i class="icon icon-close icon-small padding-rs"></i><span class="label">Cancel</span></button> <button data-action="editor-cancel" class="btn"><i class="icon icon-close icon-small padding-rs"></i><span class="label">Cancel</span></button>
<button data-action="editor-confirm" class="btn btn-positive"><i class="icon icon-check icon-small padding-rs"></i><span class="label">Confirm</span></button> <button data-action="editor-confirm" class="btn btn-positive" style="margin-left:4px"><i class="icon icon-check icon-small padding-rs"></i><span class="label">Confirm</span></button>
</div> </div>
</div> </div>
</div> </div>
@@ -392,6 +395,10 @@ enabled(){
toggleEditor(); toggleEditor();
onTemplatesUpdated(true); onTemplatesUpdated(true);
break; break;
case "close":
hideTemplateModal();
break;
} }
$(this).blur(); $(this).blur();

View File

@@ -3,7 +3,7 @@ using CefSharp.WinForms;
using System; using System;
using System.IO; using System.IO;
using System.Text; using System.Text;
using System.Windows.Forms; using TweetDuck.Core.Other;
namespace TweetDuck.Resources{ namespace TweetDuck.Resources{
static class ScriptLoader{ static class ScriptLoader{
@@ -14,7 +14,7 @@ namespace TweetDuck.Resources{
return File.ReadAllText(Path.Combine(Program.ScriptPath, name), Encoding.UTF8); return File.ReadAllText(Path.Combine(Program.ScriptPath, name), Encoding.UTF8);
}catch(Exception ex){ }catch(Exception ex){
if (!silent){ if (!silent){
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); FormMessage.Error("TweetDuck Has Failed :(", "Unfortunately, TweetDuck could not load the "+name+" file. The program will continue running with limited functionality.\n\n"+ex.Message, FormMessage.OK);
} }
return null; return null;

View File

@@ -56,7 +56,7 @@
// //
var appendToFunction = function(func, extension){ var appendToFunction = function(func, extension){
return function(){ return function(){
var res = func.apply(this, arguments); let res = func.apply(this, arguments);
extension.apply(this, arguments); extension.apply(this, arguments);
return res; return res;
}; };
@@ -66,7 +66,7 @@
// Function: Returns true if an object has a specified property, otherwise returns false without throwing an error. // Function: Returns true if an object has a specified property, otherwise returns false without throwing an error.
// //
var ensurePropertyExists = function(obj, ...chain){ var ensurePropertyExists = function(obj, ...chain){
for(var index = 0; index < chain.length; index++){ for(let index = 0; index < chain.length; index++){
if (!obj.hasOwnProperty(chain[index])){ if (!obj.hasOwnProperty(chain[index])){
$TD.crashDebug("Missing property "+chain[index]+" in chain [obj]."+chain.join(".")); $TD.crashDebug("Missing property "+chain[index]+" in chain [obj]."+chain.join("."));
return false; return false;
@@ -97,52 +97,84 @@
// Function: Event callback for a new tweet. // Function: Event callback for a new tweet.
// //
var onNewTweet = (function(){ var onNewTweet = (function(){
let recentMessages = new Set();
let recentTweets = new Set(); let recentTweets = new Set();
let recentTweetTimer = null; let recentTweetTimer = null;
let startRecentTweetTimer = () => { let resetRecentTweets = () => {
if (recentTweetTimer){
window.clearTimeout(recentTweetTimer);
}
recentTweetTimer = window.setTimeout(() => {
recentTweetTimer = null; recentTweetTimer = null;
recentTweets.clear(); recentTweets.clear();
}, 10000);
}; };
let checkRecentTweet = id => { let startRecentTweetTimer = () => {
if (recentTweets.size > 50){ recentTweetTimer && window.clearTimeout(recentTweetTimer);
recentTweets.clear(); recentTweetTimer = window.setTimeout(resetRecentTweets, 20000);
} };
else if (recentTweets.has(id)){
let checkTweetCache = (set, id) => {
if (set.has(id)){
return true; return true;
} }
recentTweets.add(id); if (set.size > 50){
startRecentTweetTimer(); set.clear();
}
set.add(id);
return false; return false;
}; };
let fixMedia = (html, media) => {
return html.find(".js-media a[data-media-entity-id='"+media.mediaId+"']").css("background-image", 'url("'+media.thumb()+'")').removeClass("is-zoomable");
};
return function(column, tweet){ return function(column, tweet){
if (checkRecentTweet(tweet.id)){ if (tweet instanceof TD.services.TwitterConversation || tweet instanceof TD.services.TwitterConversationMessageEvent){
if (checkTweetCache(recentMessages, tweet.id)){
return;
}
}
else if (checkTweetCache(recentTweets, tweet.id)){
return; return;
} }
startRecentTweetTimer();
if (column.model.getHasNotification()){ if (column.model.getHasNotification()){
let previews = $TDX.notificationMediaPreviews;
let html = $(tweet.render({ let html = $(tweet.render({
withFooter: false, withFooter: false,
withTweetActions: false, withTweetActions: false,
withMediaPreview: true, withMediaPreview: true,
isMediaPreviewOff: true, isMediaPreviewOff: !previews,
isMediaPreviewSmall: false, isMediaPreviewSmall: previews,
isMediaPreviewLarge: false isMediaPreviewLarge: false,
isMediaPreviewCompact: false,
isMediaPreviewInQuoted: previews,
thumbSizeClass: "media-size-medium"
})); }));
html.css("border", "0"); html.css("border", "0");
html.find("footer").last().remove(); // apparently withTweetActions breaks for certain tweets, nice html.find("footer").last().remove(); // apparently withTweetActions breaks for certain tweets, nice
html.find(".js-media").last().remove(); // and quoted tweets still show media previews, nice nice html.find(".js-quote-detail").removeClass("is-actionable margin-b--8"); // prevent quoted tweets from changing the cursor and reduce bottom margin
html.find(".js-quote-detail").removeClass("is-actionable"); // prevent quoted tweets from changing the cursor
if (previews){
html.find(".reverse-image-search").remove();
for(let media of tweet.getMedia()){
fixMedia(html, media);
}
if (tweet.quotedTweet){
for(let media of tweet.quotedTweet.getMedia()){
fixMedia(html, media).addClass("media-size-medium");
}
}
}
else if (tweet instanceof TD.services.TwitterActionOnTweet){
html.find(".js-media").remove();
}
html.find("a[href='#']").each(function(){ // remove <a> tags around links that don't lead anywhere (such as account names the tweet replied to) html.find("a[href='#']").each(function(){ // remove <a> tags around links that don't lead anywhere (such as account names the tweet replied to)
this.outerHTML = this.innerHTML; this.outerHTML = this.innerHTML;
@@ -210,6 +242,9 @@
onAppReady.push(function(){ onAppReady.push(function(){
document.documentElement.setAttribute("data-td-theme", TD.settings.getTheme()); document.documentElement.setAttribute("data-td-theme", TD.settings.getTheme());
$TD.loadFontSizeClass(TD.settings.getFontSize());
$TD.loadNotificationHeadContents(getNotificationHeadContents());
}); });
// //
@@ -237,12 +272,12 @@
onAppReady.push(function(){ onAppReady.push(function(){
$("[data-action='settings-menu']").click(function(){ $("[data-action='settings-menu']").click(function(){
setTimeout(function(){ setTimeout(function(){
var menu = $(".js-dropdown-content").children("ul").first(); let menu = $(".js-dropdown-content").children("ul").first();
if (menu.length === 0)return; if (menu.length === 0)return;
menu.children(".drp-h-divider").last().before('<li class="is-selectable" data-std><a href="#" data-action="tweetduck">TweetDuck</a></li>'); menu.children(".drp-h-divider").last().before('<li class="is-selectable" data-std><a href="#" data-action="tweetduck">TweetDuck</a></li>');
var button = menu.children("[data-std]"); let button = menu.children("[data-std]");
button.on("click", "a", function(){ button.on("click", "a", function(){
$TD.openContextMenu(); $TD.openContextMenu();
@@ -272,7 +307,7 @@
var me = $(this); var me = $(this);
if (e.type === "mouseenter"){ if (e.type === "mouseenter"){
var text = me.text(); let text = me.text();
if (text.charCodeAt(text.length-1) !== 8230){ // horizontal ellipsis if (text.charCodeAt(text.length-1) !== 8230){ // horizontal ellipsis
return; return;
@@ -280,7 +315,7 @@
if ($TDX.expandLinksOnHover){ if ($TDX.expandLinksOnHover){
tooltipTimer = window.setTimeout(function(){ tooltipTimer = window.setTimeout(function(){
var expanded = me.attr("data-full-url"); let expanded = me.attr("data-full-url");
expanded = cutStart(expanded, "https://"); expanded = cutStart(expanded, "https://");
expanded = cutStart(expanded, "http://"); expanded = cutStart(expanded, "http://");
expanded = cutStart(expanded, "www."); expanded = cutStart(expanded, "www.");
@@ -298,7 +333,7 @@
} }
else if (e.type === "mouseleave"){ else if (e.type === "mouseleave"){
if ($TDX.expandLinksOnHover){ if ($TDX.expandLinksOnHover){
var prevText = me.attr("td-prev-text"); let prevText = me.attr("td-prev-text");
if (prevText){ if (prevText){
me.text(prevText); me.text(prevText);
@@ -323,17 +358,28 @@
})(); })();
// //
// Block: Allow bypassing of t.co in context menus. // Block: Allow bypassing of t.co and include media previews in context menus.
// //
$(document.body).delegate("a", "contextmenu", function(){ $(document.body).delegate("a", "contextmenu", function(){
$TD.setLastRightClickedLink($(this).attr("data-full-url") || ""); $TD.setLastRightClickedLink($(this).attr("data-full-url") || "");
}); });
$(document.body).delegate("a.js-media-image-link", "contextmenu", function(){
let me = $(this)[0];
if (me.firstElementChild){
$TD.setLastRightClickedImage(me.firstElementChild.getAttribute("src"));
}
else{
$TD.setLastRightClickedImage(me.style.backgroundImage.replace(/url\(['"]?(.*?)['"]?\)/, "$1"));
}
});
// //
// Block: Hook into the notification sound effect. // Block: Hook into the notification sound effect.
// //
(function(){ (function(){
var soundEle = document.getElementById("update-sound"); let soundEle = document.getElementById("update-sound");
soundEle.play = prependToFunction(soundEle.play, function(){ soundEle.play = prependToFunction(soundEle.play, function(){
return $TDX.muteNotifications || $TDX.hasCustomNotificationSound; return $TDX.muteNotifications || $TDX.hasCustomNotificationSound;
@@ -352,12 +398,12 @@
return !!highlightedColumnObj; return !!highlightedColumnObj;
}; };
var updateHighlightedTweet = function(ele, obj, link, embeddedLink){ var updateHighlightedTweet = function(ele, obj, link, embeddedLink, author, imageList){
highlightedTweetEle = ele; highlightedTweetEle = ele;
highlightedTweetObj = obj; highlightedTweetObj = obj;
if (lastTweet !== link){ if (lastTweet !== link){
$TD.setLastHighlightedTweet(link, embeddedLink); $TD.setLastHighlightedTweet(link, embeddedLink, author, imageList);
lastTweet = link; lastTweet = link;
} }
}; };
@@ -375,28 +421,31 @@
app.delegate("article.js-stream-item", "mouseenter mouseleave", function(e){ app.delegate("article.js-stream-item", "mouseenter mouseleave", function(e){
if (e.type === "mouseenter"){ if (e.type === "mouseenter"){
var me = $(this); let me = $(this);
if (!me[0].hasAttribute("data-account-key") || (!highlightedColumnObj && !updateHighlightedColumn(me.closest("section.js-column")))){ if (!me[0].hasAttribute("data-account-key") || (!highlightedColumnObj && !updateHighlightedColumn(me.closest("section.js-column")))){
return; return;
} }
var tweet = highlightedColumnObj.findChirp(me.attr("data-tweet-id")) || highlightedColumnObj.findChirp(me.attr("data-key")); let tweet = highlightedColumnObj.findChirp(me.attr("data-tweet-id")) || highlightedColumnObj.findChirp(me.attr("data-key"));
if (tweet){ if (tweet){
if (tweet.chirpType === TD.services.ChirpBase.TWEET){ if (tweet.chirpType === TD.services.ChirpBase.TWEET){
var link = tweet.getChirpURL(); let link = tweet.getChirpURL();
var embedded = tweet.quotedTweet ? tweet.quotedTweet.getChirpURL() : ""; let embedded = tweet.quotedTweet ? tweet.quotedTweet.getChirpURL() : "";
let username = tweet.getMainUser().screenName;
let images = tweet.hasImage() ? tweet.getMedia().filter(item => !item.isAnimatedGif).map(item => item.entity.media_url_https+":small").join(";") : "";
// TODO maybe handle embedded images too?
updateHighlightedTweet(me, tweet, link || "", embedded || ""); updateHighlightedTweet(me, tweet, link || "", embedded || "", username, images);
} }
else{ else{
updateHighlightedTweet(me, tweet, "", ""); updateHighlightedTweet(me, tweet, "", "", "", "");
} }
} }
} }
else if (e.type === "mouseleave"){ else if (e.type === "mouseleave"){
updateHighlightedTweet(null, null, "", ""); updateHighlightedTweet(null, null, "", "", "", "");
} }
}); });
})(); })();
@@ -419,11 +468,11 @@
window.TDGF_triggerScreenshot = function(){ window.TDGF_triggerScreenshot = function(){
if (selectedTweet){ if (selectedTweet){
var tweetWidth = Math.floor(selectedTweet.width()); let tweetWidth = Math.floor(selectedTweet.width());
var parent = selectedTweet.parent(); let parent = selectedTweet.parent();
var isDetail = parent.hasClass("js-tweet-detail"); let isDetail = parent.hasClass("js-tweet-detail");
var isReply = !isDetail && (parent.hasClass("js-replies-to") || parent.hasClass("js-replies-before")); let isReply = !isDetail && (parent.hasClass("js-replies-to") || parent.hasClass("js-replies-before"));
selectedTweet = selectedTweet.clone(); selectedTweet = selectedTweet.clone();
selectedTweet.children().first().addClass($(document.documentElement).attr("class")).css("padding-bottom", "0"); selectedTweet.children().first().addClass($(document.documentElement).attr("class")).css("padding-bottom", "0");
@@ -457,13 +506,13 @@
selectedTweet.find(".js-poll-link").remove(); selectedTweet.find(".js-poll-link").remove();
selectedTweet.find(".td-screenshot-remove").remove(); selectedTweet.find(".td-screenshot-remove").remove();
var testTweet = selectedTweet.clone().css({ let testTweet = selectedTweet.clone().css({
position: "absolute", position: "absolute",
left: "-999px", left: "-999px",
width: tweetWidth+"px" width: tweetWidth+"px"
}).appendTo(document.body); }).appendTo(document.body);
var realHeight = Math.floor(testTweet.height()); let realHeight = Math.floor(testTweet.height());
testTweet.remove(); testTweet.remove();
$TD.screenshotTweet(selectedTweet.html(), tweetWidth, realHeight); $TD.screenshotTweet(selectedTweet.html(), tweetWidth, realHeight);
@@ -480,8 +529,15 @@
app.delegate(".js-compose-text,.js-reply-tweetbox", "paste", function(e){ app.delegate(".js-compose-text,.js-reply-tweetbox", "paste", function(e){
for(let item of e.originalEvent.clipboardData.items){ for(let item of e.originalEvent.clipboardData.items){
if (item.type.startsWith("image/")){ if (item.type.startsWith("image/")){
$(this).closest(".rpl").find(".js-reply-popout").click(); // popout direct messages if (!$(this).closest(".rpl").find(".js-reply-popout").click().length){ // popout direct messages
if ($(".js-add-image-button").is(".is-disabled")){ // tweetdeck does not check upload count properly
return;
}
}
uploader.addFilesToUpload([ item.getAsFile() ]); uploader.addFilesToUpload([ item.getAsFile() ]);
$(".js-compose-text", ".js-docked-compose").focus();
break; break;
} }
} }
@@ -496,26 +552,34 @@
return $(selector, parent).click().length; return $(selector, parent).click().length;
}; };
var tryCloseModal = function(){ var tryCloseModal1 = function(){
var modal = $("#open-modal"); let modal = $("#open-modal");
return modal.is(":visible") && tryClickSelector("a[rel=dismiss]", modal); return modal.is(":visible") && tryClickSelector("a.mdl-dismiss", modal);
};
var tryCloseModal2 = function(){
let modal = $(".js-modals-container");
return modal.length && tryClickSelector("a.mdl-dismiss", modal);
}; };
var tryCloseHighlightedColumn = function(){ var tryCloseHighlightedColumn = function(){
if (highlightedColumnEle){ if (highlightedColumnEle){
var column = highlightedColumnEle.closest(".js-column"); let column = highlightedColumnEle.closest(".js-column");
return (column.is(".is-shifted-2") && tryClickSelector(".js-tweet-social-proof-back", column)) || (column.is(".is-shifted-1") && tryClickSelector(".js-column-back", column)); return (column.is(".is-shifted-2") && tryClickSelector(".js-tweet-social-proof-back", column)) || (column.is(".is-shifted-1") && tryClickSelector(".js-column-back", column));
} }
}; };
window.TDGF_onMouseClickExtra = function(button){ window.TDGF_onMouseClickExtra = function(button){
if (button === 1){ // back button if (button === 1){ // back button
tryCloseModal() || tryClickSelector(".is-shifted-2 .js-tweet-social-proof-back", ".js-modal-panel") ||
tryClickSelector(".is-shifted-1 .js-column-back", ".js-modal-panel") ||
tryCloseModal1() ||
tryCloseModal2() ||
tryClickSelector(".js-inline-compose-close") || tryClickSelector(".js-inline-compose-close") ||
tryCloseHighlightedColumn() || tryCloseHighlightedColumn() ||
tryClickSelector(".js-app-content.is-open .js-drawer-close:visible") || tryClickSelector(".js-app-content.is-open .js-drawer-close:visible") ||
tryClickSelector(".is-shifted-2 .js-tweet-social-proof-back, .is-shifted-2 .js-dm-participants-back") || tryClickSelector(".is-shifted-2 .js-tweet-social-proof-back, .is-shifted-2 .js-dm-participants-back") ||
$(".js-column-back").click(); $(".is-shifted-1 .js-column-back").click();
} }
else if (button === 2){ // forward button else if (button === 2){ // forward button
if (highlightedTweetEle){ if (highlightedTweetEle){
@@ -530,7 +594,7 @@
// //
$(document).on("dataTweetSent", function(e, data){ $(document).on("dataTweetSent", function(e, data){
if (data.response.state && data.response.state === "scheduled"){ if (data.response.state && data.response.state === "scheduled"){
var column = Object.values(TD.controller.columnManager.getAll()).find(column => column.model.state.type === "scheduled"); let column = Object.values(TD.controller.columnManager.getAll()).find(column => column.model.state.type === "scheduled");
if (column){ if (column){
setTimeout(function(){ setTimeout(function(){
@@ -583,16 +647,18 @@
})(); })();
// //
// Block: Swap shift key functionality for selecting accounts. // Block: Swap shift key functionality for selecting accounts, and refocus the textbox afterwards.
// //
onAppReady.push(function(){ onAppReady.push(function(){
var toggleEventShiftKey = function(e){ var onAccountClick = function(e){
if ($TDX.switchAccountSelectors){ if ($TDX.switchAccountSelectors){
e.shiftKey = !e.shiftKey; e.shiftKey = !e.shiftKey;
} }
$(".js-compose-text", ".js-docked-compose").focus();
}; };
$(".js-drawer[data-drawer='compose']").delegate(".js-account-list > .js-account-item", "click", toggleEventShiftKey); $(".js-drawer[data-drawer='compose']").delegate(".js-account-list > .js-account-item", "click", onAccountClick);
if (!ensurePropertyExists(TD, "components", "AccountSelector", "prototype", "refreshPostingAccounts")){ if (!ensurePropertyExists(TD, "components", "AccountSelector", "prototype", "refreshPostingAccounts")){
return; return;
@@ -601,7 +667,7 @@
TD.components.AccountSelector.prototype.refreshPostingAccounts = appendToFunction(TD.components.AccountSelector.prototype.refreshPostingAccounts, function(){ TD.components.AccountSelector.prototype.refreshPostingAccounts = appendToFunction(TD.components.AccountSelector.prototype.refreshPostingAccounts, function(){
if (!this.$node.attr("td-account-selector-hook")){ if (!this.$node.attr("td-account-selector-hook")){
this.$node.attr("td-account-selector-hook", "1"); this.$node.attr("td-account-selector-hook", "1");
this.$node.delegate(".js-account-item", "click", toggleEventShiftKey); this.$node.delegate(".js-account-item", "click", onAccountClick);
} }
}); });
}); });
@@ -637,41 +703,65 @@
// Block: Inject custom CSS and layout into the page. // Block: Inject custom CSS and layout into the page.
// //
(function(){ (function(){
var styleOfficial = document.createElement("style"); let styleOfficial = document.createElement("style");
document.head.appendChild(styleOfficial); document.head.appendChild(styleOfficial);
styleOfficial.sheet.insertRule("a[data-full-url] { word-break: break-all; }", 0); // break long urls let addRule = (rule) => {
styleOfficial.sheet.insertRule(".column-nav-link .attribution { position: absolute; }", 0); // fix cut off account names styleOfficial.sheet.insertRule(rule, 0);
styleOfficial.sheet.insertRule(".txt-base-smallest .sprite-verified-mini { width: 13px !important; height: 13px !important; background-position: -223px -99px !important; }", 0); // fix cut off badge icon when zoomed in };
styleOfficial.sheet.insertRule(".keyboard-shortcut-list { vertical-align: top; }", 0); // fix keyboard navigation alignment
styleOfficial.sheet.insertRule(".sprite-logo { background-position: -5px -46px !important; }", 0); // fix TweetDeck logo on certain zoom levels
styleOfficial.sheet.insertRule(".app-navigator .tooltip { display: none !important; }", 0); // hide broken tooltips in the menu
styleOfficial.sheet.insertRule(".account-inline .username { vertical-align: 10%; }", 0); // move usernames a bit higher
styleOfficial.sheet.insertRule(".btn-compose, .app-search-fake, .app-search-input { border-radius: 1px; }", 0); // use consistent menu button radius addRule("a[data-full-url] { word-break: break-all; }"); // break long urls
styleOfficial.sheet.insertRule(".is-condensed .app-header-inner { padding-top: 10px !important; }", 0); // add extra padding to menu buttons when condensed addRule(".keyboard-shortcut-list { vertical-align: top; }"); // fix keyboard navigation alignment
styleOfficial.sheet.insertRule(".is-condensed .btn-compose { padding: 8px !important; }", 0); // fix compose button icon when condensed addRule(".account-inline .username { vertical-align: 10%; }"); // move usernames a bit higher
styleOfficial.sheet.insertRule(".app-header:not(.is-condensed) .nav-user-info { padding: 0 5px; }", 0); // add padding to user info addRule(".character-count-compose { width: 40px !important; }"); // fix strangely wide character count element
addRule(".is-video a:not([href*='youtu']) .icon-bg-dot, .is-gif .icon-bg-dot { color: #9f51cf; }"); // change play icon on mp4s
styleOfficial.sheet.insertRule(".app-title { display: none; }", 0); // hide TweetDeck logo addRule(".column-nav-link .attribution { position: absolute; }"); // fix cut off account names
styleOfficial.sheet.insertRule(".nav-user-info { bottom: 10px !important; }", 0); // move user info addRule(".txt-base-smallest .sprite-verified-mini { width: 13px !important; height: 13px !important; background-position: -223px -99px !important; }"); // fix cut off badge icon when zoomed in
styleOfficial.sheet.insertRule(".app-navigator { bottom: 50px !important; }", 0); // move navigation
styleOfficial.sheet.insertRule(".column-navigator-overflow { bottom: 192px !important; }", 0); // move column list
styleOfficial.sheet.insertRule(".column .column-header { height: 49px !important; }", 0); // fix one pixel space below column header addRule(".btn, .mdl, .mdl-content, .app-search-fake, .app-search-input, .popover, .lst-modal, .media-item, .tooltip-inner { border-radius: 1px !important; }"); // square-ify buttons, inputs, dialogs, menus, media previews
styleOfficial.sheet.insertRule(".column:not(.is-options-open) .column-header { border-bottom: none; }", 0); // fix one pixel space below column header addRule(".dropdown-menu, .list-item-last, .quoted-tweet { border-radius: 0 !important; }"); // square-ify dropdowns, quoted tweets, and account selectors
addRule(".prf-header { border-radius: 0; }"); // fix user account header border
styleOfficial.sheet.insertRule(".activity-header { align-items: center !important; margin-bottom: 4px; }", 0); // tweak alignment of avatar and text in notifications addRule(".accs li, .accs img { border-radius: 0 !important; }"); // square-ify retweet account selector
styleOfficial.sheet.insertRule(".activity-header .tweet-timestamp { line-height: unset }", 0); // fix timestamp position in notifications addRule(".accs-header { padding-left: 0 !important; }"); // fix retweet account selector heading
styleOfficial.sheet.insertRule(".app-columns-container::-webkit-scrollbar-track { border-left: 0; }", 0); // remove weird border in the column container scrollbar addRule(".scroll-styled-v::-webkit-scrollbar-thumb, .scroll-styled-h::-webkit-scrollbar-thumb, .antiscroll-scrollbar { border-radius: 0; }"); // square-ify scroll bars
styleOfficial.sheet.insertRule(".app-columns-container { bottom: 0 !important; }", 0); // move column container scrollbar to bottom to fit updated style addRule(".antiscroll-scrollbar-vertical { margin-top: 0; }"); // square-ify scroll bars
addRule(".antiscroll-scrollbar-horizontal { margin-left: 0; }"); // square-ify scroll bars
addRule(".scroll-styled-v:not(.antiscroll-inner)::-webkit-scrollbar { width: 8px; }"); // square-ify scroll bars
addRule(".scroll-styled-h:not(.antiscroll-inner)::-webkit-scrollbar { height: 8px; }"); // square-ify scroll bars
addRule(".app-columns-container::-webkit-scrollbar { height: 9px !important; }"); // square-ify scroll bars
styleOfficial.sheet.insertRule(".js-column-header .column-header-link { padding: 0; }", 0); // fix column header tooltip hover box addRule(".is-condensed .app-header-inner { padding-top: 10px !important; }"); // add extra padding to menu buttons when condensed
styleOfficial.sheet.insertRule(".js-column-header .column-header-link .icon { padding: 9px 4px; width: calc(1em + 8px); height: 100%; box-sizing: border-box; }", 0); // fix column header tooltip hover box addRule(".is-condensed .btn-compose { padding: 8px !important; }"); // fix compose button icon when condensed
addRule(".app-header:not(.is-condensed) .nav-user-info { padding: 0 5px; }"); // add padding to user info
styleOfficial.sheet.insertRule(".is-video a:not([href*='youtu']), .is-gif .js-media-gif-container { cursor: alias; }", 0); // change cursor on unsupported videos addRule(".app-title { display: none; }"); // hide TweetDeck logo
styleOfficial.sheet.insertRule(".is-video a:not([href*='youtu']) .icon-bg-dot, .is-gif .icon-bg-dot { color: #bd3d37; }", 0); // change play icon color on unsupported videos addRule(".nav-user-info { bottom: 10px !important; }"); // move user info
addRule(".app-navigator { bottom: 50px !important; }"); // move navigation
addRule(".column-navigator-overflow { bottom: 192px !important; }"); // move column list
addRule(".app-navigator .tooltip { display: none !important; }"); // hide broken tooltips in the menu
addRule(".column .column-header { height: 49px !important; }"); // fix one pixel space below column header
addRule(".column:not(.is-options-open) .column-header { border-bottom: none; }"); // fix one pixel space below column header
addRule(".is-options-open .column-type-icon { bottom: 27px; }"); // fix one pixel space below column header
addRule(".activity-header { align-items: center !important; margin-bottom: 4px; }"); // tweak alignment of avatar and text in notifications
addRule(".activity-header .tweet-timestamp { line-height: unset; }"); // fix timestamp position in notifications
addRule("html[data-td-theme='light'] .stream-item:not(:hover) .js-user-actions-menu { color: #000; border-color: #000; opacity: 0.25; }"); // make follow notification button nicer
addRule("html[data-td-theme='dark'] .stream-item:not(:hover) .js-user-actions-menu { color: #fff; border-color: #fff; opacity: 0.25; }"); // make follow notification button nicer
addRule(".app-columns-container::-webkit-scrollbar-track { border-left: 0; }"); // remove weird border in the column container scrollbar
addRule(".app-columns-container { bottom: 0 !important; }"); // move column container scrollbar to bottom to fit updated style
addRule(".js-column-header .column-header-link { padding: 0; }"); // fix column header tooltip hover box
addRule(".js-column-header .column-header-link .icon { padding: 9px 4px; width: calc(1em + 8px); height: 100%; box-sizing: border-box; }"); // fix column header tooltip hover box
addRule("#td-compose-drawer-pin { margin: 17px 4px 0 0; transition: transform 0.1s ease; fill: #fff; float: right; cursor: pointer; }"); // replace 'stay open' checkbox with a pin icon
addRule(".js-docked-compose footer { display: none; }"); // replace 'stay open' checkbox with a pin icon
addRule(".compose-content { bottom: 0 !important; }"); // replace 'stay open' checkbox with a pin icon
window.TDGF_reinjectCustomCSS = function(styles){ window.TDGF_reinjectCustomCSS = function(styles){
$("#tweetduck-custom-css").remove(); $("#tweetduck-custom-css").remove();
@@ -701,20 +791,47 @@
} }
// //
// Block: Setup unsupported video element hook. // Block: Setup video player hooks.
// //
(function(){ (function(){
var cancelModal = false; var playVideo = function(url){
$('<div id="td-video-player-overlay" class="ovl" style="display:block"></div>').on("click contextmenu", function(){
$TD.playVideo(null);
$(this).remove();
}).appendTo(app);
$TD.playVideo(url);
};
app.delegate(".js-gif-play", "click", function(e){
let src = !e.ctrlKey && $(this).closest(".js-media-gif-container").find("video").attr("src");
if (src){
playVideo(src);
}
else{
let parent = $(e.target).closest(".js-tweet").first();
let link = (parent.hasClass("tweet-detail") ? parent.find("a[rel='url']") : parent.find("time").first().children("a")).first();
$TD.openBrowser(link.attr("href"));
}
e.stopPropagation();
});
TD.mustaches["status/media_thumb.mustache"] = TD.mustaches["status/media_thumb.mustache"].replace("is-gif", "is-gif is-paused");
TD.mustaches["media/native_video.mustache"] = '<div class="js-media-gif-container media-item nbfc is-video" style="background-image:url({{imageSrc}})"><video class="js-media-gif media-item-gif full-width block {{#isPossiblySensitive}}is-invisible{{/isPossiblySensitive}}" loop src="{{videoUrl}}"></video><a class="js-gif-play pin-all is-actionable">{{> media/video_overlay}}</a></div>';
if (!ensurePropertyExists(TD, "components", "MediaGallery", "prototype", "_loadTweet") || if (!ensurePropertyExists(TD, "components", "MediaGallery", "prototype", "_loadTweet") ||
!ensurePropertyExists(TD, "components", "BaseModal", "prototype", "setAndShowContainer") || !ensurePropertyExists(TD, "components", "BaseModal", "prototype", "setAndShowContainer") ||
!ensurePropertyExists(TD, "ui", "Column", "prototype", "playGifIfNotManuallyPaused"))return; !ensurePropertyExists(TD, "ui", "Column", "prototype", "playGifIfNotManuallyPaused"))return;
var cancelModal = false;
TD.components.MediaGallery.prototype._loadTweet = appendToFunction(TD.components.MediaGallery.prototype._loadTweet, function(){ TD.components.MediaGallery.prototype._loadTweet = appendToFunction(TD.components.MediaGallery.prototype._loadTweet, function(){
var media = this.chirp.getMedia().find(media => media.mediaId === this.clickedMediaEntityId); let media = this.chirp.getMedia().find(media => media.mediaId === this.clickedMediaEntityId);
if (media && media.isVideo && media.service !== "youtube"){ if (media && media.isVideo && media.service !== "youtube"){
$TD.openBrowser(this.clickedLink); playVideo(media.chooseVideoVariant().url);
cancelModal = true; cancelModal = true;
} }
}); });
@@ -727,22 +844,13 @@
}); });
TD.ui.Column.prototype.playGifIfNotManuallyPaused = function(){}; TD.ui.Column.prototype.playGifIfNotManuallyPaused = function(){};
TD.mustaches["status/media_thumb.mustache"] = TD.mustaches["status/media_thumb.mustache"].replace("is-gif", "is-gif is-paused");
app.delegate(".js-gif-play", "click", function(e){
var parent = $(e.target).closest(".js-tweet").first();
var link = (parent.hasClass("tweet-detail") ? parent.find("a[rel='url']") : parent.find("time").first().children("a")).first();
$TD.openBrowser(link.attr("href"));
e.stopPropagation();
});
})(); })();
// //
// Block: Fix youtu.be previews not showing up for https links. // Block: Fix youtu.be previews not showing up for https links.
// //
if (ensurePropertyExists(TD, "services", "TwitterMedia")){ if (ensurePropertyExists(TD, "services", "TwitterMedia")){
var media = TD.services.TwitterMedia; let media = TD.services.TwitterMedia;
if (!ensurePropertyExists(media, "YOUTUBE_TINY_RE") || if (!ensurePropertyExists(media, "YOUTUBE_TINY_RE") ||
!ensurePropertyExists(media, "YOUTUBE_LONG_RE") || !ensurePropertyExists(media, "YOUTUBE_LONG_RE") ||
@@ -754,6 +862,34 @@
media.SERVICES["youtube"] = media.YOUTUBE_RE; media.SERVICES["youtube"] = media.YOUTUBE_RE;
} }
//
// Block: Add a pin icon to make tweet compose drawer stay open.
//
onAppReady.push(function(){
let ele = $(`<svg id="td-compose-drawer-pin" viewBox="0 0 24 24" class="icon js-show-tip" data-original-title="Stay open" data-tooltip-position="left">
<path d="M9.884,16.959l3.272,0.001l-0.82,4.568l-1.635,0l-0.817,-4.569Z"/>
<rect x="8.694" y="7.208" width="5.652" height="7.445"/>
<path d="M16.877,17.448c0,-1.908 -1.549,-3.456 -3.456,-3.456l-3.802,0c-1.907,0 -3.456,1.548 -3.456,3.456l10.714,0Z"/>
<path d="M6.572,5.676l2.182,2.183l5.532,0l2.182,-2.183l0,-1.455l-9.896,0l0,1.455Z"/>
</svg>`
).appendTo(".js-docked-compose .js-compose-header");
ele.click(function(){
if (TD.settings.getComposeStayOpen()){
ele.css("transform", "rotate(0deg)");
TD.settings.setComposeStayOpen(false);
}
else{
ele.css("transform", "rotate(90deg)");
TD.settings.setComposeStayOpen(true);
}
});
if (TD.settings.getComposeStayOpen()){
ele.css("transform", "rotate(90deg)");
}
});
// //
// Block: Fix DM reply input box not getting focused after opening a conversation. // Block: Fix DM reply input box not getting focused after opening a conversation.
// //
@@ -778,6 +914,102 @@
}; };
} }
//
// Block: Custom reload function with memory cleanup.
//
window.TDGF_reload = function(){
let session = TD.storage.feedController.getAll()
.filter(feed => !!feed.getTopSortIndex())
.reduce((obj, feed) => (obj[feed.privateState.key] = feed.getTopSortIndex(), obj), {});
$TD.setSessionData("gc", JSON.stringify(session)).then(() => {
window.gc && window.gc();
window.location.reload();
});
};
window.TDGF_tryRunCleanup = function(){
// all textareas are empty
if ($("textarea").is(function(){
return $(this).val().length > 0;
})){
return false;
}
// no modals are visible
if ($("#open-modal").is(":visible") || !$(".js-modals-container").is(":empty")){
return false;
}
// all columns are in a default state
if ($("section.js-column").is(".is-shifted-1,.is-shifted-2")){
return false;
}
// all columns are scrolled to top
if ($(".js-column-scroller").is(function(){
return $(this).scrollTop() > 0;
})){
return false;
}
// cleanup
window.TDGF_reload();
return true;
};
if (window.TD_SESSION && window.TD_SESSION.gc){
var state;
try{
state = JSON.parse(window.TD_SESSION.gc);
}catch(err){
$TD.crashDebug("Invalid session gc data: "+window.TD_SESSION.gc);
state = {};
}
var showMissedNotifications = function(){
let tweets = [];
let columns = {};
let tmp = new TD.services.ChirpBase;
for(let column of Object.values(TD.controller.columnManager.getAll())){
for(let feed of column.getFeeds()){
if (feed.privateState.key in state){
tmp.sortIndex = state[feed.privateState.key];
for(let tweet of [].concat.apply([], column.updateArray.map(function(chirp){
return chirp.getUnreadChirps(tmp);
}))){
tweets.push(tweet);
columns[tweet.id] = column;
}
}
}
}
tweets.sort(TD.util.chirpReverseColumnSort);
for(let tweet of tweets){
onNewTweet(columns[tweet.id], tweet);
}
};
$(document).one("dataColumnsLoaded", function(){
let columns = Object.values(TD.controller.columnManager.getAll());
let remaining = columns.length;
for(let column of columns){
column.ui.getChirpContainer().one("dataColumnFeedUpdated", () => {
if (--remaining === 0){
setTimeout(showMissedNotifications, 1);
}
});
}
});
}
// //
// Block: Disable TweetDeck metrics. // Block: Disable TweetDeck metrics.
// //
@@ -800,9 +1032,9 @@
// //
$(document).one("TD.ready", function(){ $(document).one("TD.ready", function(){
onAppReady.forEach(func => func()); onAppReady.forEach(func => func());
onAppReady = null;
$TD.loadFontSizeClass(TD.settings.getFontSize()); delete window.TD_SESSION;
$TD.loadNotificationHeadContents(getNotificationHeadContents());
if (window.TD_PLUGINS){ if (window.TD_PLUGINS){
window.TD_PLUGINS.onReady(); window.TD_PLUGINS.onReady();

View File

@@ -98,7 +98,7 @@
// //
window.TDPF_requestReload = function(){ window.TDPF_requestReload = function(){
if (!isReloading){ if (!isReloading){
window.setTimeout(() => location.reload(), 1); window.setTimeout(window.TDGF_reload, 1);
isReloading = true; isReloading = true;
} }
}; };

View File

@@ -11,22 +11,30 @@
var style = document.createElement("style"); var style = document.createElement("style");
document.head.appendChild(style); document.head.appendChild(style);
style.sheet.insertRule("body { overflow: hidden !important; }", 0); // remove scrollbar let addRule = (rule) => {
style.sheet.insertRule(".topbar { display: none !important; }", 0); // hide top bar style.sheet.insertRule(rule, 0);
style.sheet.insertRule(".page-canvas, .buttons, .btn, input { border-radius: 0 !important; }", 0); // sharpen borders };
style.sheet.insertRule("input { padding: 5px 8px 4px !important; }", 0); // tweak input padding
style.sheet.insertRule("#doc { width: 100%; height: 100%; margin: 0; position: absolute; display: table; }", 0); // center everything addRule("body { overflow: hidden !important; background-color: #1c6399 !important; }"); // remove scrollbar and change background
style.sheet.insertRule("#page-outer { display: table-cell; vertical-align: middle; }", 0); // center everything addRule(".page-canvas { box-shadow: 0 0 150px rgba(255, 255, 255, 0.3) !important; }"); // change page box shadow
style.sheet.insertRule("#page-container { padding: 0 20px !important; width: 100% !important; box-sizing: border-box !important; }", 0); // center everything addRule(".topbar { display: none !important; }"); // hide top bar
style.sheet.insertRule(".page-canvas { margin: 0 auto !important; }", 0); // center everything
addRule(".page-canvas, .buttons, .btn, input { border-radius: 0 !important; }"); // sharpen borders
addRule("input { padding: 5px 8px 4px !important; }"); // tweak input padding
addRule("button[type='submit'] { border: 1px solid rgba(0, 0, 0, 0.3) !important; border-radius: 0 !important; }"); // style buttons
addRule("#doc { width: 100%; height: 100%; margin: 0; position: absolute; display: table; }"); // center everything
addRule("#page-outer { display: table-cell; vertical-align: middle; }"); // center everything
addRule("#page-container { padding: 0 20px !important; width: 100% !important; box-sizing: border-box !important; }"); // center everything
addRule(".page-canvas { margin: 0 auto !important; }"); // center everything
if (location.pathname === "/logout"){ if (location.pathname === "/logout"){
style.sheet.insertRule(".page-canvas { width: auto !important; max-width: 888px; }", 0); // fix min width addRule(".page-canvas { width: auto !important; max-width: 888px; }"); // fix min width
style.sheet.insertRule(".signout-wrapper { width: auto !important; }", 0); // fix min width addRule(".signout-wrapper { width: auto !important; margin: 0 auto !important; }"); // fix min width and margins
style.sheet.insertRule(".btn { margin: 0 4px !important; }", 0); // add margin around buttons addRule(".signout { margin: 60px 0 54px !important; }"); // fix dialog margins
style.sheet.insertRule(".btn.cancel { border: 1px solid #bbc1c5 !important; }", 0); // add border to cancel button addRule(".buttons { padding-bottom: 0 !important; }"); // fix dialog margins
style.sheet.insertRule(".aside p { display: none; }", 0); // hide text below the logout dialog addRule(".aside { display: none; }"); // hide elements around logout dialog
addRule(".buttons button, .buttons a { display: inline-block; margin: 0 4px !important; border: 1px solid rgba(0, 0, 0, 0.3) !important; border-radius: 0 !important; }"); // style buttons
} }
}; };

View File

@@ -27,9 +27,10 @@
// //
// Function: Creates the update notification element. Removes the old one if already exists. // Function: Creates the update notification element. Removes the old one if already exists.
// //
var displayNotification = function(version, download){ var displayNotification = function(version, download, changelog){
var outdated = version === "unsupported"; var outdated = version === "unsupported";
// styles
var css = $("#tweetduck-update-css"); var css = $("#tweetduck-update-css");
if (!css.length){ if (!css.length){
@@ -39,25 +40,30 @@
position: absolute; position: absolute;
bottom: 0; bottom: 0;
width: 200px; width: 200px;
height: 170px; height: 178px;
z-index: 9999; z-index: 9999;
color: #fff; color: #fff;
background-color: rgb(32, 94, 138); background-color: rgb(32, 94, 138);
text-align: center;
text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.5); text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.5);
} }
.tdu-title { .tdu-title {
font-size: 17px; font-size: 15px;
font-weight: bold; font-weight: bold;
text-align: center; margin: 8px 0 2px;
letter-spacing: 0.2px; cursor: default;
margin: 8px auto 2px;
} }
.tdu-info { .tdu-info {
font-size: 12px; display: inline-block;
text-align: center; font-size: 14px;
margin: 3px auto 0; margin: 3px 0;
}
.tdu-showlog {
text-decoration: underline;
cursor: pointer;
} }
.tdu-buttons button { .tdu-buttons button {
@@ -86,10 +92,90 @@
background-color: #607a8e; background-color: #607a8e;
color: #dfdfdf; color: #dfdfdf;
} }
#tweetduck-changelog {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 9998;
}
#tweetduck-changelog-box {
position: absolute;
width: 60%;
height: 75%;
max-width: calc(90% - 200px);
max-height: 90%;
left: calc(50% + 100px);
top: 50%;
padding: 12px;
overflow-y: auto;
transform: translateX(-50%) translateY(-50%);
font-size: 14px;
color: #000;
background-color: #fff;
box-sizing: border-box;
}
#tweetduck-changelog h2 {
margin: 0 0 7px;
font-size: 23px;
}
#tweetduck-changelog h2 + br {
display: none;
}
#tweetduck-changelog h3 {
margin: 0 0 5px 7px;
font-size: 18px;
}
#tweetduck-changelog p {
margin: 8px 8px 0 6px;
}
#tweetduck-changelog p.li {
margin: 0 8px 2px 30px;
display: list-item;
}
#tweetduck-changelog .l2 {
margin-left: 50px;
}
#tweetduck-changelog a {
color: #247fbb;
}
#tweetduck-changelog code {
padding: 0 4px;
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
color: #24292e;
background-color: rgba(27, 31, 35, 0.05);
}
</style> </style>
`).appendTo(document.head); `).appendTo(document.head);
} }
// changelog
var log = $("#tweetduck-changelog");
if (!log.length){
var log = $(`
<div id='tweetduck-changelog'>
<div id='tweetduck-changelog-box'>
<h2>TweetDuck Update ${version}</h2>
${changelog}
</div>
</div>
`).appendTo(document.body).css("display", "none");
}
// notification
var ele = $("#tweetduck-update"); var ele = $("#tweetduck-update");
var existed = ele.length > 0; var existed = ele.length > 0;
@@ -100,7 +186,7 @@
ele = $(outdated ? ` ele = $(outdated ? `
<div id='tweetduck-update'> <div id='tweetduck-update'>
<p class='tdu-title'>Unsupported System</p> <p class='tdu-title'>Unsupported System</p>
<p class='tdu-info'>You will not receive updates.</p> <p class='tdu-info'>You will not receive updates</p>
<div class='tdu-buttons'> <div class='tdu-buttons'>
<button class='tdu-btn-unsupported'>Read more</button> <button class='tdu-btn-unsupported'>Read more</button>
<button class='tdu-btn-ignore'>Dismiss</button> <button class='tdu-btn-ignore'>Dismiss</button>
@@ -108,8 +194,8 @@
</div> </div>
` : ` ` : `
<div id='tweetduck-update'> <div id='tweetduck-update'>
<p class='tdu-title'>TweetDuck Update</p> <p class='tdu-title'>T&#8202;weetDuck Update ${version}</p>
<p class='tdu-info'>Version ${version} is now available.</p> <p class='tdu-info tdu-showlog'>View update information</p>
<div class='tdu-buttons'> <div class='tdu-buttons'>
<button class='tdu-btn-download'>Update now</button> <button class='tdu-btn-download'>Update now</button>
<button class='tdu-btn-later'>Remind me later</button> <button class='tdu-btn-later'>Remind me later</button>
@@ -118,11 +204,28 @@
</div> </div>
`).appendTo(document.body).css("display", existed ? "block" : "none"); `).appendTo(document.body).css("display", existed ? "block" : "none");
// ui logic
var hide = function(){ var hide = function(){
ele.remove(); ele.remove();
log.remove();
css.remove(); css.remove();
}; };
var slide = function(){
log.hide();
ele.slideUp(hide);
};
ele.children(".tdu-showlog").click(function(){
log.toggle();
});
log.click(function(){
log.hide();
}).children().first().click(function(e){
e.stopPropagation();
});
var buttonDiv = ele.children(".tdu-buttons").first(); var buttonDiv = ele.children(".tdu-buttons").first();
buttonDiv.children(".tdu-btn-download").click(function(){ buttonDiv.children(".tdu-btn-download").click(function(){
@@ -138,7 +241,7 @@
buttonDiv.children(".tdu-btn-later").click(function(){ buttonDiv.children(".tdu-btn-later").click(function(){
clearTimeout(updateCheckTimeoutID); clearTimeout(updateCheckTimeoutID);
ele.slideUp(hide); slide();
}); });
buttonDiv.children(".tdu-btn-unsupported").click(function(){ buttonDiv.children(".tdu-btn-unsupported").click(function(){
@@ -147,7 +250,7 @@
buttonDiv.children(".tdu-btn-ignore,.tdu-btn-unsupported").click(function(){ buttonDiv.children(".tdu-btn-ignore,.tdu-btn-unsupported").click(function(){
$TDU.onUpdateDismissed(); $TDU.onUpdateDismissed();
ele.slideUp(hide); slide();
}); });
if (!existed){ if (!existed){
@@ -166,6 +269,25 @@
return new Date(offset.getFullYear(), offset.getMonth(), offset.getDate(), offset.getHours()+1, 0, 0)-now; return new Date(offset.getFullYear(), offset.getMonth(), offset.getDate(), offset.getHours()+1, 0, 0)-now;
}; };
//
// Function: Ghetto-converts markdown to HTML.
//
var markdown = function(md){
return md.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/^##? (.*?)$/gm, "<h2>$1</h2>")
.replace(/^### (.*?)$/gm, "<h3>$1</h3>")
.replace(/^- (.*?)$/gm, "<p class='li'>$1</p>")
.replace(/^ - (.*?)$/gm, "<p class='li l2'>$1</p>")
.replace(/\*\*(.*?)\*\*/g, "<strong>$1</strong>")
.replace(/\*(.*?)\*/g, "<em>$1</em>")
.replace(/`(.*?)`/g, "<code>$1</code>")
.replace(/\[(.*?)\]\((.*?)\)/g, "<a href='$2'>$1</a>")
.replace(/^([a-z0-9].*?)$/gmi, "<p>$1</p>")
.replace(/\n\r?\n\r?/g, "<br>");
};
// //
// Function: Runs an update check and updates all DOM elements appropriately. // Function: Runs an update check and updates all DOM elements appropriately.
// //
@@ -181,7 +303,7 @@
if (hasUpdate){ if (hasUpdate){
var obj = release.assets.find(asset => asset.name === updateFileName) || { browser_download_url: "" }; var obj = release.assets.find(asset => asset.name === updateFileName) || { browser_download_url: "" };
displayNotification(tagName, obj.browser_download_url); displayNotification(tagName, obj.browser_download_url, markdown(release.body));
if (eventID){ // ignore undefined and 0 if (eventID){ // ignore undefined and 0
$TDU.onUpdateCheckFinished(eventID, tagName, obj.browser_download_url); $TDU.onUpdateCheckFinished(eventID, tagName, obj.browser_download_url);

View File

@@ -65,6 +65,7 @@
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Core" /> <Reference Include="System.Core" />
<Reference Include="System.Drawing" /> <Reference Include="System.Drawing" />
<Reference Include="System.Management" />
<Reference Include="System.Windows.Forms" /> <Reference Include="System.Windows.Forms" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -72,6 +73,7 @@
<Compile Include="Configuration\LockManager.cs" /> <Compile Include="Configuration\LockManager.cs" />
<Compile Include="Configuration\SystemConfig.cs" /> <Compile Include="Configuration\SystemConfig.cs" />
<Compile Include="Configuration\UserConfig.cs" /> <Compile Include="Configuration\UserConfig.cs" />
<Compile Include="Configuration\UserConfigLegacy.cs" />
<Compile Include="Core\Bridge\PropertyBridge.cs" /> <Compile Include="Core\Bridge\PropertyBridge.cs" />
<Compile Include="Core\Controls\ControlExtensions.cs" /> <Compile Include="Core\Controls\ControlExtensions.cs" />
<Compile Include="Core\Controls\FlatButton.cs"> <Compile Include="Core\Controls\FlatButton.cs">
@@ -83,7 +85,11 @@
<Compile Include="Core\Controls\LabelVertical.cs"> <Compile Include="Core\Controls\LabelVertical.cs">
<SubType>Component</SubType> <SubType>Component</SubType>
</Compile> </Compile>
<Compile Include="Core\Handling\BrowserProcessHandler.cs" /> <Compile Include="Core\Controls\NumericUpDownEx.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="Core\FormManager.cs" />
<Compile Include="Core\Handling\General\BrowserProcessHandler.cs" />
<Compile Include="Core\Handling\ContextMenuBase.cs" /> <Compile Include="Core\Handling\ContextMenuBase.cs" />
<Compile Include="Core\Handling\ContextMenuBrowser.cs" /> <Compile Include="Core\Handling\ContextMenuBrowser.cs" />
<Compile Include="Core\FormBrowser.cs"> <Compile Include="Core\FormBrowser.cs">
@@ -92,8 +98,9 @@
<Compile Include="Core\FormBrowser.Designer.cs"> <Compile Include="Core\FormBrowser.Designer.cs">
<DependentUpon>FormBrowser.cs</DependentUpon> <DependentUpon>FormBrowser.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Core\Handling\KeyboardHandlerNotification.cs" />
<Compile Include="Core\Handling\RequestHandlerBrowser.cs" /> <Compile Include="Core\Handling\RequestHandlerBrowser.cs" />
<Compile Include="Core\Handling\RequestHandler.cs" /> <Compile Include="Core\Handling\General\RequestHandlerBase.cs" />
<Compile Include="Core\Handling\ResourceHandlerNotification.cs" /> <Compile Include="Core\Handling\ResourceHandlerNotification.cs" />
<Compile Include="Core\Notification\FormNotificationMain.cs"> <Compile Include="Core\Notification\FormNotificationMain.cs">
<SubType>Form</SubType> <SubType>Form</SubType>
@@ -108,8 +115,8 @@
<DependentUpon>FormNotificationBase.cs</DependentUpon> <DependentUpon>FormNotificationBase.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Core\Handling\ContextMenuNotification.cs" /> <Compile Include="Core\Handling\ContextMenuNotification.cs" />
<Compile Include="Core\Handling\JavaScriptDialogHandler.cs" /> <Compile Include="Core\Handling\General\JavaScriptDialogHandler.cs" />
<Compile Include="Core\Handling\LifeSpanHandler.cs" /> <Compile Include="Core\Handling\General\LifeSpanHandler.cs" />
<Compile Include="Core\Notification\FormNotificationTweet.cs"> <Compile Include="Core\Notification\FormNotificationTweet.cs">
<SubType>Form</SubType> <SubType>Form</SubType>
</Compile> </Compile>
@@ -136,6 +143,7 @@
<Compile Include="Core\Other\FormPlugins.Designer.cs"> <Compile Include="Core\Other\FormPlugins.Designer.cs">
<DependentUpon>FormPlugins.cs</DependentUpon> <DependentUpon>FormPlugins.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Core\Other\Management\VideoPlayer.cs" />
<Compile Include="Core\Other\Settings\Dialogs\DialogSettingsCSS.cs"> <Compile Include="Core\Other\Settings\Dialogs\DialogSettingsCSS.cs">
<SubType>Form</SubType> <SubType>Form</SubType>
</Compile> </Compile>
@@ -160,7 +168,10 @@
<Compile Include="Core\Other\Settings\Dialogs\DialogSettingsRestart.Designer.cs"> <Compile Include="Core\Other\Settings\Dialogs\DialogSettingsRestart.Designer.cs">
<DependentUpon>DialogSettingsRestart.cs</DependentUpon> <DependentUpon>DialogSettingsRestart.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Core\Other\Settings\Export\CombinedFileStream.cs" /> <Compile Include="Core\Other\Management\BrowserProcesses.cs" />
<Compile Include="Core\Utils\StringUtils.cs" />
<Compile Include="Core\Utils\TwitterUtils.cs" />
<Compile Include="Data\CombinedFileStream.cs" />
<Compile Include="Core\Other\Settings\Export\ExportFileFlags.cs" /> <Compile Include="Core\Other\Settings\Export\ExportFileFlags.cs" />
<Compile Include="Core\Other\Settings\Export\ExportManager.cs" /> <Compile Include="Core\Other\Settings\Export\ExportManager.cs" />
<Compile Include="Core\Other\Settings\TabSettingsAdvanced.cs"> <Compile Include="Core\Other\Settings\TabSettingsAdvanced.cs">
@@ -191,15 +202,18 @@
<DependentUpon>TabSettingsNotifications.cs</DependentUpon> <DependentUpon>TabSettingsNotifications.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Core\Bridge\CallbackBridge.cs" /> <Compile Include="Core\Bridge\CallbackBridge.cs" />
<Compile Include="Core\Utils\CommandLineArgs.cs" /> <Compile Include="Data\CommandLineArgs.cs" />
<Compile Include="Core\Utils\CommandLineArgsParser.cs" />
<Compile Include="Core\Notification\Screenshot\FormNotificationScreenshotable.cs"> <Compile Include="Core\Notification\Screenshot\FormNotificationScreenshotable.cs">
<SubType>Form</SubType> <SubType>Form</SubType>
</Compile> </Compile>
<Compile Include="Core\Notification\Screenshot\TweetScreenshotManager.cs" /> <Compile Include="Core\Notification\Screenshot\TweetScreenshotManager.cs" />
<Compile Include="Core\Utils\InjectedHTML.cs" /> <Compile Include="Data\Serialization\FileSerializer.cs" />
<Compile Include="Core\Utils\TwoKeyDictionary.cs" /> <Compile Include="Data\InjectedHTML.cs" />
<Compile Include="Core\Utils\WindowState.cs" /> <Compile Include="Data\Serialization\ITypeConverter.cs" />
<Compile Include="Data\Serialization\SingleTypeConverter.cs" />
<Compile Include="Data\TwoKeyDictionary.cs" />
<Compile Include="Data\WindowState.cs" />
<Compile Include="Core\Other\Management\MemoryUsageTracker.cs" />
<Compile Include="Core\Utils\WindowsUtils.cs" /> <Compile Include="Core\Utils\WindowsUtils.cs" />
<Compile Include="Core\Bridge\TweetDeckBridge.cs" /> <Compile Include="Core\Bridge\TweetDeckBridge.cs" />
<Compile Include="Core\Other\FormSettings.cs"> <Compile Include="Core\Other\FormSettings.cs">
@@ -284,15 +298,11 @@
<ItemGroup> <ItemGroup>
<EmbeddedResource Include="Core\FormBrowser.resx"> <EmbeddedResource Include="Core\FormBrowser.resx">
<DependentUpon>FormBrowser.cs</DependentUpon> <DependentUpon>FormBrowser.cs</DependentUpon>
<SubType>Designer</SubType>
</EmbeddedResource> </EmbeddedResource>
<EmbeddedResource Include="Core\Other\FormAbout.resx"> <EmbeddedResource Include="Core\Other\FormAbout.resx">
<DependentUpon>FormAbout.cs</DependentUpon> <DependentUpon>FormAbout.cs</DependentUpon>
</EmbeddedResource> <SubType>Designer</SubType>
<EmbeddedResource Include="Core\Other\FormPlugins.resx">
<DependentUpon>FormPlugins.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Core\Other\FormSettings.resx">
<DependentUpon>FormSettings.cs</DependentUpon>
</EmbeddedResource> </EmbeddedResource>
<EmbeddedResource Include="Updates\FormUpdateDownload.resx"> <EmbeddedResource Include="Updates\FormUpdateDownload.resx">
<DependentUpon>FormUpdateDownload.cs</DependentUpon> <DependentUpon>FormUpdateDownload.cs</DependentUpon>
@@ -337,11 +347,14 @@
<Project>{b10b0017-819e-4f71-870f-8256b36a26aa}</Project> <Project>{b10b0017-819e-4f71-870f-8256b36a26aa}</Project>
<Name>TweetDuck.Browser</Name> <Name>TweetDuck.Browser</Name>
</ProjectReference> </ProjectReference>
<ProjectReference Include="video\TweetDuck.Video\TweetDuck.Video.csproj">
<Project>{278b2d11-402d-44b6-b6a1-8fa67db65565}</Project>
<Name>TweetDuck.Video</Name>
</ProjectReference>
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup> <PropertyGroup>
<PostBuildEvent>del "$(TargetPath).config" <PostBuildEvent>xcopy "$(ProjectDir)LICENSE.md" "$(TargetDir)" /Y
xcopy "$(ProjectDir)LICENSE.md" "$(TargetDir)" /Y
del "$(TargetDir)LICENSE.txt" del "$(TargetDir)LICENSE.txt"
ren "$(TargetDir)LICENSE.md" "LICENSE.txt" ren "$(TargetDir)LICENSE.md" "LICENSE.txt"
xcopy "$(ProjectDir)bld\Resources\CEFSHARP-LICENSE.txt" "$(TargetDir)" /Y xcopy "$(ProjectDir)bld\Resources\CEFSHARP-LICENSE.txt" "$(TargetDir)" /Y
@@ -359,6 +372,7 @@ rmdir "$(ProjectDir)bin\Debug"
rmdir "$(ProjectDir)bin\Release" rmdir "$(ProjectDir)bin\Release"
rmdir "$(TargetDir)plugins\official\.debug" /S /Q rmdir "$(TargetDir)plugins\official\.debug" /S /Q
del "$(TargetDir)plugins\official\emoji-keyboard\emoji-instructions.txt"
if $(ConfigurationName) == Debug ( if $(ConfigurationName) == Debug (
rmdir "$(TargetDir)plugins\official\.debug" /S /Q rmdir "$(TargetDir)plugins\official\.debug" /S /Q
@@ -366,7 +380,6 @@ if $(ConfigurationName) == Debug (
xcopy "$(ProjectDir)Resources\Plugins\.debug\*" "$(TargetDir)plugins\user\.debug\" /E /Y xcopy "$(ProjectDir)Resources\Plugins\.debug\*" "$(TargetDir)plugins\user\.debug\" /E /Y
)</PostBuildEvent> )</PostBuildEvent>
</PropertyGroup> </PropertyGroup>
<Import Project="packages\cef.redist.x64.3.2987.1601\build\cef.redist.x64.targets" Condition="Exists('packages\cef.redist.x64.3.2987.1601\build\cef.redist.x64.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup> <PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText> <ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
@@ -378,14 +391,15 @@ if $(ConfigurationName) == Debug (
<Error Condition="!Exists('packages\CefSharp.WinForms.57.0.0\build\CefSharp.WinForms.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.WinForms.57.0.0\build\CefSharp.WinForms.props'))" /> <Error Condition="!Exists('packages\CefSharp.WinForms.57.0.0\build\CefSharp.WinForms.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.WinForms.57.0.0\build\CefSharp.WinForms.props'))" />
<Error Condition="!Exists('packages\CefSharp.WinForms.57.0.0\build\CefSharp.WinForms.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.WinForms.57.0.0\build\CefSharp.WinForms.targets'))" /> <Error Condition="!Exists('packages\CefSharp.WinForms.57.0.0\build\CefSharp.WinForms.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.WinForms.57.0.0\build\CefSharp.WinForms.targets'))" />
</Target> </Target>
<Import Project="packages\cef.redist.x64.3.2987.1601\build\cef.redist.x64.targets" Condition="Exists('packages\cef.redist.x64.3.2987.1601\build\cef.redist.x64.targets')" />
<Import Project="packages\cef.redist.x86.3.2987.1601\build\cef.redist.x86.targets" Condition="Exists('packages\cef.redist.x86.3.2987.1601\build\cef.redist.x86.targets')" /> <Import Project="packages\cef.redist.x86.3.2987.1601\build\cef.redist.x86.targets" Condition="Exists('packages\cef.redist.x86.3.2987.1601\build\cef.redist.x86.targets')" />
<Import Project="packages\CefSharp.Common.57.0.0\build\CefSharp.Common.targets" Condition="Exists('packages\CefSharp.Common.57.0.0\build\CefSharp.Common.targets')" /> <Import Project="packages\CefSharp.Common.57.0.0\build\CefSharp.Common.targets" Condition="Exists('packages\CefSharp.Common.57.0.0\build\CefSharp.Common.targets')" />
<Import Project="packages\CefSharp.WinForms.57.0.0\build\CefSharp.WinForms.targets" Condition="Exists('packages\CefSharp.WinForms.57.0.0\build\CefSharp.WinForms.targets')" /> <Import Project="packages\CefSharp.WinForms.57.0.0\build\CefSharp.WinForms.targets" Condition="Exists('packages\CefSharp.WinForms.57.0.0\build\CefSharp.WinForms.targets')" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. <Target Name="AfterBuild" Condition="$(ConfigurationName) == Release">
Other similar extension points exist, see Microsoft.Common.targets. <Exec Command="del &quot;$(TargetDir)*.pdb&quot;" />
<Target Name="BeforeBuild"> <Exec Command="del &quot;$(TargetDir)*.xml&quot;" />
<Delete Files="$(TargetDir)CefSharp.BrowserSubprocess.exe" />
<Delete Files="$(TargetDir)devtools_resources.pak" />
<Delete Files="$(TargetDir)widevinecdmadapter.dll" />
</Target> </Target>
<Target Name="AfterBuild">
</Target>
-->
</Project> </Project>

View File

@@ -1,6 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15 # Visual Studio 15
VisualStudioVersion = 15.0.26430.12 VisualStudioVersion = 15.0.26430.16
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetDuck", "TweetDuck.csproj", "{2389A7CD-E0D3-4706-8294-092929A33A2D}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetDuck", "TweetDuck.csproj", "{2389A7CD-E0D3-4706-8294-092929A33A2D}"
EndProject EndProject
@@ -10,6 +10,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests", "tests\UnitTest
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetLib.Audio", "lib\TweetLib.Audio\TweetLib.Audio.csproj", "{E9E1FD1B-F480-45B7-9970-BE2ECFD309AC}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetLib.Audio", "lib\TweetLib.Audio\TweetLib.Audio.csproj", "{E9E1FD1B-F480-45B7-9970-BE2ECFD309AC}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetDuck.Video", "video\TweetDuck.Video\TweetDuck.Video.csproj", "{278B2D11-402D-44B6-B6A1-8FA67DB65565}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x86 = Debug|x86 Debug|x86 = Debug|x86
@@ -32,6 +34,10 @@ Global
{E9E1FD1B-F480-45B7-9970-BE2ECFD309AC}.Debug|x86.Build.0 = Debug|x86 {E9E1FD1B-F480-45B7-9970-BE2ECFD309AC}.Debug|x86.Build.0 = Debug|x86
{E9E1FD1B-F480-45B7-9970-BE2ECFD309AC}.Release|x86.ActiveCfg = Release|x86 {E9E1FD1B-F480-45B7-9970-BE2ECFD309AC}.Release|x86.ActiveCfg = Release|x86
{E9E1FD1B-F480-45B7-9970-BE2ECFD309AC}.Release|x86.Build.0 = Release|x86 {E9E1FD1B-F480-45B7-9970-BE2ECFD309AC}.Release|x86.Build.0 = Release|x86
{278B2D11-402D-44B6-B6A1-8FA67DB65565}.Debug|x86.ActiveCfg = Debug|x86
{278B2D11-402D-44B6-B6A1-8FA67DB65565}.Debug|x86.Build.0 = Debug|x86
{278B2D11-402D-44B6-B6A1-8FA67DB65565}.Release|x86.ActiveCfg = Release|x86
{278B2D11-402D-44B6-B6A1-8FA67DB65565}.Release|x86.Build.0 = Release|x86
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

View File

@@ -33,8 +33,6 @@
// btnCancel // btnCancel
// //
this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.btnCancel.AutoSize = true;
this.btnCancel.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
this.btnCancel.Location = new System.Drawing.Point(195, 34); this.btnCancel.Location = new System.Drawing.Point(195, 34);
this.btnCancel.Name = "btnCancel"; this.btnCancel.Name = "btnCancel";
this.btnCancel.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0); this.btnCancel.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);

View File

@@ -1,5 +1,6 @@
using System; using System;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Core.Other;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
namespace TweetDuck.Updates{ namespace TweetDuck.Updates{
@@ -29,7 +30,7 @@ namespace TweetDuck.Updates{
else if (updateInfo.DownloadStatus == UpdateDownloadStatus.Failed){ else if (updateInfo.DownloadStatus == UpdateDownloadStatus.Failed){
timerDownloadCheck.Stop(); timerDownloadCheck.Stop();
if (MessageBox.Show("Could not download the update: "+(updateInfo.DownloadError?.Message ?? "unknown error")+"\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){ if (FormMessage.Error("Update Has Failed", "Could not download the update: "+(updateInfo.DownloadError?.Message ?? "unknown error")+"\n\nDo you want to open the website and try downloading the update manually?", FormMessage.Yes, FormMessage.No)){
BrowserUtils.OpenExternalBrowserUnsafe(Program.Website); BrowserUtils.OpenExternalBrowserUnsafe(Program.Website);
DialogResult = DialogResult.OK; DialogResult = DialogResult.OK;
} }

View File

@@ -2,7 +2,6 @@
using CefSharp.WinForms; using CefSharp.WinForms;
using System; using System;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Core;
using TweetDuck.Core.Controls; using TweetDuck.Core.Controls;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
using TweetDuck.Resources; using TweetDuck.Resources;
@@ -13,7 +12,6 @@ namespace TweetDuck.Updates{
private static bool IsSystemSupported => true; // Environment.OSVersion.Version >= new Version("6.1"); // 6.1 NT version = Windows 7 private static bool IsSystemSupported => true; // Environment.OSVersion.Version >= new Version("6.1"); // 6.1 NT version = Windows 7
private readonly ChromiumWebBrowser browser; private readonly ChromiumWebBrowser browser;
private readonly FormBrowser form;
private readonly UpdaterSettings settings; private readonly UpdaterSettings settings;
public event EventHandler<UpdateAcceptedEventArgs> UpdateAccepted; public event EventHandler<UpdateAcceptedEventArgs> UpdateAccepted;
@@ -23,9 +21,8 @@ namespace TweetDuck.Updates{
private int lastEventId; private int lastEventId;
private UpdateInfo lastUpdateInfo; private UpdateInfo lastUpdateInfo;
public UpdateHandler(ChromiumWebBrowser browser, FormBrowser form, UpdaterSettings settings){ public UpdateHandler(ChromiumWebBrowser browser, UpdaterSettings settings){
this.browser = browser; this.browser = browser;
this.form = form;
this.settings = settings; this.settings = settings;
browser.FrameLoadEnd += browser_FrameLoadEnd; browser.FrameLoadEnd += browser_FrameLoadEnd;
@@ -33,7 +30,7 @@ namespace TweetDuck.Updates{
} }
private void browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){ private void browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
if (e.Frame.IsMain && BrowserUtils.IsTweetDeckWebsite(e.Frame)){ if (e.Frame.IsMain && TwitterUtils.IsTweetDeckWebsite(e.Frame)){
ScriptLoader.ExecuteFile(e.Frame, "update.js"); ScriptLoader.ExecuteFile(e.Frame, "update.js");
Check(false); Check(false);
} }
@@ -92,27 +89,20 @@ namespace TweetDuck.Updates{
} }
public void DismissUpdate(string tag){ public void DismissUpdate(string tag){
settings.DismissedUpdate = tag; TriggerUpdateDismissedEvent(new UpdateDismissedEventArgs(tag));
UpdateDismissed?.Invoke(this, new UpdateDismissedEventArgs(tag));
} }
private void TriggerUpdateAcceptedEvent(UpdateAcceptedEventArgs args){ private void TriggerUpdateAcceptedEvent(UpdateAcceptedEventArgs args){
if (UpdateAccepted != null){ UpdateAccepted?.Invoke(this, args);
form.InvokeAsyncSafe(() => UpdateAccepted(this, args));
}
} }
private void TriggerUpdateDismissedEvent(UpdateDismissedEventArgs args){ private void TriggerUpdateDismissedEvent(UpdateDismissedEventArgs args){
form.InvokeAsyncSafe(() => {
settings.DismissedUpdate = args.VersionTag; settings.DismissedUpdate = args.VersionTag;
UpdateDismissed?.Invoke(this, args); UpdateDismissed?.Invoke(this, args);
});
} }
private void TriggerCheckFinishedEvent(UpdateCheckEventArgs args){ private void TriggerCheckFinishedEvent(UpdateCheckEventArgs args){
if (CheckFinished != null){ CheckFinished?.Invoke(this, args);
form.InvokeAsyncSafe(() => CheckFinished(this, args));
}
} }
public class Bridge{ public class Bridge{

View File

@@ -4,7 +4,7 @@ using System.Net;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
namespace TweetDuck.Updates{ namespace TweetDuck.Updates{
class UpdateInfo{ sealed class UpdateInfo{
public string VersionTag { get; } public string VersionTag { get; }
public string InstallerPath { get; } public string InstallerPath { get; }

Some files were not shown because too many files have changed in this diff Show More