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

Compare commits

...

147 Commits
1.8.3 ... 1.8.7

Author SHA1 Message Date
68dca6e3d9 Fix spacebar not toggling video pause when the main app was focused 2017-08-21 14:14:38 +02:00
017f883e0b Disable custom emoji input, fix selection handling and support twemoji font if installed 2017-08-21 13:37:21 +02:00
77b5c95f75 Add basic js minification (trim whitespace and remove single line comments) 2017-08-21 09:41:15 +02:00
9d052c8339 Update close button fix to only affect New Tweet drawer 2017-08-21 02:17:48 +02:00
d67623a657 Tweak follow notification padding in the browser 2017-08-21 01:52:19 +02:00
c740b3dd46 What the fuck are you doing Twitter 2017-08-21 01:35:53 +02:00
2ef5f7f96f Fix border radius on media previews in tweet detail 2017-08-16 18:27:44 +02:00
404568d795 Fix pre-build powershell command causing build error 2017-08-16 18:23:38 +02:00
b5a6337a0c Update custom CSS to work better with recent TweetDeck changes 2017-08-14 17:15:18 +02:00
82170c3fbd Fix sensitive media in notification previews and tweak follow notification padding 2017-08-14 16:12:34 +02:00
e6d6275fcc Work on emoji keyboard contenteditable fixes (selection, focus, editor migration) 2017-08-14 15:37:55 +02:00
97c865a127 Make emoji editor only show after adding emoji, fix minor UI issues 2017-08-14 04:22:13 +02:00
1ff21f0ee0 Make emoji keyboard replace tweet input with one that displays emoji
Closes #146
2017-08-14 00:47:08 +02:00
2a3dca4467 Rewrite video player to use duplex pipe for process communication 2017-08-13 17:52:46 +02:00
d4ecfcceec Tweak DuplexPipe to set key instead of data when separator is missing 2017-08-13 17:31:58 +02:00
ec5d503e4d Make DuplexPipe data serialized as key/value pairs 2017-08-13 17:23:23 +02:00
346391ca2d Remove unused 'using' statements for the billionth time 2017-08-13 16:55:08 +02:00
9074cdf340 Add a hover effect to video player seek bar 2017-08-13 16:46:33 +02:00
2fcf3604a8 Move video player form controls to a different namespace 2017-08-13 16:14:46 +02:00
34e5185fa1 Fuck localized .NET exceptions 2017-08-13 15:53:39 +02:00
e09e0e69ca Fuck browser process when building the project 2017-08-13 15:50:43 +02:00
963c98e588 Move interprocess comms to a separate project & implement duplex pipe 2017-08-13 15:20:04 +02:00
92acb823a4 Implement a duplex anonymous pipe in TweetLib.Communication 2017-08-13 15:14:17 +02:00
b967b1288f Move process communication to a separate project 2017-08-13 13:54:34 +02:00
1db271ce90 Fix spacebar triggering fullscreen in video player 2017-08-13 00:23:08 +02:00
58c64025e3 Fix level 2 lists and links in update changelog modal 2017-08-12 23:52:38 +02:00
643a7a87aa Release 1.8.6 2017-08-12 23:39:41 +02:00
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
97 changed files with 2709 additions and 1014 deletions

View File

@@ -1,29 +1,28 @@
using System; using System;
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;
using TweetLib.Communication;
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, WindowsUtils.CurrentProcessID);
lockStream.Flush(true);
}
private bool ReleaseLockFileStream(){ private bool ReleaseLockFileStream(){
if (lockStream != null){ if (lockStream != null){
@@ -37,8 +36,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 +61,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 +75,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 +85,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 +96,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 +105,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
Comms.BroadcastMessage(Program.WindowRestoreMessage, (uint)lockingProcess.Id, 0);
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,21 +182,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);
} }
} }
} }

View File

@@ -1,43 +1,43 @@
using System; using System;
using System.IO; using System.IO;
using TweetDuck.Core.Utils; 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;
} }
private readonly string file; // END OF CONFIG
private bool hardwareAcceleration; private readonly string file;
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{
WindowsUtils.CreateDirectoryForFile(file); WindowsUtils.CreateDirectoryForFile(file);
Serializer.Write(file, this);
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);
@@ -49,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,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.Drawing; using System.Drawing;
using System.IO; using System.IO;
using TweetDuck.Core; using TweetDuck.Core;
@@ -9,8 +10,27 @@ using TweetDuck.Data;
using TweetDuck.Data.Serialization; using TweetDuck.Data.Serialization;
namespace TweetDuck.Configuration{ namespace TweetDuck.Configuration{
sealed class UserConfig : ISerializedObject{ sealed class UserConfig{
private static readonly FileSerializer<UserConfig> Serializer = new FileSerializer<UserConfig>(); private static readonly FileSerializer<UserConfig> Serializer = new FileSerializer<UserConfig>{ HandleUnknownProperties = HandleUnknownProperties };
private static void HandleUnknownProperties(UserConfig obj, Dictionary<string, string> data){
if (data.TryGetValue("EnableBrowserGCReload", out string propGCReload) && data.TryGetValue("BrowserMemoryThreshold", out string propMemThreshold)){
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();
}
}
data.Remove("EnableBrowserGCReload");
data.Remove("BrowserMemoryThreshold");
if (data.Count == 0){
obj.Save();
}
}
static UserConfig(){ static UserConfig(){
Serializer.RegisterTypeConverter(typeof(WindowState), WindowState.Converter); Serializer.RegisterTypeConverter(typeof(WindowState), WindowState.Converter);
@@ -39,7 +59,9 @@ namespace TweetDuck.Configuration{
public bool ExpandLinksOnHover { get; set; } = true; public bool ExpandLinksOnHover { get; set; } = true;
public bool SwitchAccountSelectors { get; set; } = true; public bool SwitchAccountSelectors { get; set; } = true;
public bool BestImageQuality { get; set; } = true;
public bool EnableSpellCheck { get; set; } = false; public bool EnableSpellCheck { get; set; } = false;
public int VideoPlayerVolume { get; set; } = 50;
private int _zoomLevel = 100; private int _zoomLevel = 100;
private bool _muteNotifications; private bool _muteNotifications;
@@ -50,6 +72,7 @@ namespace TweetDuck.Configuration{
public string DismissedUpdate { get; set; } = null; public string DismissedUpdate { get; set; } = null;
public bool DisplayNotificationColumn { get; set; } = false; public bool DisplayNotificationColumn { get; set; } = false;
public bool NotificationMediaPreviews { get; set; } = true;
public bool NotificationSkipOnLinkClick { get; set; } = false; public bool NotificationSkipOnLinkClick { get; set; } = false;
public bool NotificationNonIntrusiveMode { get; set; } = true; public bool NotificationNonIntrusiveMode { get; set; } = true;
public int NotificationIdlePauseSeconds { get; set; } = 0; public int NotificationIdlePauseSeconds { get; set; } = 0;
@@ -72,15 +95,14 @@ namespace TweetDuck.Configuration{
public string CustomCefArgs { get; set; } = null; public string CustomCefArgs { get; set; } = null;
public string CustomBrowserCSS { get; set; } = null; public string CustomBrowserCSS { get; set; } = null;
public string CustomNotificationCSS { get; set; } = null; public string CustomNotificationCSS { get; set; } = null;
public bool EnableBrowserGCReload { get; set; } = false;
public int BrowserMemoryThreshold { get; set; } = 350;
// SPECIAL PROPERTIES // SPECIAL PROPERTIES
public bool IsCustomNotificationPositionSet => CustomNotificationPosition != ControlExtensions.InvisibleLocation; public bool IsCustomNotificationPositionSet => CustomNotificationPosition != ControlExtensions.InvisibleLocation;
public bool IsCustomNotificationSizeSet => CustomNotificationSize != Size.Empty; 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;
@@ -126,6 +148,8 @@ namespace TweetDuck.Configuration{
public event EventHandler MuteToggled; public event EventHandler MuteToggled;
public event EventHandler ZoomLevelChanged; public event EventHandler ZoomLevelChanged;
public event EventHandler TrayBehaviorChanged; public event EventHandler TrayBehaviorChanged;
// END OF CONFIG
private readonly string file; private readonly string file;
@@ -133,10 +157,6 @@ namespace TweetDuck.Configuration{
this.file = file; this.file = file;
} }
bool ISerializedObject.OnReadUnknownProperty(string property, string value){
return false;
}
public bool Save(){ public bool Save(){
try{ try{
WindowsUtils.CreateDirectoryForFile(file); WindowsUtils.CreateDirectoryForFile(file);

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,17 +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.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;
@@ -38,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(';');
}); });
} }
@@ -72,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);
} }
@@ -80,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);
} }

View File

@@ -1,8 +1,9 @@
using System; using System;
using System.Drawing; using System.Drawing;
using System.Linq; using System.Linq;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
using TweetLib.Communication;
namespace TweetDuck.Core.Controls{ namespace TweetDuck.Core.Controls{
static class ControlExtensions{ static class ControlExtensions{
@@ -70,7 +71,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, new UIntPtr(0), new IntPtr(1)); Comms.SendMessage(button.Handle, NativeMethods.BCM_SETSHIELD, 0, 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

@@ -38,7 +38,7 @@
// //
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;

View File

@@ -2,15 +2,16 @@
using CefSharp.WinForms; using CefSharp.WinForms;
using System; using System;
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;
@@ -25,6 +26,23 @@ 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;
@@ -40,15 +58,18 @@ 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.memoryUsageTracker = new MemoryUsageTracker("TDGF_tryRunCleanup");
@@ -66,6 +87,7 @@ namespace TweetDuck.Core{
this.browser = new ChromiumWebBrowser("https://tweetdeck.twitter.com/"){ this.browser = new ChromiumWebBrowser("https://tweetdeck.twitter.com/"){
MenuHandler = new ContextMenuBrowser(this), MenuHandler = new ContextMenuBrowser(this),
JsDialogHandler = new JavaScriptDialogHandler(), JsDialogHandler = new JavaScriptDialogHandler(),
KeyboardHandler = new KeyboardHandlerBrowser(this),
LifeSpanHandler = new LifeSpanHandler(), LifeSpanHandler = new LifeSpanHandler(),
RequestHandler = new RequestHandlerBrowser() RequestHandler = new RequestHandlerBrowser()
}; };
@@ -81,7 +103,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);
@@ -96,6 +118,7 @@ namespace TweetDuck.Core{
notificationScreenshotManager?.Dispose(); notificationScreenshotManager?.Dispose();
soundNotification?.Dispose(); soundNotification?.Dispose();
videoPlayer?.Dispose();
}; };
this.trayIcon.ClickRestore += trayIcon_ClickRestore; this.trayIcon.ClickRestore += trayIcon_ClickRestore;
@@ -114,16 +137,6 @@ namespace TweetDuck.Core{
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);
@@ -158,7 +171,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);
} }
@@ -175,30 +188,26 @@ namespace TweetDuck.Core{
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 (Config.EnableBrowserGCReload){ if (Program.SystemConfig.EnableBrowserGCReload){
memoryUsageTracker.Start(this, e.Browser, Config.BrowserMemoryThreshold); memoryUsageTracker.Start(this, e.Browser, Program.SystemConfig.BrowserMemoryThreshold);
} }
} }
} }
@@ -225,6 +234,10 @@ namespace TweetDuck.Core{
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){ private void FormBrowser_LocationChanged(object sender, EventArgs e){
@@ -282,7 +295,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){
@@ -305,7 +318,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){
@@ -314,11 +339,7 @@ namespace TweetDuck.Core{
private void updates_UpdateAccepted(object sender, UpdateAcceptedEventArgs e){ private void updates_UpdateAccepted(object sender, UpdateAcceptedEventArgs e){
this.InvokeAsyncSafe(() => { this.InvokeAsyncSafe(() => {
foreach(Form form in Application.OpenForms.Cast<Form>().Reverse()){ FormManager.CloseAllDialogs();
if (form is FormSettings || form is FormPlugins || form is FormAbout){
form.Close();
}
}
updates.BeginUpdateDownload(this, e.UpdateInfo, update => { updates.BeginUpdateDownload(this, e.UpdateInfo, update => {
if (update.DownloadStatus == UpdateDownloadStatus.Done){ if (update.DownloadStatus == UpdateDownloadStatus.Done){
@@ -374,7 +395,13 @@ namespace TweetDuck.Core{
} }
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){
browser.ExecuteScriptAsync("TDGF_onMouseClickExtra", (m.WParam.ToInt32() >> 16) & 0xFFFF); if (videoPlayer != null && videoPlayer.Running){
videoPlayer.Close();
}
else{
browser.ExecuteScriptAsync("TDGF_onMouseClickExtra", (m.WParam.ToInt32() >> 16) & 0xFFFF);
}
return; return;
} }
@@ -401,12 +428,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($"gc&&gc();window.location.href='{BrowserUtils.TweetDeckURL}'"); browser.ExecuteScriptAsync($"if(window.TDGF_reload)window.TDGF_reload();else window.location.href='{TwitterUtils.TweetDeckURL}'");
} }
// callback handlers // callback handlers
@@ -420,7 +447,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);
@@ -435,14 +462,14 @@ namespace TweetDuck.Core{
trayIcon.HasNotifications = false; trayIcon.HasNotifications = false;
} }
if (Config.EnableBrowserGCReload){ if (Program.SystemConfig.EnableBrowserGCReload){
memoryUsageTracker.Start(this, browser.GetBrowser(), Config.BrowserMemoryThreshold); memoryUsageTracker.Start(this, browser.GetBrowser(), Program.SystemConfig.BrowserMemoryThreshold);
} }
else{ else{
memoryUsageTracker.Stop(); memoryUsageTracker.Stop();
} }
UpdateProperties(PropertyBridge.Properties.ExpandLinksOnHover | PropertyBridge.Properties.SwitchAccountSelectors | PropertyBridge.Properties.HasCustomNotificationSound); UpdateProperties(PropertyBridge.Environment.Browser);
notification.RequiresResize = true; notification.RequiresResize = true;
form.Dispose(); form.Dispose();
@@ -453,13 +480,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));
} }
} }
@@ -483,6 +510,37 @@ 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 bool ToggleVideoPause(){
if (videoPlayer != null && videoPlayer.Running){
videoPlayer.TogglePause();
return true;
}
return false;
}
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, plugins); notificationScreenshotManager = new TweetScreenshotManager(this, plugins);

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,35 +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.Other;
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");
@@ -42,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();
} }
} }
@@ -57,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 => {
FormMessage.Error("Image Download", "An error occurred while downloading the image: "+ex.Message, FormMessage.OK);
});
}
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;
@@ -104,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;
@@ -129,17 +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){
url = StringUtils.ExtractBefore(url, ':', dot);
}
// 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,11 +1,12 @@
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(() => {
@@ -13,23 +14,25 @@ namespace TweetDuck.Core.Handling {
TextBox input = null; TextBox input = null;
if (dialogType == CefJsDialogType.Alert){ if (dialogType == CefJsDialogType.Alert){
form = new FormMessage("TweetDuck Browser Message", messageText, MessageBoxIcon.None); form = new FormMessage("Browser Message", messageText, MessageBoxIcon.None);
form.AddButton(FormMessage.OK, ControlType.Accept | ControlType.Focused); form.AddButton(FormMessage.OK, ControlType.Accept | ControlType.Focused);
} }
else if (dialogType == CefJsDialogType.Confirm){ else if (dialogType == CefJsDialogType.Confirm){
form = new FormMessage("TweetDuck Browser Confirmation", messageText, MessageBoxIcon.None); form = new FormMessage("Browser Confirmation", messageText, MessageBoxIcon.None);
form.AddButton(FormMessage.No, DialogResult.No, ControlType.Cancel); form.AddButton(FormMessage.No, DialogResult.No, ControlType.Cancel);
form.AddButton(FormMessage.Yes, ControlType.Focused); form.AddButton(FormMessage.Yes, ControlType.Focused);
} }
else if (dialogType == CefJsDialogType.Prompt){ else if (dialogType == CefJsDialogType.Prompt){
form = new FormMessage("TweetDuck Browser Prompt", messageText, MessageBoxIcon.None); form = new FormMessage("Browser Prompt", messageText, MessageBoxIcon.None);
form.AddButton(FormMessage.Cancel, DialogResult.Cancel, ControlType.Cancel); form.AddButton(FormMessage.Cancel, DialogResult.Cancel, ControlType.Cancel);
form.AddButton(FormMessage.OK, ControlType.Accept | ControlType.Focused); 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);

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,24 @@
using System.Windows.Forms;
using CefSharp;
namespace TweetDuck.Core.Handling{
sealed class KeyboardHandlerBrowser : IKeyboardHandler{
private readonly FormBrowser form;
public KeyboardHandlerBrowser(FormBrowser form){
this.form = form;
}
bool IKeyboardHandler.OnPreKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey, ref bool isKeyboardShortcut){
if (type == KeyType.RawKeyDown && (Keys)windowsKeyCode == Keys.Space){
return form.ToggleVideoPause();
}
return false;
}
bool IKeyboardHandler.OnKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey){
return false;
}
}
}

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{
@@ -97,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()
}; };
@@ -111,7 +114,7 @@ namespace TweetDuck.Core.Notification{
this.dpiScale = this.GetDPIScale(); this.dpiScale = this.GetDPIScale();
DefaultResourceHandlerFactory handlerFactory = (DefaultResourceHandlerFactory)browser.ResourceHandlerFactory; DefaultResourceHandlerFactory handlerFactory = (DefaultResourceHandlerFactory)browser.ResourceHandlerFactory;
handlerFactory.RegisterHandler(BrowserUtils.TweetDeckURL, this.resourceHandler); handlerFactory.RegisterHandler(TwitterUtils.TweetDeckURL, this.resourceHandler);
Controls.Add(browser); Controls.Add(browser);
@@ -183,7 +186,7 @@ namespace TweetDuck.Core.Notification{
currentColumn = tweet.Column; currentColumn = tweet.Column;
resourceHandler.SetHTML(GetTweetHTML(tweet)); resourceHandler.SetHTML(GetTweetHTML(tweet));
browser.Load(BrowserUtils.TweetDeckURL); browser.Load(TwitterUtils.TweetDeckURL);
} }
protected virtual void SetNotificationSize(int width, int height){ protected virtual void SetNotificationSize(int width, int height){

View File

@@ -4,6 +4,7 @@ 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.Data;
using TweetDuck.Plugins; using TweetDuck.Plugins;
@@ -16,10 +17,7 @@ namespace TweetDuck.Core.Notification{
private const int TimerBarHeight = 4; 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 = ScriptLoader.LoadResource(NotificationScriptFile);
private static readonly string PluginJS = ScriptLoader.LoadResource(PluginManager.PluginNotificationScriptFile);
private readonly PluginManager plugins; private readonly PluginManager plugins;
@@ -66,7 +64,7 @@ namespace TweetDuck.Core.Notification{
get{ get{
switch(Program.UserConfig.NotificationSize){ switch(Program.UserConfig.NotificationSize){
default: default:
return BrowserUtils.Scale(118, SizeScale*(1.0+0.075*TweetNotification.FontSizeLevel)); return BrowserUtils.Scale(122, SizeScale*(1.0+0.075*TweetNotification.FontSizeLevel));
case TweetNotification.Size.Custom: case TweetNotification.Size.Custom:
return Program.UserConfig.CustomNotificationSize.Height; return Program.UserConfig.CustomNotificationSize.Height;
@@ -82,7 +80,9 @@ namespace TweetDuck.Core.Notification{
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);
@@ -164,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);
}
} }
} }

View File

@@ -2,6 +2,7 @@
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.Other;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
@@ -18,9 +19,11 @@ namespace TweetDuck.Core.Notification.Screenshot{
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");
}
} }
}; };
} }

View File

@@ -8,14 +8,14 @@ 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 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, PluginManager pluginManager){ public TweetScreenshotManager(FormBrowser owner, PluginManager pluginManager){
this.owner = owner; this.owner = owner;
this.plugins = pluginManager; this.plugins = pluginManager;
@@ -28,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){
@@ -50,6 +49,10 @@ namespace TweetDuck.Core.Notification.Screenshot{
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(){
@@ -61,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

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

View File

@@ -87,7 +87,7 @@ namespace TweetDuck.Core.Other{
this.prevLabelWidth = labelMessage.Width; this.prevLabelWidth = labelMessage.Width;
this.prevLabelHeight = labelMessage.Height; this.prevLabelHeight = labelMessage.Height;
this.minFormWidth = BrowserUtils.Scale(40, dpiScale); this.minFormWidth = BrowserUtils.Scale(42, dpiScale);
switch(messageIcon){ switch(messageIcon){
case MessageBoxIcon.Information: case MessageBoxIcon.Information:
@@ -108,7 +108,7 @@ 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;
} }
@@ -176,7 +176,7 @@ namespace TweetDuck.Core.Other{
private void RecalculateButtonLocation(){ private void RecalculateButtonLocation(){
int dist = ButtonDistance; int dist = ButtonDistance;
int start = ClientWidth-dist-BrowserUtils.Scale(1, dpiScale); 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];
@@ -201,7 +201,7 @@ namespace TweetDuck.Core.Other{
prevLabelHeight -= labelOffset; 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;
@@ -212,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, BrowserUtils.Scale(25, dpiScale), BrowserUtils.Scale(26, dpiScale)); e.Graphics.DrawIcon(icon, BrowserUtils.Scale(25, dpiScale), 1+BrowserUtils.Scale(25, dpiScale));
} }
base.OnPaint(e); base.OnPaint(e);

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

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

@@ -1,8 +1,9 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using CefSharp; using CefSharp;
using TweetDuck.Core.Utils;
namespace TweetDuck.Core.Utils{ namespace TweetDuck.Core.Other.Management{
static class BrowserProcesses{ static class BrowserProcesses{
private static readonly Dictionary<int, int> PIDs = new Dictionary<int, int>(); private static readonly Dictionary<int, int> PIDs = new Dictionary<int, int>();

View File

@@ -5,7 +5,7 @@ using System.Windows.Forms;
using CefSharp; using CefSharp;
using Timer = System.Timers.Timer; using Timer = System.Timers.Timer;
namespace TweetDuck.Core.Utils{ namespace TweetDuck.Core.Other.Management{
sealed class MemoryUsageTracker : IDisposable{ sealed class MemoryUsageTracker : IDisposable{
private const int IntervalMemoryCheck = 60000*30; // 30 minutes private const int IntervalMemoryCheck = 60000*30; // 30 minutes
private const int IntervalCleanupAttempt = 60000*5; // 5 minutes private const int IntervalCleanupAttempt = 60000*5; // 5 minutes

View File

@@ -0,0 +1,175 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Windows.Forms;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Utils;
using TweetLib.Communication;
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 string lastUrl;
private Process currentProcess;
private DuplexPipe.Server currentPipe;
private bool isClosing;
public VideoPlayer(Form owner){
this.owner = owner;
this.owner.FormClosing += owner_FormClosing;
}
public void Launch(string url){
if (Running){
Destroy();
isClosing = false;
}
lastUrl = url;
try{
currentPipe = DuplexPipe.CreateServer();
currentPipe.DataIn += currentPipe_DataIn;
if ((currentProcess = Process.Start(new ProcessStartInfo{
FileName = PlayerExe,
Arguments = $"{owner.Handle} {Program.UserConfig.VideoPlayerVolume} \"{url}\" \"{currentPipe.GenerateToken()}\"",
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
}
currentPipe.DisposeToken();
}catch(Exception e){
Program.Reporter.HandleException("Video Playback Error", "Error launching video player.", true, e);
}
}
public void TogglePause(){
currentPipe?.Write("pause");
}
private void currentPipe_DataIn(object sender, DuplexPipe.PipeReadEventArgs e){
owner.InvokeSafe(() => {
switch(e.Key){
case "vol":
if (int.TryParse(e.Data, out int volume) && volume != Program.UserConfig.VideoPlayerVolume){
Program.UserConfig.VideoPlayerVolume = volume;
Program.UserConfig.Save();
}
break;
case "rip":
currentPipe.Dispose();
currentPipe = null;
currentProcess.Dispose();
currentProcess = null;
isClosing = false;
TriggerProcessExitEventUnsafe();
break;
}
});
}
public void Close(){
if (currentProcess != null){
if (isClosing){
Destroy();
isClosing = false;
}
else{
isClosing = true;
currentProcess.Exited -= process_Exited;
currentPipe.Write("die");
}
}
}
public void Dispose(){
ProcessExited = null;
isClosing = true;
Destroy();
}
private void Destroy(){
if (currentProcess != null){
try{
currentProcess.Kill();
}catch{
// kill me instead then
}
currentProcess.Dispose();
currentProcess = null;
currentPipe.Dispose();
currentPipe = null;
TriggerProcessExitEventUnsafe();
}
}
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;
currentPipe.Dispose();
currentPipe = null;
owner.InvokeAsyncSafe(TriggerProcessExitEventUnsafe);
}
private void TriggerProcessExitEventUnsafe(){
ProcessExited?.Invoke(this, new EventArgs());
}
}
}

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,7 +31,7 @@ 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 (FormMessage.Question("Confirm CEF Arguments", prompt, FormMessage.OK, FormMessage.Cancel)){ if (FormMessage.Question("Confirm CEF Arguments", prompt, FormMessage.OK, FormMessage.Cancel)){

View File

@@ -68,7 +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 with rendering."); 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.");
this.checkHardwareAcceleration.UseVisualStyleBackColor = true; this.checkHardwareAcceleration.UseVisualStyleBackColor = true;
// //
// btnEditCefArgs // btnEditCefArgs
@@ -142,26 +142,14 @@
0, 0,
0}); 0});
this.numMemoryThreshold.Location = new System.Drawing.Point(202, 82); this.numMemoryThreshold.Location = new System.Drawing.Point(202, 82);
this.numMemoryThreshold.Maximum = new decimal(new int[] { this.numMemoryThreshold.Maximum = 2000;
3000, this.numMemoryThreshold.Minimum = 200;
0,
0,
0});
this.numMemoryThreshold.Minimum = new decimal(new int[] {
200,
0,
0,
0});
this.numMemoryThreshold.Name = "numMemoryThreshold"; this.numMemoryThreshold.Name = "numMemoryThreshold";
this.numMemoryThreshold.Size = new System.Drawing.Size(97, 20); this.numMemoryThreshold.Size = new System.Drawing.Size(97, 20);
this.numMemoryThreshold.TabIndex = 4; this.numMemoryThreshold.TabIndex = 4;
this.numMemoryThreshold.TextSuffix = " MB"; 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.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 = new decimal(new int[] { this.numMemoryThreshold.Value = 400;
350,
0,
0,
0});
// //
// checkBrowserGCReload // checkBrowserGCReload
// //
@@ -172,7 +160,7 @@
this.checkBrowserGCReload.Size = new System.Drawing.Size(190, 17); this.checkBrowserGCReload.Size = new System.Drawing.Size(190, 17);
this.checkBrowserGCReload.TabIndex = 3; this.checkBrowserGCReload.TabIndex = 3;
this.checkBrowserGCReload.Text = "Enable Browser Memory Threshold"; this.checkBrowserGCReload.Text = "Enable Browser Memory Threshold";
this.toolTip.SetToolTip(this.checkBrowserGCReload, "Automatically reloads TweetDeck to save memory. This option only works\r\nif the browser is in a \'default state\', i.e. all modals and drawers are closed,\r\nand all columns are scrolled to top. Some notifications may be lost."); 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; this.checkBrowserGCReload.UseVisualStyleBackColor = true;
// //
// labelApp // labelApp

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,16 +18,16 @@ 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 = Config.EnableBrowserGCReload; checkBrowserGCReload.Checked = SysConfig.EnableBrowserGCReload;
numMemoryThreshold.Enabled = checkBrowserGCReload.Checked; numMemoryThreshold.Enabled = checkBrowserGCReload.Checked;
numMemoryThreshold.SetValueSafe(Config.BrowserMemoryThreshold); numMemoryThreshold.SetValueSafe(SysConfig.BrowserMemoryThreshold);
BrowserCache.CalculateCacheSize(bytes => this.InvokeSafe(() => { BrowserCache.CalculateCacheSize(bytes => this.InvokeSafe(() => {
if (bytes == -1L){ if (bytes == -1L){
@@ -53,6 +55,10 @@ 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();
@@ -60,18 +66,17 @@ namespace TweetDuck.Core.Other.Settings{
} }
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){ private void checkBrowserGCReload_CheckedChanged(object sender, EventArgs e){
Config.EnableBrowserGCReload = checkBrowserGCReload.Checked; SysConfig.EnableBrowserGCReload = checkBrowserGCReload.Checked;
numMemoryThreshold.Enabled = checkBrowserGCReload.Checked; numMemoryThreshold.Enabled = checkBrowserGCReload.Checked;
} }
private void numMemoryThreshold_ValueChanged(object sender, EventArgs e){ private void numMemoryThreshold_ValueChanged(object sender, EventArgs e){
Config.BrowserMemoryThreshold = (int)numMemoryThreshold.Value; SysConfig.BrowserMemoryThreshold = (int)numMemoryThreshold.Value;
} }
private void btnEditCefArgs_Click(object sender, EventArgs e){ private void btnEditCefArgs_Click(object sender, EventArgs e){
@@ -82,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;
@@ -104,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;
@@ -138,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();
@@ -87,11 +88,11 @@
// 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;
@@ -122,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.");
@@ -158,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
@@ -198,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);
@@ -206,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);
@@ -226,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;
@@ -238,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;
@@ -247,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);
@@ -265,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();
@@ -299,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

@@ -28,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;
@@ -37,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;
@@ -59,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();

View File

@@ -63,6 +63,7 @@
this.labelSize = 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.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();
@@ -273,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;
@@ -297,32 +298,32 @@
// labelIdlePause // labelIdlePause
// //
this.labelIdlePause.AutoSize = true; this.labelIdlePause.AutoSize = true;
this.labelIdlePause.Location = new System.Drawing.Point(3, 83); 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 = 3; 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, 99); 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 = 4; 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, 51); this.checkNonIntrusive.Location = new System.Drawing.Point(6, 74);
this.checkNonIntrusive.Margin = new System.Windows.Forms.Padding(6, 3, 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 = 2; 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 delayed until the cursor moves away to prevent accidental clicks."); 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.");
this.checkNonIntrusive.UseVisualStyleBackColor = true; this.checkNonIntrusive.UseVisualStyleBackColor = true;
@@ -389,6 +390,7 @@
// //
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.checkNonIntrusive);
@@ -396,7 +398,7 @@
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, 126); this.panelGeneral.Size = new System.Drawing.Size(322, 149);
this.panelGeneral.TabIndex = 1; this.panelGeneral.TabIndex = 1;
// //
// labelScrollSpeedValue // labelScrollSpeedValue
@@ -437,7 +439,7 @@
// //
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, 372); 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);
@@ -458,7 +460,7 @@
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, 395); 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 = 5; this.panelLocation.TabIndex = 5;
@@ -473,7 +475,7 @@
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, 204); 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 = 3; this.panelTimer.TabIndex = 3;
@@ -492,7 +494,7 @@
// //
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, 181); 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);
@@ -503,7 +505,7 @@
// //
this.labelSize.AutoSize = true; this.labelSize.AutoSize = true;
this.labelSize.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.labelSize.Location = new System.Drawing.Point(6, 584); this.labelSize.Location = new System.Drawing.Point(6, 607);
this.labelSize.Margin = new System.Windows.Forms.Padding(0, 21, 0, 0); this.labelSize.Margin = new System.Windows.Forms.Padding(0, 21, 0, 0);
this.labelSize.Name = "labelSize"; this.labelSize.Name = "labelSize";
this.labelSize.Size = new System.Drawing.Size(40, 20); this.labelSize.Size = new System.Drawing.Size(40, 20);
@@ -519,7 +521,7 @@
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.labelScrollSpeed); this.panelMiscellaneous.Controls.Add(this.labelScrollSpeed);
this.panelMiscellaneous.Location = new System.Drawing.Point(9, 607); 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, 92); this.panelMiscellaneous.Size = new System.Drawing.Size(322, 92);
this.panelMiscellaneous.TabIndex = 7; this.panelMiscellaneous.TabIndex = 7;
@@ -529,6 +531,18 @@
this.durationUpdateTimer.Interval = 200; this.durationUpdateTimer.Interval = 200;
this.durationUpdateTimer.Tick += new System.EventHandler(this.durationUpdateTimer_Tick); 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);
@@ -542,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, 708); 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);
@@ -601,5 +615,6 @@
private System.Windows.Forms.Timer durationUpdateTimer; private System.Windows.Forms.Timer durationUpdateTimer;
private System.Windows.Forms.RadioButton radioSizeCustom; private System.Windows.Forms.RadioButton radioSizeCustom;
private System.Windows.Forms.RadioButton radioSizeAuto; private System.Windows.Forms.RadioButton radioSizeAuto;
private System.Windows.Forms.CheckBox checkMediaPreviews;
} }
} }

View File

@@ -63,6 +63,7 @@ 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;
@@ -96,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;
@@ -218,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;
} }

View File

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

View File

@@ -2,7 +2,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Drawing;
using System.IO; using System.IO;
using System.Net; using System.Net;
using System.Windows.Forms; using System.Windows.Forms;
@@ -10,8 +9,6 @@ using TweetDuck.Core.Other;
namespace TweetDuck.Core.Utils{ namespace TweetDuck.Core.Utils{
static class BrowserUtils{ static class BrowserUtils{
public const string TweetDeckURL = "https://tweetdeck.twitter.com";
public static string HeaderAcceptLanguage{ public static string HeaderAcceptLanguage{
get{ get{
string culture = Program.Culture.Name; string culture = Program.Culture.Name;
@@ -27,13 +24,6 @@ namespace TweetDuck.Core.Utils{
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 const string BackgroundColorFix = "let e=document.createElement('style');document.head.appendChild(e);e.innerHTML='body::before{background:#1c6399!important}'";
public static readonly string[] DictionaryWords = {
"tweetdeck", "TweetDeck", "tweetduck", "TweetDuck", "TD"
};
public static void SetupCefArgs(IDictionary<string, string> args){ public static void SetupCefArgs(IDictionary<string, string> args){
if (!Program.SystemConfig.HardwareAcceleration){ if (!Program.SystemConfig.HardwareAcceleration){
args["disable-gpu"] = "1"; args["disable-gpu"] = "1";
@@ -117,14 +107,6 @@ namespace TweetDuck.Core.Utils{
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,39 +0,0 @@
using System;
using System.Text.RegularExpressions;
using TweetDuck.Data;
namespace TweetDuck.Core.Utils{
static class CommandLineArgsParser{
private static readonly Lazy<Regex> SplitRegex = new Lazy<Regex>(() => new Regex(@"([^=\s]+(?:=(?:\S*""[^""]*?""\S*|\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

@@ -8,7 +8,6 @@ namespace TweetDuck.Core.Utils{
[SuppressMessage("ReSharper", "FieldCanBeMadeReadOnly.Local")] [SuppressMessage("ReSharper", "FieldCanBeMadeReadOnly.Local")]
[SuppressMessage("ReSharper", "MemberCanBePrivate.Local")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Local")]
static class NativeMethods{ static class NativeMethods{
public static readonly IntPtr HWND_BROADCAST = new IntPtr(0xFFFF);
public static readonly IntPtr HOOK_HANDLED = new IntPtr(-1); public static readonly IntPtr HOOK_HANDLED = new IntPtr(-1);
public const int HWND_TOPMOST = -1; public const int HWND_TOPMOST = -1;
@@ -65,16 +64,7 @@ namespace TweetDuck.Core.Utils{
[DllImport("gdi32.dll")] [DllImport("gdi32.dll")]
[return: MarshalAs(UnmanagedType.Bool)] [return: MarshalAs(UnmanagedType.Bool)]
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")]
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")]
public static extern uint RegisterWindowMessage(string messageName);
[DllImport("user32.dll")] [DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)] [return: MarshalAs(UnmanagedType.Bool)]
public static extern bool ShowScrollBar(IntPtr hWnd, int wBar, bool bShow); public static extern bool ShowScrollBar(IntPtr hWnd, int wBar, bool bShow);

View File

@@ -4,6 +4,8 @@ using System.Text.RegularExpressions;
namespace TweetDuck.Core.Utils{ namespace TweetDuck.Core.Utils{
static class StringUtils{ static class StringUtils{
public static readonly string[] EmptyArray = new string[0];
public static string ExtractBefore(string str, char search, int startIndex = 0){ public static string ExtractBefore(string str, char search, int startIndex = 0){
int index = str.IndexOf(search, startIndex); int index = str.IndexOf(search, startIndex);
return index == -1 ? str : str.Substring(0, index); return index == -1 ? str : str.Substring(0, index);

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

@@ -103,7 +103,7 @@ namespace TweetDuck.Data{
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);
} }
} }

View File

@@ -1,5 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
using System.Text.RegularExpressions;
namespace TweetDuck.Data{ namespace TweetDuck.Data{
sealed class CommandLineArgs{ sealed class CommandLineArgs{
@@ -32,6 +33,36 @@ namespace TweetDuck.Data{
} }
} }
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>();

View File

@@ -6,12 +6,15 @@ using System.Reflection;
using System.Runtime.Serialization; using System.Runtime.Serialization;
namespace TweetDuck.Data.Serialization{ namespace TweetDuck.Data.Serialization{
sealed class FileSerializer<T> where T : ISerializedObject{ sealed class FileSerializer<T>{
private const string NewLineReal = "\r\n"; private const string NewLineReal = "\r\n";
private const string NewLineCustom = "\r~\n"; private const string NewLineCustom = "\r~\n";
private static readonly ITypeConverter BasicSerializerObj = new BasicTypeConverter(); 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<string, PropertyInfo> props;
private readonly Dictionary<Type, ITypeConverter> converters; private readonly Dictionary<Type, ITypeConverter> converters;
@@ -48,8 +51,10 @@ namespace TweetDuck.Data.Serialization{
} }
public void Read(string file, T obj){ 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))){ using(StreamReader reader = new StreamReader(new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read))){
if (reader.Peek() == 0){ if (reader.Peek() <= 1){
throw new FormatException("Input appears to be a binary file."); throw new FormatException("Input appears to be a binary file.");
} }
@@ -75,11 +80,19 @@ namespace TweetDuck.Data.Serialization{
throw new SerializationException($"Invalid file format, cannot convert value: {value} (property: {property})"); throw new SerializationException($"Invalid file format, cannot convert value: {value} (property: {property})");
} }
} }
else if (!obj.OnReadUnknownProperty(property, value)){ else{
throw new SerializationException($"Invalid file format, unknown property: {property}+"); 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{ private class BasicTypeConverter : ITypeConverter{

View File

@@ -1,5 +0,0 @@
namespace TweetDuck.Data.Serialization{
interface ISerializedObject{
bool OnReadUnknownProperty(string property, string value);
}
}

View File

@@ -2,11 +2,8 @@
namespace TweetDuck.Data.Serialization{ namespace TweetDuck.Data.Serialization{
sealed class SingleTypeConverter<T> : ITypeConverter{ sealed class SingleTypeConverter<T> : ITypeConverter{
public delegate string FuncConvertToString<U>(U value); public Func<T, string> ConvertToString { get; set; }
public delegate U FuncConvertToObject<U>(string value); public Func<string, T> ConvertToObject { get; set; }
public FuncConvertToString<T> ConvertToString { get; set; }
public FuncConvertToObject<T> ConvertToObject { get; set; }
bool ITypeConverter.TryWriteType(Type type, object value, out string converted){ bool ITypeConverter.TryWriteType(Type type, object value, out string converted){
try{ try{

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

@@ -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{
@@ -152,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){

View File

@@ -40,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);

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

@@ -8,7 +8,7 @@ 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()) .Replace("%token", pluginToken.ToString())
.Replace("%contents", pluginContents); .Replace("%contents", pluginContents);

View File

@@ -1,4 +1,4 @@
using CefSharp; using CefSharp;
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Globalization; using System.Globalization;
@@ -8,22 +8,21 @@ 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.Data; using TweetDuck.Data;
using TweetDuck.Plugins;
using TweetDuck.Plugins.Events;
using TweetDuck.Updates; using TweetDuck.Updates;
using TweetLib.Communication;
namespace TweetDuck{ namespace TweetDuck{
static class Program{ static class Program{
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.3"; public const string VersionTag = "1.8.6";
public const string VersionFull = "1.8.3.0"; public const string VersionFull = "1.8.6";
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");
@@ -34,13 +33,13 @@ 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");
@@ -62,6 +61,10 @@ namespace TweetDuck{
Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture; Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
CultureInfo.DefaultThreadCurrentCulture = CultureInfo.InvariantCulture; CultureInfo.DefaultThreadCurrentCulture = CultureInfo.InvariantCulture;
#if DEBUG
CultureInfo.DefaultThreadCurrentUICulture = Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-us"); // force english exceptions
#endif
Reporter = new Reporter(ErrorLogFilePath); Reporter = new Reporter(ErrorLogFilePath);
Reporter.SetupUnhandledExceptionHandler("TweetDuck Has Failed :("); Reporter.SetupUnhandledExceptionHandler("TweetDuck Has Failed :(");
} }
@@ -71,8 +74,8 @@ namespace TweetDuck{
Application.EnableVisualStyles(); Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false); Application.SetCompatibleTextRenderingDefault(false);
WindowRestoreMessage = NativeMethods.RegisterWindowMessage("TweetDuckRestore"); WindowRestoreMessage = Comms.RegisterMessage("TweetDuckRestore");
SubProcessMessage = NativeMethods.RegisterWindowMessage("TweetDuckSubProcess"); SubProcessMessage = Comms.RegisterMessage("TweetDuckSubProcess");
if (!WindowsUtils.CheckFolderWritePermission(StoragePath)){ if (!WindowsUtils.CheckFolderWritePermission(StoragePath)){
FormMessage.Warning("Permission Error", "TweetDuck does not have write permissions to the storage folder: "+StoragePath, FormMessage.OK); FormMessage.Warning("Permission Error", "TweetDuck does not have write permissions to the storage folder: "+StoragePath, FormMessage.OK);
@@ -80,60 +83,43 @@ namespace TweetDuck{
} }
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){
}
else 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); 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;
} }
else if (attempt == 20){ else if (!FormMessage.Warning("TweetDuck Cannot Restart", "TweetDuck is taking too long to close.", FormMessage.Retry, FormMessage.Exit)){
if (FormMessage.Warning("TweetDuck Cannot Restart", "TweetDuck is taking too long to close.", FormMessage.Retry, FormMessage.Exit)){
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.PostMessage(NativeMethods.HWND_BROADCAST, WindowRestoreMessage, new UIntPtr((uint)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;
}
}
if (FormMessage.Error("TweetDuck is Already Running", "Another instance of TweetDuck is already running.\nDo you want to close it?", FormMessage.Yes, FormMessage.No)){
if (!LockManager.CloseLockingProcess(10000, 5000)){ if (!LockManager.CloseLockingProcess(10000, 5000)){
FormMessage.Error("TweetDuck Has Failed :(", "Could not close the other process.", FormMessage.OK); 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){
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); 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();
@@ -157,7 +143,7 @@ namespace TweetDuck{
#endif #endif
}; };
CommandLineArgsParser.ReadCefArguments(UserConfig.CustomCefArgs).ToDictionary(settings.CefCommandLineArgs); CommandLineArgs.ReadCefArguments(UserConfig.CustomCefArgs).ToDictionary(settings.CefCommandLineArgs);
BrowserUtils.SetupCefArgs(settings.CefCommandLineArgs); BrowserUtils.SetupCefArgs(settings.CefCommandLineArgs);
Cef.EnableHighDPISupport(); Cef.EnableHighDPISupport();
@@ -165,18 +151,13 @@ namespace TweetDuck{
Application.ApplicationExit += (sender, args) => ExitCleanup(); Application.ApplicationExit += (sender, args) => ExitCleanup();
PluginManager plugins = new PluginManager(PluginPath, PluginConfigFilePath);
plugins.Reloaded += plugins_Reloaded;
plugins.Executed += plugins_Executed;
plugins.Reload();
UpdaterSettings updaterSettings = new UpdaterSettings{ UpdaterSettings updaterSettings = 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(plugins, updaterSettings); FormBrowser mainForm = new FormBrowser(updaterSettings);
Application.Run(mainForm); Application.Run(mainForm);
if (mainForm.UpdateInstallerPath != null){ if (mainForm.UpdateInstallerPath != null){
@@ -191,18 +172,6 @@ namespace TweetDuck{
} }
} }
private static void plugins_Reloaded(object sender, PluginErrorEventArgs e){
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);
}
}
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 static string GetDataStoragePath(){ private static string GetDataStoragePath(){
string custom = Arguments.GetValue(Arguments.ArgDataFolder, null); string custom = Arguments.GetValue(Arguments.ArgDataFolder, null);

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

@@ -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.2 1.3
[website] [website]
https://tweetduck.chylex.com https://tweetduck.chylex.com

View File

@@ -1,4 +1,6 @@
enabled(){ enabled(){
this.ENABLE_CUSTOM_KEYBOARD = false;
this.selectedSkinTone = ""; this.selectedSkinTone = "";
this.currentKeywords = []; this.currentKeywords = [];
@@ -29,17 +31,29 @@ 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 }");
this.css.insert("#emoji-keyboard-tweet-input { padding: 0 !important; line-height: 18px }");
this.css.insert("#emoji-keyboard-tweet-input img { padding: 0.1em !important; width: 1em; height: 1em; vertical-align: -0.25em }");
this.css.insert("#emoji-keyboard-tweet-input:empty::before { content: \"What's happening?\"; display: inline-block; color: #ced8de }");
this.css.insert(".js-docked-compose .compose-text-container.td-emoji-keyboard-swap .js-compose-text { position: absolute; z-index: -9999; left: 0; opacity: 0 }");
this.css.insert(".compose-text-container:not(.td-emoji-keyboard-swap) #emoji-keyboard-tweet-input { display: none; }");
this.css.insert(".js-compose-text { font-family: \"Twitter Color Emoji\", Helvetica, Arial, Verdana, sans-serif; }");
// layout // layout
var buttonHTML = '<button class="needsclick btn btn-on-blue txt-left padding-v--9 emoji-keyboard-popup-btn"><i class="icon icon-heart"></i></button>'; var buttonHTML = '<button class="needsclick btn btn-on-blue txt-left padding-v--9 emoji-keyboard-popup-btn"><i class="icon icon-heart"></i></button>';
@@ -47,10 +61,10 @@ enabled(){
this.prevComposeMustache = TD.mustaches["compose/docked_compose.mustache"]; this.prevComposeMustache = TD.mustaches["compose/docked_compose.mustache"];
TD.mustaches["compose/docked_compose.mustache"] = TD.mustaches["compose/docked_compose.mustache"].replace('<div class="cf margin-t--12 margin-b--30">', '<div class="cf margin-t--12 margin-b--30">'+buttonHTML); TD.mustaches["compose/docked_compose.mustache"] = TD.mustaches["compose/docked_compose.mustache"].replace('<div class="cf margin-t--12 margin-b--30">', '<div class="cf margin-t--12 margin-b--30">'+buttonHTML);
var dockedComposePanel = $(".js-docked-compose"); var maybeDockedComposePanel = $(".js-docked-compose");
if (dockedComposePanel.length){ if (maybeDockedComposePanel.length){
dockedComposePanel.find(".cf.margin-t--12.margin-b--30").first().append(buttonHTML); maybeDockedComposePanel.find(".cf.margin-t--12.margin-b--30").first().append(buttonHTML);
} }
// keyboard generation // keyboard generation
@@ -58,7 +72,7 @@ enabled(){
this.currentKeyboard = null; this.currentKeyboard = null;
this.currentSpanner = null; this.currentSpanner = null;
var hideKeyboard = () => { var hideKeyboard = (refocus) => {
$(this.currentKeyboard).remove(); $(this.currentKeyboard).remove();
this.currentKeyboard = null; this.currentKeyboard = null;
@@ -70,7 +84,15 @@ enabled(){
this.composePanelScroller.trigger("scroll"); this.composePanelScroller.trigger("scroll");
$(".emoji-keyboard-popup-btn").removeClass("is-selected"); $(".emoji-keyboard-popup-btn").removeClass("is-selected");
$(".js-compose-text").first().focus();
if (refocus){
if ($(".compose-text-container", ".js-docked-compose").hasClass("td-emoji-keyboard-swap")){
$("#emoji-keyboard-tweet-input").focus();
}
else{
$(".js-compose-text", ".js-docked-compose").focus();
}
}
}; };
var generateEmojiHTML = skinTone => { var generateEmojiHTML = skinTone => {
@@ -124,7 +146,7 @@ enabled(){
updateFilters(); updateFilters();
}; };
this.generateKeyboard = (input, left, top) => { this.generateKeyboard = (left, top) => {
var outer = document.createElement("div"); var outer = document.createElement("div");
outer.classList.add("emoji-keyboard"); outer.classList.add("emoji-keyboard");
outer.style.left = left+"px"; outer.style.left = left+"px";
@@ -134,18 +156,10 @@ enabled(){
keyboard.classList.add("emoji-keyboard-list"); keyboard.classList.add("emoji-keyboard-list");
keyboard.addEventListener("click", function(e){ keyboard.addEventListener("click", function(e){
if (e.target.tagName === "IMG"){ let ele = e.target;
var val = input.val();
var inserted = e.target.getAttribute("alt"); if (ele.tagName === "IMG"){
var posStart = input[0].selectionStart; insertEmoji(ele.getAttribute("src"), ele.getAttribute("alt"));
var posEnd = input[0].selectionEnd;
input.val(val.slice(0, posStart)+inserted+val.slice(posStart));
input.trigger("change");
input.focus();
input[0].selectionStart = posStart+inserted.length;
input[0].selectionEnd = posEnd+inserted.length;
} }
e.stopPropagation(); e.stopPropagation();
@@ -204,7 +218,43 @@ enabled(){
return button.offset().top+button.outerHeight()+me.composePanelScroller.scrollTop()+8; return button.offset().top+button.outerHeight()+me.composePanelScroller.scrollTop()+8;
}; };
// event handlers var focusWithCaretAtEnd = () => {
let range = document.createRange();
range.selectNodeContents(me.composeInputNew[0]);
range.collapse(false);
let sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
};
var insertEmoji = (src, alt) => {
if (this.ENABLE_CUSTOM_KEYBOARD){
replaceEditor(true);
if (!hasSelectionInEditor()){
focusWithCaretAtEnd();
}
document.execCommand("insertHTML", false, `<img src="${src}" alt="${alt}">`);
}
else{
let input = $(".js-compose-text", ".js-docked-compose");
let val = input.val();
let posStart = input[0].selectionStart;
let posEnd = input[0].selectionEnd;
input.val(val.slice(0, posStart)+alt+val.slice(posEnd));
input.trigger("change");
input.focus();
input[0].selectionStart = posStart+alt.length;
input[0].selectionEnd = posStart+alt.length;
}
};
// general event handlers
this.emojiKeyboardButtonClickEvent = function(e){ this.emojiKeyboardButtonClickEvent = function(e){
if (me.currentKeyboard){ if (me.currentKeyboard){
@@ -212,7 +262,7 @@ enabled(){
$(this).blur(); $(this).blur();
} }
else{ else{
me.generateKeyboard($(".js-compose-text").first(), $(this).offset().left, getKeyboardTop()); me.generateKeyboard($(this).offset().left, getKeyboardTop());
$(this).addClass("is-selected"); $(this).addClass("is-selected");
} }
@@ -225,15 +275,19 @@ 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).closest(".compose-text-container").length === 0){
hideKeyboard(); hideKeyboard();
} }
}; };
this.documentKeyEvent = function(e){ this.documentKeyEvent = function(e){
if (me.currentKeyboard && e.keyCode === 27){ // escape if (me.currentKeyboard && e.keyCode === 27){ // escape
hideKeyboard(); hideKeyboard(true);
e.stopPropagation(); e.stopPropagation();
} }
}; };
@@ -242,10 +296,154 @@ enabled(){
if (me.currentKeyboard){ if (me.currentKeyboard){
me.currentKeyboard.style.top = getKeyboardTop()+"px"; me.currentKeyboard.style.top = getKeyboardTop()+"px";
} }
} };
// new editor event handlers
var prevOldInputVal = "";
var isEditorActive = false;
var hasSelectionInEditor = function(){
let sel = window.getSelection();
return sel.anchorNode && $(sel.anchorNode).closest("#emoji-keyboard-tweet-input").length;
};
var migrateEditorText = function(){
let selStart = me.composeInputOrig[0].selectionStart;
let selEnd = me.composeInputOrig[0].selectionEnd;
let val = me.composeInputOrig.val();
let splitStart = val.substring(0, selStart).split("\n");
let splitEnd = val.substring(0, selEnd).split("\n");
me.composeInputNew.text(val);
me.composeInputNew.html(me.composeInputNew.text().replace(/^(.*?)$/gm, "<div>$1</div>").replace(/<div><\/div>/g, "<div><br></div>"));
let sel = window.getSelection();
let range = document.createRange();
if (me.composeInputNew[0].children.length === 0){
focusWithCaretAtEnd();
}
else{
me.composeInputNew.focus();
range.setStart(me.composeInputNew[0].children[splitStart.length-1].firstChild, splitStart.length > 1 ? selStart-val.lastIndexOf("\n", selStart)-1 : selStart);
range.setEnd(me.composeInputNew[0].children[splitEnd.length-1].firstChild, splitEnd.length > 1 ? selEnd-val.lastIndexOf("\n", selEnd)-1 : selEnd);
}
sel.removeAllRanges();
sel.addRange(range);
};
var replaceEditor = function(useCustom){
if (useCustom && !isEditorActive){
isEditorActive = true;
}
else if (!useCustom && isEditorActive){
isEditorActive = false;
}
else return;
$(".compose-text-container", ".js-docked-compose").toggleClass("td-emoji-keyboard-swap", isEditorActive);
if (isEditorActive){
migrateEditorText();
}
else{
me.composeInputOrig.focus();
}
};
this.composeOldInputFocusEvent = function(){
if (!isEditorActive){
return;
}
let val = $(this).val();
if (val.length === 0){
replaceEditor(false);
}
else if (val != prevOldInputVal){
setTimeout(migrateEditorText, 1);
}
else{
focusWithCaretAtEnd();
}
prevOldInputVal = val;
};
var allowedShortcuts = [ 65 /* A */, 67 /* C */, 86 /* V */, 89 /* Y */, 90 /* Z */ ];
this.composeInputKeyEvent = function(e){
if (e.type === "keydown" && (e.ctrlKey || e.metaKey)){
if (e.keyCode === 13){ // enter
$(".js-send-button", ".js-docked-compose").click();
}
else if (e.keyCode >= 48 && !allowedShortcuts.includes(e.keyCode)){
e.preventDefault();
return;
}
}
if (e.keyCode !== 27){ // escape
e.stopPropagation();
}
};
this.composeInputUpdateEvent = function(){
let clone = $(this).clone();
clone.children("div").each(function(){
let ele = $(this)[0];
ele.outerHTML = "\n"+ele.innerHTML;
});
clone.children("img").each(function(){
let ele = $(this)[0];
ele.outerHTML = ele.getAttribute("alt");
});
me.composeInputOrig.val(prevOldInputVal = clone.text());
me.composeInputOrig.trigger("change");
if (prevOldInputVal.length === 0){
replaceEditor(false);
}
/* TODO if (!emoji.length){
let sel = window.getSelection();
let selStart = -1, selEnd = -1;
if ($(sel.anchorNode).closest("#emoji-keyboard-tweet-input").length && sel.rangeCount === 1){
let range = sel.getRangeAt(0);
// TODO figure out offset
}
replaceEditor(false);
me.composeInputOrig.focus();
if (selStart !== -1){
me.composeInputOrig[0].selectionStart = selStart;
me.composeInputOrig[0].selectionEnd = selEnd;
}
}*/
};
this.composeInputPasteEvent = function(e){ // contenteditable with <img alt> handles copying just fine
e.preventDefault();
let text = e.originalEvent.clipboardData.getData("text/plain");
text && document.execCommand("insertText", false, text);
};
// TODO handle @ and hashtags
} }
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 +451,20 @@ 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);
// Editor
this.composeInputOrig = $(".js-compose-text", ".js-docked-compose").first();
this.composeInputNew = $('<div id="emoji-keyboard-tweet-input" contenteditable="true" class="compose-text txt-size--14 scroll-v scroll-styled-v scroll-styled-h scroll-alt td-detect-image-paste"></div>').insertAfter(this.composeInputOrig);
if (this.ENABLE_CUSTOM_KEYBOARD){
this.composeInputOrig.on("focus", this.composeOldInputFocusEvent);
this.composeInputNew.on("keydown keypress keyup", this.composeInputKeyEvent);
this.composeInputNew.on("input", this.composeInputUpdateEvent);
this.composeInputNew.on("paste", this.composeInputPasteEvent);
}
// HTML generation // HTML generation
@@ -273,12 +485,14 @@ ready(){
// declaration inserters // declaration inserters
let mapUnicode = pt => convUnicode(parseInt(pt, 16));
let addDeclaration1 = decl => { let addDeclaration1 = decl => {
this.emojiData1.push(decl.split(" ").map(pt => convUnicode(parseInt(pt, 16))).join("")); this.emojiData1.push(decl.split(" ").map(mapUnicode).join(""));
}; };
let addDeclaration2 = (tone, decl) => { let addDeclaration2 = (tone, decl) => {
let gen = decl.split(" ").map(pt => convUnicode(parseInt(pt, 16))).join(""); let gen = decl.split(" ").map(mapUnicode).join("");
if (tone === null){ if (tone === null){
for(let skinTone of this.skinToneList){ for(let skinTone of this.skinToneList){
@@ -291,7 +505,7 @@ ready(){
}; };
let addDeclaration3 = decl => { let addDeclaration3 = decl => {
this.emojiData3.push(decl.split(" ").map(pt => convUnicode(parseInt(pt, 16))).join("")); this.emojiData3.push(decl.split(" ").map(mapUnicode).join(""));
}; };
// line reading // line reading
@@ -365,6 +579,9 @@ disabled(){
$(this.currentSpanner).remove(); $(this.currentSpanner).remove();
} }
this.composeInputNew.remove();
this.composeInputOrig.off("focus", this.composeOldInputFocusEvent);
this.composePanelScroller.off("scroll", this.composerScrollEvent); this.composePanelScroller.off("scroll", this.composerScrollEvent);
$(".emoji-keyboard-popup-btn").off("click", this.emojiKeyboardButtonClickEvent); $(".emoji-keyboard-popup-btn").off("click", this.emojiKeyboardButtonClickEvent);
@@ -373,5 +590,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

@@ -8,7 +8,7 @@ Templates
chylex chylex
[version] [version]
1.0 1.0.2
[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"><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();

12
Resources/PostBuild.ps1 Normal file
View File

@@ -0,0 +1,12 @@
Param([Parameter(Mandatory = $True, Position = 1)][string] $dir)
$ErrorActionPreference = "Stop"
Set-Location $dir
ForEach($file in Get-ChildItem -Include *.js, *.html -Recurse){
$lines = Get-Content -Path $file.FullName
$lines = ($lines | % { $_.TrimStart() }) -Replace '^(.*?)((?<=^|[;{}()] )//\s.*)?$', '$1'
$lines | Where { $_ -ne '' } | Set-Content -Path $file.FullName
Write-Host "Processed" $file.FullName.Substring($dir.Length)
}

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,53 +97,86 @@
// 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){ recentTweetTimer = null;
window.clearTimeout(recentTweetTimer); recentTweets.clear();
}
recentTweetTimer = window.setTimeout(() => {
recentTweetTimer = null;
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 sensitive = (tweet.getRelatedTweet() && tweet.getRelatedTweet().possiblySensitive || (tweet.quotedTweet && tweet.quotedTweet.possiblySensitive));
let previews = $TDX.notificationMediaPreviews && !sensitive;
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;
}); });
@@ -152,12 +185,19 @@
if (type === "follow"){ if (type === "follow"){
html.find(".js-user-actions-menu").parent().remove(); html.find(".js-user-actions-menu").parent().remove();
html.find(".account-bio").removeClass("padding-t--5").css("padding-top", "2px");
} }
else if (type.includes("list_member")){ else if (type.includes("list_member")){
html.find(".activity-header").first().css("margin-top", "2px"); html.find(".activity-header").css("margin-top", "2px");
html.find(".avatar").first().css("margin-bottom", "0"); html.find(".avatar").first().css("margin-bottom", "0");
} }
if (sensitive){
html.find(".media-badge").each(function(){
$(this)[0].lastChild.textContent += " (possibly sensitive)";
});
}
let source = tweet.getRelatedTweet(); let source = tweet.getRelatedTweet();
let duration = source ? source.text.length+(source.quotedTweet ? source.quotedTweet.text.length : 0) : tweet.text.length; let duration = source ? source.text.length+(source.quotedTweet ? source.quotedTweet.text.length : 0) : tweet.text.length;
let tweetUrl = source ? source.getChirpURL() : ""; let tweetUrl = source ? source.getChirpURL() : "";
@@ -210,6 +250,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 +280,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 +315,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 +323,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 +341,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 +366,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 +406,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 +429,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;
updateHighlightedTweet(me, tweet, link || "", embedded || ""); 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 || "", 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 +476,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 +514,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);
@@ -477,10 +534,15 @@
onAppReady.push(function(){ onAppReady.push(function(){
var uploader = $._data(document, "events")["uiComposeAddImageClick"][0].handler.context; var uploader = $._data(document, "events")["uiComposeAddImageClick"][0].handler.context;
app.delegate(".js-compose-text,.js-reply-tweetbox", "paste", function(e){ app.delegate(".js-compose-text,.js-reply-tweetbox,.td-detect-image-paste", "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(); $(".js-compose-text", ".js-docked-compose").focus();
@@ -499,31 +561,33 @@
}; };
var tryCloseModal1 = function(){ var tryCloseModal1 = function(){
var modal = $("#open-modal"); let modal = $("#open-modal");
return modal.is(":visible") && tryClickSelector("a.mdl-dismiss", modal); return modal.is(":visible") && tryClickSelector("a.mdl-dismiss", modal);
}; };
var tryCloseModal2 = function(){ var tryCloseModal2 = function(){
var modal = $(".js-modals-container"); let modal = $(".js-modals-container");
return modal.length && tryClickSelector("a.mdl-dismiss", modal); 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
tryClickSelector(".is-shifted-2 .js-tweet-social-proof-back", ".js-modal-panel") ||
tryClickSelector(".is-shifted-1 .js-column-back", ".js-modal-panel") ||
tryCloseModal1() || tryCloseModal1() ||
tryCloseModal2() || 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){
@@ -538,7 +602,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(){
@@ -591,16 +655,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;
@@ -609,7 +675,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);
} }
}); });
}); });
@@ -645,41 +711,67 @@
// 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, .media-preview, .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(".compose-text-container, .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(".account-bio.padding-t--5 { padding-top: 2px !important; }"); // decrease padding on follow 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
addRule(".js-docked-compose .js-drawer-close { margin: 20px 0 0 !important; }"); // fix close drawer button because twitter is fucking incompetent
window.TDGF_reinjectCustomCSS = function(styles){ window.TDGF_reinjectCustomCSS = function(styles){
$("#tweetduck-custom-css").remove(); $("#tweetduck-custom-css").remove();
@@ -709,20 +801,46 @@
} }
// //
// 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);
}).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;
} }
}); });
@@ -735,22 +853,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") ||
@@ -762,6 +871,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.
// //
@@ -787,8 +924,19 @@
} }
// //
// Block: Memory cleanup check and execution. // 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(){ window.TDGF_tryRunCleanup = function(){
// all textareas are empty // all textareas are empty
if ($("textarea").is(function(){ if ($("textarea").is(function(){
@@ -815,11 +963,62 @@
} }
// cleanup // cleanup
window.gc && window.gc(); window.TDGF_reload();
window.location.reload();
return true; 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.
// //
@@ -842,9 +1041,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

@@ -10,23 +10,31 @@
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
addRule("body { overflow: hidden !important; background-color: #1c6399 !important; }"); // remove scrollbar and change background
style.sheet.insertRule("#doc { width: 100%; height: 100%; margin: 0; position: absolute; display: table; }", 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-outer { display: table-cell; vertical-align: middle; }", 0); // center everything addRule(".topbar { display: none !important; }"); // hide top bar
style.sheet.insertRule("#page-container { padding: 0 20px !important; width: 100% !important; box-sizing: border-box !important; }", 0); // center everything
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

@@ -125,18 +125,26 @@
font-size: 23px; font-size: 23px;
} }
#tweetduck-changelog h2 + br {
display: none;
}
#tweetduck-changelog h3 { #tweetduck-changelog h3 {
margin: 0 0 5px 7px; margin: 0 0 5px 7px;
font-size: 18px; font-size: 18px;
} }
#tweetduck-changelog p { #tweetduck-changelog p {
margin: 0 0 2px 30px; margin: 8px 8px 0 6px;
}
#tweetduck-changelog p.li {
margin: 0 8px 2px 30px;
display: list-item; display: list-item;
} }
#tweetduck-changelog p.l2 { #tweetduck-changelog p.l2 {
margin-left: 50px; margin-left: 50px !important;
} }
#tweetduck-changelog a { #tweetduck-changelog a {
@@ -268,14 +276,16 @@
return md.replace(/&/g, "&amp;") return md.replace(/&/g, "&amp;")
.replace(/</g, "&lt;") .replace(/</g, "&lt;")
.replace(/>/g, "&gt;") .replace(/>/g, "&gt;")
.replace(/^##? (.*?)$/gm, "<h2>$1</h2>")
.replace(/^### (.*?)$/gm, "<h3>$1</h3>") .replace(/^### (.*?)$/gm, "<h3>$1</h3>")
.replace(/^- (.*?)$/gm, "<p>$1</p>") .replace(/^- (.*?)$/gm, "<p class='li'>$1</p>")
.replace(/^ - (.*?)$/gm, "<p class='l2'>$1</p>") .replace(/^ - (.*?)$/gm, "<p class='li l2'>$1</p>")
.replace(/\*\*(.*?)\*\*/g, "<strong>$1</strong>") .replace(/\*\*(.*?)\*\*/g, "<strong>$1</strong>")
.replace(/\*(.*?)\*/g, "<em>$1</em>") .replace(/\*(.*?)\*/g, "<em>$1</em>")
.replace(/`(.*?)`/g, "<code>$1</code>") .replace(/`(.*?)`/g, "<code>$1</code>")
.replace(/\[(.*?)\]\((.*?)\)/g, "<a href='$2'>$1</a>") .replace(/\[(.*?)\]\((.*?)\)/g, "<a href='$2' target='_blank'>$1</a>")
.replace(/\n\r?\n/g, "<br>"); .replace(/^([a-z0-9].*?)$/gmi, "<p>$1</p>")
.replace(/\n\r?\n\r?/g, "<br>");
}; };
// //

View File

@@ -88,7 +88,8 @@
<Compile Include="Core\Controls\NumericUpDownEx.cs"> <Compile Include="Core\Controls\NumericUpDownEx.cs">
<SubType>Component</SubType> <SubType>Component</SubType>
</Compile> </Compile>
<Compile Include="Core\Handling\BrowserProcessHandler.cs" /> <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">
@@ -97,8 +98,10 @@
<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\KeyboardHandlerBrowser.cs" />
<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>
@@ -113,8 +116,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>
@@ -141,6 +144,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>
@@ -165,8 +169,9 @@
<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\Utils\BrowserProcesses.cs" /> <Compile Include="Core\Other\Management\BrowserProcesses.cs" />
<Compile Include="Core\Utils\StringUtils.cs" /> <Compile Include="Core\Utils\StringUtils.cs" />
<Compile Include="Core\Utils\TwitterUtils.cs" />
<Compile Include="Data\CombinedFileStream.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" />
@@ -199,19 +204,17 @@
</Compile> </Compile>
<Compile Include="Core\Bridge\CallbackBridge.cs" /> <Compile Include="Core\Bridge\CallbackBridge.cs" />
<Compile Include="Data\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="Data\Serialization\FileSerializer.cs" /> <Compile Include="Data\Serialization\FileSerializer.cs" />
<Compile Include="Data\InjectedHTML.cs" /> <Compile Include="Data\InjectedHTML.cs" />
<Compile Include="Data\Serialization\ISerializedObject.cs" />
<Compile Include="Data\Serialization\ITypeConverter.cs" /> <Compile Include="Data\Serialization\ITypeConverter.cs" />
<Compile Include="Data\Serialization\SingleTypeConverter.cs" /> <Compile Include="Data\Serialization\SingleTypeConverter.cs" />
<Compile Include="Data\TwoKeyDictionary.cs" /> <Compile Include="Data\TwoKeyDictionary.cs" />
<Compile Include="Data\WindowState.cs" /> <Compile Include="Data\WindowState.cs" />
<Compile Include="Core\Utils\MemoryUsageTracker.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">
@@ -296,15 +299,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>
@@ -325,6 +324,7 @@
<None Include="Resources\icon-small.ico" /> <None Include="Resources\icon-small.ico" />
<None Include="Resources\icon-tray-new.ico" /> <None Include="Resources\icon-tray-new.ico" />
<None Include="Resources\icon-tray.ico" /> <None Include="Resources\icon-tray.ico" />
<None Include="Resources\PostBuild.ps1" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Folder Include="Resources\Plugins\" /> <Folder Include="Resources\Plugins\" />
@@ -349,11 +349,18 @@
<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>
<ProjectReference Include="lib\TweetLib.Communication\TweetLib.Communication.csproj">
<Project>{72473763-4b9d-4fb6-a923-9364b2680f06}</Project>
<Name>TweetLib.Communication</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
@@ -371,14 +378,16 @@ 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
mkdir "$(TargetDir)plugins\user\.debug" mkdir "$(TargetDir)plugins\user\.debug"
xcopy "$(ProjectDir)Resources\Plugins\.debug\*" "$(TargetDir)plugins\user\.debug\" /E /Y xcopy "$(ProjectDir)Resources\Plugins\.debug\*" "$(TargetDir)plugins\user\.debug\" /E /Y
)</PostBuildEvent> )
powershell -ExecutionPolicy Unrestricted -File "$(ProjectDir)Resources\PostBuild.ps1" "$(TargetDir)\"</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>
@@ -390,14 +399,18 @@ 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"> <PropertyGroup>
</Target> <PreBuildEvent>powershell Get-Process TweetDuck.Browser -ErrorAction SilentlyContinue ^| Where-Object {$_.Path -eq '$(TargetDir)TweetDuck.Browser.exe'} ^| Stop-Process; Exit 0</PreBuildEvent>
--> </PropertyGroup>
</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,10 @@ 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
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetLib.Communication", "lib\TweetLib.Communication\TweetLib.Communication.csproj", "{72473763-4B9D-4FB6-A923-9364B2680F06}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x86 = Debug|x86 Debug|x86 = Debug|x86
@@ -32,6 +36,14 @@ 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
{72473763-4B9D-4FB6-A923-9364B2680F06}.Debug|x86.ActiveCfg = Debug|x86
{72473763-4B9D-4FB6-A923-9364B2680F06}.Debug|x86.Build.0 = Debug|x86
{72473763-4B9D-4FB6-A923-9364B2680F06}.Release|x86.ActiveCfg = Release|x86
{72473763-4B9D-4FB6-A923-9364B2680F06}.Release|x86.Build.0 = Release|x86
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

View File

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

View File

@@ -1,6 +0,0 @@
del "bin\x86\Release\*.xml"
del "bin\x86\Release\*.pdb"
del "bin\x86\Release\CefSharp.BrowserSubprocess.exe"
del "bin\x86\Release\devtools_resources.pak"
del "bin\x86\Release\d3dcompiler_43.dll"
del "bin\x86\Release\widevinecdmadapter.dll"

View File

@@ -21,7 +21,6 @@ DefaultDirName={pf}\{#MyAppName}
DefaultGroupName={#MyAppName} DefaultGroupName={#MyAppName}
OutputBaseFilename={#MyAppName} OutputBaseFilename={#MyAppName}
VersionInfoVersion={#MyAppVersion} VersionInfoVersion={#MyAppVersion}
LicenseFile=.\Resources\LICENSE
SetupIconFile=.\Resources\icon.ico SetupIconFile=.\Resources\icon.ico
Uninstallable=TDIsUninstallable Uninstallable=TDIsUninstallable
UninstallDisplayName={#MyAppName} UninstallDisplayName={#MyAppName}

View File

@@ -21,7 +21,6 @@ DefaultDirName={sd}\{#MyAppName}
DefaultGroupName={#MyAppName} DefaultGroupName={#MyAppName}
OutputBaseFilename={#MyAppName}.Portable OutputBaseFilename={#MyAppName}.Portable
VersionInfoVersion={#MyAppVersion} VersionInfoVersion={#MyAppVersion}
LicenseFile=.\Resources\LICENSE
SetupIconFile=.\Resources\icon.ico SetupIconFile=.\Resources\icon.ico
Uninstallable=no Uninstallable=no
UsePreviousAppDir=no UsePreviousAppDir=no

View File

@@ -23,7 +23,6 @@ DefaultDirName={pf}\{#MyAppName}
DefaultGroupName={#MyAppName} DefaultGroupName={#MyAppName}
OutputBaseFilename={#MyAppName}.Update OutputBaseFilename={#MyAppName}.Update
VersionInfoVersion={#MyAppVersion} VersionInfoVersion={#MyAppVersion}
LicenseFile=.\Resources\LICENSE
SetupIconFile=.\Resources\icon.ico SetupIconFile=.\Resources\icon.ico
Uninstallable=TDIsUninstallable Uninstallable=TDIsUninstallable
UninstallDisplayName={#MyAppName} UninstallDisplayName={#MyAppName}
@@ -59,6 +58,9 @@ Type: filesandordirs; Name: "{app}\scripts"
Type: filesandordirs; Name: "{localappdata}\{#MyAppName}\Cache" Type: filesandordirs; Name: "{localappdata}\{#MyAppName}\Cache"
Type: filesandordirs; Name: "{localappdata}\{#MyAppName}\GPUCache" Type: filesandordirs; Name: "{localappdata}\{#MyAppName}\GPUCache"
[InstallDelete]
Type: files; Name: "{app}\plugins\official\emoji-keyboard\emoji-instructions.txt"
[Code] [Code]
function TDIsUninstallable: Boolean; forward; function TDIsUninstallable: Boolean; forward;
function TDGetRunArgs(Param: String): String; forward; function TDGetRunArgs(Param: String): String; forward;

View File

@@ -0,0 +1,34 @@
using System;
using TweetLib.Communication.Utils;
namespace TweetLib.Communication{
public static class Comms{
public static void SendMessage(IntPtr hWnd, uint msg, uint wParam, int lParam){
NativeMethods.SendMessage(hWnd, msg, new UIntPtr(wParam), new IntPtr(lParam));
}
public static void SendMessage(IntPtr hWnd, uint msg, UIntPtr wParam, IntPtr lParam){
NativeMethods.SendMessage(hWnd, msg, wParam, lParam);
}
public static void PostMessage(IntPtr hWnd, uint msg, uint wParam, int lParam){
NativeMethods.PostMessage(hWnd, msg, new UIntPtr(wParam), new IntPtr(lParam));
}
public static void PostMessage(IntPtr hWnd, uint msg, UIntPtr wParam, IntPtr lParam){
NativeMethods.PostMessage(hWnd, msg, wParam, lParam);
}
public static void BroadcastMessage(uint msg, uint wParam, int lParam){
NativeMethods.PostMessage(NativeMethods.HWND_BROADCAST, msg, new UIntPtr(wParam), new IntPtr(lParam));
}
public static void BroadcastMessage(uint msg, UIntPtr wParam, IntPtr lParam){
NativeMethods.PostMessage(NativeMethods.HWND_BROADCAST, msg, wParam, lParam);
}
public static uint RegisterMessage(string name){
return NativeMethods.RegisterWindowMessage(name);
}
}
}

View File

@@ -0,0 +1,108 @@
using System;
using System.IO;
using System.IO.Pipes;
using System.Threading;
namespace TweetLib.Communication{
public abstract class DuplexPipe : IDisposable{
private const string Separator = "\x1F";
public static Server CreateServer(){
return new Server();
}
public static Client CreateClient(string token){
int space = token.IndexOf(' ');
return new Client(token.Substring(0, space), token.Substring(space+1));
}
protected readonly PipeStream pipeIn;
protected readonly PipeStream pipeOut;
private readonly Thread readerThread;
private readonly StreamWriter writerStream;
public event EventHandler<PipeReadEventArgs> DataIn;
protected DuplexPipe(PipeStream pipeIn, PipeStream pipeOut){
this.pipeIn = pipeIn;
this.pipeOut = pipeOut;
this.readerThread = new Thread(ReaderThread){
IsBackground = true
};
this.readerThread.Start();
this.writerStream = new StreamWriter(this.pipeOut);
}
private void ReaderThread(){
using(StreamReader read = new StreamReader(pipeIn)){
string data;
while((data = read.ReadLine()) != null){
DataIn?.Invoke(this, new PipeReadEventArgs(data));
}
}
}
public void Write(string key){
writerStream.WriteLine(key);
writerStream.Flush();
}
public void Write(string key, string data){
writerStream.WriteLine(string.Concat(key, Separator, data));
writerStream.Flush();
}
public void Dispose(){
try{
readerThread.Abort();
}catch{
// /shrug
}
pipeIn.Dispose();
writerStream.Dispose();
}
public sealed class Server : DuplexPipe{
private AnonymousPipeServerStream ServerPipeIn => (AnonymousPipeServerStream)pipeIn;
private AnonymousPipeServerStream ServerPipeOut => (AnonymousPipeServerStream)pipeOut;
internal Server() : base(new AnonymousPipeServerStream(PipeDirection.In, HandleInheritability.Inheritable), new AnonymousPipeServerStream(PipeDirection.Out, HandleInheritability.Inheritable)){}
public string GenerateToken(){
return ServerPipeIn.GetClientHandleAsString()+" "+ServerPipeOut.GetClientHandleAsString();
}
public void DisposeToken(){
ServerPipeIn.DisposeLocalCopyOfClientHandle();
ServerPipeOut.DisposeLocalCopyOfClientHandle();
}
}
public sealed class Client : DuplexPipe{
internal Client(string handleOut, string handleIn) : base(new AnonymousPipeClientStream(PipeDirection.In, handleIn), new AnonymousPipeClientStream(PipeDirection.Out, handleOut)){}
}
public sealed class PipeReadEventArgs : EventArgs{
public string Key { get; }
public string Data { get; }
internal PipeReadEventArgs(string line){
int separatorIndex = line.IndexOf(Separator, StringComparison.Ordinal);
if (separatorIndex == -1){
Key = line;
Data = string.Empty;
}
else{
Key = line.Substring(0, separatorIndex);
Data = line.Substring(separatorIndex+1);
}
}
}
}
}

View File

@@ -0,0 +1,35 @@
using System.Reflection;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("TweetDuck Communication Library")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("TweetDuck Communication Library")]
[assembly: AssemblyCopyright("")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("72473763-4b9d-4fb6-a923-9364b2680f06")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{72473763-4B9D-4FB6-A923-9364B2680F06}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>TweetLib.Communication</RootNamespace>
<AssemblyName>TweetLib.Communication</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x86\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<PlatformTarget>x86</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
<OutputPath>bin\x86\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>none</DebugType>
<PlatformTarget>x86</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
</ItemGroup>
<ItemGroup>
<Compile Include="DuplexPipe.cs" />
<Compile Include="Utils\NativeMethods.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Comms.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View File

@@ -1,9 +1,12 @@
using System; using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace TweetDuck.Browser{ namespace TweetLib.Communication.Utils{
static class NativeMethods{ static class NativeMethods{
public static readonly IntPtr HWND_BROADCAST = new IntPtr(0xFFFF); public static readonly IntPtr HWND_BROADCAST = new IntPtr(0xFFFF);
[DllImport("user32.dll")]
public static extern IntPtr SendMessage(IntPtr hWnd, uint msg, UIntPtr wParam, IntPtr lParam);
[DllImport("user32.dll")] [DllImport("user32.dll")]
public static extern bool PostMessage(IntPtr hWnd, uint msg, UIntPtr wParam, IntPtr lParam); public static extern bool PostMessage(IntPtr hWnd, uint msg, UIntPtr wParam, IntPtr lParam);

View File

@@ -1,7 +1,8 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using CefSharp; using CefSharp;
using CefSharp.BrowserSubprocess; using CefSharp.BrowserSubprocess;
using TweetLib.Communication;
namespace TweetDuck.Browser{ namespace TweetDuck.Browser{
static class Program{ static class Program{
@@ -26,7 +27,7 @@ namespace TweetDuck.Browser{
base.OnBrowserCreated(wrapper); base.OnBrowserCreated(wrapper);
using(Process me = Process.GetCurrentProcess()){ using(Process me = Process.GetCurrentProcess()){
NativeMethods.PostMessage(NativeMethods.HWND_BROADCAST, NativeMethods.RegisterWindowMessage("TweetDuckSubProcess"), new UIntPtr((uint)me.Id), new IntPtr(wrapper.BrowserId)); Comms.BroadcastMessage(Comms.RegisterMessage("TweetDuckSubProcess"), (uint)me.Id, wrapper.BrowserId);
} }
} }
} }

View File

@@ -31,5 +31,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers // You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below: // by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")] // [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.1.0")] [assembly: AssemblyVersion("1.1.0.0")]
[assembly: AssemblyFileVersion("1.0.1.0")] [assembly: AssemblyFileVersion("1.1.0.0")]

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup> <PropertyGroup>
@@ -31,10 +31,15 @@
<Reference Include="System" /> <Reference Include="System" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="NativeMethods.cs" />
<Compile Include="Program.cs" /> <Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<ProjectReference Include="..\lib\TweetLib.Communication\TweetLib.Communication.csproj">
<Project>{72473763-4b9d-4fb6-a923-9364b2680f06}</Project>
<Name>TweetLib.Communication</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup> <PropertyGroup>
<PostBuildEvent>call "$(DevEnvDir)..\..\VC\Auxiliary\Build\vcvars32.bat" <PostBuildEvent>call "$(DevEnvDir)..\..\VC\Auxiliary\Build\vcvars32.bat"

View File

@@ -1,33 +0,0 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using TweetDuck.Core.Utils;
using TweetDuck.Data;
namespace UnitTests.Core{
[TestClass]
public class TestCommandLineArgsParser{
[TestMethod]
public void TestEmptyString(){
Assert.AreEqual(0, CommandLineArgsParser.ReadCefArguments("").Count);
Assert.AreEqual(0, CommandLineArgsParser.ReadCefArguments(" ").Count);
}
[TestMethod]
public void TestValidString(){
CommandLineArgs args = CommandLineArgsParser.ReadCefArguments("--aaa --bbb --first-value=123 --SECOND-VALUE=\"a b c d e\"\r\n--ccc");
// cef has no flags, flag arguments have a value of 1
// the processing removes all dashes in front of each key
Assert.AreEqual(5, args.Count);
Assert.IsTrue(args.HasValue("aaa"));
Assert.IsTrue(args.HasValue("bbb"));
Assert.IsTrue(args.HasValue("ccc"));
Assert.IsTrue(args.HasValue("first-value"));
Assert.IsTrue(args.HasValue("second-value"));
Assert.AreEqual("1", args.GetValue("aaa", string.Empty));
Assert.AreEqual("1", args.GetValue("bbb", string.Empty));
Assert.AreEqual("1", args.GetValue("ccc", string.Empty));
Assert.AreEqual("123", args.GetValue("first-value", string.Empty));
Assert.AreEqual("a b c d e", args.GetValue("second-value", string.Empty));
}
}
}

View File

@@ -0,0 +1,45 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using TweetDuck.Core.Utils;
namespace UnitTests.Core{
[TestClass]
public class TestTwitterUtils{
[TestMethod]
public void TestAccountRegex(){
Assert.IsTrue(TwitterUtils.RegexAccount.IsMatch("http://twitter.com/chylexmc"));
Assert.IsTrue(TwitterUtils.RegexAccount.IsMatch("https://twitter.com/chylexmc"));
Assert.IsTrue(TwitterUtils.RegexAccount.IsMatch("http://twitter.com/chylexmc/"));
Assert.IsTrue(TwitterUtils.RegexAccount.IsMatch("https://twitter.com/chylexmc/"));
Assert.AreEqual("chylexmc", TwitterUtils.RegexAccount.Match("http://twitter.com/chylexmc").Groups[1].Value);
Assert.AreEqual("123", TwitterUtils.RegexAccount.Match("http://twitter.com/123").Groups[1].Value);
Assert.AreEqual("_", TwitterUtils.RegexAccount.Match("http://twitter.com/_").Groups[1].Value);
Assert.AreEqual("Abc_123", TwitterUtils.RegexAccount.Match("http://twitter.com/Abc_123").Groups[1].Value);
Assert.AreEqual("Abc_123", TwitterUtils.RegexAccount.Match("https://twitter.com/Abc_123/").Groups[1].Value);
Assert.IsFalse(TwitterUtils.RegexAccount.IsMatch("http://twitter.com/"));
Assert.IsFalse(TwitterUtils.RegexAccount.IsMatch("http://twitter.com/chylexmc/status"));
Assert.IsFalse(TwitterUtils.RegexAccount.IsMatch("http://nottwitter.com/chylexmc"));
Assert.IsFalse(TwitterUtils.RegexAccount.IsMatch("www.twitter.com/chylexmc"));
}
[TestMethod]
public void TestImageQualityLink(){
Assert.AreEqual("https://pbs.twimg.com/profile_images/123", TwitterUtils.GetImageLink("https://pbs.twimg.com/profile_images/123", TwitterUtils.ImageQuality.Default));
Assert.AreEqual("https://pbs.twimg.com/profile_images/123", TwitterUtils.GetImageLink("https://pbs.twimg.com/profile_images/123", TwitterUtils.ImageQuality.Orig));
Assert.AreEqual("https://pbs.twimg.com/profile_images/123.jpg", TwitterUtils.GetImageLink("https://pbs.twimg.com/profile_images/123.jpg", TwitterUtils.ImageQuality.Default));
Assert.AreEqual("https://pbs.twimg.com/profile_images/123.jpg", TwitterUtils.GetImageLink("https://pbs.twimg.com/profile_images/123.jpg", TwitterUtils.ImageQuality.Orig));
Assert.AreEqual("https://pbs.twimg.com/media/123.jpg", TwitterUtils.GetImageLink("https://pbs.twimg.com/media/123.jpg", TwitterUtils.ImageQuality.Default));
Assert.AreEqual("https://pbs.twimg.com/media/123.jpg:orig", TwitterUtils.GetImageLink("https://pbs.twimg.com/media/123.jpg", TwitterUtils.ImageQuality.Orig));
Assert.AreEqual("https://pbs.twimg.com/media/123.jpg:small", TwitterUtils.GetImageLink("https://pbs.twimg.com/media/123.jpg:small", TwitterUtils.ImageQuality.Default));
Assert.AreEqual("https://pbs.twimg.com/media/123.jpg:orig", TwitterUtils.GetImageLink("https://pbs.twimg.com/media/123.jpg:small", TwitterUtils.ImageQuality.Orig));
Assert.AreEqual("https://pbs.twimg.com/media/123.jpg:large", TwitterUtils.GetImageLink("https://pbs.twimg.com/media/123.jpg:large", TwitterUtils.ImageQuality.Default));
Assert.AreEqual("https://pbs.twimg.com/media/123.jpg:orig", TwitterUtils.GetImageLink("https://pbs.twimg.com/media/123.jpg:large", TwitterUtils.ImageQuality.Orig));
}
}
}

View File

@@ -1,5 +1,6 @@
using System; using System;
using System.IO; using System.IO;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using TweetDuck.Data; using TweetDuck.Data;
@@ -139,5 +140,31 @@ namespace UnitTests.Data{
Directory.Delete("cfs_directory", true); Directory.Delete("cfs_directory", true);
} }
[TestMethod]
public void TestLongIdentifierSuccess(){
TestUtils.WriteText("cfs_long_identifier_fail_in", "test");
string identifier = string.Join("", Enumerable.Repeat("x", 255));
using(CombinedFileStream cfs = new CombinedFileStream(TestUtils.WriteFile("cfs_long_identifier_success"))){
cfs.WriteFile(identifier, "cfs_long_identifier_fail_in");
cfs.Flush();
}
using(CombinedFileStream cfs = new CombinedFileStream(TestUtils.ReadFile("cfs_long_identifier_success"))){
Assert.AreEqual(identifier, cfs.ReadFile().Identifier);
}
}
[TestMethod]
[ExpectedException(typeof(ArgumentOutOfRangeException))]
public void TestLongIdentifierFail(){
TestUtils.WriteText("cfs_long_identifier_fail_in", "test");
using(CombinedFileStream cfs = new CombinedFileStream(TestUtils.WriteFile("cfs_long_identifier_fail"))){
cfs.WriteFile(string.Join("", Enumerable.Repeat("x", 256)), "cfs_long_identifier_fail_in");
}
}
} }
} }

View File

@@ -153,5 +153,30 @@ namespace UnitTests.Data{
Assert.IsTrue(args.HasValue("-value")); Assert.IsTrue(args.HasValue("-value"));
Assert.AreEqual("Here is some text!", args.GetValue("-value", string.Empty)); Assert.AreEqual("Here is some text!", args.GetValue("-value", string.Empty));
} }
[TestMethod]
public void TestCefEmptyString(){
Assert.AreEqual(0, CommandLineArgs.ReadCefArguments("").Count);
Assert.AreEqual(0, CommandLineArgs.ReadCefArguments(" ").Count);
}
[TestMethod]
public void TestCefValidString(){
CommandLineArgs args = CommandLineArgs.ReadCefArguments("--aaa --bbb --first-value=123 --SECOND-VALUE=\"a b c d e\"\r\n--ccc");
// cef has no flags, flag arguments have a value of 1
// the processing removes all dashes in front of each key
Assert.AreEqual(5, args.Count);
Assert.IsTrue(args.HasValue("aaa"));
Assert.IsTrue(args.HasValue("bbb"));
Assert.IsTrue(args.HasValue("ccc"));
Assert.IsTrue(args.HasValue("first-value"));
Assert.IsTrue(args.HasValue("second-value"));
Assert.AreEqual("1", args.GetValue("aaa", string.Empty));
Assert.AreEqual("1", args.GetValue("bbb", string.Empty));
Assert.AreEqual("1", args.GetValue("ccc", string.Empty));
Assert.AreEqual("123", args.GetValue("first-value", string.Empty));
Assert.AreEqual("a b c d e", args.GetValue("second-value", string.Empty));
}
} }
} }

View File

@@ -10,16 +10,12 @@ namespace UnitTests.Data{
A, B, C, D, E A, B, C, D, E
} }
private class SerializationTestBasic : ISerializedObject{ private class SerializationTestBasic{
public bool TestBool { get; set; } public bool TestBool { get; set; }
public int TestInt { get; set; } public int TestInt { get; set; }
public string TestString { get; set; } public string TestString { get; set; }
public string TestStringNull { get; set; } public string TestStringNull { get; set; }
public TestEnum TestEnum { get; set; } public TestEnum TestEnum { get; set; }
bool ISerializedObject.OnReadUnknownProperty(string property, string value){
return false;
}
} }
[TestMethod] [TestMethod]

View File

@@ -48,10 +48,10 @@
</Choose> </Choose>
<ItemGroup> <ItemGroup>
<Compile Include="Core\TestStringUtils.cs" /> <Compile Include="Core\TestStringUtils.cs" />
<Compile Include="Core\TestTwitterUtils.cs" />
<Compile Include="Data\TestCombinedFileStream.cs" /> <Compile Include="Data\TestCombinedFileStream.cs" />
<Compile Include="Core\TestBrowserUtils.cs" /> <Compile Include="Core\TestBrowserUtils.cs" />
<Compile Include="Data\TestCommandLineArgs.cs" /> <Compile Include="Data\TestCommandLineArgs.cs" />
<Compile Include="Core\TestCommandLineArgsParser.cs" />
<Compile Include="Data\TestFileSerializer.cs" /> <Compile Include="Data\TestFileSerializer.cs" />
<Compile Include="Data\TestInjectedHTML.cs" /> <Compile Include="Data\TestInjectedHTML.cs" />
<Compile Include="Data\TestTwoKeyDictionary.cs" /> <Compile Include="Data\TestTwoKeyDictionary.cs" />

View File

@@ -0,0 +1,17 @@
using System.ComponentModel;
using System.Windows.Forms;
using WMPLib;
namespace TweetDuck.Video.Controls{
[DesignTimeVisible(true)]
[Clsid("{6bf52a52-394a-11d3-b153-00c04f79faa6}")]
class ControlWMP : AxHost{
public WindowsMediaPlayer Ocx { get; private set; }
public ControlWMP() : base("6bf52a52-394a-11d3-b153-00c04f79faa6"){}
protected override void AttachInterfaces(){
Ocx = (WindowsMediaPlayer)GetOcx();
}
}
}

View File

@@ -0,0 +1,57 @@
using System.Drawing;
using System.Windows.Forms;
namespace TweetDuck.Video.Controls{
sealed class SeekBar : ProgressBar{
private readonly SolidBrush brushFore;
private readonly SolidBrush brushHover;
private readonly SolidBrush brushOverlap;
public SeekBar(){
brushFore = new SolidBrush(Color.White);
brushHover = new SolidBrush(Color.White);
brushOverlap = new SolidBrush(Color.White);
SetStyle(ControlStyles.UserPaint, true);
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
}
protected override void OnPaint(PaintEventArgs e){
if (brushFore.Color != ForeColor){
brushFore.Color = ForeColor;
brushHover.Color = Color.FromArgb(128, ForeColor);
brushOverlap.Color = Color.FromArgb(80+ForeColor.R*11/16, 80+ForeColor.G*11/16, 80+ForeColor.B*11/16);
}
Rectangle rect = e.ClipRectangle;
Point cursor = PointToClient(Cursor.Position);
int width = rect.Width;
int progress = (int)(width*((double)Value/Maximum));
rect.Width = progress;
e.Graphics.FillRectangle(brushFore, rect);
if (cursor.X >= 0 && cursor.Y >= 0 && cursor.X <= width && cursor.Y <= rect.Height){
if (progress >= cursor.X){
rect.Width = cursor.X;
e.Graphics.FillRectangle(brushOverlap, rect);
}
else{
rect.X = progress;
rect.Width = cursor.X-rect.X;
e.Graphics.FillRectangle(brushHover, rect);
}
}
}
protected override void Dispose(bool disposing){
base.Dispose(disposing);
if (disposing){
brushFore.Dispose();
brushHover.Dispose();
brushOverlap.Dispose();
}
}
}
}

View File

@@ -0,0 +1,142 @@
namespace TweetDuck.Video {
partial class FormPlayer {
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing) {
if (disposing && (components != null)) {
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent() {
this.components = new System.ComponentModel.Container();
this.timerSync = new System.Windows.Forms.Timer(this.components);
this.trackBarVolume = new System.Windows.Forms.TrackBar();
this.tablePanel = new System.Windows.Forms.TableLayoutPanel();
this.progressSeek = new TweetDuck.Video.Controls.SeekBar();
this.labelTime = new System.Windows.Forms.Label();
this.timerData = new System.Windows.Forms.Timer(this.components);
((System.ComponentModel.ISupportInitialize)(this.trackBarVolume)).BeginInit();
this.tablePanel.SuspendLayout();
this.SuspendLayout();
//
// timerSync
//
this.timerSync.Interval = 15;
this.timerSync.Tick += new System.EventHandler(this.timerSync_Tick);
//
// trackBarVolume
//
this.trackBarVolume.AutoSize = false;
this.trackBarVolume.BackColor = System.Drawing.SystemColors.Control;
this.trackBarVolume.Dock = System.Windows.Forms.DockStyle.Fill;
this.trackBarVolume.Location = new System.Drawing.Point(158, 5);
this.trackBarVolume.Margin = new System.Windows.Forms.Padding(3, 5, 3, 3);
this.trackBarVolume.Maximum = 100;
this.trackBarVolume.Name = "trackBarVolume";
this.trackBarVolume.Size = new System.Drawing.Size(94, 26);
this.trackBarVolume.SmallChange = 5;
this.trackBarVolume.TabIndex = 2;
this.trackBarVolume.TickFrequency = 10;
this.trackBarVolume.TickStyle = System.Windows.Forms.TickStyle.None;
this.trackBarVolume.Value = 50;
this.trackBarVolume.ValueChanged += new System.EventHandler(this.trackBarVolume_ValueChanged);
this.trackBarVolume.MouseDown += new System.Windows.Forms.MouseEventHandler(this.trackBarVolume_MouseDown);
this.trackBarVolume.MouseUp += new System.Windows.Forms.MouseEventHandler(this.trackBarVolume_MouseUp);
//
// tablePanel
//
this.tablePanel.BackColor = System.Drawing.SystemColors.Control;
this.tablePanel.ColumnCount = 3;
this.tablePanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));
this.tablePanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 75F));
this.tablePanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 100F));
this.tablePanel.Controls.Add(this.trackBarVolume, 2, 0);
this.tablePanel.Controls.Add(this.progressSeek, 0, 0);
this.tablePanel.Controls.Add(this.labelTime, 1, 0);
this.tablePanel.Dock = System.Windows.Forms.DockStyle.Bottom;
this.tablePanel.Location = new System.Drawing.Point(0, 86);
this.tablePanel.Name = "tablePanel";
this.tablePanel.RowCount = 1;
this.tablePanel.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F));
this.tablePanel.Size = new System.Drawing.Size(255, 34);
this.tablePanel.TabIndex = 1;
//
// progressSeek
//
this.progressSeek.BackColor = System.Drawing.Color.White;
this.progressSeek.Dock = System.Windows.Forms.DockStyle.Fill;
this.progressSeek.ForeColor = System.Drawing.Color.LimeGreen;
this.progressSeek.Location = new System.Drawing.Point(9, 10);
this.progressSeek.Margin = new System.Windows.Forms.Padding(9, 10, 9, 11);
this.progressSeek.Maximum = 5000;
this.progressSeek.Name = "progressSeek";
this.progressSeek.Size = new System.Drawing.Size(62, 13);
this.progressSeek.Style = System.Windows.Forms.ProgressBarStyle.Continuous;
this.progressSeek.TabIndex = 0;
this.progressSeek.MouseDown += new System.Windows.Forms.MouseEventHandler(this.progressSeek_MouseDown);
//
// labelTime
//
this.labelTime.Dock = System.Windows.Forms.DockStyle.Fill;
this.labelTime.Location = new System.Drawing.Point(80, 2);
this.labelTime.Margin = new System.Windows.Forms.Padding(0, 2, 0, 5);
this.labelTime.Name = "labelTime";
this.labelTime.Size = new System.Drawing.Size(75, 27);
this.labelTime.TabIndex = 1;
this.labelTime.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
//
// timerData
//
this.timerData.Interval = 500;
this.timerData.Tick += new System.EventHandler(this.timerData_Tick);
//
// FormPlayer
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.BackColor = System.Drawing.Color.Black;
this.ClientSize = new System.Drawing.Size(255, 120);
this.ControlBox = false;
this.Controls.Add(this.tablePanel);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
this.Location = new System.Drawing.Point(-32000, -32000);
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "FormPlayer";
this.ShowIcon = false;
this.ShowInTaskbar = false;
this.StartPosition = System.Windows.Forms.FormStartPosition.Manual;
this.Text = "TweetDuck Video";
this.Load += new System.EventHandler(this.FormPlayer_Load);
((System.ComponentModel.ISupportInitialize)(this.trackBarVolume)).EndInit();
this.tablePanel.ResumeLayout(false);
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.Timer timerSync;
private System.Windows.Forms.TrackBar trackBarVolume;
private System.Windows.Forms.TableLayoutPanel tablePanel;
private Controls.SeekBar progressSeek;
private System.Windows.Forms.Label labelTime;
private System.Windows.Forms.Timer timerData;
}
}

View File

@@ -0,0 +1,249 @@
using System;
using System.Drawing;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using TweetDuck.Video.Controls;
using TweetLib.Communication;
using WMPLib;
namespace TweetDuck.Video{
partial class FormPlayer : Form{
protected override bool ShowWithoutActivation => true;
private readonly IntPtr ownerHandle;
private readonly string videoUrl;
private readonly DuplexPipe pipe;
private readonly ControlWMP player;
private bool wasCursorInside;
private bool isPaused;
private bool isDragging;
private WindowsMediaPlayer Player => player.Ocx;
public FormPlayer(IntPtr handle, int volume, string url, string token){
InitializeComponent();
this.ownerHandle = handle;
this.videoUrl = url;
this.pipe = DuplexPipe.CreateClient(token);
this.pipe.DataIn += pipe_DataIn;
player = new ControlWMP{
Dock = DockStyle.Fill
};
player.BeginInit();
Controls.Add(player);
player.EndInit();
Player.enableContextMenu = false;
Player.uiMode = "none";
Player.settings.autoStart = false;
Player.settings.enableErrorDialogs = false;
Player.settings.setMode("loop", true);
Player.PlayStateChange += player_PlayStateChange;
Player.MediaError += player_MediaError;
trackBarVolume.Value = volume; // changes player volume too if non-default
Application.AddMessageFilter(new MessageFilter(this));
}
// Events
private void FormPlayer_Load(object sender, EventArgs e){
Player.URL = videoUrl;
}
private void pipe_DataIn(object sender, DuplexPipe.PipeReadEventArgs e){
switch(e.Key){
case "pause":
TogglePause();
break;
case "die":
timerSync.Stop();
Visible = false;
pipe.Write("rip");
Close();
break;
}
}
private void player_PlayStateChange(int newState){
WMPPlayState state = (WMPPlayState)newState;
if (state == WMPPlayState.wmppsReady){
Player.controls.play();
}
else if (state == WMPPlayState.wmppsPlaying){
Player.PlayStateChange -= player_PlayStateChange;
timerSync.Start();
NativeMethods.SetWindowOwner(Handle, ownerHandle);
Cursor.Current = Cursors.Default;
}
}
private void player_MediaError(object pMediaObject){
Console.Out.WriteLine(((IWMPMedia2)pMediaObject).Error.errorDescription);
Marshal.ReleaseComObject(pMediaObject);
Environment.Exit(Program.CODE_MEDIA_ERROR);
}
private void timerSync_Tick(object sender, EventArgs e){
if (NativeMethods.GetWindowRect(ownerHandle, out NativeMethods.RECT rect)){
int width = rect.Right-rect.Left+1;
int height = rect.Bottom-rect.Top+1;
IWMPMedia media = Player.currentMedia;
IWMPControls controls = Player.controls;
bool isCursorInside = ClientRectangle.Contains(PointToClient(Cursor.Position));
ClientSize = new Size(Math.Min(media.imageSourceWidth, width*3/4), Math.Min(media.imageSourceHeight, height*3/4));
Location = new Point(rect.Left+(width-ClientSize.Width)/2, rect.Top+(height-ClientSize.Height+SystemInformation.CaptionHeight)/2);
tablePanel.Visible = isCursorInside || isDragging;
if (tablePanel.Visible){
labelTime.Text = $"{controls.currentPositionString} / {media.durationString}";
int value = (int)Math.Round(progressSeek.Maximum*controls.currentPosition/media.duration);
if (value >= progressSeek.Maximum){
progressSeek.Value = progressSeek.Maximum;
progressSeek.Value = progressSeek.Maximum-1;
progressSeek.Value = progressSeek.Maximum;
}
else{
progressSeek.Value = value+1;
progressSeek.Value = value;
}
}
if (controls.currentPosition > media.duration){ // pausing near the end of the video causes WMP to play beyond the end of the video wtf
controls.stop();
controls.currentPosition = 0;
controls.play();
}
if (isCursorInside && !wasCursorInside){
wasCursorInside = true;
}
else if (!isCursorInside && wasCursorInside){
wasCursorInside = false;
if (!Player.fullScreen){
NativeMethods.SetForegroundWindow(ownerHandle);
}
}
Marshal.ReleaseComObject(media);
Marshal.ReleaseComObject(controls);
}
else{
Environment.Exit(Program.CODE_OWNER_GONE);
}
}
private void timerData_Tick(object sender, EventArgs e){
timerData.Stop();
pipe.Write("vol", trackBarVolume.Value.ToString(CultureInfo.InvariantCulture));
}
private void progressSeek_MouseDown(object sender, MouseEventArgs e){
if (e.Button == MouseButtons.Left){
IWMPMedia media = Player.currentMedia;
IWMPControls controls = Player.controls;
controls.currentPosition = media.duration*progressSeek.PointToClient(Cursor.Position).X/progressSeek.Width;
Marshal.ReleaseComObject(media);
Marshal.ReleaseComObject(controls);
}
}
private void trackBarVolume_ValueChanged(object sender, EventArgs e){
IWMPSettings settings = Player.settings;
settings.volume = trackBarVolume.Value;
Marshal.ReleaseComObject(settings);
if (timerSync.Enabled){
timerData.Stop();
timerData.Start();
}
}
private void trackBarVolume_MouseDown(object sender, MouseEventArgs e){
isDragging = true;
}
private void trackBarVolume_MouseUp(object sender, MouseEventArgs e){
isDragging = false;
}
// Controls & messages
private void TogglePause(){
IWMPControls controls = Player.controls;
if (isPaused){
controls.play();
}
else{
controls.pause();
}
isPaused = !isPaused;
Marshal.ReleaseComObject(controls);
}
internal class MessageFilter : IMessageFilter{
private readonly FormPlayer form;
private bool IsCursorOverVideo{
get{
Point cursor = form.PointToClient(Cursor.Position);
return cursor.Y < form.tablePanel.Location.Y;
}
}
public MessageFilter(FormPlayer form){
this.form = form;
}
bool IMessageFilter.PreFilterMessage(ref Message m){
if (m.Msg == 0x0201){ // WM_LBUTTONDOWN
if (IsCursorOverVideo){
form.TogglePause();
return true;
}
}
else if (m.Msg == 0x0203){ // WM_LBUTTONDBLCLK
if (IsCursorOverVideo){
form.TogglePause();
form.Player.fullScreen = !form.Player.fullScreen;
return true;
}
}
else if (m.Msg == 0x0100 && m.WParam.ToInt32() == 0x20){ // WM_KEYDOWN, VK_SPACE
form.TogglePause();
return true;
}
else if (m.Msg == 0x020B && ((m.WParam.ToInt32() >> 16) & 0xFFFF) == 1){ // WM_XBUTTONDOWN
NativeMethods.SetForegroundWindow(form.ownerHandle);
Environment.Exit(Program.CODE_USER_REQUESTED);
}
return false;
}
}
}
}

View File

@@ -0,0 +1,38 @@
using System;
using System.Runtime.InteropServices;
namespace TweetDuck.Video{
static class NativeMethods{
private const int GWL_HWNDPARENT = -8;
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
[DllImport("user32.dll")]
private static extern int SetWindowLong(IntPtr hWnd, int nIndex, IntPtr dwNewLong);
[DllImport("user32.dll")]
public static extern bool SetForegroundWindow(IntPtr hWnd);
[StructLayout(LayoutKind.Sequential)]
public struct RECT{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
public static void SetWindowOwner(IntPtr child, IntPtr owner){
SetWindowLong(child, GWL_HWNDPARENT, owner);
/*
* "You must not call SetWindowLong with the GWL_HWNDPARENT index to change the parent of a child window"
*
* ...which I'm not sure they're saying because this is completely unsupported and causes demons to come out of sewers
* ...or because GWL_HWNDPARENT actually changes the OWNER and is therefore NOT changing the parent of a child window
*
* ...so technically, this is following the documentation to the word.
*/
}
}
}

View File

@@ -0,0 +1,45 @@
using System;
using System.Globalization;
using System.Windows.Forms;
using TweetLib.Communication;
namespace TweetDuck.Video{
static class Program{
// referenced in VideoPlayer
// set by task manager -- public const int CODE_PROCESS_KILLED = 1;
public const int CODE_INVALID_ARGS = 2;
public const int CODE_LAUNCH_FAIL = 3;
public const int CODE_MEDIA_ERROR = 4;
public const int CODE_OWNER_GONE = 5;
public const int CODE_USER_REQUESTED = 6;
[STAThread]
private static int Main(string[] args){
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
IntPtr ownerHandle;
int defaultVolume;
string videoUrl;
string pipeToken;
try{
ownerHandle = new IntPtr(int.Parse(args[0], NumberStyles.Integer, CultureInfo.InvariantCulture));
defaultVolume = int.Parse(args[1], NumberStyles.Integer, CultureInfo.InvariantCulture);
videoUrl = new Uri(args[2], UriKind.Absolute).AbsoluteUri;
pipeToken = args[3];
}catch{
return CODE_INVALID_ARGS;
}
try{
Application.Run(new FormPlayer(ownerHandle, defaultVolume, videoUrl, pipeToken));
}catch(Exception e){
Console.Out.WriteLine(e.Message);
return CODE_LAUNCH_FAIL;
}
return 0;
}
}
}

View File

@@ -0,0 +1,35 @@
using System.Reflection;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("TweetDuck Video")]
[assembly: AssemblyDescription("TweetDuck Video")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("TweetDuck.Video")]
[assembly: AssemblyCopyright("")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("278b2d11-402d-44b6-b6a1-8fa67db65565")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -0,0 +1,84 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{278B2D11-402D-44B6-B6A1-8FA67DB65565}</ProjectGuid>
<OutputType>WinExe</OutputType>
<RootNamespace>TweetDuck.Video</RootNamespace>
<AssemblyName>TweetDuck.Video</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<ResolveComReferenceSilent>True</ResolveComReferenceSilent>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x86\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<PlatformTarget>x86</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
<OutputPath>bin\x86\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>none</DebugType>
<PlatformTarget>x86</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<Prefer32Bit>true</Prefer32Bit>
<GenerateSerializationAssemblies>Auto</GenerateSerializationAssemblies>
</PropertyGroup>
<PropertyGroup>
<ApplicationIcon>Resources\icon.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Drawing" />
<Reference Include="System.Windows.Forms" />
</ItemGroup>
<ItemGroup>
<Compile Include="Controls\ControlWMP.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="Controls\SeekBar.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="FormPlayer.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="FormPlayer.Designer.cs">
<DependentUpon>FormPlayer.cs</DependentUpon>
</Compile>
<Compile Include="NativeMethods.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<COMReference Include="WMPLib">
<Guid>{6BF52A50-394A-11D3-B153-00C04F79FAA6}</Guid>
<VersionMajor>1</VersionMajor>
<VersionMinor>0</VersionMinor>
<Lcid>0</Lcid>
<WrapperTool>tlbimp</WrapperTool>
<Isolated>False</Isolated>
<EmbedInteropTypes>True</EmbedInteropTypes>
</COMReference>
</ItemGroup>
<ItemGroup>
<Content Include="Resources\icon.ico" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\lib\TweetLib.Communication\TweetLib.Communication.csproj">
<Project>{72473763-4b9d-4fb6-a923-9364b2680f06}</Project>
<Name>TweetLib.Windows</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>